##// END OF EJS Templates
landing-refs: create helpers for landing ref to make clear indication about type/name
marcink -
r4370:ef6d7bca stable
parent child Browse files
Show More
@@ -1,1070 +1,1070 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_rev[1]
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)
184 commit_id=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 @pytest.mark.parametrize('arch_ext',[
545 @pytest.mark.parametrize('arch_ext',[
546 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
546 'tar', 'rar', 'x', '..ax', '.zipz', 'tar.gz.tar'])
547 def test_archival_wrong_ext(self, backend, arch_ext):
547 def test_archival_wrong_ext(self, backend, arch_ext):
548 backend.enable_downloads()
548 backend.enable_downloads()
549 commit = backend.repo.get_commit(commit_idx=173)
549 commit = backend.repo.get_commit(commit_idx=173)
550
550
551 fname = commit.raw_id + '.' + arch_ext
551 fname = commit.raw_id + '.' + arch_ext
552
552
553 response = self.app.get(
553 response = self.app.get(
554 route_path('repo_archivefile',
554 route_path('repo_archivefile',
555 repo_name=backend.repo_name,
555 repo_name=backend.repo_name,
556 fname=fname))
556 fname=fname))
557 response.mustcontain(
557 response.mustcontain(
558 'Unknown archive type for: `{}`'.format(fname))
558 'Unknown archive type for: `{}`'.format(fname))
559
559
560 @pytest.mark.parametrize('commit_id', [
560 @pytest.mark.parametrize('commit_id', [
561 '00x000000', 'tar', 'wrong', '@$@$42413232', '232dffcd'])
561 '00x000000', 'tar', 'wrong', '@$@$42413232', '232dffcd'])
562 def test_archival_wrong_commit_id(self, backend, commit_id):
562 def test_archival_wrong_commit_id(self, backend, commit_id):
563 backend.enable_downloads()
563 backend.enable_downloads()
564 fname = '%s.zip' % commit_id
564 fname = '%s.zip' % commit_id
565
565
566 response = self.app.get(
566 response = self.app.get(
567 route_path('repo_archivefile',
567 route_path('repo_archivefile',
568 repo_name=backend.repo_name,
568 repo_name=backend.repo_name,
569 fname=fname))
569 fname=fname))
570 response.mustcontain('Unknown commit_id')
570 response.mustcontain('Unknown commit_id')
571
571
572
572
573 @pytest.mark.usefixtures("app")
573 @pytest.mark.usefixtures("app")
574 class TestFilesDiff(object):
574 class TestFilesDiff(object):
575
575
576 @pytest.mark.parametrize("diff", ['diff', 'download', 'raw'])
576 @pytest.mark.parametrize("diff", ['diff', 'download', 'raw'])
577 def test_file_full_diff(self, backend, diff):
577 def test_file_full_diff(self, backend, diff):
578 commit1 = backend.repo.get_commit(commit_idx=-1)
578 commit1 = backend.repo.get_commit(commit_idx=-1)
579 commit2 = backend.repo.get_commit(commit_idx=-2)
579 commit2 = backend.repo.get_commit(commit_idx=-2)
580
580
581 response = self.app.get(
581 response = self.app.get(
582 route_path('repo_files_diff',
582 route_path('repo_files_diff',
583 repo_name=backend.repo_name,
583 repo_name=backend.repo_name,
584 f_path='README'),
584 f_path='README'),
585 params={
585 params={
586 'diff1': commit2.raw_id,
586 'diff1': commit2.raw_id,
587 'diff2': commit1.raw_id,
587 'diff2': commit1.raw_id,
588 'fulldiff': '1',
588 'fulldiff': '1',
589 'diff': diff,
589 'diff': diff,
590 })
590 })
591
591
592 if diff == 'diff':
592 if diff == 'diff':
593 # use redirect since this is OLD view redirecting to compare page
593 # use redirect since this is OLD view redirecting to compare page
594 response = response.follow()
594 response = response.follow()
595
595
596 # It's a symlink to README.rst
596 # It's a symlink to README.rst
597 response.mustcontain('README.rst')
597 response.mustcontain('README.rst')
598 response.mustcontain('No newline at end of file')
598 response.mustcontain('No newline at end of file')
599
599
600 def test_file_binary_diff(self, backend):
600 def test_file_binary_diff(self, backend):
601 commits = [
601 commits = [
602 {'message': 'First commit'},
602 {'message': 'First commit'},
603 {'message': 'Commit with binary',
603 {'message': 'Commit with binary',
604 'added': [nodes.FileNode('file.bin', content='\0BINARY\0')]},
604 'added': [nodes.FileNode('file.bin', content='\0BINARY\0')]},
605 ]
605 ]
606 repo = backend.create_repo(commits=commits)
606 repo = backend.create_repo(commits=commits)
607
607
608 response = self.app.get(
608 response = self.app.get(
609 route_path('repo_files_diff',
609 route_path('repo_files_diff',
610 repo_name=backend.repo_name,
610 repo_name=backend.repo_name,
611 f_path='file.bin'),
611 f_path='file.bin'),
612 params={
612 params={
613 'diff1': repo.get_commit(commit_idx=0).raw_id,
613 'diff1': repo.get_commit(commit_idx=0).raw_id,
614 'diff2': repo.get_commit(commit_idx=1).raw_id,
614 'diff2': repo.get_commit(commit_idx=1).raw_id,
615 'fulldiff': '1',
615 'fulldiff': '1',
616 'diff': 'diff',
616 'diff': 'diff',
617 })
617 })
618 # use redirect since this is OLD view redirecting to compare page
618 # use redirect since this is OLD view redirecting to compare page
619 response = response.follow()
619 response = response.follow()
620 response.mustcontain('Collapse 1 commit')
620 response.mustcontain('Collapse 1 commit')
621 file_changes = (1, 0, 0)
621 file_changes = (1, 0, 0)
622
622
623 compare_page = ComparePage(response)
623 compare_page = ComparePage(response)
624 compare_page.contains_change_summary(*file_changes)
624 compare_page.contains_change_summary(*file_changes)
625
625
626 if backend.alias == 'svn':
626 if backend.alias == 'svn':
627 response.mustcontain('new file 10644')
627 response.mustcontain('new file 10644')
628 # TODO(marcink): SVN doesn't yet detect binary changes
628 # TODO(marcink): SVN doesn't yet detect binary changes
629 else:
629 else:
630 response.mustcontain('new file 100644')
630 response.mustcontain('new file 100644')
631 response.mustcontain('binary diff hidden')
631 response.mustcontain('binary diff hidden')
632
632
633 def test_diff_2way(self, backend):
633 def test_diff_2way(self, backend):
634 commit1 = backend.repo.get_commit(commit_idx=-1)
634 commit1 = backend.repo.get_commit(commit_idx=-1)
635 commit2 = backend.repo.get_commit(commit_idx=-2)
635 commit2 = backend.repo.get_commit(commit_idx=-2)
636 response = self.app.get(
636 response = self.app.get(
637 route_path('repo_files_diff_2way_redirect',
637 route_path('repo_files_diff_2way_redirect',
638 repo_name=backend.repo_name,
638 repo_name=backend.repo_name,
639 f_path='README'),
639 f_path='README'),
640 params={
640 params={
641 'diff1': commit2.raw_id,
641 'diff1': commit2.raw_id,
642 'diff2': commit1.raw_id,
642 'diff2': commit1.raw_id,
643 })
643 })
644 # use redirect since this is OLD view redirecting to compare page
644 # use redirect since this is OLD view redirecting to compare page
645 response = response.follow()
645 response = response.follow()
646
646
647 # It's a symlink to README.rst
647 # It's a symlink to README.rst
648 response.mustcontain('README.rst')
648 response.mustcontain('README.rst')
649 response.mustcontain('No newline at end of file')
649 response.mustcontain('No newline at end of file')
650
650
651 def test_requires_one_commit_id(self, backend, autologin_user):
651 def test_requires_one_commit_id(self, backend, autologin_user):
652 response = self.app.get(
652 response = self.app.get(
653 route_path('repo_files_diff',
653 route_path('repo_files_diff',
654 repo_name=backend.repo_name,
654 repo_name=backend.repo_name,
655 f_path='README.rst'),
655 f_path='README.rst'),
656 status=400)
656 status=400)
657 response.mustcontain(
657 response.mustcontain(
658 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.')
658 'Need query parameter', 'diff1', 'diff2', 'to generate a diff.')
659
659
660 def test_returns_no_files_if_file_does_not_exist(self, vcsbackend):
660 def test_returns_no_files_if_file_does_not_exist(self, vcsbackend):
661 repo = vcsbackend.repo
661 repo = vcsbackend.repo
662 response = self.app.get(
662 response = self.app.get(
663 route_path('repo_files_diff',
663 route_path('repo_files_diff',
664 repo_name=repo.name,
664 repo_name=repo.name,
665 f_path='does-not-exist-in-any-commit'),
665 f_path='does-not-exist-in-any-commit'),
666 params={
666 params={
667 'diff1': repo[0].raw_id,
667 'diff1': repo[0].raw_id,
668 'diff2': repo[1].raw_id
668 'diff2': repo[1].raw_id
669 })
669 })
670
670
671 response = response.follow()
671 response = response.follow()
672 response.mustcontain('No files')
672 response.mustcontain('No files')
673
673
674 def test_returns_redirect_if_file_not_changed(self, backend):
674 def test_returns_redirect_if_file_not_changed(self, backend):
675 commit = backend.repo.get_commit(commit_idx=-1)
675 commit = backend.repo.get_commit(commit_idx=-1)
676 response = self.app.get(
676 response = self.app.get(
677 route_path('repo_files_diff_2way_redirect',
677 route_path('repo_files_diff_2way_redirect',
678 repo_name=backend.repo_name,
678 repo_name=backend.repo_name,
679 f_path='README'),
679 f_path='README'),
680 params={
680 params={
681 'diff1': commit.raw_id,
681 'diff1': commit.raw_id,
682 'diff2': commit.raw_id,
682 'diff2': commit.raw_id,
683 })
683 })
684
684
685 response = response.follow()
685 response = response.follow()
686 response.mustcontain('No files')
686 response.mustcontain('No files')
687 response.mustcontain('No commits in this compare')
687 response.mustcontain('No commits in this compare')
688
688
689 def test_supports_diff_to_different_path_svn(self, backend_svn):
689 def test_supports_diff_to_different_path_svn(self, backend_svn):
690 #TODO: check this case
690 #TODO: check this case
691 return
691 return
692
692
693 repo = backend_svn['svn-simple-layout'].scm_instance()
693 repo = backend_svn['svn-simple-layout'].scm_instance()
694 commit_id_1 = '24'
694 commit_id_1 = '24'
695 commit_id_2 = '26'
695 commit_id_2 = '26'
696
696
697 response = self.app.get(
697 response = self.app.get(
698 route_path('repo_files_diff',
698 route_path('repo_files_diff',
699 repo_name=backend_svn.repo_name,
699 repo_name=backend_svn.repo_name,
700 f_path='trunk/example.py'),
700 f_path='trunk/example.py'),
701 params={
701 params={
702 'diff1': 'tags/v0.2/example.py@' + commit_id_1,
702 'diff1': 'tags/v0.2/example.py@' + commit_id_1,
703 'diff2': commit_id_2,
703 'diff2': commit_id_2,
704 })
704 })
705
705
706 response = response.follow()
706 response = response.follow()
707 response.mustcontain(
707 response.mustcontain(
708 # diff contains this
708 # diff contains this
709 "Will print out a useful message on invocation.")
709 "Will print out a useful message on invocation.")
710
710
711 # Note: Expecting that we indicate the user what's being compared
711 # Note: Expecting that we indicate the user what's being compared
712 response.mustcontain("trunk/example.py")
712 response.mustcontain("trunk/example.py")
713 response.mustcontain("tags/v0.2/example.py")
713 response.mustcontain("tags/v0.2/example.py")
714
714
715 def test_show_rev_redirects_to_svn_path(self, backend_svn):
715 def test_show_rev_redirects_to_svn_path(self, backend_svn):
716 #TODO: check this case
716 #TODO: check this case
717 return
717 return
718
718
719 repo = backend_svn['svn-simple-layout'].scm_instance()
719 repo = backend_svn['svn-simple-layout'].scm_instance()
720 commit_id = repo[-1].raw_id
720 commit_id = repo[-1].raw_id
721
721
722 response = self.app.get(
722 response = self.app.get(
723 route_path('repo_files_diff',
723 route_path('repo_files_diff',
724 repo_name=backend_svn.repo_name,
724 repo_name=backend_svn.repo_name,
725 f_path='trunk/example.py'),
725 f_path='trunk/example.py'),
726 params={
726 params={
727 'diff1': 'branches/argparse/example.py@' + commit_id,
727 'diff1': 'branches/argparse/example.py@' + commit_id,
728 'diff2': commit_id,
728 'diff2': commit_id,
729 },
729 },
730 status=302)
730 status=302)
731 response = response.follow()
731 response = response.follow()
732 assert response.headers['Location'].endswith(
732 assert response.headers['Location'].endswith(
733 'svn-svn-simple-layout/files/26/branches/argparse/example.py')
733 'svn-svn-simple-layout/files/26/branches/argparse/example.py')
734
734
735 def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn):
735 def test_show_rev_and_annotate_redirects_to_svn_path(self, backend_svn):
736 #TODO: check this case
736 #TODO: check this case
737 return
737 return
738
738
739 repo = backend_svn['svn-simple-layout'].scm_instance()
739 repo = backend_svn['svn-simple-layout'].scm_instance()
740 commit_id = repo[-1].raw_id
740 commit_id = repo[-1].raw_id
741 response = self.app.get(
741 response = self.app.get(
742 route_path('repo_files_diff',
742 route_path('repo_files_diff',
743 repo_name=backend_svn.repo_name,
743 repo_name=backend_svn.repo_name,
744 f_path='trunk/example.py'),
744 f_path='trunk/example.py'),
745 params={
745 params={
746 'diff1': 'branches/argparse/example.py@' + commit_id,
746 'diff1': 'branches/argparse/example.py@' + commit_id,
747 'diff2': commit_id,
747 'diff2': commit_id,
748 'show_rev': 'Show at Revision',
748 'show_rev': 'Show at Revision',
749 'annotate': 'true',
749 'annotate': 'true',
750 },
750 },
751 status=302)
751 status=302)
752 response = response.follow()
752 response = response.follow()
753 assert response.headers['Location'].endswith(
753 assert response.headers['Location'].endswith(
754 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py')
754 'svn-svn-simple-layout/annotate/26/branches/argparse/example.py')
755
755
756
756
757 @pytest.mark.usefixtures("app", "autologin_user")
757 @pytest.mark.usefixtures("app", "autologin_user")
758 class TestModifyFilesWithWebInterface(object):
758 class TestModifyFilesWithWebInterface(object):
759
759
760 def test_add_file_view(self, backend):
760 def test_add_file_view(self, backend):
761 self.app.get(
761 self.app.get(
762 route_path('repo_files_add_file',
762 route_path('repo_files_add_file',
763 repo_name=backend.repo_name,
763 repo_name=backend.repo_name,
764 commit_id='tip', f_path='/')
764 commit_id='tip', f_path='/')
765 )
765 )
766
766
767 @pytest.mark.xfail_backends("svn", reason="Depends on online editing")
767 @pytest.mark.xfail_backends("svn", reason="Depends on online editing")
768 def test_add_file_into_repo_missing_content(self, backend, csrf_token):
768 def test_add_file_into_repo_missing_content(self, backend, csrf_token):
769 backend.create_repo()
769 backend.create_repo()
770 filename = 'init.py'
770 filename = 'init.py'
771 response = self.app.post(
771 response = self.app.post(
772 route_path('repo_files_create_file',
772 route_path('repo_files_create_file',
773 repo_name=backend.repo_name,
773 repo_name=backend.repo_name,
774 commit_id='tip', f_path='/'),
774 commit_id='tip', f_path='/'),
775 params={
775 params={
776 'content': "",
776 'content': "",
777 'filename': filename,
777 'filename': filename,
778 'csrf_token': csrf_token,
778 'csrf_token': csrf_token,
779 },
779 },
780 status=302)
780 status=302)
781 expected_msg = 'Successfully committed new file `{}`'.format(os.path.join(filename))
781 expected_msg = 'Successfully committed new file `{}`'.format(os.path.join(filename))
782 assert_session_flash(response, expected_msg)
782 assert_session_flash(response, expected_msg)
783
783
784 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
784 def test_add_file_into_repo_missing_filename(self, backend, csrf_token):
785 commit_id = backend.repo.get_commit().raw_id
785 commit_id = backend.repo.get_commit().raw_id
786 response = self.app.post(
786 response = self.app.post(
787 route_path('repo_files_create_file',
787 route_path('repo_files_create_file',
788 repo_name=backend.repo_name,
788 repo_name=backend.repo_name,
789 commit_id=commit_id, f_path='/'),
789 commit_id=commit_id, f_path='/'),
790 params={
790 params={
791 'content': "foo",
791 'content': "foo",
792 'csrf_token': csrf_token,
792 'csrf_token': csrf_token,
793 },
793 },
794 status=302)
794 status=302)
795
795
796 assert_session_flash(response, 'No filename specified')
796 assert_session_flash(response, 'No filename specified')
797
797
798 def test_add_file_into_repo_errors_and_no_commits(
798 def test_add_file_into_repo_errors_and_no_commits(
799 self, backend, csrf_token):
799 self, backend, csrf_token):
800 repo = backend.create_repo()
800 repo = backend.create_repo()
801 # Create a file with no filename, it will display an error but
801 # Create a file with no filename, it will display an error but
802 # the repo has no commits yet
802 # the repo has no commits yet
803 response = self.app.post(
803 response = self.app.post(
804 route_path('repo_files_create_file',
804 route_path('repo_files_create_file',
805 repo_name=repo.repo_name,
805 repo_name=repo.repo_name,
806 commit_id='tip', f_path='/'),
806 commit_id='tip', f_path='/'),
807 params={
807 params={
808 'content': "foo",
808 'content': "foo",
809 'csrf_token': csrf_token,
809 'csrf_token': csrf_token,
810 },
810 },
811 status=302)
811 status=302)
812
812
813 assert_session_flash(response, 'No filename specified')
813 assert_session_flash(response, 'No filename specified')
814
814
815 # Not allowed, redirect to the summary
815 # Not allowed, redirect to the summary
816 redirected = response.follow()
816 redirected = response.follow()
817 summary_url = h.route_path('repo_summary', repo_name=repo.repo_name)
817 summary_url = h.route_path('repo_summary', repo_name=repo.repo_name)
818
818
819 # As there are no commits, displays the summary page with the error of
819 # As there are no commits, displays the summary page with the error of
820 # creating a file with no filename
820 # creating a file with no filename
821
821
822 assert redirected.request.path == summary_url
822 assert redirected.request.path == summary_url
823
823
824 @pytest.mark.parametrize("filename, clean_filename", [
824 @pytest.mark.parametrize("filename, clean_filename", [
825 ('/abs/foo', 'abs/foo'),
825 ('/abs/foo', 'abs/foo'),
826 ('../rel/foo', 'rel/foo'),
826 ('../rel/foo', 'rel/foo'),
827 ('file/../foo/foo', 'file/foo/foo'),
827 ('file/../foo/foo', 'file/foo/foo'),
828 ])
828 ])
829 def test_add_file_into_repo_bad_filenames(self, filename, clean_filename, backend, csrf_token):
829 def test_add_file_into_repo_bad_filenames(self, filename, clean_filename, backend, csrf_token):
830 repo = backend.create_repo()
830 repo = backend.create_repo()
831 commit_id = repo.get_commit().raw_id
831 commit_id = repo.get_commit().raw_id
832
832
833 response = self.app.post(
833 response = self.app.post(
834 route_path('repo_files_create_file',
834 route_path('repo_files_create_file',
835 repo_name=repo.repo_name,
835 repo_name=repo.repo_name,
836 commit_id=commit_id, f_path='/'),
836 commit_id=commit_id, f_path='/'),
837 params={
837 params={
838 'content': "foo",
838 'content': "foo",
839 'filename': filename,
839 'filename': filename,
840 'csrf_token': csrf_token,
840 'csrf_token': csrf_token,
841 },
841 },
842 status=302)
842 status=302)
843
843
844 expected_msg = 'Successfully committed new file `{}`'.format(clean_filename)
844 expected_msg = 'Successfully committed new file `{}`'.format(clean_filename)
845 assert_session_flash(response, expected_msg)
845 assert_session_flash(response, expected_msg)
846
846
847 @pytest.mark.parametrize("cnt, filename, content", [
847 @pytest.mark.parametrize("cnt, filename, content", [
848 (1, 'foo.txt', "Content"),
848 (1, 'foo.txt', "Content"),
849 (2, 'dir/foo.rst', "Content"),
849 (2, 'dir/foo.rst', "Content"),
850 (3, 'dir/foo-second.rst', "Content"),
850 (3, 'dir/foo-second.rst', "Content"),
851 (4, 'rel/dir/foo.bar', "Content"),
851 (4, 'rel/dir/foo.bar', "Content"),
852 ])
852 ])
853 def test_add_file_into_empty_repo(self, cnt, filename, content, backend, csrf_token):
853 def test_add_file_into_empty_repo(self, cnt, filename, content, backend, csrf_token):
854 repo = backend.create_repo()
854 repo = backend.create_repo()
855 commit_id = repo.get_commit().raw_id
855 commit_id = repo.get_commit().raw_id
856 response = self.app.post(
856 response = self.app.post(
857 route_path('repo_files_create_file',
857 route_path('repo_files_create_file',
858 repo_name=repo.repo_name,
858 repo_name=repo.repo_name,
859 commit_id=commit_id, f_path='/'),
859 commit_id=commit_id, f_path='/'),
860 params={
860 params={
861 'content': content,
861 'content': content,
862 'filename': filename,
862 'filename': filename,
863 'csrf_token': csrf_token,
863 'csrf_token': csrf_token,
864 },
864 },
865 status=302)
865 status=302)
866
866
867 expected_msg = 'Successfully committed new file `{}`'.format(filename)
867 expected_msg = 'Successfully committed new file `{}`'.format(filename)
868 assert_session_flash(response, expected_msg)
868 assert_session_flash(response, expected_msg)
869
869
870 def test_edit_file_view(self, backend):
870 def test_edit_file_view(self, backend):
871 response = self.app.get(
871 response = self.app.get(
872 route_path('repo_files_edit_file',
872 route_path('repo_files_edit_file',
873 repo_name=backend.repo_name,
873 repo_name=backend.repo_name,
874 commit_id=backend.default_head_id,
874 commit_id=backend.default_head_id,
875 f_path='vcs/nodes.py'),
875 f_path='vcs/nodes.py'),
876 status=200)
876 status=200)
877 response.mustcontain("Module holding everything related to vcs nodes.")
877 response.mustcontain("Module holding everything related to vcs nodes.")
878
878
879 def test_edit_file_view_not_on_branch(self, backend):
879 def test_edit_file_view_not_on_branch(self, backend):
880 repo = backend.create_repo()
880 repo = backend.create_repo()
881 backend.ensure_file("vcs/nodes.py")
881 backend.ensure_file("vcs/nodes.py")
882
882
883 response = self.app.get(
883 response = self.app.get(
884 route_path('repo_files_edit_file',
884 route_path('repo_files_edit_file',
885 repo_name=repo.repo_name,
885 repo_name=repo.repo_name,
886 commit_id='tip',
886 commit_id='tip',
887 f_path='vcs/nodes.py'),
887 f_path='vcs/nodes.py'),
888 status=302)
888 status=302)
889 assert_session_flash(
889 assert_session_flash(
890 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
890 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
891
891
892 def test_edit_file_view_commit_changes(self, backend, csrf_token):
892 def test_edit_file_view_commit_changes(self, backend, csrf_token):
893 repo = backend.create_repo()
893 repo = backend.create_repo()
894 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
894 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
895
895
896 response = self.app.post(
896 response = self.app.post(
897 route_path('repo_files_update_file',
897 route_path('repo_files_update_file',
898 repo_name=repo.repo_name,
898 repo_name=repo.repo_name,
899 commit_id=backend.default_head_id,
899 commit_id=backend.default_head_id,
900 f_path='vcs/nodes.py'),
900 f_path='vcs/nodes.py'),
901 params={
901 params={
902 'content': "print 'hello world'",
902 'content': "print 'hello world'",
903 'message': 'I committed',
903 'message': 'I committed',
904 'filename': "vcs/nodes.py",
904 'filename': "vcs/nodes.py",
905 'csrf_token': csrf_token,
905 'csrf_token': csrf_token,
906 },
906 },
907 status=302)
907 status=302)
908 assert_session_flash(
908 assert_session_flash(
909 response, 'Successfully committed changes to file `vcs/nodes.py`')
909 response, 'Successfully committed changes to file `vcs/nodes.py`')
910 tip = repo.get_commit(commit_idx=-1)
910 tip = repo.get_commit(commit_idx=-1)
911 assert tip.message == 'I committed'
911 assert tip.message == 'I committed'
912
912
913 def test_edit_file_view_commit_changes_default_message(self, backend,
913 def test_edit_file_view_commit_changes_default_message(self, backend,
914 csrf_token):
914 csrf_token):
915 repo = backend.create_repo()
915 repo = backend.create_repo()
916 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
916 backend.ensure_file("vcs/nodes.py", content="print 'hello'")
917
917
918 commit_id = (
918 commit_id = (
919 backend.default_branch_name or
919 backend.default_branch_name or
920 backend.repo.scm_instance().commit_ids[-1])
920 backend.repo.scm_instance().commit_ids[-1])
921
921
922 response = self.app.post(
922 response = self.app.post(
923 route_path('repo_files_update_file',
923 route_path('repo_files_update_file',
924 repo_name=repo.repo_name,
924 repo_name=repo.repo_name,
925 commit_id=commit_id,
925 commit_id=commit_id,
926 f_path='vcs/nodes.py'),
926 f_path='vcs/nodes.py'),
927 params={
927 params={
928 'content': "print 'hello world'",
928 'content': "print 'hello world'",
929 'message': '',
929 'message': '',
930 'filename': "vcs/nodes.py",
930 'filename': "vcs/nodes.py",
931 'csrf_token': csrf_token,
931 'csrf_token': csrf_token,
932 },
932 },
933 status=302)
933 status=302)
934 assert_session_flash(
934 assert_session_flash(
935 response, 'Successfully committed changes to file `vcs/nodes.py`')
935 response, 'Successfully committed changes to file `vcs/nodes.py`')
936 tip = repo.get_commit(commit_idx=-1)
936 tip = repo.get_commit(commit_idx=-1)
937 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
937 assert tip.message == 'Edited file vcs/nodes.py via RhodeCode Enterprise'
938
938
939 def test_delete_file_view(self, backend):
939 def test_delete_file_view(self, backend):
940 self.app.get(
940 self.app.get(
941 route_path('repo_files_remove_file',
941 route_path('repo_files_remove_file',
942 repo_name=backend.repo_name,
942 repo_name=backend.repo_name,
943 commit_id=backend.default_head_id,
943 commit_id=backend.default_head_id,
944 f_path='vcs/nodes.py'),
944 f_path='vcs/nodes.py'),
945 status=200)
945 status=200)
946
946
947 def test_delete_file_view_not_on_branch(self, backend):
947 def test_delete_file_view_not_on_branch(self, backend):
948 repo = backend.create_repo()
948 repo = backend.create_repo()
949 backend.ensure_file('vcs/nodes.py')
949 backend.ensure_file('vcs/nodes.py')
950
950
951 response = self.app.get(
951 response = self.app.get(
952 route_path('repo_files_remove_file',
952 route_path('repo_files_remove_file',
953 repo_name=repo.repo_name,
953 repo_name=repo.repo_name,
954 commit_id='tip',
954 commit_id='tip',
955 f_path='vcs/nodes.py'),
955 f_path='vcs/nodes.py'),
956 status=302)
956 status=302)
957 assert_session_flash(
957 assert_session_flash(
958 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
958 response, 'Cannot modify file. Given commit `tip` is not head of a branch.')
959
959
960 def test_delete_file_view_commit_changes(self, backend, csrf_token):
960 def test_delete_file_view_commit_changes(self, backend, csrf_token):
961 repo = backend.create_repo()
961 repo = backend.create_repo()
962 backend.ensure_file("vcs/nodes.py")
962 backend.ensure_file("vcs/nodes.py")
963
963
964 response = self.app.post(
964 response = self.app.post(
965 route_path('repo_files_delete_file',
965 route_path('repo_files_delete_file',
966 repo_name=repo.repo_name,
966 repo_name=repo.repo_name,
967 commit_id=backend.default_head_id,
967 commit_id=backend.default_head_id,
968 f_path='vcs/nodes.py'),
968 f_path='vcs/nodes.py'),
969 params={
969 params={
970 'message': 'i commited',
970 'message': 'i commited',
971 'csrf_token': csrf_token,
971 'csrf_token': csrf_token,
972 },
972 },
973 status=302)
973 status=302)
974 assert_session_flash(
974 assert_session_flash(
975 response, 'Successfully deleted file `vcs/nodes.py`')
975 response, 'Successfully deleted file `vcs/nodes.py`')
976
976
977
977
978 @pytest.mark.usefixtures("app")
978 @pytest.mark.usefixtures("app")
979 class TestFilesViewOtherCases(object):
979 class TestFilesViewOtherCases(object):
980
980
981 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
981 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
982 self, backend_stub, autologin_regular_user, user_regular,
982 self, backend_stub, autologin_regular_user, user_regular,
983 user_util):
983 user_util):
984
984
985 repo = backend_stub.create_repo()
985 repo = backend_stub.create_repo()
986 user_util.grant_user_permission_to_repo(
986 user_util.grant_user_permission_to_repo(
987 repo, user_regular, 'repository.write')
987 repo, user_regular, 'repository.write')
988 response = self.app.get(
988 response = self.app.get(
989 route_path('repo_files',
989 route_path('repo_files',
990 repo_name=repo.repo_name,
990 repo_name=repo.repo_name,
991 commit_id='tip', f_path='/'))
991 commit_id='tip', f_path='/'))
992
992
993 repo_file_add_url = route_path(
993 repo_file_add_url = route_path(
994 'repo_files_add_file',
994 'repo_files_add_file',
995 repo_name=repo.repo_name,
995 repo_name=repo.repo_name,
996 commit_id=0, f_path='')
996 commit_id=0, f_path='')
997
997
998 assert_session_flash(
998 assert_session_flash(
999 response,
999 response,
1000 'There are no files yet. <a class="alert-link" '
1000 'There are no files yet. <a class="alert-link" '
1001 'href="{}">Click here to add a new file.</a>'
1001 'href="{}">Click here to add a new file.</a>'
1002 .format(repo_file_add_url))
1002 .format(repo_file_add_url))
1003
1003
1004 def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
1004 def test_access_empty_repo_redirect_to_summary_with_alert_no_write_perms(
1005 self, backend_stub, autologin_regular_user):
1005 self, backend_stub, autologin_regular_user):
1006 repo = backend_stub.create_repo()
1006 repo = backend_stub.create_repo()
1007 # init session for anon user
1007 # init session for anon user
1008 route_path('repo_summary', repo_name=repo.repo_name)
1008 route_path('repo_summary', repo_name=repo.repo_name)
1009
1009
1010 repo_file_add_url = route_path(
1010 repo_file_add_url = route_path(
1011 'repo_files_add_file',
1011 'repo_files_add_file',
1012 repo_name=repo.repo_name,
1012 repo_name=repo.repo_name,
1013 commit_id=0, f_path='')
1013 commit_id=0, f_path='')
1014
1014
1015 response = self.app.get(
1015 response = self.app.get(
1016 route_path('repo_files',
1016 route_path('repo_files',
1017 repo_name=repo.repo_name,
1017 repo_name=repo.repo_name,
1018 commit_id='tip', f_path='/'))
1018 commit_id='tip', f_path='/'))
1019
1019
1020 assert_session_flash(response, no_=repo_file_add_url)
1020 assert_session_flash(response, no_=repo_file_add_url)
1021
1021
1022 @pytest.mark.parametrize('file_node', [
1022 @pytest.mark.parametrize('file_node', [
1023 'archive/file.zip',
1023 'archive/file.zip',
1024 'diff/my-file.txt',
1024 'diff/my-file.txt',
1025 'render.py',
1025 'render.py',
1026 'render',
1026 'render',
1027 'remove_file',
1027 'remove_file',
1028 'remove_file/to-delete.txt',
1028 'remove_file/to-delete.txt',
1029 ])
1029 ])
1030 def test_file_names_equal_to_routes_parts(self, backend, file_node):
1030 def test_file_names_equal_to_routes_parts(self, backend, file_node):
1031 backend.create_repo()
1031 backend.create_repo()
1032 backend.ensure_file(file_node)
1032 backend.ensure_file(file_node)
1033
1033
1034 self.app.get(
1034 self.app.get(
1035 route_path('repo_files',
1035 route_path('repo_files',
1036 repo_name=backend.repo_name,
1036 repo_name=backend.repo_name,
1037 commit_id='tip', f_path=file_node),
1037 commit_id='tip', f_path=file_node),
1038 status=200)
1038 status=200)
1039
1039
1040
1040
1041 class TestAdjustFilePathForSvn(object):
1041 class TestAdjustFilePathForSvn(object):
1042 """
1042 """
1043 SVN specific adjustments of node history in RepoFilesView.
1043 SVN specific adjustments of node history in RepoFilesView.
1044 """
1044 """
1045
1045
1046 def test_returns_path_relative_to_matched_reference(self):
1046 def test_returns_path_relative_to_matched_reference(self):
1047 repo = self._repo(branches=['trunk'])
1047 repo = self._repo(branches=['trunk'])
1048 self.assert_file_adjustment('trunk/file', 'file', repo)
1048 self.assert_file_adjustment('trunk/file', 'file', repo)
1049
1049
1050 def test_does_not_modify_file_if_no_reference_matches(self):
1050 def test_does_not_modify_file_if_no_reference_matches(self):
1051 repo = self._repo(branches=['trunk'])
1051 repo = self._repo(branches=['trunk'])
1052 self.assert_file_adjustment('notes/file', 'notes/file', repo)
1052 self.assert_file_adjustment('notes/file', 'notes/file', repo)
1053
1053
1054 def test_does_not_adjust_partial_directory_names(self):
1054 def test_does_not_adjust_partial_directory_names(self):
1055 repo = self._repo(branches=['trun'])
1055 repo = self._repo(branches=['trun'])
1056 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
1056 self.assert_file_adjustment('trunk/file', 'trunk/file', repo)
1057
1057
1058 def test_is_robust_to_patterns_which_prefix_other_patterns(self):
1058 def test_is_robust_to_patterns_which_prefix_other_patterns(self):
1059 repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
1059 repo = self._repo(branches=['trunk', 'trunk/new', 'trunk/old'])
1060 self.assert_file_adjustment('trunk/new/file', 'file', repo)
1060 self.assert_file_adjustment('trunk/new/file', 'file', repo)
1061
1061
1062 def assert_file_adjustment(self, f_path, expected, repo):
1062 def assert_file_adjustment(self, f_path, expected, repo):
1063 result = RepoFilesView.adjust_file_path_for_svn(f_path, repo)
1063 result = RepoFilesView.adjust_file_path_for_svn(f_path, repo)
1064 assert result == expected
1064 assert result == expected
1065
1065
1066 def _repo(self, branches=None):
1066 def _repo(self, branches=None):
1067 repo = mock.Mock()
1067 repo = mock.Mock()
1068 repo.branches = OrderedDict((name, '0') for name in branches or [])
1068 repo.branches = OrderedDict((name, '0') for name in branches or [])
1069 repo.tags = {}
1069 repo.tags = {}
1070 return repo
1070 return repo
@@ -1,1603 +1,1603 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 from pyramid.view import view_config
31 from pyramid.view import view_config
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 branch_name, rule)
128 branch_name, 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_rev[1]
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):
184 except (CommitDoesNotExistError, LookupError):
185 msg = _('No such commit exists for this repository')
185 msg = _('No such commit exists for this repository')
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):
214 def _is_valid_head(self, commit_id, repo):
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 not repo.is_empty():
240 if not repo.is_empty():
241 commit = repo.get_commit(commit_id=commit_id)
241 commit = repo.get_commit(commit_id=commit_id)
242 if commit:
242 if commit:
243 branch_name = commit.branch
243 branch_name = commit.branch
244 sha_commit_id = commit.raw_id
244 sha_commit_id = commit.raw_id
245
245
246 return branch_name, sha_commit_id, is_head
246 return branch_name, sha_commit_id, is_head
247
247
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
248 def _get_tree_at_commit(self, c, commit_id, f_path, full_load=False, at_rev=None):
249
249
250 repo_id = self.db_repo.repo_id
250 repo_id = self.db_repo.repo_id
251 force_recache = self.get_recache_flag()
251 force_recache = self.get_recache_flag()
252
252
253 cache_seconds = safe_int(
253 cache_seconds = safe_int(
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
254 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
255 cache_on = not force_recache and cache_seconds > 0
255 cache_on = not force_recache and cache_seconds > 0
256 log.debug(
256 log.debug(
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
257 'Computing FILE TREE for repo_id %s commit_id `%s` and path `%s`'
258 'with caching: %s[TTL: %ss]' % (
258 'with caching: %s[TTL: %ss]' % (
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
259 repo_id, commit_id, f_path, cache_on, cache_seconds or 0))
260
260
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
261 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
262 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
263
263
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
264 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on)
265 def compute_file_tree(ver, _name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
265 def compute_file_tree(ver, _name_hash, _repo_id, _commit_id, _f_path, _full_load, _at_rev):
266 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
266 log.debug('Generating cached file tree at ver:%s for repo_id: %s, %s, %s',
267 ver, _repo_id, _commit_id, _f_path)
267 ver, _repo_id, _commit_id, _f_path)
268
268
269 c.full_load = _full_load
269 c.full_load = _full_load
270 return render(
270 return render(
271 'rhodecode:templates/files/files_browser_tree.mako',
271 'rhodecode:templates/files/files_browser_tree.mako',
272 self._get_template_context(c), self.request, _at_rev)
272 self._get_template_context(c), self.request, _at_rev)
273
273
274 return compute_file_tree(
274 return compute_file_tree(
275 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_name_hash,
275 rc_cache.FILE_TREE_CACHE_VER, self.db_repo.repo_name_hash,
276 self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
276 self.db_repo.repo_id, commit_id, f_path, full_load, at_rev)
277
277
278 def _get_archive_spec(self, fname):
278 def _get_archive_spec(self, fname):
279 log.debug('Detecting archive spec for: `%s`', fname)
279 log.debug('Detecting archive spec for: `%s`', fname)
280
280
281 fileformat = None
281 fileformat = None
282 ext = None
282 ext = None
283 content_type = None
283 content_type = None
284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
284 for a_type, content_type, extension in settings.ARCHIVE_SPECS:
285
285
286 if fname.endswith(extension):
286 if fname.endswith(extension):
287 fileformat = a_type
287 fileformat = a_type
288 log.debug('archive is of type: %s', fileformat)
288 log.debug('archive is of type: %s', fileformat)
289 ext = extension
289 ext = extension
290 break
290 break
291
291
292 if not fileformat:
292 if not fileformat:
293 raise ValueError()
293 raise ValueError()
294
294
295 # left over part of whole fname is the commit
295 # left over part of whole fname is the commit
296 commit_id = fname[:-len(ext)]
296 commit_id = fname[:-len(ext)]
297
297
298 return commit_id, ext, fileformat, content_type
298 return commit_id, ext, fileformat, content_type
299
299
300 def create_pure_path(self, *parts):
300 def create_pure_path(self, *parts):
301 # Split paths and sanitize them, removing any ../ etc
301 # Split paths and sanitize them, removing any ../ etc
302 sanitized_path = [
302 sanitized_path = [
303 x for x in pathlib2.PurePath(*parts).parts
303 x for x in pathlib2.PurePath(*parts).parts
304 if x not in ['.', '..']]
304 if x not in ['.', '..']]
305
305
306 pure_path = pathlib2.PurePath(*sanitized_path)
306 pure_path = pathlib2.PurePath(*sanitized_path)
307 return pure_path
307 return pure_path
308
308
309 def _is_lf_enabled(self, target_repo):
309 def _is_lf_enabled(self, target_repo):
310 lf_enabled = False
310 lf_enabled = False
311
311
312 lf_key_for_vcs_map = {
312 lf_key_for_vcs_map = {
313 'hg': 'extensions_largefiles',
313 'hg': 'extensions_largefiles',
314 'git': 'vcs_git_lfs_enabled'
314 'git': 'vcs_git_lfs_enabled'
315 }
315 }
316
316
317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
317 lf_key_for_vcs = lf_key_for_vcs_map.get(target_repo.repo_type)
318
318
319 if lf_key_for_vcs:
319 if lf_key_for_vcs:
320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
320 lf_enabled = self._get_repo_setting(target_repo, lf_key_for_vcs)
321
321
322 return lf_enabled
322 return lf_enabled
323
323
324 @LoginRequired()
324 @LoginRequired()
325 @HasRepoPermissionAnyDecorator(
325 @HasRepoPermissionAnyDecorator(
326 'repository.read', 'repository.write', 'repository.admin')
326 'repository.read', 'repository.write', 'repository.admin')
327 @view_config(
327 @view_config(
328 route_name='repo_archivefile', request_method='GET',
328 route_name='repo_archivefile', request_method='GET',
329 renderer=None)
329 renderer=None)
330 def repo_archivefile(self):
330 def repo_archivefile(self):
331 # archive cache config
331 # archive cache config
332 from rhodecode import CONFIG
332 from rhodecode import CONFIG
333 _ = self.request.translate
333 _ = self.request.translate
334 self.load_default_context()
334 self.load_default_context()
335 default_at_path = '/'
335 default_at_path = '/'
336 fname = self.request.matchdict['fname']
336 fname = self.request.matchdict['fname']
337 subrepos = self.request.GET.get('subrepos') == 'true'
337 subrepos = self.request.GET.get('subrepos') == 'true'
338 at_path = self.request.GET.get('at_path') or default_at_path
338 at_path = self.request.GET.get('at_path') or default_at_path
339
339
340 if not self.db_repo.enable_downloads:
340 if not self.db_repo.enable_downloads:
341 return Response(_('Downloads disabled'))
341 return Response(_('Downloads disabled'))
342
342
343 try:
343 try:
344 commit_id, ext, fileformat, content_type = \
344 commit_id, ext, fileformat, content_type = \
345 self._get_archive_spec(fname)
345 self._get_archive_spec(fname)
346 except ValueError:
346 except ValueError:
347 return Response(_('Unknown archive type for: `{}`').format(
347 return Response(_('Unknown archive type for: `{}`').format(
348 h.escape(fname)))
348 h.escape(fname)))
349
349
350 try:
350 try:
351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
351 commit = self.rhodecode_vcs_repo.get_commit(commit_id)
352 except CommitDoesNotExistError:
352 except CommitDoesNotExistError:
353 return Response(_('Unknown commit_id {}').format(
353 return Response(_('Unknown commit_id {}').format(
354 h.escape(commit_id)))
354 h.escape(commit_id)))
355 except EmptyRepositoryError:
355 except EmptyRepositoryError:
356 return Response(_('Empty repository'))
356 return Response(_('Empty repository'))
357
357
358 try:
358 try:
359 at_path = commit.get_node(at_path).path or default_at_path
359 at_path = commit.get_node(at_path).path or default_at_path
360 except Exception:
360 except Exception:
361 return Response(_('No node at path {} for this repository').format(at_path))
361 return Response(_('No node at path {} for this repository').format(at_path))
362
362
363 path_sha = sha1(at_path)[:8]
363 path_sha = sha1(at_path)[:8]
364
364
365 # original backward compat name of archive
365 # original backward compat name of archive
366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
366 clean_name = safe_str(self.db_repo_name.replace('/', '_'))
367 short_sha = safe_str(commit.short_id)
367 short_sha = safe_str(commit.short_id)
368
368
369 if at_path == default_at_path:
369 if at_path == default_at_path:
370 archive_name = '{}-{}{}{}'.format(
370 archive_name = '{}-{}{}{}'.format(
371 clean_name,
371 clean_name,
372 '-sub' if subrepos else '',
372 '-sub' if subrepos else '',
373 short_sha,
373 short_sha,
374 ext)
374 ext)
375 # custom path and new name
375 # custom path and new name
376 else:
376 else:
377 archive_name = '{}-{}{}-{}{}'.format(
377 archive_name = '{}-{}{}-{}{}'.format(
378 clean_name,
378 clean_name,
379 '-sub' if subrepos else '',
379 '-sub' if subrepos else '',
380 short_sha,
380 short_sha,
381 path_sha,
381 path_sha,
382 ext)
382 ext)
383
383
384 use_cached_archive = False
384 use_cached_archive = False
385 archive_cache_enabled = CONFIG.get(
385 archive_cache_enabled = CONFIG.get(
386 'archive_cache_dir') and not self.request.GET.get('no_cache')
386 'archive_cache_dir') and not self.request.GET.get('no_cache')
387 cached_archive_path = None
387 cached_archive_path = None
388
388
389 if archive_cache_enabled:
389 if archive_cache_enabled:
390 # check if we it's ok to write
390 # check if we it's ok to write
391 if not os.path.isdir(CONFIG['archive_cache_dir']):
391 if not os.path.isdir(CONFIG['archive_cache_dir']):
392 os.makedirs(CONFIG['archive_cache_dir'])
392 os.makedirs(CONFIG['archive_cache_dir'])
393 cached_archive_path = os.path.join(
393 cached_archive_path = os.path.join(
394 CONFIG['archive_cache_dir'], archive_name)
394 CONFIG['archive_cache_dir'], archive_name)
395 if os.path.isfile(cached_archive_path):
395 if os.path.isfile(cached_archive_path):
396 log.debug('Found cached archive in %s', cached_archive_path)
396 log.debug('Found cached archive in %s', cached_archive_path)
397 fd, archive = None, cached_archive_path
397 fd, archive = None, cached_archive_path
398 use_cached_archive = True
398 use_cached_archive = True
399 else:
399 else:
400 log.debug('Archive %s is not yet cached', archive_name)
400 log.debug('Archive %s is not yet cached', archive_name)
401
401
402 if not use_cached_archive:
402 if not use_cached_archive:
403 # generate new archive
403 # generate new archive
404 fd, archive = tempfile.mkstemp()
404 fd, archive = tempfile.mkstemp()
405 log.debug('Creating new temp archive in %s', archive)
405 log.debug('Creating new temp archive in %s', archive)
406 try:
406 try:
407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
407 commit.archive_repo(archive, kind=fileformat, subrepos=subrepos,
408 archive_at_path=at_path)
408 archive_at_path=at_path)
409 except ImproperArchiveTypeError:
409 except ImproperArchiveTypeError:
410 return _('Unknown archive type')
410 return _('Unknown archive type')
411 if archive_cache_enabled:
411 if archive_cache_enabled:
412 # if we generated the archive and we have cache enabled
412 # if we generated the archive and we have cache enabled
413 # let's use this for future
413 # let's use this for future
414 log.debug('Storing new archive in %s', cached_archive_path)
414 log.debug('Storing new archive in %s', cached_archive_path)
415 shutil.move(archive, cached_archive_path)
415 shutil.move(archive, cached_archive_path)
416 archive = cached_archive_path
416 archive = cached_archive_path
417
417
418 # store download action
418 # store download action
419 audit_logger.store_web(
419 audit_logger.store_web(
420 'repo.archive.download', action_data={
420 'repo.archive.download', action_data={
421 'user_agent': self.request.user_agent,
421 'user_agent': self.request.user_agent,
422 'archive_name': archive_name,
422 'archive_name': archive_name,
423 'archive_spec': fname,
423 'archive_spec': fname,
424 'archive_cached': use_cached_archive},
424 'archive_cached': use_cached_archive},
425 user=self._rhodecode_user,
425 user=self._rhodecode_user,
426 repo=self.db_repo,
426 repo=self.db_repo,
427 commit=True
427 commit=True
428 )
428 )
429
429
430 def get_chunked_archive(archive_path):
430 def get_chunked_archive(archive_path):
431 with open(archive_path, 'rb') as stream:
431 with open(archive_path, 'rb') as stream:
432 while True:
432 while True:
433 data = stream.read(16 * 1024)
433 data = stream.read(16 * 1024)
434 if not data:
434 if not data:
435 if fd: # fd means we used temporary file
435 if fd: # fd means we used temporary file
436 os.close(fd)
436 os.close(fd)
437 if not archive_cache_enabled:
437 if not archive_cache_enabled:
438 log.debug('Destroying temp archive %s', archive_path)
438 log.debug('Destroying temp archive %s', archive_path)
439 os.remove(archive_path)
439 os.remove(archive_path)
440 break
440 break
441 yield data
441 yield data
442
442
443 response = Response(app_iter=get_chunked_archive(archive))
443 response = Response(app_iter=get_chunked_archive(archive))
444 response.content_disposition = str(
444 response.content_disposition = str(
445 'attachment; filename=%s' % archive_name)
445 'attachment; filename=%s' % archive_name)
446 response.content_type = str(content_type)
446 response.content_type = str(content_type)
447
447
448 return response
448 return response
449
449
450 def _get_file_node(self, commit_id, f_path):
450 def _get_file_node(self, commit_id, f_path):
451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
451 if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]:
452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
452 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
453 try:
453 try:
454 node = commit.get_node(f_path)
454 node = commit.get_node(f_path)
455 if node.is_dir():
455 if node.is_dir():
456 raise NodeError('%s path is a %s not a file'
456 raise NodeError('%s path is a %s not a file'
457 % (node, type(node)))
457 % (node, type(node)))
458 except NodeDoesNotExistError:
458 except NodeDoesNotExistError:
459 commit = EmptyCommit(
459 commit = EmptyCommit(
460 commit_id=commit_id,
460 commit_id=commit_id,
461 idx=commit.idx,
461 idx=commit.idx,
462 repo=commit.repository,
462 repo=commit.repository,
463 alias=commit.repository.alias,
463 alias=commit.repository.alias,
464 message=commit.message,
464 message=commit.message,
465 author=commit.author,
465 author=commit.author,
466 date=commit.date)
466 date=commit.date)
467 node = FileNode(f_path, '', commit=commit)
467 node = FileNode(f_path, '', commit=commit)
468 else:
468 else:
469 commit = EmptyCommit(
469 commit = EmptyCommit(
470 repo=self.rhodecode_vcs_repo,
470 repo=self.rhodecode_vcs_repo,
471 alias=self.rhodecode_vcs_repo.alias)
471 alias=self.rhodecode_vcs_repo.alias)
472 node = FileNode(f_path, '', commit=commit)
472 node = FileNode(f_path, '', commit=commit)
473 return node
473 return node
474
474
475 @LoginRequired()
475 @LoginRequired()
476 @HasRepoPermissionAnyDecorator(
476 @HasRepoPermissionAnyDecorator(
477 'repository.read', 'repository.write', 'repository.admin')
477 'repository.read', 'repository.write', 'repository.admin')
478 @view_config(
478 @view_config(
479 route_name='repo_files_diff', request_method='GET',
479 route_name='repo_files_diff', request_method='GET',
480 renderer=None)
480 renderer=None)
481 def repo_files_diff(self):
481 def repo_files_diff(self):
482 c = self.load_default_context()
482 c = self.load_default_context()
483 f_path = self._get_f_path(self.request.matchdict)
483 f_path = self._get_f_path(self.request.matchdict)
484 diff1 = self.request.GET.get('diff1', '')
484 diff1 = self.request.GET.get('diff1', '')
485 diff2 = self.request.GET.get('diff2', '')
485 diff2 = self.request.GET.get('diff2', '')
486
486
487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
487 path1, diff1 = parse_path_ref(diff1, default_path=f_path)
488
488
489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
489 ignore_whitespace = str2bool(self.request.GET.get('ignorews'))
490 line_context = self.request.GET.get('context', 3)
490 line_context = self.request.GET.get('context', 3)
491
491
492 if not any((diff1, diff2)):
492 if not any((diff1, diff2)):
493 h.flash(
493 h.flash(
494 'Need query parameter "diff1" or "diff2" to generate a diff.',
494 'Need query parameter "diff1" or "diff2" to generate a diff.',
495 category='error')
495 category='error')
496 raise HTTPBadRequest()
496 raise HTTPBadRequest()
497
497
498 c.action = self.request.GET.get('diff')
498 c.action = self.request.GET.get('diff')
499 if c.action not in ['download', 'raw']:
499 if c.action not in ['download', 'raw']:
500 compare_url = h.route_path(
500 compare_url = h.route_path(
501 'repo_compare',
501 'repo_compare',
502 repo_name=self.db_repo_name,
502 repo_name=self.db_repo_name,
503 source_ref_type='rev',
503 source_ref_type='rev',
504 source_ref=diff1,
504 source_ref=diff1,
505 target_repo=self.db_repo_name,
505 target_repo=self.db_repo_name,
506 target_ref_type='rev',
506 target_ref_type='rev',
507 target_ref=diff2,
507 target_ref=diff2,
508 _query=dict(f_path=f_path))
508 _query=dict(f_path=f_path))
509 # redirect to new view if we render diff
509 # redirect to new view if we render diff
510 raise HTTPFound(compare_url)
510 raise HTTPFound(compare_url)
511
511
512 try:
512 try:
513 node1 = self._get_file_node(diff1, path1)
513 node1 = self._get_file_node(diff1, path1)
514 node2 = self._get_file_node(diff2, f_path)
514 node2 = self._get_file_node(diff2, f_path)
515 except (RepositoryError, NodeError):
515 except (RepositoryError, NodeError):
516 log.exception("Exception while trying to get node from repository")
516 log.exception("Exception while trying to get node from repository")
517 raise HTTPFound(
517 raise HTTPFound(
518 h.route_path('repo_files', repo_name=self.db_repo_name,
518 h.route_path('repo_files', repo_name=self.db_repo_name,
519 commit_id='tip', f_path=f_path))
519 commit_id='tip', f_path=f_path))
520
520
521 if all(isinstance(node.commit, EmptyCommit)
521 if all(isinstance(node.commit, EmptyCommit)
522 for node in (node1, node2)):
522 for node in (node1, node2)):
523 raise HTTPNotFound()
523 raise HTTPNotFound()
524
524
525 c.commit_1 = node1.commit
525 c.commit_1 = node1.commit
526 c.commit_2 = node2.commit
526 c.commit_2 = node2.commit
527
527
528 if c.action == 'download':
528 if c.action == 'download':
529 _diff = diffs.get_gitdiff(node1, node2,
529 _diff = diffs.get_gitdiff(node1, node2,
530 ignore_whitespace=ignore_whitespace,
530 ignore_whitespace=ignore_whitespace,
531 context=line_context)
531 context=line_context)
532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
532 diff = diffs.DiffProcessor(_diff, format='gitdiff')
533
533
534 response = Response(self.path_filter.get_raw_patch(diff))
534 response = Response(self.path_filter.get_raw_patch(diff))
535 response.content_type = 'text/plain'
535 response.content_type = 'text/plain'
536 response.content_disposition = (
536 response.content_disposition = (
537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
537 'attachment; filename=%s_%s_vs_%s.diff' % (f_path, diff1, diff2)
538 )
538 )
539 charset = self._get_default_encoding(c)
539 charset = self._get_default_encoding(c)
540 if charset:
540 if charset:
541 response.charset = charset
541 response.charset = charset
542 return response
542 return response
543
543
544 elif c.action == 'raw':
544 elif c.action == 'raw':
545 _diff = diffs.get_gitdiff(node1, node2,
545 _diff = diffs.get_gitdiff(node1, node2,
546 ignore_whitespace=ignore_whitespace,
546 ignore_whitespace=ignore_whitespace,
547 context=line_context)
547 context=line_context)
548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
548 diff = diffs.DiffProcessor(_diff, format='gitdiff')
549
549
550 response = Response(self.path_filter.get_raw_patch(diff))
550 response = Response(self.path_filter.get_raw_patch(diff))
551 response.content_type = 'text/plain'
551 response.content_type = 'text/plain'
552 charset = self._get_default_encoding(c)
552 charset = self._get_default_encoding(c)
553 if charset:
553 if charset:
554 response.charset = charset
554 response.charset = charset
555 return response
555 return response
556
556
557 # in case we ever end up here
557 # in case we ever end up here
558 raise HTTPNotFound()
558 raise HTTPNotFound()
559
559
560 @LoginRequired()
560 @LoginRequired()
561 @HasRepoPermissionAnyDecorator(
561 @HasRepoPermissionAnyDecorator(
562 'repository.read', 'repository.write', 'repository.admin')
562 'repository.read', 'repository.write', 'repository.admin')
563 @view_config(
563 @view_config(
564 route_name='repo_files_diff_2way_redirect', request_method='GET',
564 route_name='repo_files_diff_2way_redirect', request_method='GET',
565 renderer=None)
565 renderer=None)
566 def repo_files_diff_2way_redirect(self):
566 def repo_files_diff_2way_redirect(self):
567 """
567 """
568 Kept only to make OLD links work
568 Kept only to make OLD links work
569 """
569 """
570 f_path = self._get_f_path_unchecked(self.request.matchdict)
570 f_path = self._get_f_path_unchecked(self.request.matchdict)
571 diff1 = self.request.GET.get('diff1', '')
571 diff1 = self.request.GET.get('diff1', '')
572 diff2 = self.request.GET.get('diff2', '')
572 diff2 = self.request.GET.get('diff2', '')
573
573
574 if not any((diff1, diff2)):
574 if not any((diff1, diff2)):
575 h.flash(
575 h.flash(
576 'Need query parameter "diff1" or "diff2" to generate a diff.',
576 'Need query parameter "diff1" or "diff2" to generate a diff.',
577 category='error')
577 category='error')
578 raise HTTPBadRequest()
578 raise HTTPBadRequest()
579
579
580 compare_url = h.route_path(
580 compare_url = h.route_path(
581 'repo_compare',
581 'repo_compare',
582 repo_name=self.db_repo_name,
582 repo_name=self.db_repo_name,
583 source_ref_type='rev',
583 source_ref_type='rev',
584 source_ref=diff1,
584 source_ref=diff1,
585 target_ref_type='rev',
585 target_ref_type='rev',
586 target_ref=diff2,
586 target_ref=diff2,
587 _query=dict(f_path=f_path, diffmode='sideside',
587 _query=dict(f_path=f_path, diffmode='sideside',
588 target_repo=self.db_repo_name,))
588 target_repo=self.db_repo_name,))
589 raise HTTPFound(compare_url)
589 raise HTTPFound(compare_url)
590
590
591 @LoginRequired()
591 @LoginRequired()
592 @HasRepoPermissionAnyDecorator(
592 @HasRepoPermissionAnyDecorator(
593 'repository.read', 'repository.write', 'repository.admin')
593 'repository.read', 'repository.write', 'repository.admin')
594 @view_config(
594 @view_config(
595 route_name='repo_files', request_method='GET',
595 route_name='repo_files', request_method='GET',
596 renderer=None)
596 renderer=None)
597 @view_config(
597 @view_config(
598 route_name='repo_files:default_path', request_method='GET',
598 route_name='repo_files:default_path', request_method='GET',
599 renderer=None)
599 renderer=None)
600 @view_config(
600 @view_config(
601 route_name='repo_files:default_commit', request_method='GET',
601 route_name='repo_files:default_commit', request_method='GET',
602 renderer=None)
602 renderer=None)
603 @view_config(
603 @view_config(
604 route_name='repo_files:rendered', request_method='GET',
604 route_name='repo_files:rendered', request_method='GET',
605 renderer=None)
605 renderer=None)
606 @view_config(
606 @view_config(
607 route_name='repo_files:annotated', request_method='GET',
607 route_name='repo_files:annotated', request_method='GET',
608 renderer=None)
608 renderer=None)
609 def repo_files(self):
609 def repo_files(self):
610 c = self.load_default_context()
610 c = self.load_default_context()
611
611
612 view_name = getattr(self.request.matched_route, 'name', None)
612 view_name = getattr(self.request.matched_route, 'name', None)
613
613
614 c.annotate = view_name == 'repo_files:annotated'
614 c.annotate = view_name == 'repo_files:annotated'
615 # default is false, but .rst/.md files later are auto rendered, we can
615 # default is false, but .rst/.md files later are auto rendered, we can
616 # overwrite auto rendering by setting this GET flag
616 # overwrite auto rendering by setting this GET flag
617 c.renderer = view_name == 'repo_files:rendered' or \
617 c.renderer = view_name == 'repo_files:rendered' or \
618 not self.request.GET.get('no-render', False)
618 not self.request.GET.get('no-render', False)
619
619
620 commit_id, f_path = self._get_commit_and_path()
620 commit_id, f_path = self._get_commit_and_path()
621
621
622 c.commit = self._get_commit_or_redirect(commit_id)
622 c.commit = self._get_commit_or_redirect(commit_id)
623 c.branch = self.request.GET.get('branch', None)
623 c.branch = self.request.GET.get('branch', None)
624 c.f_path = f_path
624 c.f_path = f_path
625 at_rev = self.request.GET.get('at')
625 at_rev = self.request.GET.get('at')
626
626
627 # prev link
627 # prev link
628 try:
628 try:
629 prev_commit = c.commit.prev(c.branch)
629 prev_commit = c.commit.prev(c.branch)
630 c.prev_commit = prev_commit
630 c.prev_commit = prev_commit
631 c.url_prev = h.route_path(
631 c.url_prev = h.route_path(
632 'repo_files', repo_name=self.db_repo_name,
632 'repo_files', repo_name=self.db_repo_name,
633 commit_id=prev_commit.raw_id, f_path=f_path)
633 commit_id=prev_commit.raw_id, f_path=f_path)
634 if c.branch:
634 if c.branch:
635 c.url_prev += '?branch=%s' % c.branch
635 c.url_prev += '?branch=%s' % c.branch
636 except (CommitDoesNotExistError, VCSError):
636 except (CommitDoesNotExistError, VCSError):
637 c.url_prev = '#'
637 c.url_prev = '#'
638 c.prev_commit = EmptyCommit()
638 c.prev_commit = EmptyCommit()
639
639
640 # next link
640 # next link
641 try:
641 try:
642 next_commit = c.commit.next(c.branch)
642 next_commit = c.commit.next(c.branch)
643 c.next_commit = next_commit
643 c.next_commit = next_commit
644 c.url_next = h.route_path(
644 c.url_next = h.route_path(
645 'repo_files', repo_name=self.db_repo_name,
645 'repo_files', repo_name=self.db_repo_name,
646 commit_id=next_commit.raw_id, f_path=f_path)
646 commit_id=next_commit.raw_id, f_path=f_path)
647 if c.branch:
647 if c.branch:
648 c.url_next += '?branch=%s' % c.branch
648 c.url_next += '?branch=%s' % c.branch
649 except (CommitDoesNotExistError, VCSError):
649 except (CommitDoesNotExistError, VCSError):
650 c.url_next = '#'
650 c.url_next = '#'
651 c.next_commit = EmptyCommit()
651 c.next_commit = EmptyCommit()
652
652
653 # files or dirs
653 # files or dirs
654 try:
654 try:
655 c.file = c.commit.get_node(f_path)
655 c.file = c.commit.get_node(f_path)
656 c.file_author = True
656 c.file_author = True
657 c.file_tree = ''
657 c.file_tree = ''
658
658
659 # load file content
659 # load file content
660 if c.file.is_file():
660 if c.file.is_file():
661 c.lf_node = {}
661 c.lf_node = {}
662
662
663 has_lf_enabled = self._is_lf_enabled(self.db_repo)
663 has_lf_enabled = self._is_lf_enabled(self.db_repo)
664 if has_lf_enabled:
664 if has_lf_enabled:
665 c.lf_node = c.file.get_largefile_node()
665 c.lf_node = c.file.get_largefile_node()
666
666
667 c.file_source_page = 'true'
667 c.file_source_page = 'true'
668 c.file_last_commit = c.file.last_commit
668 c.file_last_commit = c.file.last_commit
669
669
670 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
670 c.file_size_too_big = c.file.size > c.visual.cut_off_limit_file
671
671
672 if not (c.file_size_too_big or c.file.is_binary):
672 if not (c.file_size_too_big or c.file.is_binary):
673 if c.annotate: # annotation has precedence over renderer
673 if c.annotate: # annotation has precedence over renderer
674 c.annotated_lines = filenode_as_annotated_lines_tokens(
674 c.annotated_lines = filenode_as_annotated_lines_tokens(
675 c.file
675 c.file
676 )
676 )
677 else:
677 else:
678 c.renderer = (
678 c.renderer = (
679 c.renderer and h.renderer_from_filename(c.file.path)
679 c.renderer and h.renderer_from_filename(c.file.path)
680 )
680 )
681 if not c.renderer:
681 if not c.renderer:
682 c.lines = filenode_as_lines_tokens(c.file)
682 c.lines = filenode_as_lines_tokens(c.file)
683
683
684 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
684 _branch_name, _sha_commit_id, is_head = self._is_valid_head(
685 commit_id, self.rhodecode_vcs_repo)
685 commit_id, self.rhodecode_vcs_repo)
686 c.on_branch_head = is_head
686 c.on_branch_head = is_head
687
687
688 branch = c.commit.branch if (
688 branch = c.commit.branch if (
689 c.commit.branch and '/' not in c.commit.branch) else None
689 c.commit.branch and '/' not in c.commit.branch) else None
690 c.branch_or_raw_id = branch or c.commit.raw_id
690 c.branch_or_raw_id = branch or c.commit.raw_id
691 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
691 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
692
692
693 author = c.file_last_commit.author
693 author = c.file_last_commit.author
694 c.authors = [[
694 c.authors = [[
695 h.email(author),
695 h.email(author),
696 h.person(author, 'username_or_name_or_email'),
696 h.person(author, 'username_or_name_or_email'),
697 1
697 1
698 ]]
698 ]]
699
699
700 else: # load tree content at path
700 else: # load tree content at path
701 c.file_source_page = 'false'
701 c.file_source_page = 'false'
702 c.authors = []
702 c.authors = []
703 # this loads a simple tree without metadata to speed things up
703 # this loads a simple tree without metadata to speed things up
704 # later via ajax we call repo_nodetree_full and fetch whole
704 # later via ajax we call repo_nodetree_full and fetch whole
705 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
705 c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev)
706
706
707 c.readme_data, c.readme_file = \
707 c.readme_data, c.readme_file = \
708 self._get_readme_data(self.db_repo, c.visual.default_renderer,
708 self._get_readme_data(self.db_repo, c.visual.default_renderer,
709 c.commit.raw_id, f_path)
709 c.commit.raw_id, f_path)
710
710
711 except RepositoryError as e:
711 except RepositoryError as e:
712 h.flash(safe_str(h.escape(e)), category='error')
712 h.flash(safe_str(h.escape(e)), category='error')
713 raise HTTPNotFound()
713 raise HTTPNotFound()
714
714
715 if self.request.environ.get('HTTP_X_PJAX'):
715 if self.request.environ.get('HTTP_X_PJAX'):
716 html = render('rhodecode:templates/files/files_pjax.mako',
716 html = render('rhodecode:templates/files/files_pjax.mako',
717 self._get_template_context(c), self.request)
717 self._get_template_context(c), self.request)
718 else:
718 else:
719 html = render('rhodecode:templates/files/files.mako',
719 html = render('rhodecode:templates/files/files.mako',
720 self._get_template_context(c), self.request)
720 self._get_template_context(c), self.request)
721 return Response(html)
721 return Response(html)
722
722
723 @HasRepoPermissionAnyDecorator(
723 @HasRepoPermissionAnyDecorator(
724 'repository.read', 'repository.write', 'repository.admin')
724 'repository.read', 'repository.write', 'repository.admin')
725 @view_config(
725 @view_config(
726 route_name='repo_files:annotated_previous', request_method='GET',
726 route_name='repo_files:annotated_previous', request_method='GET',
727 renderer=None)
727 renderer=None)
728 def repo_files_annotated_previous(self):
728 def repo_files_annotated_previous(self):
729 self.load_default_context()
729 self.load_default_context()
730
730
731 commit_id, f_path = self._get_commit_and_path()
731 commit_id, f_path = self._get_commit_and_path()
732 commit = self._get_commit_or_redirect(commit_id)
732 commit = self._get_commit_or_redirect(commit_id)
733 prev_commit_id = commit.raw_id
733 prev_commit_id = commit.raw_id
734 line_anchor = self.request.GET.get('line_anchor')
734 line_anchor = self.request.GET.get('line_anchor')
735 is_file = False
735 is_file = False
736 try:
736 try:
737 _file = commit.get_node(f_path)
737 _file = commit.get_node(f_path)
738 is_file = _file.is_file()
738 is_file = _file.is_file()
739 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
739 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
740 pass
740 pass
741
741
742 if is_file:
742 if is_file:
743 history = commit.get_path_history(f_path)
743 history = commit.get_path_history(f_path)
744 prev_commit_id = history[1].raw_id \
744 prev_commit_id = history[1].raw_id \
745 if len(history) > 1 else prev_commit_id
745 if len(history) > 1 else prev_commit_id
746 prev_url = h.route_path(
746 prev_url = h.route_path(
747 'repo_files:annotated', repo_name=self.db_repo_name,
747 'repo_files:annotated', repo_name=self.db_repo_name,
748 commit_id=prev_commit_id, f_path=f_path,
748 commit_id=prev_commit_id, f_path=f_path,
749 _anchor='L{}'.format(line_anchor))
749 _anchor='L{}'.format(line_anchor))
750
750
751 raise HTTPFound(prev_url)
751 raise HTTPFound(prev_url)
752
752
753 @LoginRequired()
753 @LoginRequired()
754 @HasRepoPermissionAnyDecorator(
754 @HasRepoPermissionAnyDecorator(
755 'repository.read', 'repository.write', 'repository.admin')
755 'repository.read', 'repository.write', 'repository.admin')
756 @view_config(
756 @view_config(
757 route_name='repo_nodetree_full', request_method='GET',
757 route_name='repo_nodetree_full', request_method='GET',
758 renderer=None, xhr=True)
758 renderer=None, xhr=True)
759 @view_config(
759 @view_config(
760 route_name='repo_nodetree_full:default_path', request_method='GET',
760 route_name='repo_nodetree_full:default_path', request_method='GET',
761 renderer=None, xhr=True)
761 renderer=None, xhr=True)
762 def repo_nodetree_full(self):
762 def repo_nodetree_full(self):
763 """
763 """
764 Returns rendered html of file tree that contains commit date,
764 Returns rendered html of file tree that contains commit date,
765 author, commit_id for the specified combination of
765 author, commit_id for the specified combination of
766 repo, commit_id and file path
766 repo, commit_id and file path
767 """
767 """
768 c = self.load_default_context()
768 c = self.load_default_context()
769
769
770 commit_id, f_path = self._get_commit_and_path()
770 commit_id, f_path = self._get_commit_and_path()
771 commit = self._get_commit_or_redirect(commit_id)
771 commit = self._get_commit_or_redirect(commit_id)
772 try:
772 try:
773 dir_node = commit.get_node(f_path)
773 dir_node = commit.get_node(f_path)
774 except RepositoryError as e:
774 except RepositoryError as e:
775 return Response('error: {}'.format(h.escape(safe_str(e))))
775 return Response('error: {}'.format(h.escape(safe_str(e))))
776
776
777 if dir_node.is_file():
777 if dir_node.is_file():
778 return Response('')
778 return Response('')
779
779
780 c.file = dir_node
780 c.file = dir_node
781 c.commit = commit
781 c.commit = commit
782 at_rev = self.request.GET.get('at')
782 at_rev = self.request.GET.get('at')
783
783
784 html = self._get_tree_at_commit(
784 html = self._get_tree_at_commit(
785 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
785 c, commit.raw_id, dir_node.path, full_load=True, at_rev=at_rev)
786
786
787 return Response(html)
787 return Response(html)
788
788
789 def _get_attachement_headers(self, f_path):
789 def _get_attachement_headers(self, f_path):
790 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
790 f_name = safe_str(f_path.split(Repository.NAME_SEP)[-1])
791 safe_path = f_name.replace('"', '\\"')
791 safe_path = f_name.replace('"', '\\"')
792 encoded_path = urllib.quote(f_name)
792 encoded_path = urllib.quote(f_name)
793
793
794 return "attachment; " \
794 return "attachment; " \
795 "filename=\"{}\"; " \
795 "filename=\"{}\"; " \
796 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
796 "filename*=UTF-8\'\'{}".format(safe_path, encoded_path)
797
797
798 @LoginRequired()
798 @LoginRequired()
799 @HasRepoPermissionAnyDecorator(
799 @HasRepoPermissionAnyDecorator(
800 'repository.read', 'repository.write', 'repository.admin')
800 'repository.read', 'repository.write', 'repository.admin')
801 @view_config(
801 @view_config(
802 route_name='repo_file_raw', request_method='GET',
802 route_name='repo_file_raw', request_method='GET',
803 renderer=None)
803 renderer=None)
804 def repo_file_raw(self):
804 def repo_file_raw(self):
805 """
805 """
806 Action for show as raw, some mimetypes are "rendered",
806 Action for show as raw, some mimetypes are "rendered",
807 those include images, icons.
807 those include images, icons.
808 """
808 """
809 c = self.load_default_context()
809 c = self.load_default_context()
810
810
811 commit_id, f_path = self._get_commit_and_path()
811 commit_id, f_path = self._get_commit_and_path()
812 commit = self._get_commit_or_redirect(commit_id)
812 commit = self._get_commit_or_redirect(commit_id)
813 file_node = self._get_filenode_or_redirect(commit, f_path)
813 file_node = self._get_filenode_or_redirect(commit, f_path)
814
814
815 raw_mimetype_mapping = {
815 raw_mimetype_mapping = {
816 # map original mimetype to a mimetype used for "show as raw"
816 # map original mimetype to a mimetype used for "show as raw"
817 # you can also provide a content-disposition to override the
817 # you can also provide a content-disposition to override the
818 # default "attachment" disposition.
818 # default "attachment" disposition.
819 # orig_type: (new_type, new_dispo)
819 # orig_type: (new_type, new_dispo)
820
820
821 # show images inline:
821 # show images inline:
822 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
822 # Do not re-add SVG: it is unsafe and permits XSS attacks. One can
823 # for example render an SVG with javascript inside or even render
823 # for example render an SVG with javascript inside or even render
824 # HTML.
824 # HTML.
825 'image/x-icon': ('image/x-icon', 'inline'),
825 'image/x-icon': ('image/x-icon', 'inline'),
826 'image/png': ('image/png', 'inline'),
826 'image/png': ('image/png', 'inline'),
827 'image/gif': ('image/gif', 'inline'),
827 'image/gif': ('image/gif', 'inline'),
828 'image/jpeg': ('image/jpeg', 'inline'),
828 'image/jpeg': ('image/jpeg', 'inline'),
829 'application/pdf': ('application/pdf', 'inline'),
829 'application/pdf': ('application/pdf', 'inline'),
830 }
830 }
831
831
832 mimetype = file_node.mimetype
832 mimetype = file_node.mimetype
833 try:
833 try:
834 mimetype, disposition = raw_mimetype_mapping[mimetype]
834 mimetype, disposition = raw_mimetype_mapping[mimetype]
835 except KeyError:
835 except KeyError:
836 # we don't know anything special about this, handle it safely
836 # we don't know anything special about this, handle it safely
837 if file_node.is_binary:
837 if file_node.is_binary:
838 # do same as download raw for binary files
838 # do same as download raw for binary files
839 mimetype, disposition = 'application/octet-stream', 'attachment'
839 mimetype, disposition = 'application/octet-stream', 'attachment'
840 else:
840 else:
841 # do not just use the original mimetype, but force text/plain,
841 # do not just use the original mimetype, but force text/plain,
842 # otherwise it would serve text/html and that might be unsafe.
842 # otherwise it would serve text/html and that might be unsafe.
843 # Note: underlying vcs library fakes text/plain mimetype if the
843 # Note: underlying vcs library fakes text/plain mimetype if the
844 # mimetype can not be determined and it thinks it is not
844 # mimetype can not be determined and it thinks it is not
845 # binary.This might lead to erroneous text display in some
845 # binary.This might lead to erroneous text display in some
846 # cases, but helps in other cases, like with text files
846 # cases, but helps in other cases, like with text files
847 # without extension.
847 # without extension.
848 mimetype, disposition = 'text/plain', 'inline'
848 mimetype, disposition = 'text/plain', 'inline'
849
849
850 if disposition == 'attachment':
850 if disposition == 'attachment':
851 disposition = self._get_attachement_headers(f_path)
851 disposition = self._get_attachement_headers(f_path)
852
852
853 stream_content = file_node.stream_bytes()
853 stream_content = file_node.stream_bytes()
854
854
855 response = Response(app_iter=stream_content)
855 response = Response(app_iter=stream_content)
856 response.content_disposition = disposition
856 response.content_disposition = disposition
857 response.content_type = mimetype
857 response.content_type = mimetype
858
858
859 charset = self._get_default_encoding(c)
859 charset = self._get_default_encoding(c)
860 if charset:
860 if charset:
861 response.charset = charset
861 response.charset = charset
862
862
863 return response
863 return response
864
864
865 @LoginRequired()
865 @LoginRequired()
866 @HasRepoPermissionAnyDecorator(
866 @HasRepoPermissionAnyDecorator(
867 'repository.read', 'repository.write', 'repository.admin')
867 'repository.read', 'repository.write', 'repository.admin')
868 @view_config(
868 @view_config(
869 route_name='repo_file_download', request_method='GET',
869 route_name='repo_file_download', request_method='GET',
870 renderer=None)
870 renderer=None)
871 @view_config(
871 @view_config(
872 route_name='repo_file_download:legacy', request_method='GET',
872 route_name='repo_file_download:legacy', request_method='GET',
873 renderer=None)
873 renderer=None)
874 def repo_file_download(self):
874 def repo_file_download(self):
875 c = self.load_default_context()
875 c = self.load_default_context()
876
876
877 commit_id, f_path = self._get_commit_and_path()
877 commit_id, f_path = self._get_commit_and_path()
878 commit = self._get_commit_or_redirect(commit_id)
878 commit = self._get_commit_or_redirect(commit_id)
879 file_node = self._get_filenode_or_redirect(commit, f_path)
879 file_node = self._get_filenode_or_redirect(commit, f_path)
880
880
881 if self.request.GET.get('lf'):
881 if self.request.GET.get('lf'):
882 # only if lf get flag is passed, we download this file
882 # only if lf get flag is passed, we download this file
883 # as LFS/Largefile
883 # as LFS/Largefile
884 lf_node = file_node.get_largefile_node()
884 lf_node = file_node.get_largefile_node()
885 if lf_node:
885 if lf_node:
886 # overwrite our pointer with the REAL large-file
886 # overwrite our pointer with the REAL large-file
887 file_node = lf_node
887 file_node = lf_node
888
888
889 disposition = self._get_attachement_headers(f_path)
889 disposition = self._get_attachement_headers(f_path)
890
890
891 stream_content = file_node.stream_bytes()
891 stream_content = file_node.stream_bytes()
892
892
893 response = Response(app_iter=stream_content)
893 response = Response(app_iter=stream_content)
894 response.content_disposition = disposition
894 response.content_disposition = disposition
895 response.content_type = file_node.mimetype
895 response.content_type = file_node.mimetype
896
896
897 charset = self._get_default_encoding(c)
897 charset = self._get_default_encoding(c)
898 if charset:
898 if charset:
899 response.charset = charset
899 response.charset = charset
900
900
901 return response
901 return response
902
902
903 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):
904
904
905 cache_seconds = safe_int(
905 cache_seconds = safe_int(
906 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
906 rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
907 cache_on = cache_seconds > 0
907 cache_on = cache_seconds > 0
908 log.debug(
908 log.debug(
909 '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`'
910 'with caching: %s[TTL: %ss]' % (
910 'with caching: %s[TTL: %ss]' % (
911 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))
912
912
913 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
913 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
914 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)
915
915
916 @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)
917 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):
918 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
918 log.debug('Generating cached nodelist for repo_id:%s, %s, %s',
919 _repo_id, commit_id, f_path)
919 _repo_id, commit_id, f_path)
920 try:
920 try:
921 _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)
922 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
922 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
923 log.exception(safe_str(e))
923 log.exception(safe_str(e))
924 h.flash(safe_str(h.escape(e)), category='error')
924 h.flash(safe_str(h.escape(e)), category='error')
925 raise HTTPFound(h.route_path(
925 raise HTTPFound(h.route_path(
926 'repo_files', repo_name=self.db_repo_name,
926 'repo_files', repo_name=self.db_repo_name,
927 commit_id='tip', f_path='/'))
927 commit_id='tip', f_path='/'))
928
928
929 return _d + _f
929 return _d + _f
930
930
931 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,
932 commit_id, f_path)
932 commit_id, f_path)
933 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)
934
934
935 @LoginRequired()
935 @LoginRequired()
936 @HasRepoPermissionAnyDecorator(
936 @HasRepoPermissionAnyDecorator(
937 'repository.read', 'repository.write', 'repository.admin')
937 'repository.read', 'repository.write', 'repository.admin')
938 @view_config(
938 @view_config(
939 route_name='repo_files_nodelist', request_method='GET',
939 route_name='repo_files_nodelist', request_method='GET',
940 renderer='json_ext', xhr=True)
940 renderer='json_ext', xhr=True)
941 def repo_nodelist(self):
941 def repo_nodelist(self):
942 self.load_default_context()
942 self.load_default_context()
943
943
944 commit_id, f_path = self._get_commit_and_path()
944 commit_id, f_path = self._get_commit_and_path()
945 commit = self._get_commit_or_redirect(commit_id)
945 commit = self._get_commit_or_redirect(commit_id)
946
946
947 metadata = self._get_nodelist_at_commit(
947 metadata = self._get_nodelist_at_commit(
948 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
948 self.db_repo_name, self.db_repo.repo_id, commit.raw_id, f_path)
949 return {'nodes': metadata}
949 return {'nodes': metadata}
950
950
951 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
951 def _create_references(self, branches_or_tags, symbolic_reference, f_path, ref_type):
952 items = []
952 items = []
953 for name, commit_id in branches_or_tags.items():
953 for name, commit_id in branches_or_tags.items():
954 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
954 sym_ref = symbolic_reference(commit_id, name, f_path, ref_type)
955 items.append((sym_ref, name, ref_type))
955 items.append((sym_ref, name, ref_type))
956 return items
956 return items
957
957
958 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
958 def _symbolic_reference(self, commit_id, name, f_path, ref_type):
959 return commit_id
959 return commit_id
960
960
961 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
961 def _symbolic_reference_svn(self, commit_id, name, f_path, ref_type):
962 return commit_id
962 return commit_id
963
963
964 # NOTE(dan): old code we used in "diff" mode compare
964 # NOTE(dan): old code we used in "diff" mode compare
965 new_f_path = vcspath.join(name, f_path)
965 new_f_path = vcspath.join(name, f_path)
966 return u'%s@%s' % (new_f_path, commit_id)
966 return u'%s@%s' % (new_f_path, commit_id)
967
967
968 def _get_node_history(self, commit_obj, f_path, commits=None):
968 def _get_node_history(self, commit_obj, f_path, commits=None):
969 """
969 """
970 get commit history for given node
970 get commit history for given node
971
971
972 :param commit_obj: commit to calculate history
972 :param commit_obj: commit to calculate history
973 :param f_path: path for node to calculate history for
973 :param f_path: path for node to calculate history for
974 :param commits: if passed don't calculate history and take
974 :param commits: if passed don't calculate history and take
975 commits defined in this list
975 commits defined in this list
976 """
976 """
977 _ = self.request.translate
977 _ = self.request.translate
978
978
979 # calculate history based on tip
979 # calculate history based on tip
980 tip = self.rhodecode_vcs_repo.get_commit()
980 tip = self.rhodecode_vcs_repo.get_commit()
981 if commits is None:
981 if commits is None:
982 pre_load = ["author", "branch"]
982 pre_load = ["author", "branch"]
983 try:
983 try:
984 commits = tip.get_path_history(f_path, pre_load=pre_load)
984 commits = tip.get_path_history(f_path, pre_load=pre_load)
985 except (NodeDoesNotExistError, CommitError):
985 except (NodeDoesNotExistError, CommitError):
986 # this node is not present at tip!
986 # this node is not present at tip!
987 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
987 commits = commit_obj.get_path_history(f_path, pre_load=pre_load)
988
988
989 history = []
989 history = []
990 commits_group = ([], _("Changesets"))
990 commits_group = ([], _("Changesets"))
991 for commit in commits:
991 for commit in commits:
992 branch = ' (%s)' % commit.branch if commit.branch else ''
992 branch = ' (%s)' % commit.branch if commit.branch else ''
993 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
993 n_desc = 'r%s:%s%s' % (commit.idx, commit.short_id, branch)
994 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
994 commits_group[0].append((commit.raw_id, n_desc, 'sha'))
995 history.append(commits_group)
995 history.append(commits_group)
996
996
997 symbolic_reference = self._symbolic_reference
997 symbolic_reference = self._symbolic_reference
998
998
999 if self.rhodecode_vcs_repo.alias == 'svn':
999 if self.rhodecode_vcs_repo.alias == 'svn':
1000 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1000 adjusted_f_path = RepoFilesView.adjust_file_path_for_svn(
1001 f_path, self.rhodecode_vcs_repo)
1001 f_path, self.rhodecode_vcs_repo)
1002 if adjusted_f_path != f_path:
1002 if adjusted_f_path != f_path:
1003 log.debug(
1003 log.debug(
1004 'Recognized svn tag or branch in file "%s", using svn '
1004 'Recognized svn tag or branch in file "%s", using svn '
1005 'specific symbolic references', f_path)
1005 'specific symbolic references', f_path)
1006 f_path = adjusted_f_path
1006 f_path = adjusted_f_path
1007 symbolic_reference = self._symbolic_reference_svn
1007 symbolic_reference = self._symbolic_reference_svn
1008
1008
1009 branches = self._create_references(
1009 branches = self._create_references(
1010 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1010 self.rhodecode_vcs_repo.branches, symbolic_reference, f_path, 'branch')
1011 branches_group = (branches, _("Branches"))
1011 branches_group = (branches, _("Branches"))
1012
1012
1013 tags = self._create_references(
1013 tags = self._create_references(
1014 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1014 self.rhodecode_vcs_repo.tags, symbolic_reference, f_path, 'tag')
1015 tags_group = (tags, _("Tags"))
1015 tags_group = (tags, _("Tags"))
1016
1016
1017 history.append(branches_group)
1017 history.append(branches_group)
1018 history.append(tags_group)
1018 history.append(tags_group)
1019
1019
1020 return history, commits
1020 return history, commits
1021
1021
1022 @LoginRequired()
1022 @LoginRequired()
1023 @HasRepoPermissionAnyDecorator(
1023 @HasRepoPermissionAnyDecorator(
1024 'repository.read', 'repository.write', 'repository.admin')
1024 'repository.read', 'repository.write', 'repository.admin')
1025 @view_config(
1025 @view_config(
1026 route_name='repo_file_history', request_method='GET',
1026 route_name='repo_file_history', request_method='GET',
1027 renderer='json_ext')
1027 renderer='json_ext')
1028 def repo_file_history(self):
1028 def repo_file_history(self):
1029 self.load_default_context()
1029 self.load_default_context()
1030
1030
1031 commit_id, f_path = self._get_commit_and_path()
1031 commit_id, f_path = self._get_commit_and_path()
1032 commit = self._get_commit_or_redirect(commit_id)
1032 commit = self._get_commit_or_redirect(commit_id)
1033 file_node = self._get_filenode_or_redirect(commit, f_path)
1033 file_node = self._get_filenode_or_redirect(commit, f_path)
1034
1034
1035 if file_node.is_file():
1035 if file_node.is_file():
1036 file_history, _hist = self._get_node_history(commit, f_path)
1036 file_history, _hist = self._get_node_history(commit, f_path)
1037
1037
1038 res = []
1038 res = []
1039 for section_items, section in file_history:
1039 for section_items, section in file_history:
1040 items = []
1040 items = []
1041 for obj_id, obj_text, obj_type in section_items:
1041 for obj_id, obj_text, obj_type in section_items:
1042 at_rev = ''
1042 at_rev = ''
1043 if obj_type in ['branch', 'bookmark', 'tag']:
1043 if obj_type in ['branch', 'bookmark', 'tag']:
1044 at_rev = obj_text
1044 at_rev = obj_text
1045 entry = {
1045 entry = {
1046 'id': obj_id,
1046 'id': obj_id,
1047 'text': obj_text,
1047 'text': obj_text,
1048 'type': obj_type,
1048 'type': obj_type,
1049 'at_rev': at_rev
1049 'at_rev': at_rev
1050 }
1050 }
1051
1051
1052 items.append(entry)
1052 items.append(entry)
1053
1053
1054 res.append({
1054 res.append({
1055 'text': section,
1055 'text': section,
1056 'children': items
1056 'children': items
1057 })
1057 })
1058
1058
1059 data = {
1059 data = {
1060 'more': False,
1060 'more': False,
1061 'results': res
1061 'results': res
1062 }
1062 }
1063 return data
1063 return data
1064
1064
1065 log.warning('Cannot fetch history for directory')
1065 log.warning('Cannot fetch history for directory')
1066 raise HTTPBadRequest()
1066 raise HTTPBadRequest()
1067
1067
1068 @LoginRequired()
1068 @LoginRequired()
1069 @HasRepoPermissionAnyDecorator(
1069 @HasRepoPermissionAnyDecorator(
1070 'repository.read', 'repository.write', 'repository.admin')
1070 'repository.read', 'repository.write', 'repository.admin')
1071 @view_config(
1071 @view_config(
1072 route_name='repo_file_authors', request_method='GET',
1072 route_name='repo_file_authors', request_method='GET',
1073 renderer='rhodecode:templates/files/file_authors_box.mako')
1073 renderer='rhodecode:templates/files/file_authors_box.mako')
1074 def repo_file_authors(self):
1074 def repo_file_authors(self):
1075 c = self.load_default_context()
1075 c = self.load_default_context()
1076
1076
1077 commit_id, f_path = self._get_commit_and_path()
1077 commit_id, f_path = self._get_commit_and_path()
1078 commit = self._get_commit_or_redirect(commit_id)
1078 commit = self._get_commit_or_redirect(commit_id)
1079 file_node = self._get_filenode_or_redirect(commit, f_path)
1079 file_node = self._get_filenode_or_redirect(commit, f_path)
1080
1080
1081 if not file_node.is_file():
1081 if not file_node.is_file():
1082 raise HTTPBadRequest()
1082 raise HTTPBadRequest()
1083
1083
1084 c.file_last_commit = file_node.last_commit
1084 c.file_last_commit = file_node.last_commit
1085 if self.request.GET.get('annotate') == '1':
1085 if self.request.GET.get('annotate') == '1':
1086 # use _hist from annotation if annotation mode is on
1086 # use _hist from annotation if annotation mode is on
1087 commit_ids = set(x[1] for x in file_node.annotate)
1087 commit_ids = set(x[1] for x in file_node.annotate)
1088 _hist = (
1088 _hist = (
1089 self.rhodecode_vcs_repo.get_commit(commit_id)
1089 self.rhodecode_vcs_repo.get_commit(commit_id)
1090 for commit_id in commit_ids)
1090 for commit_id in commit_ids)
1091 else:
1091 else:
1092 _f_history, _hist = self._get_node_history(commit, f_path)
1092 _f_history, _hist = self._get_node_history(commit, f_path)
1093 c.file_author = False
1093 c.file_author = False
1094
1094
1095 unique = collections.OrderedDict()
1095 unique = collections.OrderedDict()
1096 for commit in _hist:
1096 for commit in _hist:
1097 author = commit.author
1097 author = commit.author
1098 if author not in unique:
1098 if author not in unique:
1099 unique[commit.author] = [
1099 unique[commit.author] = [
1100 h.email(author),
1100 h.email(author),
1101 h.person(author, 'username_or_name_or_email'),
1101 h.person(author, 'username_or_name_or_email'),
1102 1 # counter
1102 1 # counter
1103 ]
1103 ]
1104
1104
1105 else:
1105 else:
1106 # increase counter
1106 # increase counter
1107 unique[commit.author][2] += 1
1107 unique[commit.author][2] += 1
1108
1108
1109 c.authors = [val for val in unique.values()]
1109 c.authors = [val for val in unique.values()]
1110
1110
1111 return self._get_template_context(c)
1111 return self._get_template_context(c)
1112
1112
1113 @LoginRequired()
1113 @LoginRequired()
1114 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1114 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1115 @view_config(
1115 @view_config(
1116 route_name='repo_files_check_head', request_method='POST',
1116 route_name='repo_files_check_head', request_method='POST',
1117 renderer='json_ext', xhr=True)
1117 renderer='json_ext', xhr=True)
1118 def repo_files_check_head(self):
1118 def repo_files_check_head(self):
1119 self.load_default_context()
1119 self.load_default_context()
1120
1120
1121 commit_id, f_path = self._get_commit_and_path()
1121 commit_id, f_path = self._get_commit_and_path()
1122 _branch_name, _sha_commit_id, is_head = \
1122 _branch_name, _sha_commit_id, is_head = \
1123 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1123 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1124
1124
1125 new_path = self.request.POST.get('path')
1125 new_path = self.request.POST.get('path')
1126 operation = self.request.POST.get('operation')
1126 operation = self.request.POST.get('operation')
1127 path_exist = ''
1127 path_exist = ''
1128
1128
1129 if new_path and operation in ['create', 'upload']:
1129 if new_path and operation in ['create', 'upload']:
1130 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1130 new_f_path = os.path.join(f_path.lstrip('/'), new_path)
1131 try:
1131 try:
1132 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1132 commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
1133 # NOTE(dan): construct whole path without leading /
1133 # NOTE(dan): construct whole path without leading /
1134 file_node = commit_obj.get_node(new_f_path)
1134 file_node = commit_obj.get_node(new_f_path)
1135 if file_node is not None:
1135 if file_node is not None:
1136 path_exist = new_f_path
1136 path_exist = new_f_path
1137 except EmptyRepositoryError:
1137 except EmptyRepositoryError:
1138 pass
1138 pass
1139 except Exception:
1139 except Exception:
1140 pass
1140 pass
1141
1141
1142 return {
1142 return {
1143 'branch': _branch_name,
1143 'branch': _branch_name,
1144 'sha': _sha_commit_id,
1144 'sha': _sha_commit_id,
1145 'is_head': is_head,
1145 'is_head': is_head,
1146 'path_exists': path_exist
1146 'path_exists': path_exist
1147 }
1147 }
1148
1148
1149 @LoginRequired()
1149 @LoginRequired()
1150 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1150 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1151 @view_config(
1151 @view_config(
1152 route_name='repo_files_remove_file', request_method='GET',
1152 route_name='repo_files_remove_file', request_method='GET',
1153 renderer='rhodecode:templates/files/files_delete.mako')
1153 renderer='rhodecode:templates/files/files_delete.mako')
1154 def repo_files_remove_file(self):
1154 def repo_files_remove_file(self):
1155 _ = self.request.translate
1155 _ = self.request.translate
1156 c = self.load_default_context()
1156 c = self.load_default_context()
1157 commit_id, f_path = self._get_commit_and_path()
1157 commit_id, f_path = self._get_commit_and_path()
1158
1158
1159 self._ensure_not_locked()
1159 self._ensure_not_locked()
1160 _branch_name, _sha_commit_id, is_head = \
1160 _branch_name, _sha_commit_id, is_head = \
1161 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1161 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1162
1162
1163 self.forbid_non_head(is_head, f_path)
1163 self.forbid_non_head(is_head, f_path)
1164 self.check_branch_permission(_branch_name)
1164 self.check_branch_permission(_branch_name)
1165
1165
1166 c.commit = self._get_commit_or_redirect(commit_id)
1166 c.commit = self._get_commit_or_redirect(commit_id)
1167 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1167 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1168
1168
1169 c.default_message = _(
1169 c.default_message = _(
1170 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1170 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1171 c.f_path = f_path
1171 c.f_path = f_path
1172
1172
1173 return self._get_template_context(c)
1173 return self._get_template_context(c)
1174
1174
1175 @LoginRequired()
1175 @LoginRequired()
1176 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1176 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1177 @CSRFRequired()
1177 @CSRFRequired()
1178 @view_config(
1178 @view_config(
1179 route_name='repo_files_delete_file', request_method='POST',
1179 route_name='repo_files_delete_file', request_method='POST',
1180 renderer=None)
1180 renderer=None)
1181 def repo_files_delete_file(self):
1181 def repo_files_delete_file(self):
1182 _ = self.request.translate
1182 _ = self.request.translate
1183
1183
1184 c = self.load_default_context()
1184 c = self.load_default_context()
1185 commit_id, f_path = self._get_commit_and_path()
1185 commit_id, f_path = self._get_commit_and_path()
1186
1186
1187 self._ensure_not_locked()
1187 self._ensure_not_locked()
1188 _branch_name, _sha_commit_id, is_head = \
1188 _branch_name, _sha_commit_id, is_head = \
1189 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1189 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1190
1190
1191 self.forbid_non_head(is_head, f_path)
1191 self.forbid_non_head(is_head, f_path)
1192 self.check_branch_permission(_branch_name)
1192 self.check_branch_permission(_branch_name)
1193
1193
1194 c.commit = self._get_commit_or_redirect(commit_id)
1194 c.commit = self._get_commit_or_redirect(commit_id)
1195 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1195 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1196
1196
1197 c.default_message = _(
1197 c.default_message = _(
1198 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1198 'Deleted file {} via RhodeCode Enterprise').format(f_path)
1199 c.f_path = f_path
1199 c.f_path = f_path
1200 node_path = f_path
1200 node_path = f_path
1201 author = self._rhodecode_db_user.full_contact
1201 author = self._rhodecode_db_user.full_contact
1202 message = self.request.POST.get('message') or c.default_message
1202 message = self.request.POST.get('message') or c.default_message
1203 try:
1203 try:
1204 nodes = {
1204 nodes = {
1205 node_path: {
1205 node_path: {
1206 'content': ''
1206 'content': ''
1207 }
1207 }
1208 }
1208 }
1209 ScmModel().delete_nodes(
1209 ScmModel().delete_nodes(
1210 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1210 user=self._rhodecode_db_user.user_id, repo=self.db_repo,
1211 message=message,
1211 message=message,
1212 nodes=nodes,
1212 nodes=nodes,
1213 parent_commit=c.commit,
1213 parent_commit=c.commit,
1214 author=author,
1214 author=author,
1215 )
1215 )
1216
1216
1217 h.flash(
1217 h.flash(
1218 _('Successfully deleted file `{}`').format(
1218 _('Successfully deleted file `{}`').format(
1219 h.escape(f_path)), category='success')
1219 h.escape(f_path)), category='success')
1220 except Exception:
1220 except Exception:
1221 log.exception('Error during commit operation')
1221 log.exception('Error during commit operation')
1222 h.flash(_('Error occurred during commit'), category='error')
1222 h.flash(_('Error occurred during commit'), category='error')
1223 raise HTTPFound(
1223 raise HTTPFound(
1224 h.route_path('repo_commit', repo_name=self.db_repo_name,
1224 h.route_path('repo_commit', repo_name=self.db_repo_name,
1225 commit_id='tip'))
1225 commit_id='tip'))
1226
1226
1227 @LoginRequired()
1227 @LoginRequired()
1228 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1228 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1229 @view_config(
1229 @view_config(
1230 route_name='repo_files_edit_file', request_method='GET',
1230 route_name='repo_files_edit_file', request_method='GET',
1231 renderer='rhodecode:templates/files/files_edit.mako')
1231 renderer='rhodecode:templates/files/files_edit.mako')
1232 def repo_files_edit_file(self):
1232 def repo_files_edit_file(self):
1233 _ = self.request.translate
1233 _ = self.request.translate
1234 c = self.load_default_context()
1234 c = self.load_default_context()
1235 commit_id, f_path = self._get_commit_and_path()
1235 commit_id, f_path = self._get_commit_and_path()
1236
1236
1237 self._ensure_not_locked()
1237 self._ensure_not_locked()
1238 _branch_name, _sha_commit_id, is_head = \
1238 _branch_name, _sha_commit_id, is_head = \
1239 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1239 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1240
1240
1241 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1241 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1242 self.check_branch_permission(_branch_name, commit_id=commit_id)
1242 self.check_branch_permission(_branch_name, commit_id=commit_id)
1243
1243
1244 c.commit = self._get_commit_or_redirect(commit_id)
1244 c.commit = self._get_commit_or_redirect(commit_id)
1245 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1245 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1246
1246
1247 if c.file.is_binary:
1247 if c.file.is_binary:
1248 files_url = h.route_path(
1248 files_url = h.route_path(
1249 'repo_files',
1249 'repo_files',
1250 repo_name=self.db_repo_name,
1250 repo_name=self.db_repo_name,
1251 commit_id=c.commit.raw_id, f_path=f_path)
1251 commit_id=c.commit.raw_id, f_path=f_path)
1252 raise HTTPFound(files_url)
1252 raise HTTPFound(files_url)
1253
1253
1254 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1254 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1255 c.f_path = f_path
1255 c.f_path = f_path
1256
1256
1257 return self._get_template_context(c)
1257 return self._get_template_context(c)
1258
1258
1259 @LoginRequired()
1259 @LoginRequired()
1260 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1260 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1261 @CSRFRequired()
1261 @CSRFRequired()
1262 @view_config(
1262 @view_config(
1263 route_name='repo_files_update_file', request_method='POST',
1263 route_name='repo_files_update_file', request_method='POST',
1264 renderer=None)
1264 renderer=None)
1265 def repo_files_update_file(self):
1265 def repo_files_update_file(self):
1266 _ = self.request.translate
1266 _ = self.request.translate
1267 c = self.load_default_context()
1267 c = self.load_default_context()
1268 commit_id, f_path = self._get_commit_and_path()
1268 commit_id, f_path = self._get_commit_and_path()
1269
1269
1270 self._ensure_not_locked()
1270 self._ensure_not_locked()
1271
1271
1272 c.commit = self._get_commit_or_redirect(commit_id)
1272 c.commit = self._get_commit_or_redirect(commit_id)
1273 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1273 c.file = self._get_filenode_or_redirect(c.commit, f_path)
1274
1274
1275 if c.file.is_binary:
1275 if c.file.is_binary:
1276 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1276 raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name,
1277 commit_id=c.commit.raw_id, f_path=f_path))
1277 commit_id=c.commit.raw_id, f_path=f_path))
1278
1278
1279 _branch_name, _sha_commit_id, is_head = \
1279 _branch_name, _sha_commit_id, is_head = \
1280 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1280 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1281
1281
1282 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1282 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1283 self.check_branch_permission(_branch_name, commit_id=commit_id)
1283 self.check_branch_permission(_branch_name, commit_id=commit_id)
1284
1284
1285 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1285 c.default_message = _('Edited file {} via RhodeCode Enterprise').format(f_path)
1286 c.f_path = f_path
1286 c.f_path = f_path
1287
1287
1288 old_content = c.file.content
1288 old_content = c.file.content
1289 sl = old_content.splitlines(1)
1289 sl = old_content.splitlines(1)
1290 first_line = sl[0] if sl else ''
1290 first_line = sl[0] if sl else ''
1291
1291
1292 r_post = self.request.POST
1292 r_post = self.request.POST
1293 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1293 # line endings: 0 - Unix, 1 - Mac, 2 - DOS
1294 line_ending_mode = detect_mode(first_line, 0)
1294 line_ending_mode = detect_mode(first_line, 0)
1295 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1295 content = convert_line_endings(r_post.get('content', ''), line_ending_mode)
1296
1296
1297 message = r_post.get('message') or c.default_message
1297 message = r_post.get('message') or c.default_message
1298 org_node_path = c.file.unicode_path
1298 org_node_path = c.file.unicode_path
1299 filename = r_post['filename']
1299 filename = r_post['filename']
1300
1300
1301 root_path = c.file.dir_path
1301 root_path = c.file.dir_path
1302 pure_path = self.create_pure_path(root_path, filename)
1302 pure_path = self.create_pure_path(root_path, filename)
1303 node_path = safe_unicode(bytes(pure_path))
1303 node_path = safe_unicode(bytes(pure_path))
1304
1304
1305 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1305 default_redirect_url = h.route_path('repo_commit', repo_name=self.db_repo_name,
1306 commit_id=commit_id)
1306 commit_id=commit_id)
1307 if content == old_content and node_path == org_node_path:
1307 if content == old_content and node_path == org_node_path:
1308 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1308 h.flash(_('No changes detected on {}').format(h.escape(org_node_path)),
1309 category='warning')
1309 category='warning')
1310 raise HTTPFound(default_redirect_url)
1310 raise HTTPFound(default_redirect_url)
1311
1311
1312 try:
1312 try:
1313 mapping = {
1313 mapping = {
1314 org_node_path: {
1314 org_node_path: {
1315 'org_filename': org_node_path,
1315 'org_filename': org_node_path,
1316 'filename': node_path,
1316 'filename': node_path,
1317 'content': content,
1317 'content': content,
1318 'lexer': '',
1318 'lexer': '',
1319 'op': 'mod',
1319 'op': 'mod',
1320 'mode': c.file.mode
1320 'mode': c.file.mode
1321 }
1321 }
1322 }
1322 }
1323
1323
1324 commit = ScmModel().update_nodes(
1324 commit = ScmModel().update_nodes(
1325 user=self._rhodecode_db_user.user_id,
1325 user=self._rhodecode_db_user.user_id,
1326 repo=self.db_repo,
1326 repo=self.db_repo,
1327 message=message,
1327 message=message,
1328 nodes=mapping,
1328 nodes=mapping,
1329 parent_commit=c.commit,
1329 parent_commit=c.commit,
1330 )
1330 )
1331
1331
1332 h.flash(_('Successfully committed changes to file `{}`').format(
1332 h.flash(_('Successfully committed changes to file `{}`').format(
1333 h.escape(f_path)), category='success')
1333 h.escape(f_path)), category='success')
1334 default_redirect_url = h.route_path(
1334 default_redirect_url = h.route_path(
1335 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1335 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1336
1336
1337 except Exception:
1337 except Exception:
1338 log.exception('Error occurred during commit')
1338 log.exception('Error occurred during commit')
1339 h.flash(_('Error occurred during commit'), category='error')
1339 h.flash(_('Error occurred during commit'), category='error')
1340
1340
1341 raise HTTPFound(default_redirect_url)
1341 raise HTTPFound(default_redirect_url)
1342
1342
1343 @LoginRequired()
1343 @LoginRequired()
1344 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1344 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1345 @view_config(
1345 @view_config(
1346 route_name='repo_files_add_file', request_method='GET',
1346 route_name='repo_files_add_file', request_method='GET',
1347 renderer='rhodecode:templates/files/files_add.mako')
1347 renderer='rhodecode:templates/files/files_add.mako')
1348 @view_config(
1348 @view_config(
1349 route_name='repo_files_upload_file', request_method='GET',
1349 route_name='repo_files_upload_file', request_method='GET',
1350 renderer='rhodecode:templates/files/files_upload.mako')
1350 renderer='rhodecode:templates/files/files_upload.mako')
1351 def repo_files_add_file(self):
1351 def repo_files_add_file(self):
1352 _ = self.request.translate
1352 _ = self.request.translate
1353 c = self.load_default_context()
1353 c = self.load_default_context()
1354 commit_id, f_path = self._get_commit_and_path()
1354 commit_id, f_path = self._get_commit_and_path()
1355
1355
1356 self._ensure_not_locked()
1356 self._ensure_not_locked()
1357
1357
1358 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1358 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1359 if c.commit is None:
1359 if c.commit is None:
1360 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1360 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1361
1361
1362 if self.rhodecode_vcs_repo.is_empty():
1362 if self.rhodecode_vcs_repo.is_empty():
1363 # for empty repository we cannot check for current branch, we rely on
1363 # for empty repository we cannot check for current branch, we rely on
1364 # c.commit.branch instead
1364 # c.commit.branch instead
1365 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1365 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1366 else:
1366 else:
1367 _branch_name, _sha_commit_id, is_head = \
1367 _branch_name, _sha_commit_id, is_head = \
1368 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1368 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1369
1369
1370 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1370 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1371 self.check_branch_permission(_branch_name, commit_id=commit_id)
1371 self.check_branch_permission(_branch_name, commit_id=commit_id)
1372
1372
1373 c.default_message = (_('Added file via RhodeCode Enterprise'))
1373 c.default_message = (_('Added file via RhodeCode Enterprise'))
1374 c.f_path = f_path.lstrip('/') # ensure not relative path
1374 c.f_path = f_path.lstrip('/') # ensure not relative path
1375
1375
1376 return self._get_template_context(c)
1376 return self._get_template_context(c)
1377
1377
1378 @LoginRequired()
1378 @LoginRequired()
1379 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1379 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1380 @CSRFRequired()
1380 @CSRFRequired()
1381 @view_config(
1381 @view_config(
1382 route_name='repo_files_create_file', request_method='POST',
1382 route_name='repo_files_create_file', request_method='POST',
1383 renderer=None)
1383 renderer=None)
1384 def repo_files_create_file(self):
1384 def repo_files_create_file(self):
1385 _ = self.request.translate
1385 _ = self.request.translate
1386 c = self.load_default_context()
1386 c = self.load_default_context()
1387 commit_id, f_path = self._get_commit_and_path()
1387 commit_id, f_path = self._get_commit_and_path()
1388
1388
1389 self._ensure_not_locked()
1389 self._ensure_not_locked()
1390
1390
1391 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1391 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1392 if c.commit is None:
1392 if c.commit is None:
1393 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1393 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1394
1394
1395 # calculate redirect URL
1395 # calculate redirect URL
1396 if self.rhodecode_vcs_repo.is_empty():
1396 if self.rhodecode_vcs_repo.is_empty():
1397 default_redirect_url = h.route_path(
1397 default_redirect_url = h.route_path(
1398 'repo_summary', repo_name=self.db_repo_name)
1398 'repo_summary', repo_name=self.db_repo_name)
1399 else:
1399 else:
1400 default_redirect_url = h.route_path(
1400 default_redirect_url = h.route_path(
1401 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1401 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1402
1402
1403 if self.rhodecode_vcs_repo.is_empty():
1403 if self.rhodecode_vcs_repo.is_empty():
1404 # for empty repository we cannot check for current branch, we rely on
1404 # for empty repository we cannot check for current branch, we rely on
1405 # c.commit.branch instead
1405 # c.commit.branch instead
1406 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1406 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1407 else:
1407 else:
1408 _branch_name, _sha_commit_id, is_head = \
1408 _branch_name, _sha_commit_id, is_head = \
1409 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1409 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1410
1410
1411 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1411 self.forbid_non_head(is_head, f_path, commit_id=commit_id)
1412 self.check_branch_permission(_branch_name, commit_id=commit_id)
1412 self.check_branch_permission(_branch_name, commit_id=commit_id)
1413
1413
1414 c.default_message = (_('Added file via RhodeCode Enterprise'))
1414 c.default_message = (_('Added file via RhodeCode Enterprise'))
1415 c.f_path = f_path
1415 c.f_path = f_path
1416
1416
1417 r_post = self.request.POST
1417 r_post = self.request.POST
1418 message = r_post.get('message') or c.default_message
1418 message = r_post.get('message') or c.default_message
1419 filename = r_post.get('filename')
1419 filename = r_post.get('filename')
1420 unix_mode = 0
1420 unix_mode = 0
1421 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1421 content = convert_line_endings(r_post.get('content', ''), unix_mode)
1422
1422
1423 if not filename:
1423 if not filename:
1424 # If there's no commit, redirect to repo summary
1424 # If there's no commit, redirect to repo summary
1425 if type(c.commit) is EmptyCommit:
1425 if type(c.commit) is EmptyCommit:
1426 redirect_url = h.route_path(
1426 redirect_url = h.route_path(
1427 'repo_summary', repo_name=self.db_repo_name)
1427 'repo_summary', repo_name=self.db_repo_name)
1428 else:
1428 else:
1429 redirect_url = default_redirect_url
1429 redirect_url = default_redirect_url
1430 h.flash(_('No filename specified'), category='warning')
1430 h.flash(_('No filename specified'), category='warning')
1431 raise HTTPFound(redirect_url)
1431 raise HTTPFound(redirect_url)
1432
1432
1433 root_path = f_path
1433 root_path = f_path
1434 pure_path = self.create_pure_path(root_path, filename)
1434 pure_path = self.create_pure_path(root_path, filename)
1435 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1435 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1436
1436
1437 author = self._rhodecode_db_user.full_contact
1437 author = self._rhodecode_db_user.full_contact
1438 nodes = {
1438 nodes = {
1439 node_path: {
1439 node_path: {
1440 'content': content
1440 'content': content
1441 }
1441 }
1442 }
1442 }
1443
1443
1444 try:
1444 try:
1445
1445
1446 commit = ScmModel().create_nodes(
1446 commit = ScmModel().create_nodes(
1447 user=self._rhodecode_db_user.user_id,
1447 user=self._rhodecode_db_user.user_id,
1448 repo=self.db_repo,
1448 repo=self.db_repo,
1449 message=message,
1449 message=message,
1450 nodes=nodes,
1450 nodes=nodes,
1451 parent_commit=c.commit,
1451 parent_commit=c.commit,
1452 author=author,
1452 author=author,
1453 )
1453 )
1454
1454
1455 h.flash(_('Successfully committed new file `{}`').format(
1455 h.flash(_('Successfully committed new file `{}`').format(
1456 h.escape(node_path)), category='success')
1456 h.escape(node_path)), category='success')
1457
1457
1458 default_redirect_url = h.route_path(
1458 default_redirect_url = h.route_path(
1459 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1459 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1460
1460
1461 except NonRelativePathError:
1461 except NonRelativePathError:
1462 log.exception('Non Relative path found')
1462 log.exception('Non Relative path found')
1463 h.flash(_('The location specified must be a relative path and must not '
1463 h.flash(_('The location specified must be a relative path and must not '
1464 'contain .. in the path'), category='warning')
1464 'contain .. in the path'), category='warning')
1465 raise HTTPFound(default_redirect_url)
1465 raise HTTPFound(default_redirect_url)
1466 except (NodeError, NodeAlreadyExistsError) as e:
1466 except (NodeError, NodeAlreadyExistsError) as e:
1467 h.flash(_(h.escape(e)), category='error')
1467 h.flash(_(h.escape(e)), category='error')
1468 except Exception:
1468 except Exception:
1469 log.exception('Error occurred during commit')
1469 log.exception('Error occurred during commit')
1470 h.flash(_('Error occurred during commit'), category='error')
1470 h.flash(_('Error occurred during commit'), category='error')
1471
1471
1472 raise HTTPFound(default_redirect_url)
1472 raise HTTPFound(default_redirect_url)
1473
1473
1474 @LoginRequired()
1474 @LoginRequired()
1475 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1475 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
1476 @CSRFRequired()
1476 @CSRFRequired()
1477 @view_config(
1477 @view_config(
1478 route_name='repo_files_upload_file', request_method='POST',
1478 route_name='repo_files_upload_file', request_method='POST',
1479 renderer='json_ext')
1479 renderer='json_ext')
1480 def repo_files_upload_file(self):
1480 def repo_files_upload_file(self):
1481 _ = self.request.translate
1481 _ = self.request.translate
1482 c = self.load_default_context()
1482 c = self.load_default_context()
1483 commit_id, f_path = self._get_commit_and_path()
1483 commit_id, f_path = self._get_commit_and_path()
1484
1484
1485 self._ensure_not_locked()
1485 self._ensure_not_locked()
1486
1486
1487 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1487 c.commit = self._get_commit_or_redirect(commit_id, redirect_after=False)
1488 if c.commit is None:
1488 if c.commit is None:
1489 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1489 c.commit = EmptyCommit(alias=self.rhodecode_vcs_repo.alias)
1490
1490
1491 # calculate redirect URL
1491 # calculate redirect URL
1492 if self.rhodecode_vcs_repo.is_empty():
1492 if self.rhodecode_vcs_repo.is_empty():
1493 default_redirect_url = h.route_path(
1493 default_redirect_url = h.route_path(
1494 'repo_summary', repo_name=self.db_repo_name)
1494 'repo_summary', repo_name=self.db_repo_name)
1495 else:
1495 else:
1496 default_redirect_url = h.route_path(
1496 default_redirect_url = h.route_path(
1497 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1497 'repo_commit', repo_name=self.db_repo_name, commit_id='tip')
1498
1498
1499 if self.rhodecode_vcs_repo.is_empty():
1499 if self.rhodecode_vcs_repo.is_empty():
1500 # for empty repository we cannot check for current branch, we rely on
1500 # for empty repository we cannot check for current branch, we rely on
1501 # c.commit.branch instead
1501 # c.commit.branch instead
1502 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1502 _branch_name, _sha_commit_id, is_head = c.commit.branch, '', True
1503 else:
1503 else:
1504 _branch_name, _sha_commit_id, is_head = \
1504 _branch_name, _sha_commit_id, is_head = \
1505 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1505 self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
1506
1506
1507 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1507 error = self.forbid_non_head(is_head, f_path, json_mode=True)
1508 if error:
1508 if error:
1509 return {
1509 return {
1510 'error': error,
1510 'error': error,
1511 'redirect_url': default_redirect_url
1511 'redirect_url': default_redirect_url
1512 }
1512 }
1513 error = self.check_branch_permission(_branch_name, json_mode=True)
1513 error = self.check_branch_permission(_branch_name, json_mode=True)
1514 if error:
1514 if error:
1515 return {
1515 return {
1516 'error': error,
1516 'error': error,
1517 'redirect_url': default_redirect_url
1517 'redirect_url': default_redirect_url
1518 }
1518 }
1519
1519
1520 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1520 c.default_message = (_('Uploaded file via RhodeCode Enterprise'))
1521 c.f_path = f_path
1521 c.f_path = f_path
1522
1522
1523 r_post = self.request.POST
1523 r_post = self.request.POST
1524
1524
1525 message = c.default_message
1525 message = c.default_message
1526 user_message = r_post.getall('message')
1526 user_message = r_post.getall('message')
1527 if isinstance(user_message, list) and user_message:
1527 if isinstance(user_message, list) and user_message:
1528 # we take the first from duplicated results if it's not empty
1528 # we take the first from duplicated results if it's not empty
1529 message = user_message[0] if user_message[0] else message
1529 message = user_message[0] if user_message[0] else message
1530
1530
1531 nodes = {}
1531 nodes = {}
1532
1532
1533 for file_obj in r_post.getall('files_upload') or []:
1533 for file_obj in r_post.getall('files_upload') or []:
1534 content = file_obj.file
1534 content = file_obj.file
1535 filename = file_obj.filename
1535 filename = file_obj.filename
1536
1536
1537 root_path = f_path
1537 root_path = f_path
1538 pure_path = self.create_pure_path(root_path, filename)
1538 pure_path = self.create_pure_path(root_path, filename)
1539 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1539 node_path = safe_unicode(bytes(pure_path).lstrip('/'))
1540
1540
1541 nodes[node_path] = {
1541 nodes[node_path] = {
1542 'content': content
1542 'content': content
1543 }
1543 }
1544
1544
1545 if not nodes:
1545 if not nodes:
1546 error = 'missing files'
1546 error = 'missing files'
1547 return {
1547 return {
1548 'error': error,
1548 'error': error,
1549 'redirect_url': default_redirect_url
1549 'redirect_url': default_redirect_url
1550 }
1550 }
1551
1551
1552 author = self._rhodecode_db_user.full_contact
1552 author = self._rhodecode_db_user.full_contact
1553
1553
1554 try:
1554 try:
1555 commit = ScmModel().create_nodes(
1555 commit = ScmModel().create_nodes(
1556 user=self._rhodecode_db_user.user_id,
1556 user=self._rhodecode_db_user.user_id,
1557 repo=self.db_repo,
1557 repo=self.db_repo,
1558 message=message,
1558 message=message,
1559 nodes=nodes,
1559 nodes=nodes,
1560 parent_commit=c.commit,
1560 parent_commit=c.commit,
1561 author=author,
1561 author=author,
1562 )
1562 )
1563 if len(nodes) == 1:
1563 if len(nodes) == 1:
1564 flash_message = _('Successfully committed {} new files').format(len(nodes))
1564 flash_message = _('Successfully committed {} new files').format(len(nodes))
1565 else:
1565 else:
1566 flash_message = _('Successfully committed 1 new file')
1566 flash_message = _('Successfully committed 1 new file')
1567
1567
1568 h.flash(flash_message, category='success')
1568 h.flash(flash_message, category='success')
1569
1569
1570 default_redirect_url = h.route_path(
1570 default_redirect_url = h.route_path(
1571 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1571 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id)
1572
1572
1573 except NonRelativePathError:
1573 except NonRelativePathError:
1574 log.exception('Non Relative path found')
1574 log.exception('Non Relative path found')
1575 error = _('The location specified must be a relative path and must not '
1575 error = _('The location specified must be a relative path and must not '
1576 'contain .. in the path')
1576 'contain .. in the path')
1577 h.flash(error, category='warning')
1577 h.flash(error, category='warning')
1578
1578
1579 return {
1579 return {
1580 'error': error,
1580 'error': error,
1581 'redirect_url': default_redirect_url
1581 'redirect_url': default_redirect_url
1582 }
1582 }
1583 except (NodeError, NodeAlreadyExistsError) as e:
1583 except (NodeError, NodeAlreadyExistsError) as e:
1584 error = h.escape(e)
1584 error = h.escape(e)
1585 h.flash(error, category='error')
1585 h.flash(error, category='error')
1586
1586
1587 return {
1587 return {
1588 'error': error,
1588 'error': error,
1589 'redirect_url': default_redirect_url
1589 'redirect_url': default_redirect_url
1590 }
1590 }
1591 except Exception:
1591 except Exception:
1592 log.exception('Error occurred during commit')
1592 log.exception('Error occurred during commit')
1593 error = _('Error occurred during commit')
1593 error = _('Error occurred during commit')
1594 h.flash(error, category='error')
1594 h.flash(error, category='error')
1595 return {
1595 return {
1596 'error': error,
1596 'error': error,
1597 'redirect_url': default_redirect_url
1597 'redirect_url': default_redirect_url
1598 }
1598 }
1599
1599
1600 return {
1600 return {
1601 'error': None,
1601 'error': None,
1602 'redirect_url': default_redirect_url
1602 'redirect_url': default_redirect_url
1603 }
1603 }
@@ -1,266 +1,266 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 logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
34 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous,
35 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
35 HasRepoPermissionAny, HasPermissionAnyDecorator, CSRFRequired)
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.celerylib.utils import get_task_id
37 from rhodecode.lib.celerylib.utils import get_task_id
38 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
38 from rhodecode.model.db import coalesce, or_, Repository, RepoGroup
39 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.forms import RepoForkForm
41 from rhodecode.model.forms import RepoForkForm
42 from rhodecode.model.scm import ScmModel, RepoGroupList
42 from rhodecode.model.scm import ScmModel, RepoGroupList
43 from rhodecode.lib.utils2 import safe_int, safe_unicode
43 from rhodecode.lib.utils2 import safe_int, safe_unicode
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class RepoForksView(RepoAppView, DataGridAppView):
48 class RepoForksView(RepoAppView, DataGridAppView):
49
49
50 def load_default_context(self):
50 def load_default_context(self):
51 c = self._get_local_tmpl_context(include_app_defaults=True)
51 c = self._get_local_tmpl_context(include_app_defaults=True)
52 c.rhodecode_repo = self.rhodecode_vcs_repo
52 c.rhodecode_repo = self.rhodecode_vcs_repo
53
53
54 acl_groups = RepoGroupList(
54 acl_groups = RepoGroupList(
55 RepoGroup.query().all(),
55 RepoGroup.query().all(),
56 perm_set=['group.write', 'group.admin'])
56 perm_set=['group.write', 'group.admin'])
57 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
57 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
58 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
58 c.repo_groups_choices = map(lambda k: safe_unicode(k[0]), c.repo_groups)
59
59
60 c.personal_repo_group = c.rhodecode_user.personal_repo_group
60 c.personal_repo_group = c.rhodecode_user.personal_repo_group
61
61
62 return c
62 return c
63
63
64 @LoginRequired()
64 @LoginRequired()
65 @HasRepoPermissionAnyDecorator(
65 @HasRepoPermissionAnyDecorator(
66 'repository.read', 'repository.write', 'repository.admin')
66 'repository.read', 'repository.write', 'repository.admin')
67 @view_config(
67 @view_config(
68 route_name='repo_forks_show_all', request_method='GET',
68 route_name='repo_forks_show_all', request_method='GET',
69 renderer='rhodecode:templates/forks/forks.mako')
69 renderer='rhodecode:templates/forks/forks.mako')
70 def repo_forks_show_all(self):
70 def repo_forks_show_all(self):
71 c = self.load_default_context()
71 c = self.load_default_context()
72 return self._get_template_context(c)
72 return self._get_template_context(c)
73
73
74 @LoginRequired()
74 @LoginRequired()
75 @HasRepoPermissionAnyDecorator(
75 @HasRepoPermissionAnyDecorator(
76 'repository.read', 'repository.write', 'repository.admin')
76 'repository.read', 'repository.write', 'repository.admin')
77 @view_config(
77 @view_config(
78 route_name='repo_forks_data', request_method='GET',
78 route_name='repo_forks_data', request_method='GET',
79 renderer='json_ext', xhr=True)
79 renderer='json_ext', xhr=True)
80 def repo_forks_data(self):
80 def repo_forks_data(self):
81 _ = self.request.translate
81 _ = self.request.translate
82 self.load_default_context()
82 self.load_default_context()
83 column_map = {
83 column_map = {
84 'fork_name': 'repo_name',
84 'fork_name': 'repo_name',
85 'fork_date': 'created_on',
85 'fork_date': 'created_on',
86 'last_activity': 'updated_on'
86 'last_activity': 'updated_on'
87 }
87 }
88 draw, start, limit = self._extract_chunk(self.request)
88 draw, start, limit = self._extract_chunk(self.request)
89 search_q, order_by, order_dir = self._extract_ordering(
89 search_q, order_by, order_dir = self._extract_ordering(
90 self.request, column_map=column_map)
90 self.request, column_map=column_map)
91
91
92 acl_check = HasRepoPermissionAny(
92 acl_check = HasRepoPermissionAny(
93 'repository.read', 'repository.write', 'repository.admin')
93 'repository.read', 'repository.write', 'repository.admin')
94 repo_id = self.db_repo.repo_id
94 repo_id = self.db_repo.repo_id
95 allowed_ids = [-1]
95 allowed_ids = [-1]
96 for f in Repository.query().filter(Repository.fork_id == repo_id):
96 for f in Repository.query().filter(Repository.fork_id == repo_id):
97 if acl_check(f.repo_name, 'get forks check'):
97 if acl_check(f.repo_name, 'get forks check'):
98 allowed_ids.append(f.repo_id)
98 allowed_ids.append(f.repo_id)
99
99
100 forks_data_total_count = Repository.query()\
100 forks_data_total_count = Repository.query()\
101 .filter(Repository.fork_id == repo_id)\
101 .filter(Repository.fork_id == repo_id)\
102 .filter(Repository.repo_id.in_(allowed_ids))\
102 .filter(Repository.repo_id.in_(allowed_ids))\
103 .count()
103 .count()
104
104
105 # json generate
105 # json generate
106 base_q = Repository.query()\
106 base_q = Repository.query()\
107 .filter(Repository.fork_id == repo_id)\
107 .filter(Repository.fork_id == repo_id)\
108 .filter(Repository.repo_id.in_(allowed_ids))\
108 .filter(Repository.repo_id.in_(allowed_ids))\
109
109
110 if search_q:
110 if search_q:
111 like_expression = u'%{}%'.format(safe_unicode(search_q))
111 like_expression = u'%{}%'.format(safe_unicode(search_q))
112 base_q = base_q.filter(or_(
112 base_q = base_q.filter(or_(
113 Repository.repo_name.ilike(like_expression),
113 Repository.repo_name.ilike(like_expression),
114 Repository.description.ilike(like_expression),
114 Repository.description.ilike(like_expression),
115 ))
115 ))
116
116
117 forks_data_total_filtered_count = base_q.count()
117 forks_data_total_filtered_count = base_q.count()
118
118
119 sort_col = getattr(Repository, order_by, None)
119 sort_col = getattr(Repository, order_by, None)
120 if sort_col:
120 if sort_col:
121 if order_dir == 'asc':
121 if order_dir == 'asc':
122 # handle null values properly to order by NULL last
122 # handle null values properly to order by NULL last
123 if order_by in ['last_activity']:
123 if order_by in ['last_activity']:
124 sort_col = coalesce(sort_col, datetime.date.max)
124 sort_col = coalesce(sort_col, datetime.date.max)
125 sort_col = sort_col.asc()
125 sort_col = sort_col.asc()
126 else:
126 else:
127 # handle null values properly to order by NULL last
127 # handle null values properly to order by NULL last
128 if order_by in ['last_activity']:
128 if order_by in ['last_activity']:
129 sort_col = coalesce(sort_col, datetime.date.min)
129 sort_col = coalesce(sort_col, datetime.date.min)
130 sort_col = sort_col.desc()
130 sort_col = sort_col.desc()
131
131
132 base_q = base_q.order_by(sort_col)
132 base_q = base_q.order_by(sort_col)
133 base_q = base_q.offset(start).limit(limit)
133 base_q = base_q.offset(start).limit(limit)
134
134
135 fork_list = base_q.all()
135 fork_list = base_q.all()
136
136
137 def fork_actions(fork):
137 def fork_actions(fork):
138 url_link = h.route_path(
138 url_link = h.route_path(
139 'repo_compare',
139 'repo_compare',
140 repo_name=fork.repo_name,
140 repo_name=fork.repo_name,
141 source_ref_type=self.db_repo.landing_rev[0],
141 source_ref_type=self.db_repo.landing_ref_type,
142 source_ref=self.db_repo.landing_rev[1],
142 source_ref=self.db_repo.landing_ref_name,
143 target_ref_type=self.db_repo.landing_rev[0],
143 target_ref_type=self.db_repo.landing_ref_type,
144 target_ref=self.db_repo.landing_rev[1],
144 target_ref=self.db_repo.landing_ref_name,
145 _query=dict(merge=1, target_repo=f.repo_name))
145 _query=dict(merge=1, target_repo=f.repo_name))
146 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
146 return h.link_to(_('Compare fork'), url_link, class_='btn-link')
147
147
148 def fork_name(fork):
148 def fork_name(fork):
149 return h.link_to(fork.repo_name,
149 return h.link_to(fork.repo_name,
150 h.route_path('repo_summary', repo_name=fork.repo_name))
150 h.route_path('repo_summary', repo_name=fork.repo_name))
151
151
152 forks_data = []
152 forks_data = []
153 for fork in fork_list:
153 for fork in fork_list:
154 forks_data.append({
154 forks_data.append({
155 "username": h.gravatar_with_user(self.request, fork.user.username),
155 "username": h.gravatar_with_user(self.request, fork.user.username),
156 "fork_name": fork_name(fork),
156 "fork_name": fork_name(fork),
157 "description": fork.description_safe,
157 "description": fork.description_safe,
158 "fork_date": h.age_component(fork.created_on, time_is_local=True),
158 "fork_date": h.age_component(fork.created_on, time_is_local=True),
159 "last_activity": h.format_date(fork.updated_on),
159 "last_activity": h.format_date(fork.updated_on),
160 "action": fork_actions(fork),
160 "action": fork_actions(fork),
161 })
161 })
162
162
163 data = ({
163 data = ({
164 'draw': draw,
164 'draw': draw,
165 'data': forks_data,
165 'data': forks_data,
166 'recordsTotal': forks_data_total_count,
166 'recordsTotal': forks_data_total_count,
167 'recordsFiltered': forks_data_total_filtered_count,
167 'recordsFiltered': forks_data_total_filtered_count,
168 })
168 })
169
169
170 return data
170 return data
171
171
172 @LoginRequired()
172 @LoginRequired()
173 @NotAnonymous()
173 @NotAnonymous()
174 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
174 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
175 @HasRepoPermissionAnyDecorator(
175 @HasRepoPermissionAnyDecorator(
176 'repository.read', 'repository.write', 'repository.admin')
176 'repository.read', 'repository.write', 'repository.admin')
177 @view_config(
177 @view_config(
178 route_name='repo_fork_new', request_method='GET',
178 route_name='repo_fork_new', request_method='GET',
179 renderer='rhodecode:templates/forks/forks.mako')
179 renderer='rhodecode:templates/forks/forks.mako')
180 def repo_fork_new(self):
180 def repo_fork_new(self):
181 c = self.load_default_context()
181 c = self.load_default_context()
182
182
183 defaults = RepoModel()._get_defaults(self.db_repo_name)
183 defaults = RepoModel()._get_defaults(self.db_repo_name)
184 # alter the description to indicate a fork
184 # alter the description to indicate a fork
185 defaults['description'] = (
185 defaults['description'] = (
186 'fork of repository: %s \n%s' % (
186 'fork of repository: %s \n%s' % (
187 defaults['repo_name'], defaults['description']))
187 defaults['repo_name'], defaults['description']))
188 # add suffix to fork
188 # add suffix to fork
189 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
189 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
190
190
191 data = render('rhodecode:templates/forks/fork.mako',
191 data = render('rhodecode:templates/forks/fork.mako',
192 self._get_template_context(c), self.request)
192 self._get_template_context(c), self.request)
193 html = formencode.htmlfill.render(
193 html = formencode.htmlfill.render(
194 data,
194 data,
195 defaults=defaults,
195 defaults=defaults,
196 encoding="UTF-8",
196 encoding="UTF-8",
197 force_defaults=False
197 force_defaults=False
198 )
198 )
199 return Response(html)
199 return Response(html)
200
200
201 @LoginRequired()
201 @LoginRequired()
202 @NotAnonymous()
202 @NotAnonymous()
203 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
203 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
204 @HasRepoPermissionAnyDecorator(
204 @HasRepoPermissionAnyDecorator(
205 'repository.read', 'repository.write', 'repository.admin')
205 'repository.read', 'repository.write', 'repository.admin')
206 @CSRFRequired()
206 @CSRFRequired()
207 @view_config(
207 @view_config(
208 route_name='repo_fork_create', request_method='POST',
208 route_name='repo_fork_create', request_method='POST',
209 renderer='rhodecode:templates/forks/fork.mako')
209 renderer='rhodecode:templates/forks/fork.mako')
210 def repo_fork_create(self):
210 def repo_fork_create(self):
211 _ = self.request.translate
211 _ = self.request.translate
212 c = self.load_default_context()
212 c = self.load_default_context()
213
213
214 _form = RepoForkForm(self.request.translate,
214 _form = RepoForkForm(self.request.translate,
215 old_data={'repo_type': self.db_repo.repo_type},
215 old_data={'repo_type': self.db_repo.repo_type},
216 repo_groups=c.repo_groups_choices)()
216 repo_groups=c.repo_groups_choices)()
217 post_data = dict(self.request.POST)
217 post_data = dict(self.request.POST)
218
218
219 # forbid injecting other repo by forging a request
219 # forbid injecting other repo by forging a request
220 post_data['fork_parent_id'] = self.db_repo.repo_id
220 post_data['fork_parent_id'] = self.db_repo.repo_id
221 post_data['landing_rev'] = self.db_repo._landing_revision
221 post_data['landing_rev'] = self.db_repo._landing_revision
222
222
223 form_result = {}
223 form_result = {}
224 task_id = None
224 task_id = None
225 try:
225 try:
226 form_result = _form.to_python(post_data)
226 form_result = _form.to_python(post_data)
227 copy_permissions = form_result.get('copy_permissions')
227 copy_permissions = form_result.get('copy_permissions')
228 # create fork is done sometimes async on celery, db transaction
228 # create fork is done sometimes async on celery, db transaction
229 # management is handled there.
229 # management is handled there.
230 task = RepoModel().create_fork(
230 task = RepoModel().create_fork(
231 form_result, c.rhodecode_user.user_id)
231 form_result, c.rhodecode_user.user_id)
232
232
233 task_id = get_task_id(task)
233 task_id = get_task_id(task)
234 except formencode.Invalid as errors:
234 except formencode.Invalid as errors:
235 c.rhodecode_db_repo = self.db_repo
235 c.rhodecode_db_repo = self.db_repo
236
236
237 data = render('rhodecode:templates/forks/fork.mako',
237 data = render('rhodecode:templates/forks/fork.mako',
238 self._get_template_context(c), self.request)
238 self._get_template_context(c), self.request)
239 html = formencode.htmlfill.render(
239 html = formencode.htmlfill.render(
240 data,
240 data,
241 defaults=errors.value,
241 defaults=errors.value,
242 errors=errors.error_dict or {},
242 errors=errors.error_dict or {},
243 prefix_error=False,
243 prefix_error=False,
244 encoding="UTF-8",
244 encoding="UTF-8",
245 force_defaults=False
245 force_defaults=False
246 )
246 )
247 return Response(html)
247 return Response(html)
248 except Exception:
248 except Exception:
249 log.exception(
249 log.exception(
250 u'Exception while trying to fork the repository %s', self.db_repo_name)
250 u'Exception while trying to fork the repository %s', self.db_repo_name)
251 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
251 msg = _('An error occurred during repository forking %s') % (self.db_repo_name, )
252 h.flash(msg, category='error')
252 h.flash(msg, category='error')
253 raise HTTPFound(h.route_path('home'))
253 raise HTTPFound(h.route_path('home'))
254
254
255 repo_name = form_result.get('repo_name_full', self.db_repo_name)
255 repo_name = form_result.get('repo_name_full', self.db_repo_name)
256
256
257 affected_user_ids = [self._rhodecode_user.user_id]
257 affected_user_ids = [self._rhodecode_user.user_id]
258 if copy_permissions:
258 if copy_permissions:
259 # permission flush is done in repo creating
259 # permission flush is done in repo creating
260 pass
260 pass
261
261
262 PermissionModel().trigger_permission_flush(affected_user_ids)
262 PermissionModel().trigger_permission_flush(affected_user_ids)
263
263
264 raise HTTPFound(
264 raise HTTPFound(
265 h.route_path('repo_creating', repo_name=repo_name,
265 h.route_path('repo_creating', repo_name=repo_name,
266 _query=dict(task_id=task_id)))
266 _query=dict(task_id=task_id)))
@@ -1,5594 +1,5602 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 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers2.text import remove_formatting
55 from webhelpers2.text import remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.exceptions import (
70 from rhodecode.lib.exceptions import (
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
71 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 from rhodecode.model.meta import Base, Session
72 from rhodecode.model.meta import Base, Session
73
73
74 URL_SEP = '/'
74 URL_SEP = '/'
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76
76
77 # =============================================================================
77 # =============================================================================
78 # BASE CLASSES
78 # BASE CLASSES
79 # =============================================================================
79 # =============================================================================
80
80
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
81 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # beaker.session.secret if first is not set.
82 # beaker.session.secret if first is not set.
83 # and initialized at environment.py
83 # and initialized at environment.py
84 ENCRYPTION_KEY = None
84 ENCRYPTION_KEY = None
85
85
86 # used to sort permissions by types, '#' used here is not allowed to be in
86 # used to sort permissions by types, '#' used here is not allowed to be in
87 # usernames, and it's very early in sorted string.printable table.
87 # usernames, and it's very early in sorted string.printable table.
88 PERMISSION_TYPE_SORT = {
88 PERMISSION_TYPE_SORT = {
89 'admin': '####',
89 'admin': '####',
90 'write': '###',
90 'write': '###',
91 'read': '##',
91 'read': '##',
92 'none': '#',
92 'none': '#',
93 }
93 }
94
94
95
95
96 def display_user_sort(obj):
96 def display_user_sort(obj):
97 """
97 """
98 Sort function used to sort permissions in .permissions() function of
98 Sort function used to sort permissions in .permissions() function of
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
99 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 of all other resources
100 of all other resources
101 """
101 """
102
102
103 if obj.username == User.DEFAULT_USER:
103 if obj.username == User.DEFAULT_USER:
104 return '#####'
104 return '#####'
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
105 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 return prefix + obj.username
106 return prefix + obj.username
107
107
108
108
109 def display_user_group_sort(obj):
109 def display_user_group_sort(obj):
110 """
110 """
111 Sort function used to sort permissions in .permissions() function of
111 Sort function used to sort permissions in .permissions() function of
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
112 Repository, RepoGroup, UserGroup. Also it put the default user in front
113 of all other resources
113 of all other resources
114 """
114 """
115
115
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
116 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
117 return prefix + obj.users_group_name
117 return prefix + obj.users_group_name
118
118
119
119
120 def _hash_key(k):
120 def _hash_key(k):
121 return sha1_safe(k)
121 return sha1_safe(k)
122
122
123
123
124 def in_filter_generator(qry, items, limit=500):
124 def in_filter_generator(qry, items, limit=500):
125 """
125 """
126 Splits IN() into multiple with OR
126 Splits IN() into multiple with OR
127 e.g.::
127 e.g.::
128 cnt = Repository.query().filter(
128 cnt = Repository.query().filter(
129 or_(
129 or_(
130 *in_filter_generator(Repository.repo_id, range(100000))
130 *in_filter_generator(Repository.repo_id, range(100000))
131 )).count()
131 )).count()
132 """
132 """
133 if not items:
133 if not items:
134 # empty list will cause empty query which might cause security issues
134 # empty list will cause empty query which might cause security issues
135 # this can lead to hidden unpleasant results
135 # this can lead to hidden unpleasant results
136 items = [-1]
136 items = [-1]
137
137
138 parts = []
138 parts = []
139 for chunk in xrange(0, len(items), limit):
139 for chunk in xrange(0, len(items), limit):
140 parts.append(
140 parts.append(
141 qry.in_(items[chunk: chunk + limit])
141 qry.in_(items[chunk: chunk + limit])
142 )
142 )
143
143
144 return parts
144 return parts
145
145
146
146
147 base_table_args = {
147 base_table_args = {
148 'extend_existing': True,
148 'extend_existing': True,
149 'mysql_engine': 'InnoDB',
149 'mysql_engine': 'InnoDB',
150 'mysql_charset': 'utf8',
150 'mysql_charset': 'utf8',
151 'sqlite_autoincrement': True
151 'sqlite_autoincrement': True
152 }
152 }
153
153
154
154
155 class EncryptedTextValue(TypeDecorator):
155 class EncryptedTextValue(TypeDecorator):
156 """
156 """
157 Special column for encrypted long text data, use like::
157 Special column for encrypted long text data, use like::
158
158
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
159 value = Column("encrypted_value", EncryptedValue(), nullable=False)
160
160
161 This column is intelligent so if value is in unencrypted form it return
161 This column is intelligent so if value is in unencrypted form it return
162 unencrypted form, but on save it always encrypts
162 unencrypted form, but on save it always encrypts
163 """
163 """
164 impl = Text
164 impl = Text
165
165
166 def process_bind_param(self, value, dialect):
166 def process_bind_param(self, value, dialect):
167 """
167 """
168 Setter for storing value
168 Setter for storing value
169 """
169 """
170 import rhodecode
170 import rhodecode
171 if not value:
171 if not value:
172 return value
172 return value
173
173
174 # protect against double encrypting if values is already encrypted
174 # protect against double encrypting if values is already encrypted
175 if value.startswith('enc$aes$') \
175 if value.startswith('enc$aes$') \
176 or value.startswith('enc$aes_hmac$') \
176 or value.startswith('enc$aes_hmac$') \
177 or value.startswith('enc2$'):
177 or value.startswith('enc2$'):
178 raise ValueError('value needs to be in unencrypted format, '
178 raise ValueError('value needs to be in unencrypted format, '
179 'ie. not starting with enc$ or enc2$')
179 'ie. not starting with enc$ or enc2$')
180
180
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
181 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
182 if algo == 'aes':
182 if algo == 'aes':
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
183 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
184 elif algo == 'fernet':
184 elif algo == 'fernet':
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
185 return Encryptor(ENCRYPTION_KEY).encrypt(value)
186 else:
186 else:
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
187 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
188
188
189 def process_result_value(self, value, dialect):
189 def process_result_value(self, value, dialect):
190 """
190 """
191 Getter for retrieving value
191 Getter for retrieving value
192 """
192 """
193
193
194 import rhodecode
194 import rhodecode
195 if not value:
195 if not value:
196 return value
196 return value
197
197
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
198 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
199 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
200 if algo == 'aes':
200 if algo == 'aes':
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
201 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
202 elif algo == 'fernet':
202 elif algo == 'fernet':
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
203 return Encryptor(ENCRYPTION_KEY).decrypt(value)
204 else:
204 else:
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
205 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
206 return decrypted_data
206 return decrypted_data
207
207
208
208
209 class BaseModel(object):
209 class BaseModel(object):
210 """
210 """
211 Base Model for all classes
211 Base Model for all classes
212 """
212 """
213
213
214 @classmethod
214 @classmethod
215 def _get_keys(cls):
215 def _get_keys(cls):
216 """return column names for this model """
216 """return column names for this model """
217 return class_mapper(cls).c.keys()
217 return class_mapper(cls).c.keys()
218
218
219 def get_dict(self):
219 def get_dict(self):
220 """
220 """
221 return dict with keys and values corresponding
221 return dict with keys and values corresponding
222 to this model data """
222 to this model data """
223
223
224 d = {}
224 d = {}
225 for k in self._get_keys():
225 for k in self._get_keys():
226 d[k] = getattr(self, k)
226 d[k] = getattr(self, k)
227
227
228 # also use __json__() if present to get additional fields
228 # also use __json__() if present to get additional fields
229 _json_attr = getattr(self, '__json__', None)
229 _json_attr = getattr(self, '__json__', None)
230 if _json_attr:
230 if _json_attr:
231 # update with attributes from __json__
231 # update with attributes from __json__
232 if callable(_json_attr):
232 if callable(_json_attr):
233 _json_attr = _json_attr()
233 _json_attr = _json_attr()
234 for k, val in _json_attr.iteritems():
234 for k, val in _json_attr.iteritems():
235 d[k] = val
235 d[k] = val
236 return d
236 return d
237
237
238 def get_appstruct(self):
238 def get_appstruct(self):
239 """return list with keys and values tuples corresponding
239 """return list with keys and values tuples corresponding
240 to this model data """
240 to this model data """
241
241
242 lst = []
242 lst = []
243 for k in self._get_keys():
243 for k in self._get_keys():
244 lst.append((k, getattr(self, k),))
244 lst.append((k, getattr(self, k),))
245 return lst
245 return lst
246
246
247 def populate_obj(self, populate_dict):
247 def populate_obj(self, populate_dict):
248 """populate model with data from given populate_dict"""
248 """populate model with data from given populate_dict"""
249
249
250 for k in self._get_keys():
250 for k in self._get_keys():
251 if k in populate_dict:
251 if k in populate_dict:
252 setattr(self, k, populate_dict[k])
252 setattr(self, k, populate_dict[k])
253
253
254 @classmethod
254 @classmethod
255 def query(cls):
255 def query(cls):
256 return Session().query(cls)
256 return Session().query(cls)
257
257
258 @classmethod
258 @classmethod
259 def get(cls, id_):
259 def get(cls, id_):
260 if id_:
260 if id_:
261 return cls.query().get(id_)
261 return cls.query().get(id_)
262
262
263 @classmethod
263 @classmethod
264 def get_or_404(cls, id_):
264 def get_or_404(cls, id_):
265 from pyramid.httpexceptions import HTTPNotFound
265 from pyramid.httpexceptions import HTTPNotFound
266
266
267 try:
267 try:
268 id_ = int(id_)
268 id_ = int(id_)
269 except (TypeError, ValueError):
269 except (TypeError, ValueError):
270 raise HTTPNotFound()
270 raise HTTPNotFound()
271
271
272 res = cls.query().get(id_)
272 res = cls.query().get(id_)
273 if not res:
273 if not res:
274 raise HTTPNotFound()
274 raise HTTPNotFound()
275 return res
275 return res
276
276
277 @classmethod
277 @classmethod
278 def getAll(cls):
278 def getAll(cls):
279 # deprecated and left for backward compatibility
279 # deprecated and left for backward compatibility
280 return cls.get_all()
280 return cls.get_all()
281
281
282 @classmethod
282 @classmethod
283 def get_all(cls):
283 def get_all(cls):
284 return cls.query().all()
284 return cls.query().all()
285
285
286 @classmethod
286 @classmethod
287 def delete(cls, id_):
287 def delete(cls, id_):
288 obj = cls.query().get(id_)
288 obj = cls.query().get(id_)
289 Session().delete(obj)
289 Session().delete(obj)
290
290
291 @classmethod
291 @classmethod
292 def identity_cache(cls, session, attr_name, value):
292 def identity_cache(cls, session, attr_name, value):
293 exist_in_session = []
293 exist_in_session = []
294 for (item_cls, pkey), instance in session.identity_map.items():
294 for (item_cls, pkey), instance in session.identity_map.items():
295 if cls == item_cls and getattr(instance, attr_name) == value:
295 if cls == item_cls and getattr(instance, attr_name) == value:
296 exist_in_session.append(instance)
296 exist_in_session.append(instance)
297 if exist_in_session:
297 if exist_in_session:
298 if len(exist_in_session) == 1:
298 if len(exist_in_session) == 1:
299 return exist_in_session[0]
299 return exist_in_session[0]
300 log.exception(
300 log.exception(
301 'multiple objects with attr %s and '
301 'multiple objects with attr %s and '
302 'value %s found with same name: %r',
302 'value %s found with same name: %r',
303 attr_name, value, exist_in_session)
303 attr_name, value, exist_in_session)
304
304
305 def __repr__(self):
305 def __repr__(self):
306 if hasattr(self, '__unicode__'):
306 if hasattr(self, '__unicode__'):
307 # python repr needs to return str
307 # python repr needs to return str
308 try:
308 try:
309 return safe_str(self.__unicode__())
309 return safe_str(self.__unicode__())
310 except UnicodeDecodeError:
310 except UnicodeDecodeError:
311 pass
311 pass
312 return '<DB:%s>' % (self.__class__.__name__)
312 return '<DB:%s>' % (self.__class__.__name__)
313
313
314
314
315 class RhodeCodeSetting(Base, BaseModel):
315 class RhodeCodeSetting(Base, BaseModel):
316 __tablename__ = 'rhodecode_settings'
316 __tablename__ = 'rhodecode_settings'
317 __table_args__ = (
317 __table_args__ = (
318 UniqueConstraint('app_settings_name'),
318 UniqueConstraint('app_settings_name'),
319 base_table_args
319 base_table_args
320 )
320 )
321
321
322 SETTINGS_TYPES = {
322 SETTINGS_TYPES = {
323 'str': safe_str,
323 'str': safe_str,
324 'int': safe_int,
324 'int': safe_int,
325 'unicode': safe_unicode,
325 'unicode': safe_unicode,
326 'bool': str2bool,
326 'bool': str2bool,
327 'list': functools.partial(aslist, sep=',')
327 'list': functools.partial(aslist, sep=',')
328 }
328 }
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
329 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
330 GLOBAL_CONF_KEY = 'app_settings'
330 GLOBAL_CONF_KEY = 'app_settings'
331
331
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
332 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
333 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
334 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
335 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
336
336
337 def __init__(self, key='', val='', type='unicode'):
337 def __init__(self, key='', val='', type='unicode'):
338 self.app_settings_name = key
338 self.app_settings_name = key
339 self.app_settings_type = type
339 self.app_settings_type = type
340 self.app_settings_value = val
340 self.app_settings_value = val
341
341
342 @validates('_app_settings_value')
342 @validates('_app_settings_value')
343 def validate_settings_value(self, key, val):
343 def validate_settings_value(self, key, val):
344 assert type(val) == unicode
344 assert type(val) == unicode
345 return val
345 return val
346
346
347 @hybrid_property
347 @hybrid_property
348 def app_settings_value(self):
348 def app_settings_value(self):
349 v = self._app_settings_value
349 v = self._app_settings_value
350 _type = self.app_settings_type
350 _type = self.app_settings_type
351 if _type:
351 if _type:
352 _type = self.app_settings_type.split('.')[0]
352 _type = self.app_settings_type.split('.')[0]
353 # decode the encrypted value
353 # decode the encrypted value
354 if 'encrypted' in self.app_settings_type:
354 if 'encrypted' in self.app_settings_type:
355 cipher = EncryptedTextValue()
355 cipher = EncryptedTextValue()
356 v = safe_unicode(cipher.process_result_value(v, None))
356 v = safe_unicode(cipher.process_result_value(v, None))
357
357
358 converter = self.SETTINGS_TYPES.get(_type) or \
358 converter = self.SETTINGS_TYPES.get(_type) or \
359 self.SETTINGS_TYPES['unicode']
359 self.SETTINGS_TYPES['unicode']
360 return converter(v)
360 return converter(v)
361
361
362 @app_settings_value.setter
362 @app_settings_value.setter
363 def app_settings_value(self, val):
363 def app_settings_value(self, val):
364 """
364 """
365 Setter that will always make sure we use unicode in app_settings_value
365 Setter that will always make sure we use unicode in app_settings_value
366
366
367 :param val:
367 :param val:
368 """
368 """
369 val = safe_unicode(val)
369 val = safe_unicode(val)
370 # encode the encrypted value
370 # encode the encrypted value
371 if 'encrypted' in self.app_settings_type:
371 if 'encrypted' in self.app_settings_type:
372 cipher = EncryptedTextValue()
372 cipher = EncryptedTextValue()
373 val = safe_unicode(cipher.process_bind_param(val, None))
373 val = safe_unicode(cipher.process_bind_param(val, None))
374 self._app_settings_value = val
374 self._app_settings_value = val
375
375
376 @hybrid_property
376 @hybrid_property
377 def app_settings_type(self):
377 def app_settings_type(self):
378 return self._app_settings_type
378 return self._app_settings_type
379
379
380 @app_settings_type.setter
380 @app_settings_type.setter
381 def app_settings_type(self, val):
381 def app_settings_type(self, val):
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
382 if val.split('.')[0] not in self.SETTINGS_TYPES:
383 raise Exception('type must be one of %s got %s'
383 raise Exception('type must be one of %s got %s'
384 % (self.SETTINGS_TYPES.keys(), val))
384 % (self.SETTINGS_TYPES.keys(), val))
385 self._app_settings_type = val
385 self._app_settings_type = val
386
386
387 @classmethod
387 @classmethod
388 def get_by_prefix(cls, prefix):
388 def get_by_prefix(cls, prefix):
389 return RhodeCodeSetting.query()\
389 return RhodeCodeSetting.query()\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
390 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
391 .all()
391 .all()
392
392
393 def __unicode__(self):
393 def __unicode__(self):
394 return u"<%s('%s:%s[%s]')>" % (
394 return u"<%s('%s:%s[%s]')>" % (
395 self.__class__.__name__,
395 self.__class__.__name__,
396 self.app_settings_name, self.app_settings_value,
396 self.app_settings_name, self.app_settings_value,
397 self.app_settings_type
397 self.app_settings_type
398 )
398 )
399
399
400
400
401 class RhodeCodeUi(Base, BaseModel):
401 class RhodeCodeUi(Base, BaseModel):
402 __tablename__ = 'rhodecode_ui'
402 __tablename__ = 'rhodecode_ui'
403 __table_args__ = (
403 __table_args__ = (
404 UniqueConstraint('ui_key'),
404 UniqueConstraint('ui_key'),
405 base_table_args
405 base_table_args
406 )
406 )
407
407
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
408 HOOK_REPO_SIZE = 'changegroup.repo_size'
409 # HG
409 # HG
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
410 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
411 HOOK_PULL = 'outgoing.pull_logger'
411 HOOK_PULL = 'outgoing.pull_logger'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
412 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
413 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
414 HOOK_PUSH = 'changegroup.push_logger'
414 HOOK_PUSH = 'changegroup.push_logger'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
415 HOOK_PUSH_KEY = 'pushkey.key_push'
416
416
417 HOOKS_BUILTIN = [
417 HOOKS_BUILTIN = [
418 HOOK_PRE_PULL,
418 HOOK_PRE_PULL,
419 HOOK_PULL,
419 HOOK_PULL,
420 HOOK_PRE_PUSH,
420 HOOK_PRE_PUSH,
421 HOOK_PRETX_PUSH,
421 HOOK_PRETX_PUSH,
422 HOOK_PUSH,
422 HOOK_PUSH,
423 HOOK_PUSH_KEY,
423 HOOK_PUSH_KEY,
424 ]
424 ]
425
425
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
426 # TODO: johbo: Unify way how hooks are configured for git and hg,
427 # git part is currently hardcoded.
427 # git part is currently hardcoded.
428
428
429 # SVN PATTERNS
429 # SVN PATTERNS
430 SVN_BRANCH_ID = 'vcs_svn_branch'
430 SVN_BRANCH_ID = 'vcs_svn_branch'
431 SVN_TAG_ID = 'vcs_svn_tag'
431 SVN_TAG_ID = 'vcs_svn_tag'
432
432
433 ui_id = Column(
433 ui_id = Column(
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
434 "ui_id", Integer(), nullable=False, unique=True, default=None,
435 primary_key=True)
435 primary_key=True)
436 ui_section = Column(
436 ui_section = Column(
437 "ui_section", String(255), nullable=True, unique=None, default=None)
437 "ui_section", String(255), nullable=True, unique=None, default=None)
438 ui_key = Column(
438 ui_key = Column(
439 "ui_key", String(255), nullable=True, unique=None, default=None)
439 "ui_key", String(255), nullable=True, unique=None, default=None)
440 ui_value = Column(
440 ui_value = Column(
441 "ui_value", String(255), nullable=True, unique=None, default=None)
441 "ui_value", String(255), nullable=True, unique=None, default=None)
442 ui_active = Column(
442 ui_active = Column(
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
443 "ui_active", Boolean(), nullable=True, unique=None, default=True)
444
444
445 def __repr__(self):
445 def __repr__(self):
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
446 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
447 self.ui_key, self.ui_value)
447 self.ui_key, self.ui_value)
448
448
449
449
450 class RepoRhodeCodeSetting(Base, BaseModel):
450 class RepoRhodeCodeSetting(Base, BaseModel):
451 __tablename__ = 'repo_rhodecode_settings'
451 __tablename__ = 'repo_rhodecode_settings'
452 __table_args__ = (
452 __table_args__ = (
453 UniqueConstraint(
453 UniqueConstraint(
454 'app_settings_name', 'repository_id',
454 'app_settings_name', 'repository_id',
455 name='uq_repo_rhodecode_setting_name_repo_id'),
455 name='uq_repo_rhodecode_setting_name_repo_id'),
456 base_table_args
456 base_table_args
457 )
457 )
458
458
459 repository_id = Column(
459 repository_id = Column(
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
460 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
461 nullable=False)
461 nullable=False)
462 app_settings_id = Column(
462 app_settings_id = Column(
463 "app_settings_id", Integer(), nullable=False, unique=True,
463 "app_settings_id", Integer(), nullable=False, unique=True,
464 default=None, primary_key=True)
464 default=None, primary_key=True)
465 app_settings_name = Column(
465 app_settings_name = Column(
466 "app_settings_name", String(255), nullable=True, unique=None,
466 "app_settings_name", String(255), nullable=True, unique=None,
467 default=None)
467 default=None)
468 _app_settings_value = Column(
468 _app_settings_value = Column(
469 "app_settings_value", String(4096), nullable=True, unique=None,
469 "app_settings_value", String(4096), nullable=True, unique=None,
470 default=None)
470 default=None)
471 _app_settings_type = Column(
471 _app_settings_type = Column(
472 "app_settings_type", String(255), nullable=True, unique=None,
472 "app_settings_type", String(255), nullable=True, unique=None,
473 default=None)
473 default=None)
474
474
475 repository = relationship('Repository')
475 repository = relationship('Repository')
476
476
477 def __init__(self, repository_id, key='', val='', type='unicode'):
477 def __init__(self, repository_id, key='', val='', type='unicode'):
478 self.repository_id = repository_id
478 self.repository_id = repository_id
479 self.app_settings_name = key
479 self.app_settings_name = key
480 self.app_settings_type = type
480 self.app_settings_type = type
481 self.app_settings_value = val
481 self.app_settings_value = val
482
482
483 @validates('_app_settings_value')
483 @validates('_app_settings_value')
484 def validate_settings_value(self, key, val):
484 def validate_settings_value(self, key, val):
485 assert type(val) == unicode
485 assert type(val) == unicode
486 return val
486 return val
487
487
488 @hybrid_property
488 @hybrid_property
489 def app_settings_value(self):
489 def app_settings_value(self):
490 v = self._app_settings_value
490 v = self._app_settings_value
491 type_ = self.app_settings_type
491 type_ = self.app_settings_type
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
492 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
493 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
494 return converter(v)
494 return converter(v)
495
495
496 @app_settings_value.setter
496 @app_settings_value.setter
497 def app_settings_value(self, val):
497 def app_settings_value(self, val):
498 """
498 """
499 Setter that will always make sure we use unicode in app_settings_value
499 Setter that will always make sure we use unicode in app_settings_value
500
500
501 :param val:
501 :param val:
502 """
502 """
503 self._app_settings_value = safe_unicode(val)
503 self._app_settings_value = safe_unicode(val)
504
504
505 @hybrid_property
505 @hybrid_property
506 def app_settings_type(self):
506 def app_settings_type(self):
507 return self._app_settings_type
507 return self._app_settings_type
508
508
509 @app_settings_type.setter
509 @app_settings_type.setter
510 def app_settings_type(self, val):
510 def app_settings_type(self, val):
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
511 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
512 if val not in SETTINGS_TYPES:
512 if val not in SETTINGS_TYPES:
513 raise Exception('type must be one of %s got %s'
513 raise Exception('type must be one of %s got %s'
514 % (SETTINGS_TYPES.keys(), val))
514 % (SETTINGS_TYPES.keys(), val))
515 self._app_settings_type = val
515 self._app_settings_type = val
516
516
517 def __unicode__(self):
517 def __unicode__(self):
518 return u"<%s('%s:%s:%s[%s]')>" % (
518 return u"<%s('%s:%s:%s[%s]')>" % (
519 self.__class__.__name__, self.repository.repo_name,
519 self.__class__.__name__, self.repository.repo_name,
520 self.app_settings_name, self.app_settings_value,
520 self.app_settings_name, self.app_settings_value,
521 self.app_settings_type
521 self.app_settings_type
522 )
522 )
523
523
524
524
525 class RepoRhodeCodeUi(Base, BaseModel):
525 class RepoRhodeCodeUi(Base, BaseModel):
526 __tablename__ = 'repo_rhodecode_ui'
526 __tablename__ = 'repo_rhodecode_ui'
527 __table_args__ = (
527 __table_args__ = (
528 UniqueConstraint(
528 UniqueConstraint(
529 'repository_id', 'ui_section', 'ui_key',
529 'repository_id', 'ui_section', 'ui_key',
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
530 name='uq_repo_rhodecode_ui_repository_id_section_key'),
531 base_table_args
531 base_table_args
532 )
532 )
533
533
534 repository_id = Column(
534 repository_id = Column(
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
535 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
536 nullable=False)
536 nullable=False)
537 ui_id = Column(
537 ui_id = Column(
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
538 "ui_id", Integer(), nullable=False, unique=True, default=None,
539 primary_key=True)
539 primary_key=True)
540 ui_section = Column(
540 ui_section = Column(
541 "ui_section", String(255), nullable=True, unique=None, default=None)
541 "ui_section", String(255), nullable=True, unique=None, default=None)
542 ui_key = Column(
542 ui_key = Column(
543 "ui_key", String(255), nullable=True, unique=None, default=None)
543 "ui_key", String(255), nullable=True, unique=None, default=None)
544 ui_value = Column(
544 ui_value = Column(
545 "ui_value", String(255), nullable=True, unique=None, default=None)
545 "ui_value", String(255), nullable=True, unique=None, default=None)
546 ui_active = Column(
546 ui_active = Column(
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
547 "ui_active", Boolean(), nullable=True, unique=None, default=True)
548
548
549 repository = relationship('Repository')
549 repository = relationship('Repository')
550
550
551 def __repr__(self):
551 def __repr__(self):
552 return '<%s[%s:%s]%s=>%s]>' % (
552 return '<%s[%s:%s]%s=>%s]>' % (
553 self.__class__.__name__, self.repository.repo_name,
553 self.__class__.__name__, self.repository.repo_name,
554 self.ui_section, self.ui_key, self.ui_value)
554 self.ui_section, self.ui_key, self.ui_value)
555
555
556
556
557 class User(Base, BaseModel):
557 class User(Base, BaseModel):
558 __tablename__ = 'users'
558 __tablename__ = 'users'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('username'), UniqueConstraint('email'),
560 UniqueConstraint('username'), UniqueConstraint('email'),
561 Index('u_username_idx', 'username'),
561 Index('u_username_idx', 'username'),
562 Index('u_email_idx', 'email'),
562 Index('u_email_idx', 'email'),
563 base_table_args
563 base_table_args
564 )
564 )
565
565
566 DEFAULT_USER = 'default'
566 DEFAULT_USER = 'default'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
567 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
568 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
569
569
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
571 username = Column("username", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
572 password = Column("password", String(255), nullable=True, unique=None, default=None)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
573 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
574 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
575 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
576 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
577 _email = Column("email", String(255), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
578 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
579 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
580 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
581
581
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
582 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
583 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
584 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
585 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
587 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
588
588
589 user_log = relationship('UserLog')
589 user_log = relationship('UserLog')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
590 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
591
591
592 repositories = relationship('Repository')
592 repositories = relationship('Repository')
593 repository_groups = relationship('RepoGroup')
593 repository_groups = relationship('RepoGroup')
594 user_groups = relationship('UserGroup')
594 user_groups = relationship('UserGroup')
595
595
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
596 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
597 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
598
598
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
600 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
601 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
602
602
603 group_member = relationship('UserGroupMember', cascade='all')
603 group_member = relationship('UserGroupMember', cascade='all')
604
604
605 notifications = relationship('UserNotification', cascade='all')
605 notifications = relationship('UserNotification', cascade='all')
606 # notifications assigned to this user
606 # notifications assigned to this user
607 user_created_notifications = relationship('Notification', cascade='all')
607 user_created_notifications = relationship('Notification', cascade='all')
608 # comments created by this user
608 # comments created by this user
609 user_comments = relationship('ChangesetComment', cascade='all')
609 user_comments = relationship('ChangesetComment', cascade='all')
610 # user profile extra info
610 # user profile extra info
611 user_emails = relationship('UserEmailMap', cascade='all')
611 user_emails = relationship('UserEmailMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
612 user_ip_map = relationship('UserIpMap', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
613 user_auth_tokens = relationship('UserApiKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
614 user_ssh_keys = relationship('UserSshKeys', cascade='all')
615
615
616 # gists
616 # gists
617 user_gists = relationship('Gist', cascade='all')
617 user_gists = relationship('Gist', cascade='all')
618 # user pull requests
618 # user pull requests
619 user_pull_requests = relationship('PullRequest', cascade='all')
619 user_pull_requests = relationship('PullRequest', cascade='all')
620
620
621 # external identities
621 # external identities
622 external_identities = relationship(
622 external_identities = relationship(
623 'ExternalIdentity',
623 'ExternalIdentity',
624 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
625 cascade='all')
625 cascade='all')
626 # review rules
626 # review rules
627 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
628
628
629 # artifacts owned
629 # artifacts owned
630 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
631
631
632 # no cascade, set NULL
632 # no cascade, set NULL
633 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
634
634
635 def __unicode__(self):
635 def __unicode__(self):
636 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
637 self.user_id, self.username)
637 self.user_id, self.username)
638
638
639 @hybrid_property
639 @hybrid_property
640 def email(self):
640 def email(self):
641 return self._email
641 return self._email
642
642
643 @email.setter
643 @email.setter
644 def email(self, val):
644 def email(self, val):
645 self._email = val.lower() if val else None
645 self._email = val.lower() if val else None
646
646
647 @hybrid_property
647 @hybrid_property
648 def first_name(self):
648 def first_name(self):
649 from rhodecode.lib import helpers as h
649 from rhodecode.lib import helpers as h
650 if self.name:
650 if self.name:
651 return h.escape(self.name)
651 return h.escape(self.name)
652 return self.name
652 return self.name
653
653
654 @hybrid_property
654 @hybrid_property
655 def last_name(self):
655 def last_name(self):
656 from rhodecode.lib import helpers as h
656 from rhodecode.lib import helpers as h
657 if self.lastname:
657 if self.lastname:
658 return h.escape(self.lastname)
658 return h.escape(self.lastname)
659 return self.lastname
659 return self.lastname
660
660
661 @hybrid_property
661 @hybrid_property
662 def api_key(self):
662 def api_key(self):
663 """
663 """
664 Fetch if exist an auth-token with role ALL connected to this user
664 Fetch if exist an auth-token with role ALL connected to this user
665 """
665 """
666 user_auth_token = UserApiKeys.query()\
666 user_auth_token = UserApiKeys.query()\
667 .filter(UserApiKeys.user_id == self.user_id)\
667 .filter(UserApiKeys.user_id == self.user_id)\
668 .filter(or_(UserApiKeys.expires == -1,
668 .filter(or_(UserApiKeys.expires == -1,
669 UserApiKeys.expires >= time.time()))\
669 UserApiKeys.expires >= time.time()))\
670 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
671 if user_auth_token:
671 if user_auth_token:
672 user_auth_token = user_auth_token.api_key
672 user_auth_token = user_auth_token.api_key
673
673
674 return user_auth_token
674 return user_auth_token
675
675
676 @api_key.setter
676 @api_key.setter
677 def api_key(self, val):
677 def api_key(self, val):
678 # don't allow to set API key this is deprecated for now
678 # don't allow to set API key this is deprecated for now
679 self._api_key = None
679 self._api_key = None
680
680
681 @property
681 @property
682 def reviewer_pull_requests(self):
682 def reviewer_pull_requests(self):
683 return PullRequestReviewers.query() \
683 return PullRequestReviewers.query() \
684 .options(joinedload(PullRequestReviewers.pull_request)) \
684 .options(joinedload(PullRequestReviewers.pull_request)) \
685 .filter(PullRequestReviewers.user_id == self.user_id) \
685 .filter(PullRequestReviewers.user_id == self.user_id) \
686 .all()
686 .all()
687
687
688 @property
688 @property
689 def firstname(self):
689 def firstname(self):
690 # alias for future
690 # alias for future
691 return self.name
691 return self.name
692
692
693 @property
693 @property
694 def emails(self):
694 def emails(self):
695 other = UserEmailMap.query()\
695 other = UserEmailMap.query()\
696 .filter(UserEmailMap.user == self) \
696 .filter(UserEmailMap.user == self) \
697 .order_by(UserEmailMap.email_id.asc()) \
697 .order_by(UserEmailMap.email_id.asc()) \
698 .all()
698 .all()
699 return [self.email] + [x.email for x in other]
699 return [self.email] + [x.email for x in other]
700
700
701 def emails_cached(self):
701 def emails_cached(self):
702 emails = UserEmailMap.query()\
702 emails = UserEmailMap.query()\
703 .filter(UserEmailMap.user == self) \
703 .filter(UserEmailMap.user == self) \
704 .order_by(UserEmailMap.email_id.asc())
704 .order_by(UserEmailMap.email_id.asc())
705
705
706 emails = emails.options(
706 emails = emails.options(
707 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
708 )
708 )
709
709
710 return [self.email] + [x.email for x in emails]
710 return [self.email] + [x.email for x in emails]
711
711
712 @property
712 @property
713 def auth_tokens(self):
713 def auth_tokens(self):
714 auth_tokens = self.get_auth_tokens()
714 auth_tokens = self.get_auth_tokens()
715 return [x.api_key for x in auth_tokens]
715 return [x.api_key for x in auth_tokens]
716
716
717 def get_auth_tokens(self):
717 def get_auth_tokens(self):
718 return UserApiKeys.query()\
718 return UserApiKeys.query()\
719 .filter(UserApiKeys.user == self)\
719 .filter(UserApiKeys.user == self)\
720 .order_by(UserApiKeys.user_api_key_id.asc())\
720 .order_by(UserApiKeys.user_api_key_id.asc())\
721 .all()
721 .all()
722
722
723 @LazyProperty
723 @LazyProperty
724 def feed_token(self):
724 def feed_token(self):
725 return self.get_feed_token()
725 return self.get_feed_token()
726
726
727 def get_feed_token(self, cache=True):
727 def get_feed_token(self, cache=True):
728 feed_tokens = UserApiKeys.query()\
728 feed_tokens = UserApiKeys.query()\
729 .filter(UserApiKeys.user == self)\
729 .filter(UserApiKeys.user == self)\
730 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
731 if cache:
731 if cache:
732 feed_tokens = feed_tokens.options(
732 feed_tokens = feed_tokens.options(
733 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
734
734
735 feed_tokens = feed_tokens.all()
735 feed_tokens = feed_tokens.all()
736 if feed_tokens:
736 if feed_tokens:
737 return feed_tokens[0].api_key
737 return feed_tokens[0].api_key
738 return 'NO_FEED_TOKEN_AVAILABLE'
738 return 'NO_FEED_TOKEN_AVAILABLE'
739
739
740 @LazyProperty
740 @LazyProperty
741 def artifact_token(self):
741 def artifact_token(self):
742 return self.get_artifact_token()
742 return self.get_artifact_token()
743
743
744 def get_artifact_token(self, cache=True):
744 def get_artifact_token(self, cache=True):
745 artifacts_tokens = UserApiKeys.query()\
745 artifacts_tokens = UserApiKeys.query()\
746 .filter(UserApiKeys.user == self)\
746 .filter(UserApiKeys.user == self)\
747 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
748 if cache:
748 if cache:
749 artifacts_tokens = artifacts_tokens.options(
749 artifacts_tokens = artifacts_tokens.options(
750 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
751
751
752 artifacts_tokens = artifacts_tokens.all()
752 artifacts_tokens = artifacts_tokens.all()
753 if artifacts_tokens:
753 if artifacts_tokens:
754 return artifacts_tokens[0].api_key
754 return artifacts_tokens[0].api_key
755 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
756
756
757 @classmethod
757 @classmethod
758 def get(cls, user_id, cache=False):
758 def get(cls, user_id, cache=False):
759 if not user_id:
759 if not user_id:
760 return
760 return
761
761
762 user = cls.query()
762 user = cls.query()
763 if cache:
763 if cache:
764 user = user.options(
764 user = user.options(
765 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 FromCache("sql_cache_short", "get_users_%s" % user_id))
766 return user.get(user_id)
766 return user.get(user_id)
767
767
768 @classmethod
768 @classmethod
769 def extra_valid_auth_tokens(cls, user, role=None):
769 def extra_valid_auth_tokens(cls, user, role=None):
770 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
771 .filter(or_(UserApiKeys.expires == -1,
771 .filter(or_(UserApiKeys.expires == -1,
772 UserApiKeys.expires >= time.time()))
772 UserApiKeys.expires >= time.time()))
773 if role:
773 if role:
774 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 tokens = tokens.filter(or_(UserApiKeys.role == role,
775 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 UserApiKeys.role == UserApiKeys.ROLE_ALL))
776 return tokens.all()
776 return tokens.all()
777
777
778 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
779 from rhodecode.lib import auth
779 from rhodecode.lib import auth
780
780
781 log.debug('Trying to authenticate user: %s via auth-token, '
781 log.debug('Trying to authenticate user: %s via auth-token, '
782 'and roles: %s', self, roles)
782 'and roles: %s', self, roles)
783
783
784 if not auth_token:
784 if not auth_token:
785 return False
785 return False
786
786
787 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
788 tokens_q = UserApiKeys.query()\
788 tokens_q = UserApiKeys.query()\
789 .filter(UserApiKeys.user_id == self.user_id)\
789 .filter(UserApiKeys.user_id == self.user_id)\
790 .filter(or_(UserApiKeys.expires == -1,
790 .filter(or_(UserApiKeys.expires == -1,
791 UserApiKeys.expires >= time.time()))
791 UserApiKeys.expires >= time.time()))
792
792
793 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
794
794
795 crypto_backend = auth.crypto_backend()
795 crypto_backend = auth.crypto_backend()
796 enc_token_map = {}
796 enc_token_map = {}
797 plain_token_map = {}
797 plain_token_map = {}
798 for token in tokens_q:
798 for token in tokens_q:
799 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 if token.api_key.startswith(crypto_backend.ENC_PREF):
800 enc_token_map[token.api_key] = token
800 enc_token_map[token.api_key] = token
801 else:
801 else:
802 plain_token_map[token.api_key] = token
802 plain_token_map[token.api_key] = token
803 log.debug(
803 log.debug(
804 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 'Found %s plain and %s encrypted tokens to check for authentication for this user',
805 len(plain_token_map), len(enc_token_map))
805 len(plain_token_map), len(enc_token_map))
806
806
807 # plain token match comes first
807 # plain token match comes first
808 match = plain_token_map.get(auth_token)
808 match = plain_token_map.get(auth_token)
809
809
810 # check encrypted tokens now
810 # check encrypted tokens now
811 if not match:
811 if not match:
812 for token_hash, token in enc_token_map.items():
812 for token_hash, token in enc_token_map.items():
813 # NOTE(marcink): this is expensive to calculate, but most secure
813 # NOTE(marcink): this is expensive to calculate, but most secure
814 if crypto_backend.hash_check(auth_token, token_hash):
814 if crypto_backend.hash_check(auth_token, token_hash):
815 match = token
815 match = token
816 break
816 break
817
817
818 if match:
818 if match:
819 log.debug('Found matching token %s', match)
819 log.debug('Found matching token %s', match)
820 if match.repo_id:
820 if match.repo_id:
821 log.debug('Found scope, checking for scope match of token %s', match)
821 log.debug('Found scope, checking for scope match of token %s', match)
822 if match.repo_id == scope_repo_id:
822 if match.repo_id == scope_repo_id:
823 return True
823 return True
824 else:
824 else:
825 log.debug(
825 log.debug(
826 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
827 'and calling scope is:%s, skipping further checks',
827 'and calling scope is:%s, skipping further checks',
828 match.repo, scope_repo_id)
828 match.repo, scope_repo_id)
829 return False
829 return False
830 else:
830 else:
831 return True
831 return True
832
832
833 return False
833 return False
834
834
835 @property
835 @property
836 def ip_addresses(self):
836 def ip_addresses(self):
837 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
838 return [x.ip_addr for x in ret]
838 return [x.ip_addr for x in ret]
839
839
840 @property
840 @property
841 def username_and_name(self):
841 def username_and_name(self):
842 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
843
843
844 @property
844 @property
845 def username_or_name_or_email(self):
845 def username_or_name_or_email(self):
846 full_name = self.full_name if self.full_name is not ' ' else None
846 full_name = self.full_name if self.full_name is not ' ' else None
847 return self.username or full_name or self.email
847 return self.username or full_name or self.email
848
848
849 @property
849 @property
850 def full_name(self):
850 def full_name(self):
851 return '%s %s' % (self.first_name, self.last_name)
851 return '%s %s' % (self.first_name, self.last_name)
852
852
853 @property
853 @property
854 def full_name_or_username(self):
854 def full_name_or_username(self):
855 return ('%s %s' % (self.first_name, self.last_name)
855 return ('%s %s' % (self.first_name, self.last_name)
856 if (self.first_name and self.last_name) else self.username)
856 if (self.first_name and self.last_name) else self.username)
857
857
858 @property
858 @property
859 def full_contact(self):
859 def full_contact(self):
860 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
861
861
862 @property
862 @property
863 def short_contact(self):
863 def short_contact(self):
864 return '%s %s' % (self.first_name, self.last_name)
864 return '%s %s' % (self.first_name, self.last_name)
865
865
866 @property
866 @property
867 def is_admin(self):
867 def is_admin(self):
868 return self.admin
868 return self.admin
869
869
870 @property
870 @property
871 def language(self):
871 def language(self):
872 return self.user_data.get('language')
872 return self.user_data.get('language')
873
873
874 def AuthUser(self, **kwargs):
874 def AuthUser(self, **kwargs):
875 """
875 """
876 Returns instance of AuthUser for this user
876 Returns instance of AuthUser for this user
877 """
877 """
878 from rhodecode.lib.auth import AuthUser
878 from rhodecode.lib.auth import AuthUser
879 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
880
880
881 @hybrid_property
881 @hybrid_property
882 def user_data(self):
882 def user_data(self):
883 if not self._user_data:
883 if not self._user_data:
884 return {}
884 return {}
885
885
886 try:
886 try:
887 return json.loads(self._user_data)
887 return json.loads(self._user_data)
888 except TypeError:
888 except TypeError:
889 return {}
889 return {}
890
890
891 @user_data.setter
891 @user_data.setter
892 def user_data(self, val):
892 def user_data(self, val):
893 if not isinstance(val, dict):
893 if not isinstance(val, dict):
894 raise Exception('user_data must be dict, got %s' % type(val))
894 raise Exception('user_data must be dict, got %s' % type(val))
895 try:
895 try:
896 self._user_data = json.dumps(val)
896 self._user_data = json.dumps(val)
897 except Exception:
897 except Exception:
898 log.error(traceback.format_exc())
898 log.error(traceback.format_exc())
899
899
900 @classmethod
900 @classmethod
901 def get_by_username(cls, username, case_insensitive=False,
901 def get_by_username(cls, username, case_insensitive=False,
902 cache=False, identity_cache=False):
902 cache=False, identity_cache=False):
903 session = Session()
903 session = Session()
904
904
905 if case_insensitive:
905 if case_insensitive:
906 q = cls.query().filter(
906 q = cls.query().filter(
907 func.lower(cls.username) == func.lower(username))
907 func.lower(cls.username) == func.lower(username))
908 else:
908 else:
909 q = cls.query().filter(cls.username == username)
909 q = cls.query().filter(cls.username == username)
910
910
911 if cache:
911 if cache:
912 if identity_cache:
912 if identity_cache:
913 val = cls.identity_cache(session, 'username', username)
913 val = cls.identity_cache(session, 'username', username)
914 if val:
914 if val:
915 return val
915 return val
916 else:
916 else:
917 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 cache_key = "get_user_by_name_%s" % _hash_key(username)
918 q = q.options(
918 q = q.options(
919 FromCache("sql_cache_short", cache_key))
919 FromCache("sql_cache_short", cache_key))
920
920
921 return q.scalar()
921 return q.scalar()
922
922
923 @classmethod
923 @classmethod
924 def get_by_auth_token(cls, auth_token, cache=False):
924 def get_by_auth_token(cls, auth_token, cache=False):
925 q = UserApiKeys.query()\
925 q = UserApiKeys.query()\
926 .filter(UserApiKeys.api_key == auth_token)\
926 .filter(UserApiKeys.api_key == auth_token)\
927 .filter(or_(UserApiKeys.expires == -1,
927 .filter(or_(UserApiKeys.expires == -1,
928 UserApiKeys.expires >= time.time()))
928 UserApiKeys.expires >= time.time()))
929 if cache:
929 if cache:
930 q = q.options(
930 q = q.options(
931 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
932
932
933 match = q.first()
933 match = q.first()
934 if match:
934 if match:
935 return match.user
935 return match.user
936
936
937 @classmethod
937 @classmethod
938 def get_by_email(cls, email, case_insensitive=False, cache=False):
938 def get_by_email(cls, email, case_insensitive=False, cache=False):
939
939
940 if case_insensitive:
940 if case_insensitive:
941 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
942
942
943 else:
943 else:
944 q = cls.query().filter(cls.email == email)
944 q = cls.query().filter(cls.email == email)
945
945
946 email_key = _hash_key(email)
946 email_key = _hash_key(email)
947 if cache:
947 if cache:
948 q = q.options(
948 q = q.options(
949 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
950
950
951 ret = q.scalar()
951 ret = q.scalar()
952 if ret is None:
952 if ret is None:
953 q = UserEmailMap.query()
953 q = UserEmailMap.query()
954 # try fetching in alternate email map
954 # try fetching in alternate email map
955 if case_insensitive:
955 if case_insensitive:
956 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
957 else:
957 else:
958 q = q.filter(UserEmailMap.email == email)
958 q = q.filter(UserEmailMap.email == email)
959 q = q.options(joinedload(UserEmailMap.user))
959 q = q.options(joinedload(UserEmailMap.user))
960 if cache:
960 if cache:
961 q = q.options(
961 q = q.options(
962 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
963 ret = getattr(q.scalar(), 'user', None)
963 ret = getattr(q.scalar(), 'user', None)
964
964
965 return ret
965 return ret
966
966
967 @classmethod
967 @classmethod
968 def get_from_cs_author(cls, author):
968 def get_from_cs_author(cls, author):
969 """
969 """
970 Tries to get User objects out of commit author string
970 Tries to get User objects out of commit author string
971
971
972 :param author:
972 :param author:
973 """
973 """
974 from rhodecode.lib.helpers import email, author_name
974 from rhodecode.lib.helpers import email, author_name
975 # Valid email in the attribute passed, see if they're in the system
975 # Valid email in the attribute passed, see if they're in the system
976 _email = email(author)
976 _email = email(author)
977 if _email:
977 if _email:
978 user = cls.get_by_email(_email, case_insensitive=True)
978 user = cls.get_by_email(_email, case_insensitive=True)
979 if user:
979 if user:
980 return user
980 return user
981 # Maybe we can match by username?
981 # Maybe we can match by username?
982 _author = author_name(author)
982 _author = author_name(author)
983 user = cls.get_by_username(_author, case_insensitive=True)
983 user = cls.get_by_username(_author, case_insensitive=True)
984 if user:
984 if user:
985 return user
985 return user
986
986
987 def update_userdata(self, **kwargs):
987 def update_userdata(self, **kwargs):
988 usr = self
988 usr = self
989 old = usr.user_data
989 old = usr.user_data
990 old.update(**kwargs)
990 old.update(**kwargs)
991 usr.user_data = old
991 usr.user_data = old
992 Session().add(usr)
992 Session().add(usr)
993 log.debug('updated userdata with %s', kwargs)
993 log.debug('updated userdata with %s', kwargs)
994
994
995 def update_lastlogin(self):
995 def update_lastlogin(self):
996 """Update user lastlogin"""
996 """Update user lastlogin"""
997 self.last_login = datetime.datetime.now()
997 self.last_login = datetime.datetime.now()
998 Session().add(self)
998 Session().add(self)
999 log.debug('updated user %s lastlogin', self.username)
999 log.debug('updated user %s lastlogin', self.username)
1000
1000
1001 def update_password(self, new_password):
1001 def update_password(self, new_password):
1002 from rhodecode.lib.auth import get_crypt_password
1002 from rhodecode.lib.auth import get_crypt_password
1003
1003
1004 self.password = get_crypt_password(new_password)
1004 self.password = get_crypt_password(new_password)
1005 Session().add(self)
1005 Session().add(self)
1006
1006
1007 @classmethod
1007 @classmethod
1008 def get_first_super_admin(cls):
1008 def get_first_super_admin(cls):
1009 user = User.query()\
1009 user = User.query()\
1010 .filter(User.admin == true()) \
1010 .filter(User.admin == true()) \
1011 .order_by(User.user_id.asc()) \
1011 .order_by(User.user_id.asc()) \
1012 .first()
1012 .first()
1013
1013
1014 if user is None:
1014 if user is None:
1015 raise Exception('FATAL: Missing administrative account!')
1015 raise Exception('FATAL: Missing administrative account!')
1016 return user
1016 return user
1017
1017
1018 @classmethod
1018 @classmethod
1019 def get_all_super_admins(cls, only_active=False):
1019 def get_all_super_admins(cls, only_active=False):
1020 """
1020 """
1021 Returns all admin accounts sorted by username
1021 Returns all admin accounts sorted by username
1022 """
1022 """
1023 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1024 if only_active:
1024 if only_active:
1025 qry = qry.filter(User.active == true())
1025 qry = qry.filter(User.active == true())
1026 return qry.all()
1026 return qry.all()
1027
1027
1028 @classmethod
1028 @classmethod
1029 def get_all_user_ids(cls, only_active=True):
1029 def get_all_user_ids(cls, only_active=True):
1030 """
1030 """
1031 Returns all users IDs
1031 Returns all users IDs
1032 """
1032 """
1033 qry = Session().query(User.user_id)
1033 qry = Session().query(User.user_id)
1034
1034
1035 if only_active:
1035 if only_active:
1036 qry = qry.filter(User.active == true())
1036 qry = qry.filter(User.active == true())
1037 return [x.user_id for x in qry]
1037 return [x.user_id for x in qry]
1038
1038
1039 @classmethod
1039 @classmethod
1040 def get_default_user(cls, cache=False, refresh=False):
1040 def get_default_user(cls, cache=False, refresh=False):
1041 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1041 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1042 if user is None:
1042 if user is None:
1043 raise Exception('FATAL: Missing default account!')
1043 raise Exception('FATAL: Missing default account!')
1044 if refresh:
1044 if refresh:
1045 # The default user might be based on outdated state which
1045 # The default user might be based on outdated state which
1046 # has been loaded from the cache.
1046 # has been loaded from the cache.
1047 # A call to refresh() ensures that the
1047 # A call to refresh() ensures that the
1048 # latest state from the database is used.
1048 # latest state from the database is used.
1049 Session().refresh(user)
1049 Session().refresh(user)
1050 return user
1050 return user
1051
1051
1052 @classmethod
1052 @classmethod
1053 def get_default_user_id(cls):
1053 def get_default_user_id(cls):
1054 import rhodecode
1054 import rhodecode
1055 return rhodecode.CONFIG['default_user_id']
1055 return rhodecode.CONFIG['default_user_id']
1056
1056
1057 def _get_default_perms(self, user, suffix=''):
1057 def _get_default_perms(self, user, suffix=''):
1058 from rhodecode.model.permission import PermissionModel
1058 from rhodecode.model.permission import PermissionModel
1059 return PermissionModel().get_default_perms(user.user_perms, suffix)
1059 return PermissionModel().get_default_perms(user.user_perms, suffix)
1060
1060
1061 def get_default_perms(self, suffix=''):
1061 def get_default_perms(self, suffix=''):
1062 return self._get_default_perms(self, suffix)
1062 return self._get_default_perms(self, suffix)
1063
1063
1064 def get_api_data(self, include_secrets=False, details='full'):
1064 def get_api_data(self, include_secrets=False, details='full'):
1065 """
1065 """
1066 Common function for generating user related data for API
1066 Common function for generating user related data for API
1067
1067
1068 :param include_secrets: By default secrets in the API data will be replaced
1068 :param include_secrets: By default secrets in the API data will be replaced
1069 by a placeholder value to prevent exposing this data by accident. In case
1069 by a placeholder value to prevent exposing this data by accident. In case
1070 this data shall be exposed, set this flag to ``True``.
1070 this data shall be exposed, set this flag to ``True``.
1071
1071
1072 :param details: details can be 'basic|full' basic gives only a subset of
1072 :param details: details can be 'basic|full' basic gives only a subset of
1073 the available user information that includes user_id, name and emails.
1073 the available user information that includes user_id, name and emails.
1074 """
1074 """
1075 user = self
1075 user = self
1076 user_data = self.user_data
1076 user_data = self.user_data
1077 data = {
1077 data = {
1078 'user_id': user.user_id,
1078 'user_id': user.user_id,
1079 'username': user.username,
1079 'username': user.username,
1080 'firstname': user.name,
1080 'firstname': user.name,
1081 'lastname': user.lastname,
1081 'lastname': user.lastname,
1082 'description': user.description,
1082 'description': user.description,
1083 'email': user.email,
1083 'email': user.email,
1084 'emails': user.emails,
1084 'emails': user.emails,
1085 }
1085 }
1086 if details == 'basic':
1086 if details == 'basic':
1087 return data
1087 return data
1088
1088
1089 auth_token_length = 40
1089 auth_token_length = 40
1090 auth_token_replacement = '*' * auth_token_length
1090 auth_token_replacement = '*' * auth_token_length
1091
1091
1092 extras = {
1092 extras = {
1093 'auth_tokens': [auth_token_replacement],
1093 'auth_tokens': [auth_token_replacement],
1094 'active': user.active,
1094 'active': user.active,
1095 'admin': user.admin,
1095 'admin': user.admin,
1096 'extern_type': user.extern_type,
1096 'extern_type': user.extern_type,
1097 'extern_name': user.extern_name,
1097 'extern_name': user.extern_name,
1098 'last_login': user.last_login,
1098 'last_login': user.last_login,
1099 'last_activity': user.last_activity,
1099 'last_activity': user.last_activity,
1100 'ip_addresses': user.ip_addresses,
1100 'ip_addresses': user.ip_addresses,
1101 'language': user_data.get('language')
1101 'language': user_data.get('language')
1102 }
1102 }
1103 data.update(extras)
1103 data.update(extras)
1104
1104
1105 if include_secrets:
1105 if include_secrets:
1106 data['auth_tokens'] = user.auth_tokens
1106 data['auth_tokens'] = user.auth_tokens
1107 return data
1107 return data
1108
1108
1109 def __json__(self):
1109 def __json__(self):
1110 data = {
1110 data = {
1111 'full_name': self.full_name,
1111 'full_name': self.full_name,
1112 'full_name_or_username': self.full_name_or_username,
1112 'full_name_or_username': self.full_name_or_username,
1113 'short_contact': self.short_contact,
1113 'short_contact': self.short_contact,
1114 'full_contact': self.full_contact,
1114 'full_contact': self.full_contact,
1115 }
1115 }
1116 data.update(self.get_api_data())
1116 data.update(self.get_api_data())
1117 return data
1117 return data
1118
1118
1119
1119
1120 class UserApiKeys(Base, BaseModel):
1120 class UserApiKeys(Base, BaseModel):
1121 __tablename__ = 'user_api_keys'
1121 __tablename__ = 'user_api_keys'
1122 __table_args__ = (
1122 __table_args__ = (
1123 Index('uak_api_key_idx', 'api_key'),
1123 Index('uak_api_key_idx', 'api_key'),
1124 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1124 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1125 base_table_args
1125 base_table_args
1126 )
1126 )
1127 __mapper_args__ = {}
1127 __mapper_args__ = {}
1128
1128
1129 # ApiKey role
1129 # ApiKey role
1130 ROLE_ALL = 'token_role_all'
1130 ROLE_ALL = 'token_role_all'
1131 ROLE_HTTP = 'token_role_http'
1131 ROLE_HTTP = 'token_role_http'
1132 ROLE_VCS = 'token_role_vcs'
1132 ROLE_VCS = 'token_role_vcs'
1133 ROLE_API = 'token_role_api'
1133 ROLE_API = 'token_role_api'
1134 ROLE_FEED = 'token_role_feed'
1134 ROLE_FEED = 'token_role_feed'
1135 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1135 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1136 ROLE_PASSWORD_RESET = 'token_password_reset'
1136 ROLE_PASSWORD_RESET = 'token_password_reset'
1137
1137
1138 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1138 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1139
1139
1140 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1140 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1141 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1141 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1142 api_key = Column("api_key", String(255), nullable=False, unique=True)
1142 api_key = Column("api_key", String(255), nullable=False, unique=True)
1143 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1143 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1144 expires = Column('expires', Float(53), nullable=False)
1144 expires = Column('expires', Float(53), nullable=False)
1145 role = Column('role', String(255), nullable=True)
1145 role = Column('role', String(255), nullable=True)
1146 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1146 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1147
1147
1148 # scope columns
1148 # scope columns
1149 repo_id = Column(
1149 repo_id = Column(
1150 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1150 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1151 nullable=True, unique=None, default=None)
1151 nullable=True, unique=None, default=None)
1152 repo = relationship('Repository', lazy='joined')
1152 repo = relationship('Repository', lazy='joined')
1153
1153
1154 repo_group_id = Column(
1154 repo_group_id = Column(
1155 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1155 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1156 nullable=True, unique=None, default=None)
1156 nullable=True, unique=None, default=None)
1157 repo_group = relationship('RepoGroup', lazy='joined')
1157 repo_group = relationship('RepoGroup', lazy='joined')
1158
1158
1159 user = relationship('User', lazy='joined')
1159 user = relationship('User', lazy='joined')
1160
1160
1161 def __unicode__(self):
1161 def __unicode__(self):
1162 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1162 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1163
1163
1164 def __json__(self):
1164 def __json__(self):
1165 data = {
1165 data = {
1166 'auth_token': self.api_key,
1166 'auth_token': self.api_key,
1167 'role': self.role,
1167 'role': self.role,
1168 'scope': self.scope_humanized,
1168 'scope': self.scope_humanized,
1169 'expired': self.expired
1169 'expired': self.expired
1170 }
1170 }
1171 return data
1171 return data
1172
1172
1173 def get_api_data(self, include_secrets=False):
1173 def get_api_data(self, include_secrets=False):
1174 data = self.__json__()
1174 data = self.__json__()
1175 if include_secrets:
1175 if include_secrets:
1176 return data
1176 return data
1177 else:
1177 else:
1178 data['auth_token'] = self.token_obfuscated
1178 data['auth_token'] = self.token_obfuscated
1179 return data
1179 return data
1180
1180
1181 @hybrid_property
1181 @hybrid_property
1182 def description_safe(self):
1182 def description_safe(self):
1183 from rhodecode.lib import helpers as h
1183 from rhodecode.lib import helpers as h
1184 return h.escape(self.description)
1184 return h.escape(self.description)
1185
1185
1186 @property
1186 @property
1187 def expired(self):
1187 def expired(self):
1188 if self.expires == -1:
1188 if self.expires == -1:
1189 return False
1189 return False
1190 return time.time() > self.expires
1190 return time.time() > self.expires
1191
1191
1192 @classmethod
1192 @classmethod
1193 def _get_role_name(cls, role):
1193 def _get_role_name(cls, role):
1194 return {
1194 return {
1195 cls.ROLE_ALL: _('all'),
1195 cls.ROLE_ALL: _('all'),
1196 cls.ROLE_HTTP: _('http/web interface'),
1196 cls.ROLE_HTTP: _('http/web interface'),
1197 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1197 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1198 cls.ROLE_API: _('api calls'),
1198 cls.ROLE_API: _('api calls'),
1199 cls.ROLE_FEED: _('feed access'),
1199 cls.ROLE_FEED: _('feed access'),
1200 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1200 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1201 }.get(role, role)
1201 }.get(role, role)
1202
1202
1203 @property
1203 @property
1204 def role_humanized(self):
1204 def role_humanized(self):
1205 return self._get_role_name(self.role)
1205 return self._get_role_name(self.role)
1206
1206
1207 def _get_scope(self):
1207 def _get_scope(self):
1208 if self.repo:
1208 if self.repo:
1209 return 'Repository: {}'.format(self.repo.repo_name)
1209 return 'Repository: {}'.format(self.repo.repo_name)
1210 if self.repo_group:
1210 if self.repo_group:
1211 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1211 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1212 return 'Global'
1212 return 'Global'
1213
1213
1214 @property
1214 @property
1215 def scope_humanized(self):
1215 def scope_humanized(self):
1216 return self._get_scope()
1216 return self._get_scope()
1217
1217
1218 @property
1218 @property
1219 def token_obfuscated(self):
1219 def token_obfuscated(self):
1220 if self.api_key:
1220 if self.api_key:
1221 return self.api_key[:4] + "****"
1221 return self.api_key[:4] + "****"
1222
1222
1223
1223
1224 class UserEmailMap(Base, BaseModel):
1224 class UserEmailMap(Base, BaseModel):
1225 __tablename__ = 'user_email_map'
1225 __tablename__ = 'user_email_map'
1226 __table_args__ = (
1226 __table_args__ = (
1227 Index('uem_email_idx', 'email'),
1227 Index('uem_email_idx', 'email'),
1228 UniqueConstraint('email'),
1228 UniqueConstraint('email'),
1229 base_table_args
1229 base_table_args
1230 )
1230 )
1231 __mapper_args__ = {}
1231 __mapper_args__ = {}
1232
1232
1233 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1233 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1234 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1234 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1235 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1235 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1236 user = relationship('User', lazy='joined')
1236 user = relationship('User', lazy='joined')
1237
1237
1238 @validates('_email')
1238 @validates('_email')
1239 def validate_email(self, key, email):
1239 def validate_email(self, key, email):
1240 # check if this email is not main one
1240 # check if this email is not main one
1241 main_email = Session().query(User).filter(User.email == email).scalar()
1241 main_email = Session().query(User).filter(User.email == email).scalar()
1242 if main_email is not None:
1242 if main_email is not None:
1243 raise AttributeError('email %s is present is user table' % email)
1243 raise AttributeError('email %s is present is user table' % email)
1244 return email
1244 return email
1245
1245
1246 @hybrid_property
1246 @hybrid_property
1247 def email(self):
1247 def email(self):
1248 return self._email
1248 return self._email
1249
1249
1250 @email.setter
1250 @email.setter
1251 def email(self, val):
1251 def email(self, val):
1252 self._email = val.lower() if val else None
1252 self._email = val.lower() if val else None
1253
1253
1254
1254
1255 class UserIpMap(Base, BaseModel):
1255 class UserIpMap(Base, BaseModel):
1256 __tablename__ = 'user_ip_map'
1256 __tablename__ = 'user_ip_map'
1257 __table_args__ = (
1257 __table_args__ = (
1258 UniqueConstraint('user_id', 'ip_addr'),
1258 UniqueConstraint('user_id', 'ip_addr'),
1259 base_table_args
1259 base_table_args
1260 )
1260 )
1261 __mapper_args__ = {}
1261 __mapper_args__ = {}
1262
1262
1263 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1263 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1264 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1264 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1265 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1265 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1266 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1266 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1267 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1267 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1268 user = relationship('User', lazy='joined')
1268 user = relationship('User', lazy='joined')
1269
1269
1270 @hybrid_property
1270 @hybrid_property
1271 def description_safe(self):
1271 def description_safe(self):
1272 from rhodecode.lib import helpers as h
1272 from rhodecode.lib import helpers as h
1273 return h.escape(self.description)
1273 return h.escape(self.description)
1274
1274
1275 @classmethod
1275 @classmethod
1276 def _get_ip_range(cls, ip_addr):
1276 def _get_ip_range(cls, ip_addr):
1277 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1277 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1278 return [str(net.network_address), str(net.broadcast_address)]
1278 return [str(net.network_address), str(net.broadcast_address)]
1279
1279
1280 def __json__(self):
1280 def __json__(self):
1281 return {
1281 return {
1282 'ip_addr': self.ip_addr,
1282 'ip_addr': self.ip_addr,
1283 'ip_range': self._get_ip_range(self.ip_addr),
1283 'ip_range': self._get_ip_range(self.ip_addr),
1284 }
1284 }
1285
1285
1286 def __unicode__(self):
1286 def __unicode__(self):
1287 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1287 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1288 self.user_id, self.ip_addr)
1288 self.user_id, self.ip_addr)
1289
1289
1290
1290
1291 class UserSshKeys(Base, BaseModel):
1291 class UserSshKeys(Base, BaseModel):
1292 __tablename__ = 'user_ssh_keys'
1292 __tablename__ = 'user_ssh_keys'
1293 __table_args__ = (
1293 __table_args__ = (
1294 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1294 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1295
1295
1296 UniqueConstraint('ssh_key_fingerprint'),
1296 UniqueConstraint('ssh_key_fingerprint'),
1297
1297
1298 base_table_args
1298 base_table_args
1299 )
1299 )
1300 __mapper_args__ = {}
1300 __mapper_args__ = {}
1301
1301
1302 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1302 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1303 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1303 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1304 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1304 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1305
1305
1306 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1306 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1307
1307
1308 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1308 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1309 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1309 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1310 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1310 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1311
1311
1312 user = relationship('User', lazy='joined')
1312 user = relationship('User', lazy='joined')
1313
1313
1314 def __json__(self):
1314 def __json__(self):
1315 data = {
1315 data = {
1316 'ssh_fingerprint': self.ssh_key_fingerprint,
1316 'ssh_fingerprint': self.ssh_key_fingerprint,
1317 'description': self.description,
1317 'description': self.description,
1318 'created_on': self.created_on
1318 'created_on': self.created_on
1319 }
1319 }
1320 return data
1320 return data
1321
1321
1322 def get_api_data(self):
1322 def get_api_data(self):
1323 data = self.__json__()
1323 data = self.__json__()
1324 return data
1324 return data
1325
1325
1326
1326
1327 class UserLog(Base, BaseModel):
1327 class UserLog(Base, BaseModel):
1328 __tablename__ = 'user_logs'
1328 __tablename__ = 'user_logs'
1329 __table_args__ = (
1329 __table_args__ = (
1330 base_table_args,
1330 base_table_args,
1331 )
1331 )
1332
1332
1333 VERSION_1 = 'v1'
1333 VERSION_1 = 'v1'
1334 VERSION_2 = 'v2'
1334 VERSION_2 = 'v2'
1335 VERSIONS = [VERSION_1, VERSION_2]
1335 VERSIONS = [VERSION_1, VERSION_2]
1336
1336
1337 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1337 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1338 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1338 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1339 username = Column("username", String(255), nullable=True, unique=None, default=None)
1339 username = Column("username", String(255), nullable=True, unique=None, default=None)
1340 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1340 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1341 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1341 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1342 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1342 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1343 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1343 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1344 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1344 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1345
1345
1346 version = Column("version", String(255), nullable=True, default=VERSION_1)
1346 version = Column("version", String(255), nullable=True, default=VERSION_1)
1347 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1347 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1348 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1348 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1349
1349
1350 def __unicode__(self):
1350 def __unicode__(self):
1351 return u"<%s('id:%s:%s')>" % (
1351 return u"<%s('id:%s:%s')>" % (
1352 self.__class__.__name__, self.repository_name, self.action)
1352 self.__class__.__name__, self.repository_name, self.action)
1353
1353
1354 def __json__(self):
1354 def __json__(self):
1355 return {
1355 return {
1356 'user_id': self.user_id,
1356 'user_id': self.user_id,
1357 'username': self.username,
1357 'username': self.username,
1358 'repository_id': self.repository_id,
1358 'repository_id': self.repository_id,
1359 'repository_name': self.repository_name,
1359 'repository_name': self.repository_name,
1360 'user_ip': self.user_ip,
1360 'user_ip': self.user_ip,
1361 'action_date': self.action_date,
1361 'action_date': self.action_date,
1362 'action': self.action,
1362 'action': self.action,
1363 }
1363 }
1364
1364
1365 @hybrid_property
1365 @hybrid_property
1366 def entry_id(self):
1366 def entry_id(self):
1367 return self.user_log_id
1367 return self.user_log_id
1368
1368
1369 @property
1369 @property
1370 def action_as_day(self):
1370 def action_as_day(self):
1371 return datetime.date(*self.action_date.timetuple()[:3])
1371 return datetime.date(*self.action_date.timetuple()[:3])
1372
1372
1373 user = relationship('User')
1373 user = relationship('User')
1374 repository = relationship('Repository', cascade='')
1374 repository = relationship('Repository', cascade='')
1375
1375
1376
1376
1377 class UserGroup(Base, BaseModel):
1377 class UserGroup(Base, BaseModel):
1378 __tablename__ = 'users_groups'
1378 __tablename__ = 'users_groups'
1379 __table_args__ = (
1379 __table_args__ = (
1380 base_table_args,
1380 base_table_args,
1381 )
1381 )
1382
1382
1383 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1383 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1384 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1384 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1385 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1385 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1386 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1386 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1387 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1387 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1388 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1388 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1389 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1389 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1390 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1390 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1391
1391
1392 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1392 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1393 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1393 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1394 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1394 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1395 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1395 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1396 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1396 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1397 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1397 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1398
1398
1399 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1399 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1400 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1400 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1401
1401
1402 @classmethod
1402 @classmethod
1403 def _load_group_data(cls, column):
1403 def _load_group_data(cls, column):
1404 if not column:
1404 if not column:
1405 return {}
1405 return {}
1406
1406
1407 try:
1407 try:
1408 return json.loads(column) or {}
1408 return json.loads(column) or {}
1409 except TypeError:
1409 except TypeError:
1410 return {}
1410 return {}
1411
1411
1412 @hybrid_property
1412 @hybrid_property
1413 def description_safe(self):
1413 def description_safe(self):
1414 from rhodecode.lib import helpers as h
1414 from rhodecode.lib import helpers as h
1415 return h.escape(self.user_group_description)
1415 return h.escape(self.user_group_description)
1416
1416
1417 @hybrid_property
1417 @hybrid_property
1418 def group_data(self):
1418 def group_data(self):
1419 return self._load_group_data(self._group_data)
1419 return self._load_group_data(self._group_data)
1420
1420
1421 @group_data.expression
1421 @group_data.expression
1422 def group_data(self, **kwargs):
1422 def group_data(self, **kwargs):
1423 return self._group_data
1423 return self._group_data
1424
1424
1425 @group_data.setter
1425 @group_data.setter
1426 def group_data(self, val):
1426 def group_data(self, val):
1427 try:
1427 try:
1428 self._group_data = json.dumps(val)
1428 self._group_data = json.dumps(val)
1429 except Exception:
1429 except Exception:
1430 log.error(traceback.format_exc())
1430 log.error(traceback.format_exc())
1431
1431
1432 @classmethod
1432 @classmethod
1433 def _load_sync(cls, group_data):
1433 def _load_sync(cls, group_data):
1434 if group_data:
1434 if group_data:
1435 return group_data.get('extern_type')
1435 return group_data.get('extern_type')
1436
1436
1437 @property
1437 @property
1438 def sync(self):
1438 def sync(self):
1439 return self._load_sync(self.group_data)
1439 return self._load_sync(self.group_data)
1440
1440
1441 def __unicode__(self):
1441 def __unicode__(self):
1442 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1442 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1443 self.users_group_id,
1443 self.users_group_id,
1444 self.users_group_name)
1444 self.users_group_name)
1445
1445
1446 @classmethod
1446 @classmethod
1447 def get_by_group_name(cls, group_name, cache=False,
1447 def get_by_group_name(cls, group_name, cache=False,
1448 case_insensitive=False):
1448 case_insensitive=False):
1449 if case_insensitive:
1449 if case_insensitive:
1450 q = cls.query().filter(func.lower(cls.users_group_name) ==
1450 q = cls.query().filter(func.lower(cls.users_group_name) ==
1451 func.lower(group_name))
1451 func.lower(group_name))
1452
1452
1453 else:
1453 else:
1454 q = cls.query().filter(cls.users_group_name == group_name)
1454 q = cls.query().filter(cls.users_group_name == group_name)
1455 if cache:
1455 if cache:
1456 q = q.options(
1456 q = q.options(
1457 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1457 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1458 return q.scalar()
1458 return q.scalar()
1459
1459
1460 @classmethod
1460 @classmethod
1461 def get(cls, user_group_id, cache=False):
1461 def get(cls, user_group_id, cache=False):
1462 if not user_group_id:
1462 if not user_group_id:
1463 return
1463 return
1464
1464
1465 user_group = cls.query()
1465 user_group = cls.query()
1466 if cache:
1466 if cache:
1467 user_group = user_group.options(
1467 user_group = user_group.options(
1468 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1468 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1469 return user_group.get(user_group_id)
1469 return user_group.get(user_group_id)
1470
1470
1471 def permissions(self, with_admins=True, with_owner=True,
1471 def permissions(self, with_admins=True, with_owner=True,
1472 expand_from_user_groups=False):
1472 expand_from_user_groups=False):
1473 """
1473 """
1474 Permissions for user groups
1474 Permissions for user groups
1475 """
1475 """
1476 _admin_perm = 'usergroup.admin'
1476 _admin_perm = 'usergroup.admin'
1477
1477
1478 owner_row = []
1478 owner_row = []
1479 if with_owner:
1479 if with_owner:
1480 usr = AttributeDict(self.user.get_dict())
1480 usr = AttributeDict(self.user.get_dict())
1481 usr.owner_row = True
1481 usr.owner_row = True
1482 usr.permission = _admin_perm
1482 usr.permission = _admin_perm
1483 owner_row.append(usr)
1483 owner_row.append(usr)
1484
1484
1485 super_admin_ids = []
1485 super_admin_ids = []
1486 super_admin_rows = []
1486 super_admin_rows = []
1487 if with_admins:
1487 if with_admins:
1488 for usr in User.get_all_super_admins():
1488 for usr in User.get_all_super_admins():
1489 super_admin_ids.append(usr.user_id)
1489 super_admin_ids.append(usr.user_id)
1490 # if this admin is also owner, don't double the record
1490 # if this admin is also owner, don't double the record
1491 if usr.user_id == owner_row[0].user_id:
1491 if usr.user_id == owner_row[0].user_id:
1492 owner_row[0].admin_row = True
1492 owner_row[0].admin_row = True
1493 else:
1493 else:
1494 usr = AttributeDict(usr.get_dict())
1494 usr = AttributeDict(usr.get_dict())
1495 usr.admin_row = True
1495 usr.admin_row = True
1496 usr.permission = _admin_perm
1496 usr.permission = _admin_perm
1497 super_admin_rows.append(usr)
1497 super_admin_rows.append(usr)
1498
1498
1499 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1499 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1500 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1500 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1501 joinedload(UserUserGroupToPerm.user),
1501 joinedload(UserUserGroupToPerm.user),
1502 joinedload(UserUserGroupToPerm.permission),)
1502 joinedload(UserUserGroupToPerm.permission),)
1503
1503
1504 # get owners and admins and permissions. We do a trick of re-writing
1504 # get owners and admins and permissions. We do a trick of re-writing
1505 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1505 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1506 # has a global reference and changing one object propagates to all
1506 # has a global reference and changing one object propagates to all
1507 # others. This means if admin is also an owner admin_row that change
1507 # others. This means if admin is also an owner admin_row that change
1508 # would propagate to both objects
1508 # would propagate to both objects
1509 perm_rows = []
1509 perm_rows = []
1510 for _usr in q.all():
1510 for _usr in q.all():
1511 usr = AttributeDict(_usr.user.get_dict())
1511 usr = AttributeDict(_usr.user.get_dict())
1512 # if this user is also owner/admin, mark as duplicate record
1512 # if this user is also owner/admin, mark as duplicate record
1513 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1513 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1514 usr.duplicate_perm = True
1514 usr.duplicate_perm = True
1515 usr.permission = _usr.permission.permission_name
1515 usr.permission = _usr.permission.permission_name
1516 perm_rows.append(usr)
1516 perm_rows.append(usr)
1517
1517
1518 # filter the perm rows by 'default' first and then sort them by
1518 # filter the perm rows by 'default' first and then sort them by
1519 # admin,write,read,none permissions sorted again alphabetically in
1519 # admin,write,read,none permissions sorted again alphabetically in
1520 # each group
1520 # each group
1521 perm_rows = sorted(perm_rows, key=display_user_sort)
1521 perm_rows = sorted(perm_rows, key=display_user_sort)
1522
1522
1523 user_groups_rows = []
1523 user_groups_rows = []
1524 if expand_from_user_groups:
1524 if expand_from_user_groups:
1525 for ug in self.permission_user_groups(with_members=True):
1525 for ug in self.permission_user_groups(with_members=True):
1526 for user_data in ug.members:
1526 for user_data in ug.members:
1527 user_groups_rows.append(user_data)
1527 user_groups_rows.append(user_data)
1528
1528
1529 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1529 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1530
1530
1531 def permission_user_groups(self, with_members=False):
1531 def permission_user_groups(self, with_members=False):
1532 q = UserGroupUserGroupToPerm.query()\
1532 q = UserGroupUserGroupToPerm.query()\
1533 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1533 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1534 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1534 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1535 joinedload(UserGroupUserGroupToPerm.target_user_group),
1535 joinedload(UserGroupUserGroupToPerm.target_user_group),
1536 joinedload(UserGroupUserGroupToPerm.permission),)
1536 joinedload(UserGroupUserGroupToPerm.permission),)
1537
1537
1538 perm_rows = []
1538 perm_rows = []
1539 for _user_group in q.all():
1539 for _user_group in q.all():
1540 entry = AttributeDict(_user_group.user_group.get_dict())
1540 entry = AttributeDict(_user_group.user_group.get_dict())
1541 entry.permission = _user_group.permission.permission_name
1541 entry.permission = _user_group.permission.permission_name
1542 if with_members:
1542 if with_members:
1543 entry.members = [x.user.get_dict()
1543 entry.members = [x.user.get_dict()
1544 for x in _user_group.user_group.members]
1544 for x in _user_group.user_group.members]
1545 perm_rows.append(entry)
1545 perm_rows.append(entry)
1546
1546
1547 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1547 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1548 return perm_rows
1548 return perm_rows
1549
1549
1550 def _get_default_perms(self, user_group, suffix=''):
1550 def _get_default_perms(self, user_group, suffix=''):
1551 from rhodecode.model.permission import PermissionModel
1551 from rhodecode.model.permission import PermissionModel
1552 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1552 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1553
1553
1554 def get_default_perms(self, suffix=''):
1554 def get_default_perms(self, suffix=''):
1555 return self._get_default_perms(self, suffix)
1555 return self._get_default_perms(self, suffix)
1556
1556
1557 def get_api_data(self, with_group_members=True, include_secrets=False):
1557 def get_api_data(self, with_group_members=True, include_secrets=False):
1558 """
1558 """
1559 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1559 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1560 basically forwarded.
1560 basically forwarded.
1561
1561
1562 """
1562 """
1563 user_group = self
1563 user_group = self
1564 data = {
1564 data = {
1565 'users_group_id': user_group.users_group_id,
1565 'users_group_id': user_group.users_group_id,
1566 'group_name': user_group.users_group_name,
1566 'group_name': user_group.users_group_name,
1567 'group_description': user_group.user_group_description,
1567 'group_description': user_group.user_group_description,
1568 'active': user_group.users_group_active,
1568 'active': user_group.users_group_active,
1569 'owner': user_group.user.username,
1569 'owner': user_group.user.username,
1570 'sync': user_group.sync,
1570 'sync': user_group.sync,
1571 'owner_email': user_group.user.email,
1571 'owner_email': user_group.user.email,
1572 }
1572 }
1573
1573
1574 if with_group_members:
1574 if with_group_members:
1575 users = []
1575 users = []
1576 for user in user_group.members:
1576 for user in user_group.members:
1577 user = user.user
1577 user = user.user
1578 users.append(user.get_api_data(include_secrets=include_secrets))
1578 users.append(user.get_api_data(include_secrets=include_secrets))
1579 data['users'] = users
1579 data['users'] = users
1580
1580
1581 return data
1581 return data
1582
1582
1583
1583
1584 class UserGroupMember(Base, BaseModel):
1584 class UserGroupMember(Base, BaseModel):
1585 __tablename__ = 'users_groups_members'
1585 __tablename__ = 'users_groups_members'
1586 __table_args__ = (
1586 __table_args__ = (
1587 base_table_args,
1587 base_table_args,
1588 )
1588 )
1589
1589
1590 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1590 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1591 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1593
1593
1594 user = relationship('User', lazy='joined')
1594 user = relationship('User', lazy='joined')
1595 users_group = relationship('UserGroup')
1595 users_group = relationship('UserGroup')
1596
1596
1597 def __init__(self, gr_id='', u_id=''):
1597 def __init__(self, gr_id='', u_id=''):
1598 self.users_group_id = gr_id
1598 self.users_group_id = gr_id
1599 self.user_id = u_id
1599 self.user_id = u_id
1600
1600
1601
1601
1602 class RepositoryField(Base, BaseModel):
1602 class RepositoryField(Base, BaseModel):
1603 __tablename__ = 'repositories_fields'
1603 __tablename__ = 'repositories_fields'
1604 __table_args__ = (
1604 __table_args__ = (
1605 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1605 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1606 base_table_args,
1606 base_table_args,
1607 )
1607 )
1608
1608
1609 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1609 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1610
1610
1611 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1611 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1612 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1612 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1613 field_key = Column("field_key", String(250))
1613 field_key = Column("field_key", String(250))
1614 field_label = Column("field_label", String(1024), nullable=False)
1614 field_label = Column("field_label", String(1024), nullable=False)
1615 field_value = Column("field_value", String(10000), nullable=False)
1615 field_value = Column("field_value", String(10000), nullable=False)
1616 field_desc = Column("field_desc", String(1024), nullable=False)
1616 field_desc = Column("field_desc", String(1024), nullable=False)
1617 field_type = Column("field_type", String(255), nullable=False, unique=None)
1617 field_type = Column("field_type", String(255), nullable=False, unique=None)
1618 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1618 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1619
1619
1620 repository = relationship('Repository')
1620 repository = relationship('Repository')
1621
1621
1622 @property
1622 @property
1623 def field_key_prefixed(self):
1623 def field_key_prefixed(self):
1624 return 'ex_%s' % self.field_key
1624 return 'ex_%s' % self.field_key
1625
1625
1626 @classmethod
1626 @classmethod
1627 def un_prefix_key(cls, key):
1627 def un_prefix_key(cls, key):
1628 if key.startswith(cls.PREFIX):
1628 if key.startswith(cls.PREFIX):
1629 return key[len(cls.PREFIX):]
1629 return key[len(cls.PREFIX):]
1630 return key
1630 return key
1631
1631
1632 @classmethod
1632 @classmethod
1633 def get_by_key_name(cls, key, repo):
1633 def get_by_key_name(cls, key, repo):
1634 row = cls.query()\
1634 row = cls.query()\
1635 .filter(cls.repository == repo)\
1635 .filter(cls.repository == repo)\
1636 .filter(cls.field_key == key).scalar()
1636 .filter(cls.field_key == key).scalar()
1637 return row
1637 return row
1638
1638
1639
1639
1640 class Repository(Base, BaseModel):
1640 class Repository(Base, BaseModel):
1641 __tablename__ = 'repositories'
1641 __tablename__ = 'repositories'
1642 __table_args__ = (
1642 __table_args__ = (
1643 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1643 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1644 base_table_args,
1644 base_table_args,
1645 )
1645 )
1646 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1646 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1647 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1647 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1648 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1648 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1649
1649
1650 STATE_CREATED = 'repo_state_created'
1650 STATE_CREATED = 'repo_state_created'
1651 STATE_PENDING = 'repo_state_pending'
1651 STATE_PENDING = 'repo_state_pending'
1652 STATE_ERROR = 'repo_state_error'
1652 STATE_ERROR = 'repo_state_error'
1653
1653
1654 LOCK_AUTOMATIC = 'lock_auto'
1654 LOCK_AUTOMATIC = 'lock_auto'
1655 LOCK_API = 'lock_api'
1655 LOCK_API = 'lock_api'
1656 LOCK_WEB = 'lock_web'
1656 LOCK_WEB = 'lock_web'
1657 LOCK_PULL = 'lock_pull'
1657 LOCK_PULL = 'lock_pull'
1658
1658
1659 NAME_SEP = URL_SEP
1659 NAME_SEP = URL_SEP
1660
1660
1661 repo_id = Column(
1661 repo_id = Column(
1662 "repo_id", Integer(), nullable=False, unique=True, default=None,
1662 "repo_id", Integer(), nullable=False, unique=True, default=None,
1663 primary_key=True)
1663 primary_key=True)
1664 _repo_name = Column(
1664 _repo_name = Column(
1665 "repo_name", Text(), nullable=False, default=None)
1665 "repo_name", Text(), nullable=False, default=None)
1666 repo_name_hash = Column(
1666 repo_name_hash = Column(
1667 "repo_name_hash", String(255), nullable=False, unique=True)
1667 "repo_name_hash", String(255), nullable=False, unique=True)
1668 repo_state = Column("repo_state", String(255), nullable=True)
1668 repo_state = Column("repo_state", String(255), nullable=True)
1669
1669
1670 clone_uri = Column(
1670 clone_uri = Column(
1671 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1671 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1672 default=None)
1672 default=None)
1673 push_uri = Column(
1673 push_uri = Column(
1674 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1674 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1675 default=None)
1675 default=None)
1676 repo_type = Column(
1676 repo_type = Column(
1677 "repo_type", String(255), nullable=False, unique=False, default=None)
1677 "repo_type", String(255), nullable=False, unique=False, default=None)
1678 user_id = Column(
1678 user_id = Column(
1679 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1679 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1680 unique=False, default=None)
1680 unique=False, default=None)
1681 private = Column(
1681 private = Column(
1682 "private", Boolean(), nullable=True, unique=None, default=None)
1682 "private", Boolean(), nullable=True, unique=None, default=None)
1683 archived = Column(
1683 archived = Column(
1684 "archived", Boolean(), nullable=True, unique=None, default=None)
1684 "archived", Boolean(), nullable=True, unique=None, default=None)
1685 enable_statistics = Column(
1685 enable_statistics = Column(
1686 "statistics", Boolean(), nullable=True, unique=None, default=True)
1686 "statistics", Boolean(), nullable=True, unique=None, default=True)
1687 enable_downloads = Column(
1687 enable_downloads = Column(
1688 "downloads", Boolean(), nullable=True, unique=None, default=True)
1688 "downloads", Boolean(), nullable=True, unique=None, default=True)
1689 description = Column(
1689 description = Column(
1690 "description", String(10000), nullable=True, unique=None, default=None)
1690 "description", String(10000), nullable=True, unique=None, default=None)
1691 created_on = Column(
1691 created_on = Column(
1692 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1692 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1693 default=datetime.datetime.now)
1693 default=datetime.datetime.now)
1694 updated_on = Column(
1694 updated_on = Column(
1695 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1695 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1696 default=datetime.datetime.now)
1696 default=datetime.datetime.now)
1697 _landing_revision = Column(
1697 _landing_revision = Column(
1698 "landing_revision", String(255), nullable=False, unique=False,
1698 "landing_revision", String(255), nullable=False, unique=False,
1699 default=None)
1699 default=None)
1700 enable_locking = Column(
1700 enable_locking = Column(
1701 "enable_locking", Boolean(), nullable=False, unique=None,
1701 "enable_locking", Boolean(), nullable=False, unique=None,
1702 default=False)
1702 default=False)
1703 _locked = Column(
1703 _locked = Column(
1704 "locked", String(255), nullable=True, unique=False, default=None)
1704 "locked", String(255), nullable=True, unique=False, default=None)
1705 _changeset_cache = Column(
1705 _changeset_cache = Column(
1706 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1706 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1707
1707
1708 fork_id = Column(
1708 fork_id = Column(
1709 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1709 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1710 nullable=True, unique=False, default=None)
1710 nullable=True, unique=False, default=None)
1711 group_id = Column(
1711 group_id = Column(
1712 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1712 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1713 unique=False, default=None)
1713 unique=False, default=None)
1714
1714
1715 user = relationship('User', lazy='joined')
1715 user = relationship('User', lazy='joined')
1716 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1716 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1717 group = relationship('RepoGroup', lazy='joined')
1717 group = relationship('RepoGroup', lazy='joined')
1718 repo_to_perm = relationship(
1718 repo_to_perm = relationship(
1719 'UserRepoToPerm', cascade='all',
1719 'UserRepoToPerm', cascade='all',
1720 order_by='UserRepoToPerm.repo_to_perm_id')
1720 order_by='UserRepoToPerm.repo_to_perm_id')
1721 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1721 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1722 stats = relationship('Statistics', cascade='all', uselist=False)
1722 stats = relationship('Statistics', cascade='all', uselist=False)
1723
1723
1724 followers = relationship(
1724 followers = relationship(
1725 'UserFollowing',
1725 'UserFollowing',
1726 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1726 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1727 cascade='all')
1727 cascade='all')
1728 extra_fields = relationship(
1728 extra_fields = relationship(
1729 'RepositoryField', cascade="all, delete-orphan")
1729 'RepositoryField', cascade="all, delete-orphan")
1730 logs = relationship('UserLog')
1730 logs = relationship('UserLog')
1731 comments = relationship(
1731 comments = relationship(
1732 'ChangesetComment', cascade="all, delete-orphan")
1732 'ChangesetComment', cascade="all, delete-orphan")
1733 pull_requests_source = relationship(
1733 pull_requests_source = relationship(
1734 'PullRequest',
1734 'PullRequest',
1735 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1735 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1736 cascade="all, delete-orphan")
1736 cascade="all, delete-orphan")
1737 pull_requests_target = relationship(
1737 pull_requests_target = relationship(
1738 'PullRequest',
1738 'PullRequest',
1739 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1739 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1740 cascade="all, delete-orphan")
1740 cascade="all, delete-orphan")
1741 ui = relationship('RepoRhodeCodeUi', cascade="all")
1741 ui = relationship('RepoRhodeCodeUi', cascade="all")
1742 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1742 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1743 integrations = relationship('Integration', cascade="all, delete-orphan")
1743 integrations = relationship('Integration', cascade="all, delete-orphan")
1744
1744
1745 scoped_tokens = relationship('UserApiKeys', cascade="all")
1745 scoped_tokens = relationship('UserApiKeys', cascade="all")
1746
1746
1747 # no cascade, set NULL
1747 # no cascade, set NULL
1748 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1748 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1749
1749
1750 def __unicode__(self):
1750 def __unicode__(self):
1751 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1751 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1752 safe_unicode(self.repo_name))
1752 safe_unicode(self.repo_name))
1753
1753
1754 @hybrid_property
1754 @hybrid_property
1755 def description_safe(self):
1755 def description_safe(self):
1756 from rhodecode.lib import helpers as h
1756 from rhodecode.lib import helpers as h
1757 return h.escape(self.description)
1757 return h.escape(self.description)
1758
1758
1759 @hybrid_property
1759 @hybrid_property
1760 def landing_rev(self):
1760 def landing_rev(self):
1761 # always should return [rev_type, rev]
1761 # always should return [rev_type, rev], e.g ['branch', 'master']
1762 if self._landing_revision:
1762 if self._landing_revision:
1763 _rev_info = self._landing_revision.split(':')
1763 _rev_info = self._landing_revision.split(':')
1764 if len(_rev_info) < 2:
1764 if len(_rev_info) < 2:
1765 _rev_info.insert(0, 'rev')
1765 _rev_info.insert(0, 'rev')
1766 return [_rev_info[0], _rev_info[1]]
1766 return [_rev_info[0], _rev_info[1]]
1767 return [None, None]
1767 return [None, None]
1768
1768
1769 @property
1770 def landing_ref_type(self):
1771 return self.landing_rev[0]
1772
1773 @property
1774 def landing_ref_name(self):
1775 return self.landing_rev[1]
1776
1769 @landing_rev.setter
1777 @landing_rev.setter
1770 def landing_rev(self, val):
1778 def landing_rev(self, val):
1771 if ':' not in val:
1779 if ':' not in val:
1772 raise ValueError('value must be delimited with `:` and consist '
1780 raise ValueError('value must be delimited with `:` and consist '
1773 'of <rev_type>:<rev>, got %s instead' % val)
1781 'of <rev_type>:<rev>, got %s instead' % val)
1774 self._landing_revision = val
1782 self._landing_revision = val
1775
1783
1776 @hybrid_property
1784 @hybrid_property
1777 def locked(self):
1785 def locked(self):
1778 if self._locked:
1786 if self._locked:
1779 user_id, timelocked, reason = self._locked.split(':')
1787 user_id, timelocked, reason = self._locked.split(':')
1780 lock_values = int(user_id), timelocked, reason
1788 lock_values = int(user_id), timelocked, reason
1781 else:
1789 else:
1782 lock_values = [None, None, None]
1790 lock_values = [None, None, None]
1783 return lock_values
1791 return lock_values
1784
1792
1785 @locked.setter
1793 @locked.setter
1786 def locked(self, val):
1794 def locked(self, val):
1787 if val and isinstance(val, (list, tuple)):
1795 if val and isinstance(val, (list, tuple)):
1788 self._locked = ':'.join(map(str, val))
1796 self._locked = ':'.join(map(str, val))
1789 else:
1797 else:
1790 self._locked = None
1798 self._locked = None
1791
1799
1792 @classmethod
1800 @classmethod
1793 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1801 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1794 from rhodecode.lib.vcs.backends.base import EmptyCommit
1802 from rhodecode.lib.vcs.backends.base import EmptyCommit
1795 dummy = EmptyCommit().__json__()
1803 dummy = EmptyCommit().__json__()
1796 if not changeset_cache_raw:
1804 if not changeset_cache_raw:
1797 dummy['source_repo_id'] = repo_id
1805 dummy['source_repo_id'] = repo_id
1798 return json.loads(json.dumps(dummy))
1806 return json.loads(json.dumps(dummy))
1799
1807
1800 try:
1808 try:
1801 return json.loads(changeset_cache_raw)
1809 return json.loads(changeset_cache_raw)
1802 except TypeError:
1810 except TypeError:
1803 return dummy
1811 return dummy
1804 except Exception:
1812 except Exception:
1805 log.error(traceback.format_exc())
1813 log.error(traceback.format_exc())
1806 return dummy
1814 return dummy
1807
1815
1808 @hybrid_property
1816 @hybrid_property
1809 def changeset_cache(self):
1817 def changeset_cache(self):
1810 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1818 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1811
1819
1812 @changeset_cache.setter
1820 @changeset_cache.setter
1813 def changeset_cache(self, val):
1821 def changeset_cache(self, val):
1814 try:
1822 try:
1815 self._changeset_cache = json.dumps(val)
1823 self._changeset_cache = json.dumps(val)
1816 except Exception:
1824 except Exception:
1817 log.error(traceback.format_exc())
1825 log.error(traceback.format_exc())
1818
1826
1819 @hybrid_property
1827 @hybrid_property
1820 def repo_name(self):
1828 def repo_name(self):
1821 return self._repo_name
1829 return self._repo_name
1822
1830
1823 @repo_name.setter
1831 @repo_name.setter
1824 def repo_name(self, value):
1832 def repo_name(self, value):
1825 self._repo_name = value
1833 self._repo_name = value
1826 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1834 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1827
1835
1828 @classmethod
1836 @classmethod
1829 def normalize_repo_name(cls, repo_name):
1837 def normalize_repo_name(cls, repo_name):
1830 """
1838 """
1831 Normalizes os specific repo_name to the format internally stored inside
1839 Normalizes os specific repo_name to the format internally stored inside
1832 database using URL_SEP
1840 database using URL_SEP
1833
1841
1834 :param cls:
1842 :param cls:
1835 :param repo_name:
1843 :param repo_name:
1836 """
1844 """
1837 return cls.NAME_SEP.join(repo_name.split(os.sep))
1845 return cls.NAME_SEP.join(repo_name.split(os.sep))
1838
1846
1839 @classmethod
1847 @classmethod
1840 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1848 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1841 session = Session()
1849 session = Session()
1842 q = session.query(cls).filter(cls.repo_name == repo_name)
1850 q = session.query(cls).filter(cls.repo_name == repo_name)
1843
1851
1844 if cache:
1852 if cache:
1845 if identity_cache:
1853 if identity_cache:
1846 val = cls.identity_cache(session, 'repo_name', repo_name)
1854 val = cls.identity_cache(session, 'repo_name', repo_name)
1847 if val:
1855 if val:
1848 return val
1856 return val
1849 else:
1857 else:
1850 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1858 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1851 q = q.options(
1859 q = q.options(
1852 FromCache("sql_cache_short", cache_key))
1860 FromCache("sql_cache_short", cache_key))
1853
1861
1854 return q.scalar()
1862 return q.scalar()
1855
1863
1856 @classmethod
1864 @classmethod
1857 def get_by_id_or_repo_name(cls, repoid):
1865 def get_by_id_or_repo_name(cls, repoid):
1858 if isinstance(repoid, (int, long)):
1866 if isinstance(repoid, (int, long)):
1859 try:
1867 try:
1860 repo = cls.get(repoid)
1868 repo = cls.get(repoid)
1861 except ValueError:
1869 except ValueError:
1862 repo = None
1870 repo = None
1863 else:
1871 else:
1864 repo = cls.get_by_repo_name(repoid)
1872 repo = cls.get_by_repo_name(repoid)
1865 return repo
1873 return repo
1866
1874
1867 @classmethod
1875 @classmethod
1868 def get_by_full_path(cls, repo_full_path):
1876 def get_by_full_path(cls, repo_full_path):
1869 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1877 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1870 repo_name = cls.normalize_repo_name(repo_name)
1878 repo_name = cls.normalize_repo_name(repo_name)
1871 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1879 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1872
1880
1873 @classmethod
1881 @classmethod
1874 def get_repo_forks(cls, repo_id):
1882 def get_repo_forks(cls, repo_id):
1875 return cls.query().filter(Repository.fork_id == repo_id)
1883 return cls.query().filter(Repository.fork_id == repo_id)
1876
1884
1877 @classmethod
1885 @classmethod
1878 def base_path(cls):
1886 def base_path(cls):
1879 """
1887 """
1880 Returns base path when all repos are stored
1888 Returns base path when all repos are stored
1881
1889
1882 :param cls:
1890 :param cls:
1883 """
1891 """
1884 q = Session().query(RhodeCodeUi)\
1892 q = Session().query(RhodeCodeUi)\
1885 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1893 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1886 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1894 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1887 return q.one().ui_value
1895 return q.one().ui_value
1888
1896
1889 @classmethod
1897 @classmethod
1890 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1898 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1891 case_insensitive=True, archived=False):
1899 case_insensitive=True, archived=False):
1892 q = Repository.query()
1900 q = Repository.query()
1893
1901
1894 if not archived:
1902 if not archived:
1895 q = q.filter(Repository.archived.isnot(true()))
1903 q = q.filter(Repository.archived.isnot(true()))
1896
1904
1897 if not isinstance(user_id, Optional):
1905 if not isinstance(user_id, Optional):
1898 q = q.filter(Repository.user_id == user_id)
1906 q = q.filter(Repository.user_id == user_id)
1899
1907
1900 if not isinstance(group_id, Optional):
1908 if not isinstance(group_id, Optional):
1901 q = q.filter(Repository.group_id == group_id)
1909 q = q.filter(Repository.group_id == group_id)
1902
1910
1903 if case_insensitive:
1911 if case_insensitive:
1904 q = q.order_by(func.lower(Repository.repo_name))
1912 q = q.order_by(func.lower(Repository.repo_name))
1905 else:
1913 else:
1906 q = q.order_by(Repository.repo_name)
1914 q = q.order_by(Repository.repo_name)
1907
1915
1908 return q.all()
1916 return q.all()
1909
1917
1910 @property
1918 @property
1911 def repo_uid(self):
1919 def repo_uid(self):
1912 return '_{}'.format(self.repo_id)
1920 return '_{}'.format(self.repo_id)
1913
1921
1914 @property
1922 @property
1915 def forks(self):
1923 def forks(self):
1916 """
1924 """
1917 Return forks of this repo
1925 Return forks of this repo
1918 """
1926 """
1919 return Repository.get_repo_forks(self.repo_id)
1927 return Repository.get_repo_forks(self.repo_id)
1920
1928
1921 @property
1929 @property
1922 def parent(self):
1930 def parent(self):
1923 """
1931 """
1924 Returns fork parent
1932 Returns fork parent
1925 """
1933 """
1926 return self.fork
1934 return self.fork
1927
1935
1928 @property
1936 @property
1929 def just_name(self):
1937 def just_name(self):
1930 return self.repo_name.split(self.NAME_SEP)[-1]
1938 return self.repo_name.split(self.NAME_SEP)[-1]
1931
1939
1932 @property
1940 @property
1933 def groups_with_parents(self):
1941 def groups_with_parents(self):
1934 groups = []
1942 groups = []
1935 if self.group is None:
1943 if self.group is None:
1936 return groups
1944 return groups
1937
1945
1938 cur_gr = self.group
1946 cur_gr = self.group
1939 groups.insert(0, cur_gr)
1947 groups.insert(0, cur_gr)
1940 while 1:
1948 while 1:
1941 gr = getattr(cur_gr, 'parent_group', None)
1949 gr = getattr(cur_gr, 'parent_group', None)
1942 cur_gr = cur_gr.parent_group
1950 cur_gr = cur_gr.parent_group
1943 if gr is None:
1951 if gr is None:
1944 break
1952 break
1945 groups.insert(0, gr)
1953 groups.insert(0, gr)
1946
1954
1947 return groups
1955 return groups
1948
1956
1949 @property
1957 @property
1950 def groups_and_repo(self):
1958 def groups_and_repo(self):
1951 return self.groups_with_parents, self
1959 return self.groups_with_parents, self
1952
1960
1953 @LazyProperty
1961 @LazyProperty
1954 def repo_path(self):
1962 def repo_path(self):
1955 """
1963 """
1956 Returns base full path for that repository means where it actually
1964 Returns base full path for that repository means where it actually
1957 exists on a filesystem
1965 exists on a filesystem
1958 """
1966 """
1959 q = Session().query(RhodeCodeUi).filter(
1967 q = Session().query(RhodeCodeUi).filter(
1960 RhodeCodeUi.ui_key == self.NAME_SEP)
1968 RhodeCodeUi.ui_key == self.NAME_SEP)
1961 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1969 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1962 return q.one().ui_value
1970 return q.one().ui_value
1963
1971
1964 @property
1972 @property
1965 def repo_full_path(self):
1973 def repo_full_path(self):
1966 p = [self.repo_path]
1974 p = [self.repo_path]
1967 # we need to split the name by / since this is how we store the
1975 # we need to split the name by / since this is how we store the
1968 # names in the database, but that eventually needs to be converted
1976 # names in the database, but that eventually needs to be converted
1969 # into a valid system path
1977 # into a valid system path
1970 p += self.repo_name.split(self.NAME_SEP)
1978 p += self.repo_name.split(self.NAME_SEP)
1971 return os.path.join(*map(safe_unicode, p))
1979 return os.path.join(*map(safe_unicode, p))
1972
1980
1973 @property
1981 @property
1974 def cache_keys(self):
1982 def cache_keys(self):
1975 """
1983 """
1976 Returns associated cache keys for that repo
1984 Returns associated cache keys for that repo
1977 """
1985 """
1978 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1986 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1979 repo_id=self.repo_id)
1987 repo_id=self.repo_id)
1980 return CacheKey.query()\
1988 return CacheKey.query()\
1981 .filter(CacheKey.cache_args == invalidation_namespace)\
1989 .filter(CacheKey.cache_args == invalidation_namespace)\
1982 .order_by(CacheKey.cache_key)\
1990 .order_by(CacheKey.cache_key)\
1983 .all()
1991 .all()
1984
1992
1985 @property
1993 @property
1986 def cached_diffs_relative_dir(self):
1994 def cached_diffs_relative_dir(self):
1987 """
1995 """
1988 Return a relative to the repository store path of cached diffs
1996 Return a relative to the repository store path of cached diffs
1989 used for safe display for users, who shouldn't know the absolute store
1997 used for safe display for users, who shouldn't know the absolute store
1990 path
1998 path
1991 """
1999 """
1992 return os.path.join(
2000 return os.path.join(
1993 os.path.dirname(self.repo_name),
2001 os.path.dirname(self.repo_name),
1994 self.cached_diffs_dir.split(os.path.sep)[-1])
2002 self.cached_diffs_dir.split(os.path.sep)[-1])
1995
2003
1996 @property
2004 @property
1997 def cached_diffs_dir(self):
2005 def cached_diffs_dir(self):
1998 path = self.repo_full_path
2006 path = self.repo_full_path
1999 return os.path.join(
2007 return os.path.join(
2000 os.path.dirname(path),
2008 os.path.dirname(path),
2001 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2009 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2002
2010
2003 def cached_diffs(self):
2011 def cached_diffs(self):
2004 diff_cache_dir = self.cached_diffs_dir
2012 diff_cache_dir = self.cached_diffs_dir
2005 if os.path.isdir(diff_cache_dir):
2013 if os.path.isdir(diff_cache_dir):
2006 return os.listdir(diff_cache_dir)
2014 return os.listdir(diff_cache_dir)
2007 return []
2015 return []
2008
2016
2009 def shadow_repos(self):
2017 def shadow_repos(self):
2010 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2018 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2011 return [
2019 return [
2012 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2020 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2013 if x.startswith(shadow_repos_pattern)]
2021 if x.startswith(shadow_repos_pattern)]
2014
2022
2015 def get_new_name(self, repo_name):
2023 def get_new_name(self, repo_name):
2016 """
2024 """
2017 returns new full repository name based on assigned group and new new
2025 returns new full repository name based on assigned group and new new
2018
2026
2019 :param group_name:
2027 :param group_name:
2020 """
2028 """
2021 path_prefix = self.group.full_path_splitted if self.group else []
2029 path_prefix = self.group.full_path_splitted if self.group else []
2022 return self.NAME_SEP.join(path_prefix + [repo_name])
2030 return self.NAME_SEP.join(path_prefix + [repo_name])
2023
2031
2024 @property
2032 @property
2025 def _config(self):
2033 def _config(self):
2026 """
2034 """
2027 Returns db based config object.
2035 Returns db based config object.
2028 """
2036 """
2029 from rhodecode.lib.utils import make_db_config
2037 from rhodecode.lib.utils import make_db_config
2030 return make_db_config(clear_session=False, repo=self)
2038 return make_db_config(clear_session=False, repo=self)
2031
2039
2032 def permissions(self, with_admins=True, with_owner=True,
2040 def permissions(self, with_admins=True, with_owner=True,
2033 expand_from_user_groups=False):
2041 expand_from_user_groups=False):
2034 """
2042 """
2035 Permissions for repositories
2043 Permissions for repositories
2036 """
2044 """
2037 _admin_perm = 'repository.admin'
2045 _admin_perm = 'repository.admin'
2038
2046
2039 owner_row = []
2047 owner_row = []
2040 if with_owner:
2048 if with_owner:
2041 usr = AttributeDict(self.user.get_dict())
2049 usr = AttributeDict(self.user.get_dict())
2042 usr.owner_row = True
2050 usr.owner_row = True
2043 usr.permission = _admin_perm
2051 usr.permission = _admin_perm
2044 usr.permission_id = None
2052 usr.permission_id = None
2045 owner_row.append(usr)
2053 owner_row.append(usr)
2046
2054
2047 super_admin_ids = []
2055 super_admin_ids = []
2048 super_admin_rows = []
2056 super_admin_rows = []
2049 if with_admins:
2057 if with_admins:
2050 for usr in User.get_all_super_admins():
2058 for usr in User.get_all_super_admins():
2051 super_admin_ids.append(usr.user_id)
2059 super_admin_ids.append(usr.user_id)
2052 # if this admin is also owner, don't double the record
2060 # if this admin is also owner, don't double the record
2053 if usr.user_id == owner_row[0].user_id:
2061 if usr.user_id == owner_row[0].user_id:
2054 owner_row[0].admin_row = True
2062 owner_row[0].admin_row = True
2055 else:
2063 else:
2056 usr = AttributeDict(usr.get_dict())
2064 usr = AttributeDict(usr.get_dict())
2057 usr.admin_row = True
2065 usr.admin_row = True
2058 usr.permission = _admin_perm
2066 usr.permission = _admin_perm
2059 usr.permission_id = None
2067 usr.permission_id = None
2060 super_admin_rows.append(usr)
2068 super_admin_rows.append(usr)
2061
2069
2062 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2070 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2063 q = q.options(joinedload(UserRepoToPerm.repository),
2071 q = q.options(joinedload(UserRepoToPerm.repository),
2064 joinedload(UserRepoToPerm.user),
2072 joinedload(UserRepoToPerm.user),
2065 joinedload(UserRepoToPerm.permission),)
2073 joinedload(UserRepoToPerm.permission),)
2066
2074
2067 # get owners and admins and permissions. We do a trick of re-writing
2075 # get owners and admins and permissions. We do a trick of re-writing
2068 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2076 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2069 # has a global reference and changing one object propagates to all
2077 # has a global reference and changing one object propagates to all
2070 # others. This means if admin is also an owner admin_row that change
2078 # others. This means if admin is also an owner admin_row that change
2071 # would propagate to both objects
2079 # would propagate to both objects
2072 perm_rows = []
2080 perm_rows = []
2073 for _usr in q.all():
2081 for _usr in q.all():
2074 usr = AttributeDict(_usr.user.get_dict())
2082 usr = AttributeDict(_usr.user.get_dict())
2075 # if this user is also owner/admin, mark as duplicate record
2083 # if this user is also owner/admin, mark as duplicate record
2076 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2084 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2077 usr.duplicate_perm = True
2085 usr.duplicate_perm = True
2078 # also check if this permission is maybe used by branch_permissions
2086 # also check if this permission is maybe used by branch_permissions
2079 if _usr.branch_perm_entry:
2087 if _usr.branch_perm_entry:
2080 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2088 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2081
2089
2082 usr.permission = _usr.permission.permission_name
2090 usr.permission = _usr.permission.permission_name
2083 usr.permission_id = _usr.repo_to_perm_id
2091 usr.permission_id = _usr.repo_to_perm_id
2084 perm_rows.append(usr)
2092 perm_rows.append(usr)
2085
2093
2086 # filter the perm rows by 'default' first and then sort them by
2094 # filter the perm rows by 'default' first and then sort them by
2087 # admin,write,read,none permissions sorted again alphabetically in
2095 # admin,write,read,none permissions sorted again alphabetically in
2088 # each group
2096 # each group
2089 perm_rows = sorted(perm_rows, key=display_user_sort)
2097 perm_rows = sorted(perm_rows, key=display_user_sort)
2090
2098
2091 user_groups_rows = []
2099 user_groups_rows = []
2092 if expand_from_user_groups:
2100 if expand_from_user_groups:
2093 for ug in self.permission_user_groups(with_members=True):
2101 for ug in self.permission_user_groups(with_members=True):
2094 for user_data in ug.members:
2102 for user_data in ug.members:
2095 user_groups_rows.append(user_data)
2103 user_groups_rows.append(user_data)
2096
2104
2097 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2105 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2098
2106
2099 def permission_user_groups(self, with_members=True):
2107 def permission_user_groups(self, with_members=True):
2100 q = UserGroupRepoToPerm.query()\
2108 q = UserGroupRepoToPerm.query()\
2101 .filter(UserGroupRepoToPerm.repository == self)
2109 .filter(UserGroupRepoToPerm.repository == self)
2102 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2110 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2103 joinedload(UserGroupRepoToPerm.users_group),
2111 joinedload(UserGroupRepoToPerm.users_group),
2104 joinedload(UserGroupRepoToPerm.permission),)
2112 joinedload(UserGroupRepoToPerm.permission),)
2105
2113
2106 perm_rows = []
2114 perm_rows = []
2107 for _user_group in q.all():
2115 for _user_group in q.all():
2108 entry = AttributeDict(_user_group.users_group.get_dict())
2116 entry = AttributeDict(_user_group.users_group.get_dict())
2109 entry.permission = _user_group.permission.permission_name
2117 entry.permission = _user_group.permission.permission_name
2110 if with_members:
2118 if with_members:
2111 entry.members = [x.user.get_dict()
2119 entry.members = [x.user.get_dict()
2112 for x in _user_group.users_group.members]
2120 for x in _user_group.users_group.members]
2113 perm_rows.append(entry)
2121 perm_rows.append(entry)
2114
2122
2115 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2123 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2116 return perm_rows
2124 return perm_rows
2117
2125
2118 def get_api_data(self, include_secrets=False):
2126 def get_api_data(self, include_secrets=False):
2119 """
2127 """
2120 Common function for generating repo api data
2128 Common function for generating repo api data
2121
2129
2122 :param include_secrets: See :meth:`User.get_api_data`.
2130 :param include_secrets: See :meth:`User.get_api_data`.
2123
2131
2124 """
2132 """
2125 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2133 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2126 # move this methods on models level.
2134 # move this methods on models level.
2127 from rhodecode.model.settings import SettingsModel
2135 from rhodecode.model.settings import SettingsModel
2128 from rhodecode.model.repo import RepoModel
2136 from rhodecode.model.repo import RepoModel
2129
2137
2130 repo = self
2138 repo = self
2131 _user_id, _time, _reason = self.locked
2139 _user_id, _time, _reason = self.locked
2132
2140
2133 data = {
2141 data = {
2134 'repo_id': repo.repo_id,
2142 'repo_id': repo.repo_id,
2135 'repo_name': repo.repo_name,
2143 'repo_name': repo.repo_name,
2136 'repo_type': repo.repo_type,
2144 'repo_type': repo.repo_type,
2137 'clone_uri': repo.clone_uri or '',
2145 'clone_uri': repo.clone_uri or '',
2138 'push_uri': repo.push_uri or '',
2146 'push_uri': repo.push_uri or '',
2139 'url': RepoModel().get_url(self),
2147 'url': RepoModel().get_url(self),
2140 'private': repo.private,
2148 'private': repo.private,
2141 'created_on': repo.created_on,
2149 'created_on': repo.created_on,
2142 'description': repo.description_safe,
2150 'description': repo.description_safe,
2143 'landing_rev': repo.landing_rev,
2151 'landing_rev': repo.landing_rev,
2144 'owner': repo.user.username,
2152 'owner': repo.user.username,
2145 'fork_of': repo.fork.repo_name if repo.fork else None,
2153 'fork_of': repo.fork.repo_name if repo.fork else None,
2146 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2154 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2147 'enable_statistics': repo.enable_statistics,
2155 'enable_statistics': repo.enable_statistics,
2148 'enable_locking': repo.enable_locking,
2156 'enable_locking': repo.enable_locking,
2149 'enable_downloads': repo.enable_downloads,
2157 'enable_downloads': repo.enable_downloads,
2150 'last_changeset': repo.changeset_cache,
2158 'last_changeset': repo.changeset_cache,
2151 'locked_by': User.get(_user_id).get_api_data(
2159 'locked_by': User.get(_user_id).get_api_data(
2152 include_secrets=include_secrets) if _user_id else None,
2160 include_secrets=include_secrets) if _user_id else None,
2153 'locked_date': time_to_datetime(_time) if _time else None,
2161 'locked_date': time_to_datetime(_time) if _time else None,
2154 'lock_reason': _reason if _reason else None,
2162 'lock_reason': _reason if _reason else None,
2155 }
2163 }
2156
2164
2157 # TODO: mikhail: should be per-repo settings here
2165 # TODO: mikhail: should be per-repo settings here
2158 rc_config = SettingsModel().get_all_settings()
2166 rc_config = SettingsModel().get_all_settings()
2159 repository_fields = str2bool(
2167 repository_fields = str2bool(
2160 rc_config.get('rhodecode_repository_fields'))
2168 rc_config.get('rhodecode_repository_fields'))
2161 if repository_fields:
2169 if repository_fields:
2162 for f in self.extra_fields:
2170 for f in self.extra_fields:
2163 data[f.field_key_prefixed] = f.field_value
2171 data[f.field_key_prefixed] = f.field_value
2164
2172
2165 return data
2173 return data
2166
2174
2167 @classmethod
2175 @classmethod
2168 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2176 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2169 if not lock_time:
2177 if not lock_time:
2170 lock_time = time.time()
2178 lock_time = time.time()
2171 if not lock_reason:
2179 if not lock_reason:
2172 lock_reason = cls.LOCK_AUTOMATIC
2180 lock_reason = cls.LOCK_AUTOMATIC
2173 repo.locked = [user_id, lock_time, lock_reason]
2181 repo.locked = [user_id, lock_time, lock_reason]
2174 Session().add(repo)
2182 Session().add(repo)
2175 Session().commit()
2183 Session().commit()
2176
2184
2177 @classmethod
2185 @classmethod
2178 def unlock(cls, repo):
2186 def unlock(cls, repo):
2179 repo.locked = None
2187 repo.locked = None
2180 Session().add(repo)
2188 Session().add(repo)
2181 Session().commit()
2189 Session().commit()
2182
2190
2183 @classmethod
2191 @classmethod
2184 def getlock(cls, repo):
2192 def getlock(cls, repo):
2185 return repo.locked
2193 return repo.locked
2186
2194
2187 def is_user_lock(self, user_id):
2195 def is_user_lock(self, user_id):
2188 if self.lock[0]:
2196 if self.lock[0]:
2189 lock_user_id = safe_int(self.lock[0])
2197 lock_user_id = safe_int(self.lock[0])
2190 user_id = safe_int(user_id)
2198 user_id = safe_int(user_id)
2191 # both are ints, and they are equal
2199 # both are ints, and they are equal
2192 return all([lock_user_id, user_id]) and lock_user_id == user_id
2200 return all([lock_user_id, user_id]) and lock_user_id == user_id
2193
2201
2194 return False
2202 return False
2195
2203
2196 def get_locking_state(self, action, user_id, only_when_enabled=True):
2204 def get_locking_state(self, action, user_id, only_when_enabled=True):
2197 """
2205 """
2198 Checks locking on this repository, if locking is enabled and lock is
2206 Checks locking on this repository, if locking is enabled and lock is
2199 present returns a tuple of make_lock, locked, locked_by.
2207 present returns a tuple of make_lock, locked, locked_by.
2200 make_lock can have 3 states None (do nothing) True, make lock
2208 make_lock can have 3 states None (do nothing) True, make lock
2201 False release lock, This value is later propagated to hooks, which
2209 False release lock, This value is later propagated to hooks, which
2202 do the locking. Think about this as signals passed to hooks what to do.
2210 do the locking. Think about this as signals passed to hooks what to do.
2203
2211
2204 """
2212 """
2205 # TODO: johbo: This is part of the business logic and should be moved
2213 # TODO: johbo: This is part of the business logic and should be moved
2206 # into the RepositoryModel.
2214 # into the RepositoryModel.
2207
2215
2208 if action not in ('push', 'pull'):
2216 if action not in ('push', 'pull'):
2209 raise ValueError("Invalid action value: %s" % repr(action))
2217 raise ValueError("Invalid action value: %s" % repr(action))
2210
2218
2211 # defines if locked error should be thrown to user
2219 # defines if locked error should be thrown to user
2212 currently_locked = False
2220 currently_locked = False
2213 # defines if new lock should be made, tri-state
2221 # defines if new lock should be made, tri-state
2214 make_lock = None
2222 make_lock = None
2215 repo = self
2223 repo = self
2216 user = User.get(user_id)
2224 user = User.get(user_id)
2217
2225
2218 lock_info = repo.locked
2226 lock_info = repo.locked
2219
2227
2220 if repo and (repo.enable_locking or not only_when_enabled):
2228 if repo and (repo.enable_locking or not only_when_enabled):
2221 if action == 'push':
2229 if action == 'push':
2222 # check if it's already locked !, if it is compare users
2230 # check if it's already locked !, if it is compare users
2223 locked_by_user_id = lock_info[0]
2231 locked_by_user_id = lock_info[0]
2224 if user.user_id == locked_by_user_id:
2232 if user.user_id == locked_by_user_id:
2225 log.debug(
2233 log.debug(
2226 'Got `push` action from user %s, now unlocking', user)
2234 'Got `push` action from user %s, now unlocking', user)
2227 # unlock if we have push from user who locked
2235 # unlock if we have push from user who locked
2228 make_lock = False
2236 make_lock = False
2229 else:
2237 else:
2230 # we're not the same user who locked, ban with
2238 # we're not the same user who locked, ban with
2231 # code defined in settings (default is 423 HTTP Locked) !
2239 # code defined in settings (default is 423 HTTP Locked) !
2232 log.debug('Repo %s is currently locked by %s', repo, user)
2240 log.debug('Repo %s is currently locked by %s', repo, user)
2233 currently_locked = True
2241 currently_locked = True
2234 elif action == 'pull':
2242 elif action == 'pull':
2235 # [0] user [1] date
2243 # [0] user [1] date
2236 if lock_info[0] and lock_info[1]:
2244 if lock_info[0] and lock_info[1]:
2237 log.debug('Repo %s is currently locked by %s', repo, user)
2245 log.debug('Repo %s is currently locked by %s', repo, user)
2238 currently_locked = True
2246 currently_locked = True
2239 else:
2247 else:
2240 log.debug('Setting lock on repo %s by %s', repo, user)
2248 log.debug('Setting lock on repo %s by %s', repo, user)
2241 make_lock = True
2249 make_lock = True
2242
2250
2243 else:
2251 else:
2244 log.debug('Repository %s do not have locking enabled', repo)
2252 log.debug('Repository %s do not have locking enabled', repo)
2245
2253
2246 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2254 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2247 make_lock, currently_locked, lock_info)
2255 make_lock, currently_locked, lock_info)
2248
2256
2249 from rhodecode.lib.auth import HasRepoPermissionAny
2257 from rhodecode.lib.auth import HasRepoPermissionAny
2250 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2258 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2251 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2259 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2252 # if we don't have at least write permission we cannot make a lock
2260 # if we don't have at least write permission we cannot make a lock
2253 log.debug('lock state reset back to FALSE due to lack '
2261 log.debug('lock state reset back to FALSE due to lack '
2254 'of at least read permission')
2262 'of at least read permission')
2255 make_lock = False
2263 make_lock = False
2256
2264
2257 return make_lock, currently_locked, lock_info
2265 return make_lock, currently_locked, lock_info
2258
2266
2259 @property
2267 @property
2260 def last_commit_cache_update_diff(self):
2268 def last_commit_cache_update_diff(self):
2261 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2269 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2262
2270
2263 @classmethod
2271 @classmethod
2264 def _load_commit_change(cls, last_commit_cache):
2272 def _load_commit_change(cls, last_commit_cache):
2265 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2273 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2266 empty_date = datetime.datetime.fromtimestamp(0)
2274 empty_date = datetime.datetime.fromtimestamp(0)
2267 date_latest = last_commit_cache.get('date', empty_date)
2275 date_latest = last_commit_cache.get('date', empty_date)
2268 try:
2276 try:
2269 return parse_datetime(date_latest)
2277 return parse_datetime(date_latest)
2270 except Exception:
2278 except Exception:
2271 return empty_date
2279 return empty_date
2272
2280
2273 @property
2281 @property
2274 def last_commit_change(self):
2282 def last_commit_change(self):
2275 return self._load_commit_change(self.changeset_cache)
2283 return self._load_commit_change(self.changeset_cache)
2276
2284
2277 @property
2285 @property
2278 def last_db_change(self):
2286 def last_db_change(self):
2279 return self.updated_on
2287 return self.updated_on
2280
2288
2281 @property
2289 @property
2282 def clone_uri_hidden(self):
2290 def clone_uri_hidden(self):
2283 clone_uri = self.clone_uri
2291 clone_uri = self.clone_uri
2284 if clone_uri:
2292 if clone_uri:
2285 import urlobject
2293 import urlobject
2286 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2294 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2287 if url_obj.password:
2295 if url_obj.password:
2288 clone_uri = url_obj.with_password('*****')
2296 clone_uri = url_obj.with_password('*****')
2289 return clone_uri
2297 return clone_uri
2290
2298
2291 @property
2299 @property
2292 def push_uri_hidden(self):
2300 def push_uri_hidden(self):
2293 push_uri = self.push_uri
2301 push_uri = self.push_uri
2294 if push_uri:
2302 if push_uri:
2295 import urlobject
2303 import urlobject
2296 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2304 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2297 if url_obj.password:
2305 if url_obj.password:
2298 push_uri = url_obj.with_password('*****')
2306 push_uri = url_obj.with_password('*****')
2299 return push_uri
2307 return push_uri
2300
2308
2301 def clone_url(self, **override):
2309 def clone_url(self, **override):
2302 from rhodecode.model.settings import SettingsModel
2310 from rhodecode.model.settings import SettingsModel
2303
2311
2304 uri_tmpl = None
2312 uri_tmpl = None
2305 if 'with_id' in override:
2313 if 'with_id' in override:
2306 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2314 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2307 del override['with_id']
2315 del override['with_id']
2308
2316
2309 if 'uri_tmpl' in override:
2317 if 'uri_tmpl' in override:
2310 uri_tmpl = override['uri_tmpl']
2318 uri_tmpl = override['uri_tmpl']
2311 del override['uri_tmpl']
2319 del override['uri_tmpl']
2312
2320
2313 ssh = False
2321 ssh = False
2314 if 'ssh' in override:
2322 if 'ssh' in override:
2315 ssh = True
2323 ssh = True
2316 del override['ssh']
2324 del override['ssh']
2317
2325
2318 # we didn't override our tmpl from **overrides
2326 # we didn't override our tmpl from **overrides
2319 request = get_current_request()
2327 request = get_current_request()
2320 if not uri_tmpl:
2328 if not uri_tmpl:
2321 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2329 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2322 rc_config = request.call_context.rc_config
2330 rc_config = request.call_context.rc_config
2323 else:
2331 else:
2324 rc_config = SettingsModel().get_all_settings(cache=True)
2332 rc_config = SettingsModel().get_all_settings(cache=True)
2325
2333
2326 if ssh:
2334 if ssh:
2327 uri_tmpl = rc_config.get(
2335 uri_tmpl = rc_config.get(
2328 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2336 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2329
2337
2330 else:
2338 else:
2331 uri_tmpl = rc_config.get(
2339 uri_tmpl = rc_config.get(
2332 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2340 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2333
2341
2334 return get_clone_url(request=request,
2342 return get_clone_url(request=request,
2335 uri_tmpl=uri_tmpl,
2343 uri_tmpl=uri_tmpl,
2336 repo_name=self.repo_name,
2344 repo_name=self.repo_name,
2337 repo_id=self.repo_id,
2345 repo_id=self.repo_id,
2338 repo_type=self.repo_type,
2346 repo_type=self.repo_type,
2339 **override)
2347 **override)
2340
2348
2341 def set_state(self, state):
2349 def set_state(self, state):
2342 self.repo_state = state
2350 self.repo_state = state
2343 Session().add(self)
2351 Session().add(self)
2344 #==========================================================================
2352 #==========================================================================
2345 # SCM PROPERTIES
2353 # SCM PROPERTIES
2346 #==========================================================================
2354 #==========================================================================
2347
2355
2348 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2356 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2349 return get_commit_safe(
2357 return get_commit_safe(
2350 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2358 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2351 maybe_unreachable=maybe_unreachable)
2359 maybe_unreachable=maybe_unreachable)
2352
2360
2353 def get_changeset(self, rev=None, pre_load=None):
2361 def get_changeset(self, rev=None, pre_load=None):
2354 warnings.warn("Use get_commit", DeprecationWarning)
2362 warnings.warn("Use get_commit", DeprecationWarning)
2355 commit_id = None
2363 commit_id = None
2356 commit_idx = None
2364 commit_idx = None
2357 if isinstance(rev, compat.string_types):
2365 if isinstance(rev, compat.string_types):
2358 commit_id = rev
2366 commit_id = rev
2359 else:
2367 else:
2360 commit_idx = rev
2368 commit_idx = rev
2361 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2369 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2362 pre_load=pre_load)
2370 pre_load=pre_load)
2363
2371
2364 def get_landing_commit(self):
2372 def get_landing_commit(self):
2365 """
2373 """
2366 Returns landing commit, or if that doesn't exist returns the tip
2374 Returns landing commit, or if that doesn't exist returns the tip
2367 """
2375 """
2368 _rev_type, _rev = self.landing_rev
2376 _rev_type, _rev = self.landing_rev
2369 commit = self.get_commit(_rev)
2377 commit = self.get_commit(_rev)
2370 if isinstance(commit, EmptyCommit):
2378 if isinstance(commit, EmptyCommit):
2371 return self.get_commit()
2379 return self.get_commit()
2372 return commit
2380 return commit
2373
2381
2374 def flush_commit_cache(self):
2382 def flush_commit_cache(self):
2375 self.update_commit_cache(cs_cache={'raw_id':'0'})
2383 self.update_commit_cache(cs_cache={'raw_id':'0'})
2376 self.update_commit_cache()
2384 self.update_commit_cache()
2377
2385
2378 def update_commit_cache(self, cs_cache=None, config=None):
2386 def update_commit_cache(self, cs_cache=None, config=None):
2379 """
2387 """
2380 Update cache of last commit for repository
2388 Update cache of last commit for repository
2381 cache_keys should be::
2389 cache_keys should be::
2382
2390
2383 source_repo_id
2391 source_repo_id
2384 short_id
2392 short_id
2385 raw_id
2393 raw_id
2386 revision
2394 revision
2387 parents
2395 parents
2388 message
2396 message
2389 date
2397 date
2390 author
2398 author
2391 updated_on
2399 updated_on
2392
2400
2393 """
2401 """
2394 from rhodecode.lib.vcs.backends.base import BaseChangeset
2402 from rhodecode.lib.vcs.backends.base import BaseChangeset
2395 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2403 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2396 empty_date = datetime.datetime.fromtimestamp(0)
2404 empty_date = datetime.datetime.fromtimestamp(0)
2397
2405
2398 if cs_cache is None:
2406 if cs_cache is None:
2399 # use no-cache version here
2407 # use no-cache version here
2400 try:
2408 try:
2401 scm_repo = self.scm_instance(cache=False, config=config)
2409 scm_repo = self.scm_instance(cache=False, config=config)
2402 except VCSError:
2410 except VCSError:
2403 scm_repo = None
2411 scm_repo = None
2404 empty = scm_repo is None or scm_repo.is_empty()
2412 empty = scm_repo is None or scm_repo.is_empty()
2405
2413
2406 if not empty:
2414 if not empty:
2407 cs_cache = scm_repo.get_commit(
2415 cs_cache = scm_repo.get_commit(
2408 pre_load=["author", "date", "message", "parents", "branch"])
2416 pre_load=["author", "date", "message", "parents", "branch"])
2409 else:
2417 else:
2410 cs_cache = EmptyCommit()
2418 cs_cache = EmptyCommit()
2411
2419
2412 if isinstance(cs_cache, BaseChangeset):
2420 if isinstance(cs_cache, BaseChangeset):
2413 cs_cache = cs_cache.__json__()
2421 cs_cache = cs_cache.__json__()
2414
2422
2415 def is_outdated(new_cs_cache):
2423 def is_outdated(new_cs_cache):
2416 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2424 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2417 new_cs_cache['revision'] != self.changeset_cache['revision']):
2425 new_cs_cache['revision'] != self.changeset_cache['revision']):
2418 return True
2426 return True
2419 return False
2427 return False
2420
2428
2421 # check if we have maybe already latest cached revision
2429 # check if we have maybe already latest cached revision
2422 if is_outdated(cs_cache) or not self.changeset_cache:
2430 if is_outdated(cs_cache) or not self.changeset_cache:
2423 _current_datetime = datetime.datetime.utcnow()
2431 _current_datetime = datetime.datetime.utcnow()
2424 last_change = cs_cache.get('date') or _current_datetime
2432 last_change = cs_cache.get('date') or _current_datetime
2425 # we check if last update is newer than the new value
2433 # we check if last update is newer than the new value
2426 # if yes, we use the current timestamp instead. Imagine you get
2434 # if yes, we use the current timestamp instead. Imagine you get
2427 # old commit pushed 1y ago, we'd set last update 1y to ago.
2435 # old commit pushed 1y ago, we'd set last update 1y to ago.
2428 last_change_timestamp = datetime_to_time(last_change)
2436 last_change_timestamp = datetime_to_time(last_change)
2429 current_timestamp = datetime_to_time(last_change)
2437 current_timestamp = datetime_to_time(last_change)
2430 if last_change_timestamp > current_timestamp and not empty:
2438 if last_change_timestamp > current_timestamp and not empty:
2431 cs_cache['date'] = _current_datetime
2439 cs_cache['date'] = _current_datetime
2432
2440
2433 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2441 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2434 cs_cache['updated_on'] = time.time()
2442 cs_cache['updated_on'] = time.time()
2435 self.changeset_cache = cs_cache
2443 self.changeset_cache = cs_cache
2436 self.updated_on = last_change
2444 self.updated_on = last_change
2437 Session().add(self)
2445 Session().add(self)
2438 Session().commit()
2446 Session().commit()
2439
2447
2440 else:
2448 else:
2441 if empty:
2449 if empty:
2442 cs_cache = EmptyCommit().__json__()
2450 cs_cache = EmptyCommit().__json__()
2443 else:
2451 else:
2444 cs_cache = self.changeset_cache
2452 cs_cache = self.changeset_cache
2445
2453
2446 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2454 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2447
2455
2448 cs_cache['updated_on'] = time.time()
2456 cs_cache['updated_on'] = time.time()
2449 self.changeset_cache = cs_cache
2457 self.changeset_cache = cs_cache
2450 self.updated_on = _date_latest
2458 self.updated_on = _date_latest
2451 Session().add(self)
2459 Session().add(self)
2452 Session().commit()
2460 Session().commit()
2453
2461
2454 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2462 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2455 self.repo_name, cs_cache, _date_latest)
2463 self.repo_name, cs_cache, _date_latest)
2456
2464
2457 @property
2465 @property
2458 def tip(self):
2466 def tip(self):
2459 return self.get_commit('tip')
2467 return self.get_commit('tip')
2460
2468
2461 @property
2469 @property
2462 def author(self):
2470 def author(self):
2463 return self.tip.author
2471 return self.tip.author
2464
2472
2465 @property
2473 @property
2466 def last_change(self):
2474 def last_change(self):
2467 return self.scm_instance().last_change
2475 return self.scm_instance().last_change
2468
2476
2469 def get_comments(self, revisions=None):
2477 def get_comments(self, revisions=None):
2470 """
2478 """
2471 Returns comments for this repository grouped by revisions
2479 Returns comments for this repository grouped by revisions
2472
2480
2473 :param revisions: filter query by revisions only
2481 :param revisions: filter query by revisions only
2474 """
2482 """
2475 cmts = ChangesetComment.query()\
2483 cmts = ChangesetComment.query()\
2476 .filter(ChangesetComment.repo == self)
2484 .filter(ChangesetComment.repo == self)
2477 if revisions:
2485 if revisions:
2478 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2486 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2479 grouped = collections.defaultdict(list)
2487 grouped = collections.defaultdict(list)
2480 for cmt in cmts.all():
2488 for cmt in cmts.all():
2481 grouped[cmt.revision].append(cmt)
2489 grouped[cmt.revision].append(cmt)
2482 return grouped
2490 return grouped
2483
2491
2484 def statuses(self, revisions=None):
2492 def statuses(self, revisions=None):
2485 """
2493 """
2486 Returns statuses for this repository
2494 Returns statuses for this repository
2487
2495
2488 :param revisions: list of revisions to get statuses for
2496 :param revisions: list of revisions to get statuses for
2489 """
2497 """
2490 statuses = ChangesetStatus.query()\
2498 statuses = ChangesetStatus.query()\
2491 .filter(ChangesetStatus.repo == self)\
2499 .filter(ChangesetStatus.repo == self)\
2492 .filter(ChangesetStatus.version == 0)
2500 .filter(ChangesetStatus.version == 0)
2493
2501
2494 if revisions:
2502 if revisions:
2495 # Try doing the filtering in chunks to avoid hitting limits
2503 # Try doing the filtering in chunks to avoid hitting limits
2496 size = 500
2504 size = 500
2497 status_results = []
2505 status_results = []
2498 for chunk in xrange(0, len(revisions), size):
2506 for chunk in xrange(0, len(revisions), size):
2499 status_results += statuses.filter(
2507 status_results += statuses.filter(
2500 ChangesetStatus.revision.in_(
2508 ChangesetStatus.revision.in_(
2501 revisions[chunk: chunk+size])
2509 revisions[chunk: chunk+size])
2502 ).all()
2510 ).all()
2503 else:
2511 else:
2504 status_results = statuses.all()
2512 status_results = statuses.all()
2505
2513
2506 grouped = {}
2514 grouped = {}
2507
2515
2508 # maybe we have open new pullrequest without a status?
2516 # maybe we have open new pullrequest without a status?
2509 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2517 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2510 status_lbl = ChangesetStatus.get_status_lbl(stat)
2518 status_lbl = ChangesetStatus.get_status_lbl(stat)
2511 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2519 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2512 for rev in pr.revisions:
2520 for rev in pr.revisions:
2513 pr_id = pr.pull_request_id
2521 pr_id = pr.pull_request_id
2514 pr_repo = pr.target_repo.repo_name
2522 pr_repo = pr.target_repo.repo_name
2515 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2523 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2516
2524
2517 for stat in status_results:
2525 for stat in status_results:
2518 pr_id = pr_repo = None
2526 pr_id = pr_repo = None
2519 if stat.pull_request:
2527 if stat.pull_request:
2520 pr_id = stat.pull_request.pull_request_id
2528 pr_id = stat.pull_request.pull_request_id
2521 pr_repo = stat.pull_request.target_repo.repo_name
2529 pr_repo = stat.pull_request.target_repo.repo_name
2522 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2530 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2523 pr_id, pr_repo]
2531 pr_id, pr_repo]
2524 return grouped
2532 return grouped
2525
2533
2526 # ==========================================================================
2534 # ==========================================================================
2527 # SCM CACHE INSTANCE
2535 # SCM CACHE INSTANCE
2528 # ==========================================================================
2536 # ==========================================================================
2529
2537
2530 def scm_instance(self, **kwargs):
2538 def scm_instance(self, **kwargs):
2531 import rhodecode
2539 import rhodecode
2532
2540
2533 # Passing a config will not hit the cache currently only used
2541 # Passing a config will not hit the cache currently only used
2534 # for repo2dbmapper
2542 # for repo2dbmapper
2535 config = kwargs.pop('config', None)
2543 config = kwargs.pop('config', None)
2536 cache = kwargs.pop('cache', None)
2544 cache = kwargs.pop('cache', None)
2537 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2545 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2538 if vcs_full_cache is not None:
2546 if vcs_full_cache is not None:
2539 # allows override global config
2547 # allows override global config
2540 full_cache = vcs_full_cache
2548 full_cache = vcs_full_cache
2541 else:
2549 else:
2542 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2550 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2543 # if cache is NOT defined use default global, else we have a full
2551 # if cache is NOT defined use default global, else we have a full
2544 # control over cache behaviour
2552 # control over cache behaviour
2545 if cache is None and full_cache and not config:
2553 if cache is None and full_cache and not config:
2546 log.debug('Initializing pure cached instance for %s', self.repo_path)
2554 log.debug('Initializing pure cached instance for %s', self.repo_path)
2547 return self._get_instance_cached()
2555 return self._get_instance_cached()
2548
2556
2549 # cache here is sent to the "vcs server"
2557 # cache here is sent to the "vcs server"
2550 return self._get_instance(cache=bool(cache), config=config)
2558 return self._get_instance(cache=bool(cache), config=config)
2551
2559
2552 def _get_instance_cached(self):
2560 def _get_instance_cached(self):
2553 from rhodecode.lib import rc_cache
2561 from rhodecode.lib import rc_cache
2554
2562
2555 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2563 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2556 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2564 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2557 repo_id=self.repo_id)
2565 repo_id=self.repo_id)
2558 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2566 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2559
2567
2560 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2568 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2561 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2569 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2562 return self._get_instance(repo_state_uid=_cache_state_uid)
2570 return self._get_instance(repo_state_uid=_cache_state_uid)
2563
2571
2564 # we must use thread scoped cache here,
2572 # we must use thread scoped cache here,
2565 # because each thread of gevent needs it's own not shared connection and cache
2573 # because each thread of gevent needs it's own not shared connection and cache
2566 # we also alter `args` so the cache key is individual for every green thread.
2574 # we also alter `args` so the cache key is individual for every green thread.
2567 inv_context_manager = rc_cache.InvalidationContext(
2575 inv_context_manager = rc_cache.InvalidationContext(
2568 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2576 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2569 thread_scoped=True)
2577 thread_scoped=True)
2570 with inv_context_manager as invalidation_context:
2578 with inv_context_manager as invalidation_context:
2571 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2579 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2572 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2580 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2573
2581
2574 # re-compute and store cache if we get invalidate signal
2582 # re-compute and store cache if we get invalidate signal
2575 if invalidation_context.should_invalidate():
2583 if invalidation_context.should_invalidate():
2576 instance = get_instance_cached.refresh(*args)
2584 instance = get_instance_cached.refresh(*args)
2577 else:
2585 else:
2578 instance = get_instance_cached(*args)
2586 instance = get_instance_cached(*args)
2579
2587
2580 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2588 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2581 return instance
2589 return instance
2582
2590
2583 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2591 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2584 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2592 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2585 self.repo_type, self.repo_path, cache)
2593 self.repo_type, self.repo_path, cache)
2586 config = config or self._config
2594 config = config or self._config
2587 custom_wire = {
2595 custom_wire = {
2588 'cache': cache, # controls the vcs.remote cache
2596 'cache': cache, # controls the vcs.remote cache
2589 'repo_state_uid': repo_state_uid
2597 'repo_state_uid': repo_state_uid
2590 }
2598 }
2591 repo = get_vcs_instance(
2599 repo = get_vcs_instance(
2592 repo_path=safe_str(self.repo_full_path),
2600 repo_path=safe_str(self.repo_full_path),
2593 config=config,
2601 config=config,
2594 with_wire=custom_wire,
2602 with_wire=custom_wire,
2595 create=False,
2603 create=False,
2596 _vcs_alias=self.repo_type)
2604 _vcs_alias=self.repo_type)
2597 if repo is not None:
2605 if repo is not None:
2598 repo.count() # cache rebuild
2606 repo.count() # cache rebuild
2599 return repo
2607 return repo
2600
2608
2601 def get_shadow_repository_path(self, workspace_id):
2609 def get_shadow_repository_path(self, workspace_id):
2602 from rhodecode.lib.vcs.backends.base import BaseRepository
2610 from rhodecode.lib.vcs.backends.base import BaseRepository
2603 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2611 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2604 self.repo_full_path, self.repo_id, workspace_id)
2612 self.repo_full_path, self.repo_id, workspace_id)
2605 return shadow_repo_path
2613 return shadow_repo_path
2606
2614
2607 def __json__(self):
2615 def __json__(self):
2608 return {'landing_rev': self.landing_rev}
2616 return {'landing_rev': self.landing_rev}
2609
2617
2610 def get_dict(self):
2618 def get_dict(self):
2611
2619
2612 # Since we transformed `repo_name` to a hybrid property, we need to
2620 # Since we transformed `repo_name` to a hybrid property, we need to
2613 # keep compatibility with the code which uses `repo_name` field.
2621 # keep compatibility with the code which uses `repo_name` field.
2614
2622
2615 result = super(Repository, self).get_dict()
2623 result = super(Repository, self).get_dict()
2616 result['repo_name'] = result.pop('_repo_name', None)
2624 result['repo_name'] = result.pop('_repo_name', None)
2617 return result
2625 return result
2618
2626
2619
2627
2620 class RepoGroup(Base, BaseModel):
2628 class RepoGroup(Base, BaseModel):
2621 __tablename__ = 'groups'
2629 __tablename__ = 'groups'
2622 __table_args__ = (
2630 __table_args__ = (
2623 UniqueConstraint('group_name', 'group_parent_id'),
2631 UniqueConstraint('group_name', 'group_parent_id'),
2624 base_table_args,
2632 base_table_args,
2625 )
2633 )
2626 __mapper_args__ = {'order_by': 'group_name'}
2634 __mapper_args__ = {'order_by': 'group_name'}
2627
2635
2628 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2636 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2629
2637
2630 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2638 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2631 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2639 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2632 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2640 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2633 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2641 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2634 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2642 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2635 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2643 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2636 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2644 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2637 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2645 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2638 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2646 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2639 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2647 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2640 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2648 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2641
2649
2642 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2650 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2643 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2651 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2644 parent_group = relationship('RepoGroup', remote_side=group_id)
2652 parent_group = relationship('RepoGroup', remote_side=group_id)
2645 user = relationship('User')
2653 user = relationship('User')
2646 integrations = relationship('Integration', cascade="all, delete-orphan")
2654 integrations = relationship('Integration', cascade="all, delete-orphan")
2647
2655
2648 # no cascade, set NULL
2656 # no cascade, set NULL
2649 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2657 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2650
2658
2651 def __init__(self, group_name='', parent_group=None):
2659 def __init__(self, group_name='', parent_group=None):
2652 self.group_name = group_name
2660 self.group_name = group_name
2653 self.parent_group = parent_group
2661 self.parent_group = parent_group
2654
2662
2655 def __unicode__(self):
2663 def __unicode__(self):
2656 return u"<%s('id:%s:%s')>" % (
2664 return u"<%s('id:%s:%s')>" % (
2657 self.__class__.__name__, self.group_id, self.group_name)
2665 self.__class__.__name__, self.group_id, self.group_name)
2658
2666
2659 @hybrid_property
2667 @hybrid_property
2660 def group_name(self):
2668 def group_name(self):
2661 return self._group_name
2669 return self._group_name
2662
2670
2663 @group_name.setter
2671 @group_name.setter
2664 def group_name(self, value):
2672 def group_name(self, value):
2665 self._group_name = value
2673 self._group_name = value
2666 self.group_name_hash = self.hash_repo_group_name(value)
2674 self.group_name_hash = self.hash_repo_group_name(value)
2667
2675
2668 @classmethod
2676 @classmethod
2669 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2677 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2670 from rhodecode.lib.vcs.backends.base import EmptyCommit
2678 from rhodecode.lib.vcs.backends.base import EmptyCommit
2671 dummy = EmptyCommit().__json__()
2679 dummy = EmptyCommit().__json__()
2672 if not changeset_cache_raw:
2680 if not changeset_cache_raw:
2673 dummy['source_repo_id'] = repo_id
2681 dummy['source_repo_id'] = repo_id
2674 return json.loads(json.dumps(dummy))
2682 return json.loads(json.dumps(dummy))
2675
2683
2676 try:
2684 try:
2677 return json.loads(changeset_cache_raw)
2685 return json.loads(changeset_cache_raw)
2678 except TypeError:
2686 except TypeError:
2679 return dummy
2687 return dummy
2680 except Exception:
2688 except Exception:
2681 log.error(traceback.format_exc())
2689 log.error(traceback.format_exc())
2682 return dummy
2690 return dummy
2683
2691
2684 @hybrid_property
2692 @hybrid_property
2685 def changeset_cache(self):
2693 def changeset_cache(self):
2686 return self._load_changeset_cache('', self._changeset_cache)
2694 return self._load_changeset_cache('', self._changeset_cache)
2687
2695
2688 @changeset_cache.setter
2696 @changeset_cache.setter
2689 def changeset_cache(self, val):
2697 def changeset_cache(self, val):
2690 try:
2698 try:
2691 self._changeset_cache = json.dumps(val)
2699 self._changeset_cache = json.dumps(val)
2692 except Exception:
2700 except Exception:
2693 log.error(traceback.format_exc())
2701 log.error(traceback.format_exc())
2694
2702
2695 @validates('group_parent_id')
2703 @validates('group_parent_id')
2696 def validate_group_parent_id(self, key, val):
2704 def validate_group_parent_id(self, key, val):
2697 """
2705 """
2698 Check cycle references for a parent group to self
2706 Check cycle references for a parent group to self
2699 """
2707 """
2700 if self.group_id and val:
2708 if self.group_id and val:
2701 assert val != self.group_id
2709 assert val != self.group_id
2702
2710
2703 return val
2711 return val
2704
2712
2705 @hybrid_property
2713 @hybrid_property
2706 def description_safe(self):
2714 def description_safe(self):
2707 from rhodecode.lib import helpers as h
2715 from rhodecode.lib import helpers as h
2708 return h.escape(self.group_description)
2716 return h.escape(self.group_description)
2709
2717
2710 @classmethod
2718 @classmethod
2711 def hash_repo_group_name(cls, repo_group_name):
2719 def hash_repo_group_name(cls, repo_group_name):
2712 val = remove_formatting(repo_group_name)
2720 val = remove_formatting(repo_group_name)
2713 val = safe_str(val).lower()
2721 val = safe_str(val).lower()
2714 chars = []
2722 chars = []
2715 for c in val:
2723 for c in val:
2716 if c not in string.ascii_letters:
2724 if c not in string.ascii_letters:
2717 c = str(ord(c))
2725 c = str(ord(c))
2718 chars.append(c)
2726 chars.append(c)
2719
2727
2720 return ''.join(chars)
2728 return ''.join(chars)
2721
2729
2722 @classmethod
2730 @classmethod
2723 def _generate_choice(cls, repo_group):
2731 def _generate_choice(cls, repo_group):
2724 from webhelpers2.html import literal as _literal
2732 from webhelpers2.html import literal as _literal
2725 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2733 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2726 return repo_group.group_id, _name(repo_group.full_path_splitted)
2734 return repo_group.group_id, _name(repo_group.full_path_splitted)
2727
2735
2728 @classmethod
2736 @classmethod
2729 def groups_choices(cls, groups=None, show_empty_group=True):
2737 def groups_choices(cls, groups=None, show_empty_group=True):
2730 if not groups:
2738 if not groups:
2731 groups = cls.query().all()
2739 groups = cls.query().all()
2732
2740
2733 repo_groups = []
2741 repo_groups = []
2734 if show_empty_group:
2742 if show_empty_group:
2735 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2743 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2736
2744
2737 repo_groups.extend([cls._generate_choice(x) for x in groups])
2745 repo_groups.extend([cls._generate_choice(x) for x in groups])
2738
2746
2739 repo_groups = sorted(
2747 repo_groups = sorted(
2740 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2748 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2741 return repo_groups
2749 return repo_groups
2742
2750
2743 @classmethod
2751 @classmethod
2744 def url_sep(cls):
2752 def url_sep(cls):
2745 return URL_SEP
2753 return URL_SEP
2746
2754
2747 @classmethod
2755 @classmethod
2748 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2756 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2749 if case_insensitive:
2757 if case_insensitive:
2750 gr = cls.query().filter(func.lower(cls.group_name)
2758 gr = cls.query().filter(func.lower(cls.group_name)
2751 == func.lower(group_name))
2759 == func.lower(group_name))
2752 else:
2760 else:
2753 gr = cls.query().filter(cls.group_name == group_name)
2761 gr = cls.query().filter(cls.group_name == group_name)
2754 if cache:
2762 if cache:
2755 name_key = _hash_key(group_name)
2763 name_key = _hash_key(group_name)
2756 gr = gr.options(
2764 gr = gr.options(
2757 FromCache("sql_cache_short", "get_group_%s" % name_key))
2765 FromCache("sql_cache_short", "get_group_%s" % name_key))
2758 return gr.scalar()
2766 return gr.scalar()
2759
2767
2760 @classmethod
2768 @classmethod
2761 def get_user_personal_repo_group(cls, user_id):
2769 def get_user_personal_repo_group(cls, user_id):
2762 user = User.get(user_id)
2770 user = User.get(user_id)
2763 if user.username == User.DEFAULT_USER:
2771 if user.username == User.DEFAULT_USER:
2764 return None
2772 return None
2765
2773
2766 return cls.query()\
2774 return cls.query()\
2767 .filter(cls.personal == true()) \
2775 .filter(cls.personal == true()) \
2768 .filter(cls.user == user) \
2776 .filter(cls.user == user) \
2769 .order_by(cls.group_id.asc()) \
2777 .order_by(cls.group_id.asc()) \
2770 .first()
2778 .first()
2771
2779
2772 @classmethod
2780 @classmethod
2773 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2781 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2774 case_insensitive=True):
2782 case_insensitive=True):
2775 q = RepoGroup.query()
2783 q = RepoGroup.query()
2776
2784
2777 if not isinstance(user_id, Optional):
2785 if not isinstance(user_id, Optional):
2778 q = q.filter(RepoGroup.user_id == user_id)
2786 q = q.filter(RepoGroup.user_id == user_id)
2779
2787
2780 if not isinstance(group_id, Optional):
2788 if not isinstance(group_id, Optional):
2781 q = q.filter(RepoGroup.group_parent_id == group_id)
2789 q = q.filter(RepoGroup.group_parent_id == group_id)
2782
2790
2783 if case_insensitive:
2791 if case_insensitive:
2784 q = q.order_by(func.lower(RepoGroup.group_name))
2792 q = q.order_by(func.lower(RepoGroup.group_name))
2785 else:
2793 else:
2786 q = q.order_by(RepoGroup.group_name)
2794 q = q.order_by(RepoGroup.group_name)
2787 return q.all()
2795 return q.all()
2788
2796
2789 @property
2797 @property
2790 def parents(self, parents_recursion_limit=10):
2798 def parents(self, parents_recursion_limit=10):
2791 groups = []
2799 groups = []
2792 if self.parent_group is None:
2800 if self.parent_group is None:
2793 return groups
2801 return groups
2794 cur_gr = self.parent_group
2802 cur_gr = self.parent_group
2795 groups.insert(0, cur_gr)
2803 groups.insert(0, cur_gr)
2796 cnt = 0
2804 cnt = 0
2797 while 1:
2805 while 1:
2798 cnt += 1
2806 cnt += 1
2799 gr = getattr(cur_gr, 'parent_group', None)
2807 gr = getattr(cur_gr, 'parent_group', None)
2800 cur_gr = cur_gr.parent_group
2808 cur_gr = cur_gr.parent_group
2801 if gr is None:
2809 if gr is None:
2802 break
2810 break
2803 if cnt == parents_recursion_limit:
2811 if cnt == parents_recursion_limit:
2804 # this will prevent accidental infinit loops
2812 # this will prevent accidental infinit loops
2805 log.error('more than %s parents found for group %s, stopping '
2813 log.error('more than %s parents found for group %s, stopping '
2806 'recursive parent fetching', parents_recursion_limit, self)
2814 'recursive parent fetching', parents_recursion_limit, self)
2807 break
2815 break
2808
2816
2809 groups.insert(0, gr)
2817 groups.insert(0, gr)
2810 return groups
2818 return groups
2811
2819
2812 @property
2820 @property
2813 def last_commit_cache_update_diff(self):
2821 def last_commit_cache_update_diff(self):
2814 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2822 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2815
2823
2816 @classmethod
2824 @classmethod
2817 def _load_commit_change(cls, last_commit_cache):
2825 def _load_commit_change(cls, last_commit_cache):
2818 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2826 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2819 empty_date = datetime.datetime.fromtimestamp(0)
2827 empty_date = datetime.datetime.fromtimestamp(0)
2820 date_latest = last_commit_cache.get('date', empty_date)
2828 date_latest = last_commit_cache.get('date', empty_date)
2821 try:
2829 try:
2822 return parse_datetime(date_latest)
2830 return parse_datetime(date_latest)
2823 except Exception:
2831 except Exception:
2824 return empty_date
2832 return empty_date
2825
2833
2826 @property
2834 @property
2827 def last_commit_change(self):
2835 def last_commit_change(self):
2828 return self._load_commit_change(self.changeset_cache)
2836 return self._load_commit_change(self.changeset_cache)
2829
2837
2830 @property
2838 @property
2831 def last_db_change(self):
2839 def last_db_change(self):
2832 return self.updated_on
2840 return self.updated_on
2833
2841
2834 @property
2842 @property
2835 def children(self):
2843 def children(self):
2836 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2844 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2837
2845
2838 @property
2846 @property
2839 def name(self):
2847 def name(self):
2840 return self.group_name.split(RepoGroup.url_sep())[-1]
2848 return self.group_name.split(RepoGroup.url_sep())[-1]
2841
2849
2842 @property
2850 @property
2843 def full_path(self):
2851 def full_path(self):
2844 return self.group_name
2852 return self.group_name
2845
2853
2846 @property
2854 @property
2847 def full_path_splitted(self):
2855 def full_path_splitted(self):
2848 return self.group_name.split(RepoGroup.url_sep())
2856 return self.group_name.split(RepoGroup.url_sep())
2849
2857
2850 @property
2858 @property
2851 def repositories(self):
2859 def repositories(self):
2852 return Repository.query()\
2860 return Repository.query()\
2853 .filter(Repository.group == self)\
2861 .filter(Repository.group == self)\
2854 .order_by(Repository.repo_name)
2862 .order_by(Repository.repo_name)
2855
2863
2856 @property
2864 @property
2857 def repositories_recursive_count(self):
2865 def repositories_recursive_count(self):
2858 cnt = self.repositories.count()
2866 cnt = self.repositories.count()
2859
2867
2860 def children_count(group):
2868 def children_count(group):
2861 cnt = 0
2869 cnt = 0
2862 for child in group.children:
2870 for child in group.children:
2863 cnt += child.repositories.count()
2871 cnt += child.repositories.count()
2864 cnt += children_count(child)
2872 cnt += children_count(child)
2865 return cnt
2873 return cnt
2866
2874
2867 return cnt + children_count(self)
2875 return cnt + children_count(self)
2868
2876
2869 def _recursive_objects(self, include_repos=True, include_groups=True):
2877 def _recursive_objects(self, include_repos=True, include_groups=True):
2870 all_ = []
2878 all_ = []
2871
2879
2872 def _get_members(root_gr):
2880 def _get_members(root_gr):
2873 if include_repos:
2881 if include_repos:
2874 for r in root_gr.repositories:
2882 for r in root_gr.repositories:
2875 all_.append(r)
2883 all_.append(r)
2876 childs = root_gr.children.all()
2884 childs = root_gr.children.all()
2877 if childs:
2885 if childs:
2878 for gr in childs:
2886 for gr in childs:
2879 if include_groups:
2887 if include_groups:
2880 all_.append(gr)
2888 all_.append(gr)
2881 _get_members(gr)
2889 _get_members(gr)
2882
2890
2883 root_group = []
2891 root_group = []
2884 if include_groups:
2892 if include_groups:
2885 root_group = [self]
2893 root_group = [self]
2886
2894
2887 _get_members(self)
2895 _get_members(self)
2888 return root_group + all_
2896 return root_group + all_
2889
2897
2890 def recursive_groups_and_repos(self):
2898 def recursive_groups_and_repos(self):
2891 """
2899 """
2892 Recursive return all groups, with repositories in those groups
2900 Recursive return all groups, with repositories in those groups
2893 """
2901 """
2894 return self._recursive_objects()
2902 return self._recursive_objects()
2895
2903
2896 def recursive_groups(self):
2904 def recursive_groups(self):
2897 """
2905 """
2898 Returns all children groups for this group including children of children
2906 Returns all children groups for this group including children of children
2899 """
2907 """
2900 return self._recursive_objects(include_repos=False)
2908 return self._recursive_objects(include_repos=False)
2901
2909
2902 def recursive_repos(self):
2910 def recursive_repos(self):
2903 """
2911 """
2904 Returns all children repositories for this group
2912 Returns all children repositories for this group
2905 """
2913 """
2906 return self._recursive_objects(include_groups=False)
2914 return self._recursive_objects(include_groups=False)
2907
2915
2908 def get_new_name(self, group_name):
2916 def get_new_name(self, group_name):
2909 """
2917 """
2910 returns new full group name based on parent and new name
2918 returns new full group name based on parent and new name
2911
2919
2912 :param group_name:
2920 :param group_name:
2913 """
2921 """
2914 path_prefix = (self.parent_group.full_path_splitted if
2922 path_prefix = (self.parent_group.full_path_splitted if
2915 self.parent_group else [])
2923 self.parent_group else [])
2916 return RepoGroup.url_sep().join(path_prefix + [group_name])
2924 return RepoGroup.url_sep().join(path_prefix + [group_name])
2917
2925
2918 def update_commit_cache(self, config=None):
2926 def update_commit_cache(self, config=None):
2919 """
2927 """
2920 Update cache of last commit for newest repository inside this repository group.
2928 Update cache of last commit for newest repository inside this repository group.
2921 cache_keys should be::
2929 cache_keys should be::
2922
2930
2923 source_repo_id
2931 source_repo_id
2924 short_id
2932 short_id
2925 raw_id
2933 raw_id
2926 revision
2934 revision
2927 parents
2935 parents
2928 message
2936 message
2929 date
2937 date
2930 author
2938 author
2931
2939
2932 """
2940 """
2933 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2941 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2934 empty_date = datetime.datetime.fromtimestamp(0)
2942 empty_date = datetime.datetime.fromtimestamp(0)
2935
2943
2936 def repo_groups_and_repos(root_gr):
2944 def repo_groups_and_repos(root_gr):
2937 for _repo in root_gr.repositories:
2945 for _repo in root_gr.repositories:
2938 yield _repo
2946 yield _repo
2939 for child_group in root_gr.children.all():
2947 for child_group in root_gr.children.all():
2940 yield child_group
2948 yield child_group
2941
2949
2942 latest_repo_cs_cache = {}
2950 latest_repo_cs_cache = {}
2943 for obj in repo_groups_and_repos(self):
2951 for obj in repo_groups_and_repos(self):
2944 repo_cs_cache = obj.changeset_cache
2952 repo_cs_cache = obj.changeset_cache
2945 date_latest = latest_repo_cs_cache.get('date', empty_date)
2953 date_latest = latest_repo_cs_cache.get('date', empty_date)
2946 date_current = repo_cs_cache.get('date', empty_date)
2954 date_current = repo_cs_cache.get('date', empty_date)
2947 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2955 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2948 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2956 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2949 latest_repo_cs_cache = repo_cs_cache
2957 latest_repo_cs_cache = repo_cs_cache
2950 if hasattr(obj, 'repo_id'):
2958 if hasattr(obj, 'repo_id'):
2951 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2959 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2952 else:
2960 else:
2953 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2961 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2954
2962
2955 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2963 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2956
2964
2957 latest_repo_cs_cache['updated_on'] = time.time()
2965 latest_repo_cs_cache['updated_on'] = time.time()
2958 self.changeset_cache = latest_repo_cs_cache
2966 self.changeset_cache = latest_repo_cs_cache
2959 self.updated_on = _date_latest
2967 self.updated_on = _date_latest
2960 Session().add(self)
2968 Session().add(self)
2961 Session().commit()
2969 Session().commit()
2962
2970
2963 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2971 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2964 self.group_name, latest_repo_cs_cache, _date_latest)
2972 self.group_name, latest_repo_cs_cache, _date_latest)
2965
2973
2966 def permissions(self, with_admins=True, with_owner=True,
2974 def permissions(self, with_admins=True, with_owner=True,
2967 expand_from_user_groups=False):
2975 expand_from_user_groups=False):
2968 """
2976 """
2969 Permissions for repository groups
2977 Permissions for repository groups
2970 """
2978 """
2971 _admin_perm = 'group.admin'
2979 _admin_perm = 'group.admin'
2972
2980
2973 owner_row = []
2981 owner_row = []
2974 if with_owner:
2982 if with_owner:
2975 usr = AttributeDict(self.user.get_dict())
2983 usr = AttributeDict(self.user.get_dict())
2976 usr.owner_row = True
2984 usr.owner_row = True
2977 usr.permission = _admin_perm
2985 usr.permission = _admin_perm
2978 owner_row.append(usr)
2986 owner_row.append(usr)
2979
2987
2980 super_admin_ids = []
2988 super_admin_ids = []
2981 super_admin_rows = []
2989 super_admin_rows = []
2982 if with_admins:
2990 if with_admins:
2983 for usr in User.get_all_super_admins():
2991 for usr in User.get_all_super_admins():
2984 super_admin_ids.append(usr.user_id)
2992 super_admin_ids.append(usr.user_id)
2985 # if this admin is also owner, don't double the record
2993 # if this admin is also owner, don't double the record
2986 if usr.user_id == owner_row[0].user_id:
2994 if usr.user_id == owner_row[0].user_id:
2987 owner_row[0].admin_row = True
2995 owner_row[0].admin_row = True
2988 else:
2996 else:
2989 usr = AttributeDict(usr.get_dict())
2997 usr = AttributeDict(usr.get_dict())
2990 usr.admin_row = True
2998 usr.admin_row = True
2991 usr.permission = _admin_perm
2999 usr.permission = _admin_perm
2992 super_admin_rows.append(usr)
3000 super_admin_rows.append(usr)
2993
3001
2994 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3002 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2995 q = q.options(joinedload(UserRepoGroupToPerm.group),
3003 q = q.options(joinedload(UserRepoGroupToPerm.group),
2996 joinedload(UserRepoGroupToPerm.user),
3004 joinedload(UserRepoGroupToPerm.user),
2997 joinedload(UserRepoGroupToPerm.permission),)
3005 joinedload(UserRepoGroupToPerm.permission),)
2998
3006
2999 # get owners and admins and permissions. We do a trick of re-writing
3007 # get owners and admins and permissions. We do a trick of re-writing
3000 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3008 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3001 # has a global reference and changing one object propagates to all
3009 # has a global reference and changing one object propagates to all
3002 # others. This means if admin is also an owner admin_row that change
3010 # others. This means if admin is also an owner admin_row that change
3003 # would propagate to both objects
3011 # would propagate to both objects
3004 perm_rows = []
3012 perm_rows = []
3005 for _usr in q.all():
3013 for _usr in q.all():
3006 usr = AttributeDict(_usr.user.get_dict())
3014 usr = AttributeDict(_usr.user.get_dict())
3007 # if this user is also owner/admin, mark as duplicate record
3015 # if this user is also owner/admin, mark as duplicate record
3008 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3016 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3009 usr.duplicate_perm = True
3017 usr.duplicate_perm = True
3010 usr.permission = _usr.permission.permission_name
3018 usr.permission = _usr.permission.permission_name
3011 perm_rows.append(usr)
3019 perm_rows.append(usr)
3012
3020
3013 # filter the perm rows by 'default' first and then sort them by
3021 # filter the perm rows by 'default' first and then sort them by
3014 # admin,write,read,none permissions sorted again alphabetically in
3022 # admin,write,read,none permissions sorted again alphabetically in
3015 # each group
3023 # each group
3016 perm_rows = sorted(perm_rows, key=display_user_sort)
3024 perm_rows = sorted(perm_rows, key=display_user_sort)
3017
3025
3018 user_groups_rows = []
3026 user_groups_rows = []
3019 if expand_from_user_groups:
3027 if expand_from_user_groups:
3020 for ug in self.permission_user_groups(with_members=True):
3028 for ug in self.permission_user_groups(with_members=True):
3021 for user_data in ug.members:
3029 for user_data in ug.members:
3022 user_groups_rows.append(user_data)
3030 user_groups_rows.append(user_data)
3023
3031
3024 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3032 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3025
3033
3026 def permission_user_groups(self, with_members=False):
3034 def permission_user_groups(self, with_members=False):
3027 q = UserGroupRepoGroupToPerm.query()\
3035 q = UserGroupRepoGroupToPerm.query()\
3028 .filter(UserGroupRepoGroupToPerm.group == self)
3036 .filter(UserGroupRepoGroupToPerm.group == self)
3029 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3037 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3030 joinedload(UserGroupRepoGroupToPerm.users_group),
3038 joinedload(UserGroupRepoGroupToPerm.users_group),
3031 joinedload(UserGroupRepoGroupToPerm.permission),)
3039 joinedload(UserGroupRepoGroupToPerm.permission),)
3032
3040
3033 perm_rows = []
3041 perm_rows = []
3034 for _user_group in q.all():
3042 for _user_group in q.all():
3035 entry = AttributeDict(_user_group.users_group.get_dict())
3043 entry = AttributeDict(_user_group.users_group.get_dict())
3036 entry.permission = _user_group.permission.permission_name
3044 entry.permission = _user_group.permission.permission_name
3037 if with_members:
3045 if with_members:
3038 entry.members = [x.user.get_dict()
3046 entry.members = [x.user.get_dict()
3039 for x in _user_group.users_group.members]
3047 for x in _user_group.users_group.members]
3040 perm_rows.append(entry)
3048 perm_rows.append(entry)
3041
3049
3042 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3050 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3043 return perm_rows
3051 return perm_rows
3044
3052
3045 def get_api_data(self):
3053 def get_api_data(self):
3046 """
3054 """
3047 Common function for generating api data
3055 Common function for generating api data
3048
3056
3049 """
3057 """
3050 group = self
3058 group = self
3051 data = {
3059 data = {
3052 'group_id': group.group_id,
3060 'group_id': group.group_id,
3053 'group_name': group.group_name,
3061 'group_name': group.group_name,
3054 'group_description': group.description_safe,
3062 'group_description': group.description_safe,
3055 'parent_group': group.parent_group.group_name if group.parent_group else None,
3063 'parent_group': group.parent_group.group_name if group.parent_group else None,
3056 'repositories': [x.repo_name for x in group.repositories],
3064 'repositories': [x.repo_name for x in group.repositories],
3057 'owner': group.user.username,
3065 'owner': group.user.username,
3058 }
3066 }
3059 return data
3067 return data
3060
3068
3061 def get_dict(self):
3069 def get_dict(self):
3062 # Since we transformed `group_name` to a hybrid property, we need to
3070 # Since we transformed `group_name` to a hybrid property, we need to
3063 # keep compatibility with the code which uses `group_name` field.
3071 # keep compatibility with the code which uses `group_name` field.
3064 result = super(RepoGroup, self).get_dict()
3072 result = super(RepoGroup, self).get_dict()
3065 result['group_name'] = result.pop('_group_name', None)
3073 result['group_name'] = result.pop('_group_name', None)
3066 return result
3074 return result
3067
3075
3068
3076
3069 class Permission(Base, BaseModel):
3077 class Permission(Base, BaseModel):
3070 __tablename__ = 'permissions'
3078 __tablename__ = 'permissions'
3071 __table_args__ = (
3079 __table_args__ = (
3072 Index('p_perm_name_idx', 'permission_name'),
3080 Index('p_perm_name_idx', 'permission_name'),
3073 base_table_args,
3081 base_table_args,
3074 )
3082 )
3075
3083
3076 PERMS = [
3084 PERMS = [
3077 ('hg.admin', _('RhodeCode Super Administrator')),
3085 ('hg.admin', _('RhodeCode Super Administrator')),
3078
3086
3079 ('repository.none', _('Repository no access')),
3087 ('repository.none', _('Repository no access')),
3080 ('repository.read', _('Repository read access')),
3088 ('repository.read', _('Repository read access')),
3081 ('repository.write', _('Repository write access')),
3089 ('repository.write', _('Repository write access')),
3082 ('repository.admin', _('Repository admin access')),
3090 ('repository.admin', _('Repository admin access')),
3083
3091
3084 ('group.none', _('Repository group no access')),
3092 ('group.none', _('Repository group no access')),
3085 ('group.read', _('Repository group read access')),
3093 ('group.read', _('Repository group read access')),
3086 ('group.write', _('Repository group write access')),
3094 ('group.write', _('Repository group write access')),
3087 ('group.admin', _('Repository group admin access')),
3095 ('group.admin', _('Repository group admin access')),
3088
3096
3089 ('usergroup.none', _('User group no access')),
3097 ('usergroup.none', _('User group no access')),
3090 ('usergroup.read', _('User group read access')),
3098 ('usergroup.read', _('User group read access')),
3091 ('usergroup.write', _('User group write access')),
3099 ('usergroup.write', _('User group write access')),
3092 ('usergroup.admin', _('User group admin access')),
3100 ('usergroup.admin', _('User group admin access')),
3093
3101
3094 ('branch.none', _('Branch no permissions')),
3102 ('branch.none', _('Branch no permissions')),
3095 ('branch.merge', _('Branch access by web merge')),
3103 ('branch.merge', _('Branch access by web merge')),
3096 ('branch.push', _('Branch access by push')),
3104 ('branch.push', _('Branch access by push')),
3097 ('branch.push_force', _('Branch access by push with force')),
3105 ('branch.push_force', _('Branch access by push with force')),
3098
3106
3099 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3107 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3100 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3108 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3101
3109
3102 ('hg.usergroup.create.false', _('User Group creation disabled')),
3110 ('hg.usergroup.create.false', _('User Group creation disabled')),
3103 ('hg.usergroup.create.true', _('User Group creation enabled')),
3111 ('hg.usergroup.create.true', _('User Group creation enabled')),
3104
3112
3105 ('hg.create.none', _('Repository creation disabled')),
3113 ('hg.create.none', _('Repository creation disabled')),
3106 ('hg.create.repository', _('Repository creation enabled')),
3114 ('hg.create.repository', _('Repository creation enabled')),
3107 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3115 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3108 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3116 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3109
3117
3110 ('hg.fork.none', _('Repository forking disabled')),
3118 ('hg.fork.none', _('Repository forking disabled')),
3111 ('hg.fork.repository', _('Repository forking enabled')),
3119 ('hg.fork.repository', _('Repository forking enabled')),
3112
3120
3113 ('hg.register.none', _('Registration disabled')),
3121 ('hg.register.none', _('Registration disabled')),
3114 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3122 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3115 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3123 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3116
3124
3117 ('hg.password_reset.enabled', _('Password reset enabled')),
3125 ('hg.password_reset.enabled', _('Password reset enabled')),
3118 ('hg.password_reset.hidden', _('Password reset hidden')),
3126 ('hg.password_reset.hidden', _('Password reset hidden')),
3119 ('hg.password_reset.disabled', _('Password reset disabled')),
3127 ('hg.password_reset.disabled', _('Password reset disabled')),
3120
3128
3121 ('hg.extern_activate.manual', _('Manual activation of external account')),
3129 ('hg.extern_activate.manual', _('Manual activation of external account')),
3122 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3130 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3123
3131
3124 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3132 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3125 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3133 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3126 ]
3134 ]
3127
3135
3128 # definition of system default permissions for DEFAULT user, created on
3136 # definition of system default permissions for DEFAULT user, created on
3129 # system setup
3137 # system setup
3130 DEFAULT_USER_PERMISSIONS = [
3138 DEFAULT_USER_PERMISSIONS = [
3131 # object perms
3139 # object perms
3132 'repository.read',
3140 'repository.read',
3133 'group.read',
3141 'group.read',
3134 'usergroup.read',
3142 'usergroup.read',
3135 # branch, for backward compat we need same value as before so forced pushed
3143 # branch, for backward compat we need same value as before so forced pushed
3136 'branch.push_force',
3144 'branch.push_force',
3137 # global
3145 # global
3138 'hg.create.repository',
3146 'hg.create.repository',
3139 'hg.repogroup.create.false',
3147 'hg.repogroup.create.false',
3140 'hg.usergroup.create.false',
3148 'hg.usergroup.create.false',
3141 'hg.create.write_on_repogroup.true',
3149 'hg.create.write_on_repogroup.true',
3142 'hg.fork.repository',
3150 'hg.fork.repository',
3143 'hg.register.manual_activate',
3151 'hg.register.manual_activate',
3144 'hg.password_reset.enabled',
3152 'hg.password_reset.enabled',
3145 'hg.extern_activate.auto',
3153 'hg.extern_activate.auto',
3146 'hg.inherit_default_perms.true',
3154 'hg.inherit_default_perms.true',
3147 ]
3155 ]
3148
3156
3149 # defines which permissions are more important higher the more important
3157 # defines which permissions are more important higher the more important
3150 # Weight defines which permissions are more important.
3158 # Weight defines which permissions are more important.
3151 # The higher number the more important.
3159 # The higher number the more important.
3152 PERM_WEIGHTS = {
3160 PERM_WEIGHTS = {
3153 'repository.none': 0,
3161 'repository.none': 0,
3154 'repository.read': 1,
3162 'repository.read': 1,
3155 'repository.write': 3,
3163 'repository.write': 3,
3156 'repository.admin': 4,
3164 'repository.admin': 4,
3157
3165
3158 'group.none': 0,
3166 'group.none': 0,
3159 'group.read': 1,
3167 'group.read': 1,
3160 'group.write': 3,
3168 'group.write': 3,
3161 'group.admin': 4,
3169 'group.admin': 4,
3162
3170
3163 'usergroup.none': 0,
3171 'usergroup.none': 0,
3164 'usergroup.read': 1,
3172 'usergroup.read': 1,
3165 'usergroup.write': 3,
3173 'usergroup.write': 3,
3166 'usergroup.admin': 4,
3174 'usergroup.admin': 4,
3167
3175
3168 'branch.none': 0,
3176 'branch.none': 0,
3169 'branch.merge': 1,
3177 'branch.merge': 1,
3170 'branch.push': 3,
3178 'branch.push': 3,
3171 'branch.push_force': 4,
3179 'branch.push_force': 4,
3172
3180
3173 'hg.repogroup.create.false': 0,
3181 'hg.repogroup.create.false': 0,
3174 'hg.repogroup.create.true': 1,
3182 'hg.repogroup.create.true': 1,
3175
3183
3176 'hg.usergroup.create.false': 0,
3184 'hg.usergroup.create.false': 0,
3177 'hg.usergroup.create.true': 1,
3185 'hg.usergroup.create.true': 1,
3178
3186
3179 'hg.fork.none': 0,
3187 'hg.fork.none': 0,
3180 'hg.fork.repository': 1,
3188 'hg.fork.repository': 1,
3181 'hg.create.none': 0,
3189 'hg.create.none': 0,
3182 'hg.create.repository': 1
3190 'hg.create.repository': 1
3183 }
3191 }
3184
3192
3185 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3193 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3186 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3194 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3187 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3195 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3188
3196
3189 def __unicode__(self):
3197 def __unicode__(self):
3190 return u"<%s('%s:%s')>" % (
3198 return u"<%s('%s:%s')>" % (
3191 self.__class__.__name__, self.permission_id, self.permission_name
3199 self.__class__.__name__, self.permission_id, self.permission_name
3192 )
3200 )
3193
3201
3194 @classmethod
3202 @classmethod
3195 def get_by_key(cls, key):
3203 def get_by_key(cls, key):
3196 return cls.query().filter(cls.permission_name == key).scalar()
3204 return cls.query().filter(cls.permission_name == key).scalar()
3197
3205
3198 @classmethod
3206 @classmethod
3199 def get_default_repo_perms(cls, user_id, repo_id=None):
3207 def get_default_repo_perms(cls, user_id, repo_id=None):
3200 q = Session().query(UserRepoToPerm, Repository, Permission)\
3208 q = Session().query(UserRepoToPerm, Repository, Permission)\
3201 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3209 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3202 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3210 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3203 .filter(UserRepoToPerm.user_id == user_id)
3211 .filter(UserRepoToPerm.user_id == user_id)
3204 if repo_id:
3212 if repo_id:
3205 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3213 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3206 return q.all()
3214 return q.all()
3207
3215
3208 @classmethod
3216 @classmethod
3209 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3217 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3210 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3218 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3211 .join(
3219 .join(
3212 Permission,
3220 Permission,
3213 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3221 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3214 .join(
3222 .join(
3215 UserRepoToPerm,
3223 UserRepoToPerm,
3216 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3224 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3217 .filter(UserRepoToPerm.user_id == user_id)
3225 .filter(UserRepoToPerm.user_id == user_id)
3218
3226
3219 if repo_id:
3227 if repo_id:
3220 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3228 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3221 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3229 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3222
3230
3223 @classmethod
3231 @classmethod
3224 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3232 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3225 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3233 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3226 .join(
3234 .join(
3227 Permission,
3235 Permission,
3228 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3236 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3229 .join(
3237 .join(
3230 Repository,
3238 Repository,
3231 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3239 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3232 .join(
3240 .join(
3233 UserGroup,
3241 UserGroup,
3234 UserGroupRepoToPerm.users_group_id ==
3242 UserGroupRepoToPerm.users_group_id ==
3235 UserGroup.users_group_id)\
3243 UserGroup.users_group_id)\
3236 .join(
3244 .join(
3237 UserGroupMember,
3245 UserGroupMember,
3238 UserGroupRepoToPerm.users_group_id ==
3246 UserGroupRepoToPerm.users_group_id ==
3239 UserGroupMember.users_group_id)\
3247 UserGroupMember.users_group_id)\
3240 .filter(
3248 .filter(
3241 UserGroupMember.user_id == user_id,
3249 UserGroupMember.user_id == user_id,
3242 UserGroup.users_group_active == true())
3250 UserGroup.users_group_active == true())
3243 if repo_id:
3251 if repo_id:
3244 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3252 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3245 return q.all()
3253 return q.all()
3246
3254
3247 @classmethod
3255 @classmethod
3248 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3256 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3249 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3257 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3250 .join(
3258 .join(
3251 Permission,
3259 Permission,
3252 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3260 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3253 .join(
3261 .join(
3254 UserGroupRepoToPerm,
3262 UserGroupRepoToPerm,
3255 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3263 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3256 .join(
3264 .join(
3257 UserGroup,
3265 UserGroup,
3258 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3266 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3259 .join(
3267 .join(
3260 UserGroupMember,
3268 UserGroupMember,
3261 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3269 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3262 .filter(
3270 .filter(
3263 UserGroupMember.user_id == user_id,
3271 UserGroupMember.user_id == user_id,
3264 UserGroup.users_group_active == true())
3272 UserGroup.users_group_active == true())
3265
3273
3266 if repo_id:
3274 if repo_id:
3267 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3275 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3268 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3276 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3269
3277
3270 @classmethod
3278 @classmethod
3271 def get_default_group_perms(cls, user_id, repo_group_id=None):
3279 def get_default_group_perms(cls, user_id, repo_group_id=None):
3272 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3280 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3273 .join(
3281 .join(
3274 Permission,
3282 Permission,
3275 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3283 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3276 .join(
3284 .join(
3277 RepoGroup,
3285 RepoGroup,
3278 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3286 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3279 .filter(UserRepoGroupToPerm.user_id == user_id)
3287 .filter(UserRepoGroupToPerm.user_id == user_id)
3280 if repo_group_id:
3288 if repo_group_id:
3281 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3289 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3282 return q.all()
3290 return q.all()
3283
3291
3284 @classmethod
3292 @classmethod
3285 def get_default_group_perms_from_user_group(
3293 def get_default_group_perms_from_user_group(
3286 cls, user_id, repo_group_id=None):
3294 cls, user_id, repo_group_id=None):
3287 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3295 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3288 .join(
3296 .join(
3289 Permission,
3297 Permission,
3290 UserGroupRepoGroupToPerm.permission_id ==
3298 UserGroupRepoGroupToPerm.permission_id ==
3291 Permission.permission_id)\
3299 Permission.permission_id)\
3292 .join(
3300 .join(
3293 RepoGroup,
3301 RepoGroup,
3294 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3302 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3295 .join(
3303 .join(
3296 UserGroup,
3304 UserGroup,
3297 UserGroupRepoGroupToPerm.users_group_id ==
3305 UserGroupRepoGroupToPerm.users_group_id ==
3298 UserGroup.users_group_id)\
3306 UserGroup.users_group_id)\
3299 .join(
3307 .join(
3300 UserGroupMember,
3308 UserGroupMember,
3301 UserGroupRepoGroupToPerm.users_group_id ==
3309 UserGroupRepoGroupToPerm.users_group_id ==
3302 UserGroupMember.users_group_id)\
3310 UserGroupMember.users_group_id)\
3303 .filter(
3311 .filter(
3304 UserGroupMember.user_id == user_id,
3312 UserGroupMember.user_id == user_id,
3305 UserGroup.users_group_active == true())
3313 UserGroup.users_group_active == true())
3306 if repo_group_id:
3314 if repo_group_id:
3307 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3315 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3308 return q.all()
3316 return q.all()
3309
3317
3310 @classmethod
3318 @classmethod
3311 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3319 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3312 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3320 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3313 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3321 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3314 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3322 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3315 .filter(UserUserGroupToPerm.user_id == user_id)
3323 .filter(UserUserGroupToPerm.user_id == user_id)
3316 if user_group_id:
3324 if user_group_id:
3317 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3325 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3318 return q.all()
3326 return q.all()
3319
3327
3320 @classmethod
3328 @classmethod
3321 def get_default_user_group_perms_from_user_group(
3329 def get_default_user_group_perms_from_user_group(
3322 cls, user_id, user_group_id=None):
3330 cls, user_id, user_group_id=None):
3323 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3331 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3324 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3332 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3325 .join(
3333 .join(
3326 Permission,
3334 Permission,
3327 UserGroupUserGroupToPerm.permission_id ==
3335 UserGroupUserGroupToPerm.permission_id ==
3328 Permission.permission_id)\
3336 Permission.permission_id)\
3329 .join(
3337 .join(
3330 TargetUserGroup,
3338 TargetUserGroup,
3331 UserGroupUserGroupToPerm.target_user_group_id ==
3339 UserGroupUserGroupToPerm.target_user_group_id ==
3332 TargetUserGroup.users_group_id)\
3340 TargetUserGroup.users_group_id)\
3333 .join(
3341 .join(
3334 UserGroup,
3342 UserGroup,
3335 UserGroupUserGroupToPerm.user_group_id ==
3343 UserGroupUserGroupToPerm.user_group_id ==
3336 UserGroup.users_group_id)\
3344 UserGroup.users_group_id)\
3337 .join(
3345 .join(
3338 UserGroupMember,
3346 UserGroupMember,
3339 UserGroupUserGroupToPerm.user_group_id ==
3347 UserGroupUserGroupToPerm.user_group_id ==
3340 UserGroupMember.users_group_id)\
3348 UserGroupMember.users_group_id)\
3341 .filter(
3349 .filter(
3342 UserGroupMember.user_id == user_id,
3350 UserGroupMember.user_id == user_id,
3343 UserGroup.users_group_active == true())
3351 UserGroup.users_group_active == true())
3344 if user_group_id:
3352 if user_group_id:
3345 q = q.filter(
3353 q = q.filter(
3346 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3354 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3347
3355
3348 return q.all()
3356 return q.all()
3349
3357
3350
3358
3351 class UserRepoToPerm(Base, BaseModel):
3359 class UserRepoToPerm(Base, BaseModel):
3352 __tablename__ = 'repo_to_perm'
3360 __tablename__ = 'repo_to_perm'
3353 __table_args__ = (
3361 __table_args__ = (
3354 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3362 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3355 base_table_args
3363 base_table_args
3356 )
3364 )
3357
3365
3358 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3366 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3359 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3367 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3360 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3368 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3361 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3369 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3362
3370
3363 user = relationship('User')
3371 user = relationship('User')
3364 repository = relationship('Repository')
3372 repository = relationship('Repository')
3365 permission = relationship('Permission')
3373 permission = relationship('Permission')
3366
3374
3367 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3375 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3368
3376
3369 @classmethod
3377 @classmethod
3370 def create(cls, user, repository, permission):
3378 def create(cls, user, repository, permission):
3371 n = cls()
3379 n = cls()
3372 n.user = user
3380 n.user = user
3373 n.repository = repository
3381 n.repository = repository
3374 n.permission = permission
3382 n.permission = permission
3375 Session().add(n)
3383 Session().add(n)
3376 return n
3384 return n
3377
3385
3378 def __unicode__(self):
3386 def __unicode__(self):
3379 return u'<%s => %s >' % (self.user, self.repository)
3387 return u'<%s => %s >' % (self.user, self.repository)
3380
3388
3381
3389
3382 class UserUserGroupToPerm(Base, BaseModel):
3390 class UserUserGroupToPerm(Base, BaseModel):
3383 __tablename__ = 'user_user_group_to_perm'
3391 __tablename__ = 'user_user_group_to_perm'
3384 __table_args__ = (
3392 __table_args__ = (
3385 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3393 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3386 base_table_args
3394 base_table_args
3387 )
3395 )
3388
3396
3389 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3397 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3390 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3398 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3391 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3399 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3392 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3400 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3393
3401
3394 user = relationship('User')
3402 user = relationship('User')
3395 user_group = relationship('UserGroup')
3403 user_group = relationship('UserGroup')
3396 permission = relationship('Permission')
3404 permission = relationship('Permission')
3397
3405
3398 @classmethod
3406 @classmethod
3399 def create(cls, user, user_group, permission):
3407 def create(cls, user, user_group, permission):
3400 n = cls()
3408 n = cls()
3401 n.user = user
3409 n.user = user
3402 n.user_group = user_group
3410 n.user_group = user_group
3403 n.permission = permission
3411 n.permission = permission
3404 Session().add(n)
3412 Session().add(n)
3405 return n
3413 return n
3406
3414
3407 def __unicode__(self):
3415 def __unicode__(self):
3408 return u'<%s => %s >' % (self.user, self.user_group)
3416 return u'<%s => %s >' % (self.user, self.user_group)
3409
3417
3410
3418
3411 class UserToPerm(Base, BaseModel):
3419 class UserToPerm(Base, BaseModel):
3412 __tablename__ = 'user_to_perm'
3420 __tablename__ = 'user_to_perm'
3413 __table_args__ = (
3421 __table_args__ = (
3414 UniqueConstraint('user_id', 'permission_id'),
3422 UniqueConstraint('user_id', 'permission_id'),
3415 base_table_args
3423 base_table_args
3416 )
3424 )
3417
3425
3418 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3426 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3419 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3427 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3420 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3428 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3421
3429
3422 user = relationship('User')
3430 user = relationship('User')
3423 permission = relationship('Permission', lazy='joined')
3431 permission = relationship('Permission', lazy='joined')
3424
3432
3425 def __unicode__(self):
3433 def __unicode__(self):
3426 return u'<%s => %s >' % (self.user, self.permission)
3434 return u'<%s => %s >' % (self.user, self.permission)
3427
3435
3428
3436
3429 class UserGroupRepoToPerm(Base, BaseModel):
3437 class UserGroupRepoToPerm(Base, BaseModel):
3430 __tablename__ = 'users_group_repo_to_perm'
3438 __tablename__ = 'users_group_repo_to_perm'
3431 __table_args__ = (
3439 __table_args__ = (
3432 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3440 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3433 base_table_args
3441 base_table_args
3434 )
3442 )
3435
3443
3436 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3444 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3437 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3445 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3438 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3446 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3439 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3447 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3440
3448
3441 users_group = relationship('UserGroup')
3449 users_group = relationship('UserGroup')
3442 permission = relationship('Permission')
3450 permission = relationship('Permission')
3443 repository = relationship('Repository')
3451 repository = relationship('Repository')
3444 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3452 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3445
3453
3446 @classmethod
3454 @classmethod
3447 def create(cls, users_group, repository, permission):
3455 def create(cls, users_group, repository, permission):
3448 n = cls()
3456 n = cls()
3449 n.users_group = users_group
3457 n.users_group = users_group
3450 n.repository = repository
3458 n.repository = repository
3451 n.permission = permission
3459 n.permission = permission
3452 Session().add(n)
3460 Session().add(n)
3453 return n
3461 return n
3454
3462
3455 def __unicode__(self):
3463 def __unicode__(self):
3456 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3464 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3457
3465
3458
3466
3459 class UserGroupUserGroupToPerm(Base, BaseModel):
3467 class UserGroupUserGroupToPerm(Base, BaseModel):
3460 __tablename__ = 'user_group_user_group_to_perm'
3468 __tablename__ = 'user_group_user_group_to_perm'
3461 __table_args__ = (
3469 __table_args__ = (
3462 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3470 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3463 CheckConstraint('target_user_group_id != user_group_id'),
3471 CheckConstraint('target_user_group_id != user_group_id'),
3464 base_table_args
3472 base_table_args
3465 )
3473 )
3466
3474
3467 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3475 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3468 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3476 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3469 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3477 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3470 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3478 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3471
3479
3472 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3480 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3473 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3481 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3474 permission = relationship('Permission')
3482 permission = relationship('Permission')
3475
3483
3476 @classmethod
3484 @classmethod
3477 def create(cls, target_user_group, user_group, permission):
3485 def create(cls, target_user_group, user_group, permission):
3478 n = cls()
3486 n = cls()
3479 n.target_user_group = target_user_group
3487 n.target_user_group = target_user_group
3480 n.user_group = user_group
3488 n.user_group = user_group
3481 n.permission = permission
3489 n.permission = permission
3482 Session().add(n)
3490 Session().add(n)
3483 return n
3491 return n
3484
3492
3485 def __unicode__(self):
3493 def __unicode__(self):
3486 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3494 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3487
3495
3488
3496
3489 class UserGroupToPerm(Base, BaseModel):
3497 class UserGroupToPerm(Base, BaseModel):
3490 __tablename__ = 'users_group_to_perm'
3498 __tablename__ = 'users_group_to_perm'
3491 __table_args__ = (
3499 __table_args__ = (
3492 UniqueConstraint('users_group_id', 'permission_id',),
3500 UniqueConstraint('users_group_id', 'permission_id',),
3493 base_table_args
3501 base_table_args
3494 )
3502 )
3495
3503
3496 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3504 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3497 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3505 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3498 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3506 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3499
3507
3500 users_group = relationship('UserGroup')
3508 users_group = relationship('UserGroup')
3501 permission = relationship('Permission')
3509 permission = relationship('Permission')
3502
3510
3503
3511
3504 class UserRepoGroupToPerm(Base, BaseModel):
3512 class UserRepoGroupToPerm(Base, BaseModel):
3505 __tablename__ = 'user_repo_group_to_perm'
3513 __tablename__ = 'user_repo_group_to_perm'
3506 __table_args__ = (
3514 __table_args__ = (
3507 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3515 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3508 base_table_args
3516 base_table_args
3509 )
3517 )
3510
3518
3511 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3519 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3512 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3520 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3513 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3521 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3514 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3515
3523
3516 user = relationship('User')
3524 user = relationship('User')
3517 group = relationship('RepoGroup')
3525 group = relationship('RepoGroup')
3518 permission = relationship('Permission')
3526 permission = relationship('Permission')
3519
3527
3520 @classmethod
3528 @classmethod
3521 def create(cls, user, repository_group, permission):
3529 def create(cls, user, repository_group, permission):
3522 n = cls()
3530 n = cls()
3523 n.user = user
3531 n.user = user
3524 n.group = repository_group
3532 n.group = repository_group
3525 n.permission = permission
3533 n.permission = permission
3526 Session().add(n)
3534 Session().add(n)
3527 return n
3535 return n
3528
3536
3529
3537
3530 class UserGroupRepoGroupToPerm(Base, BaseModel):
3538 class UserGroupRepoGroupToPerm(Base, BaseModel):
3531 __tablename__ = 'users_group_repo_group_to_perm'
3539 __tablename__ = 'users_group_repo_group_to_perm'
3532 __table_args__ = (
3540 __table_args__ = (
3533 UniqueConstraint('users_group_id', 'group_id'),
3541 UniqueConstraint('users_group_id', 'group_id'),
3534 base_table_args
3542 base_table_args
3535 )
3543 )
3536
3544
3537 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3545 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3538 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3546 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3539 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3547 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3540 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3548 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3541
3549
3542 users_group = relationship('UserGroup')
3550 users_group = relationship('UserGroup')
3543 permission = relationship('Permission')
3551 permission = relationship('Permission')
3544 group = relationship('RepoGroup')
3552 group = relationship('RepoGroup')
3545
3553
3546 @classmethod
3554 @classmethod
3547 def create(cls, user_group, repository_group, permission):
3555 def create(cls, user_group, repository_group, permission):
3548 n = cls()
3556 n = cls()
3549 n.users_group = user_group
3557 n.users_group = user_group
3550 n.group = repository_group
3558 n.group = repository_group
3551 n.permission = permission
3559 n.permission = permission
3552 Session().add(n)
3560 Session().add(n)
3553 return n
3561 return n
3554
3562
3555 def __unicode__(self):
3563 def __unicode__(self):
3556 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3564 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3557
3565
3558
3566
3559 class Statistics(Base, BaseModel):
3567 class Statistics(Base, BaseModel):
3560 __tablename__ = 'statistics'
3568 __tablename__ = 'statistics'
3561 __table_args__ = (
3569 __table_args__ = (
3562 base_table_args
3570 base_table_args
3563 )
3571 )
3564
3572
3565 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3573 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3566 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3574 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3567 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3575 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3568 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3576 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3569 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3577 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3570 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3578 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3571
3579
3572 repository = relationship('Repository', single_parent=True)
3580 repository = relationship('Repository', single_parent=True)
3573
3581
3574
3582
3575 class UserFollowing(Base, BaseModel):
3583 class UserFollowing(Base, BaseModel):
3576 __tablename__ = 'user_followings'
3584 __tablename__ = 'user_followings'
3577 __table_args__ = (
3585 __table_args__ = (
3578 UniqueConstraint('user_id', 'follows_repository_id'),
3586 UniqueConstraint('user_id', 'follows_repository_id'),
3579 UniqueConstraint('user_id', 'follows_user_id'),
3587 UniqueConstraint('user_id', 'follows_user_id'),
3580 base_table_args
3588 base_table_args
3581 )
3589 )
3582
3590
3583 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3591 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3584 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3585 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3593 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3586 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3594 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3587 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3595 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3588
3596
3589 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3597 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3590
3598
3591 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3599 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3592 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3600 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3593
3601
3594 @classmethod
3602 @classmethod
3595 def get_repo_followers(cls, repo_id):
3603 def get_repo_followers(cls, repo_id):
3596 return cls.query().filter(cls.follows_repo_id == repo_id)
3604 return cls.query().filter(cls.follows_repo_id == repo_id)
3597
3605
3598
3606
3599 class CacheKey(Base, BaseModel):
3607 class CacheKey(Base, BaseModel):
3600 __tablename__ = 'cache_invalidation'
3608 __tablename__ = 'cache_invalidation'
3601 __table_args__ = (
3609 __table_args__ = (
3602 UniqueConstraint('cache_key'),
3610 UniqueConstraint('cache_key'),
3603 Index('key_idx', 'cache_key'),
3611 Index('key_idx', 'cache_key'),
3604 base_table_args,
3612 base_table_args,
3605 )
3613 )
3606
3614
3607 CACHE_TYPE_FEED = 'FEED'
3615 CACHE_TYPE_FEED = 'FEED'
3608
3616
3609 # namespaces used to register process/thread aware caches
3617 # namespaces used to register process/thread aware caches
3610 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3618 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3611 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3619 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3612
3620
3613 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3621 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3614 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3622 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3615 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3623 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3616 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3624 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3617 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3625 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3618
3626
3619 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3627 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3620 self.cache_key = cache_key
3628 self.cache_key = cache_key
3621 self.cache_args = cache_args
3629 self.cache_args = cache_args
3622 self.cache_active = False
3630 self.cache_active = False
3623 # first key should be same for all entries, since all workers should share it
3631 # first key should be same for all entries, since all workers should share it
3624 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3632 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3625
3633
3626 def __unicode__(self):
3634 def __unicode__(self):
3627 return u"<%s('%s:%s[%s]')>" % (
3635 return u"<%s('%s:%s[%s]')>" % (
3628 self.__class__.__name__,
3636 self.__class__.__name__,
3629 self.cache_id, self.cache_key, self.cache_active)
3637 self.cache_id, self.cache_key, self.cache_active)
3630
3638
3631 def _cache_key_partition(self):
3639 def _cache_key_partition(self):
3632 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3640 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3633 return prefix, repo_name, suffix
3641 return prefix, repo_name, suffix
3634
3642
3635 def get_prefix(self):
3643 def get_prefix(self):
3636 """
3644 """
3637 Try to extract prefix from existing cache key. The key could consist
3645 Try to extract prefix from existing cache key. The key could consist
3638 of prefix, repo_name, suffix
3646 of prefix, repo_name, suffix
3639 """
3647 """
3640 # this returns prefix, repo_name, suffix
3648 # this returns prefix, repo_name, suffix
3641 return self._cache_key_partition()[0]
3649 return self._cache_key_partition()[0]
3642
3650
3643 def get_suffix(self):
3651 def get_suffix(self):
3644 """
3652 """
3645 get suffix that might have been used in _get_cache_key to
3653 get suffix that might have been used in _get_cache_key to
3646 generate self.cache_key. Only used for informational purposes
3654 generate self.cache_key. Only used for informational purposes
3647 in repo_edit.mako.
3655 in repo_edit.mako.
3648 """
3656 """
3649 # prefix, repo_name, suffix
3657 # prefix, repo_name, suffix
3650 return self._cache_key_partition()[2]
3658 return self._cache_key_partition()[2]
3651
3659
3652 @classmethod
3660 @classmethod
3653 def generate_new_state_uid(cls, based_on=None):
3661 def generate_new_state_uid(cls, based_on=None):
3654 if based_on:
3662 if based_on:
3655 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3663 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3656 else:
3664 else:
3657 return str(uuid.uuid4())
3665 return str(uuid.uuid4())
3658
3666
3659 @classmethod
3667 @classmethod
3660 def delete_all_cache(cls):
3668 def delete_all_cache(cls):
3661 """
3669 """
3662 Delete all cache keys from database.
3670 Delete all cache keys from database.
3663 Should only be run when all instances are down and all entries
3671 Should only be run when all instances are down and all entries
3664 thus stale.
3672 thus stale.
3665 """
3673 """
3666 cls.query().delete()
3674 cls.query().delete()
3667 Session().commit()
3675 Session().commit()
3668
3676
3669 @classmethod
3677 @classmethod
3670 def set_invalidate(cls, cache_uid, delete=False):
3678 def set_invalidate(cls, cache_uid, delete=False):
3671 """
3679 """
3672 Mark all caches of a repo as invalid in the database.
3680 Mark all caches of a repo as invalid in the database.
3673 """
3681 """
3674
3682
3675 try:
3683 try:
3676 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3684 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3677 if delete:
3685 if delete:
3678 qry.delete()
3686 qry.delete()
3679 log.debug('cache objects deleted for cache args %s',
3687 log.debug('cache objects deleted for cache args %s',
3680 safe_str(cache_uid))
3688 safe_str(cache_uid))
3681 else:
3689 else:
3682 qry.update({"cache_active": False,
3690 qry.update({"cache_active": False,
3683 "cache_state_uid": cls.generate_new_state_uid()})
3691 "cache_state_uid": cls.generate_new_state_uid()})
3684 log.debug('cache objects marked as invalid for cache args %s',
3692 log.debug('cache objects marked as invalid for cache args %s',
3685 safe_str(cache_uid))
3693 safe_str(cache_uid))
3686
3694
3687 Session().commit()
3695 Session().commit()
3688 except Exception:
3696 except Exception:
3689 log.exception(
3697 log.exception(
3690 'Cache key invalidation failed for cache args %s',
3698 'Cache key invalidation failed for cache args %s',
3691 safe_str(cache_uid))
3699 safe_str(cache_uid))
3692 Session().rollback()
3700 Session().rollback()
3693
3701
3694 @classmethod
3702 @classmethod
3695 def get_active_cache(cls, cache_key):
3703 def get_active_cache(cls, cache_key):
3696 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3704 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3697 if inv_obj:
3705 if inv_obj:
3698 return inv_obj
3706 return inv_obj
3699 return None
3707 return None
3700
3708
3701 @classmethod
3709 @classmethod
3702 def get_namespace_map(cls, namespace):
3710 def get_namespace_map(cls, namespace):
3703 return {
3711 return {
3704 x.cache_key: x
3712 x.cache_key: x
3705 for x in cls.query().filter(cls.cache_args == namespace)}
3713 for x in cls.query().filter(cls.cache_args == namespace)}
3706
3714
3707
3715
3708 class ChangesetComment(Base, BaseModel):
3716 class ChangesetComment(Base, BaseModel):
3709 __tablename__ = 'changeset_comments'
3717 __tablename__ = 'changeset_comments'
3710 __table_args__ = (
3718 __table_args__ = (
3711 Index('cc_revision_idx', 'revision'),
3719 Index('cc_revision_idx', 'revision'),
3712 base_table_args,
3720 base_table_args,
3713 )
3721 )
3714
3722
3715 COMMENT_OUTDATED = u'comment_outdated'
3723 COMMENT_OUTDATED = u'comment_outdated'
3716 COMMENT_TYPE_NOTE = u'note'
3724 COMMENT_TYPE_NOTE = u'note'
3717 COMMENT_TYPE_TODO = u'todo'
3725 COMMENT_TYPE_TODO = u'todo'
3718 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3726 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3719
3727
3720 OP_IMMUTABLE = u'immutable'
3728 OP_IMMUTABLE = u'immutable'
3721 OP_CHANGEABLE = u'changeable'
3729 OP_CHANGEABLE = u'changeable'
3722
3730
3723 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3731 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3724 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3732 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3725 revision = Column('revision', String(40), nullable=True)
3733 revision = Column('revision', String(40), nullable=True)
3726 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3734 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3727 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3735 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3728 line_no = Column('line_no', Unicode(10), nullable=True)
3736 line_no = Column('line_no', Unicode(10), nullable=True)
3729 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3737 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3730 f_path = Column('f_path', Unicode(1000), nullable=True)
3738 f_path = Column('f_path', Unicode(1000), nullable=True)
3731 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3739 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3732 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3740 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3733 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3741 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3734 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3742 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3735 renderer = Column('renderer', Unicode(64), nullable=True)
3743 renderer = Column('renderer', Unicode(64), nullable=True)
3736 display_state = Column('display_state', Unicode(128), nullable=True)
3744 display_state = Column('display_state', Unicode(128), nullable=True)
3737 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3745 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3738
3746
3739 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3747 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3740 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3748 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3741
3749
3742 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3750 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3743 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3751 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3744
3752
3745 author = relationship('User', lazy='joined')
3753 author = relationship('User', lazy='joined')
3746 repo = relationship('Repository')
3754 repo = relationship('Repository')
3747 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3755 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3748 pull_request = relationship('PullRequest', lazy='joined')
3756 pull_request = relationship('PullRequest', lazy='joined')
3749 pull_request_version = relationship('PullRequestVersion')
3757 pull_request_version = relationship('PullRequestVersion')
3750
3758
3751 @classmethod
3759 @classmethod
3752 def get_users(cls, revision=None, pull_request_id=None):
3760 def get_users(cls, revision=None, pull_request_id=None):
3753 """
3761 """
3754 Returns user associated with this ChangesetComment. ie those
3762 Returns user associated with this ChangesetComment. ie those
3755 who actually commented
3763 who actually commented
3756
3764
3757 :param cls:
3765 :param cls:
3758 :param revision:
3766 :param revision:
3759 """
3767 """
3760 q = Session().query(User)\
3768 q = Session().query(User)\
3761 .join(ChangesetComment.author)
3769 .join(ChangesetComment.author)
3762 if revision:
3770 if revision:
3763 q = q.filter(cls.revision == revision)
3771 q = q.filter(cls.revision == revision)
3764 elif pull_request_id:
3772 elif pull_request_id:
3765 q = q.filter(cls.pull_request_id == pull_request_id)
3773 q = q.filter(cls.pull_request_id == pull_request_id)
3766 return q.all()
3774 return q.all()
3767
3775
3768 @classmethod
3776 @classmethod
3769 def get_index_from_version(cls, pr_version, versions):
3777 def get_index_from_version(cls, pr_version, versions):
3770 num_versions = [x.pull_request_version_id for x in versions]
3778 num_versions = [x.pull_request_version_id for x in versions]
3771 try:
3779 try:
3772 return num_versions.index(pr_version) +1
3780 return num_versions.index(pr_version) +1
3773 except (IndexError, ValueError):
3781 except (IndexError, ValueError):
3774 return
3782 return
3775
3783
3776 @property
3784 @property
3777 def outdated(self):
3785 def outdated(self):
3778 return self.display_state == self.COMMENT_OUTDATED
3786 return self.display_state == self.COMMENT_OUTDATED
3779
3787
3780 @property
3788 @property
3781 def immutable(self):
3789 def immutable(self):
3782 return self.immutable_state == self.OP_IMMUTABLE
3790 return self.immutable_state == self.OP_IMMUTABLE
3783
3791
3784 def outdated_at_version(self, version):
3792 def outdated_at_version(self, version):
3785 """
3793 """
3786 Checks if comment is outdated for given pull request version
3794 Checks if comment is outdated for given pull request version
3787 """
3795 """
3788 return self.outdated and self.pull_request_version_id != version
3796 return self.outdated and self.pull_request_version_id != version
3789
3797
3790 def older_than_version(self, version):
3798 def older_than_version(self, version):
3791 """
3799 """
3792 Checks if comment is made from previous version than given
3800 Checks if comment is made from previous version than given
3793 """
3801 """
3794 if version is None:
3802 if version is None:
3795 return self.pull_request_version_id is not None
3803 return self.pull_request_version_id is not None
3796
3804
3797 return self.pull_request_version_id < version
3805 return self.pull_request_version_id < version
3798
3806
3799 @property
3807 @property
3800 def resolved(self):
3808 def resolved(self):
3801 return self.resolved_by[0] if self.resolved_by else None
3809 return self.resolved_by[0] if self.resolved_by else None
3802
3810
3803 @property
3811 @property
3804 def is_todo(self):
3812 def is_todo(self):
3805 return self.comment_type == self.COMMENT_TYPE_TODO
3813 return self.comment_type == self.COMMENT_TYPE_TODO
3806
3814
3807 @property
3815 @property
3808 def is_inline(self):
3816 def is_inline(self):
3809 return self.line_no and self.f_path
3817 return self.line_no and self.f_path
3810
3818
3811 def get_index_version(self, versions):
3819 def get_index_version(self, versions):
3812 return self.get_index_from_version(
3820 return self.get_index_from_version(
3813 self.pull_request_version_id, versions)
3821 self.pull_request_version_id, versions)
3814
3822
3815 def __repr__(self):
3823 def __repr__(self):
3816 if self.comment_id:
3824 if self.comment_id:
3817 return '<DB:Comment #%s>' % self.comment_id
3825 return '<DB:Comment #%s>' % self.comment_id
3818 else:
3826 else:
3819 return '<DB:Comment at %#x>' % id(self)
3827 return '<DB:Comment at %#x>' % id(self)
3820
3828
3821 def get_api_data(self):
3829 def get_api_data(self):
3822 comment = self
3830 comment = self
3823 data = {
3831 data = {
3824 'comment_id': comment.comment_id,
3832 'comment_id': comment.comment_id,
3825 'comment_type': comment.comment_type,
3833 'comment_type': comment.comment_type,
3826 'comment_text': comment.text,
3834 'comment_text': comment.text,
3827 'comment_status': comment.status_change,
3835 'comment_status': comment.status_change,
3828 'comment_f_path': comment.f_path,
3836 'comment_f_path': comment.f_path,
3829 'comment_lineno': comment.line_no,
3837 'comment_lineno': comment.line_no,
3830 'comment_author': comment.author,
3838 'comment_author': comment.author,
3831 'comment_created_on': comment.created_on,
3839 'comment_created_on': comment.created_on,
3832 'comment_resolved_by': self.resolved,
3840 'comment_resolved_by': self.resolved,
3833 'comment_commit_id': comment.revision,
3841 'comment_commit_id': comment.revision,
3834 'comment_pull_request_id': comment.pull_request_id,
3842 'comment_pull_request_id': comment.pull_request_id,
3835 }
3843 }
3836 return data
3844 return data
3837
3845
3838 def __json__(self):
3846 def __json__(self):
3839 data = dict()
3847 data = dict()
3840 data.update(self.get_api_data())
3848 data.update(self.get_api_data())
3841 return data
3849 return data
3842
3850
3843
3851
3844 class ChangesetStatus(Base, BaseModel):
3852 class ChangesetStatus(Base, BaseModel):
3845 __tablename__ = 'changeset_statuses'
3853 __tablename__ = 'changeset_statuses'
3846 __table_args__ = (
3854 __table_args__ = (
3847 Index('cs_revision_idx', 'revision'),
3855 Index('cs_revision_idx', 'revision'),
3848 Index('cs_version_idx', 'version'),
3856 Index('cs_version_idx', 'version'),
3849 UniqueConstraint('repo_id', 'revision', 'version'),
3857 UniqueConstraint('repo_id', 'revision', 'version'),
3850 base_table_args
3858 base_table_args
3851 )
3859 )
3852
3860
3853 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3861 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3854 STATUS_APPROVED = 'approved'
3862 STATUS_APPROVED = 'approved'
3855 STATUS_REJECTED = 'rejected'
3863 STATUS_REJECTED = 'rejected'
3856 STATUS_UNDER_REVIEW = 'under_review'
3864 STATUS_UNDER_REVIEW = 'under_review'
3857
3865
3858 STATUSES = [
3866 STATUSES = [
3859 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3867 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3860 (STATUS_APPROVED, _("Approved")),
3868 (STATUS_APPROVED, _("Approved")),
3861 (STATUS_REJECTED, _("Rejected")),
3869 (STATUS_REJECTED, _("Rejected")),
3862 (STATUS_UNDER_REVIEW, _("Under Review")),
3870 (STATUS_UNDER_REVIEW, _("Under Review")),
3863 ]
3871 ]
3864
3872
3865 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3873 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3866 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3874 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3867 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3875 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3868 revision = Column('revision', String(40), nullable=False)
3876 revision = Column('revision', String(40), nullable=False)
3869 status = Column('status', String(128), nullable=False, default=DEFAULT)
3877 status = Column('status', String(128), nullable=False, default=DEFAULT)
3870 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3878 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3871 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3879 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3872 version = Column('version', Integer(), nullable=False, default=0)
3880 version = Column('version', Integer(), nullable=False, default=0)
3873 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3881 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3874
3882
3875 author = relationship('User', lazy='joined')
3883 author = relationship('User', lazy='joined')
3876 repo = relationship('Repository')
3884 repo = relationship('Repository')
3877 comment = relationship('ChangesetComment', lazy='joined')
3885 comment = relationship('ChangesetComment', lazy='joined')
3878 pull_request = relationship('PullRequest', lazy='joined')
3886 pull_request = relationship('PullRequest', lazy='joined')
3879
3887
3880 def __unicode__(self):
3888 def __unicode__(self):
3881 return u"<%s('%s[v%s]:%s')>" % (
3889 return u"<%s('%s[v%s]:%s')>" % (
3882 self.__class__.__name__,
3890 self.__class__.__name__,
3883 self.status, self.version, self.author
3891 self.status, self.version, self.author
3884 )
3892 )
3885
3893
3886 @classmethod
3894 @classmethod
3887 def get_status_lbl(cls, value):
3895 def get_status_lbl(cls, value):
3888 return dict(cls.STATUSES).get(value)
3896 return dict(cls.STATUSES).get(value)
3889
3897
3890 @property
3898 @property
3891 def status_lbl(self):
3899 def status_lbl(self):
3892 return ChangesetStatus.get_status_lbl(self.status)
3900 return ChangesetStatus.get_status_lbl(self.status)
3893
3901
3894 def get_api_data(self):
3902 def get_api_data(self):
3895 status = self
3903 status = self
3896 data = {
3904 data = {
3897 'status_id': status.changeset_status_id,
3905 'status_id': status.changeset_status_id,
3898 'status': status.status,
3906 'status': status.status,
3899 }
3907 }
3900 return data
3908 return data
3901
3909
3902 def __json__(self):
3910 def __json__(self):
3903 data = dict()
3911 data = dict()
3904 data.update(self.get_api_data())
3912 data.update(self.get_api_data())
3905 return data
3913 return data
3906
3914
3907
3915
3908 class _SetState(object):
3916 class _SetState(object):
3909 """
3917 """
3910 Context processor allowing changing state for sensitive operation such as
3918 Context processor allowing changing state for sensitive operation such as
3911 pull request update or merge
3919 pull request update or merge
3912 """
3920 """
3913
3921
3914 def __init__(self, pull_request, pr_state, back_state=None):
3922 def __init__(self, pull_request, pr_state, back_state=None):
3915 self._pr = pull_request
3923 self._pr = pull_request
3916 self._org_state = back_state or pull_request.pull_request_state
3924 self._org_state = back_state or pull_request.pull_request_state
3917 self._pr_state = pr_state
3925 self._pr_state = pr_state
3918 self._current_state = None
3926 self._current_state = None
3919
3927
3920 def __enter__(self):
3928 def __enter__(self):
3921 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3929 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3922 self._pr, self._pr_state)
3930 self._pr, self._pr_state)
3923 self.set_pr_state(self._pr_state)
3931 self.set_pr_state(self._pr_state)
3924 return self
3932 return self
3925
3933
3926 def __exit__(self, exc_type, exc_val, exc_tb):
3934 def __exit__(self, exc_type, exc_val, exc_tb):
3927 if exc_val is not None:
3935 if exc_val is not None:
3928 log.error(traceback.format_exc(exc_tb))
3936 log.error(traceback.format_exc(exc_tb))
3929 return None
3937 return None
3930
3938
3931 self.set_pr_state(self._org_state)
3939 self.set_pr_state(self._org_state)
3932 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3940 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3933 self._pr, self._org_state)
3941 self._pr, self._org_state)
3934
3942
3935 @property
3943 @property
3936 def state(self):
3944 def state(self):
3937 return self._current_state
3945 return self._current_state
3938
3946
3939 def set_pr_state(self, pr_state):
3947 def set_pr_state(self, pr_state):
3940 try:
3948 try:
3941 self._pr.pull_request_state = pr_state
3949 self._pr.pull_request_state = pr_state
3942 Session().add(self._pr)
3950 Session().add(self._pr)
3943 Session().commit()
3951 Session().commit()
3944 self._current_state = pr_state
3952 self._current_state = pr_state
3945 except Exception:
3953 except Exception:
3946 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3954 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3947 raise
3955 raise
3948
3956
3949
3957
3950 class _PullRequestBase(BaseModel):
3958 class _PullRequestBase(BaseModel):
3951 """
3959 """
3952 Common attributes of pull request and version entries.
3960 Common attributes of pull request and version entries.
3953 """
3961 """
3954
3962
3955 # .status values
3963 # .status values
3956 STATUS_NEW = u'new'
3964 STATUS_NEW = u'new'
3957 STATUS_OPEN = u'open'
3965 STATUS_OPEN = u'open'
3958 STATUS_CLOSED = u'closed'
3966 STATUS_CLOSED = u'closed'
3959
3967
3960 # available states
3968 # available states
3961 STATE_CREATING = u'creating'
3969 STATE_CREATING = u'creating'
3962 STATE_UPDATING = u'updating'
3970 STATE_UPDATING = u'updating'
3963 STATE_MERGING = u'merging'
3971 STATE_MERGING = u'merging'
3964 STATE_CREATED = u'created'
3972 STATE_CREATED = u'created'
3965
3973
3966 title = Column('title', Unicode(255), nullable=True)
3974 title = Column('title', Unicode(255), nullable=True)
3967 description = Column(
3975 description = Column(
3968 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3976 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3969 nullable=True)
3977 nullable=True)
3970 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3978 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3971
3979
3972 # new/open/closed status of pull request (not approve/reject/etc)
3980 # new/open/closed status of pull request (not approve/reject/etc)
3973 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3981 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3974 created_on = Column(
3982 created_on = Column(
3975 'created_on', DateTime(timezone=False), nullable=False,
3983 'created_on', DateTime(timezone=False), nullable=False,
3976 default=datetime.datetime.now)
3984 default=datetime.datetime.now)
3977 updated_on = Column(
3985 updated_on = Column(
3978 'updated_on', DateTime(timezone=False), nullable=False,
3986 'updated_on', DateTime(timezone=False), nullable=False,
3979 default=datetime.datetime.now)
3987 default=datetime.datetime.now)
3980
3988
3981 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3989 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3982
3990
3983 @declared_attr
3991 @declared_attr
3984 def user_id(cls):
3992 def user_id(cls):
3985 return Column(
3993 return Column(
3986 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3994 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3987 unique=None)
3995 unique=None)
3988
3996
3989 # 500 revisions max
3997 # 500 revisions max
3990 _revisions = Column(
3998 _revisions = Column(
3991 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3999 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3992
4000
3993 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4001 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
3994
4002
3995 @declared_attr
4003 @declared_attr
3996 def source_repo_id(cls):
4004 def source_repo_id(cls):
3997 # TODO: dan: rename column to source_repo_id
4005 # TODO: dan: rename column to source_repo_id
3998 return Column(
4006 return Column(
3999 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4007 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4000 nullable=False)
4008 nullable=False)
4001
4009
4002 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4010 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4003
4011
4004 @hybrid_property
4012 @hybrid_property
4005 def source_ref(self):
4013 def source_ref(self):
4006 return self._source_ref
4014 return self._source_ref
4007
4015
4008 @source_ref.setter
4016 @source_ref.setter
4009 def source_ref(self, val):
4017 def source_ref(self, val):
4010 parts = (val or '').split(':')
4018 parts = (val or '').split(':')
4011 if len(parts) != 3:
4019 if len(parts) != 3:
4012 raise ValueError(
4020 raise ValueError(
4013 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4021 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4014 self._source_ref = safe_unicode(val)
4022 self._source_ref = safe_unicode(val)
4015
4023
4016 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4024 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4017
4025
4018 @hybrid_property
4026 @hybrid_property
4019 def target_ref(self):
4027 def target_ref(self):
4020 return self._target_ref
4028 return self._target_ref
4021
4029
4022 @target_ref.setter
4030 @target_ref.setter
4023 def target_ref(self, val):
4031 def target_ref(self, val):
4024 parts = (val or '').split(':')
4032 parts = (val or '').split(':')
4025 if len(parts) != 3:
4033 if len(parts) != 3:
4026 raise ValueError(
4034 raise ValueError(
4027 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4035 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4028 self._target_ref = safe_unicode(val)
4036 self._target_ref = safe_unicode(val)
4029
4037
4030 @declared_attr
4038 @declared_attr
4031 def target_repo_id(cls):
4039 def target_repo_id(cls):
4032 # TODO: dan: rename column to target_repo_id
4040 # TODO: dan: rename column to target_repo_id
4033 return Column(
4041 return Column(
4034 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4042 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4035 nullable=False)
4043 nullable=False)
4036
4044
4037 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4045 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4038
4046
4039 # TODO: dan: rename column to last_merge_source_rev
4047 # TODO: dan: rename column to last_merge_source_rev
4040 _last_merge_source_rev = Column(
4048 _last_merge_source_rev = Column(
4041 'last_merge_org_rev', String(40), nullable=True)
4049 'last_merge_org_rev', String(40), nullable=True)
4042 # TODO: dan: rename column to last_merge_target_rev
4050 # TODO: dan: rename column to last_merge_target_rev
4043 _last_merge_target_rev = Column(
4051 _last_merge_target_rev = Column(
4044 'last_merge_other_rev', String(40), nullable=True)
4052 'last_merge_other_rev', String(40), nullable=True)
4045 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4053 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4046 last_merge_metadata = Column(
4054 last_merge_metadata = Column(
4047 'last_merge_metadata', MutationObj.as_mutable(
4055 'last_merge_metadata', MutationObj.as_mutable(
4048 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4056 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4049
4057
4050 merge_rev = Column('merge_rev', String(40), nullable=True)
4058 merge_rev = Column('merge_rev', String(40), nullable=True)
4051
4059
4052 reviewer_data = Column(
4060 reviewer_data = Column(
4053 'reviewer_data_json', MutationObj.as_mutable(
4061 'reviewer_data_json', MutationObj.as_mutable(
4054 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4062 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4055
4063
4056 @property
4064 @property
4057 def reviewer_data_json(self):
4065 def reviewer_data_json(self):
4058 return json.dumps(self.reviewer_data)
4066 return json.dumps(self.reviewer_data)
4059
4067
4060 @property
4068 @property
4061 def work_in_progress(self):
4069 def work_in_progress(self):
4062 """checks if pull request is work in progress by checking the title"""
4070 """checks if pull request is work in progress by checking the title"""
4063 title = self.title.upper()
4071 title = self.title.upper()
4064 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4072 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4065 return True
4073 return True
4066 return False
4074 return False
4067
4075
4068 @hybrid_property
4076 @hybrid_property
4069 def description_safe(self):
4077 def description_safe(self):
4070 from rhodecode.lib import helpers as h
4078 from rhodecode.lib import helpers as h
4071 return h.escape(self.description)
4079 return h.escape(self.description)
4072
4080
4073 @hybrid_property
4081 @hybrid_property
4074 def revisions(self):
4082 def revisions(self):
4075 return self._revisions.split(':') if self._revisions else []
4083 return self._revisions.split(':') if self._revisions else []
4076
4084
4077 @revisions.setter
4085 @revisions.setter
4078 def revisions(self, val):
4086 def revisions(self, val):
4079 self._revisions = u':'.join(val)
4087 self._revisions = u':'.join(val)
4080
4088
4081 @hybrid_property
4089 @hybrid_property
4082 def last_merge_status(self):
4090 def last_merge_status(self):
4083 return safe_int(self._last_merge_status)
4091 return safe_int(self._last_merge_status)
4084
4092
4085 @last_merge_status.setter
4093 @last_merge_status.setter
4086 def last_merge_status(self, val):
4094 def last_merge_status(self, val):
4087 self._last_merge_status = val
4095 self._last_merge_status = val
4088
4096
4089 @declared_attr
4097 @declared_attr
4090 def author(cls):
4098 def author(cls):
4091 return relationship('User', lazy='joined')
4099 return relationship('User', lazy='joined')
4092
4100
4093 @declared_attr
4101 @declared_attr
4094 def source_repo(cls):
4102 def source_repo(cls):
4095 return relationship(
4103 return relationship(
4096 'Repository',
4104 'Repository',
4097 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4105 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4098
4106
4099 @property
4107 @property
4100 def source_ref_parts(self):
4108 def source_ref_parts(self):
4101 return self.unicode_to_reference(self.source_ref)
4109 return self.unicode_to_reference(self.source_ref)
4102
4110
4103 @declared_attr
4111 @declared_attr
4104 def target_repo(cls):
4112 def target_repo(cls):
4105 return relationship(
4113 return relationship(
4106 'Repository',
4114 'Repository',
4107 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4115 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4108
4116
4109 @property
4117 @property
4110 def target_ref_parts(self):
4118 def target_ref_parts(self):
4111 return self.unicode_to_reference(self.target_ref)
4119 return self.unicode_to_reference(self.target_ref)
4112
4120
4113 @property
4121 @property
4114 def shadow_merge_ref(self):
4122 def shadow_merge_ref(self):
4115 return self.unicode_to_reference(self._shadow_merge_ref)
4123 return self.unicode_to_reference(self._shadow_merge_ref)
4116
4124
4117 @shadow_merge_ref.setter
4125 @shadow_merge_ref.setter
4118 def shadow_merge_ref(self, ref):
4126 def shadow_merge_ref(self, ref):
4119 self._shadow_merge_ref = self.reference_to_unicode(ref)
4127 self._shadow_merge_ref = self.reference_to_unicode(ref)
4120
4128
4121 @staticmethod
4129 @staticmethod
4122 def unicode_to_reference(raw):
4130 def unicode_to_reference(raw):
4123 """
4131 """
4124 Convert a unicode (or string) to a reference object.
4132 Convert a unicode (or string) to a reference object.
4125 If unicode evaluates to False it returns None.
4133 If unicode evaluates to False it returns None.
4126 """
4134 """
4127 if raw:
4135 if raw:
4128 refs = raw.split(':')
4136 refs = raw.split(':')
4129 return Reference(*refs)
4137 return Reference(*refs)
4130 else:
4138 else:
4131 return None
4139 return None
4132
4140
4133 @staticmethod
4141 @staticmethod
4134 def reference_to_unicode(ref):
4142 def reference_to_unicode(ref):
4135 """
4143 """
4136 Convert a reference object to unicode.
4144 Convert a reference object to unicode.
4137 If reference is None it returns None.
4145 If reference is None it returns None.
4138 """
4146 """
4139 if ref:
4147 if ref:
4140 return u':'.join(ref)
4148 return u':'.join(ref)
4141 else:
4149 else:
4142 return None
4150 return None
4143
4151
4144 def get_api_data(self, with_merge_state=True):
4152 def get_api_data(self, with_merge_state=True):
4145 from rhodecode.model.pull_request import PullRequestModel
4153 from rhodecode.model.pull_request import PullRequestModel
4146
4154
4147 pull_request = self
4155 pull_request = self
4148 if with_merge_state:
4156 if with_merge_state:
4149 merge_response, merge_status, msg = \
4157 merge_response, merge_status, msg = \
4150 PullRequestModel().merge_status(pull_request)
4158 PullRequestModel().merge_status(pull_request)
4151 merge_state = {
4159 merge_state = {
4152 'status': merge_status,
4160 'status': merge_status,
4153 'message': safe_unicode(msg),
4161 'message': safe_unicode(msg),
4154 }
4162 }
4155 else:
4163 else:
4156 merge_state = {'status': 'not_available',
4164 merge_state = {'status': 'not_available',
4157 'message': 'not_available'}
4165 'message': 'not_available'}
4158
4166
4159 merge_data = {
4167 merge_data = {
4160 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4168 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4161 'reference': (
4169 'reference': (
4162 pull_request.shadow_merge_ref._asdict()
4170 pull_request.shadow_merge_ref._asdict()
4163 if pull_request.shadow_merge_ref else None),
4171 if pull_request.shadow_merge_ref else None),
4164 }
4172 }
4165
4173
4166 data = {
4174 data = {
4167 'pull_request_id': pull_request.pull_request_id,
4175 'pull_request_id': pull_request.pull_request_id,
4168 'url': PullRequestModel().get_url(pull_request),
4176 'url': PullRequestModel().get_url(pull_request),
4169 'title': pull_request.title,
4177 'title': pull_request.title,
4170 'description': pull_request.description,
4178 'description': pull_request.description,
4171 'status': pull_request.status,
4179 'status': pull_request.status,
4172 'state': pull_request.pull_request_state,
4180 'state': pull_request.pull_request_state,
4173 'created_on': pull_request.created_on,
4181 'created_on': pull_request.created_on,
4174 'updated_on': pull_request.updated_on,
4182 'updated_on': pull_request.updated_on,
4175 'commit_ids': pull_request.revisions,
4183 'commit_ids': pull_request.revisions,
4176 'review_status': pull_request.calculated_review_status(),
4184 'review_status': pull_request.calculated_review_status(),
4177 'mergeable': merge_state,
4185 'mergeable': merge_state,
4178 'source': {
4186 'source': {
4179 'clone_url': pull_request.source_repo.clone_url(),
4187 'clone_url': pull_request.source_repo.clone_url(),
4180 'repository': pull_request.source_repo.repo_name,
4188 'repository': pull_request.source_repo.repo_name,
4181 'reference': {
4189 'reference': {
4182 'name': pull_request.source_ref_parts.name,
4190 'name': pull_request.source_ref_parts.name,
4183 'type': pull_request.source_ref_parts.type,
4191 'type': pull_request.source_ref_parts.type,
4184 'commit_id': pull_request.source_ref_parts.commit_id,
4192 'commit_id': pull_request.source_ref_parts.commit_id,
4185 },
4193 },
4186 },
4194 },
4187 'target': {
4195 'target': {
4188 'clone_url': pull_request.target_repo.clone_url(),
4196 'clone_url': pull_request.target_repo.clone_url(),
4189 'repository': pull_request.target_repo.repo_name,
4197 'repository': pull_request.target_repo.repo_name,
4190 'reference': {
4198 'reference': {
4191 'name': pull_request.target_ref_parts.name,
4199 'name': pull_request.target_ref_parts.name,
4192 'type': pull_request.target_ref_parts.type,
4200 'type': pull_request.target_ref_parts.type,
4193 'commit_id': pull_request.target_ref_parts.commit_id,
4201 'commit_id': pull_request.target_ref_parts.commit_id,
4194 },
4202 },
4195 },
4203 },
4196 'merge': merge_data,
4204 'merge': merge_data,
4197 'author': pull_request.author.get_api_data(include_secrets=False,
4205 'author': pull_request.author.get_api_data(include_secrets=False,
4198 details='basic'),
4206 details='basic'),
4199 'reviewers': [
4207 'reviewers': [
4200 {
4208 {
4201 'user': reviewer.get_api_data(include_secrets=False,
4209 'user': reviewer.get_api_data(include_secrets=False,
4202 details='basic'),
4210 details='basic'),
4203 'reasons': reasons,
4211 'reasons': reasons,
4204 'review_status': st[0][1].status if st else 'not_reviewed',
4212 'review_status': st[0][1].status if st else 'not_reviewed',
4205 }
4213 }
4206 for obj, reviewer, reasons, mandatory, st in
4214 for obj, reviewer, reasons, mandatory, st in
4207 pull_request.reviewers_statuses()
4215 pull_request.reviewers_statuses()
4208 ]
4216 ]
4209 }
4217 }
4210
4218
4211 return data
4219 return data
4212
4220
4213 def set_state(self, pull_request_state, final_state=None):
4221 def set_state(self, pull_request_state, final_state=None):
4214 """
4222 """
4215 # goes from initial state to updating to initial state.
4223 # goes from initial state to updating to initial state.
4216 # initial state can be changed by specifying back_state=
4224 # initial state can be changed by specifying back_state=
4217 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4225 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4218 pull_request.merge()
4226 pull_request.merge()
4219
4227
4220 :param pull_request_state:
4228 :param pull_request_state:
4221 :param final_state:
4229 :param final_state:
4222
4230
4223 """
4231 """
4224
4232
4225 return _SetState(self, pull_request_state, back_state=final_state)
4233 return _SetState(self, pull_request_state, back_state=final_state)
4226
4234
4227
4235
4228 class PullRequest(Base, _PullRequestBase):
4236 class PullRequest(Base, _PullRequestBase):
4229 __tablename__ = 'pull_requests'
4237 __tablename__ = 'pull_requests'
4230 __table_args__ = (
4238 __table_args__ = (
4231 base_table_args,
4239 base_table_args,
4232 )
4240 )
4233
4241
4234 pull_request_id = Column(
4242 pull_request_id = Column(
4235 'pull_request_id', Integer(), nullable=False, primary_key=True)
4243 'pull_request_id', Integer(), nullable=False, primary_key=True)
4236
4244
4237 def __repr__(self):
4245 def __repr__(self):
4238 if self.pull_request_id:
4246 if self.pull_request_id:
4239 return '<DB:PullRequest #%s>' % self.pull_request_id
4247 return '<DB:PullRequest #%s>' % self.pull_request_id
4240 else:
4248 else:
4241 return '<DB:PullRequest at %#x>' % id(self)
4249 return '<DB:PullRequest at %#x>' % id(self)
4242
4250
4243 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4251 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4244 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4252 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4245 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4253 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4246 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4254 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4247 lazy='dynamic')
4255 lazy='dynamic')
4248
4256
4249 @classmethod
4257 @classmethod
4250 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4258 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4251 internal_methods=None):
4259 internal_methods=None):
4252
4260
4253 class PullRequestDisplay(object):
4261 class PullRequestDisplay(object):
4254 """
4262 """
4255 Special object wrapper for showing PullRequest data via Versions
4263 Special object wrapper for showing PullRequest data via Versions
4256 It mimics PR object as close as possible. This is read only object
4264 It mimics PR object as close as possible. This is read only object
4257 just for display
4265 just for display
4258 """
4266 """
4259
4267
4260 def __init__(self, attrs, internal=None):
4268 def __init__(self, attrs, internal=None):
4261 self.attrs = attrs
4269 self.attrs = attrs
4262 # internal have priority over the given ones via attrs
4270 # internal have priority over the given ones via attrs
4263 self.internal = internal or ['versions']
4271 self.internal = internal or ['versions']
4264
4272
4265 def __getattr__(self, item):
4273 def __getattr__(self, item):
4266 if item in self.internal:
4274 if item in self.internal:
4267 return getattr(self, item)
4275 return getattr(self, item)
4268 try:
4276 try:
4269 return self.attrs[item]
4277 return self.attrs[item]
4270 except KeyError:
4278 except KeyError:
4271 raise AttributeError(
4279 raise AttributeError(
4272 '%s object has no attribute %s' % (self, item))
4280 '%s object has no attribute %s' % (self, item))
4273
4281
4274 def __repr__(self):
4282 def __repr__(self):
4275 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4283 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4276
4284
4277 def versions(self):
4285 def versions(self):
4278 return pull_request_obj.versions.order_by(
4286 return pull_request_obj.versions.order_by(
4279 PullRequestVersion.pull_request_version_id).all()
4287 PullRequestVersion.pull_request_version_id).all()
4280
4288
4281 def is_closed(self):
4289 def is_closed(self):
4282 return pull_request_obj.is_closed()
4290 return pull_request_obj.is_closed()
4283
4291
4284 def is_state_changing(self):
4292 def is_state_changing(self):
4285 return pull_request_obj.is_state_changing()
4293 return pull_request_obj.is_state_changing()
4286
4294
4287 @property
4295 @property
4288 def pull_request_version_id(self):
4296 def pull_request_version_id(self):
4289 return getattr(pull_request_obj, 'pull_request_version_id', None)
4297 return getattr(pull_request_obj, 'pull_request_version_id', None)
4290
4298
4291 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4299 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4292
4300
4293 attrs.author = StrictAttributeDict(
4301 attrs.author = StrictAttributeDict(
4294 pull_request_obj.author.get_api_data())
4302 pull_request_obj.author.get_api_data())
4295 if pull_request_obj.target_repo:
4303 if pull_request_obj.target_repo:
4296 attrs.target_repo = StrictAttributeDict(
4304 attrs.target_repo = StrictAttributeDict(
4297 pull_request_obj.target_repo.get_api_data())
4305 pull_request_obj.target_repo.get_api_data())
4298 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4306 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4299
4307
4300 if pull_request_obj.source_repo:
4308 if pull_request_obj.source_repo:
4301 attrs.source_repo = StrictAttributeDict(
4309 attrs.source_repo = StrictAttributeDict(
4302 pull_request_obj.source_repo.get_api_data())
4310 pull_request_obj.source_repo.get_api_data())
4303 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4311 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4304
4312
4305 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4313 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4306 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4314 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4307 attrs.revisions = pull_request_obj.revisions
4315 attrs.revisions = pull_request_obj.revisions
4308 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4316 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4309 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4317 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4310 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4318 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4311 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4319 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4312
4320
4313 return PullRequestDisplay(attrs, internal=internal_methods)
4321 return PullRequestDisplay(attrs, internal=internal_methods)
4314
4322
4315 def is_closed(self):
4323 def is_closed(self):
4316 return self.status == self.STATUS_CLOSED
4324 return self.status == self.STATUS_CLOSED
4317
4325
4318 def is_state_changing(self):
4326 def is_state_changing(self):
4319 return self.pull_request_state != PullRequest.STATE_CREATED
4327 return self.pull_request_state != PullRequest.STATE_CREATED
4320
4328
4321 def __json__(self):
4329 def __json__(self):
4322 return {
4330 return {
4323 'revisions': self.revisions,
4331 'revisions': self.revisions,
4324 'versions': self.versions_count
4332 'versions': self.versions_count
4325 }
4333 }
4326
4334
4327 def calculated_review_status(self):
4335 def calculated_review_status(self):
4328 from rhodecode.model.changeset_status import ChangesetStatusModel
4336 from rhodecode.model.changeset_status import ChangesetStatusModel
4329 return ChangesetStatusModel().calculated_review_status(self)
4337 return ChangesetStatusModel().calculated_review_status(self)
4330
4338
4331 def reviewers_statuses(self):
4339 def reviewers_statuses(self):
4332 from rhodecode.model.changeset_status import ChangesetStatusModel
4340 from rhodecode.model.changeset_status import ChangesetStatusModel
4333 return ChangesetStatusModel().reviewers_statuses(self)
4341 return ChangesetStatusModel().reviewers_statuses(self)
4334
4342
4335 @property
4343 @property
4336 def workspace_id(self):
4344 def workspace_id(self):
4337 from rhodecode.model.pull_request import PullRequestModel
4345 from rhodecode.model.pull_request import PullRequestModel
4338 return PullRequestModel()._workspace_id(self)
4346 return PullRequestModel()._workspace_id(self)
4339
4347
4340 def get_shadow_repo(self):
4348 def get_shadow_repo(self):
4341 workspace_id = self.workspace_id
4349 workspace_id = self.workspace_id
4342 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4350 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4343 if os.path.isdir(shadow_repository_path):
4351 if os.path.isdir(shadow_repository_path):
4344 vcs_obj = self.target_repo.scm_instance()
4352 vcs_obj = self.target_repo.scm_instance()
4345 return vcs_obj.get_shadow_instance(shadow_repository_path)
4353 return vcs_obj.get_shadow_instance(shadow_repository_path)
4346
4354
4347 @property
4355 @property
4348 def versions_count(self):
4356 def versions_count(self):
4349 """
4357 """
4350 return number of versions this PR have, e.g a PR that once been
4358 return number of versions this PR have, e.g a PR that once been
4351 updated will have 2 versions
4359 updated will have 2 versions
4352 """
4360 """
4353 return self.versions.count() + 1
4361 return self.versions.count() + 1
4354
4362
4355
4363
4356 class PullRequestVersion(Base, _PullRequestBase):
4364 class PullRequestVersion(Base, _PullRequestBase):
4357 __tablename__ = 'pull_request_versions'
4365 __tablename__ = 'pull_request_versions'
4358 __table_args__ = (
4366 __table_args__ = (
4359 base_table_args,
4367 base_table_args,
4360 )
4368 )
4361
4369
4362 pull_request_version_id = Column(
4370 pull_request_version_id = Column(
4363 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4371 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4364 pull_request_id = Column(
4372 pull_request_id = Column(
4365 'pull_request_id', Integer(),
4373 'pull_request_id', Integer(),
4366 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4374 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4367 pull_request = relationship('PullRequest')
4375 pull_request = relationship('PullRequest')
4368
4376
4369 def __repr__(self):
4377 def __repr__(self):
4370 if self.pull_request_version_id:
4378 if self.pull_request_version_id:
4371 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4379 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4372 else:
4380 else:
4373 return '<DB:PullRequestVersion at %#x>' % id(self)
4381 return '<DB:PullRequestVersion at %#x>' % id(self)
4374
4382
4375 @property
4383 @property
4376 def reviewers(self):
4384 def reviewers(self):
4377 return self.pull_request.reviewers
4385 return self.pull_request.reviewers
4378
4386
4379 @property
4387 @property
4380 def versions(self):
4388 def versions(self):
4381 return self.pull_request.versions
4389 return self.pull_request.versions
4382
4390
4383 def is_closed(self):
4391 def is_closed(self):
4384 # calculate from original
4392 # calculate from original
4385 return self.pull_request.status == self.STATUS_CLOSED
4393 return self.pull_request.status == self.STATUS_CLOSED
4386
4394
4387 def is_state_changing(self):
4395 def is_state_changing(self):
4388 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4396 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4389
4397
4390 def calculated_review_status(self):
4398 def calculated_review_status(self):
4391 return self.pull_request.calculated_review_status()
4399 return self.pull_request.calculated_review_status()
4392
4400
4393 def reviewers_statuses(self):
4401 def reviewers_statuses(self):
4394 return self.pull_request.reviewers_statuses()
4402 return self.pull_request.reviewers_statuses()
4395
4403
4396
4404
4397 class PullRequestReviewers(Base, BaseModel):
4405 class PullRequestReviewers(Base, BaseModel):
4398 __tablename__ = 'pull_request_reviewers'
4406 __tablename__ = 'pull_request_reviewers'
4399 __table_args__ = (
4407 __table_args__ = (
4400 base_table_args,
4408 base_table_args,
4401 )
4409 )
4402
4410
4403 @hybrid_property
4411 @hybrid_property
4404 def reasons(self):
4412 def reasons(self):
4405 if not self._reasons:
4413 if not self._reasons:
4406 return []
4414 return []
4407 return self._reasons
4415 return self._reasons
4408
4416
4409 @reasons.setter
4417 @reasons.setter
4410 def reasons(self, val):
4418 def reasons(self, val):
4411 val = val or []
4419 val = val or []
4412 if any(not isinstance(x, compat.string_types) for x in val):
4420 if any(not isinstance(x, compat.string_types) for x in val):
4413 raise Exception('invalid reasons type, must be list of strings')
4421 raise Exception('invalid reasons type, must be list of strings')
4414 self._reasons = val
4422 self._reasons = val
4415
4423
4416 pull_requests_reviewers_id = Column(
4424 pull_requests_reviewers_id = Column(
4417 'pull_requests_reviewers_id', Integer(), nullable=False,
4425 'pull_requests_reviewers_id', Integer(), nullable=False,
4418 primary_key=True)
4426 primary_key=True)
4419 pull_request_id = Column(
4427 pull_request_id = Column(
4420 "pull_request_id", Integer(),
4428 "pull_request_id", Integer(),
4421 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4429 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4422 user_id = Column(
4430 user_id = Column(
4423 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4431 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4424 _reasons = Column(
4432 _reasons = Column(
4425 'reason', MutationList.as_mutable(
4433 'reason', MutationList.as_mutable(
4426 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4434 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4427
4435
4428 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4436 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4429 user = relationship('User')
4437 user = relationship('User')
4430 pull_request = relationship('PullRequest')
4438 pull_request = relationship('PullRequest')
4431
4439
4432 rule_data = Column(
4440 rule_data = Column(
4433 'rule_data_json',
4441 'rule_data_json',
4434 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4442 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4435
4443
4436 def rule_user_group_data(self):
4444 def rule_user_group_data(self):
4437 """
4445 """
4438 Returns the voting user group rule data for this reviewer
4446 Returns the voting user group rule data for this reviewer
4439 """
4447 """
4440
4448
4441 if self.rule_data and 'vote_rule' in self.rule_data:
4449 if self.rule_data and 'vote_rule' in self.rule_data:
4442 user_group_data = {}
4450 user_group_data = {}
4443 if 'rule_user_group_entry_id' in self.rule_data:
4451 if 'rule_user_group_entry_id' in self.rule_data:
4444 # means a group with voting rules !
4452 # means a group with voting rules !
4445 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4453 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4446 user_group_data['name'] = self.rule_data['rule_name']
4454 user_group_data['name'] = self.rule_data['rule_name']
4447 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4455 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4448
4456
4449 return user_group_data
4457 return user_group_data
4450
4458
4451 def __unicode__(self):
4459 def __unicode__(self):
4452 return u"<%s('id:%s')>" % (self.__class__.__name__,
4460 return u"<%s('id:%s')>" % (self.__class__.__name__,
4453 self.pull_requests_reviewers_id)
4461 self.pull_requests_reviewers_id)
4454
4462
4455
4463
4456 class Notification(Base, BaseModel):
4464 class Notification(Base, BaseModel):
4457 __tablename__ = 'notifications'
4465 __tablename__ = 'notifications'
4458 __table_args__ = (
4466 __table_args__ = (
4459 Index('notification_type_idx', 'type'),
4467 Index('notification_type_idx', 'type'),
4460 base_table_args,
4468 base_table_args,
4461 )
4469 )
4462
4470
4463 TYPE_CHANGESET_COMMENT = u'cs_comment'
4471 TYPE_CHANGESET_COMMENT = u'cs_comment'
4464 TYPE_MESSAGE = u'message'
4472 TYPE_MESSAGE = u'message'
4465 TYPE_MENTION = u'mention'
4473 TYPE_MENTION = u'mention'
4466 TYPE_REGISTRATION = u'registration'
4474 TYPE_REGISTRATION = u'registration'
4467 TYPE_PULL_REQUEST = u'pull_request'
4475 TYPE_PULL_REQUEST = u'pull_request'
4468 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4476 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4469 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4477 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4470
4478
4471 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4479 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4472 subject = Column('subject', Unicode(512), nullable=True)
4480 subject = Column('subject', Unicode(512), nullable=True)
4473 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4481 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4474 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4482 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4475 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4483 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4476 type_ = Column('type', Unicode(255))
4484 type_ = Column('type', Unicode(255))
4477
4485
4478 created_by_user = relationship('User')
4486 created_by_user = relationship('User')
4479 notifications_to_users = relationship('UserNotification', lazy='joined',
4487 notifications_to_users = relationship('UserNotification', lazy='joined',
4480 cascade="all, delete-orphan")
4488 cascade="all, delete-orphan")
4481
4489
4482 @property
4490 @property
4483 def recipients(self):
4491 def recipients(self):
4484 return [x.user for x in UserNotification.query()\
4492 return [x.user for x in UserNotification.query()\
4485 .filter(UserNotification.notification == self)\
4493 .filter(UserNotification.notification == self)\
4486 .order_by(UserNotification.user_id.asc()).all()]
4494 .order_by(UserNotification.user_id.asc()).all()]
4487
4495
4488 @classmethod
4496 @classmethod
4489 def create(cls, created_by, subject, body, recipients, type_=None):
4497 def create(cls, created_by, subject, body, recipients, type_=None):
4490 if type_ is None:
4498 if type_ is None:
4491 type_ = Notification.TYPE_MESSAGE
4499 type_ = Notification.TYPE_MESSAGE
4492
4500
4493 notification = cls()
4501 notification = cls()
4494 notification.created_by_user = created_by
4502 notification.created_by_user = created_by
4495 notification.subject = subject
4503 notification.subject = subject
4496 notification.body = body
4504 notification.body = body
4497 notification.type_ = type_
4505 notification.type_ = type_
4498 notification.created_on = datetime.datetime.now()
4506 notification.created_on = datetime.datetime.now()
4499
4507
4500 # For each recipient link the created notification to his account
4508 # For each recipient link the created notification to his account
4501 for u in recipients:
4509 for u in recipients:
4502 assoc = UserNotification()
4510 assoc = UserNotification()
4503 assoc.user_id = u.user_id
4511 assoc.user_id = u.user_id
4504 assoc.notification = notification
4512 assoc.notification = notification
4505
4513
4506 # if created_by is inside recipients mark his notification
4514 # if created_by is inside recipients mark his notification
4507 # as read
4515 # as read
4508 if u.user_id == created_by.user_id:
4516 if u.user_id == created_by.user_id:
4509 assoc.read = True
4517 assoc.read = True
4510 Session().add(assoc)
4518 Session().add(assoc)
4511
4519
4512 Session().add(notification)
4520 Session().add(notification)
4513
4521
4514 return notification
4522 return notification
4515
4523
4516
4524
4517 class UserNotification(Base, BaseModel):
4525 class UserNotification(Base, BaseModel):
4518 __tablename__ = 'user_to_notification'
4526 __tablename__ = 'user_to_notification'
4519 __table_args__ = (
4527 __table_args__ = (
4520 UniqueConstraint('user_id', 'notification_id'),
4528 UniqueConstraint('user_id', 'notification_id'),
4521 base_table_args
4529 base_table_args
4522 )
4530 )
4523
4531
4524 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4532 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4525 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4533 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4526 read = Column('read', Boolean, default=False)
4534 read = Column('read', Boolean, default=False)
4527 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4535 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4528
4536
4529 user = relationship('User', lazy="joined")
4537 user = relationship('User', lazy="joined")
4530 notification = relationship('Notification', lazy="joined",
4538 notification = relationship('Notification', lazy="joined",
4531 order_by=lambda: Notification.created_on.desc(),)
4539 order_by=lambda: Notification.created_on.desc(),)
4532
4540
4533 def mark_as_read(self):
4541 def mark_as_read(self):
4534 self.read = True
4542 self.read = True
4535 Session().add(self)
4543 Session().add(self)
4536
4544
4537
4545
4538 class UserNotice(Base, BaseModel):
4546 class UserNotice(Base, BaseModel):
4539 __tablename__ = 'user_notices'
4547 __tablename__ = 'user_notices'
4540 __table_args__ = (
4548 __table_args__ = (
4541 base_table_args
4549 base_table_args
4542 )
4550 )
4543
4551
4544 NOTIFICATION_TYPE_MESSAGE = 'message'
4552 NOTIFICATION_TYPE_MESSAGE = 'message'
4545 NOTIFICATION_TYPE_NOTICE = 'notice'
4553 NOTIFICATION_TYPE_NOTICE = 'notice'
4546
4554
4547 NOTIFICATION_LEVEL_INFO = 'info'
4555 NOTIFICATION_LEVEL_INFO = 'info'
4548 NOTIFICATION_LEVEL_WARNING = 'warning'
4556 NOTIFICATION_LEVEL_WARNING = 'warning'
4549 NOTIFICATION_LEVEL_ERROR = 'error'
4557 NOTIFICATION_LEVEL_ERROR = 'error'
4550
4558
4551 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4559 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4552
4560
4553 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4561 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4554 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4562 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4555
4563
4556 notice_read = Column('notice_read', Boolean, default=False)
4564 notice_read = Column('notice_read', Boolean, default=False)
4557
4565
4558 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4566 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4559 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4567 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4560
4568
4561 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4569 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4562 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4570 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4563
4571
4564 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4572 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4565 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4573 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4566
4574
4567 @classmethod
4575 @classmethod
4568 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4576 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4569
4577
4570 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4578 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4571 cls.NOTIFICATION_LEVEL_WARNING,
4579 cls.NOTIFICATION_LEVEL_WARNING,
4572 cls.NOTIFICATION_LEVEL_INFO]:
4580 cls.NOTIFICATION_LEVEL_INFO]:
4573 return
4581 return
4574
4582
4575 from rhodecode.model.user import UserModel
4583 from rhodecode.model.user import UserModel
4576 user = UserModel().get_user(user)
4584 user = UserModel().get_user(user)
4577
4585
4578 new_notice = UserNotice()
4586 new_notice = UserNotice()
4579 if not allow_duplicate:
4587 if not allow_duplicate:
4580 existing_msg = UserNotice().query() \
4588 existing_msg = UserNotice().query() \
4581 .filter(UserNotice.user == user) \
4589 .filter(UserNotice.user == user) \
4582 .filter(UserNotice.notice_body == body) \
4590 .filter(UserNotice.notice_body == body) \
4583 .filter(UserNotice.notice_read == false()) \
4591 .filter(UserNotice.notice_read == false()) \
4584 .scalar()
4592 .scalar()
4585 if existing_msg:
4593 if existing_msg:
4586 log.warning('Ignoring duplicate notice for user %s', user)
4594 log.warning('Ignoring duplicate notice for user %s', user)
4587 return
4595 return
4588
4596
4589 new_notice.user = user
4597 new_notice.user = user
4590 new_notice.notice_subject = subject
4598 new_notice.notice_subject = subject
4591 new_notice.notice_body = body
4599 new_notice.notice_body = body
4592 new_notice.notification_level = notice_level
4600 new_notice.notification_level = notice_level
4593 Session().add(new_notice)
4601 Session().add(new_notice)
4594 Session().commit()
4602 Session().commit()
4595
4603
4596
4604
4597 class Gist(Base, BaseModel):
4605 class Gist(Base, BaseModel):
4598 __tablename__ = 'gists'
4606 __tablename__ = 'gists'
4599 __table_args__ = (
4607 __table_args__ = (
4600 Index('g_gist_access_id_idx', 'gist_access_id'),
4608 Index('g_gist_access_id_idx', 'gist_access_id'),
4601 Index('g_created_on_idx', 'created_on'),
4609 Index('g_created_on_idx', 'created_on'),
4602 base_table_args
4610 base_table_args
4603 )
4611 )
4604
4612
4605 GIST_PUBLIC = u'public'
4613 GIST_PUBLIC = u'public'
4606 GIST_PRIVATE = u'private'
4614 GIST_PRIVATE = u'private'
4607 DEFAULT_FILENAME = u'gistfile1.txt'
4615 DEFAULT_FILENAME = u'gistfile1.txt'
4608
4616
4609 ACL_LEVEL_PUBLIC = u'acl_public'
4617 ACL_LEVEL_PUBLIC = u'acl_public'
4610 ACL_LEVEL_PRIVATE = u'acl_private'
4618 ACL_LEVEL_PRIVATE = u'acl_private'
4611
4619
4612 gist_id = Column('gist_id', Integer(), primary_key=True)
4620 gist_id = Column('gist_id', Integer(), primary_key=True)
4613 gist_access_id = Column('gist_access_id', Unicode(250))
4621 gist_access_id = Column('gist_access_id', Unicode(250))
4614 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4622 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4615 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4623 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4616 gist_expires = Column('gist_expires', Float(53), nullable=False)
4624 gist_expires = Column('gist_expires', Float(53), nullable=False)
4617 gist_type = Column('gist_type', Unicode(128), nullable=False)
4625 gist_type = Column('gist_type', Unicode(128), nullable=False)
4618 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4626 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4619 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4627 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4620 acl_level = Column('acl_level', Unicode(128), nullable=True)
4628 acl_level = Column('acl_level', Unicode(128), nullable=True)
4621
4629
4622 owner = relationship('User')
4630 owner = relationship('User')
4623
4631
4624 def __repr__(self):
4632 def __repr__(self):
4625 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4633 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4626
4634
4627 @hybrid_property
4635 @hybrid_property
4628 def description_safe(self):
4636 def description_safe(self):
4629 from rhodecode.lib import helpers as h
4637 from rhodecode.lib import helpers as h
4630 return h.escape(self.gist_description)
4638 return h.escape(self.gist_description)
4631
4639
4632 @classmethod
4640 @classmethod
4633 def get_or_404(cls, id_):
4641 def get_or_404(cls, id_):
4634 from pyramid.httpexceptions import HTTPNotFound
4642 from pyramid.httpexceptions import HTTPNotFound
4635
4643
4636 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4644 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4637 if not res:
4645 if not res:
4638 raise HTTPNotFound()
4646 raise HTTPNotFound()
4639 return res
4647 return res
4640
4648
4641 @classmethod
4649 @classmethod
4642 def get_by_access_id(cls, gist_access_id):
4650 def get_by_access_id(cls, gist_access_id):
4643 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4651 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4644
4652
4645 def gist_url(self):
4653 def gist_url(self):
4646 from rhodecode.model.gist import GistModel
4654 from rhodecode.model.gist import GistModel
4647 return GistModel().get_url(self)
4655 return GistModel().get_url(self)
4648
4656
4649 @classmethod
4657 @classmethod
4650 def base_path(cls):
4658 def base_path(cls):
4651 """
4659 """
4652 Returns base path when all gists are stored
4660 Returns base path when all gists are stored
4653
4661
4654 :param cls:
4662 :param cls:
4655 """
4663 """
4656 from rhodecode.model.gist import GIST_STORE_LOC
4664 from rhodecode.model.gist import GIST_STORE_LOC
4657 q = Session().query(RhodeCodeUi)\
4665 q = Session().query(RhodeCodeUi)\
4658 .filter(RhodeCodeUi.ui_key == URL_SEP)
4666 .filter(RhodeCodeUi.ui_key == URL_SEP)
4659 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4667 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4660 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4668 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4661
4669
4662 def get_api_data(self):
4670 def get_api_data(self):
4663 """
4671 """
4664 Common function for generating gist related data for API
4672 Common function for generating gist related data for API
4665 """
4673 """
4666 gist = self
4674 gist = self
4667 data = {
4675 data = {
4668 'gist_id': gist.gist_id,
4676 'gist_id': gist.gist_id,
4669 'type': gist.gist_type,
4677 'type': gist.gist_type,
4670 'access_id': gist.gist_access_id,
4678 'access_id': gist.gist_access_id,
4671 'description': gist.gist_description,
4679 'description': gist.gist_description,
4672 'url': gist.gist_url(),
4680 'url': gist.gist_url(),
4673 'expires': gist.gist_expires,
4681 'expires': gist.gist_expires,
4674 'created_on': gist.created_on,
4682 'created_on': gist.created_on,
4675 'modified_at': gist.modified_at,
4683 'modified_at': gist.modified_at,
4676 'content': None,
4684 'content': None,
4677 'acl_level': gist.acl_level,
4685 'acl_level': gist.acl_level,
4678 }
4686 }
4679 return data
4687 return data
4680
4688
4681 def __json__(self):
4689 def __json__(self):
4682 data = dict(
4690 data = dict(
4683 )
4691 )
4684 data.update(self.get_api_data())
4692 data.update(self.get_api_data())
4685 return data
4693 return data
4686 # SCM functions
4694 # SCM functions
4687
4695
4688 def scm_instance(self, **kwargs):
4696 def scm_instance(self, **kwargs):
4689 """
4697 """
4690 Get an instance of VCS Repository
4698 Get an instance of VCS Repository
4691
4699
4692 :param kwargs:
4700 :param kwargs:
4693 """
4701 """
4694 from rhodecode.model.gist import GistModel
4702 from rhodecode.model.gist import GistModel
4695 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4703 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4696 return get_vcs_instance(
4704 return get_vcs_instance(
4697 repo_path=safe_str(full_repo_path), create=False,
4705 repo_path=safe_str(full_repo_path), create=False,
4698 _vcs_alias=GistModel.vcs_backend)
4706 _vcs_alias=GistModel.vcs_backend)
4699
4707
4700
4708
4701 class ExternalIdentity(Base, BaseModel):
4709 class ExternalIdentity(Base, BaseModel):
4702 __tablename__ = 'external_identities'
4710 __tablename__ = 'external_identities'
4703 __table_args__ = (
4711 __table_args__ = (
4704 Index('local_user_id_idx', 'local_user_id'),
4712 Index('local_user_id_idx', 'local_user_id'),
4705 Index('external_id_idx', 'external_id'),
4713 Index('external_id_idx', 'external_id'),
4706 base_table_args
4714 base_table_args
4707 )
4715 )
4708
4716
4709 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4717 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4710 external_username = Column('external_username', Unicode(1024), default=u'')
4718 external_username = Column('external_username', Unicode(1024), default=u'')
4711 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4719 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4712 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4720 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4713 access_token = Column('access_token', String(1024), default=u'')
4721 access_token = Column('access_token', String(1024), default=u'')
4714 alt_token = Column('alt_token', String(1024), default=u'')
4722 alt_token = Column('alt_token', String(1024), default=u'')
4715 token_secret = Column('token_secret', String(1024), default=u'')
4723 token_secret = Column('token_secret', String(1024), default=u'')
4716
4724
4717 @classmethod
4725 @classmethod
4718 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4726 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4719 """
4727 """
4720 Returns ExternalIdentity instance based on search params
4728 Returns ExternalIdentity instance based on search params
4721
4729
4722 :param external_id:
4730 :param external_id:
4723 :param provider_name:
4731 :param provider_name:
4724 :return: ExternalIdentity
4732 :return: ExternalIdentity
4725 """
4733 """
4726 query = cls.query()
4734 query = cls.query()
4727 query = query.filter(cls.external_id == external_id)
4735 query = query.filter(cls.external_id == external_id)
4728 query = query.filter(cls.provider_name == provider_name)
4736 query = query.filter(cls.provider_name == provider_name)
4729 if local_user_id:
4737 if local_user_id:
4730 query = query.filter(cls.local_user_id == local_user_id)
4738 query = query.filter(cls.local_user_id == local_user_id)
4731 return query.first()
4739 return query.first()
4732
4740
4733 @classmethod
4741 @classmethod
4734 def user_by_external_id_and_provider(cls, external_id, provider_name):
4742 def user_by_external_id_and_provider(cls, external_id, provider_name):
4735 """
4743 """
4736 Returns User instance based on search params
4744 Returns User instance based on search params
4737
4745
4738 :param external_id:
4746 :param external_id:
4739 :param provider_name:
4747 :param provider_name:
4740 :return: User
4748 :return: User
4741 """
4749 """
4742 query = User.query()
4750 query = User.query()
4743 query = query.filter(cls.external_id == external_id)
4751 query = query.filter(cls.external_id == external_id)
4744 query = query.filter(cls.provider_name == provider_name)
4752 query = query.filter(cls.provider_name == provider_name)
4745 query = query.filter(User.user_id == cls.local_user_id)
4753 query = query.filter(User.user_id == cls.local_user_id)
4746 return query.first()
4754 return query.first()
4747
4755
4748 @classmethod
4756 @classmethod
4749 def by_local_user_id(cls, local_user_id):
4757 def by_local_user_id(cls, local_user_id):
4750 """
4758 """
4751 Returns all tokens for user
4759 Returns all tokens for user
4752
4760
4753 :param local_user_id:
4761 :param local_user_id:
4754 :return: ExternalIdentity
4762 :return: ExternalIdentity
4755 """
4763 """
4756 query = cls.query()
4764 query = cls.query()
4757 query = query.filter(cls.local_user_id == local_user_id)
4765 query = query.filter(cls.local_user_id == local_user_id)
4758 return query
4766 return query
4759
4767
4760 @classmethod
4768 @classmethod
4761 def load_provider_plugin(cls, plugin_id):
4769 def load_provider_plugin(cls, plugin_id):
4762 from rhodecode.authentication.base import loadplugin
4770 from rhodecode.authentication.base import loadplugin
4763 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4771 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4764 auth_plugin = loadplugin(_plugin_id)
4772 auth_plugin = loadplugin(_plugin_id)
4765 return auth_plugin
4773 return auth_plugin
4766
4774
4767
4775
4768 class Integration(Base, BaseModel):
4776 class Integration(Base, BaseModel):
4769 __tablename__ = 'integrations'
4777 __tablename__ = 'integrations'
4770 __table_args__ = (
4778 __table_args__ = (
4771 base_table_args
4779 base_table_args
4772 )
4780 )
4773
4781
4774 integration_id = Column('integration_id', Integer(), primary_key=True)
4782 integration_id = Column('integration_id', Integer(), primary_key=True)
4775 integration_type = Column('integration_type', String(255))
4783 integration_type = Column('integration_type', String(255))
4776 enabled = Column('enabled', Boolean(), nullable=False)
4784 enabled = Column('enabled', Boolean(), nullable=False)
4777 name = Column('name', String(255), nullable=False)
4785 name = Column('name', String(255), nullable=False)
4778 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4786 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4779 default=False)
4787 default=False)
4780
4788
4781 settings = Column(
4789 settings = Column(
4782 'settings_json', MutationObj.as_mutable(
4790 'settings_json', MutationObj.as_mutable(
4783 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4791 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4784 repo_id = Column(
4792 repo_id = Column(
4785 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4793 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4786 nullable=True, unique=None, default=None)
4794 nullable=True, unique=None, default=None)
4787 repo = relationship('Repository', lazy='joined')
4795 repo = relationship('Repository', lazy='joined')
4788
4796
4789 repo_group_id = Column(
4797 repo_group_id = Column(
4790 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4798 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4791 nullable=True, unique=None, default=None)
4799 nullable=True, unique=None, default=None)
4792 repo_group = relationship('RepoGroup', lazy='joined')
4800 repo_group = relationship('RepoGroup', lazy='joined')
4793
4801
4794 @property
4802 @property
4795 def scope(self):
4803 def scope(self):
4796 if self.repo:
4804 if self.repo:
4797 return repr(self.repo)
4805 return repr(self.repo)
4798 if self.repo_group:
4806 if self.repo_group:
4799 if self.child_repos_only:
4807 if self.child_repos_only:
4800 return repr(self.repo_group) + ' (child repos only)'
4808 return repr(self.repo_group) + ' (child repos only)'
4801 else:
4809 else:
4802 return repr(self.repo_group) + ' (recursive)'
4810 return repr(self.repo_group) + ' (recursive)'
4803 if self.child_repos_only:
4811 if self.child_repos_only:
4804 return 'root_repos'
4812 return 'root_repos'
4805 return 'global'
4813 return 'global'
4806
4814
4807 def __repr__(self):
4815 def __repr__(self):
4808 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4816 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4809
4817
4810
4818
4811 class RepoReviewRuleUser(Base, BaseModel):
4819 class RepoReviewRuleUser(Base, BaseModel):
4812 __tablename__ = 'repo_review_rules_users'
4820 __tablename__ = 'repo_review_rules_users'
4813 __table_args__ = (
4821 __table_args__ = (
4814 base_table_args
4822 base_table_args
4815 )
4823 )
4816
4824
4817 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4825 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4818 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4826 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4819 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4827 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4820 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4828 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4821 user = relationship('User')
4829 user = relationship('User')
4822
4830
4823 def rule_data(self):
4831 def rule_data(self):
4824 return {
4832 return {
4825 'mandatory': self.mandatory
4833 'mandatory': self.mandatory
4826 }
4834 }
4827
4835
4828
4836
4829 class RepoReviewRuleUserGroup(Base, BaseModel):
4837 class RepoReviewRuleUserGroup(Base, BaseModel):
4830 __tablename__ = 'repo_review_rules_users_groups'
4838 __tablename__ = 'repo_review_rules_users_groups'
4831 __table_args__ = (
4839 __table_args__ = (
4832 base_table_args
4840 base_table_args
4833 )
4841 )
4834
4842
4835 VOTE_RULE_ALL = -1
4843 VOTE_RULE_ALL = -1
4836
4844
4837 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4845 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4838 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4846 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4839 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4847 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4840 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4848 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4841 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4849 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4842 users_group = relationship('UserGroup')
4850 users_group = relationship('UserGroup')
4843
4851
4844 def rule_data(self):
4852 def rule_data(self):
4845 return {
4853 return {
4846 'mandatory': self.mandatory,
4854 'mandatory': self.mandatory,
4847 'vote_rule': self.vote_rule
4855 'vote_rule': self.vote_rule
4848 }
4856 }
4849
4857
4850 @property
4858 @property
4851 def vote_rule_label(self):
4859 def vote_rule_label(self):
4852 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4860 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4853 return 'all must vote'
4861 return 'all must vote'
4854 else:
4862 else:
4855 return 'min. vote {}'.format(self.vote_rule)
4863 return 'min. vote {}'.format(self.vote_rule)
4856
4864
4857
4865
4858 class RepoReviewRule(Base, BaseModel):
4866 class RepoReviewRule(Base, BaseModel):
4859 __tablename__ = 'repo_review_rules'
4867 __tablename__ = 'repo_review_rules'
4860 __table_args__ = (
4868 __table_args__ = (
4861 base_table_args
4869 base_table_args
4862 )
4870 )
4863
4871
4864 repo_review_rule_id = Column(
4872 repo_review_rule_id = Column(
4865 'repo_review_rule_id', Integer(), primary_key=True)
4873 'repo_review_rule_id', Integer(), primary_key=True)
4866 repo_id = Column(
4874 repo_id = Column(
4867 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4875 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4868 repo = relationship('Repository', backref='review_rules')
4876 repo = relationship('Repository', backref='review_rules')
4869
4877
4870 review_rule_name = Column('review_rule_name', String(255))
4878 review_rule_name = Column('review_rule_name', String(255))
4871 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4879 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4872 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4880 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4873 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4881 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4874
4882
4875 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4883 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4876 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4884 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4877 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4885 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4878 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4886 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4879
4887
4880 rule_users = relationship('RepoReviewRuleUser')
4888 rule_users = relationship('RepoReviewRuleUser')
4881 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4889 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4882
4890
4883 def _validate_pattern(self, value):
4891 def _validate_pattern(self, value):
4884 re.compile('^' + glob2re(value) + '$')
4892 re.compile('^' + glob2re(value) + '$')
4885
4893
4886 @hybrid_property
4894 @hybrid_property
4887 def source_branch_pattern(self):
4895 def source_branch_pattern(self):
4888 return self._branch_pattern or '*'
4896 return self._branch_pattern or '*'
4889
4897
4890 @source_branch_pattern.setter
4898 @source_branch_pattern.setter
4891 def source_branch_pattern(self, value):
4899 def source_branch_pattern(self, value):
4892 self._validate_pattern(value)
4900 self._validate_pattern(value)
4893 self._branch_pattern = value or '*'
4901 self._branch_pattern = value or '*'
4894
4902
4895 @hybrid_property
4903 @hybrid_property
4896 def target_branch_pattern(self):
4904 def target_branch_pattern(self):
4897 return self._target_branch_pattern or '*'
4905 return self._target_branch_pattern or '*'
4898
4906
4899 @target_branch_pattern.setter
4907 @target_branch_pattern.setter
4900 def target_branch_pattern(self, value):
4908 def target_branch_pattern(self, value):
4901 self._validate_pattern(value)
4909 self._validate_pattern(value)
4902 self._target_branch_pattern = value or '*'
4910 self._target_branch_pattern = value or '*'
4903
4911
4904 @hybrid_property
4912 @hybrid_property
4905 def file_pattern(self):
4913 def file_pattern(self):
4906 return self._file_pattern or '*'
4914 return self._file_pattern or '*'
4907
4915
4908 @file_pattern.setter
4916 @file_pattern.setter
4909 def file_pattern(self, value):
4917 def file_pattern(self, value):
4910 self._validate_pattern(value)
4918 self._validate_pattern(value)
4911 self._file_pattern = value or '*'
4919 self._file_pattern = value or '*'
4912
4920
4913 def matches(self, source_branch, target_branch, files_changed):
4921 def matches(self, source_branch, target_branch, files_changed):
4914 """
4922 """
4915 Check if this review rule matches a branch/files in a pull request
4923 Check if this review rule matches a branch/files in a pull request
4916
4924
4917 :param source_branch: source branch name for the commit
4925 :param source_branch: source branch name for the commit
4918 :param target_branch: target branch name for the commit
4926 :param target_branch: target branch name for the commit
4919 :param files_changed: list of file paths changed in the pull request
4927 :param files_changed: list of file paths changed in the pull request
4920 """
4928 """
4921
4929
4922 source_branch = source_branch or ''
4930 source_branch = source_branch or ''
4923 target_branch = target_branch or ''
4931 target_branch = target_branch or ''
4924 files_changed = files_changed or []
4932 files_changed = files_changed or []
4925
4933
4926 branch_matches = True
4934 branch_matches = True
4927 if source_branch or target_branch:
4935 if source_branch or target_branch:
4928 if self.source_branch_pattern == '*':
4936 if self.source_branch_pattern == '*':
4929 source_branch_match = True
4937 source_branch_match = True
4930 else:
4938 else:
4931 if self.source_branch_pattern.startswith('re:'):
4939 if self.source_branch_pattern.startswith('re:'):
4932 source_pattern = self.source_branch_pattern[3:]
4940 source_pattern = self.source_branch_pattern[3:]
4933 else:
4941 else:
4934 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4942 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4935 source_branch_regex = re.compile(source_pattern)
4943 source_branch_regex = re.compile(source_pattern)
4936 source_branch_match = bool(source_branch_regex.search(source_branch))
4944 source_branch_match = bool(source_branch_regex.search(source_branch))
4937 if self.target_branch_pattern == '*':
4945 if self.target_branch_pattern == '*':
4938 target_branch_match = True
4946 target_branch_match = True
4939 else:
4947 else:
4940 if self.target_branch_pattern.startswith('re:'):
4948 if self.target_branch_pattern.startswith('re:'):
4941 target_pattern = self.target_branch_pattern[3:]
4949 target_pattern = self.target_branch_pattern[3:]
4942 else:
4950 else:
4943 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4951 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4944 target_branch_regex = re.compile(target_pattern)
4952 target_branch_regex = re.compile(target_pattern)
4945 target_branch_match = bool(target_branch_regex.search(target_branch))
4953 target_branch_match = bool(target_branch_regex.search(target_branch))
4946
4954
4947 branch_matches = source_branch_match and target_branch_match
4955 branch_matches = source_branch_match and target_branch_match
4948
4956
4949 files_matches = True
4957 files_matches = True
4950 if self.file_pattern != '*':
4958 if self.file_pattern != '*':
4951 files_matches = False
4959 files_matches = False
4952 if self.file_pattern.startswith('re:'):
4960 if self.file_pattern.startswith('re:'):
4953 file_pattern = self.file_pattern[3:]
4961 file_pattern = self.file_pattern[3:]
4954 else:
4962 else:
4955 file_pattern = glob2re(self.file_pattern)
4963 file_pattern = glob2re(self.file_pattern)
4956 file_regex = re.compile(file_pattern)
4964 file_regex = re.compile(file_pattern)
4957 for filename in files_changed:
4965 for filename in files_changed:
4958 if file_regex.search(filename):
4966 if file_regex.search(filename):
4959 files_matches = True
4967 files_matches = True
4960 break
4968 break
4961
4969
4962 return branch_matches and files_matches
4970 return branch_matches and files_matches
4963
4971
4964 @property
4972 @property
4965 def review_users(self):
4973 def review_users(self):
4966 """ Returns the users which this rule applies to """
4974 """ Returns the users which this rule applies to """
4967
4975
4968 users = collections.OrderedDict()
4976 users = collections.OrderedDict()
4969
4977
4970 for rule_user in self.rule_users:
4978 for rule_user in self.rule_users:
4971 if rule_user.user.active:
4979 if rule_user.user.active:
4972 if rule_user.user not in users:
4980 if rule_user.user not in users:
4973 users[rule_user.user.username] = {
4981 users[rule_user.user.username] = {
4974 'user': rule_user.user,
4982 'user': rule_user.user,
4975 'source': 'user',
4983 'source': 'user',
4976 'source_data': {},
4984 'source_data': {},
4977 'data': rule_user.rule_data()
4985 'data': rule_user.rule_data()
4978 }
4986 }
4979
4987
4980 for rule_user_group in self.rule_user_groups:
4988 for rule_user_group in self.rule_user_groups:
4981 source_data = {
4989 source_data = {
4982 'user_group_id': rule_user_group.users_group.users_group_id,
4990 'user_group_id': rule_user_group.users_group.users_group_id,
4983 'name': rule_user_group.users_group.users_group_name,
4991 'name': rule_user_group.users_group.users_group_name,
4984 'members': len(rule_user_group.users_group.members)
4992 'members': len(rule_user_group.users_group.members)
4985 }
4993 }
4986 for member in rule_user_group.users_group.members:
4994 for member in rule_user_group.users_group.members:
4987 if member.user.active:
4995 if member.user.active:
4988 key = member.user.username
4996 key = member.user.username
4989 if key in users:
4997 if key in users:
4990 # skip this member as we have him already
4998 # skip this member as we have him already
4991 # this prevents from override the "first" matched
4999 # this prevents from override the "first" matched
4992 # users with duplicates in multiple groups
5000 # users with duplicates in multiple groups
4993 continue
5001 continue
4994
5002
4995 users[key] = {
5003 users[key] = {
4996 'user': member.user,
5004 'user': member.user,
4997 'source': 'user_group',
5005 'source': 'user_group',
4998 'source_data': source_data,
5006 'source_data': source_data,
4999 'data': rule_user_group.rule_data()
5007 'data': rule_user_group.rule_data()
5000 }
5008 }
5001
5009
5002 return users
5010 return users
5003
5011
5004 def user_group_vote_rule(self, user_id):
5012 def user_group_vote_rule(self, user_id):
5005
5013
5006 rules = []
5014 rules = []
5007 if not self.rule_user_groups:
5015 if not self.rule_user_groups:
5008 return rules
5016 return rules
5009
5017
5010 for user_group in self.rule_user_groups:
5018 for user_group in self.rule_user_groups:
5011 user_group_members = [x.user_id for x in user_group.users_group.members]
5019 user_group_members = [x.user_id for x in user_group.users_group.members]
5012 if user_id in user_group_members:
5020 if user_id in user_group_members:
5013 rules.append(user_group)
5021 rules.append(user_group)
5014 return rules
5022 return rules
5015
5023
5016 def __repr__(self):
5024 def __repr__(self):
5017 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5025 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5018 self.repo_review_rule_id, self.repo)
5026 self.repo_review_rule_id, self.repo)
5019
5027
5020
5028
5021 class ScheduleEntry(Base, BaseModel):
5029 class ScheduleEntry(Base, BaseModel):
5022 __tablename__ = 'schedule_entries'
5030 __tablename__ = 'schedule_entries'
5023 __table_args__ = (
5031 __table_args__ = (
5024 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5032 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5025 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5033 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5026 base_table_args,
5034 base_table_args,
5027 )
5035 )
5028
5036
5029 schedule_types = ['crontab', 'timedelta', 'integer']
5037 schedule_types = ['crontab', 'timedelta', 'integer']
5030 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5038 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5031
5039
5032 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5040 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5033 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5041 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5034 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5042 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5035
5043
5036 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5044 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5037 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5045 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5038
5046
5039 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5047 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5040 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5048 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5041
5049
5042 # task
5050 # task
5043 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5051 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5044 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5052 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5045 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5053 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5046 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5054 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5047
5055
5048 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5056 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5049 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5057 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5050
5058
5051 @hybrid_property
5059 @hybrid_property
5052 def schedule_type(self):
5060 def schedule_type(self):
5053 return self._schedule_type
5061 return self._schedule_type
5054
5062
5055 @schedule_type.setter
5063 @schedule_type.setter
5056 def schedule_type(self, val):
5064 def schedule_type(self, val):
5057 if val not in self.schedule_types:
5065 if val not in self.schedule_types:
5058 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5066 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5059 val, self.schedule_type))
5067 val, self.schedule_type))
5060
5068
5061 self._schedule_type = val
5069 self._schedule_type = val
5062
5070
5063 @classmethod
5071 @classmethod
5064 def get_uid(cls, obj):
5072 def get_uid(cls, obj):
5065 args = obj.task_args
5073 args = obj.task_args
5066 kwargs = obj.task_kwargs
5074 kwargs = obj.task_kwargs
5067 if isinstance(args, JsonRaw):
5075 if isinstance(args, JsonRaw):
5068 try:
5076 try:
5069 args = json.loads(args)
5077 args = json.loads(args)
5070 except ValueError:
5078 except ValueError:
5071 args = tuple()
5079 args = tuple()
5072
5080
5073 if isinstance(kwargs, JsonRaw):
5081 if isinstance(kwargs, JsonRaw):
5074 try:
5082 try:
5075 kwargs = json.loads(kwargs)
5083 kwargs = json.loads(kwargs)
5076 except ValueError:
5084 except ValueError:
5077 kwargs = dict()
5085 kwargs = dict()
5078
5086
5079 dot_notation = obj.task_dot_notation
5087 dot_notation = obj.task_dot_notation
5080 val = '.'.join(map(safe_str, [
5088 val = '.'.join(map(safe_str, [
5081 sorted(dot_notation), args, sorted(kwargs.items())]))
5089 sorted(dot_notation), args, sorted(kwargs.items())]))
5082 return hashlib.sha1(val).hexdigest()
5090 return hashlib.sha1(val).hexdigest()
5083
5091
5084 @classmethod
5092 @classmethod
5085 def get_by_schedule_name(cls, schedule_name):
5093 def get_by_schedule_name(cls, schedule_name):
5086 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5094 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5087
5095
5088 @classmethod
5096 @classmethod
5089 def get_by_schedule_id(cls, schedule_id):
5097 def get_by_schedule_id(cls, schedule_id):
5090 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5098 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5091
5099
5092 @property
5100 @property
5093 def task(self):
5101 def task(self):
5094 return self.task_dot_notation
5102 return self.task_dot_notation
5095
5103
5096 @property
5104 @property
5097 def schedule(self):
5105 def schedule(self):
5098 from rhodecode.lib.celerylib.utils import raw_2_schedule
5106 from rhodecode.lib.celerylib.utils import raw_2_schedule
5099 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5107 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5100 return schedule
5108 return schedule
5101
5109
5102 @property
5110 @property
5103 def args(self):
5111 def args(self):
5104 try:
5112 try:
5105 return list(self.task_args or [])
5113 return list(self.task_args or [])
5106 except ValueError:
5114 except ValueError:
5107 return list()
5115 return list()
5108
5116
5109 @property
5117 @property
5110 def kwargs(self):
5118 def kwargs(self):
5111 try:
5119 try:
5112 return dict(self.task_kwargs or {})
5120 return dict(self.task_kwargs or {})
5113 except ValueError:
5121 except ValueError:
5114 return dict()
5122 return dict()
5115
5123
5116 def _as_raw(self, val):
5124 def _as_raw(self, val):
5117 if hasattr(val, 'de_coerce'):
5125 if hasattr(val, 'de_coerce'):
5118 val = val.de_coerce()
5126 val = val.de_coerce()
5119 if val:
5127 if val:
5120 val = json.dumps(val)
5128 val = json.dumps(val)
5121
5129
5122 return val
5130 return val
5123
5131
5124 @property
5132 @property
5125 def schedule_definition_raw(self):
5133 def schedule_definition_raw(self):
5126 return self._as_raw(self.schedule_definition)
5134 return self._as_raw(self.schedule_definition)
5127
5135
5128 @property
5136 @property
5129 def args_raw(self):
5137 def args_raw(self):
5130 return self._as_raw(self.task_args)
5138 return self._as_raw(self.task_args)
5131
5139
5132 @property
5140 @property
5133 def kwargs_raw(self):
5141 def kwargs_raw(self):
5134 return self._as_raw(self.task_kwargs)
5142 return self._as_raw(self.task_kwargs)
5135
5143
5136 def __repr__(self):
5144 def __repr__(self):
5137 return '<DB:ScheduleEntry({}:{})>'.format(
5145 return '<DB:ScheduleEntry({}:{})>'.format(
5138 self.schedule_entry_id, self.schedule_name)
5146 self.schedule_entry_id, self.schedule_name)
5139
5147
5140
5148
5141 @event.listens_for(ScheduleEntry, 'before_update')
5149 @event.listens_for(ScheduleEntry, 'before_update')
5142 def update_task_uid(mapper, connection, target):
5150 def update_task_uid(mapper, connection, target):
5143 target.task_uid = ScheduleEntry.get_uid(target)
5151 target.task_uid = ScheduleEntry.get_uid(target)
5144
5152
5145
5153
5146 @event.listens_for(ScheduleEntry, 'before_insert')
5154 @event.listens_for(ScheduleEntry, 'before_insert')
5147 def set_task_uid(mapper, connection, target):
5155 def set_task_uid(mapper, connection, target):
5148 target.task_uid = ScheduleEntry.get_uid(target)
5156 target.task_uid = ScheduleEntry.get_uid(target)
5149
5157
5150
5158
5151 class _BaseBranchPerms(BaseModel):
5159 class _BaseBranchPerms(BaseModel):
5152 @classmethod
5160 @classmethod
5153 def compute_hash(cls, value):
5161 def compute_hash(cls, value):
5154 return sha1_safe(value)
5162 return sha1_safe(value)
5155
5163
5156 @hybrid_property
5164 @hybrid_property
5157 def branch_pattern(self):
5165 def branch_pattern(self):
5158 return self._branch_pattern or '*'
5166 return self._branch_pattern or '*'
5159
5167
5160 @hybrid_property
5168 @hybrid_property
5161 def branch_hash(self):
5169 def branch_hash(self):
5162 return self._branch_hash
5170 return self._branch_hash
5163
5171
5164 def _validate_glob(self, value):
5172 def _validate_glob(self, value):
5165 re.compile('^' + glob2re(value) + '$')
5173 re.compile('^' + glob2re(value) + '$')
5166
5174
5167 @branch_pattern.setter
5175 @branch_pattern.setter
5168 def branch_pattern(self, value):
5176 def branch_pattern(self, value):
5169 self._validate_glob(value)
5177 self._validate_glob(value)
5170 self._branch_pattern = value or '*'
5178 self._branch_pattern = value or '*'
5171 # set the Hash when setting the branch pattern
5179 # set the Hash when setting the branch pattern
5172 self._branch_hash = self.compute_hash(self._branch_pattern)
5180 self._branch_hash = self.compute_hash(self._branch_pattern)
5173
5181
5174 def matches(self, branch):
5182 def matches(self, branch):
5175 """
5183 """
5176 Check if this the branch matches entry
5184 Check if this the branch matches entry
5177
5185
5178 :param branch: branch name for the commit
5186 :param branch: branch name for the commit
5179 """
5187 """
5180
5188
5181 branch = branch or ''
5189 branch = branch or ''
5182
5190
5183 branch_matches = True
5191 branch_matches = True
5184 if branch:
5192 if branch:
5185 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5193 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5186 branch_matches = bool(branch_regex.search(branch))
5194 branch_matches = bool(branch_regex.search(branch))
5187
5195
5188 return branch_matches
5196 return branch_matches
5189
5197
5190
5198
5191 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5199 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5192 __tablename__ = 'user_to_repo_branch_permissions'
5200 __tablename__ = 'user_to_repo_branch_permissions'
5193 __table_args__ = (
5201 __table_args__ = (
5194 base_table_args
5202 base_table_args
5195 )
5203 )
5196
5204
5197 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5205 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5198
5206
5199 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5207 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5200 repo = relationship('Repository', backref='user_branch_perms')
5208 repo = relationship('Repository', backref='user_branch_perms')
5201
5209
5202 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5210 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5203 permission = relationship('Permission')
5211 permission = relationship('Permission')
5204
5212
5205 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5213 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5206 user_repo_to_perm = relationship('UserRepoToPerm')
5214 user_repo_to_perm = relationship('UserRepoToPerm')
5207
5215
5208 rule_order = Column('rule_order', Integer(), nullable=False)
5216 rule_order = Column('rule_order', Integer(), nullable=False)
5209 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5217 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5210 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5218 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5211
5219
5212 def __unicode__(self):
5220 def __unicode__(self):
5213 return u'<UserBranchPermission(%s => %r)>' % (
5221 return u'<UserBranchPermission(%s => %r)>' % (
5214 self.user_repo_to_perm, self.branch_pattern)
5222 self.user_repo_to_perm, self.branch_pattern)
5215
5223
5216
5224
5217 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5225 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5218 __tablename__ = 'user_group_to_repo_branch_permissions'
5226 __tablename__ = 'user_group_to_repo_branch_permissions'
5219 __table_args__ = (
5227 __table_args__ = (
5220 base_table_args
5228 base_table_args
5221 )
5229 )
5222
5230
5223 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5231 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5224
5232
5225 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5233 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5226 repo = relationship('Repository', backref='user_group_branch_perms')
5234 repo = relationship('Repository', backref='user_group_branch_perms')
5227
5235
5228 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5236 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5229 permission = relationship('Permission')
5237 permission = relationship('Permission')
5230
5238
5231 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5239 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5232 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5240 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5233
5241
5234 rule_order = Column('rule_order', Integer(), nullable=False)
5242 rule_order = Column('rule_order', Integer(), nullable=False)
5235 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5243 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5236 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5244 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5237
5245
5238 def __unicode__(self):
5246 def __unicode__(self):
5239 return u'<UserBranchPermission(%s => %r)>' % (
5247 return u'<UserBranchPermission(%s => %r)>' % (
5240 self.user_group_repo_to_perm, self.branch_pattern)
5248 self.user_group_repo_to_perm, self.branch_pattern)
5241
5249
5242
5250
5243 class UserBookmark(Base, BaseModel):
5251 class UserBookmark(Base, BaseModel):
5244 __tablename__ = 'user_bookmarks'
5252 __tablename__ = 'user_bookmarks'
5245 __table_args__ = (
5253 __table_args__ = (
5246 UniqueConstraint('user_id', 'bookmark_repo_id'),
5254 UniqueConstraint('user_id', 'bookmark_repo_id'),
5247 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5255 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5248 UniqueConstraint('user_id', 'bookmark_position'),
5256 UniqueConstraint('user_id', 'bookmark_position'),
5249 base_table_args
5257 base_table_args
5250 )
5258 )
5251
5259
5252 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5260 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5253 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5261 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5254 position = Column("bookmark_position", Integer(), nullable=False)
5262 position = Column("bookmark_position", Integer(), nullable=False)
5255 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5263 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5256 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5264 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5257 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5265 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5258
5266
5259 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5267 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5260 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5268 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5261
5269
5262 user = relationship("User")
5270 user = relationship("User")
5263
5271
5264 repository = relationship("Repository")
5272 repository = relationship("Repository")
5265 repository_group = relationship("RepoGroup")
5273 repository_group = relationship("RepoGroup")
5266
5274
5267 @classmethod
5275 @classmethod
5268 def get_by_position_for_user(cls, position, user_id):
5276 def get_by_position_for_user(cls, position, user_id):
5269 return cls.query() \
5277 return cls.query() \
5270 .filter(UserBookmark.user_id == user_id) \
5278 .filter(UserBookmark.user_id == user_id) \
5271 .filter(UserBookmark.position == position).scalar()
5279 .filter(UserBookmark.position == position).scalar()
5272
5280
5273 @classmethod
5281 @classmethod
5274 def get_bookmarks_for_user(cls, user_id, cache=True):
5282 def get_bookmarks_for_user(cls, user_id, cache=True):
5275 bookmarks = cls.query() \
5283 bookmarks = cls.query() \
5276 .filter(UserBookmark.user_id == user_id) \
5284 .filter(UserBookmark.user_id == user_id) \
5277 .options(joinedload(UserBookmark.repository)) \
5285 .options(joinedload(UserBookmark.repository)) \
5278 .options(joinedload(UserBookmark.repository_group)) \
5286 .options(joinedload(UserBookmark.repository_group)) \
5279 .order_by(UserBookmark.position.asc())
5287 .order_by(UserBookmark.position.asc())
5280
5288
5281 if cache:
5289 if cache:
5282 bookmarks = bookmarks.options(
5290 bookmarks = bookmarks.options(
5283 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5291 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5284 )
5292 )
5285
5293
5286 return bookmarks.all()
5294 return bookmarks.all()
5287
5295
5288 def __unicode__(self):
5296 def __unicode__(self):
5289 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5297 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5290
5298
5291
5299
5292 class FileStore(Base, BaseModel):
5300 class FileStore(Base, BaseModel):
5293 __tablename__ = 'file_store'
5301 __tablename__ = 'file_store'
5294 __table_args__ = (
5302 __table_args__ = (
5295 base_table_args
5303 base_table_args
5296 )
5304 )
5297
5305
5298 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5306 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5299 file_uid = Column('file_uid', String(1024), nullable=False)
5307 file_uid = Column('file_uid', String(1024), nullable=False)
5300 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5308 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5301 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5309 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5302 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5310 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5303
5311
5304 # sha256 hash
5312 # sha256 hash
5305 file_hash = Column('file_hash', String(512), nullable=False)
5313 file_hash = Column('file_hash', String(512), nullable=False)
5306 file_size = Column('file_size', BigInteger(), nullable=False)
5314 file_size = Column('file_size', BigInteger(), nullable=False)
5307
5315
5308 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5309 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5317 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5310 accessed_count = Column('accessed_count', Integer(), default=0)
5318 accessed_count = Column('accessed_count', Integer(), default=0)
5311
5319
5312 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5320 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5313
5321
5314 # if repo/repo_group reference is set, check for permissions
5322 # if repo/repo_group reference is set, check for permissions
5315 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5323 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5316
5324
5317 # hidden defines an attachment that should be hidden from showing in artifact listing
5325 # hidden defines an attachment that should be hidden from showing in artifact listing
5318 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5326 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5319
5327
5320 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5328 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5321 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5329 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5322
5330
5323 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5331 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5324
5332
5325 # scope limited to user, which requester have access to
5333 # scope limited to user, which requester have access to
5326 scope_user_id = Column(
5334 scope_user_id = Column(
5327 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5335 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5328 nullable=True, unique=None, default=None)
5336 nullable=True, unique=None, default=None)
5329 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5337 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5330
5338
5331 # scope limited to user group, which requester have access to
5339 # scope limited to user group, which requester have access to
5332 scope_user_group_id = Column(
5340 scope_user_group_id = Column(
5333 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5341 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5334 nullable=True, unique=None, default=None)
5342 nullable=True, unique=None, default=None)
5335 user_group = relationship('UserGroup', lazy='joined')
5343 user_group = relationship('UserGroup', lazy='joined')
5336
5344
5337 # scope limited to repo, which requester have access to
5345 # scope limited to repo, which requester have access to
5338 scope_repo_id = Column(
5346 scope_repo_id = Column(
5339 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5347 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5340 nullable=True, unique=None, default=None)
5348 nullable=True, unique=None, default=None)
5341 repo = relationship('Repository', lazy='joined')
5349 repo = relationship('Repository', lazy='joined')
5342
5350
5343 # scope limited to repo group, which requester have access to
5351 # scope limited to repo group, which requester have access to
5344 scope_repo_group_id = Column(
5352 scope_repo_group_id = Column(
5345 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5353 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5346 nullable=True, unique=None, default=None)
5354 nullable=True, unique=None, default=None)
5347 repo_group = relationship('RepoGroup', lazy='joined')
5355 repo_group = relationship('RepoGroup', lazy='joined')
5348
5356
5349 @classmethod
5357 @classmethod
5350 def get_by_store_uid(cls, file_store_uid):
5358 def get_by_store_uid(cls, file_store_uid):
5351 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5359 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5352
5360
5353 @classmethod
5361 @classmethod
5354 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5362 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5355 file_description='', enabled=True, hidden=False, check_acl=True,
5363 file_description='', enabled=True, hidden=False, check_acl=True,
5356 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5364 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5357
5365
5358 store_entry = FileStore()
5366 store_entry = FileStore()
5359 store_entry.file_uid = file_uid
5367 store_entry.file_uid = file_uid
5360 store_entry.file_display_name = file_display_name
5368 store_entry.file_display_name = file_display_name
5361 store_entry.file_org_name = filename
5369 store_entry.file_org_name = filename
5362 store_entry.file_size = file_size
5370 store_entry.file_size = file_size
5363 store_entry.file_hash = file_hash
5371 store_entry.file_hash = file_hash
5364 store_entry.file_description = file_description
5372 store_entry.file_description = file_description
5365
5373
5366 store_entry.check_acl = check_acl
5374 store_entry.check_acl = check_acl
5367 store_entry.enabled = enabled
5375 store_entry.enabled = enabled
5368 store_entry.hidden = hidden
5376 store_entry.hidden = hidden
5369
5377
5370 store_entry.user_id = user_id
5378 store_entry.user_id = user_id
5371 store_entry.scope_user_id = scope_user_id
5379 store_entry.scope_user_id = scope_user_id
5372 store_entry.scope_repo_id = scope_repo_id
5380 store_entry.scope_repo_id = scope_repo_id
5373 store_entry.scope_repo_group_id = scope_repo_group_id
5381 store_entry.scope_repo_group_id = scope_repo_group_id
5374
5382
5375 return store_entry
5383 return store_entry
5376
5384
5377 @classmethod
5385 @classmethod
5378 def store_metadata(cls, file_store_id, args, commit=True):
5386 def store_metadata(cls, file_store_id, args, commit=True):
5379 file_store = FileStore.get(file_store_id)
5387 file_store = FileStore.get(file_store_id)
5380 if file_store is None:
5388 if file_store is None:
5381 return
5389 return
5382
5390
5383 for section, key, value, value_type in args:
5391 for section, key, value, value_type in args:
5384 has_key = FileStoreMetadata().query() \
5392 has_key = FileStoreMetadata().query() \
5385 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5393 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5386 .filter(FileStoreMetadata.file_store_meta_section == section) \
5394 .filter(FileStoreMetadata.file_store_meta_section == section) \
5387 .filter(FileStoreMetadata.file_store_meta_key == key) \
5395 .filter(FileStoreMetadata.file_store_meta_key == key) \
5388 .scalar()
5396 .scalar()
5389 if has_key:
5397 if has_key:
5390 msg = 'key `{}` already defined under section `{}` for this file.'\
5398 msg = 'key `{}` already defined under section `{}` for this file.'\
5391 .format(key, section)
5399 .format(key, section)
5392 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5400 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5393
5401
5394 # NOTE(marcink): raises ArtifactMetadataBadValueType
5402 # NOTE(marcink): raises ArtifactMetadataBadValueType
5395 FileStoreMetadata.valid_value_type(value_type)
5403 FileStoreMetadata.valid_value_type(value_type)
5396
5404
5397 meta_entry = FileStoreMetadata()
5405 meta_entry = FileStoreMetadata()
5398 meta_entry.file_store = file_store
5406 meta_entry.file_store = file_store
5399 meta_entry.file_store_meta_section = section
5407 meta_entry.file_store_meta_section = section
5400 meta_entry.file_store_meta_key = key
5408 meta_entry.file_store_meta_key = key
5401 meta_entry.file_store_meta_value_type = value_type
5409 meta_entry.file_store_meta_value_type = value_type
5402 meta_entry.file_store_meta_value = value
5410 meta_entry.file_store_meta_value = value
5403
5411
5404 Session().add(meta_entry)
5412 Session().add(meta_entry)
5405
5413
5406 try:
5414 try:
5407 if commit:
5415 if commit:
5408 Session().commit()
5416 Session().commit()
5409 except IntegrityError:
5417 except IntegrityError:
5410 Session().rollback()
5418 Session().rollback()
5411 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5419 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5412
5420
5413 @classmethod
5421 @classmethod
5414 def bump_access_counter(cls, file_uid, commit=True):
5422 def bump_access_counter(cls, file_uid, commit=True):
5415 FileStore().query()\
5423 FileStore().query()\
5416 .filter(FileStore.file_uid == file_uid)\
5424 .filter(FileStore.file_uid == file_uid)\
5417 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5425 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5418 FileStore.accessed_on: datetime.datetime.now()})
5426 FileStore.accessed_on: datetime.datetime.now()})
5419 if commit:
5427 if commit:
5420 Session().commit()
5428 Session().commit()
5421
5429
5422 def __json__(self):
5430 def __json__(self):
5423 data = {
5431 data = {
5424 'filename': self.file_display_name,
5432 'filename': self.file_display_name,
5425 'filename_org': self.file_org_name,
5433 'filename_org': self.file_org_name,
5426 'file_uid': self.file_uid,
5434 'file_uid': self.file_uid,
5427 'description': self.file_description,
5435 'description': self.file_description,
5428 'hidden': self.hidden,
5436 'hidden': self.hidden,
5429 'size': self.file_size,
5437 'size': self.file_size,
5430 'created_on': self.created_on,
5438 'created_on': self.created_on,
5431 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5439 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5432 'downloaded_times': self.accessed_count,
5440 'downloaded_times': self.accessed_count,
5433 'sha256': self.file_hash,
5441 'sha256': self.file_hash,
5434 'metadata': self.file_metadata,
5442 'metadata': self.file_metadata,
5435 }
5443 }
5436
5444
5437 return data
5445 return data
5438
5446
5439 def __repr__(self):
5447 def __repr__(self):
5440 return '<FileStore({})>'.format(self.file_store_id)
5448 return '<FileStore({})>'.format(self.file_store_id)
5441
5449
5442
5450
5443 class FileStoreMetadata(Base, BaseModel):
5451 class FileStoreMetadata(Base, BaseModel):
5444 __tablename__ = 'file_store_metadata'
5452 __tablename__ = 'file_store_metadata'
5445 __table_args__ = (
5453 __table_args__ = (
5446 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5454 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5447 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5455 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5448 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5456 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5449 base_table_args
5457 base_table_args
5450 )
5458 )
5451 SETTINGS_TYPES = {
5459 SETTINGS_TYPES = {
5452 'str': safe_str,
5460 'str': safe_str,
5453 'int': safe_int,
5461 'int': safe_int,
5454 'unicode': safe_unicode,
5462 'unicode': safe_unicode,
5455 'bool': str2bool,
5463 'bool': str2bool,
5456 'list': functools.partial(aslist, sep=',')
5464 'list': functools.partial(aslist, sep=',')
5457 }
5465 }
5458
5466
5459 file_store_meta_id = Column(
5467 file_store_meta_id = Column(
5460 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5468 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5461 primary_key=True)
5469 primary_key=True)
5462 _file_store_meta_section = Column(
5470 _file_store_meta_section = Column(
5463 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5471 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5464 nullable=True, unique=None, default=None)
5472 nullable=True, unique=None, default=None)
5465 _file_store_meta_section_hash = Column(
5473 _file_store_meta_section_hash = Column(
5466 "file_store_meta_section_hash", String(255),
5474 "file_store_meta_section_hash", String(255),
5467 nullable=True, unique=None, default=None)
5475 nullable=True, unique=None, default=None)
5468 _file_store_meta_key = Column(
5476 _file_store_meta_key = Column(
5469 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5477 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5470 nullable=True, unique=None, default=None)
5478 nullable=True, unique=None, default=None)
5471 _file_store_meta_key_hash = Column(
5479 _file_store_meta_key_hash = Column(
5472 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5480 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5473 _file_store_meta_value = Column(
5481 _file_store_meta_value = Column(
5474 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5482 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5475 nullable=True, unique=None, default=None)
5483 nullable=True, unique=None, default=None)
5476 _file_store_meta_value_type = Column(
5484 _file_store_meta_value_type = Column(
5477 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5485 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5478 default='unicode')
5486 default='unicode')
5479
5487
5480 file_store_id = Column(
5488 file_store_id = Column(
5481 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5489 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5482 nullable=True, unique=None, default=None)
5490 nullable=True, unique=None, default=None)
5483
5491
5484 file_store = relationship('FileStore', lazy='joined')
5492 file_store = relationship('FileStore', lazy='joined')
5485
5493
5486 @classmethod
5494 @classmethod
5487 def valid_value_type(cls, value):
5495 def valid_value_type(cls, value):
5488 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5496 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5489 raise ArtifactMetadataBadValueType(
5497 raise ArtifactMetadataBadValueType(
5490 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5498 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5491
5499
5492 @hybrid_property
5500 @hybrid_property
5493 def file_store_meta_section(self):
5501 def file_store_meta_section(self):
5494 return self._file_store_meta_section
5502 return self._file_store_meta_section
5495
5503
5496 @file_store_meta_section.setter
5504 @file_store_meta_section.setter
5497 def file_store_meta_section(self, value):
5505 def file_store_meta_section(self, value):
5498 self._file_store_meta_section = value
5506 self._file_store_meta_section = value
5499 self._file_store_meta_section_hash = _hash_key(value)
5507 self._file_store_meta_section_hash = _hash_key(value)
5500
5508
5501 @hybrid_property
5509 @hybrid_property
5502 def file_store_meta_key(self):
5510 def file_store_meta_key(self):
5503 return self._file_store_meta_key
5511 return self._file_store_meta_key
5504
5512
5505 @file_store_meta_key.setter
5513 @file_store_meta_key.setter
5506 def file_store_meta_key(self, value):
5514 def file_store_meta_key(self, value):
5507 self._file_store_meta_key = value
5515 self._file_store_meta_key = value
5508 self._file_store_meta_key_hash = _hash_key(value)
5516 self._file_store_meta_key_hash = _hash_key(value)
5509
5517
5510 @hybrid_property
5518 @hybrid_property
5511 def file_store_meta_value(self):
5519 def file_store_meta_value(self):
5512 val = self._file_store_meta_value
5520 val = self._file_store_meta_value
5513
5521
5514 if self._file_store_meta_value_type:
5522 if self._file_store_meta_value_type:
5515 # e.g unicode.encrypted == unicode
5523 # e.g unicode.encrypted == unicode
5516 _type = self._file_store_meta_value_type.split('.')[0]
5524 _type = self._file_store_meta_value_type.split('.')[0]
5517 # decode the encrypted value if it's encrypted field type
5525 # decode the encrypted value if it's encrypted field type
5518 if '.encrypted' in self._file_store_meta_value_type:
5526 if '.encrypted' in self._file_store_meta_value_type:
5519 cipher = EncryptedTextValue()
5527 cipher = EncryptedTextValue()
5520 val = safe_unicode(cipher.process_result_value(val, None))
5528 val = safe_unicode(cipher.process_result_value(val, None))
5521 # do final type conversion
5529 # do final type conversion
5522 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5530 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5523 val = converter(val)
5531 val = converter(val)
5524
5532
5525 return val
5533 return val
5526
5534
5527 @file_store_meta_value.setter
5535 @file_store_meta_value.setter
5528 def file_store_meta_value(self, val):
5536 def file_store_meta_value(self, val):
5529 val = safe_unicode(val)
5537 val = safe_unicode(val)
5530 # encode the encrypted value
5538 # encode the encrypted value
5531 if '.encrypted' in self.file_store_meta_value_type:
5539 if '.encrypted' in self.file_store_meta_value_type:
5532 cipher = EncryptedTextValue()
5540 cipher = EncryptedTextValue()
5533 val = safe_unicode(cipher.process_bind_param(val, None))
5541 val = safe_unicode(cipher.process_bind_param(val, None))
5534 self._file_store_meta_value = val
5542 self._file_store_meta_value = val
5535
5543
5536 @hybrid_property
5544 @hybrid_property
5537 def file_store_meta_value_type(self):
5545 def file_store_meta_value_type(self):
5538 return self._file_store_meta_value_type
5546 return self._file_store_meta_value_type
5539
5547
5540 @file_store_meta_value_type.setter
5548 @file_store_meta_value_type.setter
5541 def file_store_meta_value_type(self, val):
5549 def file_store_meta_value_type(self, val):
5542 # e.g unicode.encrypted
5550 # e.g unicode.encrypted
5543 self.valid_value_type(val)
5551 self.valid_value_type(val)
5544 self._file_store_meta_value_type = val
5552 self._file_store_meta_value_type = val
5545
5553
5546 def __json__(self):
5554 def __json__(self):
5547 data = {
5555 data = {
5548 'artifact': self.file_store.file_uid,
5556 'artifact': self.file_store.file_uid,
5549 'section': self.file_store_meta_section,
5557 'section': self.file_store_meta_section,
5550 'key': self.file_store_meta_key,
5558 'key': self.file_store_meta_key,
5551 'value': self.file_store_meta_value,
5559 'value': self.file_store_meta_value,
5552 }
5560 }
5553
5561
5554 return data
5562 return data
5555
5563
5556 def __repr__(self):
5564 def __repr__(self):
5557 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5565 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5558 self.file_store_meta_key, self.file_store_meta_value)
5566 self.file_store_meta_key, self.file_store_meta_value)
5559
5567
5560
5568
5561 class DbMigrateVersion(Base, BaseModel):
5569 class DbMigrateVersion(Base, BaseModel):
5562 __tablename__ = 'db_migrate_version'
5570 __tablename__ = 'db_migrate_version'
5563 __table_args__ = (
5571 __table_args__ = (
5564 base_table_args,
5572 base_table_args,
5565 )
5573 )
5566
5574
5567 repository_id = Column('repository_id', String(250), primary_key=True)
5575 repository_id = Column('repository_id', String(250), primary_key=True)
5568 repository_path = Column('repository_path', Text)
5576 repository_path = Column('repository_path', Text)
5569 version = Column('version', Integer)
5577 version = Column('version', Integer)
5570
5578
5571 @classmethod
5579 @classmethod
5572 def set_version(cls, version):
5580 def set_version(cls, version):
5573 """
5581 """
5574 Helper for forcing a different version, usually for debugging purposes via ishell.
5582 Helper for forcing a different version, usually for debugging purposes via ishell.
5575 """
5583 """
5576 ver = DbMigrateVersion.query().first()
5584 ver = DbMigrateVersion.query().first()
5577 ver.version = version
5585 ver.version = version
5578 Session().commit()
5586 Session().commit()
5579
5587
5580
5588
5581 class DbSession(Base, BaseModel):
5589 class DbSession(Base, BaseModel):
5582 __tablename__ = 'db_session'
5590 __tablename__ = 'db_session'
5583 __table_args__ = (
5591 __table_args__ = (
5584 base_table_args,
5592 base_table_args,
5585 )
5593 )
5586
5594
5587 def __repr__(self):
5595 def __repr__(self):
5588 return '<DB:DbSession({})>'.format(self.id)
5596 return '<DB:DbSession({})>'.format(self.id)
5589
5597
5590 id = Column('id', Integer())
5598 id = Column('id', Integer())
5591 namespace = Column('namespace', String(255), primary_key=True)
5599 namespace = Column('namespace', String(255), primary_key=True)
5592 accessed = Column('accessed', DateTime, nullable=False)
5600 accessed = Column('accessed', DateTime, nullable=False)
5593 created = Column('created', DateTime, nullable=False)
5601 created = Column('created', DateTime, nullable=False)
5594 data = Column('data', PickleType, nullable=False)
5602 data = Column('data', PickleType, nullable=False)
@@ -1,1201 +1,1201 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%!
3 <%!
4 ## base64 filter e.g ${ example | base64 }
4 ## base64 filter e.g ${ example | base64 }
5 def base64(text):
5 def base64(text):
6 import base64
6 import base64
7 from rhodecode.lib.helpers import safe_str
7 from rhodecode.lib.helpers import safe_str
8 return base64.encodestring(safe_str(text))
8 return base64.encodestring(safe_str(text))
9 %>
9 %>
10
10
11 <%inherit file="root.mako"/>
11 <%inherit file="root.mako"/>
12
12
13 <%include file="/ejs_templates/templates.html"/>
13 <%include file="/ejs_templates/templates.html"/>
14
14
15 <div class="outerwrapper">
15 <div class="outerwrapper">
16 <!-- HEADER -->
16 <!-- HEADER -->
17 <div class="header">
17 <div class="header">
18 <div id="header-inner" class="wrapper">
18 <div id="header-inner" class="wrapper">
19 <div id="logo">
19 <div id="logo">
20 <div class="logo-wrapper">
20 <div class="logo-wrapper">
21 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
21 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
22 </div>
22 </div>
23 % if c.rhodecode_name:
23 % if c.rhodecode_name:
24 <div class="branding">
24 <div class="branding">
25 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
25 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
26 </div>
26 </div>
27 % endif
27 % endif
28 </div>
28 </div>
29 <!-- MENU BAR NAV -->
29 <!-- MENU BAR NAV -->
30 ${self.menu_bar_nav()}
30 ${self.menu_bar_nav()}
31 <!-- END MENU BAR NAV -->
31 <!-- END MENU BAR NAV -->
32 </div>
32 </div>
33 </div>
33 </div>
34 ${self.menu_bar_subnav()}
34 ${self.menu_bar_subnav()}
35 <!-- END HEADER -->
35 <!-- END HEADER -->
36
36
37 <!-- CONTENT -->
37 <!-- CONTENT -->
38 <div id="content" class="wrapper">
38 <div id="content" class="wrapper">
39
39
40 <rhodecode-toast id="notifications"></rhodecode-toast>
40 <rhodecode-toast id="notifications"></rhodecode-toast>
41
41
42 <div class="main">
42 <div class="main">
43 ${next.main()}
43 ${next.main()}
44 </div>
44 </div>
45 </div>
45 </div>
46 <!-- END CONTENT -->
46 <!-- END CONTENT -->
47
47
48 </div>
48 </div>
49 <!-- FOOTER -->
49 <!-- FOOTER -->
50 <div id="footer">
50 <div id="footer">
51 <div id="footer-inner" class="title wrapper">
51 <div id="footer-inner" class="title wrapper">
52 <div>
52 <div>
53 <p class="footer-link-right">
53 <p class="footer-link-right">
54 % if c.visual.show_version:
54 % if c.visual.show_version:
55 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
55 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
56 % endif
56 % endif
57 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
57 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
58 % if c.visual.rhodecode_support_url:
58 % if c.visual.rhodecode_support_url:
59 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
59 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
60 % endif
60 % endif
61 </p>
61 </p>
62 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
62 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
63 <p class="server-instance" style="display:${sid}">
63 <p class="server-instance" style="display:${sid}">
64 ## display hidden instance ID if specially defined
64 ## display hidden instance ID if specially defined
65 % if c.rhodecode_instanceid:
65 % if c.rhodecode_instanceid:
66 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
66 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
67 % endif
67 % endif
68 </p>
68 </p>
69 </div>
69 </div>
70 </div>
70 </div>
71 </div>
71 </div>
72
72
73 <!-- END FOOTER -->
73 <!-- END FOOTER -->
74
74
75 ### MAKO DEFS ###
75 ### MAKO DEFS ###
76
76
77 <%def name="menu_bar_subnav()">
77 <%def name="menu_bar_subnav()">
78 </%def>
78 </%def>
79
79
80 <%def name="breadcrumbs(class_='breadcrumbs')">
80 <%def name="breadcrumbs(class_='breadcrumbs')">
81 <div class="${class_}">
81 <div class="${class_}">
82 ${self.breadcrumbs_links()}
82 ${self.breadcrumbs_links()}
83 </div>
83 </div>
84 </%def>
84 </%def>
85
85
86 <%def name="admin_menu(active=None)">
86 <%def name="admin_menu(active=None)">
87
87
88 <div id="context-bar">
88 <div id="context-bar">
89 <div class="wrapper">
89 <div class="wrapper">
90 <div class="title">
90 <div class="title">
91 <div class="title-content">
91 <div class="title-content">
92 <div class="title-main">
92 <div class="title-main">
93 % if c.is_super_admin:
93 % if c.is_super_admin:
94 ${_('Super-admin Panel')}
94 ${_('Super-admin Panel')}
95 % else:
95 % else:
96 ${_('Delegated Admin Panel')}
96 ${_('Delegated Admin Panel')}
97 % endif
97 % endif
98 </div>
98 </div>
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <ul id="context-pages" class="navigation horizontal-list">
102 <ul id="context-pages" class="navigation horizontal-list">
103
103
104 ## super-admin case
104 ## super-admin case
105 % if c.is_super_admin:
105 % if c.is_super_admin:
106 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
106 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
107 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
107 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
108 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
108 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
109 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
109 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
110 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
110 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
111 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
111 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
112 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
112 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
113 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
113 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
114 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
114 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
115 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
115 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
116
116
117 ## delegated admin
117 ## delegated admin
118 % elif c.is_delegated_admin:
118 % elif c.is_delegated_admin:
119 <%
119 <%
120 repositories=c.auth_user.repositories_admin or c.can_create_repo
120 repositories=c.auth_user.repositories_admin or c.can_create_repo
121 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
121 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
122 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
122 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
123 %>
123 %>
124
124
125 %if repositories:
125 %if repositories:
126 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
126 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
127 %endif
127 %endif
128 %if repository_groups:
128 %if repository_groups:
129 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
129 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
130 %endif
130 %endif
131 %if user_groups:
131 %if user_groups:
132 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
132 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
133 %endif
133 %endif
134 % endif
134 % endif
135 </ul>
135 </ul>
136
136
137 </div>
137 </div>
138 <div class="clear"></div>
138 <div class="clear"></div>
139 </div>
139 </div>
140 </%def>
140 </%def>
141
141
142 <%def name="dt_info_panel(elements)">
142 <%def name="dt_info_panel(elements)">
143 <dl class="dl-horizontal">
143 <dl class="dl-horizontal">
144 %for dt, dd, title, show_items in elements:
144 %for dt, dd, title, show_items in elements:
145 <dt>${dt}:</dt>
145 <dt>${dt}:</dt>
146 <dd title="${h.tooltip(title)}">
146 <dd title="${h.tooltip(title)}">
147 %if callable(dd):
147 %if callable(dd):
148 ## allow lazy evaluation of elements
148 ## allow lazy evaluation of elements
149 ${dd()}
149 ${dd()}
150 %else:
150 %else:
151 ${dd}
151 ${dd}
152 %endif
152 %endif
153 %if show_items:
153 %if show_items:
154 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
154 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
155 %endif
155 %endif
156 </dd>
156 </dd>
157
157
158 %if show_items:
158 %if show_items:
159 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
159 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
160 %for item in show_items:
160 %for item in show_items:
161 <dt></dt>
161 <dt></dt>
162 <dd>${item}</dd>
162 <dd>${item}</dd>
163 %endfor
163 %endfor
164 </div>
164 </div>
165 %endif
165 %endif
166
166
167 %endfor
167 %endfor
168 </dl>
168 </dl>
169 </%def>
169 </%def>
170
170
171 <%def name="tr_info_entry(element)">
171 <%def name="tr_info_entry(element)">
172 <% key, val, title, show_items = element %>
172 <% key, val, title, show_items = element %>
173
173
174 <tr>
174 <tr>
175 <td style="vertical-align: top">${key}</td>
175 <td style="vertical-align: top">${key}</td>
176 <td title="${h.tooltip(title)}">
176 <td title="${h.tooltip(title)}">
177 %if callable(val):
177 %if callable(val):
178 ## allow lazy evaluation of elements
178 ## allow lazy evaluation of elements
179 ${val()}
179 ${val()}
180 %else:
180 %else:
181 ${val}
181 ${val}
182 %endif
182 %endif
183 %if show_items:
183 %if show_items:
184 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
184 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
185 % for item in show_items:
185 % for item in show_items:
186 <dt></dt>
186 <dt></dt>
187 <dd>${item}</dd>
187 <dd>${item}</dd>
188 % endfor
188 % endfor
189 </div>
189 </div>
190 %endif
190 %endif
191 </td>
191 </td>
192 <td style="vertical-align: top">
192 <td style="vertical-align: top">
193 %if show_items:
193 %if show_items:
194 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
194 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
195 %endif
195 %endif
196 </td>
196 </td>
197 </tr>
197 </tr>
198
198
199 </%def>
199 </%def>
200
200
201 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
201 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
202 <%
202 <%
203 if size > 16:
203 if size > 16:
204 gravatar_class = ['gravatar','gravatar-large']
204 gravatar_class = ['gravatar','gravatar-large']
205 else:
205 else:
206 gravatar_class = ['gravatar']
206 gravatar_class = ['gravatar']
207
207
208 data_hovercard_url = ''
208 data_hovercard_url = ''
209 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
209 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
210
210
211 if tooltip:
211 if tooltip:
212 gravatar_class += ['tooltip-hovercard']
212 gravatar_class += ['tooltip-hovercard']
213 if extra_class:
213 if extra_class:
214 gravatar_class += extra_class
214 gravatar_class += extra_class
215 if tooltip and user:
215 if tooltip and user:
216 if user.username == h.DEFAULT_USER:
216 if user.username == h.DEFAULT_USER:
217 gravatar_class.pop(-1)
217 gravatar_class.pop(-1)
218 else:
218 else:
219 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
219 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
220 gravatar_class = ' '.join(gravatar_class)
220 gravatar_class = ' '.join(gravatar_class)
221
221
222 %>
222 %>
223 <%doc>
223 <%doc>
224 TODO: johbo: For now we serve double size images to make it smooth
224 TODO: johbo: For now we serve double size images to make it smooth
225 for retina. This is how it worked until now. Should be replaced
225 for retina. This is how it worked until now. Should be replaced
226 with a better solution at some point.
226 with a better solution at some point.
227 </%doc>
227 </%doc>
228
228
229 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
229 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
230 </%def>
230 </%def>
231
231
232
232
233 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
233 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
234 <%
234 <%
235 email = h.email_or_none(contact)
235 email = h.email_or_none(contact)
236 rc_user = h.discover_user(contact)
236 rc_user = h.discover_user(contact)
237 %>
237 %>
238
238
239 <div class="${_class}">
239 <div class="${_class}">
240 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
240 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
241 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
241 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
242 </div>
242 </div>
243 </%def>
243 </%def>
244
244
245
245
246 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
246 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
247 <%
247 <%
248 if (size > 16):
248 if (size > 16):
249 gravatar_class = 'icon-user-group-alt'
249 gravatar_class = 'icon-user-group-alt'
250 else:
250 else:
251 gravatar_class = 'icon-user-group-alt'
251 gravatar_class = 'icon-user-group-alt'
252
252
253 if tooltip:
253 if tooltip:
254 gravatar_class += ' tooltip-hovercard'
254 gravatar_class += ' tooltip-hovercard'
255
255
256 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
256 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
257 %>
257 %>
258 <%doc>
258 <%doc>
259 TODO: johbo: For now we serve double size images to make it smooth
259 TODO: johbo: For now we serve double size images to make it smooth
260 for retina. This is how it worked until now. Should be replaced
260 for retina. This is how it worked until now. Should be replaced
261 with a better solution at some point.
261 with a better solution at some point.
262 </%doc>
262 </%doc>
263
263
264 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
264 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
265 </%def>
265 </%def>
266
266
267 <%def name="repo_page_title(repo_instance)">
267 <%def name="repo_page_title(repo_instance)">
268 <div class="title-content repo-title">
268 <div class="title-content repo-title">
269
269
270 <div class="title-main">
270 <div class="title-main">
271 ## SVN/HG/GIT icons
271 ## SVN/HG/GIT icons
272 %if h.is_hg(repo_instance):
272 %if h.is_hg(repo_instance):
273 <i class="icon-hg"></i>
273 <i class="icon-hg"></i>
274 %endif
274 %endif
275 %if h.is_git(repo_instance):
275 %if h.is_git(repo_instance):
276 <i class="icon-git"></i>
276 <i class="icon-git"></i>
277 %endif
277 %endif
278 %if h.is_svn(repo_instance):
278 %if h.is_svn(repo_instance):
279 <i class="icon-svn"></i>
279 <i class="icon-svn"></i>
280 %endif
280 %endif
281
281
282 ## public/private
282 ## public/private
283 %if repo_instance.private:
283 %if repo_instance.private:
284 <i class="icon-repo-private"></i>
284 <i class="icon-repo-private"></i>
285 %else:
285 %else:
286 <i class="icon-repo-public"></i>
286 <i class="icon-repo-public"></i>
287 %endif
287 %endif
288
288
289 ## repo name with group name
289 ## repo name with group name
290 ${h.breadcrumb_repo_link(repo_instance)}
290 ${h.breadcrumb_repo_link(repo_instance)}
291
291
292 ## Context Actions
292 ## Context Actions
293 <div class="pull-right">
293 <div class="pull-right">
294 %if c.rhodecode_user.username != h.DEFAULT_USER:
294 %if c.rhodecode_user.username != h.DEFAULT_USER:
295 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
295 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
296
296
297 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
297 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
298 % if c.repository_is_user_following:
298 % if c.repository_is_user_following:
299 <i class="icon-eye-off"></i>${_('Unwatch')}
299 <i class="icon-eye-off"></i>${_('Unwatch')}
300 % else:
300 % else:
301 <i class="icon-eye"></i>${_('Watch')}
301 <i class="icon-eye"></i>${_('Watch')}
302 % endif
302 % endif
303
303
304 </a>
304 </a>
305 %else:
305 %else:
306 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
306 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
307 %endif
307 %endif
308 </div>
308 </div>
309
309
310 </div>
310 </div>
311
311
312 ## FORKED
312 ## FORKED
313 %if repo_instance.fork:
313 %if repo_instance.fork:
314 <p class="discreet">
314 <p class="discreet">
315 <i class="icon-code-fork"></i> ${_('Fork of')}
315 <i class="icon-code-fork"></i> ${_('Fork of')}
316 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
316 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
317 </p>
317 </p>
318 %endif
318 %endif
319
319
320 ## IMPORTED FROM REMOTE
320 ## IMPORTED FROM REMOTE
321 %if repo_instance.clone_uri:
321 %if repo_instance.clone_uri:
322 <p class="discreet">
322 <p class="discreet">
323 <i class="icon-code-fork"></i> ${_('Clone from')}
323 <i class="icon-code-fork"></i> ${_('Clone from')}
324 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
324 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
325 </p>
325 </p>
326 %endif
326 %endif
327
327
328 ## LOCKING STATUS
328 ## LOCKING STATUS
329 %if repo_instance.locked[0]:
329 %if repo_instance.locked[0]:
330 <p class="locking_locked discreet">
330 <p class="locking_locked discreet">
331 <i class="icon-repo-lock"></i>
331 <i class="icon-repo-lock"></i>
332 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
332 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
333 </p>
333 </p>
334 %elif repo_instance.enable_locking:
334 %elif repo_instance.enable_locking:
335 <p class="locking_unlocked discreet">
335 <p class="locking_unlocked discreet">
336 <i class="icon-repo-unlock"></i>
336 <i class="icon-repo-unlock"></i>
337 ${_('Repository not locked. Pull repository to lock it.')}
337 ${_('Repository not locked. Pull repository to lock it.')}
338 </p>
338 </p>
339 %endif
339 %endif
340
340
341 </div>
341 </div>
342 </%def>
342 </%def>
343
343
344 <%def name="repo_menu(active=None)">
344 <%def name="repo_menu(active=None)">
345 <%
345 <%
346 ## determine if we have "any" option available
346 ## determine if we have "any" option available
347 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
347 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
348 has_actions = can_lock
348 has_actions = can_lock
349
349
350 %>
350 %>
351 % if c.rhodecode_db_repo.archived:
351 % if c.rhodecode_db_repo.archived:
352 <div class="alert alert-warning text-center">
352 <div class="alert alert-warning text-center">
353 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
353 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
354 </div>
354 </div>
355 % endif
355 % endif
356
356
357 <!--- REPO CONTEXT BAR -->
357 <!--- REPO CONTEXT BAR -->
358 <div id="context-bar">
358 <div id="context-bar">
359 <div class="wrapper">
359 <div class="wrapper">
360
360
361 <div class="title">
361 <div class="title">
362 ${self.repo_page_title(c.rhodecode_db_repo)}
362 ${self.repo_page_title(c.rhodecode_db_repo)}
363 </div>
363 </div>
364
364
365 <ul id="context-pages" class="navigation horizontal-list">
365 <ul id="context-pages" class="navigation horizontal-list">
366 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
366 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
367 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
367 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
368 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
368 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_ref_name, f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
369 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
369 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
370
370
371 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
371 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
372 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
372 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
373 <li class="${h.is_active('showpullrequest', active)}">
373 <li class="${h.is_active('showpullrequest', active)}">
374 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
374 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
375 <div class="menulabel">
375 <div class="menulabel">
376 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
376 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
377 </div>
377 </div>
378 </a>
378 </a>
379 </li>
379 </li>
380 %endif
380 %endif
381
381
382 <li class="${h.is_active('artifacts', active)}">
382 <li class="${h.is_active('artifacts', active)}">
383 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
383 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
384 <div class="menulabel">
384 <div class="menulabel">
385 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
385 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
386 </div>
386 </div>
387 </a>
387 </a>
388 </li>
388 </li>
389
389
390 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
390 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
391 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
391 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
392 %endif
392 %endif
393
393
394 <li class="${h.is_active('options', active)}">
394 <li class="${h.is_active('options', active)}">
395 % if has_actions:
395 % if has_actions:
396 <a class="menulink dropdown">
396 <a class="menulink dropdown">
397 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
397 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
398 </a>
398 </a>
399 <ul class="submenu">
399 <ul class="submenu">
400 %if can_lock:
400 %if can_lock:
401 %if c.rhodecode_db_repo.locked[0]:
401 %if c.rhodecode_db_repo.locked[0]:
402 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
402 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
403 %else:
403 %else:
404 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
404 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
405 %endif
405 %endif
406 %endif
406 %endif
407 </ul>
407 </ul>
408 % endif
408 % endif
409 </li>
409 </li>
410
410
411 </ul>
411 </ul>
412 </div>
412 </div>
413 <div class="clear"></div>
413 <div class="clear"></div>
414 </div>
414 </div>
415
415
416 <!--- REPO END CONTEXT BAR -->
416 <!--- REPO END CONTEXT BAR -->
417
417
418 </%def>
418 </%def>
419
419
420 <%def name="repo_group_page_title(repo_group_instance)">
420 <%def name="repo_group_page_title(repo_group_instance)">
421 <div class="title-content">
421 <div class="title-content">
422 <div class="title-main">
422 <div class="title-main">
423 ## Repository Group icon
423 ## Repository Group icon
424 <i class="icon-repo-group"></i>
424 <i class="icon-repo-group"></i>
425
425
426 ## repo name with group name
426 ## repo name with group name
427 ${h.breadcrumb_repo_group_link(repo_group_instance)}
427 ${h.breadcrumb_repo_group_link(repo_group_instance)}
428 </div>
428 </div>
429
429
430 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
430 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
431 <div class="repo-group-desc discreet">
431 <div class="repo-group-desc discreet">
432 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
432 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
433 </div>
433 </div>
434
434
435 </div>
435 </div>
436 </%def>
436 </%def>
437
437
438
438
439 <%def name="repo_group_menu(active=None)">
439 <%def name="repo_group_menu(active=None)">
440 <%
440 <%
441 gr_name = c.repo_group.group_name if c.repo_group else None
441 gr_name = c.repo_group.group_name if c.repo_group else None
442 # create repositories with write permission on group is set to true
442 # create repositories with write permission on group is set to true
443 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
443 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
444
444
445 %>
445 %>
446
446
447
447
448 <!--- REPO GROUP CONTEXT BAR -->
448 <!--- REPO GROUP CONTEXT BAR -->
449 <div id="context-bar">
449 <div id="context-bar">
450 <div class="wrapper">
450 <div class="wrapper">
451 <div class="title">
451 <div class="title">
452 ${self.repo_group_page_title(c.repo_group)}
452 ${self.repo_group_page_title(c.repo_group)}
453 </div>
453 </div>
454
454
455 <ul id="context-pages" class="navigation horizontal-list">
455 <ul id="context-pages" class="navigation horizontal-list">
456 <li class="${h.is_active('home', active)}">
456 <li class="${h.is_active('home', active)}">
457 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
457 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
458 </li>
458 </li>
459 % if c.is_super_admin or group_admin:
459 % if c.is_super_admin or group_admin:
460 <li class="${h.is_active('settings', active)}">
460 <li class="${h.is_active('settings', active)}">
461 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
461 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
462 </li>
462 </li>
463 % endif
463 % endif
464
464
465 </ul>
465 </ul>
466 </div>
466 </div>
467 <div class="clear"></div>
467 <div class="clear"></div>
468 </div>
468 </div>
469
469
470 <!--- REPO GROUP CONTEXT BAR -->
470 <!--- REPO GROUP CONTEXT BAR -->
471
471
472 </%def>
472 </%def>
473
473
474
474
475 <%def name="usermenu(active=False)">
475 <%def name="usermenu(active=False)">
476 <%
476 <%
477 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
477 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
478
478
479 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
479 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
480 # create repositories with write permission on group is set to true
480 # create repositories with write permission on group is set to true
481
481
482 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
482 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
483 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
483 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
484 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
484 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
485 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
485 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
486
486
487 can_create_repos = c.is_super_admin or c.can_create_repo
487 can_create_repos = c.is_super_admin or c.can_create_repo
488 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
488 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
489
489
490 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
490 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
491 can_create_repo_groups_in_group = c.is_super_admin or group_admin
491 can_create_repo_groups_in_group = c.is_super_admin or group_admin
492 %>
492 %>
493
493
494 % if not_anonymous:
494 % if not_anonymous:
495 <%
495 <%
496 default_target_group = dict()
496 default_target_group = dict()
497 if c.rhodecode_user.personal_repo_group:
497 if c.rhodecode_user.personal_repo_group:
498 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
498 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
499 %>
499 %>
500
500
501 ## create action
501 ## create action
502 <li>
502 <li>
503 <a href="#create-actions" onclick="return false;" class="menulink childs">
503 <a href="#create-actions" onclick="return false;" class="menulink childs">
504 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
504 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
505 </a>
505 </a>
506
506
507 <div class="action-menu submenu">
507 <div class="action-menu submenu">
508
508
509 <ol>
509 <ol>
510 ## scope of within a repository
510 ## scope of within a repository
511 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
511 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
512 <li class="submenu-title">${_('This Repository')}</li>
512 <li class="submenu-title">${_('This Repository')}</li>
513 <li>
513 <li>
514 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
514 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
515 </li>
515 </li>
516 % if can_fork:
516 % if can_fork:
517 <li>
517 <li>
518 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
518 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
519 </li>
519 </li>
520 % endif
520 % endif
521 % endif
521 % endif
522
522
523 ## scope of within repository groups
523 ## scope of within repository groups
524 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
524 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
525 <li class="submenu-title">${_('This Repository Group')}</li>
525 <li class="submenu-title">${_('This Repository Group')}</li>
526
526
527 % if can_create_repos_in_group:
527 % if can_create_repos_in_group:
528 <li>
528 <li>
529 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
529 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
530 </li>
530 </li>
531 % endif
531 % endif
532
532
533 % if can_create_repo_groups_in_group:
533 % if can_create_repo_groups_in_group:
534 <li>
534 <li>
535 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
535 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
536 </li>
536 </li>
537 % endif
537 % endif
538 % endif
538 % endif
539
539
540 ## personal group
540 ## personal group
541 % if c.rhodecode_user.personal_repo_group:
541 % if c.rhodecode_user.personal_repo_group:
542 <li class="submenu-title">Personal Group</li>
542 <li class="submenu-title">Personal Group</li>
543
543
544 <li>
544 <li>
545 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
545 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
546 </li>
546 </li>
547
547
548 <li>
548 <li>
549 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
549 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
550 </li>
550 </li>
551 % endif
551 % endif
552
552
553 ## Global actions
553 ## Global actions
554 <li class="submenu-title">RhodeCode</li>
554 <li class="submenu-title">RhodeCode</li>
555 % if can_create_repos:
555 % if can_create_repos:
556 <li>
556 <li>
557 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
557 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
558 </li>
558 </li>
559 % endif
559 % endif
560
560
561 % if can_create_repo_groups:
561 % if can_create_repo_groups:
562 <li>
562 <li>
563 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
563 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
564 </li>
564 </li>
565 % endif
565 % endif
566
566
567 <li>
567 <li>
568 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
568 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
569 </li>
569 </li>
570
570
571 </ol>
571 </ol>
572
572
573 </div>
573 </div>
574 </li>
574 </li>
575
575
576 ## notifications
576 ## notifications
577 <li>
577 <li>
578 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
578 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
579 ${c.unread_notifications}
579 ${c.unread_notifications}
580 </a>
580 </a>
581 </li>
581 </li>
582 % endif
582 % endif
583
583
584 ## USER MENU
584 ## USER MENU
585 <li id="quick_login_li" class="${'active' if active else ''}">
585 <li id="quick_login_li" class="${'active' if active else ''}">
586 % if c.rhodecode_user.username == h.DEFAULT_USER:
586 % if c.rhodecode_user.username == h.DEFAULT_USER:
587 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
587 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
588 ${gravatar(c.rhodecode_user.email, 20)}
588 ${gravatar(c.rhodecode_user.email, 20)}
589 <span class="user">
589 <span class="user">
590 <span>${_('Sign in')}</span>
590 <span>${_('Sign in')}</span>
591 </span>
591 </span>
592 </a>
592 </a>
593 % else:
593 % else:
594 ## logged in user
594 ## logged in user
595 <a id="quick_login_link" class="menulink childs">
595 <a id="quick_login_link" class="menulink childs">
596 ${gravatar(c.rhodecode_user.email, 20)}
596 ${gravatar(c.rhodecode_user.email, 20)}
597 <span class="user">
597 <span class="user">
598 <span class="menu_link_user">${c.rhodecode_user.username}</span>
598 <span class="menu_link_user">${c.rhodecode_user.username}</span>
599 <div class="show_more"></div>
599 <div class="show_more"></div>
600 </span>
600 </span>
601 </a>
601 </a>
602 ## subnav with menu for logged in user
602 ## subnav with menu for logged in user
603 <div class="user-menu submenu">
603 <div class="user-menu submenu">
604 <div id="quick_login">
604 <div id="quick_login">
605 %if c.rhodecode_user.username != h.DEFAULT_USER:
605 %if c.rhodecode_user.username != h.DEFAULT_USER:
606 <div class="">
606 <div class="">
607 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
607 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
608 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
608 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
609 <div class="email">${c.rhodecode_user.email}</div>
609 <div class="email">${c.rhodecode_user.email}</div>
610 </div>
610 </div>
611 <div class="">
611 <div class="">
612 <ol class="links">
612 <ol class="links">
613 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
613 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
614 % if c.rhodecode_user.personal_repo_group:
614 % if c.rhodecode_user.personal_repo_group:
615 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
615 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
616 % endif
616 % endif
617 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
617 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
618
618
619 % if c.debug_style:
619 % if c.debug_style:
620 <li>
620 <li>
621 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
621 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
622 <div class="menulabel">${_('[Style]')}</div>
622 <div class="menulabel">${_('[Style]')}</div>
623 </a>
623 </a>
624 </li>
624 </li>
625 % endif
625 % endif
626
626
627 ## bookmark-items
627 ## bookmark-items
628 <li class="bookmark-items">
628 <li class="bookmark-items">
629 ${_('Bookmarks')}
629 ${_('Bookmarks')}
630 <div class="pull-right">
630 <div class="pull-right">
631 <a href="${h.route_path('my_account_bookmarks')}">
631 <a href="${h.route_path('my_account_bookmarks')}">
632
632
633 <i class="icon-cog"></i>
633 <i class="icon-cog"></i>
634 </a>
634 </a>
635 </div>
635 </div>
636 </li>
636 </li>
637 % if not c.bookmark_items:
637 % if not c.bookmark_items:
638 <li>
638 <li>
639 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
639 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
640 </li>
640 </li>
641 % endif
641 % endif
642 % for item in c.bookmark_items:
642 % for item in c.bookmark_items:
643 <li>
643 <li>
644 % if item.repository:
644 % if item.repository:
645 <div>
645 <div>
646 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
646 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
647 <code>${item.position}</code>
647 <code>${item.position}</code>
648 % if item.repository.repo_type == 'hg':
648 % if item.repository.repo_type == 'hg':
649 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
649 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
650 % elif item.repository.repo_type == 'git':
650 % elif item.repository.repo_type == 'git':
651 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
651 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
652 % elif item.repository.repo_type == 'svn':
652 % elif item.repository.repo_type == 'svn':
653 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
653 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
654 % endif
654 % endif
655 ${(item.title or h.shorter(item.repository.repo_name, 30))}
655 ${(item.title or h.shorter(item.repository.repo_name, 30))}
656 </a>
656 </a>
657 </div>
657 </div>
658 % elif item.repository_group:
658 % elif item.repository_group:
659 <div>
659 <div>
660 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
660 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
661 <code>${item.position}</code>
661 <code>${item.position}</code>
662 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
662 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
663 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
663 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
664 </a>
664 </a>
665 </div>
665 </div>
666 % else:
666 % else:
667 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
667 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
668 <code>${item.position}</code>
668 <code>${item.position}</code>
669 ${item.title}
669 ${item.title}
670 </a>
670 </a>
671 % endif
671 % endif
672 </li>
672 </li>
673 % endfor
673 % endfor
674
674
675 <li class="logout">
675 <li class="logout">
676 ${h.secure_form(h.route_path('logout'), request=request)}
676 ${h.secure_form(h.route_path('logout'), request=request)}
677 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
677 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
678 ${h.end_form()}
678 ${h.end_form()}
679 </li>
679 </li>
680 </ol>
680 </ol>
681 </div>
681 </div>
682 %endif
682 %endif
683 </div>
683 </div>
684 </div>
684 </div>
685
685
686 % endif
686 % endif
687 </li>
687 </li>
688 </%def>
688 </%def>
689
689
690 <%def name="menu_items(active=None)">
690 <%def name="menu_items(active=None)">
691 <%
691 <%
692 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
692 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
693 notice_display = 'none' if len(notice_messages) == 0 else ''
693 notice_display = 'none' if len(notice_messages) == 0 else ''
694 %>
694 %>
695 <style>
695 <style>
696
696
697 </style>
697 </style>
698
698
699 <ul id="quick" class="main_nav navigation horizontal-list">
699 <ul id="quick" class="main_nav navigation horizontal-list">
700 ## notice box for important system messages
700 ## notice box for important system messages
701 <li style="display: ${notice_display}">
701 <li style="display: ${notice_display}">
702 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
702 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
703 <div class="menulabel-notice ${notice_level}" >
703 <div class="menulabel-notice ${notice_level}" >
704 ${len(notice_messages)}
704 ${len(notice_messages)}
705 </div>
705 </div>
706 </a>
706 </a>
707 </li>
707 </li>
708 <div class="notice-messages-container" style="display: none">
708 <div class="notice-messages-container" style="display: none">
709 <div class="notice-messages">
709 <div class="notice-messages">
710 <table class="rctable">
710 <table class="rctable">
711 % for notice in notice_messages:
711 % for notice in notice_messages:
712 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
712 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
713 <td style="vertical-align: text-top; width: 20px">
713 <td style="vertical-align: text-top; width: 20px">
714 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
714 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
715 </td>
715 </td>
716 <td>
716 <td>
717 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
717 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
718 ${notice['subject']}
718 ${notice['subject']}
719
719
720 <div id="notice-${notice['msg_id']}" style="display: none">
720 <div id="notice-${notice['msg_id']}" style="display: none">
721 ${h.render(notice['body'], renderer='markdown')}
721 ${h.render(notice['body'], renderer='markdown')}
722 </div>
722 </div>
723 </td>
723 </td>
724 <td style="vertical-align: text-top; width: 35px;">
724 <td style="vertical-align: text-top; width: 35px;">
725 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
725 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
726 <i class="icon-remove icon-filled-red"></i>
726 <i class="icon-remove icon-filled-red"></i>
727 </a>
727 </a>
728 </td>
728 </td>
729 </tr>
729 </tr>
730
730
731 % endfor
731 % endfor
732 </table>
732 </table>
733 </div>
733 </div>
734 </div>
734 </div>
735 ## Main filter
735 ## Main filter
736 <li>
736 <li>
737 <div class="menulabel main_filter_box">
737 <div class="menulabel main_filter_box">
738 <div class="main_filter_input_box">
738 <div class="main_filter_input_box">
739 <ul class="searchItems">
739 <ul class="searchItems">
740
740
741 <li class="searchTag searchTagIcon">
741 <li class="searchTag searchTagIcon">
742 <i class="icon-search"></i>
742 <i class="icon-search"></i>
743 </li>
743 </li>
744
744
745 % if c.template_context['search_context']['repo_id']:
745 % if c.template_context['search_context']['repo_id']:
746 <li class="searchTag searchTagFilter searchTagHidable" >
746 <li class="searchTag searchTagFilter searchTagHidable" >
747 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
747 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
748 <span class="tag">
748 <span class="tag">
749 This repo
749 This repo
750 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
750 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
751 </span>
751 </span>
752 ##</a>
752 ##</a>
753 </li>
753 </li>
754 % elif c.template_context['search_context']['repo_group_id']:
754 % elif c.template_context['search_context']['repo_group_id']:
755 <li class="searchTag searchTagFilter searchTagHidable">
755 <li class="searchTag searchTagFilter searchTagHidable">
756 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
756 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
757 <span class="tag">
757 <span class="tag">
758 This group
758 This group
759 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
759 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
760 </span>
760 </span>
761 ##</a>
761 ##</a>
762 </li>
762 </li>
763 % endif
763 % endif
764
764
765 <li class="searchTagInput">
765 <li class="searchTagInput">
766 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
766 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
767 </li>
767 </li>
768 <li class="searchTag searchTagHelp">
768 <li class="searchTag searchTagHelp">
769 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
769 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
770 </li>
770 </li>
771 </ul>
771 </ul>
772 </div>
772 </div>
773 </div>
773 </div>
774
774
775 <div id="main_filter_help" style="display: none">
775 <div id="main_filter_help" style="display: none">
776 - Use '/' key to quickly access this field.
776 - Use '/' key to quickly access this field.
777
777
778 - Enter a name of repository, or repository group for quick search.
778 - Enter a name of repository, or repository group for quick search.
779
779
780 - Prefix query to allow special search:
780 - Prefix query to allow special search:
781
781
782 user:admin, to search for usernames, always global
782 user:admin, to search for usernames, always global
783
783
784 user_group:devops, to search for user groups, always global
784 user_group:devops, to search for user groups, always global
785
785
786 pr:303, to search for pull request number, title, or description, always global
786 pr:303, to search for pull request number, title, or description, always global
787
787
788 commit:efced4, to search for commits, scoped to repositories or groups
788 commit:efced4, to search for commits, scoped to repositories or groups
789
789
790 file:models.py, to search for file paths, scoped to repositories or groups
790 file:models.py, to search for file paths, scoped to repositories or groups
791
791
792 % if c.template_context['search_context']['repo_id']:
792 % if c.template_context['search_context']['repo_id']:
793 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
793 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
794 % elif c.template_context['search_context']['repo_group_id']:
794 % elif c.template_context['search_context']['repo_group_id']:
795 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
795 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
796 % else:
796 % else:
797 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
797 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
798 % endif
798 % endif
799 </div>
799 </div>
800 </li>
800 </li>
801
801
802 ## ROOT MENU
802 ## ROOT MENU
803 <li class="${h.is_active('home', active)}">
803 <li class="${h.is_active('home', active)}">
804 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
804 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
805 <div class="menulabel">${_('Home')}</div>
805 <div class="menulabel">${_('Home')}</div>
806 </a>
806 </a>
807 </li>
807 </li>
808
808
809 %if c.rhodecode_user.username != h.DEFAULT_USER:
809 %if c.rhodecode_user.username != h.DEFAULT_USER:
810 <li class="${h.is_active('journal', active)}">
810 <li class="${h.is_active('journal', active)}">
811 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
811 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
812 <div class="menulabel">${_('Journal')}</div>
812 <div class="menulabel">${_('Journal')}</div>
813 </a>
813 </a>
814 </li>
814 </li>
815 %else:
815 %else:
816 <li class="${h.is_active('journal', active)}">
816 <li class="${h.is_active('journal', active)}">
817 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
817 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
818 <div class="menulabel">${_('Public journal')}</div>
818 <div class="menulabel">${_('Public journal')}</div>
819 </a>
819 </a>
820 </li>
820 </li>
821 %endif
821 %endif
822
822
823 <li class="${h.is_active('gists', active)}">
823 <li class="${h.is_active('gists', active)}">
824 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
824 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
825 <div class="menulabel">${_('Gists')}</div>
825 <div class="menulabel">${_('Gists')}</div>
826 </a>
826 </a>
827 </li>
827 </li>
828
828
829 % if c.is_super_admin or c.is_delegated_admin:
829 % if c.is_super_admin or c.is_delegated_admin:
830 <li class="${h.is_active('admin', active)}">
830 <li class="${h.is_active('admin', active)}">
831 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
831 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
832 <div class="menulabel">${_('Admin')} </div>
832 <div class="menulabel">${_('Admin')} </div>
833 </a>
833 </a>
834 </li>
834 </li>
835 % endif
835 % endif
836
836
837 ## render extra user menu
837 ## render extra user menu
838 ${usermenu(active=(active=='my_account'))}
838 ${usermenu(active=(active=='my_account'))}
839
839
840 </ul>
840 </ul>
841
841
842 <script type="text/javascript">
842 <script type="text/javascript">
843 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
843 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
844
844
845 var formatRepoResult = function(result, container, query, escapeMarkup) {
845 var formatRepoResult = function(result, container, query, escapeMarkup) {
846 return function(data, escapeMarkup) {
846 return function(data, escapeMarkup) {
847 if (!data.repo_id){
847 if (!data.repo_id){
848 return data.text; // optgroup text Repositories
848 return data.text; // optgroup text Repositories
849 }
849 }
850
850
851 var tmpl = '';
851 var tmpl = '';
852 var repoType = data['repo_type'];
852 var repoType = data['repo_type'];
853 var repoName = data['text'];
853 var repoName = data['text'];
854
854
855 if(data && data.type == 'repo'){
855 if(data && data.type == 'repo'){
856 if(repoType === 'hg'){
856 if(repoType === 'hg'){
857 tmpl += '<i class="icon-hg"></i> ';
857 tmpl += '<i class="icon-hg"></i> ';
858 }
858 }
859 else if(repoType === 'git'){
859 else if(repoType === 'git'){
860 tmpl += '<i class="icon-git"></i> ';
860 tmpl += '<i class="icon-git"></i> ';
861 }
861 }
862 else if(repoType === 'svn'){
862 else if(repoType === 'svn'){
863 tmpl += '<i class="icon-svn"></i> ';
863 tmpl += '<i class="icon-svn"></i> ';
864 }
864 }
865 if(data['private']){
865 if(data['private']){
866 tmpl += '<i class="icon-lock" ></i> ';
866 tmpl += '<i class="icon-lock" ></i> ';
867 }
867 }
868 else if(visualShowPublicIcon){
868 else if(visualShowPublicIcon){
869 tmpl += '<i class="icon-unlock-alt"></i> ';
869 tmpl += '<i class="icon-unlock-alt"></i> ';
870 }
870 }
871 }
871 }
872 tmpl += escapeMarkup(repoName);
872 tmpl += escapeMarkup(repoName);
873 return tmpl;
873 return tmpl;
874
874
875 }(result, escapeMarkup);
875 }(result, escapeMarkup);
876 };
876 };
877
877
878 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
878 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
879 return function(data, escapeMarkup) {
879 return function(data, escapeMarkup) {
880 if (!data.repo_group_id){
880 if (!data.repo_group_id){
881 return data.text; // optgroup text Repositories
881 return data.text; // optgroup text Repositories
882 }
882 }
883
883
884 var tmpl = '';
884 var tmpl = '';
885 var repoGroupName = data['text'];
885 var repoGroupName = data['text'];
886
886
887 if(data){
887 if(data){
888
888
889 tmpl += '<i class="icon-repo-group"></i> ';
889 tmpl += '<i class="icon-repo-group"></i> ';
890
890
891 }
891 }
892 tmpl += escapeMarkup(repoGroupName);
892 tmpl += escapeMarkup(repoGroupName);
893 return tmpl;
893 return tmpl;
894
894
895 }(result, escapeMarkup);
895 }(result, escapeMarkup);
896 };
896 };
897
897
898 var escapeRegExChars = function (value) {
898 var escapeRegExChars = function (value) {
899 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
899 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
900 };
900 };
901
901
902 var getRepoIcon = function(repo_type) {
902 var getRepoIcon = function(repo_type) {
903 if (repo_type === 'hg') {
903 if (repo_type === 'hg') {
904 return '<i class="icon-hg"></i> ';
904 return '<i class="icon-hg"></i> ';
905 }
905 }
906 else if (repo_type === 'git') {
906 else if (repo_type === 'git') {
907 return '<i class="icon-git"></i> ';
907 return '<i class="icon-git"></i> ';
908 }
908 }
909 else if (repo_type === 'svn') {
909 else if (repo_type === 'svn') {
910 return '<i class="icon-svn"></i> ';
910 return '<i class="icon-svn"></i> ';
911 }
911 }
912 return ''
912 return ''
913 };
913 };
914
914
915 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
915 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
916
916
917 if (value.split(':').length === 2) {
917 if (value.split(':').length === 2) {
918 value = value.split(':')[1]
918 value = value.split(':')[1]
919 }
919 }
920
920
921 var searchType = data['type'];
921 var searchType = data['type'];
922 var searchSubType = data['subtype'];
922 var searchSubType = data['subtype'];
923 var valueDisplay = data['value_display'];
923 var valueDisplay = data['value_display'];
924 var valueIcon = data['value_icon'];
924 var valueIcon = data['value_icon'];
925
925
926 var pattern = '(' + escapeRegExChars(value) + ')';
926 var pattern = '(' + escapeRegExChars(value) + ')';
927
927
928 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
928 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
929
929
930 // highlight match
930 // highlight match
931 if (searchType != 'text') {
931 if (searchType != 'text') {
932 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
932 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
933 }
933 }
934
934
935 var icon = '';
935 var icon = '';
936
936
937 if (searchType === 'hint') {
937 if (searchType === 'hint') {
938 icon += '<i class="icon-repo-group"></i> ';
938 icon += '<i class="icon-repo-group"></i> ';
939 }
939 }
940 // full text search/hints
940 // full text search/hints
941 else if (searchType === 'search') {
941 else if (searchType === 'search') {
942 if (valueIcon === undefined) {
942 if (valueIcon === undefined) {
943 icon += '<i class="icon-more"></i> ';
943 icon += '<i class="icon-more"></i> ';
944 } else {
944 } else {
945 icon += valueIcon + ' ';
945 icon += valueIcon + ' ';
946 }
946 }
947
947
948 if (searchSubType !== undefined && searchSubType == 'repo') {
948 if (searchSubType !== undefined && searchSubType == 'repo') {
949 valueDisplay += '<div class="pull-right tag">repository</div>';
949 valueDisplay += '<div class="pull-right tag">repository</div>';
950 }
950 }
951 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
951 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
952 valueDisplay += '<div class="pull-right tag">repo group</div>';
952 valueDisplay += '<div class="pull-right tag">repo group</div>';
953 }
953 }
954 }
954 }
955 // repository
955 // repository
956 else if (searchType === 'repo') {
956 else if (searchType === 'repo') {
957
957
958 var repoIcon = getRepoIcon(data['repo_type']);
958 var repoIcon = getRepoIcon(data['repo_type']);
959 icon += repoIcon;
959 icon += repoIcon;
960
960
961 if (data['private']) {
961 if (data['private']) {
962 icon += '<i class="icon-lock" ></i> ';
962 icon += '<i class="icon-lock" ></i> ';
963 }
963 }
964 else if (visualShowPublicIcon) {
964 else if (visualShowPublicIcon) {
965 icon += '<i class="icon-unlock-alt"></i> ';
965 icon += '<i class="icon-unlock-alt"></i> ';
966 }
966 }
967 }
967 }
968 // repository groups
968 // repository groups
969 else if (searchType === 'repo_group') {
969 else if (searchType === 'repo_group') {
970 icon += '<i class="icon-repo-group"></i> ';
970 icon += '<i class="icon-repo-group"></i> ';
971 }
971 }
972 // user group
972 // user group
973 else if (searchType === 'user_group') {
973 else if (searchType === 'user_group') {
974 icon += '<i class="icon-group"></i> ';
974 icon += '<i class="icon-group"></i> ';
975 }
975 }
976 // user
976 // user
977 else if (searchType === 'user') {
977 else if (searchType === 'user') {
978 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
978 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
979 }
979 }
980 // pull request
980 // pull request
981 else if (searchType === 'pull_request') {
981 else if (searchType === 'pull_request') {
982 icon += '<i class="icon-merge"></i> ';
982 icon += '<i class="icon-merge"></i> ';
983 }
983 }
984 // commit
984 // commit
985 else if (searchType === 'commit') {
985 else if (searchType === 'commit') {
986 var repo_data = data['repo_data'];
986 var repo_data = data['repo_data'];
987 var repoIcon = getRepoIcon(repo_data['repository_type']);
987 var repoIcon = getRepoIcon(repo_data['repository_type']);
988 if (repoIcon) {
988 if (repoIcon) {
989 icon += repoIcon;
989 icon += repoIcon;
990 } else {
990 } else {
991 icon += '<i class="icon-tag"></i>';
991 icon += '<i class="icon-tag"></i>';
992 }
992 }
993 }
993 }
994 // file
994 // file
995 else if (searchType === 'file') {
995 else if (searchType === 'file') {
996 var repo_data = data['repo_data'];
996 var repo_data = data['repo_data'];
997 var repoIcon = getRepoIcon(repo_data['repository_type']);
997 var repoIcon = getRepoIcon(repo_data['repository_type']);
998 if (repoIcon) {
998 if (repoIcon) {
999 icon += repoIcon;
999 icon += repoIcon;
1000 } else {
1000 } else {
1001 icon += '<i class="icon-tag"></i>';
1001 icon += '<i class="icon-tag"></i>';
1002 }
1002 }
1003 }
1003 }
1004 // generic text
1004 // generic text
1005 else if (searchType === 'text') {
1005 else if (searchType === 'text') {
1006 icon = '';
1006 icon = '';
1007 }
1007 }
1008
1008
1009 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1009 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1010 return tmpl.format(icon, valueDisplay);
1010 return tmpl.format(icon, valueDisplay);
1011 };
1011 };
1012
1012
1013 var handleSelect = function(element, suggestion) {
1013 var handleSelect = function(element, suggestion) {
1014 if (suggestion.type === "hint") {
1014 if (suggestion.type === "hint") {
1015 // we skip action
1015 // we skip action
1016 $('#main_filter').focus();
1016 $('#main_filter').focus();
1017 }
1017 }
1018 else if (suggestion.type === "text") {
1018 else if (suggestion.type === "text") {
1019 // we skip action
1019 // we skip action
1020 $('#main_filter').focus();
1020 $('#main_filter').focus();
1021
1021
1022 } else {
1022 } else {
1023 window.location = suggestion['url'];
1023 window.location = suggestion['url'];
1024 }
1024 }
1025 };
1025 };
1026
1026
1027 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1027 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1028 if (queryLowerCase.split(':').length === 2) {
1028 if (queryLowerCase.split(':').length === 2) {
1029 queryLowerCase = queryLowerCase.split(':')[1]
1029 queryLowerCase = queryLowerCase.split(':')[1]
1030 }
1030 }
1031 if (suggestion.type === "text") {
1031 if (suggestion.type === "text") {
1032 // special case we don't want to "skip" display for
1032 // special case we don't want to "skip" display for
1033 return true
1033 return true
1034 }
1034 }
1035 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1035 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1036 };
1036 };
1037
1037
1038 var cleanContext = {
1038 var cleanContext = {
1039 repo_view_type: null,
1039 repo_view_type: null,
1040
1040
1041 repo_id: null,
1041 repo_id: null,
1042 repo_name: "",
1042 repo_name: "",
1043
1043
1044 repo_group_id: null,
1044 repo_group_id: null,
1045 repo_group_name: null
1045 repo_group_name: null
1046 };
1046 };
1047 var removeGoToFilter = function () {
1047 var removeGoToFilter = function () {
1048 $('.searchTagHidable').hide();
1048 $('.searchTagHidable').hide();
1049 $('#main_filter').autocomplete(
1049 $('#main_filter').autocomplete(
1050 'setOptions', {params:{search_context: cleanContext}});
1050 'setOptions', {params:{search_context: cleanContext}});
1051 };
1051 };
1052
1052
1053 $('#main_filter').autocomplete({
1053 $('#main_filter').autocomplete({
1054 serviceUrl: pyroutes.url('goto_switcher_data'),
1054 serviceUrl: pyroutes.url('goto_switcher_data'),
1055 params: {
1055 params: {
1056 "search_context": templateContext.search_context
1056 "search_context": templateContext.search_context
1057 },
1057 },
1058 minChars:2,
1058 minChars:2,
1059 maxHeight:400,
1059 maxHeight:400,
1060 deferRequestBy: 300, //miliseconds
1060 deferRequestBy: 300, //miliseconds
1061 tabDisabled: true,
1061 tabDisabled: true,
1062 autoSelectFirst: false,
1062 autoSelectFirst: false,
1063 containerClass: 'autocomplete-qfilter-suggestions',
1063 containerClass: 'autocomplete-qfilter-suggestions',
1064 formatResult: autocompleteMainFilterFormatResult,
1064 formatResult: autocompleteMainFilterFormatResult,
1065 lookupFilter: autocompleteMainFilterResult,
1065 lookupFilter: autocompleteMainFilterResult,
1066 onSelect: function (element, suggestion) {
1066 onSelect: function (element, suggestion) {
1067 handleSelect(element, suggestion);
1067 handleSelect(element, suggestion);
1068 return false;
1068 return false;
1069 },
1069 },
1070 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1070 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1071 if (jqXHR !== 'abort') {
1071 if (jqXHR !== 'abort') {
1072 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1072 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1073 SwalNoAnimation.fire({
1073 SwalNoAnimation.fire({
1074 icon: 'error',
1074 icon: 'error',
1075 title: _gettext('Error during search operation'),
1075 title: _gettext('Error during search operation'),
1076 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1076 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1077 }).then(function(result) {
1077 }).then(function(result) {
1078 window.location.reload();
1078 window.location.reload();
1079 })
1079 })
1080 }
1080 }
1081 },
1081 },
1082 onSearchStart: function (params) {
1082 onSearchStart: function (params) {
1083 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1083 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1084 },
1084 },
1085 onSearchComplete: function (query, suggestions) {
1085 onSearchComplete: function (query, suggestions) {
1086 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1086 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1087 },
1087 },
1088 });
1088 });
1089
1089
1090 showMainFilterBox = function () {
1090 showMainFilterBox = function () {
1091 $('#main_filter_help').toggle();
1091 $('#main_filter_help').toggle();
1092 };
1092 };
1093
1093
1094 $('#main_filter').on('keydown.autocomplete', function (e) {
1094 $('#main_filter').on('keydown.autocomplete', function (e) {
1095
1095
1096 var BACKSPACE = 8;
1096 var BACKSPACE = 8;
1097 var el = $(e.currentTarget);
1097 var el = $(e.currentTarget);
1098 if(e.which === BACKSPACE){
1098 if(e.which === BACKSPACE){
1099 var inputVal = el.val();
1099 var inputVal = el.val();
1100 if (inputVal === ""){
1100 if (inputVal === ""){
1101 removeGoToFilter()
1101 removeGoToFilter()
1102 }
1102 }
1103 }
1103 }
1104 });
1104 });
1105
1105
1106 var dismissNotice = function(noticeId) {
1106 var dismissNotice = function(noticeId) {
1107
1107
1108 var url = pyroutes.url('user_notice_dismiss',
1108 var url = pyroutes.url('user_notice_dismiss',
1109 {"user_id": templateContext.rhodecode_user.user_id});
1109 {"user_id": templateContext.rhodecode_user.user_id});
1110
1110
1111 var postData = {
1111 var postData = {
1112 'csrf_token': CSRF_TOKEN,
1112 'csrf_token': CSRF_TOKEN,
1113 'notice_id': noticeId,
1113 'notice_id': noticeId,
1114 };
1114 };
1115
1115
1116 var success = function(response) {
1116 var success = function(response) {
1117 $('#notice-message-' + noticeId).remove();
1117 $('#notice-message-' + noticeId).remove();
1118 return false;
1118 return false;
1119 };
1119 };
1120 var failure = function(data, textStatus, xhr) {
1120 var failure = function(data, textStatus, xhr) {
1121 alert("error processing request: " + textStatus);
1121 alert("error processing request: " + textStatus);
1122 return false;
1122 return false;
1123 };
1123 };
1124 ajaxPOST(url, postData, success, failure);
1124 ajaxPOST(url, postData, success, failure);
1125 }
1125 }
1126 </script>
1126 </script>
1127 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1127 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1128 </%def>
1128 </%def>
1129
1129
1130 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1130 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1131 <div class="modal-dialog">
1131 <div class="modal-dialog">
1132 <div class="modal-content">
1132 <div class="modal-content">
1133 <div class="modal-header">
1133 <div class="modal-header">
1134 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1134 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1135 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1135 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1136 </div>
1136 </div>
1137 <div class="modal-body">
1137 <div class="modal-body">
1138 <div class="block-left">
1138 <div class="block-left">
1139 <table class="keyboard-mappings">
1139 <table class="keyboard-mappings">
1140 <tbody>
1140 <tbody>
1141 <tr>
1141 <tr>
1142 <th></th>
1142 <th></th>
1143 <th>${_('Site-wide shortcuts')}</th>
1143 <th>${_('Site-wide shortcuts')}</th>
1144 </tr>
1144 </tr>
1145 <%
1145 <%
1146 elems = [
1146 elems = [
1147 ('/', 'Use quick search box'),
1147 ('/', 'Use quick search box'),
1148 ('g h', 'Goto home page'),
1148 ('g h', 'Goto home page'),
1149 ('g g', 'Goto my private gists page'),
1149 ('g g', 'Goto my private gists page'),
1150 ('g G', 'Goto my public gists page'),
1150 ('g G', 'Goto my public gists page'),
1151 ('g 0-9', 'Goto bookmarked items from 0-9'),
1151 ('g 0-9', 'Goto bookmarked items from 0-9'),
1152 ('n r', 'New repository page'),
1152 ('n r', 'New repository page'),
1153 ('n g', 'New gist page'),
1153 ('n g', 'New gist page'),
1154 ]
1154 ]
1155 %>
1155 %>
1156 %for key, desc in elems:
1156 %for key, desc in elems:
1157 <tr>
1157 <tr>
1158 <td class="keys">
1158 <td class="keys">
1159 <span class="key tag">${key}</span>
1159 <span class="key tag">${key}</span>
1160 </td>
1160 </td>
1161 <td>${desc}</td>
1161 <td>${desc}</td>
1162 </tr>
1162 </tr>
1163 %endfor
1163 %endfor
1164 </tbody>
1164 </tbody>
1165 </table>
1165 </table>
1166 </div>
1166 </div>
1167 <div class="block-left">
1167 <div class="block-left">
1168 <table class="keyboard-mappings">
1168 <table class="keyboard-mappings">
1169 <tbody>
1169 <tbody>
1170 <tr>
1170 <tr>
1171 <th></th>
1171 <th></th>
1172 <th>${_('Repositories')}</th>
1172 <th>${_('Repositories')}</th>
1173 </tr>
1173 </tr>
1174 <%
1174 <%
1175 elems = [
1175 elems = [
1176 ('g s', 'Goto summary page'),
1176 ('g s', 'Goto summary page'),
1177 ('g c', 'Goto changelog page'),
1177 ('g c', 'Goto changelog page'),
1178 ('g f', 'Goto files page'),
1178 ('g f', 'Goto files page'),
1179 ('g F', 'Goto files page with file search activated'),
1179 ('g F', 'Goto files page with file search activated'),
1180 ('g p', 'Goto pull requests page'),
1180 ('g p', 'Goto pull requests page'),
1181 ('g o', 'Goto repository settings'),
1181 ('g o', 'Goto repository settings'),
1182 ('g O', 'Goto repository access permissions settings'),
1182 ('g O', 'Goto repository access permissions settings'),
1183 ]
1183 ]
1184 %>
1184 %>
1185 %for key, desc in elems:
1185 %for key, desc in elems:
1186 <tr>
1186 <tr>
1187 <td class="keys">
1187 <td class="keys">
1188 <span class="key tag">${key}</span>
1188 <span class="key tag">${key}</span>
1189 </td>
1189 </td>
1190 <td>${desc}</td>
1190 <td>${desc}</td>
1191 </tr>
1191 </tr>
1192 %endfor
1192 %endfor
1193 </tbody>
1193 </tbody>
1194 </table>
1194 </table>
1195 </div>
1195 </div>
1196 </div>
1196 </div>
1197 <div class="modal-footer">
1197 <div class="modal-footer">
1198 </div>
1198 </div>
1199 </div><!-- /.modal-content -->
1199 </div><!-- /.modal-content -->
1200 </div><!-- /.modal-dialog -->
1200 </div><!-- /.modal-dialog -->
1201 </div><!-- /.modal -->
1201 </div><!-- /.modal -->
@@ -1,166 +1,166 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html>
2 <!DOCTYPE html>
3
3
4 <%
4 <%
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
6 go_import_header = ''
6 go_import_header = ''
7 if hasattr(c, 'rhodecode_db_repo'):
7 if hasattr(c, 'rhodecode_db_repo'):
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_ref_name
10 c.template_context['repo_id'] = c.rhodecode_db_repo.repo_id
10 c.template_context['repo_id'] = c.rhodecode_db_repo.repo_id
11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
11 c.template_context['repo_view_type'] = h.get_repo_view_type(request)
12
12
13 if getattr(c, 'repo_group', None):
13 if getattr(c, 'repo_group', None):
14 c.template_context['repo_group_id'] = c.repo_group.group_id
14 c.template_context['repo_group_id'] = c.repo_group.group_id
15 c.template_context['repo_group_name'] = c.repo_group.group_name
15 c.template_context['repo_group_name'] = c.repo_group.group_name
16
16
17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
17 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
18 c.template_context['rhodecode_user']['user_id'] = c.rhodecode_user.user_id
18 c.template_context['rhodecode_user']['user_id'] = c.rhodecode_user.user_id
19 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
19 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
20 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
20 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
21 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
21 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
22 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
22 c.template_context['rhodecode_user']['first_name'] = c.rhodecode_user.first_name
23 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
23 c.template_context['rhodecode_user']['last_name'] = c.rhodecode_user.last_name
24
24
25 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
25 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
26 c.template_context['default_user'] = {
26 c.template_context['default_user'] = {
27 'username': h.DEFAULT_USER,
27 'username': h.DEFAULT_USER,
28 'user_id': 1
28 'user_id': 1
29 }
29 }
30 c.template_context['search_context'] = {
30 c.template_context['search_context'] = {
31 'repo_group_id': c.template_context.get('repo_group_id'),
31 'repo_group_id': c.template_context.get('repo_group_id'),
32 'repo_group_name': c.template_context.get('repo_group_name'),
32 'repo_group_name': c.template_context.get('repo_group_name'),
33 'repo_id': c.template_context.get('repo_id'),
33 'repo_id': c.template_context.get('repo_id'),
34 'repo_name': c.template_context.get('repo_name'),
34 'repo_name': c.template_context.get('repo_name'),
35 'repo_view_type': c.template_context.get('repo_view_type'),
35 'repo_view_type': c.template_context.get('repo_view_type'),
36 }
36 }
37
37
38 c.template_context['attachment_store'] = {
38 c.template_context['attachment_store'] = {
39 'max_file_size_mb': 10,
39 'max_file_size_mb': 10,
40 'image_ext': ["png", "jpg", "gif", "jpeg"]
40 'image_ext': ["png", "jpg", "gif", "jpeg"]
41 }
41 }
42
42
43 %>
43 %>
44 <html xmlns="http://www.w3.org/1999/xhtml">
44 <html xmlns="http://www.w3.org/1999/xhtml">
45 <head>
45 <head>
46 <title>${self.title()}</title>
46 <title>${self.title()}</title>
47 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
47 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
48
48
49 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
49 ${h.go_import_header(request, getattr(c, 'rhodecode_db_repo', None))}
50
50
51 % if 'safari' in (request.user_agent or '').lower():
51 % if 'safari' in (request.user_agent or '').lower():
52 <meta name="referrer" content="origin">
52 <meta name="referrer" content="origin">
53 % else:
53 % else:
54 <meta name="referrer" content="origin-when-cross-origin">
54 <meta name="referrer" content="origin-when-cross-origin">
55 % endif
55 % endif
56
56
57 <%def name="robots()">
57 <%def name="robots()">
58 <meta name="robots" content="index, nofollow"/>
58 <meta name="robots" content="index, nofollow"/>
59 </%def>
59 </%def>
60 ${self.robots()}
60 ${self.robots()}
61 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
61 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
62 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
62 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
63 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
63 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
64
64
65 ## CSS definitions
65 ## CSS definitions
66 <%def name="css()">
66 <%def name="css()">
67 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
67 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
68 ## EXTRA FOR CSS
68 ## EXTRA FOR CSS
69 ${self.css_extra()}
69 ${self.css_extra()}
70 </%def>
70 </%def>
71 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
71 ## CSS EXTRA - optionally inject some extra CSS stuff needed for specific websites
72 <%def name="css_extra()">
72 <%def name="css_extra()">
73 </%def>
73 </%def>
74
74
75 ${self.css()}
75 ${self.css()}
76
76
77 ## JAVASCRIPT
77 ## JAVASCRIPT
78 <%def name="js()">
78 <%def name="js()">
79
79
80 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
80 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
81 <script type="text/javascript">
81 <script type="text/javascript">
82 // register templateContext to pass template variables to JS
82 // register templateContext to pass template variables to JS
83 var templateContext = ${h.json.dumps(c.template_context)|n};
83 var templateContext = ${h.json.dumps(c.template_context)|n};
84
84
85 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
85 var APPLICATION_URL = "${h.route_path('home').rstrip('/')}";
86 var APPLICATION_PLUGINS = [];
86 var APPLICATION_PLUGINS = [];
87 var ASSET_URL = "${h.asset('')}";
87 var ASSET_URL = "${h.asset('')}";
88 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
88 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
89 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
89 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
90
90
91 var APPENLIGHT = {
91 var APPENLIGHT = {
92 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
92 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
93 key: '${getattr(c, "appenlight_api_public_key", "")}',
93 key: '${getattr(c, "appenlight_api_public_key", "")}',
94 % if getattr(c, 'appenlight_server_url', None):
94 % if getattr(c, 'appenlight_server_url', None):
95 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
95 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
96 % endif
96 % endif
97 requestInfo: {
97 requestInfo: {
98 % if getattr(c, 'rhodecode_user', None):
98 % if getattr(c, 'rhodecode_user', None):
99 ip: '${c.rhodecode_user.ip_addr}',
99 ip: '${c.rhodecode_user.ip_addr}',
100 username: '${c.rhodecode_user.username}'
100 username: '${c.rhodecode_user.username}'
101 % endif
101 % endif
102 },
102 },
103 tags: {
103 tags: {
104 rhodecode_version: '${c.rhodecode_version}',
104 rhodecode_version: '${c.rhodecode_version}',
105 rhodecode_edition: '${c.rhodecode_edition}'
105 rhodecode_edition: '${c.rhodecode_edition}'
106 }
106 }
107 };
107 };
108
108
109 </script>
109 </script>
110 <%include file="/base/plugins_base.mako"/>
110 <%include file="/base/plugins_base.mako"/>
111 <!--[if lt IE 9]>
111 <!--[if lt IE 9]>
112 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
112 <script language="javascript" type="text/javascript" src="${h.asset('js/src/excanvas.min.js')}"></script>
113 <![endif]-->
113 <![endif]-->
114 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
114 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
115 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
115 <script> var alertMessagePayloads = ${h.flash.json_alerts(request=request)|n}; </script>
116 ## avoide escaping the %N
116 ## avoide escaping the %N
117 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.min.js', ver=c.rhodecode_version_hash)}"></script>
117 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.min.js', ver=c.rhodecode_version_hash)}"></script>
118 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
118 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
119
119
120
120
121 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
121 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
122 ${self.js_extra()}
122 ${self.js_extra()}
123
123
124 <script type="text/javascript">
124 <script type="text/javascript">
125 Rhodecode = (function() {
125 Rhodecode = (function() {
126 function _Rhodecode() {
126 function _Rhodecode() {
127 this.comments = new CommentsController();
127 this.comments = new CommentsController();
128 }
128 }
129 return new _Rhodecode();
129 return new _Rhodecode();
130 })();
130 })();
131
131
132 $(document).ready(function(){
132 $(document).ready(function(){
133 show_more_event();
133 show_more_event();
134 timeagoActivate();
134 timeagoActivate();
135 tooltipActivate();
135 tooltipActivate();
136 clipboardActivate();
136 clipboardActivate();
137 })
137 })
138 </script>
138 </script>
139
139
140 </%def>
140 </%def>
141
141
142 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
142 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
143 <%def name="js_extra()"></%def>
143 <%def name="js_extra()"></%def>
144 ${self.js()}
144 ${self.js()}
145
145
146 <%def name="head_extra()"></%def>
146 <%def name="head_extra()"></%def>
147 ${self.head_extra()}
147 ${self.head_extra()}
148 ## extra stuff
148 ## extra stuff
149 %if c.pre_code:
149 %if c.pre_code:
150 ${c.pre_code|n}
150 ${c.pre_code|n}
151 %endif
151 %endif
152 </head>
152 </head>
153 <body id="body">
153 <body id="body">
154 <noscript>
154 <noscript>
155 <div class="noscript-error">
155 <div class="noscript-error">
156 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
156 ${_('Please enable JavaScript to use RhodeCode Enterprise')}
157 </div>
157 </div>
158 </noscript>
158 </noscript>
159
159
160 ${next.body()}
160 ${next.body()}
161 %if c.post_code:
161 %if c.post_code:
162 ${c.post_code|n}
162 ${c.post_code|n}
163 %endif
163 %endif
164 <rhodecode-app></rhodecode-app>
164 <rhodecode-app></rhodecode-app>
165 </body>
165 </body>
166 </html>
166 </html>
@@ -1,326 +1,326 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name}
6 ${_('%s Changelog') % c.repo_name}
7 %if c.changelog_for_path:
7 %if c.changelog_for_path:
8 /${c.changelog_for_path}
8 /${c.changelog_for_path}
9 %endif
9 %endif
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="breadcrumbs_links()">
15 <%def name="breadcrumbs_links()">
16 %if c.changelog_for_path:
16 %if c.changelog_for_path:
17 /${c.changelog_for_path}
17 /${c.changelog_for_path}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='commits')}
26 ${self.repo_menu(active='commits')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30
30
31 <div class="box">
31 <div class="box">
32
32
33 <div class="title">
33 <div class="title">
34 <div id="filter_changelog">
34 <div id="filter_changelog">
35 ${h.hidden('branch_filter')}
35 ${h.hidden('branch_filter')}
36 %if c.selected_name:
36 %if c.selected_name:
37 <div class="btn btn-default" id="clear_filter" >
37 <div class="btn btn-default" id="clear_filter" >
38 ${_('Clear filter')}
38 ${_('Clear filter')}
39 </div>
39 </div>
40 %endif
40 %endif
41 </div>
41 </div>
42 <div class="pull-left obsolete-toggle">
42 <div class="pull-left obsolete-toggle">
43 % if h.is_hg(c.rhodecode_repo):
43 % if h.is_hg(c.rhodecode_repo):
44 % if c.show_hidden:
44 % if c.show_hidden:
45 <a class="action-link" href="${h.current_route_path(request, evolve=0)}">${_('Hide obsolete/hidden')}</a>
45 <a class="action-link" href="${h.current_route_path(request, evolve=0)}">${_('Hide obsolete/hidden')}</a>
46 % else:
46 % else:
47 <a class="action-link" href="${h.current_route_path(request, evolve=1)}">${_('Show obsolete/hidden')}</a>
47 <a class="action-link" href="${h.current_route_path(request, evolve=1)}">${_('Show obsolete/hidden')}</a>
48 % endif
48 % endif
49 % else:
49 % else:
50 <span class="action-link disabled">${_('Show hidden')}</span>
50 <span class="action-link disabled">${_('Show hidden')}</span>
51 % endif
51 % endif
52 </div>
52 </div>
53 <ul class="links">
53 <ul class="links">
54 <li>
54 <li>
55
55
56 %if c.rhodecode_db_repo.fork:
56 %if c.rhodecode_db_repo.fork:
57 <span>
57 <span>
58 <a id="compare_fork_button"
58 <a id="compare_fork_button"
59 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
59 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
60 class="btn btn-small"
60 class="btn btn-small"
61 href="${h.route_path('repo_compare',
61 href="${h.route_path('repo_compare',
62 repo_name=c.rhodecode_db_repo.fork.repo_name,
62 repo_name=c.rhodecode_db_repo.fork.repo_name,
63 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
63 source_ref_type=c.rhodecode_db_repo.landing_ref_type,
64 source_ref=c.rhodecode_db_repo.landing_rev[1],
64 source_ref=c.rhodecode_db_repo.landing_ref_name,
65 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
65 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_ref_type,
66 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
66 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_ref_name,
67 _query=dict(merge=1, target_repo=c.repo_name))}"
67 _query=dict(merge=1, target_repo=c.repo_name))}"
68 >
68 >
69 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
69 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
70 </a>
70 </a>
71 </span>
71 </span>
72 %endif
72 %endif
73
73
74 ## pr open link
74 ## pr open link
75 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
75 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
76 <span>
76 <span>
77 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
77 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
78 ${_('Open new pull request')}
78 ${_('Open new pull request')}
79 </a>
79 </a>
80 </span>
80 </span>
81 %endif
81 %endif
82
82
83 </li>
83 </li>
84 </ul>
84 </ul>
85 </div>
85 </div>
86
86
87 % if c.pagination:
87 % if c.pagination:
88 <script type="text/javascript" src="${h.asset('js/src/plugins/jquery.commits-graph.js')}"></script>
88 <script type="text/javascript" src="${h.asset('js/src/plugins/jquery.commits-graph.js')}"></script>
89
89
90 <div class="graph-header">
90 <div class="graph-header">
91 ${self.breadcrumbs('breadcrumbs_light')}
91 ${self.breadcrumbs('breadcrumbs_light')}
92 </div>
92 </div>
93
93
94 <div id="graph">
94 <div id="graph">
95 <div class="graph-col-wrapper">
95 <div class="graph-col-wrapper">
96 <div id="graph_nodes">
96 <div id="graph_nodes">
97 <div id="graph_canvas"></div>
97 <div id="graph_canvas"></div>
98 </div>
98 </div>
99 <div id="graph_content" class="graph_full_width">
99 <div id="graph_content" class="graph_full_width">
100
100
101 <div class="table">
101 <div class="table">
102 <table id="changesets" class="rctable">
102 <table id="changesets" class="rctable">
103 <tr>
103 <tr>
104 ## checkbox
104 ## checkbox
105 <th colspan="4">
105 <th colspan="4">
106 ## clear selection
106 ## clear selection
107 <div title="${_('Clear selection')}" class="btn btn-sm" id="rev_range_clear" style="display:none">
107 <div title="${_('Clear selection')}" class="btn btn-sm" id="rev_range_clear" style="display:none">
108 <i class="icon-cancel-circled2"></i>
108 <i class="icon-cancel-circled2"></i>
109 </div>
109 </div>
110 <div class="btn btn-sm disabled" disabled="disabled" id="rev_range_more" style="display:none;">${_('Select second commit')}</div>
110 <div class="btn btn-sm disabled" disabled="disabled" id="rev_range_more" style="display:none;">${_('Select second commit')}</div>
111 <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a>
111 <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a>
112 </th>
112 </th>
113
113
114 ## commit message expand arrow
114 ## commit message expand arrow
115 <th></th>
115 <th></th>
116 <th>${_('Commit Message')}</th>
116 <th>${_('Commit Message')}</th>
117
117
118 <th>${_('Age')}</th>
118 <th>${_('Age')}</th>
119 <th>${_('Author')}</th>
119 <th>${_('Author')}</th>
120
120
121 <th>${_('Refs')}</th>
121 <th>${_('Refs')}</th>
122 ## comments
122 ## comments
123 <th></th>
123 <th></th>
124 </tr>
124 </tr>
125
125
126 <tbody class="commits-range">
126 <tbody class="commits-range">
127 <%include file='changelog_elements.mako'/>
127 <%include file='changelog_elements.mako'/>
128 </tbody>
128 </tbody>
129 </table>
129 </table>
130 </div>
130 </div>
131 </div>
131 </div>
132 <div class="pagination-wh pagination-left">
132 <div class="pagination-wh pagination-left">
133 ${c.pagination.render()}
133 ${c.pagination.render()}
134 </div>
134 </div>
135 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
135 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
136 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
136 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
137 </div>
137 </div>
138 </div>
138 </div>
139
139
140 <script type="text/javascript">
140 <script type="text/javascript">
141 var cache = {};
141 var cache = {};
142 $(function(){
142 $(function(){
143
143
144 // Create links to commit ranges when range checkboxes are selected
144 // Create links to commit ranges when range checkboxes are selected
145 var $commitCheckboxes = $('.commit-range');
145 var $commitCheckboxes = $('.commit-range');
146 // cache elements
146 // cache elements
147 var $commitRangeMore = $('#rev_range_more');
147 var $commitRangeMore = $('#rev_range_more');
148 var $commitRangeContainer = $('#rev_range_container');
148 var $commitRangeContainer = $('#rev_range_container');
149 var $commitRangeClear = $('#rev_range_clear');
149 var $commitRangeClear = $('#rev_range_clear');
150
150
151 var checkboxRangeSelector = function(e){
151 var checkboxRangeSelector = function(e){
152 var selectedCheckboxes = [];
152 var selectedCheckboxes = [];
153 for (pos in $commitCheckboxes){
153 for (pos in $commitCheckboxes){
154 if($commitCheckboxes[pos].checked){
154 if($commitCheckboxes[pos].checked){
155 selectedCheckboxes.push($commitCheckboxes[pos]);
155 selectedCheckboxes.push($commitCheckboxes[pos]);
156 }
156 }
157 }
157 }
158 var open_new_pull_request = $('#open_new_pull_request');
158 var open_new_pull_request = $('#open_new_pull_request');
159
159
160 if (open_new_pull_request) {
160 if (open_new_pull_request) {
161 var selected_changes = selectedCheckboxes.length;
161 var selected_changes = selectedCheckboxes.length;
162 open_new_pull_request.hide();
162 open_new_pull_request.hide();
163 if (selected_changes == 1) {
163 if (selected_changes == 1) {
164 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
164 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
165 } else {
165 } else {
166 open_new_pull_request.html(_gettext('Open new pull request'));
166 open_new_pull_request.html(_gettext('Open new pull request'));
167 }
167 }
168 open_new_pull_request.show();
168 open_new_pull_request.show();
169 }
169 }
170
170
171 if (selectedCheckboxes.length > 0) {
171 if (selectedCheckboxes.length > 0) {
172 $('#compare_fork_button').hide();
172 $('#compare_fork_button').hide();
173 var commitStart = $(selectedCheckboxes[selectedCheckboxes.length-1]).data();
173 var commitStart = $(selectedCheckboxes[selectedCheckboxes.length-1]).data();
174
174
175 var revStart = commitStart.commitId;
175 var revStart = commitStart.commitId;
176
176
177 var commitEnd = $(selectedCheckboxes[0]).data();
177 var commitEnd = $(selectedCheckboxes[0]).data();
178 var revEnd = commitEnd.commitId;
178 var revEnd = commitEnd.commitId;
179
179
180 var lbl_start = '{0}'.format(commitStart.commitIdx, commitStart.shortId);
180 var lbl_start = '{0}'.format(commitStart.commitIdx, commitStart.shortId);
181 var lbl_end = '{0}'.format(commitEnd.commitIdx, commitEnd.shortId);
181 var lbl_end = '{0}'.format(commitEnd.commitIdx, commitEnd.shortId);
182
182
183 var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd});
183 var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd});
184 var link = _gettext('Show commit range {0} ... {1}').format(lbl_start, lbl_end);
184 var link = _gettext('Show commit range {0} ... {1}').format(lbl_start, lbl_end);
185
185
186 if (selectedCheckboxes.length > 1) {
186 if (selectedCheckboxes.length > 1) {
187 $commitRangeClear.show();
187 $commitRangeClear.show();
188 $commitRangeMore.hide();
188 $commitRangeMore.hide();
189
189
190 $commitRangeContainer
190 $commitRangeContainer
191 .attr('href',url)
191 .attr('href',url)
192 .html(link)
192 .html(link)
193 .show();
193 .show();
194
194
195
195
196 } else {
196 } else {
197 $commitRangeContainer.hide();
197 $commitRangeContainer.hide();
198 $commitRangeClear.show();
198 $commitRangeClear.show();
199 $commitRangeMore.show();
199 $commitRangeMore.show();
200 }
200 }
201
201
202 // pull-request link
202 // pull-request link
203 if (selectedCheckboxes.length == 1){
203 if (selectedCheckboxes.length == 1){
204 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'commit': revEnd});
204 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'commit': revEnd});
205 open_new_pull_request.attr('href', _url);
205 open_new_pull_request.attr('href', _url);
206 } else {
206 } else {
207 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
207 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
208 open_new_pull_request.attr('href', _url);
208 open_new_pull_request.attr('href', _url);
209 }
209 }
210
210
211 } else {
211 } else {
212 $commitRangeContainer.hide();
212 $commitRangeContainer.hide();
213 $commitRangeClear.hide();
213 $commitRangeClear.hide();
214 $commitRangeMore.hide();
214 $commitRangeMore.hide();
215
215
216 %if c.branch_name:
216 %if c.branch_name:
217 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'branch':'${c.branch_name}'});
217 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'branch':'${c.branch_name}'});
218 open_new_pull_request.attr('href', _url);
218 open_new_pull_request.attr('href', _url);
219 %else:
219 %else:
220 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
220 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
221 open_new_pull_request.attr('href', _url);
221 open_new_pull_request.attr('href', _url);
222 %endif
222 %endif
223 $('#compare_fork_button').show();
223 $('#compare_fork_button').show();
224 }
224 }
225 };
225 };
226
226
227 $commitCheckboxes.on('click', checkboxRangeSelector);
227 $commitCheckboxes.on('click', checkboxRangeSelector);
228
228
229 $commitRangeClear.on('click',function(e) {
229 $commitRangeClear.on('click',function(e) {
230 $commitCheckboxes.attr('checked', false);
230 $commitCheckboxes.attr('checked', false);
231 checkboxRangeSelector();
231 checkboxRangeSelector();
232 e.preventDefault();
232 e.preventDefault();
233 });
233 });
234
234
235 // make sure the buttons are consistent when navigate back and forth
235 // make sure the buttons are consistent when navigate back and forth
236 checkboxRangeSelector();
236 checkboxRangeSelector();
237
237
238 var msgs = $('.message');
238 var msgs = $('.message');
239 // get first element height
239 // get first element height
240 var el = $('#graph_content .container')[0];
240 var el = $('#graph_content .container')[0];
241 var row_h = el.clientHeight;
241 var row_h = el.clientHeight;
242 for (var i=0; i < msgs.length; i++) {
242 for (var i=0; i < msgs.length; i++) {
243 var m = msgs[i];
243 var m = msgs[i];
244
244
245 var h = m.clientHeight;
245 var h = m.clientHeight;
246 var pad = $(m).css('padding');
246 var pad = $(m).css('padding');
247 if (h > row_h) {
247 if (h > row_h) {
248 var offset = row_h - (h+12);
248 var offset = row_h - (h+12);
249 $(m.nextElementSibling).css('display','block');
249 $(m.nextElementSibling).css('display','block');
250 $(m.nextElementSibling).css('margin-top',offset+'px');
250 $(m.nextElementSibling).css('margin-top',offset+'px');
251 }
251 }
252 }
252 }
253
253
254 $("#clear_filter").on("click", function() {
254 $("#clear_filter").on("click", function() {
255 var filter = {'repo_name': '${c.repo_name}'};
255 var filter = {'repo_name': '${c.repo_name}'};
256 window.location = pyroutes.url('repo_commits', filter);
256 window.location = pyroutes.url('repo_commits', filter);
257 });
257 });
258
258
259 $("#branch_filter").select2({
259 $("#branch_filter").select2({
260 'dropdownAutoWidth': true,
260 'dropdownAutoWidth': true,
261 'width': 'resolve',
261 'width': 'resolve',
262 'placeholder': "${c.selected_name or _('Branch filter')}",
262 'placeholder': "${c.selected_name or _('Branch filter')}",
263 containerCssClass: "drop-menu",
263 containerCssClass: "drop-menu",
264 dropdownCssClass: "drop-menu-dropdown",
264 dropdownCssClass: "drop-menu-dropdown",
265 query: function(query){
265 query: function(query){
266 var key = 'cache';
266 var key = 'cache';
267 var cached = cache[key] ;
267 var cached = cache[key] ;
268 if(cached) {
268 if(cached) {
269 var data = {results: []};
269 var data = {results: []};
270 //filter results
270 //filter results
271 $.each(cached.results, function(){
271 $.each(cached.results, function(){
272 var section = this.text;
272 var section = this.text;
273 var children = [];
273 var children = [];
274 $.each(this.children, function(){
274 $.each(this.children, function(){
275 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
275 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
276 children.push({'id': this.id, 'text': this.text, 'type': this.type})
276 children.push({'id': this.id, 'text': this.text, 'type': this.type})
277 }
277 }
278 });
278 });
279 data.results.push({'text': section, 'children': children});
279 data.results.push({'text': section, 'children': children});
280 query.callback({results: data.results});
280 query.callback({results: data.results});
281 });
281 });
282 }else{
282 }else{
283 $.ajax({
283 $.ajax({
284 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
284 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
285 data: {},
285 data: {},
286 dataType: 'json',
286 dataType: 'json',
287 type: 'GET',
287 type: 'GET',
288 success: function(data) {
288 success: function(data) {
289 cache[key] = data;
289 cache[key] = data;
290 query.callback({results: data.results});
290 query.callback({results: data.results});
291 }
291 }
292 })
292 })
293 }
293 }
294 }
294 }
295 });
295 });
296 $('#branch_filter').on('change', function(e){
296 $('#branch_filter').on('change', function(e){
297 var data = $('#branch_filter').select2('data');
297 var data = $('#branch_filter').select2('data');
298 //type: branch_closed
298 //type: branch_closed
299 var selected = data.text;
299 var selected = data.text;
300 var filter = {'repo_name': '${c.repo_name}'};
300 var filter = {'repo_name': '${c.repo_name}'};
301 if(data.type == 'branch' || data.type == 'branch_closed'){
301 if(data.type == 'branch' || data.type == 'branch_closed'){
302 filter["branch"] = selected;
302 filter["branch"] = selected;
303 if (data.type == 'branch_closed') {
303 if (data.type == 'branch_closed') {
304 filter["evolve"] = '1';
304 filter["evolve"] = '1';
305 }
305 }
306 }
306 }
307 else if (data.type == 'book'){
307 else if (data.type == 'book'){
308 filter["bookmark"] = selected;
308 filter["bookmark"] = selected;
309 }
309 }
310 window.location = pyroutes.url('repo_commits', filter);
310 window.location = pyroutes.url('repo_commits', filter);
311 });
311 });
312
312
313 commitsController = new CommitsController();
313 commitsController = new CommitsController();
314 % if not c.changelog_for_path:
314 % if not c.changelog_for_path:
315 commitsController.reloadGraph();
315 commitsController.reloadGraph();
316 % endif
316 % endif
317
317
318 });
318 });
319
319
320 </script>
320 </script>
321 </div>
321 </div>
322 % else:
322 % else:
323 ${_('There are no changes yet')}
323 ${_('There are no changes yet')}
324 % endif
324 % endif
325 </div>
325 </div>
326 </%def>
326 </%def>
@@ -1,308 +1,308 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
3 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 %if c.compare_home:
6 %if c.compare_home:
7 ${_('%s Compare') % c.repo_name}
7 ${_('%s Compare') % c.repo_name}
8 %else:
8 %else:
9 ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.source_repo.repo_name, c.source_ref)} &gt; ${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}
9 ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.source_repo.repo_name, c.source_ref)} &gt; ${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}
10 %endif
10 %endif
11 %if c.rhodecode_name:
11 %if c.rhodecode_name:
12 &middot; ${h.branding(c.rhodecode_name)}
12 &middot; ${h.branding(c.rhodecode_name)}
13 %endif
13 %endif
14 </%def>
14 </%def>
15
15
16 <%def name="breadcrumbs_links()"></%def>
16 <%def name="breadcrumbs_links()"></%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='compare')}
23 ${self.repo_menu(active='compare')}
24 </%def>
24 </%def>
25
25
26 <%def name="main()">
26 <%def name="main()">
27 <script type="text/javascript">
27 <script type="text/javascript">
28 // set fake commitId on this commit-range page
28 // set fake commitId on this commit-range page
29 templateContext.commit_data.commit_id = "${h.EmptyCommit().raw_id}";
29 templateContext.commit_data.commit_id = "${h.EmptyCommit().raw_id}";
30 </script>
30 </script>
31
31
32 <div class="box">
32 <div class="box">
33 <div class="summary changeset">
33 <div class="summary changeset">
34 <div class="summary-detail">
34 <div class="summary-detail">
35 <div class="summary-detail-header">
35 <div class="summary-detail-header">
36 <span class="breadcrumbs files_location">
36 <span class="breadcrumbs files_location">
37 <h4>
37 <h4>
38 ${_('Compare Commits')}
38 ${_('Compare Commits')}
39 % if c.file_path:
39 % if c.file_path:
40 ${_('for file')} <a href="#${('a_' + h.FID('',c.file_path))}">${c.file_path}</a>
40 ${_('for file')} <a href="#${('a_' + h.FID('',c.file_path))}">${c.file_path}</a>
41 % endif
41 % endif
42
42
43 % if c.commit_ranges:
43 % if c.commit_ranges:
44 <code>
44 <code>
45 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
45 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}...r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
46 </code>
46 </code>
47 % endif
47 % endif
48 </h4>
48 </h4>
49 </span>
49 </span>
50
50
51 <div class="clear-fix"></div>
51 <div class="clear-fix"></div>
52 </div>
52 </div>
53
53
54 <div class="fieldset">
54 <div class="fieldset">
55 <div class="left-label-summary">
55 <div class="left-label-summary">
56 <p class="spacing">${_('Target')}:</p>
56 <p class="spacing">${_('Target')}:</p>
57 <div class="right-label-summary">
57 <div class="right-label-summary">
58 <div class="code-header" >
58 <div class="code-header" >
59 <div class="compare_header">
59 <div class="compare_header">
60 ## The hidden elements are replaced with a select2 widget
60 ## The hidden elements are replaced with a select2 widget
61 ${h.hidden('compare_source')}
61 ${h.hidden('compare_source')}
62 </div>
62 </div>
63 </div>
63 </div>
64 </div>
64 </div>
65 </div>
65 </div>
66 </div>
66 </div>
67
67
68 <div class="fieldset">
68 <div class="fieldset">
69 <div class="left-label-summary">
69 <div class="left-label-summary">
70 <p class="spacing">${_('Source')}:</p>
70 <p class="spacing">${_('Source')}:</p>
71 <div class="right-label-summary">
71 <div class="right-label-summary">
72 <div class="code-header" >
72 <div class="code-header" >
73 <div class="compare_header">
73 <div class="compare_header">
74 ## The hidden elements are replaced with a select2 widget
74 ## The hidden elements are replaced with a select2 widget
75 ${h.hidden('compare_target')}
75 ${h.hidden('compare_target')}
76 </div>
76 </div>
77 </div>
77 </div>
78 </div>
78 </div>
79 </div>
79 </div>
80 </div>
80 </div>
81
81
82 <div class="fieldset">
82 <div class="fieldset">
83 <div class="left-label-summary">
83 <div class="left-label-summary">
84 <p class="spacing">${_('Actions')}:</p>
84 <p class="spacing">${_('Actions')}:</p>
85 <div class="right-label-summary">
85 <div class="right-label-summary">
86 <div class="code-header" >
86 <div class="code-header" >
87 <div class="compare_header">
87 <div class="compare_header">
88 <div class="compare-buttons">
88 <div class="compare-buttons">
89 % if c.compare_home:
89 % if c.compare_home:
90 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
90 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
91 %if c.rhodecode_db_repo.fork:
91 %if c.rhodecode_db_repo.fork:
92
92
93 <a class="btn btn-default" title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
93 <a class="btn btn-default" title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
94 href="${h.route_path('repo_compare',
94 href="${h.route_path('repo_compare',
95 repo_name=c.rhodecode_db_repo.fork.repo_name,
95 repo_name=c.rhodecode_db_repo.fork.repo_name,
96 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
96 source_ref_type=c.rhodecode_db_repo.landing_ref_type,
97 source_ref=c.rhodecode_db_repo.landing_rev[1],
97 source_ref=c.rhodecode_db_repo.landing_ref_name,
98 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
98 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_ref_type,
99 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
99 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_ref_name,
100 _query=dict(merge=1))}"
100 _query=dict(merge=1))}"
101 >
101 >
102 ${_('Compare with origin')}
102 ${_('Compare with origin')}
103 </a>
103 </a>
104
104
105 %endif
105 %endif
106
106
107 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
107 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
108 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
108 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
109 <div id="changeset_compare_view_content">
109 <div id="changeset_compare_view_content">
110 <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div>
110 <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div>
111 </div>
111 </div>
112
112
113 % elif c.preview_mode:
113 % elif c.preview_mode:
114 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Compare Commits')}</a>
114 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Compare Commits')}</a>
115 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
115 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Swap')}</a>
116 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
116 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
117
117
118 % else:
118 % else:
119 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
119 <a id="compare_revs" class="btn btn-primary"> ${_('Compare Commits')}</a>
120 <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}">${_('Swap')}</a>
120 <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}">${_('Swap')}</a>
121
121
122 ## allow comment only if there are commits to comment on
122 ## allow comment only if there are commits to comment on
123 % if c.diffset and c.diffset.files and c.commit_ranges:
123 % if c.diffset and c.diffset.files and c.commit_ranges:
124 <a id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</a>
124 <a id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</a>
125 % else:
125 % else:
126 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
126 <a class="btn disabled tooltip" disabled="disabled" title="${_('Action unavailable in current view')}">${_('Comment')}</a>
127 % endif
127 % endif
128 % endif
128 % endif
129 </div>
129 </div>
130 </div>
130 </div>
131 </div>
131 </div>
132 </div>
132 </div>
133 </div>
133 </div>
134 </div>
134 </div>
135
135
136 ## commit status form
136 ## commit status form
137 <div class="fieldset" id="compare_changeset_status" style="display: none; margin-bottom: -80px;">
137 <div class="fieldset" id="compare_changeset_status" style="display: none; margin-bottom: -80px;">
138 <div class="left-label-summary">
138 <div class="left-label-summary">
139 <p class="spacing">${_('Commit status')}:</p>
139 <p class="spacing">${_('Commit status')}:</p>
140 <div class="right-label-summary">
140 <div class="right-label-summary">
141 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
141 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
142 ## main comment form and it status
142 ## main comment form and it status
143 <%
143 <%
144 def revs(_revs):
144 def revs(_revs):
145 form_inputs = []
145 form_inputs = []
146 for cs in _revs:
146 for cs in _revs:
147 tmpl = '<input type="hidden" data-commit-id="%(cid)s" name="commit_ids" value="%(cid)s">' % {'cid': cs.raw_id}
147 tmpl = '<input type="hidden" data-commit-id="%(cid)s" name="commit_ids" value="%(cid)s">' % {'cid': cs.raw_id}
148 form_inputs.append(tmpl)
148 form_inputs.append(tmpl)
149 return form_inputs
149 return form_inputs
150 %>
150 %>
151 <div>
151 <div>
152 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))}
152 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))}
153 </div>
153 </div>
154 </div>
154 </div>
155 </div>
155 </div>
156 </div>
156 </div>
157 <div class="clear-fix"></div>
157 <div class="clear-fix"></div>
158 </div> <!-- end summary-detail -->
158 </div> <!-- end summary-detail -->
159 </div> <!-- end summary -->
159 </div> <!-- end summary -->
160
160
161 ## use JS script to load it quickly before potentially large diffs render long time
161 ## use JS script to load it quickly before potentially large diffs render long time
162 ## this prevents from situation when large diffs block rendering of select2 fields
162 ## this prevents from situation when large diffs block rendering of select2 fields
163 <script type="text/javascript">
163 <script type="text/javascript">
164
164
165 var cache = {};
165 var cache = {};
166
166
167 var formatSelection = function(repoName){
167 var formatSelection = function(repoName){
168 return function(data, container, escapeMarkup) {
168 return function(data, container, escapeMarkup) {
169 var selection = data ? this.text(data) : "";
169 var selection = data ? this.text(data) : "";
170 return escapeMarkup('{0}@{1}'.format(repoName, selection));
170 return escapeMarkup('{0}@{1}'.format(repoName, selection));
171 }
171 }
172 };
172 };
173
173
174 var feedCompareData = function(query, cachedValue){
174 var feedCompareData = function(query, cachedValue){
175 var data = {results: []};
175 var data = {results: []};
176 //filter results
176 //filter results
177 $.each(cachedValue.results, function() {
177 $.each(cachedValue.results, function() {
178 var section = this.text;
178 var section = this.text;
179 var children = [];
179 var children = [];
180 $.each(this.children, function() {
180 $.each(this.children, function() {
181 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
181 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
182 children.push({
182 children.push({
183 'id': this.id,
183 'id': this.id,
184 'text': this.text,
184 'text': this.text,
185 'type': this.type
185 'type': this.type
186 })
186 })
187 }
187 }
188 });
188 });
189 data.results.push({
189 data.results.push({
190 'text': section,
190 'text': section,
191 'children': children
191 'children': children
192 })
192 })
193 });
193 });
194 //push the typed in changeset
194 //push the typed in changeset
195 data.results.push({
195 data.results.push({
196 'text': _gettext('specify commit'),
196 'text': _gettext('specify commit'),
197 'children': [{
197 'children': [{
198 'id': query.term,
198 'id': query.term,
199 'text': query.term,
199 'text': query.term,
200 'type': 'rev'
200 'type': 'rev'
201 }]
201 }]
202 });
202 });
203 query.callback(data);
203 query.callback(data);
204 };
204 };
205
205
206 var loadCompareData = function(repoName, query, cache){
206 var loadCompareData = function(repoName, query, cache){
207 $.ajax({
207 $.ajax({
208 url: pyroutes.url('repo_refs_data', {'repo_name': repoName}),
208 url: pyroutes.url('repo_refs_data', {'repo_name': repoName}),
209 data: {},
209 data: {},
210 dataType: 'json',
210 dataType: 'json',
211 type: 'GET',
211 type: 'GET',
212 success: function(data) {
212 success: function(data) {
213 cache[repoName] = data;
213 cache[repoName] = data;
214 query.callback({results: data.results});
214 query.callback({results: data.results});
215 }
215 }
216 })
216 })
217 };
217 };
218
218
219 var enable_fields = ${"false" if c.preview_mode else "true"};
219 var enable_fields = ${"false" if c.preview_mode else "true"};
220 $("#compare_source").select2({
220 $("#compare_source").select2({
221 placeholder: "${'%s@%s' % (c.source_repo.repo_name, c.source_ref)}",
221 placeholder: "${'%s@%s' % (c.source_repo.repo_name, c.source_ref)}",
222 containerCssClass: "drop-menu",
222 containerCssClass: "drop-menu",
223 dropdownCssClass: "drop-menu-dropdown",
223 dropdownCssClass: "drop-menu-dropdown",
224 formatSelection: formatSelection("${c.source_repo.repo_name}"),
224 formatSelection: formatSelection("${c.source_repo.repo_name}"),
225 dropdownAutoWidth: true,
225 dropdownAutoWidth: true,
226 query: function(query) {
226 query: function(query) {
227 var repoName = '${c.source_repo.repo_name}';
227 var repoName = '${c.source_repo.repo_name}';
228 var cachedValue = cache[repoName];
228 var cachedValue = cache[repoName];
229
229
230 if (cachedValue){
230 if (cachedValue){
231 feedCompareData(query, cachedValue);
231 feedCompareData(query, cachedValue);
232 }
232 }
233 else {
233 else {
234 loadCompareData(repoName, query, cache);
234 loadCompareData(repoName, query, cache);
235 }
235 }
236 }
236 }
237 }).select2("enable", enable_fields);
237 }).select2("enable", enable_fields);
238
238
239 $("#compare_target").select2({
239 $("#compare_target").select2({
240 placeholder: "${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}",
240 placeholder: "${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}",
241 dropdownAutoWidth: true,
241 dropdownAutoWidth: true,
242 containerCssClass: "drop-menu",
242 containerCssClass: "drop-menu",
243 dropdownCssClass: "drop-menu-dropdown",
243 dropdownCssClass: "drop-menu-dropdown",
244 formatSelection: formatSelection("${c.target_repo.repo_name}"),
244 formatSelection: formatSelection("${c.target_repo.repo_name}"),
245 query: function(query) {
245 query: function(query) {
246 var repoName = '${c.target_repo.repo_name}';
246 var repoName = '${c.target_repo.repo_name}';
247 var cachedValue = cache[repoName];
247 var cachedValue = cache[repoName];
248
248
249 if (cachedValue){
249 if (cachedValue){
250 feedCompareData(query, cachedValue);
250 feedCompareData(query, cachedValue);
251 }
251 }
252 else {
252 else {
253 loadCompareData(repoName, query, cache);
253 loadCompareData(repoName, query, cache);
254 }
254 }
255 }
255 }
256 }).select2("enable", enable_fields);
256 }).select2("enable", enable_fields);
257 var initial_compare_source = {id: "${c.source_ref}", type:"${c.source_ref_type}"};
257 var initial_compare_source = {id: "${c.source_ref}", type:"${c.source_ref_type}"};
258 var initial_compare_target = {id: "${c.target_ref}", type:"${c.target_ref_type}"};
258 var initial_compare_target = {id: "${c.target_ref}", type:"${c.target_ref_type}"};
259
259
260 $('#compare_revs').on('click', function(e) {
260 $('#compare_revs').on('click', function(e) {
261 var source = $('#compare_source').select2('data') || initial_compare_source;
261 var source = $('#compare_source').select2('data') || initial_compare_source;
262 var target = $('#compare_target').select2('data') || initial_compare_target;
262 var target = $('#compare_target').select2('data') || initial_compare_target;
263 if (source && target) {
263 if (source && target) {
264 var url_data = {
264 var url_data = {
265 repo_name: "${c.repo_name}",
265 repo_name: "${c.repo_name}",
266 source_ref: source.id,
266 source_ref: source.id,
267 source_ref_type: source.type,
267 source_ref_type: source.type,
268 target_ref: target.id,
268 target_ref: target.id,
269 target_ref_type: target.type
269 target_ref_type: target.type
270 };
270 };
271 window.location = pyroutes.url('repo_compare', url_data);
271 window.location = pyroutes.url('repo_compare', url_data);
272 }
272 }
273 });
273 });
274 $('#compare_changeset_status_toggle').on('click', function(e) {
274 $('#compare_changeset_status_toggle').on('click', function(e) {
275 $('#compare_changeset_status').toggle();
275 $('#compare_changeset_status').toggle();
276 });
276 });
277
277
278 </script>
278 </script>
279
279
280 ## table diff data
280 ## table diff data
281 <div class="table">
281 <div class="table">
282 % if not c.compare_home:
282 % if not c.compare_home:
283 <div id="changeset_compare_view_content">
283 <div id="changeset_compare_view_content">
284 <div class="pull-left">
284 <div class="pull-left">
285 <div class="btn-group">
285 <div class="btn-group">
286 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
286 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
287 % if c.collapse_all_commits:
287 % if c.collapse_all_commits:
288 <i class="icon-plus-squared-alt icon-no-margin"></i>
288 <i class="icon-plus-squared-alt icon-no-margin"></i>
289 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
289 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
290 % else:
290 % else:
291 <i class="icon-minus-squared-alt icon-no-margin"></i>
291 <i class="icon-minus-squared-alt icon-no-margin"></i>
292 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
292 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
293 % endif
293 % endif
294 </a>
294 </a>
295 </div>
295 </div>
296 </div>
296 </div>
297 <div style="padding:0 10px 10px 0px" class="pull-left"></div>
297 <div style="padding:0 10px 10px 0px" class="pull-left"></div>
298 ## commit compare generated below
298 ## commit compare generated below
299 <%include file="compare_commits.mako"/>
299 <%include file="compare_commits.mako"/>
300 ${cbdiffs.render_diffset_menu(c.diffset)}
300 ${cbdiffs.render_diffset_menu(c.diffset)}
301 ${cbdiffs.render_diffset(c.diffset)}
301 ${cbdiffs.render_diffset(c.diffset)}
302 </div>
302 </div>
303 % endif
303 % endif
304
304
305 </div>
305 </div>
306 </div>
306 </div>
307
307
308 </%def>
308 </%def>
@@ -1,84 +1,84 b''
1
1
2 <div id="codeblock" class="browserblock">
2 <div id="codeblock" class="browserblock">
3 <div class="browser-header">
3 <div class="browser-header">
4 <div class="browser-nav">
4 <div class="browser-nav">
5
5
6 <div class="info_box">
6 <div class="info_box">
7
7
8 <div class="info_box_elem previous">
8 <div class="info_box_elem previous">
9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class=" ${('disabled' if c.url_prev == '#' else '')}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-left"></i></a>
9 <a id="prev_commit_link" data-commit-id="${c.prev_commit.raw_id}" class=" ${('disabled' if c.url_prev == '#' else '')}" href="${c.url_prev}" title="${_('Previous commit')}"><i class="icon-left"></i></a>
10 </div>
10 </div>
11
11
12 ${h.hidden('refs_filter')}
12 ${h.hidden('refs_filter')}
13
13
14 <div class="info_box_elem next">
14 <div class="info_box_elem next">
15 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class=" ${('disabled' if c.url_next == '#' else '')}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-right"></i></a>
15 <a id="next_commit_link" data-commit-id="${c.next_commit.raw_id}" class=" ${('disabled' if c.url_next == '#' else '')}" href="${c.url_next}" title="${_('Next commit')}"><i class="icon-right"></i></a>
16 </div>
16 </div>
17 </div>
17 </div>
18
18
19 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
19 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
20 <div>
20 <div>
21 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
21 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
22 ${_('Upload File')}
22 ${_('Upload File')}
23 </a>
23 </a>
24 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
24 <a class="btn btn-primary new-file" href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=c.commit.raw_id,f_path=c.f_path)}">
25 ${_('Add File')}
25 ${_('Add File')}
26 </a>
26 </a>
27 </div>
27 </div>
28 % endif
28 % endif
29
29
30 % if c.enable_downloads:
30 % if c.enable_downloads:
31 <% at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) %>
31 <% at_path = '{}'.format(request.GET.get('at') or c.commit.raw_id[:6]) %>
32 <div class="btn btn-default new-file">
32 <div class="btn btn-default new-file">
33 % if c.f_path == '/':
33 % if c.f_path == '/':
34 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}">
34 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id))}">
35 ${_('Download full tree ZIP')}
35 ${_('Download full tree ZIP')}
36 </a>
36 </a>
37 % else:
37 % else:
38 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path})}">
38 <a href="${h.route_path('repo_archivefile',repo_name=c.repo_name, fname='{}.zip'.format(c.commit.raw_id), _query={'at_path':c.f_path})}">
39 ${_('Download this tree ZIP')}
39 ${_('Download this tree ZIP')}
40 </a>
40 </a>
41 % endif
41 % endif
42 </div>
42 </div>
43 % endif
43 % endif
44
44
45 <div class="files-quick-filter">
45 <div class="files-quick-filter">
46 <ul class="files-filter-box">
46 <ul class="files-filter-box">
47 <li class="files-filter-box-path">
47 <li class="files-filter-box-path">
48 <i class="icon-search"></i>
48 <i class="icon-search"></i>
49 </li>
49 </li>
50 <li class="files-filter-box-input">
50 <li class="files-filter-box-input">
51 <input onkeydown="NodeFilter.initFilter(event)" class="init" type="text" placeholder="Quick filter" name="filter" size="25" id="node_filter" autocomplete="off">
51 <input onkeydown="NodeFilter.initFilter(event)" class="init" type="text" placeholder="Quick filter" name="filter" size="25" id="node_filter" autocomplete="off">
52 </li>
52 </li>
53 </ul>
53 </ul>
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 </div>
57 </div>
58
58
59 ## file tree is computed from caches, and filled in
59 ## file tree is computed from caches, and filled in
60 <div id="file-tree">
60 <div id="file-tree">
61 ${c.file_tree |n}
61 ${c.file_tree |n}
62 </div>
62 </div>
63
63
64 %if c.readme_data:
64 %if c.readme_data:
65 <div id="readme" class="anchor">
65 <div id="readme" class="anchor">
66 <div class="box">
66 <div class="box">
67 <div class="readme-title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
67 <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))}">
68 <div>
68 <div>
69 <i class="icon-file-text"></i>
69 <i class="icon-file-text"></i>
70 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_rev[1],f_path=c.readme_file)}">
70 <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)}">
71 ${c.readme_file}
71 ${c.readme_file}
72 </a>
72 </a>
73 </div>
73 </div>
74 </div>
74 </div>
75 <div class="readme codeblock">
75 <div class="readme codeblock">
76 <div class="readme_box">
76 <div class="readme_box">
77 ${c.readme_data|n}
77 ${c.readme_data|n}
78 </div>
78 </div>
79 </div>
79 </div>
80 </div>
80 </div>
81 </div>
81 </div>
82 %endif
82 %endif
83
83
84 </div>
84 </div>
@@ -1,118 +1,118 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_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
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_rev[1],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: 'tip',
79 text: 'tip',
80 type: 'tag',
80 type: 'tag',
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 ext = '.zip';
90 var ext = '.zip';
91 var selected_cs = e.added;
91 var selected_cs = e.added;
92 var fname = e.added.raw_id + ext;
92 var fname = e.added.raw_id + ext;
93 var href = pyroutes.url('repo_archivefile', {'repo_name': templateContext.repo_name, 'fname':fname});
93 var href = pyroutes.url('repo_archivefile', {'repo_name': templateContext.repo_name, 'fname':fname});
94 // set new label
94 // set new label
95 $('#archive_link').html('{0}{1}'.format(escapeHtml(e.added.text), ext));
95 $('#archive_link').html('{0}{1}'.format(escapeHtml(e.added.text), ext));
96
96
97 // set new url to button,
97 // set new url to button,
98 $('#archive_link').attr('href', href)
98 $('#archive_link').attr('href', href)
99 });
99 });
100
100
101
101
102 // calculate size of repository
102 // calculate size of repository
103 calculateSize = function () {
103 calculateSize = function () {
104
104
105 var callback = function (data) {
105 var callback = function (data) {
106 % if c.show_stats:
106 % if c.show_stats:
107 showRepoStats('lang_stats', data);
107 showRepoStats('lang_stats', data);
108 % endif
108 % endif
109 };
109 };
110
110
111 showRepoSize('repo_size_container', templateContext.repo_name, templateContext.repo_landing_commit, callback);
111 showRepoSize('repo_size_container', templateContext.repo_name, templateContext.repo_landing_commit, callback);
112
112
113 }
113 }
114
114
115 })
115 })
116 </script>
116 </script>
117
117
118 </%def>
118 </%def>
General Comments 0
You need to be logged in to leave comments. Login now