##// END OF EJS Templates
comments: extend API data with references to commit_id or pull_request_id for audit-logs.
marcink -
r4304:a588c9e6 default
parent child Browse files
Show More
@@ -1,83 +1,86 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 import pytest
22 import pytest
23 import urlobject
23 import urlobject
24
24
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils2 import safe_unicode
28 from rhodecode.lib.utils2 import safe_unicode
29
29
30 pytestmark = pytest.mark.backends("git", "hg")
30 pytestmark = pytest.mark.backends("git", "hg")
31
31
32
32
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestGetPullRequestComments(object):
34 class TestGetPullRequestComments(object):
35
35
36 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
36 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
37 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.pull_request import PullRequestModel
38
38
39 pull_request = pr_util.create_pull_request(mergeable=True)
39 pull_request = pr_util.create_pull_request(mergeable=True)
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'get_pull_request_comments',
41 self.apikey, 'get_pull_request_comments',
42 pullrequestid=pull_request.pull_request_id)
42 pullrequestid=pull_request.pull_request_id)
43
43
44 response = api_call(self.app, params)
44 response = api_call(self.app, params)
45
45
46 assert response.status == '200 OK'
46 assert response.status == '200 OK'
47 resp_date = response.json['result'][0]['comment_created_on']
47 resp_date = response.json['result'][0]['comment_created_on']
48 resp_comment_id = response.json['result'][0]['comment_id']
48 resp_comment_id = response.json['result'][0]['comment_id']
49
49
50 expected = [
50 expected = [
51 {'comment_author': {'active': True,
51 {'comment_author': {'active': True,
52 'full_name_or_username': 'RhodeCode Admin',
52 'full_name_or_username': 'RhodeCode Admin',
53 'username': 'test_admin'},
53 'username': 'test_admin'},
54 'comment_created_on': resp_date,
54 'comment_created_on': resp_date,
55 'comment_f_path': None,
55 'comment_f_path': None,
56 'comment_id': resp_comment_id,
56 'comment_id': resp_comment_id,
57 'comment_lineno': None,
57 'comment_lineno': None,
58 'comment_status': {'status': 'under_review',
58 'comment_status': {'status': 'under_review',
59 'status_lbl': 'Under Review'},
59 'status_lbl': 'Under Review'},
60 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
60 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
61 'comment_type': 'note',
61 'comment_type': 'note',
62 'comment_resolved_by': None,
62 'comment_resolved_by': None,
63 'pull_request_version': None}
63 'pull_request_version': None,
64 'comment_commit_id': None,
65 'comment_pull_request_id': pull_request.pull_request_id
66 }
64 ]
67 ]
65 assert_ok(id_, expected, response.body)
68 assert_ok(id_, expected, response.body)
66
69
67 def test_api_get_pull_request_comments_repo_error(self, pr_util):
70 def test_api_get_pull_request_comments_repo_error(self, pr_util):
68 pull_request = pr_util.create_pull_request()
71 pull_request = pr_util.create_pull_request()
69 id_, params = build_data(
72 id_, params = build_data(
70 self.apikey, 'get_pull_request_comments',
73 self.apikey, 'get_pull_request_comments',
71 repoid=666, pullrequestid=pull_request.pull_request_id)
74 repoid=666, pullrequestid=pull_request.pull_request_id)
72 response = api_call(self.app, params)
75 response = api_call(self.app, params)
73
76
74 expected = 'repository `666` does not exist'
77 expected = 'repository `666` does not exist'
75 assert_error(id_, expected, given=response.body)
78 assert_error(id_, expected, given=response.body)
76
79
77 def test_api_get_pull_request_comments_pull_request_error(self):
80 def test_api_get_pull_request_comments_pull_request_error(self):
78 id_, params = build_data(
81 id_, params = build_data(
79 self.apikey, 'get_pull_request_comments', pullrequestid=666)
82 self.apikey, 'get_pull_request_comments', pullrequestid=666)
80 response = api_call(self.app, params)
83 response = api_call(self.app, params)
81
84
82 expected = 'pull request `666` does not exist'
85 expected = 'pull request `666` does not exist'
83 assert_error(id_, expected, given=response.body)
86 assert_error(id_, expected, given=response.body)
@@ -1,1016 +1,1018 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 import logging
22 import logging
23
23
24 from rhodecode import events
24 from rhodecode import events
25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
29 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
29 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
31 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.base import vcs_operation_context
32 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.changeset_status import ChangesetStatusModel
34 from rhodecode.model.comment import CommentsModel
34 from rhodecode.model.comment import CommentsModel
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 from rhodecode.model.settings import SettingsModel
37 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.validation_schema import Invalid
38 from rhodecode.model.validation_schema import Invalid
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
40 ReviewerListSchema)
40 ReviewerListSchema)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 @jsonrpc_method()
45 @jsonrpc_method()
46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None),
46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None),
47 merge_state=Optional(False)):
47 merge_state=Optional(False)):
48 """
48 """
49 Get a pull request based on the given ID.
49 Get a pull request based on the given ID.
50
50
51 :param apiuser: This is filled automatically from the |authtoken|.
51 :param apiuser: This is filled automatically from the |authtoken|.
52 :type apiuser: AuthUser
52 :type apiuser: AuthUser
53 :param repoid: Optional, repository name or repository ID from where
53 :param repoid: Optional, repository name or repository ID from where
54 the pull request was opened.
54 the pull request was opened.
55 :type repoid: str or int
55 :type repoid: str or int
56 :param pullrequestid: ID of the requested pull request.
56 :param pullrequestid: ID of the requested pull request.
57 :type pullrequestid: int
57 :type pullrequestid: int
58 :param merge_state: Optional calculate merge state for each repository.
58 :param merge_state: Optional calculate merge state for each repository.
59 This could result in longer time to fetch the data
59 This could result in longer time to fetch the data
60 :type merge_state: bool
60 :type merge_state: bool
61
61
62 Example output:
62 Example output:
63
63
64 .. code-block:: bash
64 .. code-block:: bash
65
65
66 "id": <id_given_in_input>,
66 "id": <id_given_in_input>,
67 "result":
67 "result":
68 {
68 {
69 "pull_request_id": "<pull_request_id>",
69 "pull_request_id": "<pull_request_id>",
70 "url": "<url>",
70 "url": "<url>",
71 "title": "<title>",
71 "title": "<title>",
72 "description": "<description>",
72 "description": "<description>",
73 "status" : "<status>",
73 "status" : "<status>",
74 "created_on": "<date_time_created>",
74 "created_on": "<date_time_created>",
75 "updated_on": "<date_time_updated>",
75 "updated_on": "<date_time_updated>",
76 "versions": "<number_or_versions_of_pr>",
76 "versions": "<number_or_versions_of_pr>",
77 "commit_ids": [
77 "commit_ids": [
78 ...
78 ...
79 "<commit_id>",
79 "<commit_id>",
80 "<commit_id>",
80 "<commit_id>",
81 ...
81 ...
82 ],
82 ],
83 "review_status": "<review_status>",
83 "review_status": "<review_status>",
84 "mergeable": {
84 "mergeable": {
85 "status": "<bool>",
85 "status": "<bool>",
86 "message": "<message>",
86 "message": "<message>",
87 },
87 },
88 "source": {
88 "source": {
89 "clone_url": "<clone_url>",
89 "clone_url": "<clone_url>",
90 "repository": "<repository_name>",
90 "repository": "<repository_name>",
91 "reference":
91 "reference":
92 {
92 {
93 "name": "<name>",
93 "name": "<name>",
94 "type": "<type>",
94 "type": "<type>",
95 "commit_id": "<commit_id>",
95 "commit_id": "<commit_id>",
96 }
96 }
97 },
97 },
98 "target": {
98 "target": {
99 "clone_url": "<clone_url>",
99 "clone_url": "<clone_url>",
100 "repository": "<repository_name>",
100 "repository": "<repository_name>",
101 "reference":
101 "reference":
102 {
102 {
103 "name": "<name>",
103 "name": "<name>",
104 "type": "<type>",
104 "type": "<type>",
105 "commit_id": "<commit_id>",
105 "commit_id": "<commit_id>",
106 }
106 }
107 },
107 },
108 "merge": {
108 "merge": {
109 "clone_url": "<clone_url>",
109 "clone_url": "<clone_url>",
110 "reference":
110 "reference":
111 {
111 {
112 "name": "<name>",
112 "name": "<name>",
113 "type": "<type>",
113 "type": "<type>",
114 "commit_id": "<commit_id>",
114 "commit_id": "<commit_id>",
115 }
115 }
116 },
116 },
117 "author": <user_obj>,
117 "author": <user_obj>,
118 "reviewers": [
118 "reviewers": [
119 ...
119 ...
120 {
120 {
121 "user": "<user_obj>",
121 "user": "<user_obj>",
122 "review_status": "<review_status>",
122 "review_status": "<review_status>",
123 }
123 }
124 ...
124 ...
125 ]
125 ]
126 },
126 },
127 "error": null
127 "error": null
128 """
128 """
129
129
130 pull_request = get_pull_request_or_error(pullrequestid)
130 pull_request = get_pull_request_or_error(pullrequestid)
131 if Optional.extract(repoid):
131 if Optional.extract(repoid):
132 repo = get_repo_or_error(repoid)
132 repo = get_repo_or_error(repoid)
133 else:
133 else:
134 repo = pull_request.target_repo
134 repo = pull_request.target_repo
135
135
136 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
136 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
137 raise JSONRPCError('repository `%s` or pull request `%s` '
137 raise JSONRPCError('repository `%s` or pull request `%s` '
138 'does not exist' % (repoid, pullrequestid))
138 'does not exist' % (repoid, pullrequestid))
139
139
140 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
140 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
141 # otherwise we can lock the repo on calculation of merge state while update/merge
141 # otherwise we can lock the repo on calculation of merge state while update/merge
142 # is happening.
142 # is happening.
143 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
143 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
144 merge_state = Optional.extract(merge_state, binary=True) and pr_created
144 merge_state = Optional.extract(merge_state, binary=True) and pr_created
145 data = pull_request.get_api_data(with_merge_state=merge_state)
145 data = pull_request.get_api_data(with_merge_state=merge_state)
146 return data
146 return data
147
147
148
148
149 @jsonrpc_method()
149 @jsonrpc_method()
150 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
150 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
151 merge_state=Optional(False)):
151 merge_state=Optional(False)):
152 """
152 """
153 Get all pull requests from the repository specified in `repoid`.
153 Get all pull requests from the repository specified in `repoid`.
154
154
155 :param apiuser: This is filled automatically from the |authtoken|.
155 :param apiuser: This is filled automatically from the |authtoken|.
156 :type apiuser: AuthUser
156 :type apiuser: AuthUser
157 :param repoid: Optional repository name or repository ID.
157 :param repoid: Optional repository name or repository ID.
158 :type repoid: str or int
158 :type repoid: str or int
159 :param status: Only return pull requests with the specified status.
159 :param status: Only return pull requests with the specified status.
160 Valid options are.
160 Valid options are.
161 * ``new`` (default)
161 * ``new`` (default)
162 * ``open``
162 * ``open``
163 * ``closed``
163 * ``closed``
164 :type status: str
164 :type status: str
165 :param merge_state: Optional calculate merge state for each repository.
165 :param merge_state: Optional calculate merge state for each repository.
166 This could result in longer time to fetch the data
166 This could result in longer time to fetch the data
167 :type merge_state: bool
167 :type merge_state: bool
168
168
169 Example output:
169 Example output:
170
170
171 .. code-block:: bash
171 .. code-block:: bash
172
172
173 "id": <id_given_in_input>,
173 "id": <id_given_in_input>,
174 "result":
174 "result":
175 [
175 [
176 ...
176 ...
177 {
177 {
178 "pull_request_id": "<pull_request_id>",
178 "pull_request_id": "<pull_request_id>",
179 "url": "<url>",
179 "url": "<url>",
180 "title" : "<title>",
180 "title" : "<title>",
181 "description": "<description>",
181 "description": "<description>",
182 "status": "<status>",
182 "status": "<status>",
183 "created_on": "<date_time_created>",
183 "created_on": "<date_time_created>",
184 "updated_on": "<date_time_updated>",
184 "updated_on": "<date_time_updated>",
185 "commit_ids": [
185 "commit_ids": [
186 ...
186 ...
187 "<commit_id>",
187 "<commit_id>",
188 "<commit_id>",
188 "<commit_id>",
189 ...
189 ...
190 ],
190 ],
191 "review_status": "<review_status>",
191 "review_status": "<review_status>",
192 "mergeable": {
192 "mergeable": {
193 "status": "<bool>",
193 "status": "<bool>",
194 "message: "<message>",
194 "message: "<message>",
195 },
195 },
196 "source": {
196 "source": {
197 "clone_url": "<clone_url>",
197 "clone_url": "<clone_url>",
198 "reference":
198 "reference":
199 {
199 {
200 "name": "<name>",
200 "name": "<name>",
201 "type": "<type>",
201 "type": "<type>",
202 "commit_id": "<commit_id>",
202 "commit_id": "<commit_id>",
203 }
203 }
204 },
204 },
205 "target": {
205 "target": {
206 "clone_url": "<clone_url>",
206 "clone_url": "<clone_url>",
207 "reference":
207 "reference":
208 {
208 {
209 "name": "<name>",
209 "name": "<name>",
210 "type": "<type>",
210 "type": "<type>",
211 "commit_id": "<commit_id>",
211 "commit_id": "<commit_id>",
212 }
212 }
213 },
213 },
214 "merge": {
214 "merge": {
215 "clone_url": "<clone_url>",
215 "clone_url": "<clone_url>",
216 "reference":
216 "reference":
217 {
217 {
218 "name": "<name>",
218 "name": "<name>",
219 "type": "<type>",
219 "type": "<type>",
220 "commit_id": "<commit_id>",
220 "commit_id": "<commit_id>",
221 }
221 }
222 },
222 },
223 "author": <user_obj>,
223 "author": <user_obj>,
224 "reviewers": [
224 "reviewers": [
225 ...
225 ...
226 {
226 {
227 "user": "<user_obj>",
227 "user": "<user_obj>",
228 "review_status": "<review_status>",
228 "review_status": "<review_status>",
229 }
229 }
230 ...
230 ...
231 ]
231 ]
232 }
232 }
233 ...
233 ...
234 ],
234 ],
235 "error": null
235 "error": null
236
236
237 """
237 """
238 repo = get_repo_or_error(repoid)
238 repo = get_repo_or_error(repoid)
239 if not has_superadmin_permission(apiuser):
239 if not has_superadmin_permission(apiuser):
240 _perms = (
240 _perms = (
241 'repository.admin', 'repository.write', 'repository.read',)
241 'repository.admin', 'repository.write', 'repository.read',)
242 validate_repo_permissions(apiuser, repoid, repo, _perms)
242 validate_repo_permissions(apiuser, repoid, repo, _perms)
243
243
244 status = Optional.extract(status)
244 status = Optional.extract(status)
245 merge_state = Optional.extract(merge_state, binary=True)
245 merge_state = Optional.extract(merge_state, binary=True)
246 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
246 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
247 order_by='id', order_dir='desc')
247 order_by='id', order_dir='desc')
248 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
248 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
249 return data
249 return data
250
250
251
251
252 @jsonrpc_method()
252 @jsonrpc_method()
253 def merge_pull_request(
253 def merge_pull_request(
254 request, apiuser, pullrequestid, repoid=Optional(None),
254 request, apiuser, pullrequestid, repoid=Optional(None),
255 userid=Optional(OAttr('apiuser'))):
255 userid=Optional(OAttr('apiuser'))):
256 """
256 """
257 Merge the pull request specified by `pullrequestid` into its target
257 Merge the pull request specified by `pullrequestid` into its target
258 repository.
258 repository.
259
259
260 :param apiuser: This is filled automatically from the |authtoken|.
260 :param apiuser: This is filled automatically from the |authtoken|.
261 :type apiuser: AuthUser
261 :type apiuser: AuthUser
262 :param repoid: Optional, repository name or repository ID of the
262 :param repoid: Optional, repository name or repository ID of the
263 target repository to which the |pr| is to be merged.
263 target repository to which the |pr| is to be merged.
264 :type repoid: str or int
264 :type repoid: str or int
265 :param pullrequestid: ID of the pull request which shall be merged.
265 :param pullrequestid: ID of the pull request which shall be merged.
266 :type pullrequestid: int
266 :type pullrequestid: int
267 :param userid: Merge the pull request as this user.
267 :param userid: Merge the pull request as this user.
268 :type userid: Optional(str or int)
268 :type userid: Optional(str or int)
269
269
270 Example output:
270 Example output:
271
271
272 .. code-block:: bash
272 .. code-block:: bash
273
273
274 "id": <id_given_in_input>,
274 "id": <id_given_in_input>,
275 "result": {
275 "result": {
276 "executed": "<bool>",
276 "executed": "<bool>",
277 "failure_reason": "<int>",
277 "failure_reason": "<int>",
278 "merge_status_message": "<str>",
278 "merge_status_message": "<str>",
279 "merge_commit_id": "<merge_commit_id>",
279 "merge_commit_id": "<merge_commit_id>",
280 "possible": "<bool>",
280 "possible": "<bool>",
281 "merge_ref": {
281 "merge_ref": {
282 "commit_id": "<commit_id>",
282 "commit_id": "<commit_id>",
283 "type": "<type>",
283 "type": "<type>",
284 "name": "<name>"
284 "name": "<name>"
285 }
285 }
286 },
286 },
287 "error": null
287 "error": null
288 """
288 """
289 pull_request = get_pull_request_or_error(pullrequestid)
289 pull_request = get_pull_request_or_error(pullrequestid)
290 if Optional.extract(repoid):
290 if Optional.extract(repoid):
291 repo = get_repo_or_error(repoid)
291 repo = get_repo_or_error(repoid)
292 else:
292 else:
293 repo = pull_request.target_repo
293 repo = pull_request.target_repo
294 auth_user = apiuser
294 auth_user = apiuser
295 if not isinstance(userid, Optional):
295 if not isinstance(userid, Optional):
296 if (has_superadmin_permission(apiuser) or
296 if (has_superadmin_permission(apiuser) or
297 HasRepoPermissionAnyApi('repository.admin')(
297 HasRepoPermissionAnyApi('repository.admin')(
298 user=apiuser, repo_name=repo.repo_name)):
298 user=apiuser, repo_name=repo.repo_name)):
299 apiuser = get_user_or_error(userid)
299 apiuser = get_user_or_error(userid)
300 auth_user = apiuser.AuthUser()
300 auth_user = apiuser.AuthUser()
301 else:
301 else:
302 raise JSONRPCError('userid is not the same as your user')
302 raise JSONRPCError('userid is not the same as your user')
303
303
304 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
304 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
305 raise JSONRPCError(
305 raise JSONRPCError(
306 'Operation forbidden because pull request is in state {}, '
306 'Operation forbidden because pull request is in state {}, '
307 'only state {} is allowed.'.format(
307 'only state {} is allowed.'.format(
308 pull_request.pull_request_state, PullRequest.STATE_CREATED))
308 pull_request.pull_request_state, PullRequest.STATE_CREATED))
309
309
310 with pull_request.set_state(PullRequest.STATE_UPDATING):
310 with pull_request.set_state(PullRequest.STATE_UPDATING):
311 check = MergeCheck.validate(pull_request, auth_user=auth_user,
311 check = MergeCheck.validate(pull_request, auth_user=auth_user,
312 translator=request.translate)
312 translator=request.translate)
313 merge_possible = not check.failed
313 merge_possible = not check.failed
314
314
315 if not merge_possible:
315 if not merge_possible:
316 error_messages = []
316 error_messages = []
317 for err_type, error_msg in check.errors:
317 for err_type, error_msg in check.errors:
318 error_msg = request.translate(error_msg)
318 error_msg = request.translate(error_msg)
319 error_messages.append(error_msg)
319 error_messages.append(error_msg)
320
320
321 reasons = ','.join(error_messages)
321 reasons = ','.join(error_messages)
322 raise JSONRPCError(
322 raise JSONRPCError(
323 'merge not possible for following reasons: {}'.format(reasons))
323 'merge not possible for following reasons: {}'.format(reasons))
324
324
325 target_repo = pull_request.target_repo
325 target_repo = pull_request.target_repo
326 extras = vcs_operation_context(
326 extras = vcs_operation_context(
327 request.environ, repo_name=target_repo.repo_name,
327 request.environ, repo_name=target_repo.repo_name,
328 username=auth_user.username, action='push',
328 username=auth_user.username, action='push',
329 scm=target_repo.repo_type)
329 scm=target_repo.repo_type)
330 with pull_request.set_state(PullRequest.STATE_UPDATING):
330 with pull_request.set_state(PullRequest.STATE_UPDATING):
331 merge_response = PullRequestModel().merge_repo(
331 merge_response = PullRequestModel().merge_repo(
332 pull_request, apiuser, extras=extras)
332 pull_request, apiuser, extras=extras)
333 if merge_response.executed:
333 if merge_response.executed:
334 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
334 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
335
335
336 Session().commit()
336 Session().commit()
337
337
338 # In previous versions the merge response directly contained the merge
338 # In previous versions the merge response directly contained the merge
339 # commit id. It is now contained in the merge reference object. To be
339 # commit id. It is now contained in the merge reference object. To be
340 # backwards compatible we have to extract it again.
340 # backwards compatible we have to extract it again.
341 merge_response = merge_response.asdict()
341 merge_response = merge_response.asdict()
342 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
342 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
343
343
344 return merge_response
344 return merge_response
345
345
346
346
347 @jsonrpc_method()
347 @jsonrpc_method()
348 def get_pull_request_comments(
348 def get_pull_request_comments(
349 request, apiuser, pullrequestid, repoid=Optional(None)):
349 request, apiuser, pullrequestid, repoid=Optional(None)):
350 """
350 """
351 Get all comments of pull request specified with the `pullrequestid`
351 Get all comments of pull request specified with the `pullrequestid`
352
352
353 :param apiuser: This is filled automatically from the |authtoken|.
353 :param apiuser: This is filled automatically from the |authtoken|.
354 :type apiuser: AuthUser
354 :type apiuser: AuthUser
355 :param repoid: Optional repository name or repository ID.
355 :param repoid: Optional repository name or repository ID.
356 :type repoid: str or int
356 :type repoid: str or int
357 :param pullrequestid: The pull request ID.
357 :param pullrequestid: The pull request ID.
358 :type pullrequestid: int
358 :type pullrequestid: int
359
359
360 Example output:
360 Example output:
361
361
362 .. code-block:: bash
362 .. code-block:: bash
363
363
364 id : <id_given_in_input>
364 id : <id_given_in_input>
365 result : [
365 result : [
366 {
366 {
367 "comment_author": {
367 "comment_author": {
368 "active": true,
368 "active": true,
369 "full_name_or_username": "Tom Gore",
369 "full_name_or_username": "Tom Gore",
370 "username": "admin"
370 "username": "admin"
371 },
371 },
372 "comment_created_on": "2017-01-02T18:43:45.533",
372 "comment_created_on": "2017-01-02T18:43:45.533",
373 "comment_f_path": null,
373 "comment_f_path": null,
374 "comment_id": 25,
374 "comment_id": 25,
375 "comment_lineno": null,
375 "comment_lineno": null,
376 "comment_status": {
376 "comment_status": {
377 "status": "under_review",
377 "status": "under_review",
378 "status_lbl": "Under Review"
378 "status_lbl": "Under Review"
379 },
379 },
380 "comment_text": "Example text",
380 "comment_text": "Example text",
381 "comment_type": null,
381 "comment_type": null,
382 "pull_request_version": null
382 "pull_request_version": null,
383 "comment_commit_id": None,
384 "comment_pull_request_id": <pull_request_id>
383 }
385 }
384 ],
386 ],
385 error : null
387 error : null
386 """
388 """
387
389
388 pull_request = get_pull_request_or_error(pullrequestid)
390 pull_request = get_pull_request_or_error(pullrequestid)
389 if Optional.extract(repoid):
391 if Optional.extract(repoid):
390 repo = get_repo_or_error(repoid)
392 repo = get_repo_or_error(repoid)
391 else:
393 else:
392 repo = pull_request.target_repo
394 repo = pull_request.target_repo
393
395
394 if not PullRequestModel().check_user_read(
396 if not PullRequestModel().check_user_read(
395 pull_request, apiuser, api=True):
397 pull_request, apiuser, api=True):
396 raise JSONRPCError('repository `%s` or pull request `%s` '
398 raise JSONRPCError('repository `%s` or pull request `%s` '
397 'does not exist' % (repoid, pullrequestid))
399 'does not exist' % (repoid, pullrequestid))
398
400
399 (pull_request_latest,
401 (pull_request_latest,
400 pull_request_at_ver,
402 pull_request_at_ver,
401 pull_request_display_obj,
403 pull_request_display_obj,
402 at_version) = PullRequestModel().get_pr_version(
404 at_version) = PullRequestModel().get_pr_version(
403 pull_request.pull_request_id, version=None)
405 pull_request.pull_request_id, version=None)
404
406
405 versions = pull_request_display_obj.versions()
407 versions = pull_request_display_obj.versions()
406 ver_map = {
408 ver_map = {
407 ver.pull_request_version_id: cnt
409 ver.pull_request_version_id: cnt
408 for cnt, ver in enumerate(versions, 1)
410 for cnt, ver in enumerate(versions, 1)
409 }
411 }
410
412
411 # GENERAL COMMENTS with versions #
413 # GENERAL COMMENTS with versions #
412 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
414 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
413 q = q.order_by(ChangesetComment.comment_id.asc())
415 q = q.order_by(ChangesetComment.comment_id.asc())
414 general_comments = q.all()
416 general_comments = q.all()
415
417
416 # INLINE COMMENTS with versions #
418 # INLINE COMMENTS with versions #
417 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
419 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
418 q = q.order_by(ChangesetComment.comment_id.asc())
420 q = q.order_by(ChangesetComment.comment_id.asc())
419 inline_comments = q.all()
421 inline_comments = q.all()
420
422
421 data = []
423 data = []
422 for comment in inline_comments + general_comments:
424 for comment in inline_comments + general_comments:
423 full_data = comment.get_api_data()
425 full_data = comment.get_api_data()
424 pr_version_id = None
426 pr_version_id = None
425 if comment.pull_request_version_id:
427 if comment.pull_request_version_id:
426 pr_version_id = 'v{}'.format(
428 pr_version_id = 'v{}'.format(
427 ver_map[comment.pull_request_version_id])
429 ver_map[comment.pull_request_version_id])
428
430
429 # sanitize some entries
431 # sanitize some entries
430
432
431 full_data['pull_request_version'] = pr_version_id
433 full_data['pull_request_version'] = pr_version_id
432 full_data['comment_author'] = {
434 full_data['comment_author'] = {
433 'username': full_data['comment_author'].username,
435 'username': full_data['comment_author'].username,
434 'full_name_or_username': full_data['comment_author'].full_name_or_username,
436 'full_name_or_username': full_data['comment_author'].full_name_or_username,
435 'active': full_data['comment_author'].active,
437 'active': full_data['comment_author'].active,
436 }
438 }
437
439
438 if full_data['comment_status']:
440 if full_data['comment_status']:
439 full_data['comment_status'] = {
441 full_data['comment_status'] = {
440 'status': full_data['comment_status'][0].status,
442 'status': full_data['comment_status'][0].status,
441 'status_lbl': full_data['comment_status'][0].status_lbl,
443 'status_lbl': full_data['comment_status'][0].status_lbl,
442 }
444 }
443 else:
445 else:
444 full_data['comment_status'] = {}
446 full_data['comment_status'] = {}
445
447
446 data.append(full_data)
448 data.append(full_data)
447 return data
449 return data
448
450
449
451
450 @jsonrpc_method()
452 @jsonrpc_method()
451 def comment_pull_request(
453 def comment_pull_request(
452 request, apiuser, pullrequestid, repoid=Optional(None),
454 request, apiuser, pullrequestid, repoid=Optional(None),
453 message=Optional(None), commit_id=Optional(None), status=Optional(None),
455 message=Optional(None), commit_id=Optional(None), status=Optional(None),
454 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
456 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
455 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
457 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
456 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
458 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
457 """
459 """
458 Comment on the pull request specified with the `pullrequestid`,
460 Comment on the pull request specified with the `pullrequestid`,
459 in the |repo| specified by the `repoid`, and optionally change the
461 in the |repo| specified by the `repoid`, and optionally change the
460 review status.
462 review status.
461
463
462 :param apiuser: This is filled automatically from the |authtoken|.
464 :param apiuser: This is filled automatically from the |authtoken|.
463 :type apiuser: AuthUser
465 :type apiuser: AuthUser
464 :param repoid: Optional repository name or repository ID.
466 :param repoid: Optional repository name or repository ID.
465 :type repoid: str or int
467 :type repoid: str or int
466 :param pullrequestid: The pull request ID.
468 :param pullrequestid: The pull request ID.
467 :type pullrequestid: int
469 :type pullrequestid: int
468 :param commit_id: Specify the commit_id for which to set a comment. If
470 :param commit_id: Specify the commit_id for which to set a comment. If
469 given commit_id is different than latest in the PR status
471 given commit_id is different than latest in the PR status
470 change won't be performed.
472 change won't be performed.
471 :type commit_id: str
473 :type commit_id: str
472 :param message: The text content of the comment.
474 :param message: The text content of the comment.
473 :type message: str
475 :type message: str
474 :param status: (**Optional**) Set the approval status of the pull
476 :param status: (**Optional**) Set the approval status of the pull
475 request. One of: 'not_reviewed', 'approved', 'rejected',
477 request. One of: 'not_reviewed', 'approved', 'rejected',
476 'under_review'
478 'under_review'
477 :type status: str
479 :type status: str
478 :param comment_type: Comment type, one of: 'note', 'todo'
480 :param comment_type: Comment type, one of: 'note', 'todo'
479 :type comment_type: Optional(str), default: 'note'
481 :type comment_type: Optional(str), default: 'note'
480 :param resolves_comment_id: id of comment which this one will resolve
482 :param resolves_comment_id: id of comment which this one will resolve
481 :type resolves_comment_id: Optional(int)
483 :type resolves_comment_id: Optional(int)
482 :param extra_recipients: list of user ids or usernames to add
484 :param extra_recipients: list of user ids or usernames to add
483 notifications for this comment. Acts like a CC for notification
485 notifications for this comment. Acts like a CC for notification
484 :type extra_recipients: Optional(list)
486 :type extra_recipients: Optional(list)
485 :param userid: Comment on the pull request as this user
487 :param userid: Comment on the pull request as this user
486 :type userid: Optional(str or int)
488 :type userid: Optional(str or int)
487 :param send_email: Define if this comment should also send email notification
489 :param send_email: Define if this comment should also send email notification
488 :type send_email: Optional(bool)
490 :type send_email: Optional(bool)
489
491
490 Example output:
492 Example output:
491
493
492 .. code-block:: bash
494 .. code-block:: bash
493
495
494 id : <id_given_in_input>
496 id : <id_given_in_input>
495 result : {
497 result : {
496 "pull_request_id": "<Integer>",
498 "pull_request_id": "<Integer>",
497 "comment_id": "<Integer>",
499 "comment_id": "<Integer>",
498 "status": {"given": <given_status>,
500 "status": {"given": <given_status>,
499 "was_changed": <bool status_was_actually_changed> },
501 "was_changed": <bool status_was_actually_changed> },
500 },
502 },
501 error : null
503 error : null
502 """
504 """
503 pull_request = get_pull_request_or_error(pullrequestid)
505 pull_request = get_pull_request_or_error(pullrequestid)
504 if Optional.extract(repoid):
506 if Optional.extract(repoid):
505 repo = get_repo_or_error(repoid)
507 repo = get_repo_or_error(repoid)
506 else:
508 else:
507 repo = pull_request.target_repo
509 repo = pull_request.target_repo
508
510
509 auth_user = apiuser
511 auth_user = apiuser
510 if not isinstance(userid, Optional):
512 if not isinstance(userid, Optional):
511 if (has_superadmin_permission(apiuser) or
513 if (has_superadmin_permission(apiuser) or
512 HasRepoPermissionAnyApi('repository.admin')(
514 HasRepoPermissionAnyApi('repository.admin')(
513 user=apiuser, repo_name=repo.repo_name)):
515 user=apiuser, repo_name=repo.repo_name)):
514 apiuser = get_user_or_error(userid)
516 apiuser = get_user_or_error(userid)
515 auth_user = apiuser.AuthUser()
517 auth_user = apiuser.AuthUser()
516 else:
518 else:
517 raise JSONRPCError('userid is not the same as your user')
519 raise JSONRPCError('userid is not the same as your user')
518
520
519 if pull_request.is_closed():
521 if pull_request.is_closed():
520 raise JSONRPCError(
522 raise JSONRPCError(
521 'pull request `%s` comment failed, pull request is closed' % (
523 'pull request `%s` comment failed, pull request is closed' % (
522 pullrequestid,))
524 pullrequestid,))
523
525
524 if not PullRequestModel().check_user_read(
526 if not PullRequestModel().check_user_read(
525 pull_request, apiuser, api=True):
527 pull_request, apiuser, api=True):
526 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
528 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
527 message = Optional.extract(message)
529 message = Optional.extract(message)
528 status = Optional.extract(status)
530 status = Optional.extract(status)
529 commit_id = Optional.extract(commit_id)
531 commit_id = Optional.extract(commit_id)
530 comment_type = Optional.extract(comment_type)
532 comment_type = Optional.extract(comment_type)
531 resolves_comment_id = Optional.extract(resolves_comment_id)
533 resolves_comment_id = Optional.extract(resolves_comment_id)
532 extra_recipients = Optional.extract(extra_recipients)
534 extra_recipients = Optional.extract(extra_recipients)
533 send_email = Optional.extract(send_email, binary=True)
535 send_email = Optional.extract(send_email, binary=True)
534
536
535 if not message and not status:
537 if not message and not status:
536 raise JSONRPCError(
538 raise JSONRPCError(
537 'Both message and status parameters are missing. '
539 'Both message and status parameters are missing. '
538 'At least one is required.')
540 'At least one is required.')
539
541
540 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
542 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
541 status is not None):
543 status is not None):
542 raise JSONRPCError('Unknown comment status: `%s`' % status)
544 raise JSONRPCError('Unknown comment status: `%s`' % status)
543
545
544 if commit_id and commit_id not in pull_request.revisions:
546 if commit_id and commit_id not in pull_request.revisions:
545 raise JSONRPCError(
547 raise JSONRPCError(
546 'Invalid commit_id `%s` for this pull request.' % commit_id)
548 'Invalid commit_id `%s` for this pull request.' % commit_id)
547
549
548 allowed_to_change_status = PullRequestModel().check_user_change_status(
550 allowed_to_change_status = PullRequestModel().check_user_change_status(
549 pull_request, apiuser)
551 pull_request, apiuser)
550
552
551 # if commit_id is passed re-validated if user is allowed to change status
553 # if commit_id is passed re-validated if user is allowed to change status
552 # based on latest commit_id from the PR
554 # based on latest commit_id from the PR
553 if commit_id:
555 if commit_id:
554 commit_idx = pull_request.revisions.index(commit_id)
556 commit_idx = pull_request.revisions.index(commit_id)
555 if commit_idx != 0:
557 if commit_idx != 0:
556 allowed_to_change_status = False
558 allowed_to_change_status = False
557
559
558 if resolves_comment_id:
560 if resolves_comment_id:
559 comment = ChangesetComment.get(resolves_comment_id)
561 comment = ChangesetComment.get(resolves_comment_id)
560 if not comment:
562 if not comment:
561 raise JSONRPCError(
563 raise JSONRPCError(
562 'Invalid resolves_comment_id `%s` for this pull request.'
564 'Invalid resolves_comment_id `%s` for this pull request.'
563 % resolves_comment_id)
565 % resolves_comment_id)
564 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
566 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
565 raise JSONRPCError(
567 raise JSONRPCError(
566 'Comment `%s` is wrong type for setting status to resolved.'
568 'Comment `%s` is wrong type for setting status to resolved.'
567 % resolves_comment_id)
569 % resolves_comment_id)
568
570
569 text = message
571 text = message
570 status_label = ChangesetStatus.get_status_lbl(status)
572 status_label = ChangesetStatus.get_status_lbl(status)
571 if status and allowed_to_change_status:
573 if status and allowed_to_change_status:
572 st_message = ('Status change %(transition_icon)s %(status)s'
574 st_message = ('Status change %(transition_icon)s %(status)s'
573 % {'transition_icon': '>', 'status': status_label})
575 % {'transition_icon': '>', 'status': status_label})
574 text = message or st_message
576 text = message or st_message
575
577
576 rc_config = SettingsModel().get_all_settings()
578 rc_config = SettingsModel().get_all_settings()
577 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
579 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
578
580
579 status_change = status and allowed_to_change_status
581 status_change = status and allowed_to_change_status
580 comment = CommentsModel().create(
582 comment = CommentsModel().create(
581 text=text,
583 text=text,
582 repo=pull_request.target_repo.repo_id,
584 repo=pull_request.target_repo.repo_id,
583 user=apiuser.user_id,
585 user=apiuser.user_id,
584 pull_request=pull_request.pull_request_id,
586 pull_request=pull_request.pull_request_id,
585 f_path=None,
587 f_path=None,
586 line_no=None,
588 line_no=None,
587 status_change=(status_label if status_change else None),
589 status_change=(status_label if status_change else None),
588 status_change_type=(status if status_change else None),
590 status_change_type=(status if status_change else None),
589 closing_pr=False,
591 closing_pr=False,
590 renderer=renderer,
592 renderer=renderer,
591 comment_type=comment_type,
593 comment_type=comment_type,
592 resolves_comment_id=resolves_comment_id,
594 resolves_comment_id=resolves_comment_id,
593 auth_user=auth_user,
595 auth_user=auth_user,
594 extra_recipients=extra_recipients,
596 extra_recipients=extra_recipients,
595 send_email=send_email
597 send_email=send_email
596 )
598 )
597
599
598 if allowed_to_change_status and status:
600 if allowed_to_change_status and status:
599 old_calculated_status = pull_request.calculated_review_status()
601 old_calculated_status = pull_request.calculated_review_status()
600 ChangesetStatusModel().set_status(
602 ChangesetStatusModel().set_status(
601 pull_request.target_repo.repo_id,
603 pull_request.target_repo.repo_id,
602 status,
604 status,
603 apiuser.user_id,
605 apiuser.user_id,
604 comment,
606 comment,
605 pull_request=pull_request.pull_request_id
607 pull_request=pull_request.pull_request_id
606 )
608 )
607 Session().flush()
609 Session().flush()
608
610
609 Session().commit()
611 Session().commit()
610
612
611 PullRequestModel().trigger_pull_request_hook(
613 PullRequestModel().trigger_pull_request_hook(
612 pull_request, apiuser, 'comment',
614 pull_request, apiuser, 'comment',
613 data={'comment': comment})
615 data={'comment': comment})
614
616
615 if allowed_to_change_status and status:
617 if allowed_to_change_status and status:
616 # we now calculate the status of pull request, and based on that
618 # we now calculate the status of pull request, and based on that
617 # calculation we set the commits status
619 # calculation we set the commits status
618 calculated_status = pull_request.calculated_review_status()
620 calculated_status = pull_request.calculated_review_status()
619 if old_calculated_status != calculated_status:
621 if old_calculated_status != calculated_status:
620 PullRequestModel().trigger_pull_request_hook(
622 PullRequestModel().trigger_pull_request_hook(
621 pull_request, apiuser, 'review_status_change',
623 pull_request, apiuser, 'review_status_change',
622 data={'status': calculated_status})
624 data={'status': calculated_status})
623
625
624 data = {
626 data = {
625 'pull_request_id': pull_request.pull_request_id,
627 'pull_request_id': pull_request.pull_request_id,
626 'comment_id': comment.comment_id if comment else None,
628 'comment_id': comment.comment_id if comment else None,
627 'status': {'given': status, 'was_changed': status_change},
629 'status': {'given': status, 'was_changed': status_change},
628 }
630 }
629 return data
631 return data
630
632
631
633
632 @jsonrpc_method()
634 @jsonrpc_method()
633 def create_pull_request(
635 def create_pull_request(
634 request, apiuser, source_repo, target_repo, source_ref, target_ref,
636 request, apiuser, source_repo, target_repo, source_ref, target_ref,
635 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
637 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
636 description_renderer=Optional(''), reviewers=Optional(None)):
638 description_renderer=Optional(''), reviewers=Optional(None)):
637 """
639 """
638 Creates a new pull request.
640 Creates a new pull request.
639
641
640 Accepts refs in the following formats:
642 Accepts refs in the following formats:
641
643
642 * branch:<branch_name>:<sha>
644 * branch:<branch_name>:<sha>
643 * branch:<branch_name>
645 * branch:<branch_name>
644 * bookmark:<bookmark_name>:<sha> (Mercurial only)
646 * bookmark:<bookmark_name>:<sha> (Mercurial only)
645 * bookmark:<bookmark_name> (Mercurial only)
647 * bookmark:<bookmark_name> (Mercurial only)
646
648
647 :param apiuser: This is filled automatically from the |authtoken|.
649 :param apiuser: This is filled automatically from the |authtoken|.
648 :type apiuser: AuthUser
650 :type apiuser: AuthUser
649 :param source_repo: Set the source repository name.
651 :param source_repo: Set the source repository name.
650 :type source_repo: str
652 :type source_repo: str
651 :param target_repo: Set the target repository name.
653 :param target_repo: Set the target repository name.
652 :type target_repo: str
654 :type target_repo: str
653 :param source_ref: Set the source ref name.
655 :param source_ref: Set the source ref name.
654 :type source_ref: str
656 :type source_ref: str
655 :param target_ref: Set the target ref name.
657 :param target_ref: Set the target ref name.
656 :type target_ref: str
658 :type target_ref: str
657 :param owner: user_id or username
659 :param owner: user_id or username
658 :type owner: Optional(str)
660 :type owner: Optional(str)
659 :param title: Optionally Set the pull request title, it's generated otherwise
661 :param title: Optionally Set the pull request title, it's generated otherwise
660 :type title: str
662 :type title: str
661 :param description: Set the pull request description.
663 :param description: Set the pull request description.
662 :type description: Optional(str)
664 :type description: Optional(str)
663 :type description_renderer: Optional(str)
665 :type description_renderer: Optional(str)
664 :param description_renderer: Set pull request renderer for the description.
666 :param description_renderer: Set pull request renderer for the description.
665 It should be 'rst', 'markdown' or 'plain'. If not give default
667 It should be 'rst', 'markdown' or 'plain'. If not give default
666 system renderer will be used
668 system renderer will be used
667 :param reviewers: Set the new pull request reviewers list.
669 :param reviewers: Set the new pull request reviewers list.
668 Reviewer defined by review rules will be added automatically to the
670 Reviewer defined by review rules will be added automatically to the
669 defined list.
671 defined list.
670 :type reviewers: Optional(list)
672 :type reviewers: Optional(list)
671 Accepts username strings or objects of the format:
673 Accepts username strings or objects of the format:
672
674
673 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
675 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
674 """
676 """
675
677
676 source_db_repo = get_repo_or_error(source_repo)
678 source_db_repo = get_repo_or_error(source_repo)
677 target_db_repo = get_repo_or_error(target_repo)
679 target_db_repo = get_repo_or_error(target_repo)
678 if not has_superadmin_permission(apiuser):
680 if not has_superadmin_permission(apiuser):
679 _perms = ('repository.admin', 'repository.write', 'repository.read',)
681 _perms = ('repository.admin', 'repository.write', 'repository.read',)
680 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
682 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
681
683
682 owner = validate_set_owner_permissions(apiuser, owner)
684 owner = validate_set_owner_permissions(apiuser, owner)
683
685
684 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
686 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
685 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
687 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
686
688
687 source_scm = source_db_repo.scm_instance()
689 source_scm = source_db_repo.scm_instance()
688 target_scm = target_db_repo.scm_instance()
690 target_scm = target_db_repo.scm_instance()
689
691
690 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
692 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
691 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
693 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
692
694
693 ancestor = source_scm.get_common_ancestor(
695 ancestor = source_scm.get_common_ancestor(
694 source_commit.raw_id, target_commit.raw_id, target_scm)
696 source_commit.raw_id, target_commit.raw_id, target_scm)
695 if not ancestor:
697 if not ancestor:
696 raise JSONRPCError('no common ancestor found')
698 raise JSONRPCError('no common ancestor found')
697
699
698 # recalculate target ref based on ancestor
700 # recalculate target ref based on ancestor
699 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
701 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
700 full_target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
702 full_target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
701
703
702 commit_ranges = target_scm.compare(
704 commit_ranges = target_scm.compare(
703 target_commit.raw_id, source_commit.raw_id, source_scm,
705 target_commit.raw_id, source_commit.raw_id, source_scm,
704 merge=True, pre_load=[])
706 merge=True, pre_load=[])
705
707
706 if not commit_ranges:
708 if not commit_ranges:
707 raise JSONRPCError('no commits found')
709 raise JSONRPCError('no commits found')
708
710
709 reviewer_objects = Optional.extract(reviewers) or []
711 reviewer_objects = Optional.extract(reviewers) or []
710
712
711 # serialize and validate passed in given reviewers
713 # serialize and validate passed in given reviewers
712 if reviewer_objects:
714 if reviewer_objects:
713 schema = ReviewerListSchema()
715 schema = ReviewerListSchema()
714 try:
716 try:
715 reviewer_objects = schema.deserialize(reviewer_objects)
717 reviewer_objects = schema.deserialize(reviewer_objects)
716 except Invalid as err:
718 except Invalid as err:
717 raise JSONRPCValidationError(colander_exc=err)
719 raise JSONRPCValidationError(colander_exc=err)
718
720
719 # validate users
721 # validate users
720 for reviewer_object in reviewer_objects:
722 for reviewer_object in reviewer_objects:
721 user = get_user_or_error(reviewer_object['username'])
723 user = get_user_or_error(reviewer_object['username'])
722 reviewer_object['user_id'] = user.user_id
724 reviewer_object['user_id'] = user.user_id
723
725
724 get_default_reviewers_data, validate_default_reviewers = \
726 get_default_reviewers_data, validate_default_reviewers = \
725 PullRequestModel().get_reviewer_functions()
727 PullRequestModel().get_reviewer_functions()
726
728
727 # recalculate reviewers logic, to make sure we can validate this
729 # recalculate reviewers logic, to make sure we can validate this
728 reviewer_rules = get_default_reviewers_data(
730 reviewer_rules = get_default_reviewers_data(
729 owner, source_db_repo,
731 owner, source_db_repo,
730 source_commit, target_db_repo, target_commit)
732 source_commit, target_db_repo, target_commit)
731
733
732 # now MERGE our given with the calculated
734 # now MERGE our given with the calculated
733 reviewer_objects = reviewer_rules['reviewers'] + reviewer_objects
735 reviewer_objects = reviewer_rules['reviewers'] + reviewer_objects
734
736
735 try:
737 try:
736 reviewers = validate_default_reviewers(
738 reviewers = validate_default_reviewers(
737 reviewer_objects, reviewer_rules)
739 reviewer_objects, reviewer_rules)
738 except ValueError as e:
740 except ValueError as e:
739 raise JSONRPCError('Reviewers Validation: {}'.format(e))
741 raise JSONRPCError('Reviewers Validation: {}'.format(e))
740
742
741 title = Optional.extract(title)
743 title = Optional.extract(title)
742 if not title:
744 if not title:
743 title_source_ref = source_ref.split(':', 2)[1]
745 title_source_ref = source_ref.split(':', 2)[1]
744 title = PullRequestModel().generate_pullrequest_title(
746 title = PullRequestModel().generate_pullrequest_title(
745 source=source_repo,
747 source=source_repo,
746 source_ref=title_source_ref,
748 source_ref=title_source_ref,
747 target=target_repo
749 target=target_repo
748 )
750 )
749 # fetch renderer, if set fallback to plain in case of PR
751 # fetch renderer, if set fallback to plain in case of PR
750 rc_config = SettingsModel().get_all_settings()
752 rc_config = SettingsModel().get_all_settings()
751 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
753 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
752 description = Optional.extract(description)
754 description = Optional.extract(description)
753 description_renderer = Optional.extract(description_renderer) or default_system_renderer
755 description_renderer = Optional.extract(description_renderer) or default_system_renderer
754
756
755 pull_request = PullRequestModel().create(
757 pull_request = PullRequestModel().create(
756 created_by=owner.user_id,
758 created_by=owner.user_id,
757 source_repo=source_repo,
759 source_repo=source_repo,
758 source_ref=full_source_ref,
760 source_ref=full_source_ref,
759 target_repo=target_repo,
761 target_repo=target_repo,
760 target_ref=full_target_ref,
762 target_ref=full_target_ref,
761 revisions=[commit.raw_id for commit in reversed(commit_ranges)],
763 revisions=[commit.raw_id for commit in reversed(commit_ranges)],
762 reviewers=reviewers,
764 reviewers=reviewers,
763 title=title,
765 title=title,
764 description=description,
766 description=description,
765 description_renderer=description_renderer,
767 description_renderer=description_renderer,
766 reviewer_data=reviewer_rules,
768 reviewer_data=reviewer_rules,
767 auth_user=apiuser
769 auth_user=apiuser
768 )
770 )
769
771
770 Session().commit()
772 Session().commit()
771 data = {
773 data = {
772 'msg': 'Created new pull request `{}`'.format(title),
774 'msg': 'Created new pull request `{}`'.format(title),
773 'pull_request_id': pull_request.pull_request_id,
775 'pull_request_id': pull_request.pull_request_id,
774 }
776 }
775 return data
777 return data
776
778
777
779
778 @jsonrpc_method()
780 @jsonrpc_method()
779 def update_pull_request(
781 def update_pull_request(
780 request, apiuser, pullrequestid, repoid=Optional(None),
782 request, apiuser, pullrequestid, repoid=Optional(None),
781 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
783 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
782 reviewers=Optional(None), update_commits=Optional(None)):
784 reviewers=Optional(None), update_commits=Optional(None)):
783 """
785 """
784 Updates a pull request.
786 Updates a pull request.
785
787
786 :param apiuser: This is filled automatically from the |authtoken|.
788 :param apiuser: This is filled automatically from the |authtoken|.
787 :type apiuser: AuthUser
789 :type apiuser: AuthUser
788 :param repoid: Optional repository name or repository ID.
790 :param repoid: Optional repository name or repository ID.
789 :type repoid: str or int
791 :type repoid: str or int
790 :param pullrequestid: The pull request ID.
792 :param pullrequestid: The pull request ID.
791 :type pullrequestid: int
793 :type pullrequestid: int
792 :param title: Set the pull request title.
794 :param title: Set the pull request title.
793 :type title: str
795 :type title: str
794 :param description: Update pull request description.
796 :param description: Update pull request description.
795 :type description: Optional(str)
797 :type description: Optional(str)
796 :type description_renderer: Optional(str)
798 :type description_renderer: Optional(str)
797 :param description_renderer: Update pull request renderer for the description.
799 :param description_renderer: Update pull request renderer for the description.
798 It should be 'rst', 'markdown' or 'plain'
800 It should be 'rst', 'markdown' or 'plain'
799 :param reviewers: Update pull request reviewers list with new value.
801 :param reviewers: Update pull request reviewers list with new value.
800 :type reviewers: Optional(list)
802 :type reviewers: Optional(list)
801 Accepts username strings or objects of the format:
803 Accepts username strings or objects of the format:
802
804
803 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
805 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
804
806
805 :param update_commits: Trigger update of commits for this pull request
807 :param update_commits: Trigger update of commits for this pull request
806 :type: update_commits: Optional(bool)
808 :type: update_commits: Optional(bool)
807
809
808 Example output:
810 Example output:
809
811
810 .. code-block:: bash
812 .. code-block:: bash
811
813
812 id : <id_given_in_input>
814 id : <id_given_in_input>
813 result : {
815 result : {
814 "msg": "Updated pull request `63`",
816 "msg": "Updated pull request `63`",
815 "pull_request": <pull_request_object>,
817 "pull_request": <pull_request_object>,
816 "updated_reviewers": {
818 "updated_reviewers": {
817 "added": [
819 "added": [
818 "username"
820 "username"
819 ],
821 ],
820 "removed": []
822 "removed": []
821 },
823 },
822 "updated_commits": {
824 "updated_commits": {
823 "added": [
825 "added": [
824 "<sha1_hash>"
826 "<sha1_hash>"
825 ],
827 ],
826 "common": [
828 "common": [
827 "<sha1_hash>",
829 "<sha1_hash>",
828 "<sha1_hash>",
830 "<sha1_hash>",
829 ],
831 ],
830 "removed": []
832 "removed": []
831 }
833 }
832 }
834 }
833 error : null
835 error : null
834 """
836 """
835
837
836 pull_request = get_pull_request_or_error(pullrequestid)
838 pull_request = get_pull_request_or_error(pullrequestid)
837 if Optional.extract(repoid):
839 if Optional.extract(repoid):
838 repo = get_repo_or_error(repoid)
840 repo = get_repo_or_error(repoid)
839 else:
841 else:
840 repo = pull_request.target_repo
842 repo = pull_request.target_repo
841
843
842 if not PullRequestModel().check_user_update(
844 if not PullRequestModel().check_user_update(
843 pull_request, apiuser, api=True):
845 pull_request, apiuser, api=True):
844 raise JSONRPCError(
846 raise JSONRPCError(
845 'pull request `%s` update failed, no permission to update.' % (
847 'pull request `%s` update failed, no permission to update.' % (
846 pullrequestid,))
848 pullrequestid,))
847 if pull_request.is_closed():
849 if pull_request.is_closed():
848 raise JSONRPCError(
850 raise JSONRPCError(
849 'pull request `%s` update failed, pull request is closed' % (
851 'pull request `%s` update failed, pull request is closed' % (
850 pullrequestid,))
852 pullrequestid,))
851
853
852 reviewer_objects = Optional.extract(reviewers) or []
854 reviewer_objects = Optional.extract(reviewers) or []
853
855
854 if reviewer_objects:
856 if reviewer_objects:
855 schema = ReviewerListSchema()
857 schema = ReviewerListSchema()
856 try:
858 try:
857 reviewer_objects = schema.deserialize(reviewer_objects)
859 reviewer_objects = schema.deserialize(reviewer_objects)
858 except Invalid as err:
860 except Invalid as err:
859 raise JSONRPCValidationError(colander_exc=err)
861 raise JSONRPCValidationError(colander_exc=err)
860
862
861 # validate users
863 # validate users
862 for reviewer_object in reviewer_objects:
864 for reviewer_object in reviewer_objects:
863 user = get_user_or_error(reviewer_object['username'])
865 user = get_user_or_error(reviewer_object['username'])
864 reviewer_object['user_id'] = user.user_id
866 reviewer_object['user_id'] = user.user_id
865
867
866 get_default_reviewers_data, get_validated_reviewers = \
868 get_default_reviewers_data, get_validated_reviewers = \
867 PullRequestModel().get_reviewer_functions()
869 PullRequestModel().get_reviewer_functions()
868
870
869 # re-use stored rules
871 # re-use stored rules
870 reviewer_rules = pull_request.reviewer_data
872 reviewer_rules = pull_request.reviewer_data
871 try:
873 try:
872 reviewers = get_validated_reviewers(
874 reviewers = get_validated_reviewers(
873 reviewer_objects, reviewer_rules)
875 reviewer_objects, reviewer_rules)
874 except ValueError as e:
876 except ValueError as e:
875 raise JSONRPCError('Reviewers Validation: {}'.format(e))
877 raise JSONRPCError('Reviewers Validation: {}'.format(e))
876 else:
878 else:
877 reviewers = []
879 reviewers = []
878
880
879 title = Optional.extract(title)
881 title = Optional.extract(title)
880 description = Optional.extract(description)
882 description = Optional.extract(description)
881 description_renderer = Optional.extract(description_renderer)
883 description_renderer = Optional.extract(description_renderer)
882
884
883 if title or description:
885 if title or description:
884 PullRequestModel().edit(
886 PullRequestModel().edit(
885 pull_request,
887 pull_request,
886 title or pull_request.title,
888 title or pull_request.title,
887 description or pull_request.description,
889 description or pull_request.description,
888 description_renderer or pull_request.description_renderer,
890 description_renderer or pull_request.description_renderer,
889 apiuser)
891 apiuser)
890 Session().commit()
892 Session().commit()
891
893
892 commit_changes = {"added": [], "common": [], "removed": []}
894 commit_changes = {"added": [], "common": [], "removed": []}
893 if str2bool(Optional.extract(update_commits)):
895 if str2bool(Optional.extract(update_commits)):
894
896
895 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
897 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
896 raise JSONRPCError(
898 raise JSONRPCError(
897 'Operation forbidden because pull request is in state {}, '
899 'Operation forbidden because pull request is in state {}, '
898 'only state {} is allowed.'.format(
900 'only state {} is allowed.'.format(
899 pull_request.pull_request_state, PullRequest.STATE_CREATED))
901 pull_request.pull_request_state, PullRequest.STATE_CREATED))
900
902
901 with pull_request.set_state(PullRequest.STATE_UPDATING):
903 with pull_request.set_state(PullRequest.STATE_UPDATING):
902 if PullRequestModel().has_valid_update_type(pull_request):
904 if PullRequestModel().has_valid_update_type(pull_request):
903 db_user = apiuser.get_instance()
905 db_user = apiuser.get_instance()
904 update_response = PullRequestModel().update_commits(
906 update_response = PullRequestModel().update_commits(
905 pull_request, db_user)
907 pull_request, db_user)
906 commit_changes = update_response.changes or commit_changes
908 commit_changes = update_response.changes or commit_changes
907 Session().commit()
909 Session().commit()
908
910
909 reviewers_changes = {"added": [], "removed": []}
911 reviewers_changes = {"added": [], "removed": []}
910 if reviewers:
912 if reviewers:
911 old_calculated_status = pull_request.calculated_review_status()
913 old_calculated_status = pull_request.calculated_review_status()
912 added_reviewers, removed_reviewers = \
914 added_reviewers, removed_reviewers = \
913 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
915 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
914
916
915 reviewers_changes['added'] = sorted(
917 reviewers_changes['added'] = sorted(
916 [get_user_or_error(n).username for n in added_reviewers])
918 [get_user_or_error(n).username for n in added_reviewers])
917 reviewers_changes['removed'] = sorted(
919 reviewers_changes['removed'] = sorted(
918 [get_user_or_error(n).username for n in removed_reviewers])
920 [get_user_or_error(n).username for n in removed_reviewers])
919 Session().commit()
921 Session().commit()
920
922
921 # trigger status changed if change in reviewers changes the status
923 # trigger status changed if change in reviewers changes the status
922 calculated_status = pull_request.calculated_review_status()
924 calculated_status = pull_request.calculated_review_status()
923 if old_calculated_status != calculated_status:
925 if old_calculated_status != calculated_status:
924 PullRequestModel().trigger_pull_request_hook(
926 PullRequestModel().trigger_pull_request_hook(
925 pull_request, apiuser, 'review_status_change',
927 pull_request, apiuser, 'review_status_change',
926 data={'status': calculated_status})
928 data={'status': calculated_status})
927
929
928 data = {
930 data = {
929 'msg': 'Updated pull request `{}`'.format(
931 'msg': 'Updated pull request `{}`'.format(
930 pull_request.pull_request_id),
932 pull_request.pull_request_id),
931 'pull_request': pull_request.get_api_data(),
933 'pull_request': pull_request.get_api_data(),
932 'updated_commits': commit_changes,
934 'updated_commits': commit_changes,
933 'updated_reviewers': reviewers_changes
935 'updated_reviewers': reviewers_changes
934 }
936 }
935
937
936 return data
938 return data
937
939
938
940
939 @jsonrpc_method()
941 @jsonrpc_method()
940 def close_pull_request(
942 def close_pull_request(
941 request, apiuser, pullrequestid, repoid=Optional(None),
943 request, apiuser, pullrequestid, repoid=Optional(None),
942 userid=Optional(OAttr('apiuser')), message=Optional('')):
944 userid=Optional(OAttr('apiuser')), message=Optional('')):
943 """
945 """
944 Close the pull request specified by `pullrequestid`.
946 Close the pull request specified by `pullrequestid`.
945
947
946 :param apiuser: This is filled automatically from the |authtoken|.
948 :param apiuser: This is filled automatically from the |authtoken|.
947 :type apiuser: AuthUser
949 :type apiuser: AuthUser
948 :param repoid: Repository name or repository ID to which the pull
950 :param repoid: Repository name or repository ID to which the pull
949 request belongs.
951 request belongs.
950 :type repoid: str or int
952 :type repoid: str or int
951 :param pullrequestid: ID of the pull request to be closed.
953 :param pullrequestid: ID of the pull request to be closed.
952 :type pullrequestid: int
954 :type pullrequestid: int
953 :param userid: Close the pull request as this user.
955 :param userid: Close the pull request as this user.
954 :type userid: Optional(str or int)
956 :type userid: Optional(str or int)
955 :param message: Optional message to close the Pull Request with. If not
957 :param message: Optional message to close the Pull Request with. If not
956 specified it will be generated automatically.
958 specified it will be generated automatically.
957 :type message: Optional(str)
959 :type message: Optional(str)
958
960
959 Example output:
961 Example output:
960
962
961 .. code-block:: bash
963 .. code-block:: bash
962
964
963 "id": <id_given_in_input>,
965 "id": <id_given_in_input>,
964 "result": {
966 "result": {
965 "pull_request_id": "<int>",
967 "pull_request_id": "<int>",
966 "close_status": "<str:status_lbl>,
968 "close_status": "<str:status_lbl>,
967 "closed": "<bool>"
969 "closed": "<bool>"
968 },
970 },
969 "error": null
971 "error": null
970
972
971 """
973 """
972 _ = request.translate
974 _ = request.translate
973
975
974 pull_request = get_pull_request_or_error(pullrequestid)
976 pull_request = get_pull_request_or_error(pullrequestid)
975 if Optional.extract(repoid):
977 if Optional.extract(repoid):
976 repo = get_repo_or_error(repoid)
978 repo = get_repo_or_error(repoid)
977 else:
979 else:
978 repo = pull_request.target_repo
980 repo = pull_request.target_repo
979
981
980 if not isinstance(userid, Optional):
982 if not isinstance(userid, Optional):
981 if (has_superadmin_permission(apiuser) or
983 if (has_superadmin_permission(apiuser) or
982 HasRepoPermissionAnyApi('repository.admin')(
984 HasRepoPermissionAnyApi('repository.admin')(
983 user=apiuser, repo_name=repo.repo_name)):
985 user=apiuser, repo_name=repo.repo_name)):
984 apiuser = get_user_or_error(userid)
986 apiuser = get_user_or_error(userid)
985 else:
987 else:
986 raise JSONRPCError('userid is not the same as your user')
988 raise JSONRPCError('userid is not the same as your user')
987
989
988 if pull_request.is_closed():
990 if pull_request.is_closed():
989 raise JSONRPCError(
991 raise JSONRPCError(
990 'pull request `%s` is already closed' % (pullrequestid,))
992 'pull request `%s` is already closed' % (pullrequestid,))
991
993
992 # only owner or admin or person with write permissions
994 # only owner or admin or person with write permissions
993 allowed_to_close = PullRequestModel().check_user_update(
995 allowed_to_close = PullRequestModel().check_user_update(
994 pull_request, apiuser, api=True)
996 pull_request, apiuser, api=True)
995
997
996 if not allowed_to_close:
998 if not allowed_to_close:
997 raise JSONRPCError(
999 raise JSONRPCError(
998 'pull request `%s` close failed, no permission to close.' % (
1000 'pull request `%s` close failed, no permission to close.' % (
999 pullrequestid,))
1001 pullrequestid,))
1000
1002
1001 # message we're using to close the PR, else it's automatically generated
1003 # message we're using to close the PR, else it's automatically generated
1002 message = Optional.extract(message)
1004 message = Optional.extract(message)
1003
1005
1004 # finally close the PR, with proper message comment
1006 # finally close the PR, with proper message comment
1005 comment, status = PullRequestModel().close_pull_request_with_comment(
1007 comment, status = PullRequestModel().close_pull_request_with_comment(
1006 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1008 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1007 status_lbl = ChangesetStatus.get_status_lbl(status)
1009 status_lbl = ChangesetStatus.get_status_lbl(status)
1008
1010
1009 Session().commit()
1011 Session().commit()
1010
1012
1011 data = {
1013 data = {
1012 'pull_request_id': pull_request.pull_request_id,
1014 'pull_request_id': pull_request.pull_request_id,
1013 'close_status': status_lbl,
1015 'close_status': status_lbl,
1014 'closed': True,
1016 'closed': True,
1015 }
1017 }
1016 return data
1018 return data
@@ -1,2343 +1,2343 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger, rc_cache
32 from rhodecode.lib import audit_logger, rc_cache
33 from rhodecode.lib import repo_maintenance
33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 from rhodecode.lib.celerylib.utils import get_task_id
35 from rhodecode.lib.celerylib.utils import get_task_id
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 from rhodecode.lib.vcs import RepositoryError
39 from rhodecode.lib.vcs import RepositoryError
40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
40 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
41 from rhodecode.model.changeset_status import ChangesetStatusModel
41 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.comment import CommentsModel
43 from rhodecode.model.db import (
43 from rhodecode.model.db import (
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
44 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
45 ChangesetComment)
45 ChangesetComment)
46 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.permission import PermissionModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.scm import ScmModel, RepoList
48 from rhodecode.model.scm import ScmModel, RepoList
49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
49 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
50 from rhodecode.model import validation_schema
50 from rhodecode.model import validation_schema
51 from rhodecode.model.validation_schema.schemas import repo_schema
51 from rhodecode.model.validation_schema.schemas import repo_schema
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 @jsonrpc_method()
56 @jsonrpc_method()
57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
57 def get_repo(request, apiuser, repoid, cache=Optional(True)):
58 """
58 """
59 Gets an existing repository by its name or repository_id.
59 Gets an existing repository by its name or repository_id.
60
60
61 The members section so the output returns users groups or users
61 The members section so the output returns users groups or users
62 associated with that repository.
62 associated with that repository.
63
63
64 This command can only be run using an |authtoken| with admin rights,
64 This command can only be run using an |authtoken| with admin rights,
65 or users with at least read rights to the |repo|.
65 or users with at least read rights to the |repo|.
66
66
67 :param apiuser: This is filled automatically from the |authtoken|.
67 :param apiuser: This is filled automatically from the |authtoken|.
68 :type apiuser: AuthUser
68 :type apiuser: AuthUser
69 :param repoid: The repository name or repository id.
69 :param repoid: The repository name or repository id.
70 :type repoid: str or int
70 :type repoid: str or int
71 :param cache: use the cached value for last changeset
71 :param cache: use the cached value for last changeset
72 :type: cache: Optional(bool)
72 :type: cache: Optional(bool)
73
73
74 Example output:
74 Example output:
75
75
76 .. code-block:: bash
76 .. code-block:: bash
77
77
78 {
78 {
79 "error": null,
79 "error": null,
80 "id": <repo_id>,
80 "id": <repo_id>,
81 "result": {
81 "result": {
82 "clone_uri": null,
82 "clone_uri": null,
83 "created_on": "timestamp",
83 "created_on": "timestamp",
84 "description": "repo description",
84 "description": "repo description",
85 "enable_downloads": false,
85 "enable_downloads": false,
86 "enable_locking": false,
86 "enable_locking": false,
87 "enable_statistics": false,
87 "enable_statistics": false,
88 "followers": [
88 "followers": [
89 {
89 {
90 "active": true,
90 "active": true,
91 "admin": false,
91 "admin": false,
92 "api_key": "****************************************",
92 "api_key": "****************************************",
93 "api_keys": [
93 "api_keys": [
94 "****************************************"
94 "****************************************"
95 ],
95 ],
96 "email": "user@example.com",
96 "email": "user@example.com",
97 "emails": [
97 "emails": [
98 "user@example.com"
98 "user@example.com"
99 ],
99 ],
100 "extern_name": "rhodecode",
100 "extern_name": "rhodecode",
101 "extern_type": "rhodecode",
101 "extern_type": "rhodecode",
102 "firstname": "username",
102 "firstname": "username",
103 "ip_addresses": [],
103 "ip_addresses": [],
104 "language": null,
104 "language": null,
105 "last_login": "2015-09-16T17:16:35.854",
105 "last_login": "2015-09-16T17:16:35.854",
106 "lastname": "surname",
106 "lastname": "surname",
107 "user_id": <user_id>,
107 "user_id": <user_id>,
108 "username": "name"
108 "username": "name"
109 }
109 }
110 ],
110 ],
111 "fork_of": "parent-repo",
111 "fork_of": "parent-repo",
112 "landing_rev": [
112 "landing_rev": [
113 "rev",
113 "rev",
114 "tip"
114 "tip"
115 ],
115 ],
116 "last_changeset": {
116 "last_changeset": {
117 "author": "User <user@example.com>",
117 "author": "User <user@example.com>",
118 "branch": "default",
118 "branch": "default",
119 "date": "timestamp",
119 "date": "timestamp",
120 "message": "last commit message",
120 "message": "last commit message",
121 "parents": [
121 "parents": [
122 {
122 {
123 "raw_id": "commit-id"
123 "raw_id": "commit-id"
124 }
124 }
125 ],
125 ],
126 "raw_id": "commit-id",
126 "raw_id": "commit-id",
127 "revision": <revision number>,
127 "revision": <revision number>,
128 "short_id": "short id"
128 "short_id": "short id"
129 },
129 },
130 "lock_reason": null,
130 "lock_reason": null,
131 "locked_by": null,
131 "locked_by": null,
132 "locked_date": null,
132 "locked_date": null,
133 "owner": "owner-name",
133 "owner": "owner-name",
134 "permissions": [
134 "permissions": [
135 {
135 {
136 "name": "super-admin-name",
136 "name": "super-admin-name",
137 "origin": "super-admin",
137 "origin": "super-admin",
138 "permission": "repository.admin",
138 "permission": "repository.admin",
139 "type": "user"
139 "type": "user"
140 },
140 },
141 {
141 {
142 "name": "owner-name",
142 "name": "owner-name",
143 "origin": "owner",
143 "origin": "owner",
144 "permission": "repository.admin",
144 "permission": "repository.admin",
145 "type": "user"
145 "type": "user"
146 },
146 },
147 {
147 {
148 "name": "user-group-name",
148 "name": "user-group-name",
149 "origin": "permission",
149 "origin": "permission",
150 "permission": "repository.write",
150 "permission": "repository.write",
151 "type": "user_group"
151 "type": "user_group"
152 }
152 }
153 ],
153 ],
154 "private": true,
154 "private": true,
155 "repo_id": 676,
155 "repo_id": 676,
156 "repo_name": "user-group/repo-name",
156 "repo_name": "user-group/repo-name",
157 "repo_type": "hg"
157 "repo_type": "hg"
158 }
158 }
159 }
159 }
160 """
160 """
161
161
162 repo = get_repo_or_error(repoid)
162 repo = get_repo_or_error(repoid)
163 cache = Optional.extract(cache)
163 cache = Optional.extract(cache)
164
164
165 include_secrets = False
165 include_secrets = False
166 if has_superadmin_permission(apiuser):
166 if has_superadmin_permission(apiuser):
167 include_secrets = True
167 include_secrets = True
168 else:
168 else:
169 # check if we have at least read permission for this repo !
169 # check if we have at least read permission for this repo !
170 _perms = (
170 _perms = (
171 'repository.admin', 'repository.write', 'repository.read',)
171 'repository.admin', 'repository.write', 'repository.read',)
172 validate_repo_permissions(apiuser, repoid, repo, _perms)
172 validate_repo_permissions(apiuser, repoid, repo, _perms)
173
173
174 permissions = []
174 permissions = []
175 for _user in repo.permissions():
175 for _user in repo.permissions():
176 user_data = {
176 user_data = {
177 'name': _user.username,
177 'name': _user.username,
178 'permission': _user.permission,
178 'permission': _user.permission,
179 'origin': get_origin(_user),
179 'origin': get_origin(_user),
180 'type': "user",
180 'type': "user",
181 }
181 }
182 permissions.append(user_data)
182 permissions.append(user_data)
183
183
184 for _user_group in repo.permission_user_groups():
184 for _user_group in repo.permission_user_groups():
185 user_group_data = {
185 user_group_data = {
186 'name': _user_group.users_group_name,
186 'name': _user_group.users_group_name,
187 'permission': _user_group.permission,
187 'permission': _user_group.permission,
188 'origin': get_origin(_user_group),
188 'origin': get_origin(_user_group),
189 'type': "user_group",
189 'type': "user_group",
190 }
190 }
191 permissions.append(user_group_data)
191 permissions.append(user_group_data)
192
192
193 following_users = [
193 following_users = [
194 user.user.get_api_data(include_secrets=include_secrets)
194 user.user.get_api_data(include_secrets=include_secrets)
195 for user in repo.followers]
195 for user in repo.followers]
196
196
197 if not cache:
197 if not cache:
198 repo.update_commit_cache()
198 repo.update_commit_cache()
199 data = repo.get_api_data(include_secrets=include_secrets)
199 data = repo.get_api_data(include_secrets=include_secrets)
200 data['permissions'] = permissions
200 data['permissions'] = permissions
201 data['followers'] = following_users
201 data['followers'] = following_users
202 return data
202 return data
203
203
204
204
205 @jsonrpc_method()
205 @jsonrpc_method()
206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
206 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
207 """
207 """
208 Lists all existing repositories.
208 Lists all existing repositories.
209
209
210 This command can only be run using an |authtoken| with admin rights,
210 This command can only be run using an |authtoken| with admin rights,
211 or users with at least read rights to |repos|.
211 or users with at least read rights to |repos|.
212
212
213 :param apiuser: This is filled automatically from the |authtoken|.
213 :param apiuser: This is filled automatically from the |authtoken|.
214 :type apiuser: AuthUser
214 :type apiuser: AuthUser
215 :param root: specify root repository group to fetch repositories.
215 :param root: specify root repository group to fetch repositories.
216 filters the returned repositories to be members of given root group.
216 filters the returned repositories to be members of given root group.
217 :type root: Optional(None)
217 :type root: Optional(None)
218 :param traverse: traverse given root into subrepositories. With this flag
218 :param traverse: traverse given root into subrepositories. With this flag
219 set to False, it will only return top-level repositories from `root`.
219 set to False, it will only return top-level repositories from `root`.
220 if root is empty it will return just top-level repositories.
220 if root is empty it will return just top-level repositories.
221 :type traverse: Optional(True)
221 :type traverse: Optional(True)
222
222
223
223
224 Example output:
224 Example output:
225
225
226 .. code-block:: bash
226 .. code-block:: bash
227
227
228 id : <id_given_in_input>
228 id : <id_given_in_input>
229 result: [
229 result: [
230 {
230 {
231 "repo_id" : "<repo_id>",
231 "repo_id" : "<repo_id>",
232 "repo_name" : "<reponame>"
232 "repo_name" : "<reponame>"
233 "repo_type" : "<repo_type>",
233 "repo_type" : "<repo_type>",
234 "clone_uri" : "<clone_uri>",
234 "clone_uri" : "<clone_uri>",
235 "private": : "<bool>",
235 "private": : "<bool>",
236 "created_on" : "<datetimecreated>",
236 "created_on" : "<datetimecreated>",
237 "description" : "<description>",
237 "description" : "<description>",
238 "landing_rev": "<landing_rev>",
238 "landing_rev": "<landing_rev>",
239 "owner": "<repo_owner>",
239 "owner": "<repo_owner>",
240 "fork_of": "<name_of_fork_parent>",
240 "fork_of": "<name_of_fork_parent>",
241 "enable_downloads": "<bool>",
241 "enable_downloads": "<bool>",
242 "enable_locking": "<bool>",
242 "enable_locking": "<bool>",
243 "enable_statistics": "<bool>",
243 "enable_statistics": "<bool>",
244 },
244 },
245 ...
245 ...
246 ]
246 ]
247 error: null
247 error: null
248 """
248 """
249
249
250 include_secrets = has_superadmin_permission(apiuser)
250 include_secrets = has_superadmin_permission(apiuser)
251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
251 _perms = ('repository.read', 'repository.write', 'repository.admin',)
252 extras = {'user': apiuser}
252 extras = {'user': apiuser}
253
253
254 root = Optional.extract(root)
254 root = Optional.extract(root)
255 traverse = Optional.extract(traverse, binary=True)
255 traverse = Optional.extract(traverse, binary=True)
256
256
257 if root:
257 if root:
258 # verify parent existance, if it's empty return an error
258 # verify parent existance, if it's empty return an error
259 parent = RepoGroup.get_by_group_name(root)
259 parent = RepoGroup.get_by_group_name(root)
260 if not parent:
260 if not parent:
261 raise JSONRPCError(
261 raise JSONRPCError(
262 'Root repository group `{}` does not exist'.format(root))
262 'Root repository group `{}` does not exist'.format(root))
263
263
264 if traverse:
264 if traverse:
265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
265 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
266 else:
266 else:
267 repos = RepoModel().get_repos_for_root(root=parent)
267 repos = RepoModel().get_repos_for_root(root=parent)
268 else:
268 else:
269 if traverse:
269 if traverse:
270 repos = RepoModel().get_all()
270 repos = RepoModel().get_all()
271 else:
271 else:
272 # return just top-level
272 # return just top-level
273 repos = RepoModel().get_repos_for_root(root=None)
273 repos = RepoModel().get_repos_for_root(root=None)
274
274
275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
275 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
276 return [repo.get_api_data(include_secrets=include_secrets)
276 return [repo.get_api_data(include_secrets=include_secrets)
277 for repo in repo_list]
277 for repo in repo_list]
278
278
279
279
280 @jsonrpc_method()
280 @jsonrpc_method()
281 def get_repo_changeset(request, apiuser, repoid, revision,
281 def get_repo_changeset(request, apiuser, repoid, revision,
282 details=Optional('basic')):
282 details=Optional('basic')):
283 """
283 """
284 Returns information about a changeset.
284 Returns information about a changeset.
285
285
286 Additionally parameters define the amount of details returned by
286 Additionally parameters define the amount of details returned by
287 this function.
287 this function.
288
288
289 This command can only be run using an |authtoken| with admin rights,
289 This command can only be run using an |authtoken| with admin rights,
290 or users with at least read rights to the |repo|.
290 or users with at least read rights to the |repo|.
291
291
292 :param apiuser: This is filled automatically from the |authtoken|.
292 :param apiuser: This is filled automatically from the |authtoken|.
293 :type apiuser: AuthUser
293 :type apiuser: AuthUser
294 :param repoid: The repository name or repository id
294 :param repoid: The repository name or repository id
295 :type repoid: str or int
295 :type repoid: str or int
296 :param revision: revision for which listing should be done
296 :param revision: revision for which listing should be done
297 :type revision: str
297 :type revision: str
298 :param details: details can be 'basic|extended|full' full gives diff
298 :param details: details can be 'basic|extended|full' full gives diff
299 info details like the diff itself, and number of changed files etc.
299 info details like the diff itself, and number of changed files etc.
300 :type details: Optional(str)
300 :type details: Optional(str)
301
301
302 """
302 """
303 repo = get_repo_or_error(repoid)
303 repo = get_repo_or_error(repoid)
304 if not has_superadmin_permission(apiuser):
304 if not has_superadmin_permission(apiuser):
305 _perms = (
305 _perms = (
306 'repository.admin', 'repository.write', 'repository.read',)
306 'repository.admin', 'repository.write', 'repository.read',)
307 validate_repo_permissions(apiuser, repoid, repo, _perms)
307 validate_repo_permissions(apiuser, repoid, repo, _perms)
308
308
309 changes_details = Optional.extract(details)
309 changes_details = Optional.extract(details)
310 _changes_details_types = ['basic', 'extended', 'full']
310 _changes_details_types = ['basic', 'extended', 'full']
311 if changes_details not in _changes_details_types:
311 if changes_details not in _changes_details_types:
312 raise JSONRPCError(
312 raise JSONRPCError(
313 'ret_type must be one of %s' % (
313 'ret_type must be one of %s' % (
314 ','.join(_changes_details_types)))
314 ','.join(_changes_details_types)))
315
315
316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
316 pre_load = ['author', 'branch', 'date', 'message', 'parents',
317 'status', '_commit', '_file_paths']
317 'status', '_commit', '_file_paths']
318
318
319 try:
319 try:
320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
320 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
321 except TypeError as e:
321 except TypeError as e:
322 raise JSONRPCError(safe_str(e))
322 raise JSONRPCError(safe_str(e))
323 _cs_json = cs.__json__()
323 _cs_json = cs.__json__()
324 _cs_json['diff'] = build_commit_data(cs, changes_details)
324 _cs_json['diff'] = build_commit_data(cs, changes_details)
325 if changes_details == 'full':
325 if changes_details == 'full':
326 _cs_json['refs'] = cs._get_refs()
326 _cs_json['refs'] = cs._get_refs()
327 return _cs_json
327 return _cs_json
328
328
329
329
330 @jsonrpc_method()
330 @jsonrpc_method()
331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
331 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
332 details=Optional('basic')):
332 details=Optional('basic')):
333 """
333 """
334 Returns a set of commits limited by the number starting
334 Returns a set of commits limited by the number starting
335 from the `start_rev` option.
335 from the `start_rev` option.
336
336
337 Additional parameters define the amount of details returned by this
337 Additional parameters define the amount of details returned by this
338 function.
338 function.
339
339
340 This command can only be run using an |authtoken| with admin rights,
340 This command can only be run using an |authtoken| with admin rights,
341 or users with at least read rights to |repos|.
341 or users with at least read rights to |repos|.
342
342
343 :param apiuser: This is filled automatically from the |authtoken|.
343 :param apiuser: This is filled automatically from the |authtoken|.
344 :type apiuser: AuthUser
344 :type apiuser: AuthUser
345 :param repoid: The repository name or repository ID.
345 :param repoid: The repository name or repository ID.
346 :type repoid: str or int
346 :type repoid: str or int
347 :param start_rev: The starting revision from where to get changesets.
347 :param start_rev: The starting revision from where to get changesets.
348 :type start_rev: str
348 :type start_rev: str
349 :param limit: Limit the number of commits to this amount
349 :param limit: Limit the number of commits to this amount
350 :type limit: str or int
350 :type limit: str or int
351 :param details: Set the level of detail returned. Valid option are:
351 :param details: Set the level of detail returned. Valid option are:
352 ``basic``, ``extended`` and ``full``.
352 ``basic``, ``extended`` and ``full``.
353 :type details: Optional(str)
353 :type details: Optional(str)
354
354
355 .. note::
355 .. note::
356
356
357 Setting the parameter `details` to the value ``full`` is extensive
357 Setting the parameter `details` to the value ``full`` is extensive
358 and returns details like the diff itself, and the number
358 and returns details like the diff itself, and the number
359 of changed files.
359 of changed files.
360
360
361 """
361 """
362 repo = get_repo_or_error(repoid)
362 repo = get_repo_or_error(repoid)
363 if not has_superadmin_permission(apiuser):
363 if not has_superadmin_permission(apiuser):
364 _perms = (
364 _perms = (
365 'repository.admin', 'repository.write', 'repository.read',)
365 'repository.admin', 'repository.write', 'repository.read',)
366 validate_repo_permissions(apiuser, repoid, repo, _perms)
366 validate_repo_permissions(apiuser, repoid, repo, _perms)
367
367
368 changes_details = Optional.extract(details)
368 changes_details = Optional.extract(details)
369 _changes_details_types = ['basic', 'extended', 'full']
369 _changes_details_types = ['basic', 'extended', 'full']
370 if changes_details not in _changes_details_types:
370 if changes_details not in _changes_details_types:
371 raise JSONRPCError(
371 raise JSONRPCError(
372 'ret_type must be one of %s' % (
372 'ret_type must be one of %s' % (
373 ','.join(_changes_details_types)))
373 ','.join(_changes_details_types)))
374
374
375 limit = int(limit)
375 limit = int(limit)
376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
376 pre_load = ['author', 'branch', 'date', 'message', 'parents',
377 'status', '_commit', '_file_paths']
377 'status', '_commit', '_file_paths']
378
378
379 vcs_repo = repo.scm_instance()
379 vcs_repo = repo.scm_instance()
380 # SVN needs a special case to distinguish its index and commit id
380 # SVN needs a special case to distinguish its index and commit id
381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
381 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
382 start_rev = vcs_repo.commit_ids[0]
382 start_rev = vcs_repo.commit_ids[0]
383
383
384 try:
384 try:
385 commits = vcs_repo.get_commits(
385 commits = vcs_repo.get_commits(
386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
386 start_id=start_rev, pre_load=pre_load, translate_tags=False)
387 except TypeError as e:
387 except TypeError as e:
388 raise JSONRPCError(safe_str(e))
388 raise JSONRPCError(safe_str(e))
389 except Exception:
389 except Exception:
390 log.exception('Fetching of commits failed')
390 log.exception('Fetching of commits failed')
391 raise JSONRPCError('Error occurred during commit fetching')
391 raise JSONRPCError('Error occurred during commit fetching')
392
392
393 ret = []
393 ret = []
394 for cnt, commit in enumerate(commits):
394 for cnt, commit in enumerate(commits):
395 if cnt >= limit != -1:
395 if cnt >= limit != -1:
396 break
396 break
397 _cs_json = commit.__json__()
397 _cs_json = commit.__json__()
398 _cs_json['diff'] = build_commit_data(commit, changes_details)
398 _cs_json['diff'] = build_commit_data(commit, changes_details)
399 if changes_details == 'full':
399 if changes_details == 'full':
400 _cs_json['refs'] = {
400 _cs_json['refs'] = {
401 'branches': [commit.branch],
401 'branches': [commit.branch],
402 'bookmarks': getattr(commit, 'bookmarks', []),
402 'bookmarks': getattr(commit, 'bookmarks', []),
403 'tags': commit.tags
403 'tags': commit.tags
404 }
404 }
405 ret.append(_cs_json)
405 ret.append(_cs_json)
406 return ret
406 return ret
407
407
408
408
409 @jsonrpc_method()
409 @jsonrpc_method()
410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
410 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
411 ret_type=Optional('all'), details=Optional('basic'),
411 ret_type=Optional('all'), details=Optional('basic'),
412 max_file_bytes=Optional(None)):
412 max_file_bytes=Optional(None)):
413 """
413 """
414 Returns a list of nodes and children in a flat list for a given
414 Returns a list of nodes and children in a flat list for a given
415 path at given revision.
415 path at given revision.
416
416
417 It's possible to specify ret_type to show only `files` or `dirs`.
417 It's possible to specify ret_type to show only `files` or `dirs`.
418
418
419 This command can only be run using an |authtoken| with admin rights,
419 This command can only be run using an |authtoken| with admin rights,
420 or users with at least read rights to |repos|.
420 or users with at least read rights to |repos|.
421
421
422 :param apiuser: This is filled automatically from the |authtoken|.
422 :param apiuser: This is filled automatically from the |authtoken|.
423 :type apiuser: AuthUser
423 :type apiuser: AuthUser
424 :param repoid: The repository name or repository ID.
424 :param repoid: The repository name or repository ID.
425 :type repoid: str or int
425 :type repoid: str or int
426 :param revision: The revision for which listing should be done.
426 :param revision: The revision for which listing should be done.
427 :type revision: str
427 :type revision: str
428 :param root_path: The path from which to start displaying.
428 :param root_path: The path from which to start displaying.
429 :type root_path: str
429 :type root_path: str
430 :param ret_type: Set the return type. Valid options are
430 :param ret_type: Set the return type. Valid options are
431 ``all`` (default), ``files`` and ``dirs``.
431 ``all`` (default), ``files`` and ``dirs``.
432 :type ret_type: Optional(str)
432 :type ret_type: Optional(str)
433 :param details: Returns extended information about nodes, such as
433 :param details: Returns extended information about nodes, such as
434 md5, binary, and or content.
434 md5, binary, and or content.
435 The valid options are ``basic`` and ``full``.
435 The valid options are ``basic`` and ``full``.
436 :type details: Optional(str)
436 :type details: Optional(str)
437 :param max_file_bytes: Only return file content under this file size bytes
437 :param max_file_bytes: Only return file content under this file size bytes
438 :type details: Optional(int)
438 :type details: Optional(int)
439
439
440 Example output:
440 Example output:
441
441
442 .. code-block:: bash
442 .. code-block:: bash
443
443
444 id : <id_given_in_input>
444 id : <id_given_in_input>
445 result: [
445 result: [
446 {
446 {
447 "binary": false,
447 "binary": false,
448 "content": "File line",
448 "content": "File line",
449 "extension": "md",
449 "extension": "md",
450 "lines": 2,
450 "lines": 2,
451 "md5": "059fa5d29b19c0657e384749480f6422",
451 "md5": "059fa5d29b19c0657e384749480f6422",
452 "mimetype": "text/x-minidsrc",
452 "mimetype": "text/x-minidsrc",
453 "name": "file.md",
453 "name": "file.md",
454 "size": 580,
454 "size": 580,
455 "type": "file"
455 "type": "file"
456 },
456 },
457 ...
457 ...
458 ]
458 ]
459 error: null
459 error: null
460 """
460 """
461
461
462 repo = get_repo_or_error(repoid)
462 repo = get_repo_or_error(repoid)
463 if not has_superadmin_permission(apiuser):
463 if not has_superadmin_permission(apiuser):
464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
464 _perms = ('repository.admin', 'repository.write', 'repository.read',)
465 validate_repo_permissions(apiuser, repoid, repo, _perms)
465 validate_repo_permissions(apiuser, repoid, repo, _perms)
466
466
467 ret_type = Optional.extract(ret_type)
467 ret_type = Optional.extract(ret_type)
468 details = Optional.extract(details)
468 details = Optional.extract(details)
469 _extended_types = ['basic', 'full']
469 _extended_types = ['basic', 'full']
470 if details not in _extended_types:
470 if details not in _extended_types:
471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
471 raise JSONRPCError('ret_type must be one of %s' % (','.join(_extended_types)))
472 extended_info = False
472 extended_info = False
473 content = False
473 content = False
474 if details == 'basic':
474 if details == 'basic':
475 extended_info = True
475 extended_info = True
476
476
477 if details == 'full':
477 if details == 'full':
478 extended_info = content = True
478 extended_info = content = True
479
479
480 _map = {}
480 _map = {}
481 try:
481 try:
482 # check if repo is not empty by any chance, skip quicker if it is.
482 # check if repo is not empty by any chance, skip quicker if it is.
483 _scm = repo.scm_instance()
483 _scm = repo.scm_instance()
484 if _scm.is_empty():
484 if _scm.is_empty():
485 return []
485 return []
486
486
487 _d, _f = ScmModel().get_nodes(
487 _d, _f = ScmModel().get_nodes(
488 repo, revision, root_path, flat=False,
488 repo, revision, root_path, flat=False,
489 extended_info=extended_info, content=content,
489 extended_info=extended_info, content=content,
490 max_file_bytes=max_file_bytes)
490 max_file_bytes=max_file_bytes)
491 _map = {
491 _map = {
492 'all': _d + _f,
492 'all': _d + _f,
493 'files': _f,
493 'files': _f,
494 'dirs': _d,
494 'dirs': _d,
495 }
495 }
496 return _map[ret_type]
496 return _map[ret_type]
497 except KeyError:
497 except KeyError:
498 raise JSONRPCError(
498 raise JSONRPCError(
499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
499 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
500 except Exception:
500 except Exception:
501 log.exception("Exception occurred while trying to get repo nodes")
501 log.exception("Exception occurred while trying to get repo nodes")
502 raise JSONRPCError(
502 raise JSONRPCError(
503 'failed to get repo: `%s` nodes' % repo.repo_name
503 'failed to get repo: `%s` nodes' % repo.repo_name
504 )
504 )
505
505
506
506
507 @jsonrpc_method()
507 @jsonrpc_method()
508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
508 def get_repo_file(request, apiuser, repoid, commit_id, file_path,
509 max_file_bytes=Optional(None), details=Optional('basic'),
509 max_file_bytes=Optional(None), details=Optional('basic'),
510 cache=Optional(True)):
510 cache=Optional(True)):
511 """
511 """
512 Returns a single file from repository at given revision.
512 Returns a single file from repository at given revision.
513
513
514 This command can only be run using an |authtoken| with admin rights,
514 This command can only be run using an |authtoken| with admin rights,
515 or users with at least read rights to |repos|.
515 or users with at least read rights to |repos|.
516
516
517 :param apiuser: This is filled automatically from the |authtoken|.
517 :param apiuser: This is filled automatically from the |authtoken|.
518 :type apiuser: AuthUser
518 :type apiuser: AuthUser
519 :param repoid: The repository name or repository ID.
519 :param repoid: The repository name or repository ID.
520 :type repoid: str or int
520 :type repoid: str or int
521 :param commit_id: The revision for which listing should be done.
521 :param commit_id: The revision for which listing should be done.
522 :type commit_id: str
522 :type commit_id: str
523 :param file_path: The path from which to start displaying.
523 :param file_path: The path from which to start displaying.
524 :type file_path: str
524 :type file_path: str
525 :param details: Returns different set of information about nodes.
525 :param details: Returns different set of information about nodes.
526 The valid options are ``minimal`` ``basic`` and ``full``.
526 The valid options are ``minimal`` ``basic`` and ``full``.
527 :type details: Optional(str)
527 :type details: Optional(str)
528 :param max_file_bytes: Only return file content under this file size bytes
528 :param max_file_bytes: Only return file content under this file size bytes
529 :type max_file_bytes: Optional(int)
529 :type max_file_bytes: Optional(int)
530 :param cache: Use internal caches for fetching files. If disabled fetching
530 :param cache: Use internal caches for fetching files. If disabled fetching
531 files is slower but more memory efficient
531 files is slower but more memory efficient
532 :type cache: Optional(bool)
532 :type cache: Optional(bool)
533
533
534 Example output:
534 Example output:
535
535
536 .. code-block:: bash
536 .. code-block:: bash
537
537
538 id : <id_given_in_input>
538 id : <id_given_in_input>
539 result: {
539 result: {
540 "binary": false,
540 "binary": false,
541 "extension": "py",
541 "extension": "py",
542 "lines": 35,
542 "lines": 35,
543 "content": "....",
543 "content": "....",
544 "md5": "76318336366b0f17ee249e11b0c99c41",
544 "md5": "76318336366b0f17ee249e11b0c99c41",
545 "mimetype": "text/x-python",
545 "mimetype": "text/x-python",
546 "name": "python.py",
546 "name": "python.py",
547 "size": 817,
547 "size": 817,
548 "type": "file",
548 "type": "file",
549 }
549 }
550 error: null
550 error: null
551 """
551 """
552
552
553 repo = get_repo_or_error(repoid)
553 repo = get_repo_or_error(repoid)
554 if not has_superadmin_permission(apiuser):
554 if not has_superadmin_permission(apiuser):
555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
555 _perms = ('repository.admin', 'repository.write', 'repository.read',)
556 validate_repo_permissions(apiuser, repoid, repo, _perms)
556 validate_repo_permissions(apiuser, repoid, repo, _perms)
557
557
558 cache = Optional.extract(cache, binary=True)
558 cache = Optional.extract(cache, binary=True)
559 details = Optional.extract(details)
559 details = Optional.extract(details)
560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
560 _extended_types = ['minimal', 'minimal+search', 'basic', 'full']
561 if details not in _extended_types:
561 if details not in _extended_types:
562 raise JSONRPCError(
562 raise JSONRPCError(
563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
563 'ret_type must be one of %s, got %s' % (','.join(_extended_types)), details)
564 extended_info = False
564 extended_info = False
565 content = False
565 content = False
566
566
567 if details == 'minimal':
567 if details == 'minimal':
568 extended_info = False
568 extended_info = False
569
569
570 elif details == 'basic':
570 elif details == 'basic':
571 extended_info = True
571 extended_info = True
572
572
573 elif details == 'full':
573 elif details == 'full':
574 extended_info = content = True
574 extended_info = content = True
575
575
576 try:
576 try:
577 # check if repo is not empty by any chance, skip quicker if it is.
577 # check if repo is not empty by any chance, skip quicker if it is.
578 _scm = repo.scm_instance()
578 _scm = repo.scm_instance()
579 if _scm.is_empty():
579 if _scm.is_empty():
580 return None
580 return None
581
581
582 node = ScmModel().get_node(
582 node = ScmModel().get_node(
583 repo, commit_id, file_path, extended_info=extended_info,
583 repo, commit_id, file_path, extended_info=extended_info,
584 content=content, max_file_bytes=max_file_bytes, cache=cache)
584 content=content, max_file_bytes=max_file_bytes, cache=cache)
585 except NodeDoesNotExistError:
585 except NodeDoesNotExistError:
586 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
586 raise JSONRPCError('There is no file in repo: `{}` at path `{}` for commit: `{}`'.format(
587 repo.repo_name, file_path, commit_id))
587 repo.repo_name, file_path, commit_id))
588 except Exception:
588 except Exception:
589 log.exception("Exception occurred while trying to get repo %s file",
589 log.exception("Exception occurred while trying to get repo %s file",
590 repo.repo_name)
590 repo.repo_name)
591 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
591 raise JSONRPCError('failed to get repo: `{}` file at path {}'.format(
592 repo.repo_name, file_path))
592 repo.repo_name, file_path))
593
593
594 return node
594 return node
595
595
596
596
597 @jsonrpc_method()
597 @jsonrpc_method()
598 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
598 def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
599 """
599 """
600 Returns a list of tree nodes for path at given revision. This api is built
600 Returns a list of tree nodes for path at given revision. This api is built
601 strictly for usage in full text search building, and shouldn't be consumed
601 strictly for usage in full text search building, and shouldn't be consumed
602
602
603 This command can only be run using an |authtoken| with admin rights,
603 This command can only be run using an |authtoken| with admin rights,
604 or users with at least read rights to |repos|.
604 or users with at least read rights to |repos|.
605
605
606 """
606 """
607
607
608 repo = get_repo_or_error(repoid)
608 repo = get_repo_or_error(repoid)
609 if not has_superadmin_permission(apiuser):
609 if not has_superadmin_permission(apiuser):
610 _perms = ('repository.admin', 'repository.write', 'repository.read',)
610 _perms = ('repository.admin', 'repository.write', 'repository.read',)
611 validate_repo_permissions(apiuser, repoid, repo, _perms)
611 validate_repo_permissions(apiuser, repoid, repo, _perms)
612
612
613 repo_id = repo.repo_id
613 repo_id = repo.repo_id
614 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
614 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
615 cache_on = cache_seconds > 0
615 cache_on = cache_seconds > 0
616
616
617 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
617 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
618 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
618 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
619
619
620 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
620 def compute_fts_tree(cache_ver, repo_id, commit_id, root_path):
621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
621 return ScmModel().get_fts_data(repo_id, commit_id, root_path)
622
622
623 try:
623 try:
624 # check if repo is not empty by any chance, skip quicker if it is.
624 # check if repo is not empty by any chance, skip quicker if it is.
625 _scm = repo.scm_instance()
625 _scm = repo.scm_instance()
626 if _scm.is_empty():
626 if _scm.is_empty():
627 return []
627 return []
628 except RepositoryError:
628 except RepositoryError:
629 log.exception("Exception occurred while trying to get repo nodes")
629 log.exception("Exception occurred while trying to get repo nodes")
630 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
630 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
631
631
632 try:
632 try:
633 # we need to resolve commit_id to a FULL sha for cache to work correctly.
633 # we need to resolve commit_id to a FULL sha for cache to work correctly.
634 # sending 'master' is a pointer that needs to be translated to current commit.
634 # sending 'master' is a pointer that needs to be translated to current commit.
635 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
635 commit_id = _scm.get_commit(commit_id=commit_id).raw_id
636 log.debug(
636 log.debug(
637 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
637 'Computing FTS REPO TREE for repo_id %s commit_id `%s` '
638 'with caching: %s[TTL: %ss]' % (
638 'with caching: %s[TTL: %ss]' % (
639 repo_id, commit_id, cache_on, cache_seconds or 0))
639 repo_id, commit_id, cache_on, cache_seconds or 0))
640
640
641 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
641 tree_files = compute_fts_tree(rc_cache.FILE_TREE_CACHE_VER, repo_id, commit_id, root_path)
642 return tree_files
642 return tree_files
643
643
644 except Exception:
644 except Exception:
645 log.exception("Exception occurred while trying to get repo nodes")
645 log.exception("Exception occurred while trying to get repo nodes")
646 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
646 raise JSONRPCError('failed to get repo: `%s` nodes' % repo.repo_name)
647
647
648
648
649 @jsonrpc_method()
649 @jsonrpc_method()
650 def get_repo_refs(request, apiuser, repoid):
650 def get_repo_refs(request, apiuser, repoid):
651 """
651 """
652 Returns a dictionary of current references. It returns
652 Returns a dictionary of current references. It returns
653 bookmarks, branches, closed_branches, and tags for given repository
653 bookmarks, branches, closed_branches, and tags for given repository
654
654
655 It's possible to specify ret_type to show only `files` or `dirs`.
655 It's possible to specify ret_type to show only `files` or `dirs`.
656
656
657 This command can only be run using an |authtoken| with admin rights,
657 This command can only be run using an |authtoken| with admin rights,
658 or users with at least read rights to |repos|.
658 or users with at least read rights to |repos|.
659
659
660 :param apiuser: This is filled automatically from the |authtoken|.
660 :param apiuser: This is filled automatically from the |authtoken|.
661 :type apiuser: AuthUser
661 :type apiuser: AuthUser
662 :param repoid: The repository name or repository ID.
662 :param repoid: The repository name or repository ID.
663 :type repoid: str or int
663 :type repoid: str or int
664
664
665 Example output:
665 Example output:
666
666
667 .. code-block:: bash
667 .. code-block:: bash
668
668
669 id : <id_given_in_input>
669 id : <id_given_in_input>
670 "result": {
670 "result": {
671 "bookmarks": {
671 "bookmarks": {
672 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
672 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
673 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
673 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
674 },
674 },
675 "branches": {
675 "branches": {
676 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
676 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
677 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
677 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
678 },
678 },
679 "branches_closed": {},
679 "branches_closed": {},
680 "tags": {
680 "tags": {
681 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
681 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
682 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
682 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
683 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
683 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
684 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
684 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
685 }
685 }
686 }
686 }
687 error: null
687 error: null
688 """
688 """
689
689
690 repo = get_repo_or_error(repoid)
690 repo = get_repo_or_error(repoid)
691 if not has_superadmin_permission(apiuser):
691 if not has_superadmin_permission(apiuser):
692 _perms = ('repository.admin', 'repository.write', 'repository.read',)
692 _perms = ('repository.admin', 'repository.write', 'repository.read',)
693 validate_repo_permissions(apiuser, repoid, repo, _perms)
693 validate_repo_permissions(apiuser, repoid, repo, _perms)
694
694
695 try:
695 try:
696 # check if repo is not empty by any chance, skip quicker if it is.
696 # check if repo is not empty by any chance, skip quicker if it is.
697 vcs_instance = repo.scm_instance()
697 vcs_instance = repo.scm_instance()
698 refs = vcs_instance.refs()
698 refs = vcs_instance.refs()
699 return refs
699 return refs
700 except Exception:
700 except Exception:
701 log.exception("Exception occurred while trying to get repo refs")
701 log.exception("Exception occurred while trying to get repo refs")
702 raise JSONRPCError(
702 raise JSONRPCError(
703 'failed to get repo: `%s` references' % repo.repo_name
703 'failed to get repo: `%s` references' % repo.repo_name
704 )
704 )
705
705
706
706
707 @jsonrpc_method()
707 @jsonrpc_method()
708 def create_repo(
708 def create_repo(
709 request, apiuser, repo_name, repo_type,
709 request, apiuser, repo_name, repo_type,
710 owner=Optional(OAttr('apiuser')),
710 owner=Optional(OAttr('apiuser')),
711 description=Optional(''),
711 description=Optional(''),
712 private=Optional(False),
712 private=Optional(False),
713 clone_uri=Optional(None),
713 clone_uri=Optional(None),
714 push_uri=Optional(None),
714 push_uri=Optional(None),
715 landing_rev=Optional(None),
715 landing_rev=Optional(None),
716 enable_statistics=Optional(False),
716 enable_statistics=Optional(False),
717 enable_locking=Optional(False),
717 enable_locking=Optional(False),
718 enable_downloads=Optional(False),
718 enable_downloads=Optional(False),
719 copy_permissions=Optional(False)):
719 copy_permissions=Optional(False)):
720 """
720 """
721 Creates a repository.
721 Creates a repository.
722
722
723 * If the repository name contains "/", repository will be created inside
723 * If the repository name contains "/", repository will be created inside
724 a repository group or nested repository groups
724 a repository group or nested repository groups
725
725
726 For example "foo/bar/repo1" will create |repo| called "repo1" inside
726 For example "foo/bar/repo1" will create |repo| called "repo1" inside
727 group "foo/bar". You have to have permissions to access and write to
727 group "foo/bar". You have to have permissions to access and write to
728 the last repository group ("bar" in this example)
728 the last repository group ("bar" in this example)
729
729
730 This command can only be run using an |authtoken| with at least
730 This command can only be run using an |authtoken| with at least
731 permissions to create repositories, or write permissions to
731 permissions to create repositories, or write permissions to
732 parent repository groups.
732 parent repository groups.
733
733
734 :param apiuser: This is filled automatically from the |authtoken|.
734 :param apiuser: This is filled automatically from the |authtoken|.
735 :type apiuser: AuthUser
735 :type apiuser: AuthUser
736 :param repo_name: Set the repository name.
736 :param repo_name: Set the repository name.
737 :type repo_name: str
737 :type repo_name: str
738 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
738 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
739 :type repo_type: str
739 :type repo_type: str
740 :param owner: user_id or username
740 :param owner: user_id or username
741 :type owner: Optional(str)
741 :type owner: Optional(str)
742 :param description: Set the repository description.
742 :param description: Set the repository description.
743 :type description: Optional(str)
743 :type description: Optional(str)
744 :param private: set repository as private
744 :param private: set repository as private
745 :type private: bool
745 :type private: bool
746 :param clone_uri: set clone_uri
746 :param clone_uri: set clone_uri
747 :type clone_uri: str
747 :type clone_uri: str
748 :param push_uri: set push_uri
748 :param push_uri: set push_uri
749 :type push_uri: str
749 :type push_uri: str
750 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
750 :param landing_rev: <rev_type>:<rev>, e.g branch:default, book:dev, rev:abcd
751 :type landing_rev: str
751 :type landing_rev: str
752 :param enable_locking:
752 :param enable_locking:
753 :type enable_locking: bool
753 :type enable_locking: bool
754 :param enable_downloads:
754 :param enable_downloads:
755 :type enable_downloads: bool
755 :type enable_downloads: bool
756 :param enable_statistics:
756 :param enable_statistics:
757 :type enable_statistics: bool
757 :type enable_statistics: bool
758 :param copy_permissions: Copy permission from group in which the
758 :param copy_permissions: Copy permission from group in which the
759 repository is being created.
759 repository is being created.
760 :type copy_permissions: bool
760 :type copy_permissions: bool
761
761
762
762
763 Example output:
763 Example output:
764
764
765 .. code-block:: bash
765 .. code-block:: bash
766
766
767 id : <id_given_in_input>
767 id : <id_given_in_input>
768 result: {
768 result: {
769 "msg": "Created new repository `<reponame>`",
769 "msg": "Created new repository `<reponame>`",
770 "success": true,
770 "success": true,
771 "task": "<celery task id or None if done sync>"
771 "task": "<celery task id or None if done sync>"
772 }
772 }
773 error: null
773 error: null
774
774
775
775
776 Example error output:
776 Example error output:
777
777
778 .. code-block:: bash
778 .. code-block:: bash
779
779
780 id : <id_given_in_input>
780 id : <id_given_in_input>
781 result : null
781 result : null
782 error : {
782 error : {
783 'failed to create repository `<repo_name>`'
783 'failed to create repository `<repo_name>`'
784 }
784 }
785
785
786 """
786 """
787
787
788 owner = validate_set_owner_permissions(apiuser, owner)
788 owner = validate_set_owner_permissions(apiuser, owner)
789
789
790 description = Optional.extract(description)
790 description = Optional.extract(description)
791 copy_permissions = Optional.extract(copy_permissions)
791 copy_permissions = Optional.extract(copy_permissions)
792 clone_uri = Optional.extract(clone_uri)
792 clone_uri = Optional.extract(clone_uri)
793 push_uri = Optional.extract(push_uri)
793 push_uri = Optional.extract(push_uri)
794
794
795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
795 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
796 if isinstance(private, Optional):
796 if isinstance(private, Optional):
797 private = defs.get('repo_private') or Optional.extract(private)
797 private = defs.get('repo_private') or Optional.extract(private)
798 if isinstance(repo_type, Optional):
798 if isinstance(repo_type, Optional):
799 repo_type = defs.get('repo_type')
799 repo_type = defs.get('repo_type')
800 if isinstance(enable_statistics, Optional):
800 if isinstance(enable_statistics, Optional):
801 enable_statistics = defs.get('repo_enable_statistics')
801 enable_statistics = defs.get('repo_enable_statistics')
802 if isinstance(enable_locking, Optional):
802 if isinstance(enable_locking, Optional):
803 enable_locking = defs.get('repo_enable_locking')
803 enable_locking = defs.get('repo_enable_locking')
804 if isinstance(enable_downloads, Optional):
804 if isinstance(enable_downloads, Optional):
805 enable_downloads = defs.get('repo_enable_downloads')
805 enable_downloads = defs.get('repo_enable_downloads')
806
806
807 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
807 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
808 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
808 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
809 ref_choices = list(set(ref_choices + [landing_ref]))
809 ref_choices = list(set(ref_choices + [landing_ref]))
810
810
811 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
811 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
812
812
813 schema = repo_schema.RepoSchema().bind(
813 schema = repo_schema.RepoSchema().bind(
814 repo_type_options=rhodecode.BACKENDS.keys(),
814 repo_type_options=rhodecode.BACKENDS.keys(),
815 repo_ref_options=ref_choices,
815 repo_ref_options=ref_choices,
816 repo_type=repo_type,
816 repo_type=repo_type,
817 # user caller
817 # user caller
818 user=apiuser)
818 user=apiuser)
819
819
820 try:
820 try:
821 schema_data = schema.deserialize(dict(
821 schema_data = schema.deserialize(dict(
822 repo_name=repo_name,
822 repo_name=repo_name,
823 repo_type=repo_type,
823 repo_type=repo_type,
824 repo_owner=owner.username,
824 repo_owner=owner.username,
825 repo_description=description,
825 repo_description=description,
826 repo_landing_commit_ref=landing_commit_ref,
826 repo_landing_commit_ref=landing_commit_ref,
827 repo_clone_uri=clone_uri,
827 repo_clone_uri=clone_uri,
828 repo_push_uri=push_uri,
828 repo_push_uri=push_uri,
829 repo_private=private,
829 repo_private=private,
830 repo_copy_permissions=copy_permissions,
830 repo_copy_permissions=copy_permissions,
831 repo_enable_statistics=enable_statistics,
831 repo_enable_statistics=enable_statistics,
832 repo_enable_downloads=enable_downloads,
832 repo_enable_downloads=enable_downloads,
833 repo_enable_locking=enable_locking))
833 repo_enable_locking=enable_locking))
834 except validation_schema.Invalid as err:
834 except validation_schema.Invalid as err:
835 raise JSONRPCValidationError(colander_exc=err)
835 raise JSONRPCValidationError(colander_exc=err)
836
836
837 try:
837 try:
838 data = {
838 data = {
839 'owner': owner,
839 'owner': owner,
840 'repo_name': schema_data['repo_group']['repo_name_without_group'],
840 'repo_name': schema_data['repo_group']['repo_name_without_group'],
841 'repo_name_full': schema_data['repo_name'],
841 'repo_name_full': schema_data['repo_name'],
842 'repo_group': schema_data['repo_group']['repo_group_id'],
842 'repo_group': schema_data['repo_group']['repo_group_id'],
843 'repo_type': schema_data['repo_type'],
843 'repo_type': schema_data['repo_type'],
844 'repo_description': schema_data['repo_description'],
844 'repo_description': schema_data['repo_description'],
845 'repo_private': schema_data['repo_private'],
845 'repo_private': schema_data['repo_private'],
846 'clone_uri': schema_data['repo_clone_uri'],
846 'clone_uri': schema_data['repo_clone_uri'],
847 'push_uri': schema_data['repo_push_uri'],
847 'push_uri': schema_data['repo_push_uri'],
848 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
848 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
849 'enable_statistics': schema_data['repo_enable_statistics'],
849 'enable_statistics': schema_data['repo_enable_statistics'],
850 'enable_locking': schema_data['repo_enable_locking'],
850 'enable_locking': schema_data['repo_enable_locking'],
851 'enable_downloads': schema_data['repo_enable_downloads'],
851 'enable_downloads': schema_data['repo_enable_downloads'],
852 'repo_copy_permissions': schema_data['repo_copy_permissions'],
852 'repo_copy_permissions': schema_data['repo_copy_permissions'],
853 }
853 }
854
854
855 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
855 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
856 task_id = get_task_id(task)
856 task_id = get_task_id(task)
857 # no commit, it's done in RepoModel, or async via celery
857 # no commit, it's done in RepoModel, or async via celery
858 return {
858 return {
859 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
859 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
860 'success': True, # cannot return the repo data here since fork
860 'success': True, # cannot return the repo data here since fork
861 # can be done async
861 # can be done async
862 'task': task_id
862 'task': task_id
863 }
863 }
864 except Exception:
864 except Exception:
865 log.exception(
865 log.exception(
866 u"Exception while trying to create the repository %s",
866 u"Exception while trying to create the repository %s",
867 schema_data['repo_name'])
867 schema_data['repo_name'])
868 raise JSONRPCError(
868 raise JSONRPCError(
869 'failed to create repository `%s`' % (schema_data['repo_name'],))
869 'failed to create repository `%s`' % (schema_data['repo_name'],))
870
870
871
871
872 @jsonrpc_method()
872 @jsonrpc_method()
873 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
873 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
874 description=Optional('')):
874 description=Optional('')):
875 """
875 """
876 Adds an extra field to a repository.
876 Adds an extra field to a repository.
877
877
878 This command can only be run using an |authtoken| with at least
878 This command can only be run using an |authtoken| with at least
879 write permissions to the |repo|.
879 write permissions to the |repo|.
880
880
881 :param apiuser: This is filled automatically from the |authtoken|.
881 :param apiuser: This is filled automatically from the |authtoken|.
882 :type apiuser: AuthUser
882 :type apiuser: AuthUser
883 :param repoid: Set the repository name or repository id.
883 :param repoid: Set the repository name or repository id.
884 :type repoid: str or int
884 :type repoid: str or int
885 :param key: Create a unique field key for this repository.
885 :param key: Create a unique field key for this repository.
886 :type key: str
886 :type key: str
887 :param label:
887 :param label:
888 :type label: Optional(str)
888 :type label: Optional(str)
889 :param description:
889 :param description:
890 :type description: Optional(str)
890 :type description: Optional(str)
891 """
891 """
892 repo = get_repo_or_error(repoid)
892 repo = get_repo_or_error(repoid)
893 if not has_superadmin_permission(apiuser):
893 if not has_superadmin_permission(apiuser):
894 _perms = ('repository.admin',)
894 _perms = ('repository.admin',)
895 validate_repo_permissions(apiuser, repoid, repo, _perms)
895 validate_repo_permissions(apiuser, repoid, repo, _perms)
896
896
897 label = Optional.extract(label) or key
897 label = Optional.extract(label) or key
898 description = Optional.extract(description)
898 description = Optional.extract(description)
899
899
900 field = RepositoryField.get_by_key_name(key, repo)
900 field = RepositoryField.get_by_key_name(key, repo)
901 if field:
901 if field:
902 raise JSONRPCError('Field with key '
902 raise JSONRPCError('Field with key '
903 '`%s` exists for repo `%s`' % (key, repoid))
903 '`%s` exists for repo `%s`' % (key, repoid))
904
904
905 try:
905 try:
906 RepoModel().add_repo_field(repo, key, field_label=label,
906 RepoModel().add_repo_field(repo, key, field_label=label,
907 field_desc=description)
907 field_desc=description)
908 Session().commit()
908 Session().commit()
909 return {
909 return {
910 'msg': "Added new repository field `%s`" % (key,),
910 'msg': "Added new repository field `%s`" % (key,),
911 'success': True,
911 'success': True,
912 }
912 }
913 except Exception:
913 except Exception:
914 log.exception("Exception occurred while trying to add field to repo")
914 log.exception("Exception occurred while trying to add field to repo")
915 raise JSONRPCError(
915 raise JSONRPCError(
916 'failed to create new field for repository `%s`' % (repoid,))
916 'failed to create new field for repository `%s`' % (repoid,))
917
917
918
918
919 @jsonrpc_method()
919 @jsonrpc_method()
920 def remove_field_from_repo(request, apiuser, repoid, key):
920 def remove_field_from_repo(request, apiuser, repoid, key):
921 """
921 """
922 Removes an extra field from a repository.
922 Removes an extra field from a repository.
923
923
924 This command can only be run using an |authtoken| with at least
924 This command can only be run using an |authtoken| with at least
925 write permissions to the |repo|.
925 write permissions to the |repo|.
926
926
927 :param apiuser: This is filled automatically from the |authtoken|.
927 :param apiuser: This is filled automatically from the |authtoken|.
928 :type apiuser: AuthUser
928 :type apiuser: AuthUser
929 :param repoid: Set the repository name or repository ID.
929 :param repoid: Set the repository name or repository ID.
930 :type repoid: str or int
930 :type repoid: str or int
931 :param key: Set the unique field key for this repository.
931 :param key: Set the unique field key for this repository.
932 :type key: str
932 :type key: str
933 """
933 """
934
934
935 repo = get_repo_or_error(repoid)
935 repo = get_repo_or_error(repoid)
936 if not has_superadmin_permission(apiuser):
936 if not has_superadmin_permission(apiuser):
937 _perms = ('repository.admin',)
937 _perms = ('repository.admin',)
938 validate_repo_permissions(apiuser, repoid, repo, _perms)
938 validate_repo_permissions(apiuser, repoid, repo, _perms)
939
939
940 field = RepositoryField.get_by_key_name(key, repo)
940 field = RepositoryField.get_by_key_name(key, repo)
941 if not field:
941 if not field:
942 raise JSONRPCError('Field with key `%s` does not '
942 raise JSONRPCError('Field with key `%s` does not '
943 'exists for repo `%s`' % (key, repoid))
943 'exists for repo `%s`' % (key, repoid))
944
944
945 try:
945 try:
946 RepoModel().delete_repo_field(repo, field_key=key)
946 RepoModel().delete_repo_field(repo, field_key=key)
947 Session().commit()
947 Session().commit()
948 return {
948 return {
949 'msg': "Deleted repository field `%s`" % (key,),
949 'msg': "Deleted repository field `%s`" % (key,),
950 'success': True,
950 'success': True,
951 }
951 }
952 except Exception:
952 except Exception:
953 log.exception(
953 log.exception(
954 "Exception occurred while trying to delete field from repo")
954 "Exception occurred while trying to delete field from repo")
955 raise JSONRPCError(
955 raise JSONRPCError(
956 'failed to delete field for repository `%s`' % (repoid,))
956 'failed to delete field for repository `%s`' % (repoid,))
957
957
958
958
959 @jsonrpc_method()
959 @jsonrpc_method()
960 def update_repo(
960 def update_repo(
961 request, apiuser, repoid, repo_name=Optional(None),
961 request, apiuser, repoid, repo_name=Optional(None),
962 owner=Optional(OAttr('apiuser')), description=Optional(''),
962 owner=Optional(OAttr('apiuser')), description=Optional(''),
963 private=Optional(False),
963 private=Optional(False),
964 clone_uri=Optional(None), push_uri=Optional(None),
964 clone_uri=Optional(None), push_uri=Optional(None),
965 landing_rev=Optional(None), fork_of=Optional(None),
965 landing_rev=Optional(None), fork_of=Optional(None),
966 enable_statistics=Optional(False),
966 enable_statistics=Optional(False),
967 enable_locking=Optional(False),
967 enable_locking=Optional(False),
968 enable_downloads=Optional(False), fields=Optional('')):
968 enable_downloads=Optional(False), fields=Optional('')):
969 """
969 """
970 Updates a repository with the given information.
970 Updates a repository with the given information.
971
971
972 This command can only be run using an |authtoken| with at least
972 This command can only be run using an |authtoken| with at least
973 admin permissions to the |repo|.
973 admin permissions to the |repo|.
974
974
975 * If the repository name contains "/", repository will be updated
975 * If the repository name contains "/", repository will be updated
976 accordingly with a repository group or nested repository groups
976 accordingly with a repository group or nested repository groups
977
977
978 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
978 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
979 called "repo-test" and place it inside group "foo/bar".
979 called "repo-test" and place it inside group "foo/bar".
980 You have to have permissions to access and write to the last repository
980 You have to have permissions to access and write to the last repository
981 group ("bar" in this example)
981 group ("bar" in this example)
982
982
983 :param apiuser: This is filled automatically from the |authtoken|.
983 :param apiuser: This is filled automatically from the |authtoken|.
984 :type apiuser: AuthUser
984 :type apiuser: AuthUser
985 :param repoid: repository name or repository ID.
985 :param repoid: repository name or repository ID.
986 :type repoid: str or int
986 :type repoid: str or int
987 :param repo_name: Update the |repo| name, including the
987 :param repo_name: Update the |repo| name, including the
988 repository group it's in.
988 repository group it's in.
989 :type repo_name: str
989 :type repo_name: str
990 :param owner: Set the |repo| owner.
990 :param owner: Set the |repo| owner.
991 :type owner: str
991 :type owner: str
992 :param fork_of: Set the |repo| as fork of another |repo|.
992 :param fork_of: Set the |repo| as fork of another |repo|.
993 :type fork_of: str
993 :type fork_of: str
994 :param description: Update the |repo| description.
994 :param description: Update the |repo| description.
995 :type description: str
995 :type description: str
996 :param private: Set the |repo| as private. (True | False)
996 :param private: Set the |repo| as private. (True | False)
997 :type private: bool
997 :type private: bool
998 :param clone_uri: Update the |repo| clone URI.
998 :param clone_uri: Update the |repo| clone URI.
999 :type clone_uri: str
999 :type clone_uri: str
1000 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1000 :param landing_rev: Set the |repo| landing revision. e.g branch:default, book:dev, rev:abcd
1001 :type landing_rev: str
1001 :type landing_rev: str
1002 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1002 :param enable_statistics: Enable statistics on the |repo|, (True | False).
1003 :type enable_statistics: bool
1003 :type enable_statistics: bool
1004 :param enable_locking: Enable |repo| locking.
1004 :param enable_locking: Enable |repo| locking.
1005 :type enable_locking: bool
1005 :type enable_locking: bool
1006 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1006 :param enable_downloads: Enable downloads from the |repo|, (True | False).
1007 :type enable_downloads: bool
1007 :type enable_downloads: bool
1008 :param fields: Add extra fields to the |repo|. Use the following
1008 :param fields: Add extra fields to the |repo|. Use the following
1009 example format: ``field_key=field_val,field_key2=fieldval2``.
1009 example format: ``field_key=field_val,field_key2=fieldval2``.
1010 Escape ', ' with \,
1010 Escape ', ' with \,
1011 :type fields: str
1011 :type fields: str
1012 """
1012 """
1013
1013
1014 repo = get_repo_or_error(repoid)
1014 repo = get_repo_or_error(repoid)
1015
1015
1016 include_secrets = False
1016 include_secrets = False
1017 if not has_superadmin_permission(apiuser):
1017 if not has_superadmin_permission(apiuser):
1018 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1018 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
1019 else:
1019 else:
1020 include_secrets = True
1020 include_secrets = True
1021
1021
1022 updates = dict(
1022 updates = dict(
1023 repo_name=repo_name
1023 repo_name=repo_name
1024 if not isinstance(repo_name, Optional) else repo.repo_name,
1024 if not isinstance(repo_name, Optional) else repo.repo_name,
1025
1025
1026 fork_id=fork_of
1026 fork_id=fork_of
1027 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1027 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
1028
1028
1029 user=owner
1029 user=owner
1030 if not isinstance(owner, Optional) else repo.user.username,
1030 if not isinstance(owner, Optional) else repo.user.username,
1031
1031
1032 repo_description=description
1032 repo_description=description
1033 if not isinstance(description, Optional) else repo.description,
1033 if not isinstance(description, Optional) else repo.description,
1034
1034
1035 repo_private=private
1035 repo_private=private
1036 if not isinstance(private, Optional) else repo.private,
1036 if not isinstance(private, Optional) else repo.private,
1037
1037
1038 clone_uri=clone_uri
1038 clone_uri=clone_uri
1039 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1039 if not isinstance(clone_uri, Optional) else repo.clone_uri,
1040
1040
1041 push_uri=push_uri
1041 push_uri=push_uri
1042 if not isinstance(push_uri, Optional) else repo.push_uri,
1042 if not isinstance(push_uri, Optional) else repo.push_uri,
1043
1043
1044 repo_landing_rev=landing_rev
1044 repo_landing_rev=landing_rev
1045 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1045 if not isinstance(landing_rev, Optional) else repo._landing_revision,
1046
1046
1047 repo_enable_statistics=enable_statistics
1047 repo_enable_statistics=enable_statistics
1048 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1048 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
1049
1049
1050 repo_enable_locking=enable_locking
1050 repo_enable_locking=enable_locking
1051 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1051 if not isinstance(enable_locking, Optional) else repo.enable_locking,
1052
1052
1053 repo_enable_downloads=enable_downloads
1053 repo_enable_downloads=enable_downloads
1054 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1054 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
1055
1055
1056 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1056 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1057 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1057 ref_choices, _labels = ScmModel().get_repo_landing_revs(
1058 request.translate, repo=repo)
1058 request.translate, repo=repo)
1059 ref_choices = list(set(ref_choices + [landing_ref]))
1059 ref_choices = list(set(ref_choices + [landing_ref]))
1060
1060
1061 old_values = repo.get_api_data()
1061 old_values = repo.get_api_data()
1062 repo_type = repo.repo_type
1062 repo_type = repo.repo_type
1063 schema = repo_schema.RepoSchema().bind(
1063 schema = repo_schema.RepoSchema().bind(
1064 repo_type_options=rhodecode.BACKENDS.keys(),
1064 repo_type_options=rhodecode.BACKENDS.keys(),
1065 repo_ref_options=ref_choices,
1065 repo_ref_options=ref_choices,
1066 repo_type=repo_type,
1066 repo_type=repo_type,
1067 # user caller
1067 # user caller
1068 user=apiuser,
1068 user=apiuser,
1069 old_values=old_values)
1069 old_values=old_values)
1070 try:
1070 try:
1071 schema_data = schema.deserialize(dict(
1071 schema_data = schema.deserialize(dict(
1072 # we save old value, users cannot change type
1072 # we save old value, users cannot change type
1073 repo_type=repo_type,
1073 repo_type=repo_type,
1074
1074
1075 repo_name=updates['repo_name'],
1075 repo_name=updates['repo_name'],
1076 repo_owner=updates['user'],
1076 repo_owner=updates['user'],
1077 repo_description=updates['repo_description'],
1077 repo_description=updates['repo_description'],
1078 repo_clone_uri=updates['clone_uri'],
1078 repo_clone_uri=updates['clone_uri'],
1079 repo_push_uri=updates['push_uri'],
1079 repo_push_uri=updates['push_uri'],
1080 repo_fork_of=updates['fork_id'],
1080 repo_fork_of=updates['fork_id'],
1081 repo_private=updates['repo_private'],
1081 repo_private=updates['repo_private'],
1082 repo_landing_commit_ref=updates['repo_landing_rev'],
1082 repo_landing_commit_ref=updates['repo_landing_rev'],
1083 repo_enable_statistics=updates['repo_enable_statistics'],
1083 repo_enable_statistics=updates['repo_enable_statistics'],
1084 repo_enable_downloads=updates['repo_enable_downloads'],
1084 repo_enable_downloads=updates['repo_enable_downloads'],
1085 repo_enable_locking=updates['repo_enable_locking']))
1085 repo_enable_locking=updates['repo_enable_locking']))
1086 except validation_schema.Invalid as err:
1086 except validation_schema.Invalid as err:
1087 raise JSONRPCValidationError(colander_exc=err)
1087 raise JSONRPCValidationError(colander_exc=err)
1088
1088
1089 # save validated data back into the updates dict
1089 # save validated data back into the updates dict
1090 validated_updates = dict(
1090 validated_updates = dict(
1091 repo_name=schema_data['repo_group']['repo_name_without_group'],
1091 repo_name=schema_data['repo_group']['repo_name_without_group'],
1092 repo_group=schema_data['repo_group']['repo_group_id'],
1092 repo_group=schema_data['repo_group']['repo_group_id'],
1093
1093
1094 user=schema_data['repo_owner'],
1094 user=schema_data['repo_owner'],
1095 repo_description=schema_data['repo_description'],
1095 repo_description=schema_data['repo_description'],
1096 repo_private=schema_data['repo_private'],
1096 repo_private=schema_data['repo_private'],
1097 clone_uri=schema_data['repo_clone_uri'],
1097 clone_uri=schema_data['repo_clone_uri'],
1098 push_uri=schema_data['repo_push_uri'],
1098 push_uri=schema_data['repo_push_uri'],
1099 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1099 repo_landing_rev=schema_data['repo_landing_commit_ref'],
1100 repo_enable_statistics=schema_data['repo_enable_statistics'],
1100 repo_enable_statistics=schema_data['repo_enable_statistics'],
1101 repo_enable_locking=schema_data['repo_enable_locking'],
1101 repo_enable_locking=schema_data['repo_enable_locking'],
1102 repo_enable_downloads=schema_data['repo_enable_downloads'],
1102 repo_enable_downloads=schema_data['repo_enable_downloads'],
1103 )
1103 )
1104
1104
1105 if schema_data['repo_fork_of']:
1105 if schema_data['repo_fork_of']:
1106 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1106 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
1107 validated_updates['fork_id'] = fork_repo.repo_id
1107 validated_updates['fork_id'] = fork_repo.repo_id
1108
1108
1109 # extra fields
1109 # extra fields
1110 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1110 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
1111 if fields:
1111 if fields:
1112 validated_updates.update(fields)
1112 validated_updates.update(fields)
1113
1113
1114 try:
1114 try:
1115 RepoModel().update(repo, **validated_updates)
1115 RepoModel().update(repo, **validated_updates)
1116 audit_logger.store_api(
1116 audit_logger.store_api(
1117 'repo.edit', action_data={'old_data': old_values},
1117 'repo.edit', action_data={'old_data': old_values},
1118 user=apiuser, repo=repo)
1118 user=apiuser, repo=repo)
1119 Session().commit()
1119 Session().commit()
1120 return {
1120 return {
1121 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1121 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
1122 'repository': repo.get_api_data(include_secrets=include_secrets)
1122 'repository': repo.get_api_data(include_secrets=include_secrets)
1123 }
1123 }
1124 except Exception:
1124 except Exception:
1125 log.exception(
1125 log.exception(
1126 u"Exception while trying to update the repository %s",
1126 u"Exception while trying to update the repository %s",
1127 repoid)
1127 repoid)
1128 raise JSONRPCError('failed to update repo `%s`' % repoid)
1128 raise JSONRPCError('failed to update repo `%s`' % repoid)
1129
1129
1130
1130
1131 @jsonrpc_method()
1131 @jsonrpc_method()
1132 def fork_repo(request, apiuser, repoid, fork_name,
1132 def fork_repo(request, apiuser, repoid, fork_name,
1133 owner=Optional(OAttr('apiuser')),
1133 owner=Optional(OAttr('apiuser')),
1134 description=Optional(''),
1134 description=Optional(''),
1135 private=Optional(False),
1135 private=Optional(False),
1136 clone_uri=Optional(None),
1136 clone_uri=Optional(None),
1137 landing_rev=Optional(None),
1137 landing_rev=Optional(None),
1138 copy_permissions=Optional(False)):
1138 copy_permissions=Optional(False)):
1139 """
1139 """
1140 Creates a fork of the specified |repo|.
1140 Creates a fork of the specified |repo|.
1141
1141
1142 * If the fork_name contains "/", fork will be created inside
1142 * If the fork_name contains "/", fork will be created inside
1143 a repository group or nested repository groups
1143 a repository group or nested repository groups
1144
1144
1145 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1145 For example "foo/bar/fork-repo" will create fork called "fork-repo"
1146 inside group "foo/bar". You have to have permissions to access and
1146 inside group "foo/bar". You have to have permissions to access and
1147 write to the last repository group ("bar" in this example)
1147 write to the last repository group ("bar" in this example)
1148
1148
1149 This command can only be run using an |authtoken| with minimum
1149 This command can only be run using an |authtoken| with minimum
1150 read permissions of the forked repo, create fork permissions for an user.
1150 read permissions of the forked repo, create fork permissions for an user.
1151
1151
1152 :param apiuser: This is filled automatically from the |authtoken|.
1152 :param apiuser: This is filled automatically from the |authtoken|.
1153 :type apiuser: AuthUser
1153 :type apiuser: AuthUser
1154 :param repoid: Set repository name or repository ID.
1154 :param repoid: Set repository name or repository ID.
1155 :type repoid: str or int
1155 :type repoid: str or int
1156 :param fork_name: Set the fork name, including it's repository group membership.
1156 :param fork_name: Set the fork name, including it's repository group membership.
1157 :type fork_name: str
1157 :type fork_name: str
1158 :param owner: Set the fork owner.
1158 :param owner: Set the fork owner.
1159 :type owner: str
1159 :type owner: str
1160 :param description: Set the fork description.
1160 :param description: Set the fork description.
1161 :type description: str
1161 :type description: str
1162 :param copy_permissions: Copy permissions from parent |repo|. The
1162 :param copy_permissions: Copy permissions from parent |repo|. The
1163 default is False.
1163 default is False.
1164 :type copy_permissions: bool
1164 :type copy_permissions: bool
1165 :param private: Make the fork private. The default is False.
1165 :param private: Make the fork private. The default is False.
1166 :type private: bool
1166 :type private: bool
1167 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1167 :param landing_rev: Set the landing revision. E.g branch:default, book:dev, rev:abcd
1168
1168
1169 Example output:
1169 Example output:
1170
1170
1171 .. code-block:: bash
1171 .. code-block:: bash
1172
1172
1173 id : <id_for_response>
1173 id : <id_for_response>
1174 api_key : "<api_key>"
1174 api_key : "<api_key>"
1175 args: {
1175 args: {
1176 "repoid" : "<reponame or repo_id>",
1176 "repoid" : "<reponame or repo_id>",
1177 "fork_name": "<forkname>",
1177 "fork_name": "<forkname>",
1178 "owner": "<username or user_id = Optional(=apiuser)>",
1178 "owner": "<username or user_id = Optional(=apiuser)>",
1179 "description": "<description>",
1179 "description": "<description>",
1180 "copy_permissions": "<bool>",
1180 "copy_permissions": "<bool>",
1181 "private": "<bool>",
1181 "private": "<bool>",
1182 "landing_rev": "<landing_rev>"
1182 "landing_rev": "<landing_rev>"
1183 }
1183 }
1184
1184
1185 Example error output:
1185 Example error output:
1186
1186
1187 .. code-block:: bash
1187 .. code-block:: bash
1188
1188
1189 id : <id_given_in_input>
1189 id : <id_given_in_input>
1190 result: {
1190 result: {
1191 "msg": "Created fork of `<reponame>` as `<forkname>`",
1191 "msg": "Created fork of `<reponame>` as `<forkname>`",
1192 "success": true,
1192 "success": true,
1193 "task": "<celery task id or None if done sync>"
1193 "task": "<celery task id or None if done sync>"
1194 }
1194 }
1195 error: null
1195 error: null
1196
1196
1197 """
1197 """
1198
1198
1199 repo = get_repo_or_error(repoid)
1199 repo = get_repo_or_error(repoid)
1200 repo_name = repo.repo_name
1200 repo_name = repo.repo_name
1201
1201
1202 if not has_superadmin_permission(apiuser):
1202 if not has_superadmin_permission(apiuser):
1203 # check if we have at least read permission for
1203 # check if we have at least read permission for
1204 # this repo that we fork !
1204 # this repo that we fork !
1205 _perms = (
1205 _perms = (
1206 'repository.admin', 'repository.write', 'repository.read')
1206 'repository.admin', 'repository.write', 'repository.read')
1207 validate_repo_permissions(apiuser, repoid, repo, _perms)
1207 validate_repo_permissions(apiuser, repoid, repo, _perms)
1208
1208
1209 # check if the regular user has at least fork permissions as well
1209 # check if the regular user has at least fork permissions as well
1210 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1210 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1211 raise JSONRPCForbidden()
1211 raise JSONRPCForbidden()
1212
1212
1213 # check if user can set owner parameter
1213 # check if user can set owner parameter
1214 owner = validate_set_owner_permissions(apiuser, owner)
1214 owner = validate_set_owner_permissions(apiuser, owner)
1215
1215
1216 description = Optional.extract(description)
1216 description = Optional.extract(description)
1217 copy_permissions = Optional.extract(copy_permissions)
1217 copy_permissions = Optional.extract(copy_permissions)
1218 clone_uri = Optional.extract(clone_uri)
1218 clone_uri = Optional.extract(clone_uri)
1219
1219
1220 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1220 landing_ref, _label = ScmModel.backend_landing_ref(repo.repo_type)
1221 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1221 ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
1222 ref_choices = list(set(ref_choices + [landing_ref]))
1222 ref_choices = list(set(ref_choices + [landing_ref]))
1223 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1223 landing_commit_ref = Optional.extract(landing_rev) or landing_ref
1224
1224
1225 private = Optional.extract(private)
1225 private = Optional.extract(private)
1226
1226
1227 schema = repo_schema.RepoSchema().bind(
1227 schema = repo_schema.RepoSchema().bind(
1228 repo_type_options=rhodecode.BACKENDS.keys(),
1228 repo_type_options=rhodecode.BACKENDS.keys(),
1229 repo_ref_options=ref_choices,
1229 repo_ref_options=ref_choices,
1230 repo_type=repo.repo_type,
1230 repo_type=repo.repo_type,
1231 # user caller
1231 # user caller
1232 user=apiuser)
1232 user=apiuser)
1233
1233
1234 try:
1234 try:
1235 schema_data = schema.deserialize(dict(
1235 schema_data = schema.deserialize(dict(
1236 repo_name=fork_name,
1236 repo_name=fork_name,
1237 repo_type=repo.repo_type,
1237 repo_type=repo.repo_type,
1238 repo_owner=owner.username,
1238 repo_owner=owner.username,
1239 repo_description=description,
1239 repo_description=description,
1240 repo_landing_commit_ref=landing_commit_ref,
1240 repo_landing_commit_ref=landing_commit_ref,
1241 repo_clone_uri=clone_uri,
1241 repo_clone_uri=clone_uri,
1242 repo_private=private,
1242 repo_private=private,
1243 repo_copy_permissions=copy_permissions))
1243 repo_copy_permissions=copy_permissions))
1244 except validation_schema.Invalid as err:
1244 except validation_schema.Invalid as err:
1245 raise JSONRPCValidationError(colander_exc=err)
1245 raise JSONRPCValidationError(colander_exc=err)
1246
1246
1247 try:
1247 try:
1248 data = {
1248 data = {
1249 'fork_parent_id': repo.repo_id,
1249 'fork_parent_id': repo.repo_id,
1250
1250
1251 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1251 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1252 'repo_name_full': schema_data['repo_name'],
1252 'repo_name_full': schema_data['repo_name'],
1253 'repo_group': schema_data['repo_group']['repo_group_id'],
1253 'repo_group': schema_data['repo_group']['repo_group_id'],
1254 'repo_type': schema_data['repo_type'],
1254 'repo_type': schema_data['repo_type'],
1255 'description': schema_data['repo_description'],
1255 'description': schema_data['repo_description'],
1256 'private': schema_data['repo_private'],
1256 'private': schema_data['repo_private'],
1257 'copy_permissions': schema_data['repo_copy_permissions'],
1257 'copy_permissions': schema_data['repo_copy_permissions'],
1258 'landing_rev': schema_data['repo_landing_commit_ref'],
1258 'landing_rev': schema_data['repo_landing_commit_ref'],
1259 }
1259 }
1260
1260
1261 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1261 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1262 # no commit, it's done in RepoModel, or async via celery
1262 # no commit, it's done in RepoModel, or async via celery
1263 task_id = get_task_id(task)
1263 task_id = get_task_id(task)
1264
1264
1265 return {
1265 return {
1266 'msg': 'Created fork of `%s` as `%s`' % (
1266 'msg': 'Created fork of `%s` as `%s`' % (
1267 repo.repo_name, schema_data['repo_name']),
1267 repo.repo_name, schema_data['repo_name']),
1268 'success': True, # cannot return the repo data here since fork
1268 'success': True, # cannot return the repo data here since fork
1269 # can be done async
1269 # can be done async
1270 'task': task_id
1270 'task': task_id
1271 }
1271 }
1272 except Exception:
1272 except Exception:
1273 log.exception(
1273 log.exception(
1274 u"Exception while trying to create fork %s",
1274 u"Exception while trying to create fork %s",
1275 schema_data['repo_name'])
1275 schema_data['repo_name'])
1276 raise JSONRPCError(
1276 raise JSONRPCError(
1277 'failed to fork repository `%s` as `%s`' % (
1277 'failed to fork repository `%s` as `%s`' % (
1278 repo_name, schema_data['repo_name']))
1278 repo_name, schema_data['repo_name']))
1279
1279
1280
1280
1281 @jsonrpc_method()
1281 @jsonrpc_method()
1282 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1282 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1283 """
1283 """
1284 Deletes a repository.
1284 Deletes a repository.
1285
1285
1286 * When the `forks` parameter is set it's possible to detach or delete
1286 * When the `forks` parameter is set it's possible to detach or delete
1287 forks of deleted repository.
1287 forks of deleted repository.
1288
1288
1289 This command can only be run using an |authtoken| with admin
1289 This command can only be run using an |authtoken| with admin
1290 permissions on the |repo|.
1290 permissions on the |repo|.
1291
1291
1292 :param apiuser: This is filled automatically from the |authtoken|.
1292 :param apiuser: This is filled automatically from the |authtoken|.
1293 :type apiuser: AuthUser
1293 :type apiuser: AuthUser
1294 :param repoid: Set the repository name or repository ID.
1294 :param repoid: Set the repository name or repository ID.
1295 :type repoid: str or int
1295 :type repoid: str or int
1296 :param forks: Set to `detach` or `delete` forks from the |repo|.
1296 :param forks: Set to `detach` or `delete` forks from the |repo|.
1297 :type forks: Optional(str)
1297 :type forks: Optional(str)
1298
1298
1299 Example error output:
1299 Example error output:
1300
1300
1301 .. code-block:: bash
1301 .. code-block:: bash
1302
1302
1303 id : <id_given_in_input>
1303 id : <id_given_in_input>
1304 result: {
1304 result: {
1305 "msg": "Deleted repository `<reponame>`",
1305 "msg": "Deleted repository `<reponame>`",
1306 "success": true
1306 "success": true
1307 }
1307 }
1308 error: null
1308 error: null
1309 """
1309 """
1310
1310
1311 repo = get_repo_or_error(repoid)
1311 repo = get_repo_or_error(repoid)
1312 repo_name = repo.repo_name
1312 repo_name = repo.repo_name
1313 if not has_superadmin_permission(apiuser):
1313 if not has_superadmin_permission(apiuser):
1314 _perms = ('repository.admin',)
1314 _perms = ('repository.admin',)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1316
1316
1317 try:
1317 try:
1318 handle_forks = Optional.extract(forks)
1318 handle_forks = Optional.extract(forks)
1319 _forks_msg = ''
1319 _forks_msg = ''
1320 _forks = [f for f in repo.forks]
1320 _forks = [f for f in repo.forks]
1321 if handle_forks == 'detach':
1321 if handle_forks == 'detach':
1322 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1322 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1323 elif handle_forks == 'delete':
1323 elif handle_forks == 'delete':
1324 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1324 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1325 elif _forks:
1325 elif _forks:
1326 raise JSONRPCError(
1326 raise JSONRPCError(
1327 'Cannot delete `%s` it still contains attached forks' %
1327 'Cannot delete `%s` it still contains attached forks' %
1328 (repo.repo_name,)
1328 (repo.repo_name,)
1329 )
1329 )
1330 old_data = repo.get_api_data()
1330 old_data = repo.get_api_data()
1331 RepoModel().delete(repo, forks=forks)
1331 RepoModel().delete(repo, forks=forks)
1332
1332
1333 repo = audit_logger.RepoWrap(repo_id=None,
1333 repo = audit_logger.RepoWrap(repo_id=None,
1334 repo_name=repo.repo_name)
1334 repo_name=repo.repo_name)
1335
1335
1336 audit_logger.store_api(
1336 audit_logger.store_api(
1337 'repo.delete', action_data={'old_data': old_data},
1337 'repo.delete', action_data={'old_data': old_data},
1338 user=apiuser, repo=repo)
1338 user=apiuser, repo=repo)
1339
1339
1340 ScmModel().mark_for_invalidation(repo_name, delete=True)
1340 ScmModel().mark_for_invalidation(repo_name, delete=True)
1341 Session().commit()
1341 Session().commit()
1342 return {
1342 return {
1343 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1343 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1344 'success': True
1344 'success': True
1345 }
1345 }
1346 except Exception:
1346 except Exception:
1347 log.exception("Exception occurred while trying to delete repo")
1347 log.exception("Exception occurred while trying to delete repo")
1348 raise JSONRPCError(
1348 raise JSONRPCError(
1349 'failed to delete repository `%s`' % (repo_name,)
1349 'failed to delete repository `%s`' % (repo_name,)
1350 )
1350 )
1351
1351
1352
1352
1353 #TODO: marcink, change name ?
1353 #TODO: marcink, change name ?
1354 @jsonrpc_method()
1354 @jsonrpc_method()
1355 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1355 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1356 """
1356 """
1357 Invalidates the cache for the specified repository.
1357 Invalidates the cache for the specified repository.
1358
1358
1359 This command can only be run using an |authtoken| with admin rights to
1359 This command can only be run using an |authtoken| with admin rights to
1360 the specified repository.
1360 the specified repository.
1361
1361
1362 This command takes the following options:
1362 This command takes the following options:
1363
1363
1364 :param apiuser: This is filled automatically from |authtoken|.
1364 :param apiuser: This is filled automatically from |authtoken|.
1365 :type apiuser: AuthUser
1365 :type apiuser: AuthUser
1366 :param repoid: Sets the repository name or repository ID.
1366 :param repoid: Sets the repository name or repository ID.
1367 :type repoid: str or int
1367 :type repoid: str or int
1368 :param delete_keys: This deletes the invalidated keys instead of
1368 :param delete_keys: This deletes the invalidated keys instead of
1369 just flagging them.
1369 just flagging them.
1370 :type delete_keys: Optional(``True`` | ``False``)
1370 :type delete_keys: Optional(``True`` | ``False``)
1371
1371
1372 Example output:
1372 Example output:
1373
1373
1374 .. code-block:: bash
1374 .. code-block:: bash
1375
1375
1376 id : <id_given_in_input>
1376 id : <id_given_in_input>
1377 result : {
1377 result : {
1378 'msg': Cache for repository `<repository name>` was invalidated,
1378 'msg': Cache for repository `<repository name>` was invalidated,
1379 'repository': <repository name>
1379 'repository': <repository name>
1380 }
1380 }
1381 error : null
1381 error : null
1382
1382
1383 Example error output:
1383 Example error output:
1384
1384
1385 .. code-block:: bash
1385 .. code-block:: bash
1386
1386
1387 id : <id_given_in_input>
1387 id : <id_given_in_input>
1388 result : null
1388 result : null
1389 error : {
1389 error : {
1390 'Error occurred during cache invalidation action'
1390 'Error occurred during cache invalidation action'
1391 }
1391 }
1392
1392
1393 """
1393 """
1394
1394
1395 repo = get_repo_or_error(repoid)
1395 repo = get_repo_or_error(repoid)
1396 if not has_superadmin_permission(apiuser):
1396 if not has_superadmin_permission(apiuser):
1397 _perms = ('repository.admin', 'repository.write',)
1397 _perms = ('repository.admin', 'repository.write',)
1398 validate_repo_permissions(apiuser, repoid, repo, _perms)
1398 validate_repo_permissions(apiuser, repoid, repo, _perms)
1399
1399
1400 delete = Optional.extract(delete_keys)
1400 delete = Optional.extract(delete_keys)
1401 try:
1401 try:
1402 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1402 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1403 return {
1403 return {
1404 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1404 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1405 'repository': repo.repo_name
1405 'repository': repo.repo_name
1406 }
1406 }
1407 except Exception:
1407 except Exception:
1408 log.exception(
1408 log.exception(
1409 "Exception occurred while trying to invalidate repo cache")
1409 "Exception occurred while trying to invalidate repo cache")
1410 raise JSONRPCError(
1410 raise JSONRPCError(
1411 'Error occurred during cache invalidation action'
1411 'Error occurred during cache invalidation action'
1412 )
1412 )
1413
1413
1414
1414
1415 #TODO: marcink, change name ?
1415 #TODO: marcink, change name ?
1416 @jsonrpc_method()
1416 @jsonrpc_method()
1417 def lock(request, apiuser, repoid, locked=Optional(None),
1417 def lock(request, apiuser, repoid, locked=Optional(None),
1418 userid=Optional(OAttr('apiuser'))):
1418 userid=Optional(OAttr('apiuser'))):
1419 """
1419 """
1420 Sets the lock state of the specified |repo| by the given user.
1420 Sets the lock state of the specified |repo| by the given user.
1421 From more information, see :ref:`repo-locking`.
1421 From more information, see :ref:`repo-locking`.
1422
1422
1423 * If the ``userid`` option is not set, the repository is locked to the
1423 * If the ``userid`` option is not set, the repository is locked to the
1424 user who called the method.
1424 user who called the method.
1425 * If the ``locked`` parameter is not set, the current lock state of the
1425 * If the ``locked`` parameter is not set, the current lock state of the
1426 repository is displayed.
1426 repository is displayed.
1427
1427
1428 This command can only be run using an |authtoken| with admin rights to
1428 This command can only be run using an |authtoken| with admin rights to
1429 the specified repository.
1429 the specified repository.
1430
1430
1431 This command takes the following options:
1431 This command takes the following options:
1432
1432
1433 :param apiuser: This is filled automatically from the |authtoken|.
1433 :param apiuser: This is filled automatically from the |authtoken|.
1434 :type apiuser: AuthUser
1434 :type apiuser: AuthUser
1435 :param repoid: Sets the repository name or repository ID.
1435 :param repoid: Sets the repository name or repository ID.
1436 :type repoid: str or int
1436 :type repoid: str or int
1437 :param locked: Sets the lock state.
1437 :param locked: Sets the lock state.
1438 :type locked: Optional(``True`` | ``False``)
1438 :type locked: Optional(``True`` | ``False``)
1439 :param userid: Set the repository lock to this user.
1439 :param userid: Set the repository lock to this user.
1440 :type userid: Optional(str or int)
1440 :type userid: Optional(str or int)
1441
1441
1442 Example error output:
1442 Example error output:
1443
1443
1444 .. code-block:: bash
1444 .. code-block:: bash
1445
1445
1446 id : <id_given_in_input>
1446 id : <id_given_in_input>
1447 result : {
1447 result : {
1448 'repo': '<reponame>',
1448 'repo': '<reponame>',
1449 'locked': <bool: lock state>,
1449 'locked': <bool: lock state>,
1450 'locked_since': <int: lock timestamp>,
1450 'locked_since': <int: lock timestamp>,
1451 'locked_by': <username of person who made the lock>,
1451 'locked_by': <username of person who made the lock>,
1452 'lock_reason': <str: reason for locking>,
1452 'lock_reason': <str: reason for locking>,
1453 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1453 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1454 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1454 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1455 or
1455 or
1456 'msg': 'Repo `<repository name>` not locked.'
1456 'msg': 'Repo `<repository name>` not locked.'
1457 or
1457 or
1458 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1458 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1459 }
1459 }
1460 error : null
1460 error : null
1461
1461
1462 Example error output:
1462 Example error output:
1463
1463
1464 .. code-block:: bash
1464 .. code-block:: bash
1465
1465
1466 id : <id_given_in_input>
1466 id : <id_given_in_input>
1467 result : null
1467 result : null
1468 error : {
1468 error : {
1469 'Error occurred locking repository `<reponame>`'
1469 'Error occurred locking repository `<reponame>`'
1470 }
1470 }
1471 """
1471 """
1472
1472
1473 repo = get_repo_or_error(repoid)
1473 repo = get_repo_or_error(repoid)
1474 if not has_superadmin_permission(apiuser):
1474 if not has_superadmin_permission(apiuser):
1475 # check if we have at least write permission for this repo !
1475 # check if we have at least write permission for this repo !
1476 _perms = ('repository.admin', 'repository.write',)
1476 _perms = ('repository.admin', 'repository.write',)
1477 validate_repo_permissions(apiuser, repoid, repo, _perms)
1477 validate_repo_permissions(apiuser, repoid, repo, _perms)
1478
1478
1479 # make sure normal user does not pass someone else userid,
1479 # make sure normal user does not pass someone else userid,
1480 # he is not allowed to do that
1480 # he is not allowed to do that
1481 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1481 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1482 raise JSONRPCError('userid is not the same as your user')
1482 raise JSONRPCError('userid is not the same as your user')
1483
1483
1484 if isinstance(userid, Optional):
1484 if isinstance(userid, Optional):
1485 userid = apiuser.user_id
1485 userid = apiuser.user_id
1486
1486
1487 user = get_user_or_error(userid)
1487 user = get_user_or_error(userid)
1488
1488
1489 if isinstance(locked, Optional):
1489 if isinstance(locked, Optional):
1490 lockobj = repo.locked
1490 lockobj = repo.locked
1491
1491
1492 if lockobj[0] is None:
1492 if lockobj[0] is None:
1493 _d = {
1493 _d = {
1494 'repo': repo.repo_name,
1494 'repo': repo.repo_name,
1495 'locked': False,
1495 'locked': False,
1496 'locked_since': None,
1496 'locked_since': None,
1497 'locked_by': None,
1497 'locked_by': None,
1498 'lock_reason': None,
1498 'lock_reason': None,
1499 'lock_state_changed': False,
1499 'lock_state_changed': False,
1500 'msg': 'Repo `%s` not locked.' % repo.repo_name
1500 'msg': 'Repo `%s` not locked.' % repo.repo_name
1501 }
1501 }
1502 return _d
1502 return _d
1503 else:
1503 else:
1504 _user_id, _time, _reason = lockobj
1504 _user_id, _time, _reason = lockobj
1505 lock_user = get_user_or_error(userid)
1505 lock_user = get_user_or_error(userid)
1506 _d = {
1506 _d = {
1507 'repo': repo.repo_name,
1507 'repo': repo.repo_name,
1508 'locked': True,
1508 'locked': True,
1509 'locked_since': _time,
1509 'locked_since': _time,
1510 'locked_by': lock_user.username,
1510 'locked_by': lock_user.username,
1511 'lock_reason': _reason,
1511 'lock_reason': _reason,
1512 'lock_state_changed': False,
1512 'lock_state_changed': False,
1513 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1513 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1514 % (repo.repo_name, lock_user.username,
1514 % (repo.repo_name, lock_user.username,
1515 json.dumps(time_to_datetime(_time))))
1515 json.dumps(time_to_datetime(_time))))
1516 }
1516 }
1517 return _d
1517 return _d
1518
1518
1519 # force locked state through a flag
1519 # force locked state through a flag
1520 else:
1520 else:
1521 locked = str2bool(locked)
1521 locked = str2bool(locked)
1522 lock_reason = Repository.LOCK_API
1522 lock_reason = Repository.LOCK_API
1523 try:
1523 try:
1524 if locked:
1524 if locked:
1525 lock_time = time.time()
1525 lock_time = time.time()
1526 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1526 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1527 else:
1527 else:
1528 lock_time = None
1528 lock_time = None
1529 Repository.unlock(repo)
1529 Repository.unlock(repo)
1530 _d = {
1530 _d = {
1531 'repo': repo.repo_name,
1531 'repo': repo.repo_name,
1532 'locked': locked,
1532 'locked': locked,
1533 'locked_since': lock_time,
1533 'locked_since': lock_time,
1534 'locked_by': user.username,
1534 'locked_by': user.username,
1535 'lock_reason': lock_reason,
1535 'lock_reason': lock_reason,
1536 'lock_state_changed': True,
1536 'lock_state_changed': True,
1537 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1537 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1538 % (user.username, repo.repo_name, locked))
1538 % (user.username, repo.repo_name, locked))
1539 }
1539 }
1540 return _d
1540 return _d
1541 except Exception:
1541 except Exception:
1542 log.exception(
1542 log.exception(
1543 "Exception occurred while trying to lock repository")
1543 "Exception occurred while trying to lock repository")
1544 raise JSONRPCError(
1544 raise JSONRPCError(
1545 'Error occurred locking repository `%s`' % repo.repo_name
1545 'Error occurred locking repository `%s`' % repo.repo_name
1546 )
1546 )
1547
1547
1548
1548
1549 @jsonrpc_method()
1549 @jsonrpc_method()
1550 def comment_commit(
1550 def comment_commit(
1551 request, apiuser, repoid, commit_id, message, status=Optional(None),
1551 request, apiuser, repoid, commit_id, message, status=Optional(None),
1552 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1552 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1553 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1553 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
1554 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
1554 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
1555 """
1555 """
1556 Set a commit comment, and optionally change the status of the commit.
1556 Set a commit comment, and optionally change the status of the commit.
1557
1557
1558 :param apiuser: This is filled automatically from the |authtoken|.
1558 :param apiuser: This is filled automatically from the |authtoken|.
1559 :type apiuser: AuthUser
1559 :type apiuser: AuthUser
1560 :param repoid: Set the repository name or repository ID.
1560 :param repoid: Set the repository name or repository ID.
1561 :type repoid: str or int
1561 :type repoid: str or int
1562 :param commit_id: Specify the commit_id for which to set a comment.
1562 :param commit_id: Specify the commit_id for which to set a comment.
1563 :type commit_id: str
1563 :type commit_id: str
1564 :param message: The comment text.
1564 :param message: The comment text.
1565 :type message: str
1565 :type message: str
1566 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1566 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1567 'approved', 'rejected', 'under_review'
1567 'approved', 'rejected', 'under_review'
1568 :type status: str
1568 :type status: str
1569 :param comment_type: Comment type, one of: 'note', 'todo'
1569 :param comment_type: Comment type, one of: 'note', 'todo'
1570 :type comment_type: Optional(str), default: 'note'
1570 :type comment_type: Optional(str), default: 'note'
1571 :param resolves_comment_id: id of comment which this one will resolve
1571 :param resolves_comment_id: id of comment which this one will resolve
1572 :type resolves_comment_id: Optional(int)
1572 :type resolves_comment_id: Optional(int)
1573 :param extra_recipients: list of user ids or usernames to add
1573 :param extra_recipients: list of user ids or usernames to add
1574 notifications for this comment. Acts like a CC for notification
1574 notifications for this comment. Acts like a CC for notification
1575 :type extra_recipients: Optional(list)
1575 :type extra_recipients: Optional(list)
1576 :param userid: Set the user name of the comment creator.
1576 :param userid: Set the user name of the comment creator.
1577 :type userid: Optional(str or int)
1577 :type userid: Optional(str or int)
1578 :param send_email: Define if this comment should also send email notification
1578 :param send_email: Define if this comment should also send email notification
1579 :type send_email: Optional(bool)
1579 :type send_email: Optional(bool)
1580
1580
1581 Example error output:
1581 Example error output:
1582
1582
1583 .. code-block:: bash
1583 .. code-block:: bash
1584
1584
1585 {
1585 {
1586 "id" : <id_given_in_input>,
1586 "id" : <id_given_in_input>,
1587 "result" : {
1587 "result" : {
1588 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1588 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1589 "status_change": null or <status>,
1589 "status_change": null or <status>,
1590 "success": true
1590 "success": true
1591 },
1591 },
1592 "error" : null
1592 "error" : null
1593 }
1593 }
1594
1594
1595 """
1595 """
1596 repo = get_repo_or_error(repoid)
1596 repo = get_repo_or_error(repoid)
1597 if not has_superadmin_permission(apiuser):
1597 if not has_superadmin_permission(apiuser):
1598 _perms = ('repository.read', 'repository.write', 'repository.admin')
1598 _perms = ('repository.read', 'repository.write', 'repository.admin')
1599 validate_repo_permissions(apiuser, repoid, repo, _perms)
1599 validate_repo_permissions(apiuser, repoid, repo, _perms)
1600
1600
1601 try:
1601 try:
1602 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1602 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1603 except Exception as e:
1603 except Exception as e:
1604 log.exception('Failed to fetch commit')
1604 log.exception('Failed to fetch commit')
1605 raise JSONRPCError(safe_str(e))
1605 raise JSONRPCError(safe_str(e))
1606
1606
1607 if isinstance(userid, Optional):
1607 if isinstance(userid, Optional):
1608 userid = apiuser.user_id
1608 userid = apiuser.user_id
1609
1609
1610 user = get_user_or_error(userid)
1610 user = get_user_or_error(userid)
1611 status = Optional.extract(status)
1611 status = Optional.extract(status)
1612 comment_type = Optional.extract(comment_type)
1612 comment_type = Optional.extract(comment_type)
1613 resolves_comment_id = Optional.extract(resolves_comment_id)
1613 resolves_comment_id = Optional.extract(resolves_comment_id)
1614 extra_recipients = Optional.extract(extra_recipients)
1614 extra_recipients = Optional.extract(extra_recipients)
1615 send_email = Optional.extract(send_email, binary=True)
1615 send_email = Optional.extract(send_email, binary=True)
1616
1616
1617 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1617 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1618 if status and status not in allowed_statuses:
1618 if status and status not in allowed_statuses:
1619 raise JSONRPCError('Bad status, must be on '
1619 raise JSONRPCError('Bad status, must be on '
1620 'of %s got %s' % (allowed_statuses, status,))
1620 'of %s got %s' % (allowed_statuses, status,))
1621
1621
1622 if resolves_comment_id:
1622 if resolves_comment_id:
1623 comment = ChangesetComment.get(resolves_comment_id)
1623 comment = ChangesetComment.get(resolves_comment_id)
1624 if not comment:
1624 if not comment:
1625 raise JSONRPCError(
1625 raise JSONRPCError(
1626 'Invalid resolves_comment_id `%s` for this commit.'
1626 'Invalid resolves_comment_id `%s` for this commit.'
1627 % resolves_comment_id)
1627 % resolves_comment_id)
1628 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1628 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1629 raise JSONRPCError(
1629 raise JSONRPCError(
1630 'Comment `%s` is wrong type for setting status to resolved.'
1630 'Comment `%s` is wrong type for setting status to resolved.'
1631 % resolves_comment_id)
1631 % resolves_comment_id)
1632
1632
1633 try:
1633 try:
1634 rc_config = SettingsModel().get_all_settings()
1634 rc_config = SettingsModel().get_all_settings()
1635 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1635 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1636 status_change_label = ChangesetStatus.get_status_lbl(status)
1636 status_change_label = ChangesetStatus.get_status_lbl(status)
1637 comment = CommentsModel().create(
1637 comment = CommentsModel().create(
1638 message, repo, user, commit_id=commit_id,
1638 message, repo, user, commit_id=commit_id,
1639 status_change=status_change_label,
1639 status_change=status_change_label,
1640 status_change_type=status,
1640 status_change_type=status,
1641 renderer=renderer,
1641 renderer=renderer,
1642 comment_type=comment_type,
1642 comment_type=comment_type,
1643 resolves_comment_id=resolves_comment_id,
1643 resolves_comment_id=resolves_comment_id,
1644 auth_user=apiuser,
1644 auth_user=apiuser,
1645 extra_recipients=extra_recipients,
1645 extra_recipients=extra_recipients,
1646 send_email=send_email
1646 send_email=send_email
1647 )
1647 )
1648 if status:
1648 if status:
1649 # also do a status change
1649 # also do a status change
1650 try:
1650 try:
1651 ChangesetStatusModel().set_status(
1651 ChangesetStatusModel().set_status(
1652 repo, status, user, comment, revision=commit_id,
1652 repo, status, user, comment, revision=commit_id,
1653 dont_allow_on_closed_pull_request=True
1653 dont_allow_on_closed_pull_request=True
1654 )
1654 )
1655 except StatusChangeOnClosedPullRequestError:
1655 except StatusChangeOnClosedPullRequestError:
1656 log.exception(
1656 log.exception(
1657 "Exception occurred while trying to change repo commit status")
1657 "Exception occurred while trying to change repo commit status")
1658 msg = ('Changing status on a changeset associated with '
1658 msg = ('Changing status on a commit associated with '
1659 'a closed pull request is not allowed')
1659 'a closed pull request is not allowed')
1660 raise JSONRPCError(msg)
1660 raise JSONRPCError(msg)
1661
1661
1662 Session().commit()
1662 Session().commit()
1663 return {
1663 return {
1664 'msg': (
1664 'msg': (
1665 'Commented on commit `%s` for repository `%s`' % (
1665 'Commented on commit `%s` for repository `%s`' % (
1666 comment.revision, repo.repo_name)),
1666 comment.revision, repo.repo_name)),
1667 'status_change': status,
1667 'status_change': status,
1668 'success': True,
1668 'success': True,
1669 }
1669 }
1670 except JSONRPCError:
1670 except JSONRPCError:
1671 # catch any inside errors, and re-raise them to prevent from
1671 # catch any inside errors, and re-raise them to prevent from
1672 # below global catch to silence them
1672 # below global catch to silence them
1673 raise
1673 raise
1674 except Exception:
1674 except Exception:
1675 log.exception("Exception occurred while trying to comment on commit")
1675 log.exception("Exception occurred while trying to comment on commit")
1676 raise JSONRPCError(
1676 raise JSONRPCError(
1677 'failed to set comment on repository `%s`' % (repo.repo_name,)
1677 'failed to set comment on repository `%s`' % (repo.repo_name,)
1678 )
1678 )
1679
1679
1680
1680
1681 @jsonrpc_method()
1681 @jsonrpc_method()
1682 def get_repo_comments(request, apiuser, repoid,
1682 def get_repo_comments(request, apiuser, repoid,
1683 commit_id=Optional(None), comment_type=Optional(None),
1683 commit_id=Optional(None), comment_type=Optional(None),
1684 userid=Optional(None)):
1684 userid=Optional(None)):
1685 """
1685 """
1686 Get all comments for a repository
1686 Get all comments for a repository
1687
1687
1688 :param apiuser: This is filled automatically from the |authtoken|.
1688 :param apiuser: This is filled automatically from the |authtoken|.
1689 :type apiuser: AuthUser
1689 :type apiuser: AuthUser
1690 :param repoid: Set the repository name or repository ID.
1690 :param repoid: Set the repository name or repository ID.
1691 :type repoid: str or int
1691 :type repoid: str or int
1692 :param commit_id: Optionally filter the comments by the commit_id
1692 :param commit_id: Optionally filter the comments by the commit_id
1693 :type commit_id: Optional(str), default: None
1693 :type commit_id: Optional(str), default: None
1694 :param comment_type: Optionally filter the comments by the comment_type
1694 :param comment_type: Optionally filter the comments by the comment_type
1695 one of: 'note', 'todo'
1695 one of: 'note', 'todo'
1696 :type comment_type: Optional(str), default: None
1696 :type comment_type: Optional(str), default: None
1697 :param userid: Optionally filter the comments by the author of comment
1697 :param userid: Optionally filter the comments by the author of comment
1698 :type userid: Optional(str or int), Default: None
1698 :type userid: Optional(str or int), Default: None
1699
1699
1700 Example error output:
1700 Example error output:
1701
1701
1702 .. code-block:: bash
1702 .. code-block:: bash
1703
1703
1704 {
1704 {
1705 "id" : <id_given_in_input>,
1705 "id" : <id_given_in_input>,
1706 "result" : [
1706 "result" : [
1707 {
1707 {
1708 "comment_author": <USER_DETAILS>,
1708 "comment_author": <USER_DETAILS>,
1709 "comment_created_on": "2017-02-01T14:38:16.309",
1709 "comment_created_on": "2017-02-01T14:38:16.309",
1710 "comment_f_path": "file.txt",
1710 "comment_f_path": "file.txt",
1711 "comment_id": 282,
1711 "comment_id": 282,
1712 "comment_lineno": "n1",
1712 "comment_lineno": "n1",
1713 "comment_resolved_by": null,
1713 "comment_resolved_by": null,
1714 "comment_status": [],
1714 "comment_status": [],
1715 "comment_text": "This file needs a header",
1715 "comment_text": "This file needs a header",
1716 "comment_type": "todo"
1716 "comment_type": "todo"
1717 }
1717 }
1718 ],
1718 ],
1719 "error" : null
1719 "error" : null
1720 }
1720 }
1721
1721
1722 """
1722 """
1723 repo = get_repo_or_error(repoid)
1723 repo = get_repo_or_error(repoid)
1724 if not has_superadmin_permission(apiuser):
1724 if not has_superadmin_permission(apiuser):
1725 _perms = ('repository.read', 'repository.write', 'repository.admin')
1725 _perms = ('repository.read', 'repository.write', 'repository.admin')
1726 validate_repo_permissions(apiuser, repoid, repo, _perms)
1726 validate_repo_permissions(apiuser, repoid, repo, _perms)
1727
1727
1728 commit_id = Optional.extract(commit_id)
1728 commit_id = Optional.extract(commit_id)
1729
1729
1730 userid = Optional.extract(userid)
1730 userid = Optional.extract(userid)
1731 if userid:
1731 if userid:
1732 user = get_user_or_error(userid)
1732 user = get_user_or_error(userid)
1733 else:
1733 else:
1734 user = None
1734 user = None
1735
1735
1736 comment_type = Optional.extract(comment_type)
1736 comment_type = Optional.extract(comment_type)
1737 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1737 if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
1738 raise JSONRPCError(
1738 raise JSONRPCError(
1739 'comment_type must be one of `{}` got {}'.format(
1739 'comment_type must be one of `{}` got {}'.format(
1740 ChangesetComment.COMMENT_TYPES, comment_type)
1740 ChangesetComment.COMMENT_TYPES, comment_type)
1741 )
1741 )
1742
1742
1743 comments = CommentsModel().get_repository_comments(
1743 comments = CommentsModel().get_repository_comments(
1744 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1744 repo=repo, comment_type=comment_type, user=user, commit_id=commit_id)
1745 return comments
1745 return comments
1746
1746
1747
1747
1748 @jsonrpc_method()
1748 @jsonrpc_method()
1749 def grant_user_permission(request, apiuser, repoid, userid, perm):
1749 def grant_user_permission(request, apiuser, repoid, userid, perm):
1750 """
1750 """
1751 Grant permissions for the specified user on the given repository,
1751 Grant permissions for the specified user on the given repository,
1752 or update existing permissions if found.
1752 or update existing permissions if found.
1753
1753
1754 This command can only be run using an |authtoken| with admin
1754 This command can only be run using an |authtoken| with admin
1755 permissions on the |repo|.
1755 permissions on the |repo|.
1756
1756
1757 :param apiuser: This is filled automatically from the |authtoken|.
1757 :param apiuser: This is filled automatically from the |authtoken|.
1758 :type apiuser: AuthUser
1758 :type apiuser: AuthUser
1759 :param repoid: Set the repository name or repository ID.
1759 :param repoid: Set the repository name or repository ID.
1760 :type repoid: str or int
1760 :type repoid: str or int
1761 :param userid: Set the user name.
1761 :param userid: Set the user name.
1762 :type userid: str
1762 :type userid: str
1763 :param perm: Set the user permissions, using the following format
1763 :param perm: Set the user permissions, using the following format
1764 ``(repository.(none|read|write|admin))``
1764 ``(repository.(none|read|write|admin))``
1765 :type perm: str
1765 :type perm: str
1766
1766
1767 Example output:
1767 Example output:
1768
1768
1769 .. code-block:: bash
1769 .. code-block:: bash
1770
1770
1771 id : <id_given_in_input>
1771 id : <id_given_in_input>
1772 result: {
1772 result: {
1773 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1773 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1774 "success": true
1774 "success": true
1775 }
1775 }
1776 error: null
1776 error: null
1777 """
1777 """
1778
1778
1779 repo = get_repo_or_error(repoid)
1779 repo = get_repo_or_error(repoid)
1780 user = get_user_or_error(userid)
1780 user = get_user_or_error(userid)
1781 perm = get_perm_or_error(perm)
1781 perm = get_perm_or_error(perm)
1782 if not has_superadmin_permission(apiuser):
1782 if not has_superadmin_permission(apiuser):
1783 _perms = ('repository.admin',)
1783 _perms = ('repository.admin',)
1784 validate_repo_permissions(apiuser, repoid, repo, _perms)
1784 validate_repo_permissions(apiuser, repoid, repo, _perms)
1785
1785
1786 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1786 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1787 try:
1787 try:
1788 changes = RepoModel().update_permissions(
1788 changes = RepoModel().update_permissions(
1789 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1789 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1790
1790
1791 action_data = {
1791 action_data = {
1792 'added': changes['added'],
1792 'added': changes['added'],
1793 'updated': changes['updated'],
1793 'updated': changes['updated'],
1794 'deleted': changes['deleted'],
1794 'deleted': changes['deleted'],
1795 }
1795 }
1796 audit_logger.store_api(
1796 audit_logger.store_api(
1797 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1797 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1798 Session().commit()
1798 Session().commit()
1799 PermissionModel().flush_user_permission_caches(changes)
1799 PermissionModel().flush_user_permission_caches(changes)
1800
1800
1801 return {
1801 return {
1802 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1802 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1803 perm.permission_name, user.username, repo.repo_name
1803 perm.permission_name, user.username, repo.repo_name
1804 ),
1804 ),
1805 'success': True
1805 'success': True
1806 }
1806 }
1807 except Exception:
1807 except Exception:
1808 log.exception("Exception occurred while trying edit permissions for repo")
1808 log.exception("Exception occurred while trying edit permissions for repo")
1809 raise JSONRPCError(
1809 raise JSONRPCError(
1810 'failed to edit permission for user: `%s` in repo: `%s`' % (
1810 'failed to edit permission for user: `%s` in repo: `%s`' % (
1811 userid, repoid
1811 userid, repoid
1812 )
1812 )
1813 )
1813 )
1814
1814
1815
1815
1816 @jsonrpc_method()
1816 @jsonrpc_method()
1817 def revoke_user_permission(request, apiuser, repoid, userid):
1817 def revoke_user_permission(request, apiuser, repoid, userid):
1818 """
1818 """
1819 Revoke permission for a user on the specified repository.
1819 Revoke permission for a user on the specified repository.
1820
1820
1821 This command can only be run using an |authtoken| with admin
1821 This command can only be run using an |authtoken| with admin
1822 permissions on the |repo|.
1822 permissions on the |repo|.
1823
1823
1824 :param apiuser: This is filled automatically from the |authtoken|.
1824 :param apiuser: This is filled automatically from the |authtoken|.
1825 :type apiuser: AuthUser
1825 :type apiuser: AuthUser
1826 :param repoid: Set the repository name or repository ID.
1826 :param repoid: Set the repository name or repository ID.
1827 :type repoid: str or int
1827 :type repoid: str or int
1828 :param userid: Set the user name of revoked user.
1828 :param userid: Set the user name of revoked user.
1829 :type userid: str or int
1829 :type userid: str or int
1830
1830
1831 Example error output:
1831 Example error output:
1832
1832
1833 .. code-block:: bash
1833 .. code-block:: bash
1834
1834
1835 id : <id_given_in_input>
1835 id : <id_given_in_input>
1836 result: {
1836 result: {
1837 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1837 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1838 "success": true
1838 "success": true
1839 }
1839 }
1840 error: null
1840 error: null
1841 """
1841 """
1842
1842
1843 repo = get_repo_or_error(repoid)
1843 repo = get_repo_or_error(repoid)
1844 user = get_user_or_error(userid)
1844 user = get_user_or_error(userid)
1845 if not has_superadmin_permission(apiuser):
1845 if not has_superadmin_permission(apiuser):
1846 _perms = ('repository.admin',)
1846 _perms = ('repository.admin',)
1847 validate_repo_permissions(apiuser, repoid, repo, _perms)
1847 validate_repo_permissions(apiuser, repoid, repo, _perms)
1848
1848
1849 perm_deletions = [[user.user_id, None, "user"]]
1849 perm_deletions = [[user.user_id, None, "user"]]
1850 try:
1850 try:
1851 changes = RepoModel().update_permissions(
1851 changes = RepoModel().update_permissions(
1852 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1852 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1853
1853
1854 action_data = {
1854 action_data = {
1855 'added': changes['added'],
1855 'added': changes['added'],
1856 'updated': changes['updated'],
1856 'updated': changes['updated'],
1857 'deleted': changes['deleted'],
1857 'deleted': changes['deleted'],
1858 }
1858 }
1859 audit_logger.store_api(
1859 audit_logger.store_api(
1860 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1860 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1861 Session().commit()
1861 Session().commit()
1862 PermissionModel().flush_user_permission_caches(changes)
1862 PermissionModel().flush_user_permission_caches(changes)
1863
1863
1864 return {
1864 return {
1865 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1865 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1866 user.username, repo.repo_name
1866 user.username, repo.repo_name
1867 ),
1867 ),
1868 'success': True
1868 'success': True
1869 }
1869 }
1870 except Exception:
1870 except Exception:
1871 log.exception("Exception occurred while trying revoke permissions to repo")
1871 log.exception("Exception occurred while trying revoke permissions to repo")
1872 raise JSONRPCError(
1872 raise JSONRPCError(
1873 'failed to edit permission for user: `%s` in repo: `%s`' % (
1873 'failed to edit permission for user: `%s` in repo: `%s`' % (
1874 userid, repoid
1874 userid, repoid
1875 )
1875 )
1876 )
1876 )
1877
1877
1878
1878
1879 @jsonrpc_method()
1879 @jsonrpc_method()
1880 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1880 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1881 """
1881 """
1882 Grant permission for a user group on the specified repository,
1882 Grant permission for a user group on the specified repository,
1883 or update existing permissions.
1883 or update existing permissions.
1884
1884
1885 This command can only be run using an |authtoken| with admin
1885 This command can only be run using an |authtoken| with admin
1886 permissions on the |repo|.
1886 permissions on the |repo|.
1887
1887
1888 :param apiuser: This is filled automatically from the |authtoken|.
1888 :param apiuser: This is filled automatically from the |authtoken|.
1889 :type apiuser: AuthUser
1889 :type apiuser: AuthUser
1890 :param repoid: Set the repository name or repository ID.
1890 :param repoid: Set the repository name or repository ID.
1891 :type repoid: str or int
1891 :type repoid: str or int
1892 :param usergroupid: Specify the ID of the user group.
1892 :param usergroupid: Specify the ID of the user group.
1893 :type usergroupid: str or int
1893 :type usergroupid: str or int
1894 :param perm: Set the user group permissions using the following
1894 :param perm: Set the user group permissions using the following
1895 format: (repository.(none|read|write|admin))
1895 format: (repository.(none|read|write|admin))
1896 :type perm: str
1896 :type perm: str
1897
1897
1898 Example output:
1898 Example output:
1899
1899
1900 .. code-block:: bash
1900 .. code-block:: bash
1901
1901
1902 id : <id_given_in_input>
1902 id : <id_given_in_input>
1903 result : {
1903 result : {
1904 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1904 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1905 "success": true
1905 "success": true
1906
1906
1907 }
1907 }
1908 error : null
1908 error : null
1909
1909
1910 Example error output:
1910 Example error output:
1911
1911
1912 .. code-block:: bash
1912 .. code-block:: bash
1913
1913
1914 id : <id_given_in_input>
1914 id : <id_given_in_input>
1915 result : null
1915 result : null
1916 error : {
1916 error : {
1917 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1917 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1918 }
1918 }
1919
1919
1920 """
1920 """
1921
1921
1922 repo = get_repo_or_error(repoid)
1922 repo = get_repo_or_error(repoid)
1923 perm = get_perm_or_error(perm)
1923 perm = get_perm_or_error(perm)
1924 if not has_superadmin_permission(apiuser):
1924 if not has_superadmin_permission(apiuser):
1925 _perms = ('repository.admin',)
1925 _perms = ('repository.admin',)
1926 validate_repo_permissions(apiuser, repoid, repo, _perms)
1926 validate_repo_permissions(apiuser, repoid, repo, _perms)
1927
1927
1928 user_group = get_user_group_or_error(usergroupid)
1928 user_group = get_user_group_or_error(usergroupid)
1929 if not has_superadmin_permission(apiuser):
1929 if not has_superadmin_permission(apiuser):
1930 # check if we have at least read permission for this user group !
1930 # check if we have at least read permission for this user group !
1931 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1931 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1932 if not HasUserGroupPermissionAnyApi(*_perms)(
1932 if not HasUserGroupPermissionAnyApi(*_perms)(
1933 user=apiuser, user_group_name=user_group.users_group_name):
1933 user=apiuser, user_group_name=user_group.users_group_name):
1934 raise JSONRPCError(
1934 raise JSONRPCError(
1935 'user group `%s` does not exist' % (usergroupid,))
1935 'user group `%s` does not exist' % (usergroupid,))
1936
1936
1937 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1937 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1938 try:
1938 try:
1939 changes = RepoModel().update_permissions(
1939 changes = RepoModel().update_permissions(
1940 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1940 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1941 action_data = {
1941 action_data = {
1942 'added': changes['added'],
1942 'added': changes['added'],
1943 'updated': changes['updated'],
1943 'updated': changes['updated'],
1944 'deleted': changes['deleted'],
1944 'deleted': changes['deleted'],
1945 }
1945 }
1946 audit_logger.store_api(
1946 audit_logger.store_api(
1947 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1947 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1948 Session().commit()
1948 Session().commit()
1949 PermissionModel().flush_user_permission_caches(changes)
1949 PermissionModel().flush_user_permission_caches(changes)
1950
1950
1951 return {
1951 return {
1952 'msg': 'Granted perm: `%s` for user group: `%s` in '
1952 'msg': 'Granted perm: `%s` for user group: `%s` in '
1953 'repo: `%s`' % (
1953 'repo: `%s`' % (
1954 perm.permission_name, user_group.users_group_name,
1954 perm.permission_name, user_group.users_group_name,
1955 repo.repo_name
1955 repo.repo_name
1956 ),
1956 ),
1957 'success': True
1957 'success': True
1958 }
1958 }
1959 except Exception:
1959 except Exception:
1960 log.exception(
1960 log.exception(
1961 "Exception occurred while trying change permission on repo")
1961 "Exception occurred while trying change permission on repo")
1962 raise JSONRPCError(
1962 raise JSONRPCError(
1963 'failed to edit permission for user group: `%s` in '
1963 'failed to edit permission for user group: `%s` in '
1964 'repo: `%s`' % (
1964 'repo: `%s`' % (
1965 usergroupid, repo.repo_name
1965 usergroupid, repo.repo_name
1966 )
1966 )
1967 )
1967 )
1968
1968
1969
1969
1970 @jsonrpc_method()
1970 @jsonrpc_method()
1971 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1971 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1972 """
1972 """
1973 Revoke the permissions of a user group on a given repository.
1973 Revoke the permissions of a user group on a given repository.
1974
1974
1975 This command can only be run using an |authtoken| with admin
1975 This command can only be run using an |authtoken| with admin
1976 permissions on the |repo|.
1976 permissions on the |repo|.
1977
1977
1978 :param apiuser: This is filled automatically from the |authtoken|.
1978 :param apiuser: This is filled automatically from the |authtoken|.
1979 :type apiuser: AuthUser
1979 :type apiuser: AuthUser
1980 :param repoid: Set the repository name or repository ID.
1980 :param repoid: Set the repository name or repository ID.
1981 :type repoid: str or int
1981 :type repoid: str or int
1982 :param usergroupid: Specify the user group ID.
1982 :param usergroupid: Specify the user group ID.
1983 :type usergroupid: str or int
1983 :type usergroupid: str or int
1984
1984
1985 Example output:
1985 Example output:
1986
1986
1987 .. code-block:: bash
1987 .. code-block:: bash
1988
1988
1989 id : <id_given_in_input>
1989 id : <id_given_in_input>
1990 result: {
1990 result: {
1991 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1991 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1992 "success": true
1992 "success": true
1993 }
1993 }
1994 error: null
1994 error: null
1995 """
1995 """
1996
1996
1997 repo = get_repo_or_error(repoid)
1997 repo = get_repo_or_error(repoid)
1998 if not has_superadmin_permission(apiuser):
1998 if not has_superadmin_permission(apiuser):
1999 _perms = ('repository.admin',)
1999 _perms = ('repository.admin',)
2000 validate_repo_permissions(apiuser, repoid, repo, _perms)
2000 validate_repo_permissions(apiuser, repoid, repo, _perms)
2001
2001
2002 user_group = get_user_group_or_error(usergroupid)
2002 user_group = get_user_group_or_error(usergroupid)
2003 if not has_superadmin_permission(apiuser):
2003 if not has_superadmin_permission(apiuser):
2004 # check if we have at least read permission for this user group !
2004 # check if we have at least read permission for this user group !
2005 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2005 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
2006 if not HasUserGroupPermissionAnyApi(*_perms)(
2006 if not HasUserGroupPermissionAnyApi(*_perms)(
2007 user=apiuser, user_group_name=user_group.users_group_name):
2007 user=apiuser, user_group_name=user_group.users_group_name):
2008 raise JSONRPCError(
2008 raise JSONRPCError(
2009 'user group `%s` does not exist' % (usergroupid,))
2009 'user group `%s` does not exist' % (usergroupid,))
2010
2010
2011 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2011 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
2012 try:
2012 try:
2013 changes = RepoModel().update_permissions(
2013 changes = RepoModel().update_permissions(
2014 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2014 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
2015 action_data = {
2015 action_data = {
2016 'added': changes['added'],
2016 'added': changes['added'],
2017 'updated': changes['updated'],
2017 'updated': changes['updated'],
2018 'deleted': changes['deleted'],
2018 'deleted': changes['deleted'],
2019 }
2019 }
2020 audit_logger.store_api(
2020 audit_logger.store_api(
2021 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2021 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
2022 Session().commit()
2022 Session().commit()
2023 PermissionModel().flush_user_permission_caches(changes)
2023 PermissionModel().flush_user_permission_caches(changes)
2024
2024
2025 return {
2025 return {
2026 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2026 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
2027 user_group.users_group_name, repo.repo_name
2027 user_group.users_group_name, repo.repo_name
2028 ),
2028 ),
2029 'success': True
2029 'success': True
2030 }
2030 }
2031 except Exception:
2031 except Exception:
2032 log.exception("Exception occurred while trying revoke "
2032 log.exception("Exception occurred while trying revoke "
2033 "user group permission on repo")
2033 "user group permission on repo")
2034 raise JSONRPCError(
2034 raise JSONRPCError(
2035 'failed to edit permission for user group: `%s` in '
2035 'failed to edit permission for user group: `%s` in '
2036 'repo: `%s`' % (
2036 'repo: `%s`' % (
2037 user_group.users_group_name, repo.repo_name
2037 user_group.users_group_name, repo.repo_name
2038 )
2038 )
2039 )
2039 )
2040
2040
2041
2041
2042 @jsonrpc_method()
2042 @jsonrpc_method()
2043 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2043 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
2044 """
2044 """
2045 Triggers a pull on the given repository from a remote location. You
2045 Triggers a pull on the given repository from a remote location. You
2046 can use this to keep remote repositories up-to-date.
2046 can use this to keep remote repositories up-to-date.
2047
2047
2048 This command can only be run using an |authtoken| with admin
2048 This command can only be run using an |authtoken| with admin
2049 rights to the specified repository. For more information,
2049 rights to the specified repository. For more information,
2050 see :ref:`config-token-ref`.
2050 see :ref:`config-token-ref`.
2051
2051
2052 This command takes the following options:
2052 This command takes the following options:
2053
2053
2054 :param apiuser: This is filled automatically from the |authtoken|.
2054 :param apiuser: This is filled automatically from the |authtoken|.
2055 :type apiuser: AuthUser
2055 :type apiuser: AuthUser
2056 :param repoid: The repository name or repository ID.
2056 :param repoid: The repository name or repository ID.
2057 :type repoid: str or int
2057 :type repoid: str or int
2058 :param remote_uri: Optional remote URI to pass in for pull
2058 :param remote_uri: Optional remote URI to pass in for pull
2059 :type remote_uri: str
2059 :type remote_uri: str
2060
2060
2061 Example output:
2061 Example output:
2062
2062
2063 .. code-block:: bash
2063 .. code-block:: bash
2064
2064
2065 id : <id_given_in_input>
2065 id : <id_given_in_input>
2066 result : {
2066 result : {
2067 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2067 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
2068 "repository": "<repository name>"
2068 "repository": "<repository name>"
2069 }
2069 }
2070 error : null
2070 error : null
2071
2071
2072 Example error output:
2072 Example error output:
2073
2073
2074 .. code-block:: bash
2074 .. code-block:: bash
2075
2075
2076 id : <id_given_in_input>
2076 id : <id_given_in_input>
2077 result : null
2077 result : null
2078 error : {
2078 error : {
2079 "Unable to push changes from `<remote_url>`"
2079 "Unable to push changes from `<remote_url>`"
2080 }
2080 }
2081
2081
2082 """
2082 """
2083
2083
2084 repo = get_repo_or_error(repoid)
2084 repo = get_repo_or_error(repoid)
2085 remote_uri = Optional.extract(remote_uri)
2085 remote_uri = Optional.extract(remote_uri)
2086 remote_uri_display = remote_uri or repo.clone_uri_hidden
2086 remote_uri_display = remote_uri or repo.clone_uri_hidden
2087 if not has_superadmin_permission(apiuser):
2087 if not has_superadmin_permission(apiuser):
2088 _perms = ('repository.admin',)
2088 _perms = ('repository.admin',)
2089 validate_repo_permissions(apiuser, repoid, repo, _perms)
2089 validate_repo_permissions(apiuser, repoid, repo, _perms)
2090
2090
2091 try:
2091 try:
2092 ScmModel().pull_changes(
2092 ScmModel().pull_changes(
2093 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2093 repo.repo_name, apiuser.username, remote_uri=remote_uri)
2094 return {
2094 return {
2095 'msg': 'Pulled from url `%s` on repo `%s`' % (
2095 'msg': 'Pulled from url `%s` on repo `%s`' % (
2096 remote_uri_display, repo.repo_name),
2096 remote_uri_display, repo.repo_name),
2097 'repository': repo.repo_name
2097 'repository': repo.repo_name
2098 }
2098 }
2099 except Exception:
2099 except Exception:
2100 log.exception("Exception occurred while trying to "
2100 log.exception("Exception occurred while trying to "
2101 "pull changes from remote location")
2101 "pull changes from remote location")
2102 raise JSONRPCError(
2102 raise JSONRPCError(
2103 'Unable to pull changes from `%s`' % remote_uri_display
2103 'Unable to pull changes from `%s`' % remote_uri_display
2104 )
2104 )
2105
2105
2106
2106
2107 @jsonrpc_method()
2107 @jsonrpc_method()
2108 def strip(request, apiuser, repoid, revision, branch):
2108 def strip(request, apiuser, repoid, revision, branch):
2109 """
2109 """
2110 Strips the given revision from the specified repository.
2110 Strips the given revision from the specified repository.
2111
2111
2112 * This will remove the revision and all of its decendants.
2112 * This will remove the revision and all of its decendants.
2113
2113
2114 This command can only be run using an |authtoken| with admin rights to
2114 This command can only be run using an |authtoken| with admin rights to
2115 the specified repository.
2115 the specified repository.
2116
2116
2117 This command takes the following options:
2117 This command takes the following options:
2118
2118
2119 :param apiuser: This is filled automatically from the |authtoken|.
2119 :param apiuser: This is filled automatically from the |authtoken|.
2120 :type apiuser: AuthUser
2120 :type apiuser: AuthUser
2121 :param repoid: The repository name or repository ID.
2121 :param repoid: The repository name or repository ID.
2122 :type repoid: str or int
2122 :type repoid: str or int
2123 :param revision: The revision you wish to strip.
2123 :param revision: The revision you wish to strip.
2124 :type revision: str
2124 :type revision: str
2125 :param branch: The branch from which to strip the revision.
2125 :param branch: The branch from which to strip the revision.
2126 :type branch: str
2126 :type branch: str
2127
2127
2128 Example output:
2128 Example output:
2129
2129
2130 .. code-block:: bash
2130 .. code-block:: bash
2131
2131
2132 id : <id_given_in_input>
2132 id : <id_given_in_input>
2133 result : {
2133 result : {
2134 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2134 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
2135 "repository": "<repository name>"
2135 "repository": "<repository name>"
2136 }
2136 }
2137 error : null
2137 error : null
2138
2138
2139 Example error output:
2139 Example error output:
2140
2140
2141 .. code-block:: bash
2141 .. code-block:: bash
2142
2142
2143 id : <id_given_in_input>
2143 id : <id_given_in_input>
2144 result : null
2144 result : null
2145 error : {
2145 error : {
2146 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2146 "Unable to strip commit <commit_hash> from repo `<repository name>`"
2147 }
2147 }
2148
2148
2149 """
2149 """
2150
2150
2151 repo = get_repo_or_error(repoid)
2151 repo = get_repo_or_error(repoid)
2152 if not has_superadmin_permission(apiuser):
2152 if not has_superadmin_permission(apiuser):
2153 _perms = ('repository.admin',)
2153 _perms = ('repository.admin',)
2154 validate_repo_permissions(apiuser, repoid, repo, _perms)
2154 validate_repo_permissions(apiuser, repoid, repo, _perms)
2155
2155
2156 try:
2156 try:
2157 ScmModel().strip(repo, revision, branch)
2157 ScmModel().strip(repo, revision, branch)
2158 audit_logger.store_api(
2158 audit_logger.store_api(
2159 'repo.commit.strip', action_data={'commit_id': revision},
2159 'repo.commit.strip', action_data={'commit_id': revision},
2160 repo=repo,
2160 repo=repo,
2161 user=apiuser, commit=True)
2161 user=apiuser, commit=True)
2162
2162
2163 return {
2163 return {
2164 'msg': 'Stripped commit %s from repo `%s`' % (
2164 'msg': 'Stripped commit %s from repo `%s`' % (
2165 revision, repo.repo_name),
2165 revision, repo.repo_name),
2166 'repository': repo.repo_name
2166 'repository': repo.repo_name
2167 }
2167 }
2168 except Exception:
2168 except Exception:
2169 log.exception("Exception while trying to strip")
2169 log.exception("Exception while trying to strip")
2170 raise JSONRPCError(
2170 raise JSONRPCError(
2171 'Unable to strip commit %s from repo `%s`' % (
2171 'Unable to strip commit %s from repo `%s`' % (
2172 revision, repo.repo_name)
2172 revision, repo.repo_name)
2173 )
2173 )
2174
2174
2175
2175
2176 @jsonrpc_method()
2176 @jsonrpc_method()
2177 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2177 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
2178 """
2178 """
2179 Returns all settings for a repository. If key is given it only returns the
2179 Returns all settings for a repository. If key is given it only returns the
2180 setting identified by the key or null.
2180 setting identified by the key or null.
2181
2181
2182 :param apiuser: This is filled automatically from the |authtoken|.
2182 :param apiuser: This is filled automatically from the |authtoken|.
2183 :type apiuser: AuthUser
2183 :type apiuser: AuthUser
2184 :param repoid: The repository name or repository id.
2184 :param repoid: The repository name or repository id.
2185 :type repoid: str or int
2185 :type repoid: str or int
2186 :param key: Key of the setting to return.
2186 :param key: Key of the setting to return.
2187 :type: key: Optional(str)
2187 :type: key: Optional(str)
2188
2188
2189 Example output:
2189 Example output:
2190
2190
2191 .. code-block:: bash
2191 .. code-block:: bash
2192
2192
2193 {
2193 {
2194 "error": null,
2194 "error": null,
2195 "id": 237,
2195 "id": 237,
2196 "result": {
2196 "result": {
2197 "extensions_largefiles": true,
2197 "extensions_largefiles": true,
2198 "extensions_evolve": true,
2198 "extensions_evolve": true,
2199 "hooks_changegroup_push_logger": true,
2199 "hooks_changegroup_push_logger": true,
2200 "hooks_changegroup_repo_size": false,
2200 "hooks_changegroup_repo_size": false,
2201 "hooks_outgoing_pull_logger": true,
2201 "hooks_outgoing_pull_logger": true,
2202 "phases_publish": "True",
2202 "phases_publish": "True",
2203 "rhodecode_hg_use_rebase_for_merging": true,
2203 "rhodecode_hg_use_rebase_for_merging": true,
2204 "rhodecode_pr_merge_enabled": true,
2204 "rhodecode_pr_merge_enabled": true,
2205 "rhodecode_use_outdated_comments": true
2205 "rhodecode_use_outdated_comments": true
2206 }
2206 }
2207 }
2207 }
2208 """
2208 """
2209
2209
2210 # Restrict access to this api method to admins only.
2210 # Restrict access to this api method to admins only.
2211 if not has_superadmin_permission(apiuser):
2211 if not has_superadmin_permission(apiuser):
2212 raise JSONRPCForbidden()
2212 raise JSONRPCForbidden()
2213
2213
2214 try:
2214 try:
2215 repo = get_repo_or_error(repoid)
2215 repo = get_repo_or_error(repoid)
2216 settings_model = VcsSettingsModel(repo=repo)
2216 settings_model = VcsSettingsModel(repo=repo)
2217 settings = settings_model.get_global_settings()
2217 settings = settings_model.get_global_settings()
2218 settings.update(settings_model.get_repo_settings())
2218 settings.update(settings_model.get_repo_settings())
2219
2219
2220 # If only a single setting is requested fetch it from all settings.
2220 # If only a single setting is requested fetch it from all settings.
2221 key = Optional.extract(key)
2221 key = Optional.extract(key)
2222 if key is not None:
2222 if key is not None:
2223 settings = settings.get(key, None)
2223 settings = settings.get(key, None)
2224 except Exception:
2224 except Exception:
2225 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2225 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
2226 log.exception(msg)
2226 log.exception(msg)
2227 raise JSONRPCError(msg)
2227 raise JSONRPCError(msg)
2228
2228
2229 return settings
2229 return settings
2230
2230
2231
2231
2232 @jsonrpc_method()
2232 @jsonrpc_method()
2233 def set_repo_settings(request, apiuser, repoid, settings):
2233 def set_repo_settings(request, apiuser, repoid, settings):
2234 """
2234 """
2235 Update repository settings. Returns true on success.
2235 Update repository settings. Returns true on success.
2236
2236
2237 :param apiuser: This is filled automatically from the |authtoken|.
2237 :param apiuser: This is filled automatically from the |authtoken|.
2238 :type apiuser: AuthUser
2238 :type apiuser: AuthUser
2239 :param repoid: The repository name or repository id.
2239 :param repoid: The repository name or repository id.
2240 :type repoid: str or int
2240 :type repoid: str or int
2241 :param settings: The new settings for the repository.
2241 :param settings: The new settings for the repository.
2242 :type: settings: dict
2242 :type: settings: dict
2243
2243
2244 Example output:
2244 Example output:
2245
2245
2246 .. code-block:: bash
2246 .. code-block:: bash
2247
2247
2248 {
2248 {
2249 "error": null,
2249 "error": null,
2250 "id": 237,
2250 "id": 237,
2251 "result": true
2251 "result": true
2252 }
2252 }
2253 """
2253 """
2254 # Restrict access to this api method to admins only.
2254 # Restrict access to this api method to admins only.
2255 if not has_superadmin_permission(apiuser):
2255 if not has_superadmin_permission(apiuser):
2256 raise JSONRPCForbidden()
2256 raise JSONRPCForbidden()
2257
2257
2258 if type(settings) is not dict:
2258 if type(settings) is not dict:
2259 raise JSONRPCError('Settings have to be a JSON Object.')
2259 raise JSONRPCError('Settings have to be a JSON Object.')
2260
2260
2261 try:
2261 try:
2262 settings_model = VcsSettingsModel(repo=repoid)
2262 settings_model = VcsSettingsModel(repo=repoid)
2263
2263
2264 # Merge global, repo and incoming settings.
2264 # Merge global, repo and incoming settings.
2265 new_settings = settings_model.get_global_settings()
2265 new_settings = settings_model.get_global_settings()
2266 new_settings.update(settings_model.get_repo_settings())
2266 new_settings.update(settings_model.get_repo_settings())
2267 new_settings.update(settings)
2267 new_settings.update(settings)
2268
2268
2269 # Update the settings.
2269 # Update the settings.
2270 inherit_global_settings = new_settings.get(
2270 inherit_global_settings = new_settings.get(
2271 'inherit_global_settings', False)
2271 'inherit_global_settings', False)
2272 settings_model.create_or_update_repo_settings(
2272 settings_model.create_or_update_repo_settings(
2273 new_settings, inherit_global_settings=inherit_global_settings)
2273 new_settings, inherit_global_settings=inherit_global_settings)
2274 Session().commit()
2274 Session().commit()
2275 except Exception:
2275 except Exception:
2276 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2276 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2277 log.exception(msg)
2277 log.exception(msg)
2278 raise JSONRPCError(msg)
2278 raise JSONRPCError(msg)
2279
2279
2280 # Indicate success.
2280 # Indicate success.
2281 return True
2281 return True
2282
2282
2283
2283
2284 @jsonrpc_method()
2284 @jsonrpc_method()
2285 def maintenance(request, apiuser, repoid):
2285 def maintenance(request, apiuser, repoid):
2286 """
2286 """
2287 Triggers a maintenance on the given repository.
2287 Triggers a maintenance on the given repository.
2288
2288
2289 This command can only be run using an |authtoken| with admin
2289 This command can only be run using an |authtoken| with admin
2290 rights to the specified repository. For more information,
2290 rights to the specified repository. For more information,
2291 see :ref:`config-token-ref`.
2291 see :ref:`config-token-ref`.
2292
2292
2293 This command takes the following options:
2293 This command takes the following options:
2294
2294
2295 :param apiuser: This is filled automatically from the |authtoken|.
2295 :param apiuser: This is filled automatically from the |authtoken|.
2296 :type apiuser: AuthUser
2296 :type apiuser: AuthUser
2297 :param repoid: The repository name or repository ID.
2297 :param repoid: The repository name or repository ID.
2298 :type repoid: str or int
2298 :type repoid: str or int
2299
2299
2300 Example output:
2300 Example output:
2301
2301
2302 .. code-block:: bash
2302 .. code-block:: bash
2303
2303
2304 id : <id_given_in_input>
2304 id : <id_given_in_input>
2305 result : {
2305 result : {
2306 "msg": "executed maintenance command",
2306 "msg": "executed maintenance command",
2307 "executed_actions": [
2307 "executed_actions": [
2308 <action_message>, <action_message2>...
2308 <action_message>, <action_message2>...
2309 ],
2309 ],
2310 "repository": "<repository name>"
2310 "repository": "<repository name>"
2311 }
2311 }
2312 error : null
2312 error : null
2313
2313
2314 Example error output:
2314 Example error output:
2315
2315
2316 .. code-block:: bash
2316 .. code-block:: bash
2317
2317
2318 id : <id_given_in_input>
2318 id : <id_given_in_input>
2319 result : null
2319 result : null
2320 error : {
2320 error : {
2321 "Unable to execute maintenance on `<reponame>`"
2321 "Unable to execute maintenance on `<reponame>`"
2322 }
2322 }
2323
2323
2324 """
2324 """
2325
2325
2326 repo = get_repo_or_error(repoid)
2326 repo = get_repo_or_error(repoid)
2327 if not has_superadmin_permission(apiuser):
2327 if not has_superadmin_permission(apiuser):
2328 _perms = ('repository.admin',)
2328 _perms = ('repository.admin',)
2329 validate_repo_permissions(apiuser, repoid, repo, _perms)
2329 validate_repo_permissions(apiuser, repoid, repo, _perms)
2330
2330
2331 try:
2331 try:
2332 maintenance = repo_maintenance.RepoMaintenance()
2332 maintenance = repo_maintenance.RepoMaintenance()
2333 executed_actions = maintenance.execute(repo)
2333 executed_actions = maintenance.execute(repo)
2334
2334
2335 return {
2335 return {
2336 'msg': 'executed maintenance command',
2336 'msg': 'executed maintenance command',
2337 'executed_actions': executed_actions,
2337 'executed_actions': executed_actions,
2338 'repository': repo.repo_name
2338 'repository': repo.repo_name
2339 }
2339 }
2340 except Exception:
2340 except Exception:
2341 log.exception("Exception occurred while trying to run maintenance")
2341 log.exception("Exception occurred while trying to run maintenance")
2342 raise JSONRPCError(
2342 raise JSONRPCError(
2343 'Unable to execute maintenance on `%s`' % repo.repo_name)
2343 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,5576 +1,5578 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 # external identities
620 # external identities
621 external_identities = relationship(
621 external_identities = relationship(
622 'ExternalIdentity',
622 'ExternalIdentity',
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
623 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
624 cascade='all')
624 cascade='all')
625 # review rules
625 # review rules
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
626 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
627
627
628 # artifacts owned
628 # artifacts owned
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
629 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
630
630
631 # no cascade, set NULL
631 # no cascade, set NULL
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
632 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
633
633
634 def __unicode__(self):
634 def __unicode__(self):
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
635 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
636 self.user_id, self.username)
636 self.user_id, self.username)
637
637
638 @hybrid_property
638 @hybrid_property
639 def email(self):
639 def email(self):
640 return self._email
640 return self._email
641
641
642 @email.setter
642 @email.setter
643 def email(self, val):
643 def email(self, val):
644 self._email = val.lower() if val else None
644 self._email = val.lower() if val else None
645
645
646 @hybrid_property
646 @hybrid_property
647 def first_name(self):
647 def first_name(self):
648 from rhodecode.lib import helpers as h
648 from rhodecode.lib import helpers as h
649 if self.name:
649 if self.name:
650 return h.escape(self.name)
650 return h.escape(self.name)
651 return self.name
651 return self.name
652
652
653 @hybrid_property
653 @hybrid_property
654 def last_name(self):
654 def last_name(self):
655 from rhodecode.lib import helpers as h
655 from rhodecode.lib import helpers as h
656 if self.lastname:
656 if self.lastname:
657 return h.escape(self.lastname)
657 return h.escape(self.lastname)
658 return self.lastname
658 return self.lastname
659
659
660 @hybrid_property
660 @hybrid_property
661 def api_key(self):
661 def api_key(self):
662 """
662 """
663 Fetch if exist an auth-token with role ALL connected to this user
663 Fetch if exist an auth-token with role ALL connected to this user
664 """
664 """
665 user_auth_token = UserApiKeys.query()\
665 user_auth_token = UserApiKeys.query()\
666 .filter(UserApiKeys.user_id == self.user_id)\
666 .filter(UserApiKeys.user_id == self.user_id)\
667 .filter(or_(UserApiKeys.expires == -1,
667 .filter(or_(UserApiKeys.expires == -1,
668 UserApiKeys.expires >= time.time()))\
668 UserApiKeys.expires >= time.time()))\
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
669 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
670 if user_auth_token:
670 if user_auth_token:
671 user_auth_token = user_auth_token.api_key
671 user_auth_token = user_auth_token.api_key
672
672
673 return user_auth_token
673 return user_auth_token
674
674
675 @api_key.setter
675 @api_key.setter
676 def api_key(self, val):
676 def api_key(self, val):
677 # don't allow to set API key this is deprecated for now
677 # don't allow to set API key this is deprecated for now
678 self._api_key = None
678 self._api_key = None
679
679
680 @property
680 @property
681 def reviewer_pull_requests(self):
681 def reviewer_pull_requests(self):
682 return PullRequestReviewers.query() \
682 return PullRequestReviewers.query() \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
683 .options(joinedload(PullRequestReviewers.pull_request)) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
684 .filter(PullRequestReviewers.user_id == self.user_id) \
685 .all()
685 .all()
686
686
687 @property
687 @property
688 def firstname(self):
688 def firstname(self):
689 # alias for future
689 # alias for future
690 return self.name
690 return self.name
691
691
692 @property
692 @property
693 def emails(self):
693 def emails(self):
694 other = UserEmailMap.query()\
694 other = UserEmailMap.query()\
695 .filter(UserEmailMap.user == self) \
695 .filter(UserEmailMap.user == self) \
696 .order_by(UserEmailMap.email_id.asc()) \
696 .order_by(UserEmailMap.email_id.asc()) \
697 .all()
697 .all()
698 return [self.email] + [x.email for x in other]
698 return [self.email] + [x.email for x in other]
699
699
700 def emails_cached(self):
700 def emails_cached(self):
701 emails = UserEmailMap.query()\
701 emails = UserEmailMap.query()\
702 .filter(UserEmailMap.user == self) \
702 .filter(UserEmailMap.user == self) \
703 .order_by(UserEmailMap.email_id.asc())
703 .order_by(UserEmailMap.email_id.asc())
704
704
705 emails = emails.options(
705 emails = emails.options(
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
706 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
707 )
707 )
708
708
709 return [self.email] + [x.email for x in emails]
709 return [self.email] + [x.email for x in emails]
710
710
711 @property
711 @property
712 def auth_tokens(self):
712 def auth_tokens(self):
713 auth_tokens = self.get_auth_tokens()
713 auth_tokens = self.get_auth_tokens()
714 return [x.api_key for x in auth_tokens]
714 return [x.api_key for x in auth_tokens]
715
715
716 def get_auth_tokens(self):
716 def get_auth_tokens(self):
717 return UserApiKeys.query()\
717 return UserApiKeys.query()\
718 .filter(UserApiKeys.user == self)\
718 .filter(UserApiKeys.user == self)\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
719 .order_by(UserApiKeys.user_api_key_id.asc())\
720 .all()
720 .all()
721
721
722 @LazyProperty
722 @LazyProperty
723 def feed_token(self):
723 def feed_token(self):
724 return self.get_feed_token()
724 return self.get_feed_token()
725
725
726 def get_feed_token(self, cache=True):
726 def get_feed_token(self, cache=True):
727 feed_tokens = UserApiKeys.query()\
727 feed_tokens = UserApiKeys.query()\
728 .filter(UserApiKeys.user == self)\
728 .filter(UserApiKeys.user == self)\
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
729 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
730 if cache:
730 if cache:
731 feed_tokens = feed_tokens.options(
731 feed_tokens = feed_tokens.options(
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
732 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
733
733
734 feed_tokens = feed_tokens.all()
734 feed_tokens = feed_tokens.all()
735 if feed_tokens:
735 if feed_tokens:
736 return feed_tokens[0].api_key
736 return feed_tokens[0].api_key
737 return 'NO_FEED_TOKEN_AVAILABLE'
737 return 'NO_FEED_TOKEN_AVAILABLE'
738
738
739 @LazyProperty
739 @LazyProperty
740 def artifact_token(self):
740 def artifact_token(self):
741 return self.get_artifact_token()
741 return self.get_artifact_token()
742
742
743 def get_artifact_token(self, cache=True):
743 def get_artifact_token(self, cache=True):
744 artifacts_tokens = UserApiKeys.query()\
744 artifacts_tokens = UserApiKeys.query()\
745 .filter(UserApiKeys.user == self)\
745 .filter(UserApiKeys.user == self)\
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
746 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
747 if cache:
747 if cache:
748 artifacts_tokens = artifacts_tokens.options(
748 artifacts_tokens = artifacts_tokens.options(
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
749 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
750
750
751 artifacts_tokens = artifacts_tokens.all()
751 artifacts_tokens = artifacts_tokens.all()
752 if artifacts_tokens:
752 if artifacts_tokens:
753 return artifacts_tokens[0].api_key
753 return artifacts_tokens[0].api_key
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
754 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
755
755
756 @classmethod
756 @classmethod
757 def get(cls, user_id, cache=False):
757 def get(cls, user_id, cache=False):
758 if not user_id:
758 if not user_id:
759 return
759 return
760
760
761 user = cls.query()
761 user = cls.query()
762 if cache:
762 if cache:
763 user = user.options(
763 user = user.options(
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
764 FromCache("sql_cache_short", "get_users_%s" % user_id))
765 return user.get(user_id)
765 return user.get(user_id)
766
766
767 @classmethod
767 @classmethod
768 def extra_valid_auth_tokens(cls, user, role=None):
768 def extra_valid_auth_tokens(cls, user, role=None):
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
769 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
770 .filter(or_(UserApiKeys.expires == -1,
770 .filter(or_(UserApiKeys.expires == -1,
771 UserApiKeys.expires >= time.time()))
771 UserApiKeys.expires >= time.time()))
772 if role:
772 if role:
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
773 tokens = tokens.filter(or_(UserApiKeys.role == role,
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
774 UserApiKeys.role == UserApiKeys.ROLE_ALL))
775 return tokens.all()
775 return tokens.all()
776
776
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
777 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
778 from rhodecode.lib import auth
778 from rhodecode.lib import auth
779
779
780 log.debug('Trying to authenticate user: %s via auth-token, '
780 log.debug('Trying to authenticate user: %s via auth-token, '
781 'and roles: %s', self, roles)
781 'and roles: %s', self, roles)
782
782
783 if not auth_token:
783 if not auth_token:
784 return False
784 return False
785
785
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
786 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
787 tokens_q = UserApiKeys.query()\
787 tokens_q = UserApiKeys.query()\
788 .filter(UserApiKeys.user_id == self.user_id)\
788 .filter(UserApiKeys.user_id == self.user_id)\
789 .filter(or_(UserApiKeys.expires == -1,
789 .filter(or_(UserApiKeys.expires == -1,
790 UserApiKeys.expires >= time.time()))
790 UserApiKeys.expires >= time.time()))
791
791
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
792 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
793
793
794 crypto_backend = auth.crypto_backend()
794 crypto_backend = auth.crypto_backend()
795 enc_token_map = {}
795 enc_token_map = {}
796 plain_token_map = {}
796 plain_token_map = {}
797 for token in tokens_q:
797 for token in tokens_q:
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
798 if token.api_key.startswith(crypto_backend.ENC_PREF):
799 enc_token_map[token.api_key] = token
799 enc_token_map[token.api_key] = token
800 else:
800 else:
801 plain_token_map[token.api_key] = token
801 plain_token_map[token.api_key] = token
802 log.debug(
802 log.debug(
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
803 'Found %s plain and %s encrypted tokens to check for authentication for this user',
804 len(plain_token_map), len(enc_token_map))
804 len(plain_token_map), len(enc_token_map))
805
805
806 # plain token match comes first
806 # plain token match comes first
807 match = plain_token_map.get(auth_token)
807 match = plain_token_map.get(auth_token)
808
808
809 # check encrypted tokens now
809 # check encrypted tokens now
810 if not match:
810 if not match:
811 for token_hash, token in enc_token_map.items():
811 for token_hash, token in enc_token_map.items():
812 # NOTE(marcink): this is expensive to calculate, but most secure
812 # NOTE(marcink): this is expensive to calculate, but most secure
813 if crypto_backend.hash_check(auth_token, token_hash):
813 if crypto_backend.hash_check(auth_token, token_hash):
814 match = token
814 match = token
815 break
815 break
816
816
817 if match:
817 if match:
818 log.debug('Found matching token %s', match)
818 log.debug('Found matching token %s', match)
819 if match.repo_id:
819 if match.repo_id:
820 log.debug('Found scope, checking for scope match of token %s', match)
820 log.debug('Found scope, checking for scope match of token %s', match)
821 if match.repo_id == scope_repo_id:
821 if match.repo_id == scope_repo_id:
822 return True
822 return True
823 else:
823 else:
824 log.debug(
824 log.debug(
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
825 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
826 'and calling scope is:%s, skipping further checks',
826 'and calling scope is:%s, skipping further checks',
827 match.repo, scope_repo_id)
827 match.repo, scope_repo_id)
828 return False
828 return False
829 else:
829 else:
830 return True
830 return True
831
831
832 return False
832 return False
833
833
834 @property
834 @property
835 def ip_addresses(self):
835 def ip_addresses(self):
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
836 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
837 return [x.ip_addr for x in ret]
837 return [x.ip_addr for x in ret]
838
838
839 @property
839 @property
840 def username_and_name(self):
840 def username_and_name(self):
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
841 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
842
842
843 @property
843 @property
844 def username_or_name_or_email(self):
844 def username_or_name_or_email(self):
845 full_name = self.full_name if self.full_name is not ' ' else None
845 full_name = self.full_name if self.full_name is not ' ' else None
846 return self.username or full_name or self.email
846 return self.username or full_name or self.email
847
847
848 @property
848 @property
849 def full_name(self):
849 def full_name(self):
850 return '%s %s' % (self.first_name, self.last_name)
850 return '%s %s' % (self.first_name, self.last_name)
851
851
852 @property
852 @property
853 def full_name_or_username(self):
853 def full_name_or_username(self):
854 return ('%s %s' % (self.first_name, self.last_name)
854 return ('%s %s' % (self.first_name, self.last_name)
855 if (self.first_name and self.last_name) else self.username)
855 if (self.first_name and self.last_name) else self.username)
856
856
857 @property
857 @property
858 def full_contact(self):
858 def full_contact(self):
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
859 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
860
860
861 @property
861 @property
862 def short_contact(self):
862 def short_contact(self):
863 return '%s %s' % (self.first_name, self.last_name)
863 return '%s %s' % (self.first_name, self.last_name)
864
864
865 @property
865 @property
866 def is_admin(self):
866 def is_admin(self):
867 return self.admin
867 return self.admin
868
868
869 @property
869 @property
870 def language(self):
870 def language(self):
871 return self.user_data.get('language')
871 return self.user_data.get('language')
872
872
873 def AuthUser(self, **kwargs):
873 def AuthUser(self, **kwargs):
874 """
874 """
875 Returns instance of AuthUser for this user
875 Returns instance of AuthUser for this user
876 """
876 """
877 from rhodecode.lib.auth import AuthUser
877 from rhodecode.lib.auth import AuthUser
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
878 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
879
879
880 @hybrid_property
880 @hybrid_property
881 def user_data(self):
881 def user_data(self):
882 if not self._user_data:
882 if not self._user_data:
883 return {}
883 return {}
884
884
885 try:
885 try:
886 return json.loads(self._user_data)
886 return json.loads(self._user_data)
887 except TypeError:
887 except TypeError:
888 return {}
888 return {}
889
889
890 @user_data.setter
890 @user_data.setter
891 def user_data(self, val):
891 def user_data(self, val):
892 if not isinstance(val, dict):
892 if not isinstance(val, dict):
893 raise Exception('user_data must be dict, got %s' % type(val))
893 raise Exception('user_data must be dict, got %s' % type(val))
894 try:
894 try:
895 self._user_data = json.dumps(val)
895 self._user_data = json.dumps(val)
896 except Exception:
896 except Exception:
897 log.error(traceback.format_exc())
897 log.error(traceback.format_exc())
898
898
899 @classmethod
899 @classmethod
900 def get_by_username(cls, username, case_insensitive=False,
900 def get_by_username(cls, username, case_insensitive=False,
901 cache=False, identity_cache=False):
901 cache=False, identity_cache=False):
902 session = Session()
902 session = Session()
903
903
904 if case_insensitive:
904 if case_insensitive:
905 q = cls.query().filter(
905 q = cls.query().filter(
906 func.lower(cls.username) == func.lower(username))
906 func.lower(cls.username) == func.lower(username))
907 else:
907 else:
908 q = cls.query().filter(cls.username == username)
908 q = cls.query().filter(cls.username == username)
909
909
910 if cache:
910 if cache:
911 if identity_cache:
911 if identity_cache:
912 val = cls.identity_cache(session, 'username', username)
912 val = cls.identity_cache(session, 'username', username)
913 if val:
913 if val:
914 return val
914 return val
915 else:
915 else:
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
916 cache_key = "get_user_by_name_%s" % _hash_key(username)
917 q = q.options(
917 q = q.options(
918 FromCache("sql_cache_short", cache_key))
918 FromCache("sql_cache_short", cache_key))
919
919
920 return q.scalar()
920 return q.scalar()
921
921
922 @classmethod
922 @classmethod
923 def get_by_auth_token(cls, auth_token, cache=False):
923 def get_by_auth_token(cls, auth_token, cache=False):
924 q = UserApiKeys.query()\
924 q = UserApiKeys.query()\
925 .filter(UserApiKeys.api_key == auth_token)\
925 .filter(UserApiKeys.api_key == auth_token)\
926 .filter(or_(UserApiKeys.expires == -1,
926 .filter(or_(UserApiKeys.expires == -1,
927 UserApiKeys.expires >= time.time()))
927 UserApiKeys.expires >= time.time()))
928 if cache:
928 if cache:
929 q = q.options(
929 q = q.options(
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
930 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
931
931
932 match = q.first()
932 match = q.first()
933 if match:
933 if match:
934 return match.user
934 return match.user
935
935
936 @classmethod
936 @classmethod
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
937 def get_by_email(cls, email, case_insensitive=False, cache=False):
938
938
939 if case_insensitive:
939 if case_insensitive:
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
940 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
941
941
942 else:
942 else:
943 q = cls.query().filter(cls.email == email)
943 q = cls.query().filter(cls.email == email)
944
944
945 email_key = _hash_key(email)
945 email_key = _hash_key(email)
946 if cache:
946 if cache:
947 q = q.options(
947 q = q.options(
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
948 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
949
949
950 ret = q.scalar()
950 ret = q.scalar()
951 if ret is None:
951 if ret is None:
952 q = UserEmailMap.query()
952 q = UserEmailMap.query()
953 # try fetching in alternate email map
953 # try fetching in alternate email map
954 if case_insensitive:
954 if case_insensitive:
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
955 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
956 else:
956 else:
957 q = q.filter(UserEmailMap.email == email)
957 q = q.filter(UserEmailMap.email == email)
958 q = q.options(joinedload(UserEmailMap.user))
958 q = q.options(joinedload(UserEmailMap.user))
959 if cache:
959 if cache:
960 q = q.options(
960 q = q.options(
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
961 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
962 ret = getattr(q.scalar(), 'user', None)
962 ret = getattr(q.scalar(), 'user', None)
963
963
964 return ret
964 return ret
965
965
966 @classmethod
966 @classmethod
967 def get_from_cs_author(cls, author):
967 def get_from_cs_author(cls, author):
968 """
968 """
969 Tries to get User objects out of commit author string
969 Tries to get User objects out of commit author string
970
970
971 :param author:
971 :param author:
972 """
972 """
973 from rhodecode.lib.helpers import email, author_name
973 from rhodecode.lib.helpers import email, author_name
974 # Valid email in the attribute passed, see if they're in the system
974 # Valid email in the attribute passed, see if they're in the system
975 _email = email(author)
975 _email = email(author)
976 if _email:
976 if _email:
977 user = cls.get_by_email(_email, case_insensitive=True)
977 user = cls.get_by_email(_email, case_insensitive=True)
978 if user:
978 if user:
979 return user
979 return user
980 # Maybe we can match by username?
980 # Maybe we can match by username?
981 _author = author_name(author)
981 _author = author_name(author)
982 user = cls.get_by_username(_author, case_insensitive=True)
982 user = cls.get_by_username(_author, case_insensitive=True)
983 if user:
983 if user:
984 return user
984 return user
985
985
986 def update_userdata(self, **kwargs):
986 def update_userdata(self, **kwargs):
987 usr = self
987 usr = self
988 old = usr.user_data
988 old = usr.user_data
989 old.update(**kwargs)
989 old.update(**kwargs)
990 usr.user_data = old
990 usr.user_data = old
991 Session().add(usr)
991 Session().add(usr)
992 log.debug('updated userdata with %s', kwargs)
992 log.debug('updated userdata with %s', kwargs)
993
993
994 def update_lastlogin(self):
994 def update_lastlogin(self):
995 """Update user lastlogin"""
995 """Update user lastlogin"""
996 self.last_login = datetime.datetime.now()
996 self.last_login = datetime.datetime.now()
997 Session().add(self)
997 Session().add(self)
998 log.debug('updated user %s lastlogin', self.username)
998 log.debug('updated user %s lastlogin', self.username)
999
999
1000 def update_password(self, new_password):
1000 def update_password(self, new_password):
1001 from rhodecode.lib.auth import get_crypt_password
1001 from rhodecode.lib.auth import get_crypt_password
1002
1002
1003 self.password = get_crypt_password(new_password)
1003 self.password = get_crypt_password(new_password)
1004 Session().add(self)
1004 Session().add(self)
1005
1005
1006 @classmethod
1006 @classmethod
1007 def get_first_super_admin(cls):
1007 def get_first_super_admin(cls):
1008 user = User.query()\
1008 user = User.query()\
1009 .filter(User.admin == true()) \
1009 .filter(User.admin == true()) \
1010 .order_by(User.user_id.asc()) \
1010 .order_by(User.user_id.asc()) \
1011 .first()
1011 .first()
1012
1012
1013 if user is None:
1013 if user is None:
1014 raise Exception('FATAL: Missing administrative account!')
1014 raise Exception('FATAL: Missing administrative account!')
1015 return user
1015 return user
1016
1016
1017 @classmethod
1017 @classmethod
1018 def get_all_super_admins(cls, only_active=False):
1018 def get_all_super_admins(cls, only_active=False):
1019 """
1019 """
1020 Returns all admin accounts sorted by username
1020 Returns all admin accounts sorted by username
1021 """
1021 """
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1022 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1023 if only_active:
1023 if only_active:
1024 qry = qry.filter(User.active == true())
1024 qry = qry.filter(User.active == true())
1025 return qry.all()
1025 return qry.all()
1026
1026
1027 @classmethod
1027 @classmethod
1028 def get_all_user_ids(cls, only_active=True):
1028 def get_all_user_ids(cls, only_active=True):
1029 """
1029 """
1030 Returns all users IDs
1030 Returns all users IDs
1031 """
1031 """
1032 qry = Session().query(User.user_id)
1032 qry = Session().query(User.user_id)
1033
1033
1034 if only_active:
1034 if only_active:
1035 qry = qry.filter(User.active == true())
1035 qry = qry.filter(User.active == true())
1036 return [x.user_id for x in qry]
1036 return [x.user_id for x in qry]
1037
1037
1038 @classmethod
1038 @classmethod
1039 def get_default_user(cls, cache=False, refresh=False):
1039 def get_default_user(cls, cache=False, refresh=False):
1040 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1040 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1041 if user is None:
1041 if user is None:
1042 raise Exception('FATAL: Missing default account!')
1042 raise Exception('FATAL: Missing default account!')
1043 if refresh:
1043 if refresh:
1044 # The default user might be based on outdated state which
1044 # The default user might be based on outdated state which
1045 # has been loaded from the cache.
1045 # has been loaded from the cache.
1046 # A call to refresh() ensures that the
1046 # A call to refresh() ensures that the
1047 # latest state from the database is used.
1047 # latest state from the database is used.
1048 Session().refresh(user)
1048 Session().refresh(user)
1049 return user
1049 return user
1050
1050
1051 def _get_default_perms(self, user, suffix=''):
1051 def _get_default_perms(self, user, suffix=''):
1052 from rhodecode.model.permission import PermissionModel
1052 from rhodecode.model.permission import PermissionModel
1053 return PermissionModel().get_default_perms(user.user_perms, suffix)
1053 return PermissionModel().get_default_perms(user.user_perms, suffix)
1054
1054
1055 def get_default_perms(self, suffix=''):
1055 def get_default_perms(self, suffix=''):
1056 return self._get_default_perms(self, suffix)
1056 return self._get_default_perms(self, suffix)
1057
1057
1058 def get_api_data(self, include_secrets=False, details='full'):
1058 def get_api_data(self, include_secrets=False, details='full'):
1059 """
1059 """
1060 Common function for generating user related data for API
1060 Common function for generating user related data for API
1061
1061
1062 :param include_secrets: By default secrets in the API data will be replaced
1062 :param include_secrets: By default secrets in the API data will be replaced
1063 by a placeholder value to prevent exposing this data by accident. In case
1063 by a placeholder value to prevent exposing this data by accident. In case
1064 this data shall be exposed, set this flag to ``True``.
1064 this data shall be exposed, set this flag to ``True``.
1065
1065
1066 :param details: details can be 'basic|full' basic gives only a subset of
1066 :param details: details can be 'basic|full' basic gives only a subset of
1067 the available user information that includes user_id, name and emails.
1067 the available user information that includes user_id, name and emails.
1068 """
1068 """
1069 user = self
1069 user = self
1070 user_data = self.user_data
1070 user_data = self.user_data
1071 data = {
1071 data = {
1072 'user_id': user.user_id,
1072 'user_id': user.user_id,
1073 'username': user.username,
1073 'username': user.username,
1074 'firstname': user.name,
1074 'firstname': user.name,
1075 'lastname': user.lastname,
1075 'lastname': user.lastname,
1076 'description': user.description,
1076 'description': user.description,
1077 'email': user.email,
1077 'email': user.email,
1078 'emails': user.emails,
1078 'emails': user.emails,
1079 }
1079 }
1080 if details == 'basic':
1080 if details == 'basic':
1081 return data
1081 return data
1082
1082
1083 auth_token_length = 40
1083 auth_token_length = 40
1084 auth_token_replacement = '*' * auth_token_length
1084 auth_token_replacement = '*' * auth_token_length
1085
1085
1086 extras = {
1086 extras = {
1087 'auth_tokens': [auth_token_replacement],
1087 'auth_tokens': [auth_token_replacement],
1088 'active': user.active,
1088 'active': user.active,
1089 'admin': user.admin,
1089 'admin': user.admin,
1090 'extern_type': user.extern_type,
1090 'extern_type': user.extern_type,
1091 'extern_name': user.extern_name,
1091 'extern_name': user.extern_name,
1092 'last_login': user.last_login,
1092 'last_login': user.last_login,
1093 'last_activity': user.last_activity,
1093 'last_activity': user.last_activity,
1094 'ip_addresses': user.ip_addresses,
1094 'ip_addresses': user.ip_addresses,
1095 'language': user_data.get('language')
1095 'language': user_data.get('language')
1096 }
1096 }
1097 data.update(extras)
1097 data.update(extras)
1098
1098
1099 if include_secrets:
1099 if include_secrets:
1100 data['auth_tokens'] = user.auth_tokens
1100 data['auth_tokens'] = user.auth_tokens
1101 return data
1101 return data
1102
1102
1103 def __json__(self):
1103 def __json__(self):
1104 data = {
1104 data = {
1105 'full_name': self.full_name,
1105 'full_name': self.full_name,
1106 'full_name_or_username': self.full_name_or_username,
1106 'full_name_or_username': self.full_name_or_username,
1107 'short_contact': self.short_contact,
1107 'short_contact': self.short_contact,
1108 'full_contact': self.full_contact,
1108 'full_contact': self.full_contact,
1109 }
1109 }
1110 data.update(self.get_api_data())
1110 data.update(self.get_api_data())
1111 return data
1111 return data
1112
1112
1113
1113
1114 class UserApiKeys(Base, BaseModel):
1114 class UserApiKeys(Base, BaseModel):
1115 __tablename__ = 'user_api_keys'
1115 __tablename__ = 'user_api_keys'
1116 __table_args__ = (
1116 __table_args__ = (
1117 Index('uak_api_key_idx', 'api_key'),
1117 Index('uak_api_key_idx', 'api_key'),
1118 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1118 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1119 base_table_args
1119 base_table_args
1120 )
1120 )
1121 __mapper_args__ = {}
1121 __mapper_args__ = {}
1122
1122
1123 # ApiKey role
1123 # ApiKey role
1124 ROLE_ALL = 'token_role_all'
1124 ROLE_ALL = 'token_role_all'
1125 ROLE_HTTP = 'token_role_http'
1125 ROLE_HTTP = 'token_role_http'
1126 ROLE_VCS = 'token_role_vcs'
1126 ROLE_VCS = 'token_role_vcs'
1127 ROLE_API = 'token_role_api'
1127 ROLE_API = 'token_role_api'
1128 ROLE_FEED = 'token_role_feed'
1128 ROLE_FEED = 'token_role_feed'
1129 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1129 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1130 ROLE_PASSWORD_RESET = 'token_password_reset'
1130 ROLE_PASSWORD_RESET = 'token_password_reset'
1131
1131
1132 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1132 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1133
1133
1134 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1134 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1136 api_key = Column("api_key", String(255), nullable=False, unique=True)
1136 api_key = Column("api_key", String(255), nullable=False, unique=True)
1137 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1137 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1138 expires = Column('expires', Float(53), nullable=False)
1138 expires = Column('expires', Float(53), nullable=False)
1139 role = Column('role', String(255), nullable=True)
1139 role = Column('role', String(255), nullable=True)
1140 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1140 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1141
1141
1142 # scope columns
1142 # scope columns
1143 repo_id = Column(
1143 repo_id = Column(
1144 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1144 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1145 nullable=True, unique=None, default=None)
1145 nullable=True, unique=None, default=None)
1146 repo = relationship('Repository', lazy='joined')
1146 repo = relationship('Repository', lazy='joined')
1147
1147
1148 repo_group_id = Column(
1148 repo_group_id = Column(
1149 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1149 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1150 nullable=True, unique=None, default=None)
1150 nullable=True, unique=None, default=None)
1151 repo_group = relationship('RepoGroup', lazy='joined')
1151 repo_group = relationship('RepoGroup', lazy='joined')
1152
1152
1153 user = relationship('User', lazy='joined')
1153 user = relationship('User', lazy='joined')
1154
1154
1155 def __unicode__(self):
1155 def __unicode__(self):
1156 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1156 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1157
1157
1158 def __json__(self):
1158 def __json__(self):
1159 data = {
1159 data = {
1160 'auth_token': self.api_key,
1160 'auth_token': self.api_key,
1161 'role': self.role,
1161 'role': self.role,
1162 'scope': self.scope_humanized,
1162 'scope': self.scope_humanized,
1163 'expired': self.expired
1163 'expired': self.expired
1164 }
1164 }
1165 return data
1165 return data
1166
1166
1167 def get_api_data(self, include_secrets=False):
1167 def get_api_data(self, include_secrets=False):
1168 data = self.__json__()
1168 data = self.__json__()
1169 if include_secrets:
1169 if include_secrets:
1170 return data
1170 return data
1171 else:
1171 else:
1172 data['auth_token'] = self.token_obfuscated
1172 data['auth_token'] = self.token_obfuscated
1173 return data
1173 return data
1174
1174
1175 @hybrid_property
1175 @hybrid_property
1176 def description_safe(self):
1176 def description_safe(self):
1177 from rhodecode.lib import helpers as h
1177 from rhodecode.lib import helpers as h
1178 return h.escape(self.description)
1178 return h.escape(self.description)
1179
1179
1180 @property
1180 @property
1181 def expired(self):
1181 def expired(self):
1182 if self.expires == -1:
1182 if self.expires == -1:
1183 return False
1183 return False
1184 return time.time() > self.expires
1184 return time.time() > self.expires
1185
1185
1186 @classmethod
1186 @classmethod
1187 def _get_role_name(cls, role):
1187 def _get_role_name(cls, role):
1188 return {
1188 return {
1189 cls.ROLE_ALL: _('all'),
1189 cls.ROLE_ALL: _('all'),
1190 cls.ROLE_HTTP: _('http/web interface'),
1190 cls.ROLE_HTTP: _('http/web interface'),
1191 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1191 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1192 cls.ROLE_API: _('api calls'),
1192 cls.ROLE_API: _('api calls'),
1193 cls.ROLE_FEED: _('feed access'),
1193 cls.ROLE_FEED: _('feed access'),
1194 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1194 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1195 }.get(role, role)
1195 }.get(role, role)
1196
1196
1197 @property
1197 @property
1198 def role_humanized(self):
1198 def role_humanized(self):
1199 return self._get_role_name(self.role)
1199 return self._get_role_name(self.role)
1200
1200
1201 def _get_scope(self):
1201 def _get_scope(self):
1202 if self.repo:
1202 if self.repo:
1203 return 'Repository: {}'.format(self.repo.repo_name)
1203 return 'Repository: {}'.format(self.repo.repo_name)
1204 if self.repo_group:
1204 if self.repo_group:
1205 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1205 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1206 return 'Global'
1206 return 'Global'
1207
1207
1208 @property
1208 @property
1209 def scope_humanized(self):
1209 def scope_humanized(self):
1210 return self._get_scope()
1210 return self._get_scope()
1211
1211
1212 @property
1212 @property
1213 def token_obfuscated(self):
1213 def token_obfuscated(self):
1214 if self.api_key:
1214 if self.api_key:
1215 return self.api_key[:4] + "****"
1215 return self.api_key[:4] + "****"
1216
1216
1217
1217
1218 class UserEmailMap(Base, BaseModel):
1218 class UserEmailMap(Base, BaseModel):
1219 __tablename__ = 'user_email_map'
1219 __tablename__ = 'user_email_map'
1220 __table_args__ = (
1220 __table_args__ = (
1221 Index('uem_email_idx', 'email'),
1221 Index('uem_email_idx', 'email'),
1222 UniqueConstraint('email'),
1222 UniqueConstraint('email'),
1223 base_table_args
1223 base_table_args
1224 )
1224 )
1225 __mapper_args__ = {}
1225 __mapper_args__ = {}
1226
1226
1227 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1227 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1228 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1228 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1229 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1229 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1230 user = relationship('User', lazy='joined')
1230 user = relationship('User', lazy='joined')
1231
1231
1232 @validates('_email')
1232 @validates('_email')
1233 def validate_email(self, key, email):
1233 def validate_email(self, key, email):
1234 # check if this email is not main one
1234 # check if this email is not main one
1235 main_email = Session().query(User).filter(User.email == email).scalar()
1235 main_email = Session().query(User).filter(User.email == email).scalar()
1236 if main_email is not None:
1236 if main_email is not None:
1237 raise AttributeError('email %s is present is user table' % email)
1237 raise AttributeError('email %s is present is user table' % email)
1238 return email
1238 return email
1239
1239
1240 @hybrid_property
1240 @hybrid_property
1241 def email(self):
1241 def email(self):
1242 return self._email
1242 return self._email
1243
1243
1244 @email.setter
1244 @email.setter
1245 def email(self, val):
1245 def email(self, val):
1246 self._email = val.lower() if val else None
1246 self._email = val.lower() if val else None
1247
1247
1248
1248
1249 class UserIpMap(Base, BaseModel):
1249 class UserIpMap(Base, BaseModel):
1250 __tablename__ = 'user_ip_map'
1250 __tablename__ = 'user_ip_map'
1251 __table_args__ = (
1251 __table_args__ = (
1252 UniqueConstraint('user_id', 'ip_addr'),
1252 UniqueConstraint('user_id', 'ip_addr'),
1253 base_table_args
1253 base_table_args
1254 )
1254 )
1255 __mapper_args__ = {}
1255 __mapper_args__ = {}
1256
1256
1257 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1257 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1258 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1258 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1259 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1259 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1260 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1260 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1261 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1261 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1262 user = relationship('User', lazy='joined')
1262 user = relationship('User', lazy='joined')
1263
1263
1264 @hybrid_property
1264 @hybrid_property
1265 def description_safe(self):
1265 def description_safe(self):
1266 from rhodecode.lib import helpers as h
1266 from rhodecode.lib import helpers as h
1267 return h.escape(self.description)
1267 return h.escape(self.description)
1268
1268
1269 @classmethod
1269 @classmethod
1270 def _get_ip_range(cls, ip_addr):
1270 def _get_ip_range(cls, ip_addr):
1271 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1271 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1272 return [str(net.network_address), str(net.broadcast_address)]
1272 return [str(net.network_address), str(net.broadcast_address)]
1273
1273
1274 def __json__(self):
1274 def __json__(self):
1275 return {
1275 return {
1276 'ip_addr': self.ip_addr,
1276 'ip_addr': self.ip_addr,
1277 'ip_range': self._get_ip_range(self.ip_addr),
1277 'ip_range': self._get_ip_range(self.ip_addr),
1278 }
1278 }
1279
1279
1280 def __unicode__(self):
1280 def __unicode__(self):
1281 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1281 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1282 self.user_id, self.ip_addr)
1282 self.user_id, self.ip_addr)
1283
1283
1284
1284
1285 class UserSshKeys(Base, BaseModel):
1285 class UserSshKeys(Base, BaseModel):
1286 __tablename__ = 'user_ssh_keys'
1286 __tablename__ = 'user_ssh_keys'
1287 __table_args__ = (
1287 __table_args__ = (
1288 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1288 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1289
1289
1290 UniqueConstraint('ssh_key_fingerprint'),
1290 UniqueConstraint('ssh_key_fingerprint'),
1291
1291
1292 base_table_args
1292 base_table_args
1293 )
1293 )
1294 __mapper_args__ = {}
1294 __mapper_args__ = {}
1295
1295
1296 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1296 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1297 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1297 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1298 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1298 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1299
1299
1300 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1300 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1301
1301
1302 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1302 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1303 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1303 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1304 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1304 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1305
1305
1306 user = relationship('User', lazy='joined')
1306 user = relationship('User', lazy='joined')
1307
1307
1308 def __json__(self):
1308 def __json__(self):
1309 data = {
1309 data = {
1310 'ssh_fingerprint': self.ssh_key_fingerprint,
1310 'ssh_fingerprint': self.ssh_key_fingerprint,
1311 'description': self.description,
1311 'description': self.description,
1312 'created_on': self.created_on
1312 'created_on': self.created_on
1313 }
1313 }
1314 return data
1314 return data
1315
1315
1316 def get_api_data(self):
1316 def get_api_data(self):
1317 data = self.__json__()
1317 data = self.__json__()
1318 return data
1318 return data
1319
1319
1320
1320
1321 class UserLog(Base, BaseModel):
1321 class UserLog(Base, BaseModel):
1322 __tablename__ = 'user_logs'
1322 __tablename__ = 'user_logs'
1323 __table_args__ = (
1323 __table_args__ = (
1324 base_table_args,
1324 base_table_args,
1325 )
1325 )
1326
1326
1327 VERSION_1 = 'v1'
1327 VERSION_1 = 'v1'
1328 VERSION_2 = 'v2'
1328 VERSION_2 = 'v2'
1329 VERSIONS = [VERSION_1, VERSION_2]
1329 VERSIONS = [VERSION_1, VERSION_2]
1330
1330
1331 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1331 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1332 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1332 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1333 username = Column("username", String(255), nullable=True, unique=None, default=None)
1333 username = Column("username", String(255), nullable=True, unique=None, default=None)
1334 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1334 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1335 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1335 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1336 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1336 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1337 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1337 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1338 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1338 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1339
1339
1340 version = Column("version", String(255), nullable=True, default=VERSION_1)
1340 version = Column("version", String(255), nullable=True, default=VERSION_1)
1341 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1341 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1342 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1342 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1343
1343
1344 def __unicode__(self):
1344 def __unicode__(self):
1345 return u"<%s('id:%s:%s')>" % (
1345 return u"<%s('id:%s:%s')>" % (
1346 self.__class__.__name__, self.repository_name, self.action)
1346 self.__class__.__name__, self.repository_name, self.action)
1347
1347
1348 def __json__(self):
1348 def __json__(self):
1349 return {
1349 return {
1350 'user_id': self.user_id,
1350 'user_id': self.user_id,
1351 'username': self.username,
1351 'username': self.username,
1352 'repository_id': self.repository_id,
1352 'repository_id': self.repository_id,
1353 'repository_name': self.repository_name,
1353 'repository_name': self.repository_name,
1354 'user_ip': self.user_ip,
1354 'user_ip': self.user_ip,
1355 'action_date': self.action_date,
1355 'action_date': self.action_date,
1356 'action': self.action,
1356 'action': self.action,
1357 }
1357 }
1358
1358
1359 @hybrid_property
1359 @hybrid_property
1360 def entry_id(self):
1360 def entry_id(self):
1361 return self.user_log_id
1361 return self.user_log_id
1362
1362
1363 @property
1363 @property
1364 def action_as_day(self):
1364 def action_as_day(self):
1365 return datetime.date(*self.action_date.timetuple()[:3])
1365 return datetime.date(*self.action_date.timetuple()[:3])
1366
1366
1367 user = relationship('User')
1367 user = relationship('User')
1368 repository = relationship('Repository', cascade='')
1368 repository = relationship('Repository', cascade='')
1369
1369
1370
1370
1371 class UserGroup(Base, BaseModel):
1371 class UserGroup(Base, BaseModel):
1372 __tablename__ = 'users_groups'
1372 __tablename__ = 'users_groups'
1373 __table_args__ = (
1373 __table_args__ = (
1374 base_table_args,
1374 base_table_args,
1375 )
1375 )
1376
1376
1377 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1377 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1378 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1378 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1379 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1379 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1380 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1380 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1381 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1381 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1382 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1382 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1383 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1383 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1384 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1384 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1385
1385
1386 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1386 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1387 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1387 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1388 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1388 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1389 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1389 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1390 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1390 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1391 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1391 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1392
1392
1393 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1393 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1394 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1394 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1395
1395
1396 @classmethod
1396 @classmethod
1397 def _load_group_data(cls, column):
1397 def _load_group_data(cls, column):
1398 if not column:
1398 if not column:
1399 return {}
1399 return {}
1400
1400
1401 try:
1401 try:
1402 return json.loads(column) or {}
1402 return json.loads(column) or {}
1403 except TypeError:
1403 except TypeError:
1404 return {}
1404 return {}
1405
1405
1406 @hybrid_property
1406 @hybrid_property
1407 def description_safe(self):
1407 def description_safe(self):
1408 from rhodecode.lib import helpers as h
1408 from rhodecode.lib import helpers as h
1409 return h.escape(self.user_group_description)
1409 return h.escape(self.user_group_description)
1410
1410
1411 @hybrid_property
1411 @hybrid_property
1412 def group_data(self):
1412 def group_data(self):
1413 return self._load_group_data(self._group_data)
1413 return self._load_group_data(self._group_data)
1414
1414
1415 @group_data.expression
1415 @group_data.expression
1416 def group_data(self, **kwargs):
1416 def group_data(self, **kwargs):
1417 return self._group_data
1417 return self._group_data
1418
1418
1419 @group_data.setter
1419 @group_data.setter
1420 def group_data(self, val):
1420 def group_data(self, val):
1421 try:
1421 try:
1422 self._group_data = json.dumps(val)
1422 self._group_data = json.dumps(val)
1423 except Exception:
1423 except Exception:
1424 log.error(traceback.format_exc())
1424 log.error(traceback.format_exc())
1425
1425
1426 @classmethod
1426 @classmethod
1427 def _load_sync(cls, group_data):
1427 def _load_sync(cls, group_data):
1428 if group_data:
1428 if group_data:
1429 return group_data.get('extern_type')
1429 return group_data.get('extern_type')
1430
1430
1431 @property
1431 @property
1432 def sync(self):
1432 def sync(self):
1433 return self._load_sync(self.group_data)
1433 return self._load_sync(self.group_data)
1434
1434
1435 def __unicode__(self):
1435 def __unicode__(self):
1436 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1436 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1437 self.users_group_id,
1437 self.users_group_id,
1438 self.users_group_name)
1438 self.users_group_name)
1439
1439
1440 @classmethod
1440 @classmethod
1441 def get_by_group_name(cls, group_name, cache=False,
1441 def get_by_group_name(cls, group_name, cache=False,
1442 case_insensitive=False):
1442 case_insensitive=False):
1443 if case_insensitive:
1443 if case_insensitive:
1444 q = cls.query().filter(func.lower(cls.users_group_name) ==
1444 q = cls.query().filter(func.lower(cls.users_group_name) ==
1445 func.lower(group_name))
1445 func.lower(group_name))
1446
1446
1447 else:
1447 else:
1448 q = cls.query().filter(cls.users_group_name == group_name)
1448 q = cls.query().filter(cls.users_group_name == group_name)
1449 if cache:
1449 if cache:
1450 q = q.options(
1450 q = q.options(
1451 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1451 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1452 return q.scalar()
1452 return q.scalar()
1453
1453
1454 @classmethod
1454 @classmethod
1455 def get(cls, user_group_id, cache=False):
1455 def get(cls, user_group_id, cache=False):
1456 if not user_group_id:
1456 if not user_group_id:
1457 return
1457 return
1458
1458
1459 user_group = cls.query()
1459 user_group = cls.query()
1460 if cache:
1460 if cache:
1461 user_group = user_group.options(
1461 user_group = user_group.options(
1462 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1462 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1463 return user_group.get(user_group_id)
1463 return user_group.get(user_group_id)
1464
1464
1465 def permissions(self, with_admins=True, with_owner=True,
1465 def permissions(self, with_admins=True, with_owner=True,
1466 expand_from_user_groups=False):
1466 expand_from_user_groups=False):
1467 """
1467 """
1468 Permissions for user groups
1468 Permissions for user groups
1469 """
1469 """
1470 _admin_perm = 'usergroup.admin'
1470 _admin_perm = 'usergroup.admin'
1471
1471
1472 owner_row = []
1472 owner_row = []
1473 if with_owner:
1473 if with_owner:
1474 usr = AttributeDict(self.user.get_dict())
1474 usr = AttributeDict(self.user.get_dict())
1475 usr.owner_row = True
1475 usr.owner_row = True
1476 usr.permission = _admin_perm
1476 usr.permission = _admin_perm
1477 owner_row.append(usr)
1477 owner_row.append(usr)
1478
1478
1479 super_admin_ids = []
1479 super_admin_ids = []
1480 super_admin_rows = []
1480 super_admin_rows = []
1481 if with_admins:
1481 if with_admins:
1482 for usr in User.get_all_super_admins():
1482 for usr in User.get_all_super_admins():
1483 super_admin_ids.append(usr.user_id)
1483 super_admin_ids.append(usr.user_id)
1484 # if this admin is also owner, don't double the record
1484 # if this admin is also owner, don't double the record
1485 if usr.user_id == owner_row[0].user_id:
1485 if usr.user_id == owner_row[0].user_id:
1486 owner_row[0].admin_row = True
1486 owner_row[0].admin_row = True
1487 else:
1487 else:
1488 usr = AttributeDict(usr.get_dict())
1488 usr = AttributeDict(usr.get_dict())
1489 usr.admin_row = True
1489 usr.admin_row = True
1490 usr.permission = _admin_perm
1490 usr.permission = _admin_perm
1491 super_admin_rows.append(usr)
1491 super_admin_rows.append(usr)
1492
1492
1493 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1493 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1494 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1494 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1495 joinedload(UserUserGroupToPerm.user),
1495 joinedload(UserUserGroupToPerm.user),
1496 joinedload(UserUserGroupToPerm.permission),)
1496 joinedload(UserUserGroupToPerm.permission),)
1497
1497
1498 # get owners and admins and permissions. We do a trick of re-writing
1498 # get owners and admins and permissions. We do a trick of re-writing
1499 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1499 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1500 # has a global reference and changing one object propagates to all
1500 # has a global reference and changing one object propagates to all
1501 # others. This means if admin is also an owner admin_row that change
1501 # others. This means if admin is also an owner admin_row that change
1502 # would propagate to both objects
1502 # would propagate to both objects
1503 perm_rows = []
1503 perm_rows = []
1504 for _usr in q.all():
1504 for _usr in q.all():
1505 usr = AttributeDict(_usr.user.get_dict())
1505 usr = AttributeDict(_usr.user.get_dict())
1506 # if this user is also owner/admin, mark as duplicate record
1506 # if this user is also owner/admin, mark as duplicate record
1507 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1507 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1508 usr.duplicate_perm = True
1508 usr.duplicate_perm = True
1509 usr.permission = _usr.permission.permission_name
1509 usr.permission = _usr.permission.permission_name
1510 perm_rows.append(usr)
1510 perm_rows.append(usr)
1511
1511
1512 # filter the perm rows by 'default' first and then sort them by
1512 # filter the perm rows by 'default' first and then sort them by
1513 # admin,write,read,none permissions sorted again alphabetically in
1513 # admin,write,read,none permissions sorted again alphabetically in
1514 # each group
1514 # each group
1515 perm_rows = sorted(perm_rows, key=display_user_sort)
1515 perm_rows = sorted(perm_rows, key=display_user_sort)
1516
1516
1517 user_groups_rows = []
1517 user_groups_rows = []
1518 if expand_from_user_groups:
1518 if expand_from_user_groups:
1519 for ug in self.permission_user_groups(with_members=True):
1519 for ug in self.permission_user_groups(with_members=True):
1520 for user_data in ug.members:
1520 for user_data in ug.members:
1521 user_groups_rows.append(user_data)
1521 user_groups_rows.append(user_data)
1522
1522
1523 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1523 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1524
1524
1525 def permission_user_groups(self, with_members=False):
1525 def permission_user_groups(self, with_members=False):
1526 q = UserGroupUserGroupToPerm.query()\
1526 q = UserGroupUserGroupToPerm.query()\
1527 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1527 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1528 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1528 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1529 joinedload(UserGroupUserGroupToPerm.target_user_group),
1529 joinedload(UserGroupUserGroupToPerm.target_user_group),
1530 joinedload(UserGroupUserGroupToPerm.permission),)
1530 joinedload(UserGroupUserGroupToPerm.permission),)
1531
1531
1532 perm_rows = []
1532 perm_rows = []
1533 for _user_group in q.all():
1533 for _user_group in q.all():
1534 entry = AttributeDict(_user_group.user_group.get_dict())
1534 entry = AttributeDict(_user_group.user_group.get_dict())
1535 entry.permission = _user_group.permission.permission_name
1535 entry.permission = _user_group.permission.permission_name
1536 if with_members:
1536 if with_members:
1537 entry.members = [x.user.get_dict()
1537 entry.members = [x.user.get_dict()
1538 for x in _user_group.user_group.members]
1538 for x in _user_group.user_group.members]
1539 perm_rows.append(entry)
1539 perm_rows.append(entry)
1540
1540
1541 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1541 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1542 return perm_rows
1542 return perm_rows
1543
1543
1544 def _get_default_perms(self, user_group, suffix=''):
1544 def _get_default_perms(self, user_group, suffix=''):
1545 from rhodecode.model.permission import PermissionModel
1545 from rhodecode.model.permission import PermissionModel
1546 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1546 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1547
1547
1548 def get_default_perms(self, suffix=''):
1548 def get_default_perms(self, suffix=''):
1549 return self._get_default_perms(self, suffix)
1549 return self._get_default_perms(self, suffix)
1550
1550
1551 def get_api_data(self, with_group_members=True, include_secrets=False):
1551 def get_api_data(self, with_group_members=True, include_secrets=False):
1552 """
1552 """
1553 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1553 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1554 basically forwarded.
1554 basically forwarded.
1555
1555
1556 """
1556 """
1557 user_group = self
1557 user_group = self
1558 data = {
1558 data = {
1559 'users_group_id': user_group.users_group_id,
1559 'users_group_id': user_group.users_group_id,
1560 'group_name': user_group.users_group_name,
1560 'group_name': user_group.users_group_name,
1561 'group_description': user_group.user_group_description,
1561 'group_description': user_group.user_group_description,
1562 'active': user_group.users_group_active,
1562 'active': user_group.users_group_active,
1563 'owner': user_group.user.username,
1563 'owner': user_group.user.username,
1564 'sync': user_group.sync,
1564 'sync': user_group.sync,
1565 'owner_email': user_group.user.email,
1565 'owner_email': user_group.user.email,
1566 }
1566 }
1567
1567
1568 if with_group_members:
1568 if with_group_members:
1569 users = []
1569 users = []
1570 for user in user_group.members:
1570 for user in user_group.members:
1571 user = user.user
1571 user = user.user
1572 users.append(user.get_api_data(include_secrets=include_secrets))
1572 users.append(user.get_api_data(include_secrets=include_secrets))
1573 data['users'] = users
1573 data['users'] = users
1574
1574
1575 return data
1575 return data
1576
1576
1577
1577
1578 class UserGroupMember(Base, BaseModel):
1578 class UserGroupMember(Base, BaseModel):
1579 __tablename__ = 'users_groups_members'
1579 __tablename__ = 'users_groups_members'
1580 __table_args__ = (
1580 __table_args__ = (
1581 base_table_args,
1581 base_table_args,
1582 )
1582 )
1583
1583
1584 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1584 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1585 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1586 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1586 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1587
1587
1588 user = relationship('User', lazy='joined')
1588 user = relationship('User', lazy='joined')
1589 users_group = relationship('UserGroup')
1589 users_group = relationship('UserGroup')
1590
1590
1591 def __init__(self, gr_id='', u_id=''):
1591 def __init__(self, gr_id='', u_id=''):
1592 self.users_group_id = gr_id
1592 self.users_group_id = gr_id
1593 self.user_id = u_id
1593 self.user_id = u_id
1594
1594
1595
1595
1596 class RepositoryField(Base, BaseModel):
1596 class RepositoryField(Base, BaseModel):
1597 __tablename__ = 'repositories_fields'
1597 __tablename__ = 'repositories_fields'
1598 __table_args__ = (
1598 __table_args__ = (
1599 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1599 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1600 base_table_args,
1600 base_table_args,
1601 )
1601 )
1602
1602
1603 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1603 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1604
1604
1605 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1605 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1606 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1606 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1607 field_key = Column("field_key", String(250))
1607 field_key = Column("field_key", String(250))
1608 field_label = Column("field_label", String(1024), nullable=False)
1608 field_label = Column("field_label", String(1024), nullable=False)
1609 field_value = Column("field_value", String(10000), nullable=False)
1609 field_value = Column("field_value", String(10000), nullable=False)
1610 field_desc = Column("field_desc", String(1024), nullable=False)
1610 field_desc = Column("field_desc", String(1024), nullable=False)
1611 field_type = Column("field_type", String(255), nullable=False, unique=None)
1611 field_type = Column("field_type", String(255), nullable=False, unique=None)
1612 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1612 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1613
1613
1614 repository = relationship('Repository')
1614 repository = relationship('Repository')
1615
1615
1616 @property
1616 @property
1617 def field_key_prefixed(self):
1617 def field_key_prefixed(self):
1618 return 'ex_%s' % self.field_key
1618 return 'ex_%s' % self.field_key
1619
1619
1620 @classmethod
1620 @classmethod
1621 def un_prefix_key(cls, key):
1621 def un_prefix_key(cls, key):
1622 if key.startswith(cls.PREFIX):
1622 if key.startswith(cls.PREFIX):
1623 return key[len(cls.PREFIX):]
1623 return key[len(cls.PREFIX):]
1624 return key
1624 return key
1625
1625
1626 @classmethod
1626 @classmethod
1627 def get_by_key_name(cls, key, repo):
1627 def get_by_key_name(cls, key, repo):
1628 row = cls.query()\
1628 row = cls.query()\
1629 .filter(cls.repository == repo)\
1629 .filter(cls.repository == repo)\
1630 .filter(cls.field_key == key).scalar()
1630 .filter(cls.field_key == key).scalar()
1631 return row
1631 return row
1632
1632
1633
1633
1634 class Repository(Base, BaseModel):
1634 class Repository(Base, BaseModel):
1635 __tablename__ = 'repositories'
1635 __tablename__ = 'repositories'
1636 __table_args__ = (
1636 __table_args__ = (
1637 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1637 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1638 base_table_args,
1638 base_table_args,
1639 )
1639 )
1640 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1640 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1641 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1641 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1642 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1642 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1643
1643
1644 STATE_CREATED = 'repo_state_created'
1644 STATE_CREATED = 'repo_state_created'
1645 STATE_PENDING = 'repo_state_pending'
1645 STATE_PENDING = 'repo_state_pending'
1646 STATE_ERROR = 'repo_state_error'
1646 STATE_ERROR = 'repo_state_error'
1647
1647
1648 LOCK_AUTOMATIC = 'lock_auto'
1648 LOCK_AUTOMATIC = 'lock_auto'
1649 LOCK_API = 'lock_api'
1649 LOCK_API = 'lock_api'
1650 LOCK_WEB = 'lock_web'
1650 LOCK_WEB = 'lock_web'
1651 LOCK_PULL = 'lock_pull'
1651 LOCK_PULL = 'lock_pull'
1652
1652
1653 NAME_SEP = URL_SEP
1653 NAME_SEP = URL_SEP
1654
1654
1655 repo_id = Column(
1655 repo_id = Column(
1656 "repo_id", Integer(), nullable=False, unique=True, default=None,
1656 "repo_id", Integer(), nullable=False, unique=True, default=None,
1657 primary_key=True)
1657 primary_key=True)
1658 _repo_name = Column(
1658 _repo_name = Column(
1659 "repo_name", Text(), nullable=False, default=None)
1659 "repo_name", Text(), nullable=False, default=None)
1660 repo_name_hash = Column(
1660 repo_name_hash = Column(
1661 "repo_name_hash", String(255), nullable=False, unique=True)
1661 "repo_name_hash", String(255), nullable=False, unique=True)
1662 repo_state = Column("repo_state", String(255), nullable=True)
1662 repo_state = Column("repo_state", String(255), nullable=True)
1663
1663
1664 clone_uri = Column(
1664 clone_uri = Column(
1665 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1665 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1666 default=None)
1666 default=None)
1667 push_uri = Column(
1667 push_uri = Column(
1668 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1668 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1669 default=None)
1669 default=None)
1670 repo_type = Column(
1670 repo_type = Column(
1671 "repo_type", String(255), nullable=False, unique=False, default=None)
1671 "repo_type", String(255), nullable=False, unique=False, default=None)
1672 user_id = Column(
1672 user_id = Column(
1673 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1673 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1674 unique=False, default=None)
1674 unique=False, default=None)
1675 private = Column(
1675 private = Column(
1676 "private", Boolean(), nullable=True, unique=None, default=None)
1676 "private", Boolean(), nullable=True, unique=None, default=None)
1677 archived = Column(
1677 archived = Column(
1678 "archived", Boolean(), nullable=True, unique=None, default=None)
1678 "archived", Boolean(), nullable=True, unique=None, default=None)
1679 enable_statistics = Column(
1679 enable_statistics = Column(
1680 "statistics", Boolean(), nullable=True, unique=None, default=True)
1680 "statistics", Boolean(), nullable=True, unique=None, default=True)
1681 enable_downloads = Column(
1681 enable_downloads = Column(
1682 "downloads", Boolean(), nullable=True, unique=None, default=True)
1682 "downloads", Boolean(), nullable=True, unique=None, default=True)
1683 description = Column(
1683 description = Column(
1684 "description", String(10000), nullable=True, unique=None, default=None)
1684 "description", String(10000), nullable=True, unique=None, default=None)
1685 created_on = Column(
1685 created_on = Column(
1686 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1686 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1687 default=datetime.datetime.now)
1687 default=datetime.datetime.now)
1688 updated_on = Column(
1688 updated_on = Column(
1689 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1689 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1690 default=datetime.datetime.now)
1690 default=datetime.datetime.now)
1691 _landing_revision = Column(
1691 _landing_revision = Column(
1692 "landing_revision", String(255), nullable=False, unique=False,
1692 "landing_revision", String(255), nullable=False, unique=False,
1693 default=None)
1693 default=None)
1694 enable_locking = Column(
1694 enable_locking = Column(
1695 "enable_locking", Boolean(), nullable=False, unique=None,
1695 "enable_locking", Boolean(), nullable=False, unique=None,
1696 default=False)
1696 default=False)
1697 _locked = Column(
1697 _locked = Column(
1698 "locked", String(255), nullable=True, unique=False, default=None)
1698 "locked", String(255), nullable=True, unique=False, default=None)
1699 _changeset_cache = Column(
1699 _changeset_cache = Column(
1700 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1700 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1701
1701
1702 fork_id = Column(
1702 fork_id = Column(
1703 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1703 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1704 nullable=True, unique=False, default=None)
1704 nullable=True, unique=False, default=None)
1705 group_id = Column(
1705 group_id = Column(
1706 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1706 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1707 unique=False, default=None)
1707 unique=False, default=None)
1708
1708
1709 user = relationship('User', lazy='joined')
1709 user = relationship('User', lazy='joined')
1710 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1710 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1711 group = relationship('RepoGroup', lazy='joined')
1711 group = relationship('RepoGroup', lazy='joined')
1712 repo_to_perm = relationship(
1712 repo_to_perm = relationship(
1713 'UserRepoToPerm', cascade='all',
1713 'UserRepoToPerm', cascade='all',
1714 order_by='UserRepoToPerm.repo_to_perm_id')
1714 order_by='UserRepoToPerm.repo_to_perm_id')
1715 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1715 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1716 stats = relationship('Statistics', cascade='all', uselist=False)
1716 stats = relationship('Statistics', cascade='all', uselist=False)
1717
1717
1718 followers = relationship(
1718 followers = relationship(
1719 'UserFollowing',
1719 'UserFollowing',
1720 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1720 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1721 cascade='all')
1721 cascade='all')
1722 extra_fields = relationship(
1722 extra_fields = relationship(
1723 'RepositoryField', cascade="all, delete-orphan")
1723 'RepositoryField', cascade="all, delete-orphan")
1724 logs = relationship('UserLog')
1724 logs = relationship('UserLog')
1725 comments = relationship(
1725 comments = relationship(
1726 'ChangesetComment', cascade="all, delete-orphan")
1726 'ChangesetComment', cascade="all, delete-orphan")
1727 pull_requests_source = relationship(
1727 pull_requests_source = relationship(
1728 'PullRequest',
1728 'PullRequest',
1729 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1729 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1730 cascade="all, delete-orphan")
1730 cascade="all, delete-orphan")
1731 pull_requests_target = relationship(
1731 pull_requests_target = relationship(
1732 'PullRequest',
1732 'PullRequest',
1733 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1733 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1734 cascade="all, delete-orphan")
1734 cascade="all, delete-orphan")
1735 ui = relationship('RepoRhodeCodeUi', cascade="all")
1735 ui = relationship('RepoRhodeCodeUi', cascade="all")
1736 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1736 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1737 integrations = relationship('Integration', cascade="all, delete-orphan")
1737 integrations = relationship('Integration', cascade="all, delete-orphan")
1738
1738
1739 scoped_tokens = relationship('UserApiKeys', cascade="all")
1739 scoped_tokens = relationship('UserApiKeys', cascade="all")
1740
1740
1741 # no cascade, set NULL
1741 # no cascade, set NULL
1742 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1742 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1743
1743
1744 def __unicode__(self):
1744 def __unicode__(self):
1745 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1745 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1746 safe_unicode(self.repo_name))
1746 safe_unicode(self.repo_name))
1747
1747
1748 @hybrid_property
1748 @hybrid_property
1749 def description_safe(self):
1749 def description_safe(self):
1750 from rhodecode.lib import helpers as h
1750 from rhodecode.lib import helpers as h
1751 return h.escape(self.description)
1751 return h.escape(self.description)
1752
1752
1753 @hybrid_property
1753 @hybrid_property
1754 def landing_rev(self):
1754 def landing_rev(self):
1755 # always should return [rev_type, rev]
1755 # always should return [rev_type, rev]
1756 if self._landing_revision:
1756 if self._landing_revision:
1757 _rev_info = self._landing_revision.split(':')
1757 _rev_info = self._landing_revision.split(':')
1758 if len(_rev_info) < 2:
1758 if len(_rev_info) < 2:
1759 _rev_info.insert(0, 'rev')
1759 _rev_info.insert(0, 'rev')
1760 return [_rev_info[0], _rev_info[1]]
1760 return [_rev_info[0], _rev_info[1]]
1761 return [None, None]
1761 return [None, None]
1762
1762
1763 @landing_rev.setter
1763 @landing_rev.setter
1764 def landing_rev(self, val):
1764 def landing_rev(self, val):
1765 if ':' not in val:
1765 if ':' not in val:
1766 raise ValueError('value must be delimited with `:` and consist '
1766 raise ValueError('value must be delimited with `:` and consist '
1767 'of <rev_type>:<rev>, got %s instead' % val)
1767 'of <rev_type>:<rev>, got %s instead' % val)
1768 self._landing_revision = val
1768 self._landing_revision = val
1769
1769
1770 @hybrid_property
1770 @hybrid_property
1771 def locked(self):
1771 def locked(self):
1772 if self._locked:
1772 if self._locked:
1773 user_id, timelocked, reason = self._locked.split(':')
1773 user_id, timelocked, reason = self._locked.split(':')
1774 lock_values = int(user_id), timelocked, reason
1774 lock_values = int(user_id), timelocked, reason
1775 else:
1775 else:
1776 lock_values = [None, None, None]
1776 lock_values = [None, None, None]
1777 return lock_values
1777 return lock_values
1778
1778
1779 @locked.setter
1779 @locked.setter
1780 def locked(self, val):
1780 def locked(self, val):
1781 if val and isinstance(val, (list, tuple)):
1781 if val and isinstance(val, (list, tuple)):
1782 self._locked = ':'.join(map(str, val))
1782 self._locked = ':'.join(map(str, val))
1783 else:
1783 else:
1784 self._locked = None
1784 self._locked = None
1785
1785
1786 @classmethod
1786 @classmethod
1787 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1787 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1788 from rhodecode.lib.vcs.backends.base import EmptyCommit
1788 from rhodecode.lib.vcs.backends.base import EmptyCommit
1789 dummy = EmptyCommit().__json__()
1789 dummy = EmptyCommit().__json__()
1790 if not changeset_cache_raw:
1790 if not changeset_cache_raw:
1791 dummy['source_repo_id'] = repo_id
1791 dummy['source_repo_id'] = repo_id
1792 return json.loads(json.dumps(dummy))
1792 return json.loads(json.dumps(dummy))
1793
1793
1794 try:
1794 try:
1795 return json.loads(changeset_cache_raw)
1795 return json.loads(changeset_cache_raw)
1796 except TypeError:
1796 except TypeError:
1797 return dummy
1797 return dummy
1798 except Exception:
1798 except Exception:
1799 log.error(traceback.format_exc())
1799 log.error(traceback.format_exc())
1800 return dummy
1800 return dummy
1801
1801
1802 @hybrid_property
1802 @hybrid_property
1803 def changeset_cache(self):
1803 def changeset_cache(self):
1804 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1804 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1805
1805
1806 @changeset_cache.setter
1806 @changeset_cache.setter
1807 def changeset_cache(self, val):
1807 def changeset_cache(self, val):
1808 try:
1808 try:
1809 self._changeset_cache = json.dumps(val)
1809 self._changeset_cache = json.dumps(val)
1810 except Exception:
1810 except Exception:
1811 log.error(traceback.format_exc())
1811 log.error(traceback.format_exc())
1812
1812
1813 @hybrid_property
1813 @hybrid_property
1814 def repo_name(self):
1814 def repo_name(self):
1815 return self._repo_name
1815 return self._repo_name
1816
1816
1817 @repo_name.setter
1817 @repo_name.setter
1818 def repo_name(self, value):
1818 def repo_name(self, value):
1819 self._repo_name = value
1819 self._repo_name = value
1820 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1820 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1821
1821
1822 @classmethod
1822 @classmethod
1823 def normalize_repo_name(cls, repo_name):
1823 def normalize_repo_name(cls, repo_name):
1824 """
1824 """
1825 Normalizes os specific repo_name to the format internally stored inside
1825 Normalizes os specific repo_name to the format internally stored inside
1826 database using URL_SEP
1826 database using URL_SEP
1827
1827
1828 :param cls:
1828 :param cls:
1829 :param repo_name:
1829 :param repo_name:
1830 """
1830 """
1831 return cls.NAME_SEP.join(repo_name.split(os.sep))
1831 return cls.NAME_SEP.join(repo_name.split(os.sep))
1832
1832
1833 @classmethod
1833 @classmethod
1834 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1834 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1835 session = Session()
1835 session = Session()
1836 q = session.query(cls).filter(cls.repo_name == repo_name)
1836 q = session.query(cls).filter(cls.repo_name == repo_name)
1837
1837
1838 if cache:
1838 if cache:
1839 if identity_cache:
1839 if identity_cache:
1840 val = cls.identity_cache(session, 'repo_name', repo_name)
1840 val = cls.identity_cache(session, 'repo_name', repo_name)
1841 if val:
1841 if val:
1842 return val
1842 return val
1843 else:
1843 else:
1844 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1844 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1845 q = q.options(
1845 q = q.options(
1846 FromCache("sql_cache_short", cache_key))
1846 FromCache("sql_cache_short", cache_key))
1847
1847
1848 return q.scalar()
1848 return q.scalar()
1849
1849
1850 @classmethod
1850 @classmethod
1851 def get_by_id_or_repo_name(cls, repoid):
1851 def get_by_id_or_repo_name(cls, repoid):
1852 if isinstance(repoid, (int, long)):
1852 if isinstance(repoid, (int, long)):
1853 try:
1853 try:
1854 repo = cls.get(repoid)
1854 repo = cls.get(repoid)
1855 except ValueError:
1855 except ValueError:
1856 repo = None
1856 repo = None
1857 else:
1857 else:
1858 repo = cls.get_by_repo_name(repoid)
1858 repo = cls.get_by_repo_name(repoid)
1859 return repo
1859 return repo
1860
1860
1861 @classmethod
1861 @classmethod
1862 def get_by_full_path(cls, repo_full_path):
1862 def get_by_full_path(cls, repo_full_path):
1863 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1863 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1864 repo_name = cls.normalize_repo_name(repo_name)
1864 repo_name = cls.normalize_repo_name(repo_name)
1865 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1865 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1866
1866
1867 @classmethod
1867 @classmethod
1868 def get_repo_forks(cls, repo_id):
1868 def get_repo_forks(cls, repo_id):
1869 return cls.query().filter(Repository.fork_id == repo_id)
1869 return cls.query().filter(Repository.fork_id == repo_id)
1870
1870
1871 @classmethod
1871 @classmethod
1872 def base_path(cls):
1872 def base_path(cls):
1873 """
1873 """
1874 Returns base path when all repos are stored
1874 Returns base path when all repos are stored
1875
1875
1876 :param cls:
1876 :param cls:
1877 """
1877 """
1878 q = Session().query(RhodeCodeUi)\
1878 q = Session().query(RhodeCodeUi)\
1879 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1879 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1880 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1880 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1881 return q.one().ui_value
1881 return q.one().ui_value
1882
1882
1883 @classmethod
1883 @classmethod
1884 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1884 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1885 case_insensitive=True, archived=False):
1885 case_insensitive=True, archived=False):
1886 q = Repository.query()
1886 q = Repository.query()
1887
1887
1888 if not archived:
1888 if not archived:
1889 q = q.filter(Repository.archived.isnot(true()))
1889 q = q.filter(Repository.archived.isnot(true()))
1890
1890
1891 if not isinstance(user_id, Optional):
1891 if not isinstance(user_id, Optional):
1892 q = q.filter(Repository.user_id == user_id)
1892 q = q.filter(Repository.user_id == user_id)
1893
1893
1894 if not isinstance(group_id, Optional):
1894 if not isinstance(group_id, Optional):
1895 q = q.filter(Repository.group_id == group_id)
1895 q = q.filter(Repository.group_id == group_id)
1896
1896
1897 if case_insensitive:
1897 if case_insensitive:
1898 q = q.order_by(func.lower(Repository.repo_name))
1898 q = q.order_by(func.lower(Repository.repo_name))
1899 else:
1899 else:
1900 q = q.order_by(Repository.repo_name)
1900 q = q.order_by(Repository.repo_name)
1901
1901
1902 return q.all()
1902 return q.all()
1903
1903
1904 @property
1904 @property
1905 def repo_uid(self):
1905 def repo_uid(self):
1906 return '_{}'.format(self.repo_id)
1906 return '_{}'.format(self.repo_id)
1907
1907
1908 @property
1908 @property
1909 def forks(self):
1909 def forks(self):
1910 """
1910 """
1911 Return forks of this repo
1911 Return forks of this repo
1912 """
1912 """
1913 return Repository.get_repo_forks(self.repo_id)
1913 return Repository.get_repo_forks(self.repo_id)
1914
1914
1915 @property
1915 @property
1916 def parent(self):
1916 def parent(self):
1917 """
1917 """
1918 Returns fork parent
1918 Returns fork parent
1919 """
1919 """
1920 return self.fork
1920 return self.fork
1921
1921
1922 @property
1922 @property
1923 def just_name(self):
1923 def just_name(self):
1924 return self.repo_name.split(self.NAME_SEP)[-1]
1924 return self.repo_name.split(self.NAME_SEP)[-1]
1925
1925
1926 @property
1926 @property
1927 def groups_with_parents(self):
1927 def groups_with_parents(self):
1928 groups = []
1928 groups = []
1929 if self.group is None:
1929 if self.group is None:
1930 return groups
1930 return groups
1931
1931
1932 cur_gr = self.group
1932 cur_gr = self.group
1933 groups.insert(0, cur_gr)
1933 groups.insert(0, cur_gr)
1934 while 1:
1934 while 1:
1935 gr = getattr(cur_gr, 'parent_group', None)
1935 gr = getattr(cur_gr, 'parent_group', None)
1936 cur_gr = cur_gr.parent_group
1936 cur_gr = cur_gr.parent_group
1937 if gr is None:
1937 if gr is None:
1938 break
1938 break
1939 groups.insert(0, gr)
1939 groups.insert(0, gr)
1940
1940
1941 return groups
1941 return groups
1942
1942
1943 @property
1943 @property
1944 def groups_and_repo(self):
1944 def groups_and_repo(self):
1945 return self.groups_with_parents, self
1945 return self.groups_with_parents, self
1946
1946
1947 @LazyProperty
1947 @LazyProperty
1948 def repo_path(self):
1948 def repo_path(self):
1949 """
1949 """
1950 Returns base full path for that repository means where it actually
1950 Returns base full path for that repository means where it actually
1951 exists on a filesystem
1951 exists on a filesystem
1952 """
1952 """
1953 q = Session().query(RhodeCodeUi).filter(
1953 q = Session().query(RhodeCodeUi).filter(
1954 RhodeCodeUi.ui_key == self.NAME_SEP)
1954 RhodeCodeUi.ui_key == self.NAME_SEP)
1955 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1955 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1956 return q.one().ui_value
1956 return q.one().ui_value
1957
1957
1958 @property
1958 @property
1959 def repo_full_path(self):
1959 def repo_full_path(self):
1960 p = [self.repo_path]
1960 p = [self.repo_path]
1961 # we need to split the name by / since this is how we store the
1961 # we need to split the name by / since this is how we store the
1962 # names in the database, but that eventually needs to be converted
1962 # names in the database, but that eventually needs to be converted
1963 # into a valid system path
1963 # into a valid system path
1964 p += self.repo_name.split(self.NAME_SEP)
1964 p += self.repo_name.split(self.NAME_SEP)
1965 return os.path.join(*map(safe_unicode, p))
1965 return os.path.join(*map(safe_unicode, p))
1966
1966
1967 @property
1967 @property
1968 def cache_keys(self):
1968 def cache_keys(self):
1969 """
1969 """
1970 Returns associated cache keys for that repo
1970 Returns associated cache keys for that repo
1971 """
1971 """
1972 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1972 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1973 repo_id=self.repo_id)
1973 repo_id=self.repo_id)
1974 return CacheKey.query()\
1974 return CacheKey.query()\
1975 .filter(CacheKey.cache_args == invalidation_namespace)\
1975 .filter(CacheKey.cache_args == invalidation_namespace)\
1976 .order_by(CacheKey.cache_key)\
1976 .order_by(CacheKey.cache_key)\
1977 .all()
1977 .all()
1978
1978
1979 @property
1979 @property
1980 def cached_diffs_relative_dir(self):
1980 def cached_diffs_relative_dir(self):
1981 """
1981 """
1982 Return a relative to the repository store path of cached diffs
1982 Return a relative to the repository store path of cached diffs
1983 used for safe display for users, who shouldn't know the absolute store
1983 used for safe display for users, who shouldn't know the absolute store
1984 path
1984 path
1985 """
1985 """
1986 return os.path.join(
1986 return os.path.join(
1987 os.path.dirname(self.repo_name),
1987 os.path.dirname(self.repo_name),
1988 self.cached_diffs_dir.split(os.path.sep)[-1])
1988 self.cached_diffs_dir.split(os.path.sep)[-1])
1989
1989
1990 @property
1990 @property
1991 def cached_diffs_dir(self):
1991 def cached_diffs_dir(self):
1992 path = self.repo_full_path
1992 path = self.repo_full_path
1993 return os.path.join(
1993 return os.path.join(
1994 os.path.dirname(path),
1994 os.path.dirname(path),
1995 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1995 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1996
1996
1997 def cached_diffs(self):
1997 def cached_diffs(self):
1998 diff_cache_dir = self.cached_diffs_dir
1998 diff_cache_dir = self.cached_diffs_dir
1999 if os.path.isdir(diff_cache_dir):
1999 if os.path.isdir(diff_cache_dir):
2000 return os.listdir(diff_cache_dir)
2000 return os.listdir(diff_cache_dir)
2001 return []
2001 return []
2002
2002
2003 def shadow_repos(self):
2003 def shadow_repos(self):
2004 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2004 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2005 return [
2005 return [
2006 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2006 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2007 if x.startswith(shadow_repos_pattern)]
2007 if x.startswith(shadow_repos_pattern)]
2008
2008
2009 def get_new_name(self, repo_name):
2009 def get_new_name(self, repo_name):
2010 """
2010 """
2011 returns new full repository name based on assigned group and new new
2011 returns new full repository name based on assigned group and new new
2012
2012
2013 :param group_name:
2013 :param group_name:
2014 """
2014 """
2015 path_prefix = self.group.full_path_splitted if self.group else []
2015 path_prefix = self.group.full_path_splitted if self.group else []
2016 return self.NAME_SEP.join(path_prefix + [repo_name])
2016 return self.NAME_SEP.join(path_prefix + [repo_name])
2017
2017
2018 @property
2018 @property
2019 def _config(self):
2019 def _config(self):
2020 """
2020 """
2021 Returns db based config object.
2021 Returns db based config object.
2022 """
2022 """
2023 from rhodecode.lib.utils import make_db_config
2023 from rhodecode.lib.utils import make_db_config
2024 return make_db_config(clear_session=False, repo=self)
2024 return make_db_config(clear_session=False, repo=self)
2025
2025
2026 def permissions(self, with_admins=True, with_owner=True,
2026 def permissions(self, with_admins=True, with_owner=True,
2027 expand_from_user_groups=False):
2027 expand_from_user_groups=False):
2028 """
2028 """
2029 Permissions for repositories
2029 Permissions for repositories
2030 """
2030 """
2031 _admin_perm = 'repository.admin'
2031 _admin_perm = 'repository.admin'
2032
2032
2033 owner_row = []
2033 owner_row = []
2034 if with_owner:
2034 if with_owner:
2035 usr = AttributeDict(self.user.get_dict())
2035 usr = AttributeDict(self.user.get_dict())
2036 usr.owner_row = True
2036 usr.owner_row = True
2037 usr.permission = _admin_perm
2037 usr.permission = _admin_perm
2038 usr.permission_id = None
2038 usr.permission_id = None
2039 owner_row.append(usr)
2039 owner_row.append(usr)
2040
2040
2041 super_admin_ids = []
2041 super_admin_ids = []
2042 super_admin_rows = []
2042 super_admin_rows = []
2043 if with_admins:
2043 if with_admins:
2044 for usr in User.get_all_super_admins():
2044 for usr in User.get_all_super_admins():
2045 super_admin_ids.append(usr.user_id)
2045 super_admin_ids.append(usr.user_id)
2046 # if this admin is also owner, don't double the record
2046 # if this admin is also owner, don't double the record
2047 if usr.user_id == owner_row[0].user_id:
2047 if usr.user_id == owner_row[0].user_id:
2048 owner_row[0].admin_row = True
2048 owner_row[0].admin_row = True
2049 else:
2049 else:
2050 usr = AttributeDict(usr.get_dict())
2050 usr = AttributeDict(usr.get_dict())
2051 usr.admin_row = True
2051 usr.admin_row = True
2052 usr.permission = _admin_perm
2052 usr.permission = _admin_perm
2053 usr.permission_id = None
2053 usr.permission_id = None
2054 super_admin_rows.append(usr)
2054 super_admin_rows.append(usr)
2055
2055
2056 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2056 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2057 q = q.options(joinedload(UserRepoToPerm.repository),
2057 q = q.options(joinedload(UserRepoToPerm.repository),
2058 joinedload(UserRepoToPerm.user),
2058 joinedload(UserRepoToPerm.user),
2059 joinedload(UserRepoToPerm.permission),)
2059 joinedload(UserRepoToPerm.permission),)
2060
2060
2061 # get owners and admins and permissions. We do a trick of re-writing
2061 # get owners and admins and permissions. We do a trick of re-writing
2062 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2062 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2063 # has a global reference and changing one object propagates to all
2063 # has a global reference and changing one object propagates to all
2064 # others. This means if admin is also an owner admin_row that change
2064 # others. This means if admin is also an owner admin_row that change
2065 # would propagate to both objects
2065 # would propagate to both objects
2066 perm_rows = []
2066 perm_rows = []
2067 for _usr in q.all():
2067 for _usr in q.all():
2068 usr = AttributeDict(_usr.user.get_dict())
2068 usr = AttributeDict(_usr.user.get_dict())
2069 # if this user is also owner/admin, mark as duplicate record
2069 # if this user is also owner/admin, mark as duplicate record
2070 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2070 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2071 usr.duplicate_perm = True
2071 usr.duplicate_perm = True
2072 # also check if this permission is maybe used by branch_permissions
2072 # also check if this permission is maybe used by branch_permissions
2073 if _usr.branch_perm_entry:
2073 if _usr.branch_perm_entry:
2074 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2074 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2075
2075
2076 usr.permission = _usr.permission.permission_name
2076 usr.permission = _usr.permission.permission_name
2077 usr.permission_id = _usr.repo_to_perm_id
2077 usr.permission_id = _usr.repo_to_perm_id
2078 perm_rows.append(usr)
2078 perm_rows.append(usr)
2079
2079
2080 # filter the perm rows by 'default' first and then sort them by
2080 # filter the perm rows by 'default' first and then sort them by
2081 # admin,write,read,none permissions sorted again alphabetically in
2081 # admin,write,read,none permissions sorted again alphabetically in
2082 # each group
2082 # each group
2083 perm_rows = sorted(perm_rows, key=display_user_sort)
2083 perm_rows = sorted(perm_rows, key=display_user_sort)
2084
2084
2085 user_groups_rows = []
2085 user_groups_rows = []
2086 if expand_from_user_groups:
2086 if expand_from_user_groups:
2087 for ug in self.permission_user_groups(with_members=True):
2087 for ug in self.permission_user_groups(with_members=True):
2088 for user_data in ug.members:
2088 for user_data in ug.members:
2089 user_groups_rows.append(user_data)
2089 user_groups_rows.append(user_data)
2090
2090
2091 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2091 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2092
2092
2093 def permission_user_groups(self, with_members=True):
2093 def permission_user_groups(self, with_members=True):
2094 q = UserGroupRepoToPerm.query()\
2094 q = UserGroupRepoToPerm.query()\
2095 .filter(UserGroupRepoToPerm.repository == self)
2095 .filter(UserGroupRepoToPerm.repository == self)
2096 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2096 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2097 joinedload(UserGroupRepoToPerm.users_group),
2097 joinedload(UserGroupRepoToPerm.users_group),
2098 joinedload(UserGroupRepoToPerm.permission),)
2098 joinedload(UserGroupRepoToPerm.permission),)
2099
2099
2100 perm_rows = []
2100 perm_rows = []
2101 for _user_group in q.all():
2101 for _user_group in q.all():
2102 entry = AttributeDict(_user_group.users_group.get_dict())
2102 entry = AttributeDict(_user_group.users_group.get_dict())
2103 entry.permission = _user_group.permission.permission_name
2103 entry.permission = _user_group.permission.permission_name
2104 if with_members:
2104 if with_members:
2105 entry.members = [x.user.get_dict()
2105 entry.members = [x.user.get_dict()
2106 for x in _user_group.users_group.members]
2106 for x in _user_group.users_group.members]
2107 perm_rows.append(entry)
2107 perm_rows.append(entry)
2108
2108
2109 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2109 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2110 return perm_rows
2110 return perm_rows
2111
2111
2112 def get_api_data(self, include_secrets=False):
2112 def get_api_data(self, include_secrets=False):
2113 """
2113 """
2114 Common function for generating repo api data
2114 Common function for generating repo api data
2115
2115
2116 :param include_secrets: See :meth:`User.get_api_data`.
2116 :param include_secrets: See :meth:`User.get_api_data`.
2117
2117
2118 """
2118 """
2119 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2119 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2120 # move this methods on models level.
2120 # move this methods on models level.
2121 from rhodecode.model.settings import SettingsModel
2121 from rhodecode.model.settings import SettingsModel
2122 from rhodecode.model.repo import RepoModel
2122 from rhodecode.model.repo import RepoModel
2123
2123
2124 repo = self
2124 repo = self
2125 _user_id, _time, _reason = self.locked
2125 _user_id, _time, _reason = self.locked
2126
2126
2127 data = {
2127 data = {
2128 'repo_id': repo.repo_id,
2128 'repo_id': repo.repo_id,
2129 'repo_name': repo.repo_name,
2129 'repo_name': repo.repo_name,
2130 'repo_type': repo.repo_type,
2130 'repo_type': repo.repo_type,
2131 'clone_uri': repo.clone_uri or '',
2131 'clone_uri': repo.clone_uri or '',
2132 'push_uri': repo.push_uri or '',
2132 'push_uri': repo.push_uri or '',
2133 'url': RepoModel().get_url(self),
2133 'url': RepoModel().get_url(self),
2134 'private': repo.private,
2134 'private': repo.private,
2135 'created_on': repo.created_on,
2135 'created_on': repo.created_on,
2136 'description': repo.description_safe,
2136 'description': repo.description_safe,
2137 'landing_rev': repo.landing_rev,
2137 'landing_rev': repo.landing_rev,
2138 'owner': repo.user.username,
2138 'owner': repo.user.username,
2139 'fork_of': repo.fork.repo_name if repo.fork else None,
2139 'fork_of': repo.fork.repo_name if repo.fork else None,
2140 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2140 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2141 'enable_statistics': repo.enable_statistics,
2141 'enable_statistics': repo.enable_statistics,
2142 'enable_locking': repo.enable_locking,
2142 'enable_locking': repo.enable_locking,
2143 'enable_downloads': repo.enable_downloads,
2143 'enable_downloads': repo.enable_downloads,
2144 'last_changeset': repo.changeset_cache,
2144 'last_changeset': repo.changeset_cache,
2145 'locked_by': User.get(_user_id).get_api_data(
2145 'locked_by': User.get(_user_id).get_api_data(
2146 include_secrets=include_secrets) if _user_id else None,
2146 include_secrets=include_secrets) if _user_id else None,
2147 'locked_date': time_to_datetime(_time) if _time else None,
2147 'locked_date': time_to_datetime(_time) if _time else None,
2148 'lock_reason': _reason if _reason else None,
2148 'lock_reason': _reason if _reason else None,
2149 }
2149 }
2150
2150
2151 # TODO: mikhail: should be per-repo settings here
2151 # TODO: mikhail: should be per-repo settings here
2152 rc_config = SettingsModel().get_all_settings()
2152 rc_config = SettingsModel().get_all_settings()
2153 repository_fields = str2bool(
2153 repository_fields = str2bool(
2154 rc_config.get('rhodecode_repository_fields'))
2154 rc_config.get('rhodecode_repository_fields'))
2155 if repository_fields:
2155 if repository_fields:
2156 for f in self.extra_fields:
2156 for f in self.extra_fields:
2157 data[f.field_key_prefixed] = f.field_value
2157 data[f.field_key_prefixed] = f.field_value
2158
2158
2159 return data
2159 return data
2160
2160
2161 @classmethod
2161 @classmethod
2162 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2162 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2163 if not lock_time:
2163 if not lock_time:
2164 lock_time = time.time()
2164 lock_time = time.time()
2165 if not lock_reason:
2165 if not lock_reason:
2166 lock_reason = cls.LOCK_AUTOMATIC
2166 lock_reason = cls.LOCK_AUTOMATIC
2167 repo.locked = [user_id, lock_time, lock_reason]
2167 repo.locked = [user_id, lock_time, lock_reason]
2168 Session().add(repo)
2168 Session().add(repo)
2169 Session().commit()
2169 Session().commit()
2170
2170
2171 @classmethod
2171 @classmethod
2172 def unlock(cls, repo):
2172 def unlock(cls, repo):
2173 repo.locked = None
2173 repo.locked = None
2174 Session().add(repo)
2174 Session().add(repo)
2175 Session().commit()
2175 Session().commit()
2176
2176
2177 @classmethod
2177 @classmethod
2178 def getlock(cls, repo):
2178 def getlock(cls, repo):
2179 return repo.locked
2179 return repo.locked
2180
2180
2181 def is_user_lock(self, user_id):
2181 def is_user_lock(self, user_id):
2182 if self.lock[0]:
2182 if self.lock[0]:
2183 lock_user_id = safe_int(self.lock[0])
2183 lock_user_id = safe_int(self.lock[0])
2184 user_id = safe_int(user_id)
2184 user_id = safe_int(user_id)
2185 # both are ints, and they are equal
2185 # both are ints, and they are equal
2186 return all([lock_user_id, user_id]) and lock_user_id == user_id
2186 return all([lock_user_id, user_id]) and lock_user_id == user_id
2187
2187
2188 return False
2188 return False
2189
2189
2190 def get_locking_state(self, action, user_id, only_when_enabled=True):
2190 def get_locking_state(self, action, user_id, only_when_enabled=True):
2191 """
2191 """
2192 Checks locking on this repository, if locking is enabled and lock is
2192 Checks locking on this repository, if locking is enabled and lock is
2193 present returns a tuple of make_lock, locked, locked_by.
2193 present returns a tuple of make_lock, locked, locked_by.
2194 make_lock can have 3 states None (do nothing) True, make lock
2194 make_lock can have 3 states None (do nothing) True, make lock
2195 False release lock, This value is later propagated to hooks, which
2195 False release lock, This value is later propagated to hooks, which
2196 do the locking. Think about this as signals passed to hooks what to do.
2196 do the locking. Think about this as signals passed to hooks what to do.
2197
2197
2198 """
2198 """
2199 # TODO: johbo: This is part of the business logic and should be moved
2199 # TODO: johbo: This is part of the business logic and should be moved
2200 # into the RepositoryModel.
2200 # into the RepositoryModel.
2201
2201
2202 if action not in ('push', 'pull'):
2202 if action not in ('push', 'pull'):
2203 raise ValueError("Invalid action value: %s" % repr(action))
2203 raise ValueError("Invalid action value: %s" % repr(action))
2204
2204
2205 # defines if locked error should be thrown to user
2205 # defines if locked error should be thrown to user
2206 currently_locked = False
2206 currently_locked = False
2207 # defines if new lock should be made, tri-state
2207 # defines if new lock should be made, tri-state
2208 make_lock = None
2208 make_lock = None
2209 repo = self
2209 repo = self
2210 user = User.get(user_id)
2210 user = User.get(user_id)
2211
2211
2212 lock_info = repo.locked
2212 lock_info = repo.locked
2213
2213
2214 if repo and (repo.enable_locking or not only_when_enabled):
2214 if repo and (repo.enable_locking or not only_when_enabled):
2215 if action == 'push':
2215 if action == 'push':
2216 # check if it's already locked !, if it is compare users
2216 # check if it's already locked !, if it is compare users
2217 locked_by_user_id = lock_info[0]
2217 locked_by_user_id = lock_info[0]
2218 if user.user_id == locked_by_user_id:
2218 if user.user_id == locked_by_user_id:
2219 log.debug(
2219 log.debug(
2220 'Got `push` action from user %s, now unlocking', user)
2220 'Got `push` action from user %s, now unlocking', user)
2221 # unlock if we have push from user who locked
2221 # unlock if we have push from user who locked
2222 make_lock = False
2222 make_lock = False
2223 else:
2223 else:
2224 # we're not the same user who locked, ban with
2224 # we're not the same user who locked, ban with
2225 # code defined in settings (default is 423 HTTP Locked) !
2225 # code defined in settings (default is 423 HTTP Locked) !
2226 log.debug('Repo %s is currently locked by %s', repo, user)
2226 log.debug('Repo %s is currently locked by %s', repo, user)
2227 currently_locked = True
2227 currently_locked = True
2228 elif action == 'pull':
2228 elif action == 'pull':
2229 # [0] user [1] date
2229 # [0] user [1] date
2230 if lock_info[0] and lock_info[1]:
2230 if lock_info[0] and lock_info[1]:
2231 log.debug('Repo %s is currently locked by %s', repo, user)
2231 log.debug('Repo %s is currently locked by %s', repo, user)
2232 currently_locked = True
2232 currently_locked = True
2233 else:
2233 else:
2234 log.debug('Setting lock on repo %s by %s', repo, user)
2234 log.debug('Setting lock on repo %s by %s', repo, user)
2235 make_lock = True
2235 make_lock = True
2236
2236
2237 else:
2237 else:
2238 log.debug('Repository %s do not have locking enabled', repo)
2238 log.debug('Repository %s do not have locking enabled', repo)
2239
2239
2240 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2240 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2241 make_lock, currently_locked, lock_info)
2241 make_lock, currently_locked, lock_info)
2242
2242
2243 from rhodecode.lib.auth import HasRepoPermissionAny
2243 from rhodecode.lib.auth import HasRepoPermissionAny
2244 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2244 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2245 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2245 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2246 # if we don't have at least write permission we cannot make a lock
2246 # if we don't have at least write permission we cannot make a lock
2247 log.debug('lock state reset back to FALSE due to lack '
2247 log.debug('lock state reset back to FALSE due to lack '
2248 'of at least read permission')
2248 'of at least read permission')
2249 make_lock = False
2249 make_lock = False
2250
2250
2251 return make_lock, currently_locked, lock_info
2251 return make_lock, currently_locked, lock_info
2252
2252
2253 @property
2253 @property
2254 def last_commit_cache_update_diff(self):
2254 def last_commit_cache_update_diff(self):
2255 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2255 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2256
2256
2257 @classmethod
2257 @classmethod
2258 def _load_commit_change(cls, last_commit_cache):
2258 def _load_commit_change(cls, last_commit_cache):
2259 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2259 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2260 empty_date = datetime.datetime.fromtimestamp(0)
2260 empty_date = datetime.datetime.fromtimestamp(0)
2261 date_latest = last_commit_cache.get('date', empty_date)
2261 date_latest = last_commit_cache.get('date', empty_date)
2262 try:
2262 try:
2263 return parse_datetime(date_latest)
2263 return parse_datetime(date_latest)
2264 except Exception:
2264 except Exception:
2265 return empty_date
2265 return empty_date
2266
2266
2267 @property
2267 @property
2268 def last_commit_change(self):
2268 def last_commit_change(self):
2269 return self._load_commit_change(self.changeset_cache)
2269 return self._load_commit_change(self.changeset_cache)
2270
2270
2271 @property
2271 @property
2272 def last_db_change(self):
2272 def last_db_change(self):
2273 return self.updated_on
2273 return self.updated_on
2274
2274
2275 @property
2275 @property
2276 def clone_uri_hidden(self):
2276 def clone_uri_hidden(self):
2277 clone_uri = self.clone_uri
2277 clone_uri = self.clone_uri
2278 if clone_uri:
2278 if clone_uri:
2279 import urlobject
2279 import urlobject
2280 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2280 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2281 if url_obj.password:
2281 if url_obj.password:
2282 clone_uri = url_obj.with_password('*****')
2282 clone_uri = url_obj.with_password('*****')
2283 return clone_uri
2283 return clone_uri
2284
2284
2285 @property
2285 @property
2286 def push_uri_hidden(self):
2286 def push_uri_hidden(self):
2287 push_uri = self.push_uri
2287 push_uri = self.push_uri
2288 if push_uri:
2288 if push_uri:
2289 import urlobject
2289 import urlobject
2290 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2290 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2291 if url_obj.password:
2291 if url_obj.password:
2292 push_uri = url_obj.with_password('*****')
2292 push_uri = url_obj.with_password('*****')
2293 return push_uri
2293 return push_uri
2294
2294
2295 def clone_url(self, **override):
2295 def clone_url(self, **override):
2296 from rhodecode.model.settings import SettingsModel
2296 from rhodecode.model.settings import SettingsModel
2297
2297
2298 uri_tmpl = None
2298 uri_tmpl = None
2299 if 'with_id' in override:
2299 if 'with_id' in override:
2300 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2300 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2301 del override['with_id']
2301 del override['with_id']
2302
2302
2303 if 'uri_tmpl' in override:
2303 if 'uri_tmpl' in override:
2304 uri_tmpl = override['uri_tmpl']
2304 uri_tmpl = override['uri_tmpl']
2305 del override['uri_tmpl']
2305 del override['uri_tmpl']
2306
2306
2307 ssh = False
2307 ssh = False
2308 if 'ssh' in override:
2308 if 'ssh' in override:
2309 ssh = True
2309 ssh = True
2310 del override['ssh']
2310 del override['ssh']
2311
2311
2312 # we didn't override our tmpl from **overrides
2312 # we didn't override our tmpl from **overrides
2313 request = get_current_request()
2313 request = get_current_request()
2314 if not uri_tmpl:
2314 if not uri_tmpl:
2315 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2315 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2316 rc_config = request.call_context.rc_config
2316 rc_config = request.call_context.rc_config
2317 else:
2317 else:
2318 rc_config = SettingsModel().get_all_settings(cache=True)
2318 rc_config = SettingsModel().get_all_settings(cache=True)
2319
2319
2320 if ssh:
2320 if ssh:
2321 uri_tmpl = rc_config.get(
2321 uri_tmpl = rc_config.get(
2322 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2322 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2323
2323
2324 else:
2324 else:
2325 uri_tmpl = rc_config.get(
2325 uri_tmpl = rc_config.get(
2326 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2326 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2327
2327
2328 return get_clone_url(request=request,
2328 return get_clone_url(request=request,
2329 uri_tmpl=uri_tmpl,
2329 uri_tmpl=uri_tmpl,
2330 repo_name=self.repo_name,
2330 repo_name=self.repo_name,
2331 repo_id=self.repo_id,
2331 repo_id=self.repo_id,
2332 repo_type=self.repo_type,
2332 repo_type=self.repo_type,
2333 **override)
2333 **override)
2334
2334
2335 def set_state(self, state):
2335 def set_state(self, state):
2336 self.repo_state = state
2336 self.repo_state = state
2337 Session().add(self)
2337 Session().add(self)
2338 #==========================================================================
2338 #==========================================================================
2339 # SCM PROPERTIES
2339 # SCM PROPERTIES
2340 #==========================================================================
2340 #==========================================================================
2341
2341
2342 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2342 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2343 return get_commit_safe(
2343 return get_commit_safe(
2344 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2344 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2345 maybe_unreachable=maybe_unreachable)
2345 maybe_unreachable=maybe_unreachable)
2346
2346
2347 def get_changeset(self, rev=None, pre_load=None):
2347 def get_changeset(self, rev=None, pre_load=None):
2348 warnings.warn("Use get_commit", DeprecationWarning)
2348 warnings.warn("Use get_commit", DeprecationWarning)
2349 commit_id = None
2349 commit_id = None
2350 commit_idx = None
2350 commit_idx = None
2351 if isinstance(rev, compat.string_types):
2351 if isinstance(rev, compat.string_types):
2352 commit_id = rev
2352 commit_id = rev
2353 else:
2353 else:
2354 commit_idx = rev
2354 commit_idx = rev
2355 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2355 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2356 pre_load=pre_load)
2356 pre_load=pre_load)
2357
2357
2358 def get_landing_commit(self):
2358 def get_landing_commit(self):
2359 """
2359 """
2360 Returns landing commit, or if that doesn't exist returns the tip
2360 Returns landing commit, or if that doesn't exist returns the tip
2361 """
2361 """
2362 _rev_type, _rev = self.landing_rev
2362 _rev_type, _rev = self.landing_rev
2363 commit = self.get_commit(_rev)
2363 commit = self.get_commit(_rev)
2364 if isinstance(commit, EmptyCommit):
2364 if isinstance(commit, EmptyCommit):
2365 return self.get_commit()
2365 return self.get_commit()
2366 return commit
2366 return commit
2367
2367
2368 def flush_commit_cache(self):
2368 def flush_commit_cache(self):
2369 self.update_commit_cache(cs_cache={'raw_id':'0'})
2369 self.update_commit_cache(cs_cache={'raw_id':'0'})
2370 self.update_commit_cache()
2370 self.update_commit_cache()
2371
2371
2372 def update_commit_cache(self, cs_cache=None, config=None):
2372 def update_commit_cache(self, cs_cache=None, config=None):
2373 """
2373 """
2374 Update cache of last commit for repository
2374 Update cache of last commit for repository
2375 cache_keys should be::
2375 cache_keys should be::
2376
2376
2377 source_repo_id
2377 source_repo_id
2378 short_id
2378 short_id
2379 raw_id
2379 raw_id
2380 revision
2380 revision
2381 parents
2381 parents
2382 message
2382 message
2383 date
2383 date
2384 author
2384 author
2385 updated_on
2385 updated_on
2386
2386
2387 """
2387 """
2388 from rhodecode.lib.vcs.backends.base import BaseChangeset
2388 from rhodecode.lib.vcs.backends.base import BaseChangeset
2389 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2389 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2390 empty_date = datetime.datetime.fromtimestamp(0)
2390 empty_date = datetime.datetime.fromtimestamp(0)
2391
2391
2392 if cs_cache is None:
2392 if cs_cache is None:
2393 # use no-cache version here
2393 # use no-cache version here
2394 try:
2394 try:
2395 scm_repo = self.scm_instance(cache=False, config=config)
2395 scm_repo = self.scm_instance(cache=False, config=config)
2396 except VCSError:
2396 except VCSError:
2397 scm_repo = None
2397 scm_repo = None
2398 empty = scm_repo is None or scm_repo.is_empty()
2398 empty = scm_repo is None or scm_repo.is_empty()
2399
2399
2400 if not empty:
2400 if not empty:
2401 cs_cache = scm_repo.get_commit(
2401 cs_cache = scm_repo.get_commit(
2402 pre_load=["author", "date", "message", "parents", "branch"])
2402 pre_load=["author", "date", "message", "parents", "branch"])
2403 else:
2403 else:
2404 cs_cache = EmptyCommit()
2404 cs_cache = EmptyCommit()
2405
2405
2406 if isinstance(cs_cache, BaseChangeset):
2406 if isinstance(cs_cache, BaseChangeset):
2407 cs_cache = cs_cache.__json__()
2407 cs_cache = cs_cache.__json__()
2408
2408
2409 def is_outdated(new_cs_cache):
2409 def is_outdated(new_cs_cache):
2410 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2410 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2411 new_cs_cache['revision'] != self.changeset_cache['revision']):
2411 new_cs_cache['revision'] != self.changeset_cache['revision']):
2412 return True
2412 return True
2413 return False
2413 return False
2414
2414
2415 # check if we have maybe already latest cached revision
2415 # check if we have maybe already latest cached revision
2416 if is_outdated(cs_cache) or not self.changeset_cache:
2416 if is_outdated(cs_cache) or not self.changeset_cache:
2417 _current_datetime = datetime.datetime.utcnow()
2417 _current_datetime = datetime.datetime.utcnow()
2418 last_change = cs_cache.get('date') or _current_datetime
2418 last_change = cs_cache.get('date') or _current_datetime
2419 # we check if last update is newer than the new value
2419 # we check if last update is newer than the new value
2420 # if yes, we use the current timestamp instead. Imagine you get
2420 # if yes, we use the current timestamp instead. Imagine you get
2421 # old commit pushed 1y ago, we'd set last update 1y to ago.
2421 # old commit pushed 1y ago, we'd set last update 1y to ago.
2422 last_change_timestamp = datetime_to_time(last_change)
2422 last_change_timestamp = datetime_to_time(last_change)
2423 current_timestamp = datetime_to_time(last_change)
2423 current_timestamp = datetime_to_time(last_change)
2424 if last_change_timestamp > current_timestamp and not empty:
2424 if last_change_timestamp > current_timestamp and not empty:
2425 cs_cache['date'] = _current_datetime
2425 cs_cache['date'] = _current_datetime
2426
2426
2427 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2427 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2428 cs_cache['updated_on'] = time.time()
2428 cs_cache['updated_on'] = time.time()
2429 self.changeset_cache = cs_cache
2429 self.changeset_cache = cs_cache
2430 self.updated_on = last_change
2430 self.updated_on = last_change
2431 Session().add(self)
2431 Session().add(self)
2432 Session().commit()
2432 Session().commit()
2433
2433
2434 else:
2434 else:
2435 if empty:
2435 if empty:
2436 cs_cache = EmptyCommit().__json__()
2436 cs_cache = EmptyCommit().__json__()
2437 else:
2437 else:
2438 cs_cache = self.changeset_cache
2438 cs_cache = self.changeset_cache
2439
2439
2440 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2440 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2441
2441
2442 cs_cache['updated_on'] = time.time()
2442 cs_cache['updated_on'] = time.time()
2443 self.changeset_cache = cs_cache
2443 self.changeset_cache = cs_cache
2444 self.updated_on = _date_latest
2444 self.updated_on = _date_latest
2445 Session().add(self)
2445 Session().add(self)
2446 Session().commit()
2446 Session().commit()
2447
2447
2448 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2448 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2449 self.repo_name, cs_cache, _date_latest)
2449 self.repo_name, cs_cache, _date_latest)
2450
2450
2451 @property
2451 @property
2452 def tip(self):
2452 def tip(self):
2453 return self.get_commit('tip')
2453 return self.get_commit('tip')
2454
2454
2455 @property
2455 @property
2456 def author(self):
2456 def author(self):
2457 return self.tip.author
2457 return self.tip.author
2458
2458
2459 @property
2459 @property
2460 def last_change(self):
2460 def last_change(self):
2461 return self.scm_instance().last_change
2461 return self.scm_instance().last_change
2462
2462
2463 def get_comments(self, revisions=None):
2463 def get_comments(self, revisions=None):
2464 """
2464 """
2465 Returns comments for this repository grouped by revisions
2465 Returns comments for this repository grouped by revisions
2466
2466
2467 :param revisions: filter query by revisions only
2467 :param revisions: filter query by revisions only
2468 """
2468 """
2469 cmts = ChangesetComment.query()\
2469 cmts = ChangesetComment.query()\
2470 .filter(ChangesetComment.repo == self)
2470 .filter(ChangesetComment.repo == self)
2471 if revisions:
2471 if revisions:
2472 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2472 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2473 grouped = collections.defaultdict(list)
2473 grouped = collections.defaultdict(list)
2474 for cmt in cmts.all():
2474 for cmt in cmts.all():
2475 grouped[cmt.revision].append(cmt)
2475 grouped[cmt.revision].append(cmt)
2476 return grouped
2476 return grouped
2477
2477
2478 def statuses(self, revisions=None):
2478 def statuses(self, revisions=None):
2479 """
2479 """
2480 Returns statuses for this repository
2480 Returns statuses for this repository
2481
2481
2482 :param revisions: list of revisions to get statuses for
2482 :param revisions: list of revisions to get statuses for
2483 """
2483 """
2484 statuses = ChangesetStatus.query()\
2484 statuses = ChangesetStatus.query()\
2485 .filter(ChangesetStatus.repo == self)\
2485 .filter(ChangesetStatus.repo == self)\
2486 .filter(ChangesetStatus.version == 0)
2486 .filter(ChangesetStatus.version == 0)
2487
2487
2488 if revisions:
2488 if revisions:
2489 # Try doing the filtering in chunks to avoid hitting limits
2489 # Try doing the filtering in chunks to avoid hitting limits
2490 size = 500
2490 size = 500
2491 status_results = []
2491 status_results = []
2492 for chunk in xrange(0, len(revisions), size):
2492 for chunk in xrange(0, len(revisions), size):
2493 status_results += statuses.filter(
2493 status_results += statuses.filter(
2494 ChangesetStatus.revision.in_(
2494 ChangesetStatus.revision.in_(
2495 revisions[chunk: chunk+size])
2495 revisions[chunk: chunk+size])
2496 ).all()
2496 ).all()
2497 else:
2497 else:
2498 status_results = statuses.all()
2498 status_results = statuses.all()
2499
2499
2500 grouped = {}
2500 grouped = {}
2501
2501
2502 # maybe we have open new pullrequest without a status?
2502 # maybe we have open new pullrequest without a status?
2503 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2503 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2504 status_lbl = ChangesetStatus.get_status_lbl(stat)
2504 status_lbl = ChangesetStatus.get_status_lbl(stat)
2505 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2505 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2506 for rev in pr.revisions:
2506 for rev in pr.revisions:
2507 pr_id = pr.pull_request_id
2507 pr_id = pr.pull_request_id
2508 pr_repo = pr.target_repo.repo_name
2508 pr_repo = pr.target_repo.repo_name
2509 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2509 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2510
2510
2511 for stat in status_results:
2511 for stat in status_results:
2512 pr_id = pr_repo = None
2512 pr_id = pr_repo = None
2513 if stat.pull_request:
2513 if stat.pull_request:
2514 pr_id = stat.pull_request.pull_request_id
2514 pr_id = stat.pull_request.pull_request_id
2515 pr_repo = stat.pull_request.target_repo.repo_name
2515 pr_repo = stat.pull_request.target_repo.repo_name
2516 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2516 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2517 pr_id, pr_repo]
2517 pr_id, pr_repo]
2518 return grouped
2518 return grouped
2519
2519
2520 # ==========================================================================
2520 # ==========================================================================
2521 # SCM CACHE INSTANCE
2521 # SCM CACHE INSTANCE
2522 # ==========================================================================
2522 # ==========================================================================
2523
2523
2524 def scm_instance(self, **kwargs):
2524 def scm_instance(self, **kwargs):
2525 import rhodecode
2525 import rhodecode
2526
2526
2527 # Passing a config will not hit the cache currently only used
2527 # Passing a config will not hit the cache currently only used
2528 # for repo2dbmapper
2528 # for repo2dbmapper
2529 config = kwargs.pop('config', None)
2529 config = kwargs.pop('config', None)
2530 cache = kwargs.pop('cache', None)
2530 cache = kwargs.pop('cache', None)
2531 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2531 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2532 if vcs_full_cache is not None:
2532 if vcs_full_cache is not None:
2533 # allows override global config
2533 # allows override global config
2534 full_cache = vcs_full_cache
2534 full_cache = vcs_full_cache
2535 else:
2535 else:
2536 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2536 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2537 # if cache is NOT defined use default global, else we have a full
2537 # if cache is NOT defined use default global, else we have a full
2538 # control over cache behaviour
2538 # control over cache behaviour
2539 if cache is None and full_cache and not config:
2539 if cache is None and full_cache and not config:
2540 log.debug('Initializing pure cached instance for %s', self.repo_path)
2540 log.debug('Initializing pure cached instance for %s', self.repo_path)
2541 return self._get_instance_cached()
2541 return self._get_instance_cached()
2542
2542
2543 # cache here is sent to the "vcs server"
2543 # cache here is sent to the "vcs server"
2544 return self._get_instance(cache=bool(cache), config=config)
2544 return self._get_instance(cache=bool(cache), config=config)
2545
2545
2546 def _get_instance_cached(self):
2546 def _get_instance_cached(self):
2547 from rhodecode.lib import rc_cache
2547 from rhodecode.lib import rc_cache
2548
2548
2549 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2549 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2550 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2550 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2551 repo_id=self.repo_id)
2551 repo_id=self.repo_id)
2552 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2552 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2553
2553
2554 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2554 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2555 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2555 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2556 return self._get_instance(repo_state_uid=_cache_state_uid)
2556 return self._get_instance(repo_state_uid=_cache_state_uid)
2557
2557
2558 # we must use thread scoped cache here,
2558 # we must use thread scoped cache here,
2559 # because each thread of gevent needs it's own not shared connection and cache
2559 # because each thread of gevent needs it's own not shared connection and cache
2560 # we also alter `args` so the cache key is individual for every green thread.
2560 # we also alter `args` so the cache key is individual for every green thread.
2561 inv_context_manager = rc_cache.InvalidationContext(
2561 inv_context_manager = rc_cache.InvalidationContext(
2562 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2562 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2563 thread_scoped=True)
2563 thread_scoped=True)
2564 with inv_context_manager as invalidation_context:
2564 with inv_context_manager as invalidation_context:
2565 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2565 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2566 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2566 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2567
2567
2568 # re-compute and store cache if we get invalidate signal
2568 # re-compute and store cache if we get invalidate signal
2569 if invalidation_context.should_invalidate():
2569 if invalidation_context.should_invalidate():
2570 instance = get_instance_cached.refresh(*args)
2570 instance = get_instance_cached.refresh(*args)
2571 else:
2571 else:
2572 instance = get_instance_cached(*args)
2572 instance = get_instance_cached(*args)
2573
2573
2574 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2574 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2575 return instance
2575 return instance
2576
2576
2577 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2577 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2578 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2578 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2579 self.repo_type, self.repo_path, cache)
2579 self.repo_type, self.repo_path, cache)
2580 config = config or self._config
2580 config = config or self._config
2581 custom_wire = {
2581 custom_wire = {
2582 'cache': cache, # controls the vcs.remote cache
2582 'cache': cache, # controls the vcs.remote cache
2583 'repo_state_uid': repo_state_uid
2583 'repo_state_uid': repo_state_uid
2584 }
2584 }
2585 repo = get_vcs_instance(
2585 repo = get_vcs_instance(
2586 repo_path=safe_str(self.repo_full_path),
2586 repo_path=safe_str(self.repo_full_path),
2587 config=config,
2587 config=config,
2588 with_wire=custom_wire,
2588 with_wire=custom_wire,
2589 create=False,
2589 create=False,
2590 _vcs_alias=self.repo_type)
2590 _vcs_alias=self.repo_type)
2591 if repo is not None:
2591 if repo is not None:
2592 repo.count() # cache rebuild
2592 repo.count() # cache rebuild
2593 return repo
2593 return repo
2594
2594
2595 def get_shadow_repository_path(self, workspace_id):
2595 def get_shadow_repository_path(self, workspace_id):
2596 from rhodecode.lib.vcs.backends.base import BaseRepository
2596 from rhodecode.lib.vcs.backends.base import BaseRepository
2597 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2597 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2598 self.repo_full_path, self.repo_id, workspace_id)
2598 self.repo_full_path, self.repo_id, workspace_id)
2599 return shadow_repo_path
2599 return shadow_repo_path
2600
2600
2601 def __json__(self):
2601 def __json__(self):
2602 return {'landing_rev': self.landing_rev}
2602 return {'landing_rev': self.landing_rev}
2603
2603
2604 def get_dict(self):
2604 def get_dict(self):
2605
2605
2606 # Since we transformed `repo_name` to a hybrid property, we need to
2606 # Since we transformed `repo_name` to a hybrid property, we need to
2607 # keep compatibility with the code which uses `repo_name` field.
2607 # keep compatibility with the code which uses `repo_name` field.
2608
2608
2609 result = super(Repository, self).get_dict()
2609 result = super(Repository, self).get_dict()
2610 result['repo_name'] = result.pop('_repo_name', None)
2610 result['repo_name'] = result.pop('_repo_name', None)
2611 return result
2611 return result
2612
2612
2613
2613
2614 class RepoGroup(Base, BaseModel):
2614 class RepoGroup(Base, BaseModel):
2615 __tablename__ = 'groups'
2615 __tablename__ = 'groups'
2616 __table_args__ = (
2616 __table_args__ = (
2617 UniqueConstraint('group_name', 'group_parent_id'),
2617 UniqueConstraint('group_name', 'group_parent_id'),
2618 base_table_args,
2618 base_table_args,
2619 )
2619 )
2620 __mapper_args__ = {'order_by': 'group_name'}
2620 __mapper_args__ = {'order_by': 'group_name'}
2621
2621
2622 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2622 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2623
2623
2624 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2624 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2625 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2625 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2626 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2626 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2627 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2627 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2628 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2628 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2629 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2629 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2630 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2630 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2631 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2631 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2632 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2632 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2633 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2633 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2634 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2634 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2635
2635
2636 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2636 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2637 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2637 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2638 parent_group = relationship('RepoGroup', remote_side=group_id)
2638 parent_group = relationship('RepoGroup', remote_side=group_id)
2639 user = relationship('User')
2639 user = relationship('User')
2640 integrations = relationship('Integration', cascade="all, delete-orphan")
2640 integrations = relationship('Integration', cascade="all, delete-orphan")
2641
2641
2642 # no cascade, set NULL
2642 # no cascade, set NULL
2643 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2643 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2644
2644
2645 def __init__(self, group_name='', parent_group=None):
2645 def __init__(self, group_name='', parent_group=None):
2646 self.group_name = group_name
2646 self.group_name = group_name
2647 self.parent_group = parent_group
2647 self.parent_group = parent_group
2648
2648
2649 def __unicode__(self):
2649 def __unicode__(self):
2650 return u"<%s('id:%s:%s')>" % (
2650 return u"<%s('id:%s:%s')>" % (
2651 self.__class__.__name__, self.group_id, self.group_name)
2651 self.__class__.__name__, self.group_id, self.group_name)
2652
2652
2653 @hybrid_property
2653 @hybrid_property
2654 def group_name(self):
2654 def group_name(self):
2655 return self._group_name
2655 return self._group_name
2656
2656
2657 @group_name.setter
2657 @group_name.setter
2658 def group_name(self, value):
2658 def group_name(self, value):
2659 self._group_name = value
2659 self._group_name = value
2660 self.group_name_hash = self.hash_repo_group_name(value)
2660 self.group_name_hash = self.hash_repo_group_name(value)
2661
2661
2662 @classmethod
2662 @classmethod
2663 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2663 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2664 from rhodecode.lib.vcs.backends.base import EmptyCommit
2664 from rhodecode.lib.vcs.backends.base import EmptyCommit
2665 dummy = EmptyCommit().__json__()
2665 dummy = EmptyCommit().__json__()
2666 if not changeset_cache_raw:
2666 if not changeset_cache_raw:
2667 dummy['source_repo_id'] = repo_id
2667 dummy['source_repo_id'] = repo_id
2668 return json.loads(json.dumps(dummy))
2668 return json.loads(json.dumps(dummy))
2669
2669
2670 try:
2670 try:
2671 return json.loads(changeset_cache_raw)
2671 return json.loads(changeset_cache_raw)
2672 except TypeError:
2672 except TypeError:
2673 return dummy
2673 return dummy
2674 except Exception:
2674 except Exception:
2675 log.error(traceback.format_exc())
2675 log.error(traceback.format_exc())
2676 return dummy
2676 return dummy
2677
2677
2678 @hybrid_property
2678 @hybrid_property
2679 def changeset_cache(self):
2679 def changeset_cache(self):
2680 return self._load_changeset_cache('', self._changeset_cache)
2680 return self._load_changeset_cache('', self._changeset_cache)
2681
2681
2682 @changeset_cache.setter
2682 @changeset_cache.setter
2683 def changeset_cache(self, val):
2683 def changeset_cache(self, val):
2684 try:
2684 try:
2685 self._changeset_cache = json.dumps(val)
2685 self._changeset_cache = json.dumps(val)
2686 except Exception:
2686 except Exception:
2687 log.error(traceback.format_exc())
2687 log.error(traceback.format_exc())
2688
2688
2689 @validates('group_parent_id')
2689 @validates('group_parent_id')
2690 def validate_group_parent_id(self, key, val):
2690 def validate_group_parent_id(self, key, val):
2691 """
2691 """
2692 Check cycle references for a parent group to self
2692 Check cycle references for a parent group to self
2693 """
2693 """
2694 if self.group_id and val:
2694 if self.group_id and val:
2695 assert val != self.group_id
2695 assert val != self.group_id
2696
2696
2697 return val
2697 return val
2698
2698
2699 @hybrid_property
2699 @hybrid_property
2700 def description_safe(self):
2700 def description_safe(self):
2701 from rhodecode.lib import helpers as h
2701 from rhodecode.lib import helpers as h
2702 return h.escape(self.group_description)
2702 return h.escape(self.group_description)
2703
2703
2704 @classmethod
2704 @classmethod
2705 def hash_repo_group_name(cls, repo_group_name):
2705 def hash_repo_group_name(cls, repo_group_name):
2706 val = remove_formatting(repo_group_name)
2706 val = remove_formatting(repo_group_name)
2707 val = safe_str(val).lower()
2707 val = safe_str(val).lower()
2708 chars = []
2708 chars = []
2709 for c in val:
2709 for c in val:
2710 if c not in string.ascii_letters:
2710 if c not in string.ascii_letters:
2711 c = str(ord(c))
2711 c = str(ord(c))
2712 chars.append(c)
2712 chars.append(c)
2713
2713
2714 return ''.join(chars)
2714 return ''.join(chars)
2715
2715
2716 @classmethod
2716 @classmethod
2717 def _generate_choice(cls, repo_group):
2717 def _generate_choice(cls, repo_group):
2718 from webhelpers2.html import literal as _literal
2718 from webhelpers2.html import literal as _literal
2719 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2719 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2720 return repo_group.group_id, _name(repo_group.full_path_splitted)
2720 return repo_group.group_id, _name(repo_group.full_path_splitted)
2721
2721
2722 @classmethod
2722 @classmethod
2723 def groups_choices(cls, groups=None, show_empty_group=True):
2723 def groups_choices(cls, groups=None, show_empty_group=True):
2724 if not groups:
2724 if not groups:
2725 groups = cls.query().all()
2725 groups = cls.query().all()
2726
2726
2727 repo_groups = []
2727 repo_groups = []
2728 if show_empty_group:
2728 if show_empty_group:
2729 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2729 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2730
2730
2731 repo_groups.extend([cls._generate_choice(x) for x in groups])
2731 repo_groups.extend([cls._generate_choice(x) for x in groups])
2732
2732
2733 repo_groups = sorted(
2733 repo_groups = sorted(
2734 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2734 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2735 return repo_groups
2735 return repo_groups
2736
2736
2737 @classmethod
2737 @classmethod
2738 def url_sep(cls):
2738 def url_sep(cls):
2739 return URL_SEP
2739 return URL_SEP
2740
2740
2741 @classmethod
2741 @classmethod
2742 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2742 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2743 if case_insensitive:
2743 if case_insensitive:
2744 gr = cls.query().filter(func.lower(cls.group_name)
2744 gr = cls.query().filter(func.lower(cls.group_name)
2745 == func.lower(group_name))
2745 == func.lower(group_name))
2746 else:
2746 else:
2747 gr = cls.query().filter(cls.group_name == group_name)
2747 gr = cls.query().filter(cls.group_name == group_name)
2748 if cache:
2748 if cache:
2749 name_key = _hash_key(group_name)
2749 name_key = _hash_key(group_name)
2750 gr = gr.options(
2750 gr = gr.options(
2751 FromCache("sql_cache_short", "get_group_%s" % name_key))
2751 FromCache("sql_cache_short", "get_group_%s" % name_key))
2752 return gr.scalar()
2752 return gr.scalar()
2753
2753
2754 @classmethod
2754 @classmethod
2755 def get_user_personal_repo_group(cls, user_id):
2755 def get_user_personal_repo_group(cls, user_id):
2756 user = User.get(user_id)
2756 user = User.get(user_id)
2757 if user.username == User.DEFAULT_USER:
2757 if user.username == User.DEFAULT_USER:
2758 return None
2758 return None
2759
2759
2760 return cls.query()\
2760 return cls.query()\
2761 .filter(cls.personal == true()) \
2761 .filter(cls.personal == true()) \
2762 .filter(cls.user == user) \
2762 .filter(cls.user == user) \
2763 .order_by(cls.group_id.asc()) \
2763 .order_by(cls.group_id.asc()) \
2764 .first()
2764 .first()
2765
2765
2766 @classmethod
2766 @classmethod
2767 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2767 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2768 case_insensitive=True):
2768 case_insensitive=True):
2769 q = RepoGroup.query()
2769 q = RepoGroup.query()
2770
2770
2771 if not isinstance(user_id, Optional):
2771 if not isinstance(user_id, Optional):
2772 q = q.filter(RepoGroup.user_id == user_id)
2772 q = q.filter(RepoGroup.user_id == user_id)
2773
2773
2774 if not isinstance(group_id, Optional):
2774 if not isinstance(group_id, Optional):
2775 q = q.filter(RepoGroup.group_parent_id == group_id)
2775 q = q.filter(RepoGroup.group_parent_id == group_id)
2776
2776
2777 if case_insensitive:
2777 if case_insensitive:
2778 q = q.order_by(func.lower(RepoGroup.group_name))
2778 q = q.order_by(func.lower(RepoGroup.group_name))
2779 else:
2779 else:
2780 q = q.order_by(RepoGroup.group_name)
2780 q = q.order_by(RepoGroup.group_name)
2781 return q.all()
2781 return q.all()
2782
2782
2783 @property
2783 @property
2784 def parents(self, parents_recursion_limit=10):
2784 def parents(self, parents_recursion_limit=10):
2785 groups = []
2785 groups = []
2786 if self.parent_group is None:
2786 if self.parent_group is None:
2787 return groups
2787 return groups
2788 cur_gr = self.parent_group
2788 cur_gr = self.parent_group
2789 groups.insert(0, cur_gr)
2789 groups.insert(0, cur_gr)
2790 cnt = 0
2790 cnt = 0
2791 while 1:
2791 while 1:
2792 cnt += 1
2792 cnt += 1
2793 gr = getattr(cur_gr, 'parent_group', None)
2793 gr = getattr(cur_gr, 'parent_group', None)
2794 cur_gr = cur_gr.parent_group
2794 cur_gr = cur_gr.parent_group
2795 if gr is None:
2795 if gr is None:
2796 break
2796 break
2797 if cnt == parents_recursion_limit:
2797 if cnt == parents_recursion_limit:
2798 # this will prevent accidental infinit loops
2798 # this will prevent accidental infinit loops
2799 log.error('more than %s parents found for group %s, stopping '
2799 log.error('more than %s parents found for group %s, stopping '
2800 'recursive parent fetching', parents_recursion_limit, self)
2800 'recursive parent fetching', parents_recursion_limit, self)
2801 break
2801 break
2802
2802
2803 groups.insert(0, gr)
2803 groups.insert(0, gr)
2804 return groups
2804 return groups
2805
2805
2806 @property
2806 @property
2807 def last_commit_cache_update_diff(self):
2807 def last_commit_cache_update_diff(self):
2808 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2808 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2809
2809
2810 @classmethod
2810 @classmethod
2811 def _load_commit_change(cls, last_commit_cache):
2811 def _load_commit_change(cls, last_commit_cache):
2812 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2812 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2813 empty_date = datetime.datetime.fromtimestamp(0)
2813 empty_date = datetime.datetime.fromtimestamp(0)
2814 date_latest = last_commit_cache.get('date', empty_date)
2814 date_latest = last_commit_cache.get('date', empty_date)
2815 try:
2815 try:
2816 return parse_datetime(date_latest)
2816 return parse_datetime(date_latest)
2817 except Exception:
2817 except Exception:
2818 return empty_date
2818 return empty_date
2819
2819
2820 @property
2820 @property
2821 def last_commit_change(self):
2821 def last_commit_change(self):
2822 return self._load_commit_change(self.changeset_cache)
2822 return self._load_commit_change(self.changeset_cache)
2823
2823
2824 @property
2824 @property
2825 def last_db_change(self):
2825 def last_db_change(self):
2826 return self.updated_on
2826 return self.updated_on
2827
2827
2828 @property
2828 @property
2829 def children(self):
2829 def children(self):
2830 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2830 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2831
2831
2832 @property
2832 @property
2833 def name(self):
2833 def name(self):
2834 return self.group_name.split(RepoGroup.url_sep())[-1]
2834 return self.group_name.split(RepoGroup.url_sep())[-1]
2835
2835
2836 @property
2836 @property
2837 def full_path(self):
2837 def full_path(self):
2838 return self.group_name
2838 return self.group_name
2839
2839
2840 @property
2840 @property
2841 def full_path_splitted(self):
2841 def full_path_splitted(self):
2842 return self.group_name.split(RepoGroup.url_sep())
2842 return self.group_name.split(RepoGroup.url_sep())
2843
2843
2844 @property
2844 @property
2845 def repositories(self):
2845 def repositories(self):
2846 return Repository.query()\
2846 return Repository.query()\
2847 .filter(Repository.group == self)\
2847 .filter(Repository.group == self)\
2848 .order_by(Repository.repo_name)
2848 .order_by(Repository.repo_name)
2849
2849
2850 @property
2850 @property
2851 def repositories_recursive_count(self):
2851 def repositories_recursive_count(self):
2852 cnt = self.repositories.count()
2852 cnt = self.repositories.count()
2853
2853
2854 def children_count(group):
2854 def children_count(group):
2855 cnt = 0
2855 cnt = 0
2856 for child in group.children:
2856 for child in group.children:
2857 cnt += child.repositories.count()
2857 cnt += child.repositories.count()
2858 cnt += children_count(child)
2858 cnt += children_count(child)
2859 return cnt
2859 return cnt
2860
2860
2861 return cnt + children_count(self)
2861 return cnt + children_count(self)
2862
2862
2863 def _recursive_objects(self, include_repos=True, include_groups=True):
2863 def _recursive_objects(self, include_repos=True, include_groups=True):
2864 all_ = []
2864 all_ = []
2865
2865
2866 def _get_members(root_gr):
2866 def _get_members(root_gr):
2867 if include_repos:
2867 if include_repos:
2868 for r in root_gr.repositories:
2868 for r in root_gr.repositories:
2869 all_.append(r)
2869 all_.append(r)
2870 childs = root_gr.children.all()
2870 childs = root_gr.children.all()
2871 if childs:
2871 if childs:
2872 for gr in childs:
2872 for gr in childs:
2873 if include_groups:
2873 if include_groups:
2874 all_.append(gr)
2874 all_.append(gr)
2875 _get_members(gr)
2875 _get_members(gr)
2876
2876
2877 root_group = []
2877 root_group = []
2878 if include_groups:
2878 if include_groups:
2879 root_group = [self]
2879 root_group = [self]
2880
2880
2881 _get_members(self)
2881 _get_members(self)
2882 return root_group + all_
2882 return root_group + all_
2883
2883
2884 def recursive_groups_and_repos(self):
2884 def recursive_groups_and_repos(self):
2885 """
2885 """
2886 Recursive return all groups, with repositories in those groups
2886 Recursive return all groups, with repositories in those groups
2887 """
2887 """
2888 return self._recursive_objects()
2888 return self._recursive_objects()
2889
2889
2890 def recursive_groups(self):
2890 def recursive_groups(self):
2891 """
2891 """
2892 Returns all children groups for this group including children of children
2892 Returns all children groups for this group including children of children
2893 """
2893 """
2894 return self._recursive_objects(include_repos=False)
2894 return self._recursive_objects(include_repos=False)
2895
2895
2896 def recursive_repos(self):
2896 def recursive_repos(self):
2897 """
2897 """
2898 Returns all children repositories for this group
2898 Returns all children repositories for this group
2899 """
2899 """
2900 return self._recursive_objects(include_groups=False)
2900 return self._recursive_objects(include_groups=False)
2901
2901
2902 def get_new_name(self, group_name):
2902 def get_new_name(self, group_name):
2903 """
2903 """
2904 returns new full group name based on parent and new name
2904 returns new full group name based on parent and new name
2905
2905
2906 :param group_name:
2906 :param group_name:
2907 """
2907 """
2908 path_prefix = (self.parent_group.full_path_splitted if
2908 path_prefix = (self.parent_group.full_path_splitted if
2909 self.parent_group else [])
2909 self.parent_group else [])
2910 return RepoGroup.url_sep().join(path_prefix + [group_name])
2910 return RepoGroup.url_sep().join(path_prefix + [group_name])
2911
2911
2912 def update_commit_cache(self, config=None):
2912 def update_commit_cache(self, config=None):
2913 """
2913 """
2914 Update cache of last commit for newest repository inside this repository group.
2914 Update cache of last commit for newest repository inside this repository group.
2915 cache_keys should be::
2915 cache_keys should be::
2916
2916
2917 source_repo_id
2917 source_repo_id
2918 short_id
2918 short_id
2919 raw_id
2919 raw_id
2920 revision
2920 revision
2921 parents
2921 parents
2922 message
2922 message
2923 date
2923 date
2924 author
2924 author
2925
2925
2926 """
2926 """
2927 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2927 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2928 empty_date = datetime.datetime.fromtimestamp(0)
2928 empty_date = datetime.datetime.fromtimestamp(0)
2929
2929
2930 def repo_groups_and_repos(root_gr):
2930 def repo_groups_and_repos(root_gr):
2931 for _repo in root_gr.repositories:
2931 for _repo in root_gr.repositories:
2932 yield _repo
2932 yield _repo
2933 for child_group in root_gr.children.all():
2933 for child_group in root_gr.children.all():
2934 yield child_group
2934 yield child_group
2935
2935
2936 latest_repo_cs_cache = {}
2936 latest_repo_cs_cache = {}
2937 for obj in repo_groups_and_repos(self):
2937 for obj in repo_groups_and_repos(self):
2938 repo_cs_cache = obj.changeset_cache
2938 repo_cs_cache = obj.changeset_cache
2939 date_latest = latest_repo_cs_cache.get('date', empty_date)
2939 date_latest = latest_repo_cs_cache.get('date', empty_date)
2940 date_current = repo_cs_cache.get('date', empty_date)
2940 date_current = repo_cs_cache.get('date', empty_date)
2941 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2941 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2942 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2942 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2943 latest_repo_cs_cache = repo_cs_cache
2943 latest_repo_cs_cache = repo_cs_cache
2944 if hasattr(obj, 'repo_id'):
2944 if hasattr(obj, 'repo_id'):
2945 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2945 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2946 else:
2946 else:
2947 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2947 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2948
2948
2949 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2949 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2950
2950
2951 latest_repo_cs_cache['updated_on'] = time.time()
2951 latest_repo_cs_cache['updated_on'] = time.time()
2952 self.changeset_cache = latest_repo_cs_cache
2952 self.changeset_cache = latest_repo_cs_cache
2953 self.updated_on = _date_latest
2953 self.updated_on = _date_latest
2954 Session().add(self)
2954 Session().add(self)
2955 Session().commit()
2955 Session().commit()
2956
2956
2957 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2957 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2958 self.group_name, latest_repo_cs_cache, _date_latest)
2958 self.group_name, latest_repo_cs_cache, _date_latest)
2959
2959
2960 def permissions(self, with_admins=True, with_owner=True,
2960 def permissions(self, with_admins=True, with_owner=True,
2961 expand_from_user_groups=False):
2961 expand_from_user_groups=False):
2962 """
2962 """
2963 Permissions for repository groups
2963 Permissions for repository groups
2964 """
2964 """
2965 _admin_perm = 'group.admin'
2965 _admin_perm = 'group.admin'
2966
2966
2967 owner_row = []
2967 owner_row = []
2968 if with_owner:
2968 if with_owner:
2969 usr = AttributeDict(self.user.get_dict())
2969 usr = AttributeDict(self.user.get_dict())
2970 usr.owner_row = True
2970 usr.owner_row = True
2971 usr.permission = _admin_perm
2971 usr.permission = _admin_perm
2972 owner_row.append(usr)
2972 owner_row.append(usr)
2973
2973
2974 super_admin_ids = []
2974 super_admin_ids = []
2975 super_admin_rows = []
2975 super_admin_rows = []
2976 if with_admins:
2976 if with_admins:
2977 for usr in User.get_all_super_admins():
2977 for usr in User.get_all_super_admins():
2978 super_admin_ids.append(usr.user_id)
2978 super_admin_ids.append(usr.user_id)
2979 # if this admin is also owner, don't double the record
2979 # if this admin is also owner, don't double the record
2980 if usr.user_id == owner_row[0].user_id:
2980 if usr.user_id == owner_row[0].user_id:
2981 owner_row[0].admin_row = True
2981 owner_row[0].admin_row = True
2982 else:
2982 else:
2983 usr = AttributeDict(usr.get_dict())
2983 usr = AttributeDict(usr.get_dict())
2984 usr.admin_row = True
2984 usr.admin_row = True
2985 usr.permission = _admin_perm
2985 usr.permission = _admin_perm
2986 super_admin_rows.append(usr)
2986 super_admin_rows.append(usr)
2987
2987
2988 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2988 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2989 q = q.options(joinedload(UserRepoGroupToPerm.group),
2989 q = q.options(joinedload(UserRepoGroupToPerm.group),
2990 joinedload(UserRepoGroupToPerm.user),
2990 joinedload(UserRepoGroupToPerm.user),
2991 joinedload(UserRepoGroupToPerm.permission),)
2991 joinedload(UserRepoGroupToPerm.permission),)
2992
2992
2993 # get owners and admins and permissions. We do a trick of re-writing
2993 # get owners and admins and permissions. We do a trick of re-writing
2994 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2994 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2995 # has a global reference and changing one object propagates to all
2995 # has a global reference and changing one object propagates to all
2996 # others. This means if admin is also an owner admin_row that change
2996 # others. This means if admin is also an owner admin_row that change
2997 # would propagate to both objects
2997 # would propagate to both objects
2998 perm_rows = []
2998 perm_rows = []
2999 for _usr in q.all():
2999 for _usr in q.all():
3000 usr = AttributeDict(_usr.user.get_dict())
3000 usr = AttributeDict(_usr.user.get_dict())
3001 # if this user is also owner/admin, mark as duplicate record
3001 # if this user is also owner/admin, mark as duplicate record
3002 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3002 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3003 usr.duplicate_perm = True
3003 usr.duplicate_perm = True
3004 usr.permission = _usr.permission.permission_name
3004 usr.permission = _usr.permission.permission_name
3005 perm_rows.append(usr)
3005 perm_rows.append(usr)
3006
3006
3007 # filter the perm rows by 'default' first and then sort them by
3007 # filter the perm rows by 'default' first and then sort them by
3008 # admin,write,read,none permissions sorted again alphabetically in
3008 # admin,write,read,none permissions sorted again alphabetically in
3009 # each group
3009 # each group
3010 perm_rows = sorted(perm_rows, key=display_user_sort)
3010 perm_rows = sorted(perm_rows, key=display_user_sort)
3011
3011
3012 user_groups_rows = []
3012 user_groups_rows = []
3013 if expand_from_user_groups:
3013 if expand_from_user_groups:
3014 for ug in self.permission_user_groups(with_members=True):
3014 for ug in self.permission_user_groups(with_members=True):
3015 for user_data in ug.members:
3015 for user_data in ug.members:
3016 user_groups_rows.append(user_data)
3016 user_groups_rows.append(user_data)
3017
3017
3018 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3018 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3019
3019
3020 def permission_user_groups(self, with_members=False):
3020 def permission_user_groups(self, with_members=False):
3021 q = UserGroupRepoGroupToPerm.query()\
3021 q = UserGroupRepoGroupToPerm.query()\
3022 .filter(UserGroupRepoGroupToPerm.group == self)
3022 .filter(UserGroupRepoGroupToPerm.group == self)
3023 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3023 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3024 joinedload(UserGroupRepoGroupToPerm.users_group),
3024 joinedload(UserGroupRepoGroupToPerm.users_group),
3025 joinedload(UserGroupRepoGroupToPerm.permission),)
3025 joinedload(UserGroupRepoGroupToPerm.permission),)
3026
3026
3027 perm_rows = []
3027 perm_rows = []
3028 for _user_group in q.all():
3028 for _user_group in q.all():
3029 entry = AttributeDict(_user_group.users_group.get_dict())
3029 entry = AttributeDict(_user_group.users_group.get_dict())
3030 entry.permission = _user_group.permission.permission_name
3030 entry.permission = _user_group.permission.permission_name
3031 if with_members:
3031 if with_members:
3032 entry.members = [x.user.get_dict()
3032 entry.members = [x.user.get_dict()
3033 for x in _user_group.users_group.members]
3033 for x in _user_group.users_group.members]
3034 perm_rows.append(entry)
3034 perm_rows.append(entry)
3035
3035
3036 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3036 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3037 return perm_rows
3037 return perm_rows
3038
3038
3039 def get_api_data(self):
3039 def get_api_data(self):
3040 """
3040 """
3041 Common function for generating api data
3041 Common function for generating api data
3042
3042
3043 """
3043 """
3044 group = self
3044 group = self
3045 data = {
3045 data = {
3046 'group_id': group.group_id,
3046 'group_id': group.group_id,
3047 'group_name': group.group_name,
3047 'group_name': group.group_name,
3048 'group_description': group.description_safe,
3048 'group_description': group.description_safe,
3049 'parent_group': group.parent_group.group_name if group.parent_group else None,
3049 'parent_group': group.parent_group.group_name if group.parent_group else None,
3050 'repositories': [x.repo_name for x in group.repositories],
3050 'repositories': [x.repo_name for x in group.repositories],
3051 'owner': group.user.username,
3051 'owner': group.user.username,
3052 }
3052 }
3053 return data
3053 return data
3054
3054
3055 def get_dict(self):
3055 def get_dict(self):
3056 # Since we transformed `group_name` to a hybrid property, we need to
3056 # Since we transformed `group_name` to a hybrid property, we need to
3057 # keep compatibility with the code which uses `group_name` field.
3057 # keep compatibility with the code which uses `group_name` field.
3058 result = super(RepoGroup, self).get_dict()
3058 result = super(RepoGroup, self).get_dict()
3059 result['group_name'] = result.pop('_group_name', None)
3059 result['group_name'] = result.pop('_group_name', None)
3060 return result
3060 return result
3061
3061
3062
3062
3063 class Permission(Base, BaseModel):
3063 class Permission(Base, BaseModel):
3064 __tablename__ = 'permissions'
3064 __tablename__ = 'permissions'
3065 __table_args__ = (
3065 __table_args__ = (
3066 Index('p_perm_name_idx', 'permission_name'),
3066 Index('p_perm_name_idx', 'permission_name'),
3067 base_table_args,
3067 base_table_args,
3068 )
3068 )
3069
3069
3070 PERMS = [
3070 PERMS = [
3071 ('hg.admin', _('RhodeCode Super Administrator')),
3071 ('hg.admin', _('RhodeCode Super Administrator')),
3072
3072
3073 ('repository.none', _('Repository no access')),
3073 ('repository.none', _('Repository no access')),
3074 ('repository.read', _('Repository read access')),
3074 ('repository.read', _('Repository read access')),
3075 ('repository.write', _('Repository write access')),
3075 ('repository.write', _('Repository write access')),
3076 ('repository.admin', _('Repository admin access')),
3076 ('repository.admin', _('Repository admin access')),
3077
3077
3078 ('group.none', _('Repository group no access')),
3078 ('group.none', _('Repository group no access')),
3079 ('group.read', _('Repository group read access')),
3079 ('group.read', _('Repository group read access')),
3080 ('group.write', _('Repository group write access')),
3080 ('group.write', _('Repository group write access')),
3081 ('group.admin', _('Repository group admin access')),
3081 ('group.admin', _('Repository group admin access')),
3082
3082
3083 ('usergroup.none', _('User group no access')),
3083 ('usergroup.none', _('User group no access')),
3084 ('usergroup.read', _('User group read access')),
3084 ('usergroup.read', _('User group read access')),
3085 ('usergroup.write', _('User group write access')),
3085 ('usergroup.write', _('User group write access')),
3086 ('usergroup.admin', _('User group admin access')),
3086 ('usergroup.admin', _('User group admin access')),
3087
3087
3088 ('branch.none', _('Branch no permissions')),
3088 ('branch.none', _('Branch no permissions')),
3089 ('branch.merge', _('Branch access by web merge')),
3089 ('branch.merge', _('Branch access by web merge')),
3090 ('branch.push', _('Branch access by push')),
3090 ('branch.push', _('Branch access by push')),
3091 ('branch.push_force', _('Branch access by push with force')),
3091 ('branch.push_force', _('Branch access by push with force')),
3092
3092
3093 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3093 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3094 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3094 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3095
3095
3096 ('hg.usergroup.create.false', _('User Group creation disabled')),
3096 ('hg.usergroup.create.false', _('User Group creation disabled')),
3097 ('hg.usergroup.create.true', _('User Group creation enabled')),
3097 ('hg.usergroup.create.true', _('User Group creation enabled')),
3098
3098
3099 ('hg.create.none', _('Repository creation disabled')),
3099 ('hg.create.none', _('Repository creation disabled')),
3100 ('hg.create.repository', _('Repository creation enabled')),
3100 ('hg.create.repository', _('Repository creation enabled')),
3101 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3101 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3102 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3102 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3103
3103
3104 ('hg.fork.none', _('Repository forking disabled')),
3104 ('hg.fork.none', _('Repository forking disabled')),
3105 ('hg.fork.repository', _('Repository forking enabled')),
3105 ('hg.fork.repository', _('Repository forking enabled')),
3106
3106
3107 ('hg.register.none', _('Registration disabled')),
3107 ('hg.register.none', _('Registration disabled')),
3108 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3108 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3109 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3109 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3110
3110
3111 ('hg.password_reset.enabled', _('Password reset enabled')),
3111 ('hg.password_reset.enabled', _('Password reset enabled')),
3112 ('hg.password_reset.hidden', _('Password reset hidden')),
3112 ('hg.password_reset.hidden', _('Password reset hidden')),
3113 ('hg.password_reset.disabled', _('Password reset disabled')),
3113 ('hg.password_reset.disabled', _('Password reset disabled')),
3114
3114
3115 ('hg.extern_activate.manual', _('Manual activation of external account')),
3115 ('hg.extern_activate.manual', _('Manual activation of external account')),
3116 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3116 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3117
3117
3118 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3118 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3119 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3119 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3120 ]
3120 ]
3121
3121
3122 # definition of system default permissions for DEFAULT user, created on
3122 # definition of system default permissions for DEFAULT user, created on
3123 # system setup
3123 # system setup
3124 DEFAULT_USER_PERMISSIONS = [
3124 DEFAULT_USER_PERMISSIONS = [
3125 # object perms
3125 # object perms
3126 'repository.read',
3126 'repository.read',
3127 'group.read',
3127 'group.read',
3128 'usergroup.read',
3128 'usergroup.read',
3129 # branch, for backward compat we need same value as before so forced pushed
3129 # branch, for backward compat we need same value as before so forced pushed
3130 'branch.push_force',
3130 'branch.push_force',
3131 # global
3131 # global
3132 'hg.create.repository',
3132 'hg.create.repository',
3133 'hg.repogroup.create.false',
3133 'hg.repogroup.create.false',
3134 'hg.usergroup.create.false',
3134 'hg.usergroup.create.false',
3135 'hg.create.write_on_repogroup.true',
3135 'hg.create.write_on_repogroup.true',
3136 'hg.fork.repository',
3136 'hg.fork.repository',
3137 'hg.register.manual_activate',
3137 'hg.register.manual_activate',
3138 'hg.password_reset.enabled',
3138 'hg.password_reset.enabled',
3139 'hg.extern_activate.auto',
3139 'hg.extern_activate.auto',
3140 'hg.inherit_default_perms.true',
3140 'hg.inherit_default_perms.true',
3141 ]
3141 ]
3142
3142
3143 # defines which permissions are more important higher the more important
3143 # defines which permissions are more important higher the more important
3144 # Weight defines which permissions are more important.
3144 # Weight defines which permissions are more important.
3145 # The higher number the more important.
3145 # The higher number the more important.
3146 PERM_WEIGHTS = {
3146 PERM_WEIGHTS = {
3147 'repository.none': 0,
3147 'repository.none': 0,
3148 'repository.read': 1,
3148 'repository.read': 1,
3149 'repository.write': 3,
3149 'repository.write': 3,
3150 'repository.admin': 4,
3150 'repository.admin': 4,
3151
3151
3152 'group.none': 0,
3152 'group.none': 0,
3153 'group.read': 1,
3153 'group.read': 1,
3154 'group.write': 3,
3154 'group.write': 3,
3155 'group.admin': 4,
3155 'group.admin': 4,
3156
3156
3157 'usergroup.none': 0,
3157 'usergroup.none': 0,
3158 'usergroup.read': 1,
3158 'usergroup.read': 1,
3159 'usergroup.write': 3,
3159 'usergroup.write': 3,
3160 'usergroup.admin': 4,
3160 'usergroup.admin': 4,
3161
3161
3162 'branch.none': 0,
3162 'branch.none': 0,
3163 'branch.merge': 1,
3163 'branch.merge': 1,
3164 'branch.push': 3,
3164 'branch.push': 3,
3165 'branch.push_force': 4,
3165 'branch.push_force': 4,
3166
3166
3167 'hg.repogroup.create.false': 0,
3167 'hg.repogroup.create.false': 0,
3168 'hg.repogroup.create.true': 1,
3168 'hg.repogroup.create.true': 1,
3169
3169
3170 'hg.usergroup.create.false': 0,
3170 'hg.usergroup.create.false': 0,
3171 'hg.usergroup.create.true': 1,
3171 'hg.usergroup.create.true': 1,
3172
3172
3173 'hg.fork.none': 0,
3173 'hg.fork.none': 0,
3174 'hg.fork.repository': 1,
3174 'hg.fork.repository': 1,
3175 'hg.create.none': 0,
3175 'hg.create.none': 0,
3176 'hg.create.repository': 1
3176 'hg.create.repository': 1
3177 }
3177 }
3178
3178
3179 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3179 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3180 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3180 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3181 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3181 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3182
3182
3183 def __unicode__(self):
3183 def __unicode__(self):
3184 return u"<%s('%s:%s')>" % (
3184 return u"<%s('%s:%s')>" % (
3185 self.__class__.__name__, self.permission_id, self.permission_name
3185 self.__class__.__name__, self.permission_id, self.permission_name
3186 )
3186 )
3187
3187
3188 @classmethod
3188 @classmethod
3189 def get_by_key(cls, key):
3189 def get_by_key(cls, key):
3190 return cls.query().filter(cls.permission_name == key).scalar()
3190 return cls.query().filter(cls.permission_name == key).scalar()
3191
3191
3192 @classmethod
3192 @classmethod
3193 def get_default_repo_perms(cls, user_id, repo_id=None):
3193 def get_default_repo_perms(cls, user_id, repo_id=None):
3194 q = Session().query(UserRepoToPerm, Repository, Permission)\
3194 q = Session().query(UserRepoToPerm, Repository, Permission)\
3195 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3195 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3196 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3196 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3197 .filter(UserRepoToPerm.user_id == user_id)
3197 .filter(UserRepoToPerm.user_id == user_id)
3198 if repo_id:
3198 if repo_id:
3199 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3199 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3200 return q.all()
3200 return q.all()
3201
3201
3202 @classmethod
3202 @classmethod
3203 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3203 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3204 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3204 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3205 .join(
3205 .join(
3206 Permission,
3206 Permission,
3207 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3207 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3208 .join(
3208 .join(
3209 UserRepoToPerm,
3209 UserRepoToPerm,
3210 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3210 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3211 .filter(UserRepoToPerm.user_id == user_id)
3211 .filter(UserRepoToPerm.user_id == user_id)
3212
3212
3213 if repo_id:
3213 if repo_id:
3214 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3214 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3215 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3215 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3216
3216
3217 @classmethod
3217 @classmethod
3218 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3218 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3219 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3219 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3220 .join(
3220 .join(
3221 Permission,
3221 Permission,
3222 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3222 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3223 .join(
3223 .join(
3224 Repository,
3224 Repository,
3225 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3225 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3226 .join(
3226 .join(
3227 UserGroup,
3227 UserGroup,
3228 UserGroupRepoToPerm.users_group_id ==
3228 UserGroupRepoToPerm.users_group_id ==
3229 UserGroup.users_group_id)\
3229 UserGroup.users_group_id)\
3230 .join(
3230 .join(
3231 UserGroupMember,
3231 UserGroupMember,
3232 UserGroupRepoToPerm.users_group_id ==
3232 UserGroupRepoToPerm.users_group_id ==
3233 UserGroupMember.users_group_id)\
3233 UserGroupMember.users_group_id)\
3234 .filter(
3234 .filter(
3235 UserGroupMember.user_id == user_id,
3235 UserGroupMember.user_id == user_id,
3236 UserGroup.users_group_active == true())
3236 UserGroup.users_group_active == true())
3237 if repo_id:
3237 if repo_id:
3238 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3238 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3239 return q.all()
3239 return q.all()
3240
3240
3241 @classmethod
3241 @classmethod
3242 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3242 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3243 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3243 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3244 .join(
3244 .join(
3245 Permission,
3245 Permission,
3246 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3246 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3247 .join(
3247 .join(
3248 UserGroupRepoToPerm,
3248 UserGroupRepoToPerm,
3249 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3249 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3250 .join(
3250 .join(
3251 UserGroup,
3251 UserGroup,
3252 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3252 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3253 .join(
3253 .join(
3254 UserGroupMember,
3254 UserGroupMember,
3255 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3255 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3256 .filter(
3256 .filter(
3257 UserGroupMember.user_id == user_id,
3257 UserGroupMember.user_id == user_id,
3258 UserGroup.users_group_active == true())
3258 UserGroup.users_group_active == true())
3259
3259
3260 if repo_id:
3260 if repo_id:
3261 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3261 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3262 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3262 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3263
3263
3264 @classmethod
3264 @classmethod
3265 def get_default_group_perms(cls, user_id, repo_group_id=None):
3265 def get_default_group_perms(cls, user_id, repo_group_id=None):
3266 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3266 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3267 .join(
3267 .join(
3268 Permission,
3268 Permission,
3269 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3269 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3270 .join(
3270 .join(
3271 RepoGroup,
3271 RepoGroup,
3272 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3272 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3273 .filter(UserRepoGroupToPerm.user_id == user_id)
3273 .filter(UserRepoGroupToPerm.user_id == user_id)
3274 if repo_group_id:
3274 if repo_group_id:
3275 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3275 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3276 return q.all()
3276 return q.all()
3277
3277
3278 @classmethod
3278 @classmethod
3279 def get_default_group_perms_from_user_group(
3279 def get_default_group_perms_from_user_group(
3280 cls, user_id, repo_group_id=None):
3280 cls, user_id, repo_group_id=None):
3281 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3281 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3282 .join(
3282 .join(
3283 Permission,
3283 Permission,
3284 UserGroupRepoGroupToPerm.permission_id ==
3284 UserGroupRepoGroupToPerm.permission_id ==
3285 Permission.permission_id)\
3285 Permission.permission_id)\
3286 .join(
3286 .join(
3287 RepoGroup,
3287 RepoGroup,
3288 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3288 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3289 .join(
3289 .join(
3290 UserGroup,
3290 UserGroup,
3291 UserGroupRepoGroupToPerm.users_group_id ==
3291 UserGroupRepoGroupToPerm.users_group_id ==
3292 UserGroup.users_group_id)\
3292 UserGroup.users_group_id)\
3293 .join(
3293 .join(
3294 UserGroupMember,
3294 UserGroupMember,
3295 UserGroupRepoGroupToPerm.users_group_id ==
3295 UserGroupRepoGroupToPerm.users_group_id ==
3296 UserGroupMember.users_group_id)\
3296 UserGroupMember.users_group_id)\
3297 .filter(
3297 .filter(
3298 UserGroupMember.user_id == user_id,
3298 UserGroupMember.user_id == user_id,
3299 UserGroup.users_group_active == true())
3299 UserGroup.users_group_active == true())
3300 if repo_group_id:
3300 if repo_group_id:
3301 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3301 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3302 return q.all()
3302 return q.all()
3303
3303
3304 @classmethod
3304 @classmethod
3305 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3305 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3306 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3306 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3307 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3307 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3308 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3308 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3309 .filter(UserUserGroupToPerm.user_id == user_id)
3309 .filter(UserUserGroupToPerm.user_id == user_id)
3310 if user_group_id:
3310 if user_group_id:
3311 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3311 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3312 return q.all()
3312 return q.all()
3313
3313
3314 @classmethod
3314 @classmethod
3315 def get_default_user_group_perms_from_user_group(
3315 def get_default_user_group_perms_from_user_group(
3316 cls, user_id, user_group_id=None):
3316 cls, user_id, user_group_id=None):
3317 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3317 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3318 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3318 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3319 .join(
3319 .join(
3320 Permission,
3320 Permission,
3321 UserGroupUserGroupToPerm.permission_id ==
3321 UserGroupUserGroupToPerm.permission_id ==
3322 Permission.permission_id)\
3322 Permission.permission_id)\
3323 .join(
3323 .join(
3324 TargetUserGroup,
3324 TargetUserGroup,
3325 UserGroupUserGroupToPerm.target_user_group_id ==
3325 UserGroupUserGroupToPerm.target_user_group_id ==
3326 TargetUserGroup.users_group_id)\
3326 TargetUserGroup.users_group_id)\
3327 .join(
3327 .join(
3328 UserGroup,
3328 UserGroup,
3329 UserGroupUserGroupToPerm.user_group_id ==
3329 UserGroupUserGroupToPerm.user_group_id ==
3330 UserGroup.users_group_id)\
3330 UserGroup.users_group_id)\
3331 .join(
3331 .join(
3332 UserGroupMember,
3332 UserGroupMember,
3333 UserGroupUserGroupToPerm.user_group_id ==
3333 UserGroupUserGroupToPerm.user_group_id ==
3334 UserGroupMember.users_group_id)\
3334 UserGroupMember.users_group_id)\
3335 .filter(
3335 .filter(
3336 UserGroupMember.user_id == user_id,
3336 UserGroupMember.user_id == user_id,
3337 UserGroup.users_group_active == true())
3337 UserGroup.users_group_active == true())
3338 if user_group_id:
3338 if user_group_id:
3339 q = q.filter(
3339 q = q.filter(
3340 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3340 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3341
3341
3342 return q.all()
3342 return q.all()
3343
3343
3344
3344
3345 class UserRepoToPerm(Base, BaseModel):
3345 class UserRepoToPerm(Base, BaseModel):
3346 __tablename__ = 'repo_to_perm'
3346 __tablename__ = 'repo_to_perm'
3347 __table_args__ = (
3347 __table_args__ = (
3348 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3348 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3349 base_table_args
3349 base_table_args
3350 )
3350 )
3351
3351
3352 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3352 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3353 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3353 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3354 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3354 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3355 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3355 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3356
3356
3357 user = relationship('User')
3357 user = relationship('User')
3358 repository = relationship('Repository')
3358 repository = relationship('Repository')
3359 permission = relationship('Permission')
3359 permission = relationship('Permission')
3360
3360
3361 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3361 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3362
3362
3363 @classmethod
3363 @classmethod
3364 def create(cls, user, repository, permission):
3364 def create(cls, user, repository, permission):
3365 n = cls()
3365 n = cls()
3366 n.user = user
3366 n.user = user
3367 n.repository = repository
3367 n.repository = repository
3368 n.permission = permission
3368 n.permission = permission
3369 Session().add(n)
3369 Session().add(n)
3370 return n
3370 return n
3371
3371
3372 def __unicode__(self):
3372 def __unicode__(self):
3373 return u'<%s => %s >' % (self.user, self.repository)
3373 return u'<%s => %s >' % (self.user, self.repository)
3374
3374
3375
3375
3376 class UserUserGroupToPerm(Base, BaseModel):
3376 class UserUserGroupToPerm(Base, BaseModel):
3377 __tablename__ = 'user_user_group_to_perm'
3377 __tablename__ = 'user_user_group_to_perm'
3378 __table_args__ = (
3378 __table_args__ = (
3379 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3379 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3380 base_table_args
3380 base_table_args
3381 )
3381 )
3382
3382
3383 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3383 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3385 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3385 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3386 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3386 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3387
3387
3388 user = relationship('User')
3388 user = relationship('User')
3389 user_group = relationship('UserGroup')
3389 user_group = relationship('UserGroup')
3390 permission = relationship('Permission')
3390 permission = relationship('Permission')
3391
3391
3392 @classmethod
3392 @classmethod
3393 def create(cls, user, user_group, permission):
3393 def create(cls, user, user_group, permission):
3394 n = cls()
3394 n = cls()
3395 n.user = user
3395 n.user = user
3396 n.user_group = user_group
3396 n.user_group = user_group
3397 n.permission = permission
3397 n.permission = permission
3398 Session().add(n)
3398 Session().add(n)
3399 return n
3399 return n
3400
3400
3401 def __unicode__(self):
3401 def __unicode__(self):
3402 return u'<%s => %s >' % (self.user, self.user_group)
3402 return u'<%s => %s >' % (self.user, self.user_group)
3403
3403
3404
3404
3405 class UserToPerm(Base, BaseModel):
3405 class UserToPerm(Base, BaseModel):
3406 __tablename__ = 'user_to_perm'
3406 __tablename__ = 'user_to_perm'
3407 __table_args__ = (
3407 __table_args__ = (
3408 UniqueConstraint('user_id', 'permission_id'),
3408 UniqueConstraint('user_id', 'permission_id'),
3409 base_table_args
3409 base_table_args
3410 )
3410 )
3411
3411
3412 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3412 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3413 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3413 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3414 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3414 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3415
3415
3416 user = relationship('User')
3416 user = relationship('User')
3417 permission = relationship('Permission', lazy='joined')
3417 permission = relationship('Permission', lazy='joined')
3418
3418
3419 def __unicode__(self):
3419 def __unicode__(self):
3420 return u'<%s => %s >' % (self.user, self.permission)
3420 return u'<%s => %s >' % (self.user, self.permission)
3421
3421
3422
3422
3423 class UserGroupRepoToPerm(Base, BaseModel):
3423 class UserGroupRepoToPerm(Base, BaseModel):
3424 __tablename__ = 'users_group_repo_to_perm'
3424 __tablename__ = 'users_group_repo_to_perm'
3425 __table_args__ = (
3425 __table_args__ = (
3426 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3426 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3427 base_table_args
3427 base_table_args
3428 )
3428 )
3429
3429
3430 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3430 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3431 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3431 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3432 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3432 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3433 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3433 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3434
3434
3435 users_group = relationship('UserGroup')
3435 users_group = relationship('UserGroup')
3436 permission = relationship('Permission')
3436 permission = relationship('Permission')
3437 repository = relationship('Repository')
3437 repository = relationship('Repository')
3438 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3438 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3439
3439
3440 @classmethod
3440 @classmethod
3441 def create(cls, users_group, repository, permission):
3441 def create(cls, users_group, repository, permission):
3442 n = cls()
3442 n = cls()
3443 n.users_group = users_group
3443 n.users_group = users_group
3444 n.repository = repository
3444 n.repository = repository
3445 n.permission = permission
3445 n.permission = permission
3446 Session().add(n)
3446 Session().add(n)
3447 return n
3447 return n
3448
3448
3449 def __unicode__(self):
3449 def __unicode__(self):
3450 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3450 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3451
3451
3452
3452
3453 class UserGroupUserGroupToPerm(Base, BaseModel):
3453 class UserGroupUserGroupToPerm(Base, BaseModel):
3454 __tablename__ = 'user_group_user_group_to_perm'
3454 __tablename__ = 'user_group_user_group_to_perm'
3455 __table_args__ = (
3455 __table_args__ = (
3456 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3456 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3457 CheckConstraint('target_user_group_id != user_group_id'),
3457 CheckConstraint('target_user_group_id != user_group_id'),
3458 base_table_args
3458 base_table_args
3459 )
3459 )
3460
3460
3461 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)
3461 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)
3462 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3462 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3463 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3463 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3464 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3464 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3465
3465
3466 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3466 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3467 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3467 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3468 permission = relationship('Permission')
3468 permission = relationship('Permission')
3469
3469
3470 @classmethod
3470 @classmethod
3471 def create(cls, target_user_group, user_group, permission):
3471 def create(cls, target_user_group, user_group, permission):
3472 n = cls()
3472 n = cls()
3473 n.target_user_group = target_user_group
3473 n.target_user_group = target_user_group
3474 n.user_group = user_group
3474 n.user_group = user_group
3475 n.permission = permission
3475 n.permission = permission
3476 Session().add(n)
3476 Session().add(n)
3477 return n
3477 return n
3478
3478
3479 def __unicode__(self):
3479 def __unicode__(self):
3480 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3480 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3481
3481
3482
3482
3483 class UserGroupToPerm(Base, BaseModel):
3483 class UserGroupToPerm(Base, BaseModel):
3484 __tablename__ = 'users_group_to_perm'
3484 __tablename__ = 'users_group_to_perm'
3485 __table_args__ = (
3485 __table_args__ = (
3486 UniqueConstraint('users_group_id', 'permission_id',),
3486 UniqueConstraint('users_group_id', 'permission_id',),
3487 base_table_args
3487 base_table_args
3488 )
3488 )
3489
3489
3490 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3490 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3491 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3491 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3492 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3492 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3493
3493
3494 users_group = relationship('UserGroup')
3494 users_group = relationship('UserGroup')
3495 permission = relationship('Permission')
3495 permission = relationship('Permission')
3496
3496
3497
3497
3498 class UserRepoGroupToPerm(Base, BaseModel):
3498 class UserRepoGroupToPerm(Base, BaseModel):
3499 __tablename__ = 'user_repo_group_to_perm'
3499 __tablename__ = 'user_repo_group_to_perm'
3500 __table_args__ = (
3500 __table_args__ = (
3501 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3501 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3502 base_table_args
3502 base_table_args
3503 )
3503 )
3504
3504
3505 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3505 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3506 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3506 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3509
3509
3510 user = relationship('User')
3510 user = relationship('User')
3511 group = relationship('RepoGroup')
3511 group = relationship('RepoGroup')
3512 permission = relationship('Permission')
3512 permission = relationship('Permission')
3513
3513
3514 @classmethod
3514 @classmethod
3515 def create(cls, user, repository_group, permission):
3515 def create(cls, user, repository_group, permission):
3516 n = cls()
3516 n = cls()
3517 n.user = user
3517 n.user = user
3518 n.group = repository_group
3518 n.group = repository_group
3519 n.permission = permission
3519 n.permission = permission
3520 Session().add(n)
3520 Session().add(n)
3521 return n
3521 return n
3522
3522
3523
3523
3524 class UserGroupRepoGroupToPerm(Base, BaseModel):
3524 class UserGroupRepoGroupToPerm(Base, BaseModel):
3525 __tablename__ = 'users_group_repo_group_to_perm'
3525 __tablename__ = 'users_group_repo_group_to_perm'
3526 __table_args__ = (
3526 __table_args__ = (
3527 UniqueConstraint('users_group_id', 'group_id'),
3527 UniqueConstraint('users_group_id', 'group_id'),
3528 base_table_args
3528 base_table_args
3529 )
3529 )
3530
3530
3531 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)
3531 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)
3532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3533 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3533 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3534 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3534 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3535
3535
3536 users_group = relationship('UserGroup')
3536 users_group = relationship('UserGroup')
3537 permission = relationship('Permission')
3537 permission = relationship('Permission')
3538 group = relationship('RepoGroup')
3538 group = relationship('RepoGroup')
3539
3539
3540 @classmethod
3540 @classmethod
3541 def create(cls, user_group, repository_group, permission):
3541 def create(cls, user_group, repository_group, permission):
3542 n = cls()
3542 n = cls()
3543 n.users_group = user_group
3543 n.users_group = user_group
3544 n.group = repository_group
3544 n.group = repository_group
3545 n.permission = permission
3545 n.permission = permission
3546 Session().add(n)
3546 Session().add(n)
3547 return n
3547 return n
3548
3548
3549 def __unicode__(self):
3549 def __unicode__(self):
3550 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3550 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3551
3551
3552
3552
3553 class Statistics(Base, BaseModel):
3553 class Statistics(Base, BaseModel):
3554 __tablename__ = 'statistics'
3554 __tablename__ = 'statistics'
3555 __table_args__ = (
3555 __table_args__ = (
3556 base_table_args
3556 base_table_args
3557 )
3557 )
3558
3558
3559 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3559 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3560 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3560 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3561 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3561 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3562 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3562 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3563 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3563 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3564 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3564 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3565
3565
3566 repository = relationship('Repository', single_parent=True)
3566 repository = relationship('Repository', single_parent=True)
3567
3567
3568
3568
3569 class UserFollowing(Base, BaseModel):
3569 class UserFollowing(Base, BaseModel):
3570 __tablename__ = 'user_followings'
3570 __tablename__ = 'user_followings'
3571 __table_args__ = (
3571 __table_args__ = (
3572 UniqueConstraint('user_id', 'follows_repository_id'),
3572 UniqueConstraint('user_id', 'follows_repository_id'),
3573 UniqueConstraint('user_id', 'follows_user_id'),
3573 UniqueConstraint('user_id', 'follows_user_id'),
3574 base_table_args
3574 base_table_args
3575 )
3575 )
3576
3576
3577 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3577 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3578 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3578 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3579 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3579 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3580 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3580 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3581 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3581 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3582
3582
3583 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3583 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3584
3584
3585 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3585 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3586 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3586 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3587
3587
3588 @classmethod
3588 @classmethod
3589 def get_repo_followers(cls, repo_id):
3589 def get_repo_followers(cls, repo_id):
3590 return cls.query().filter(cls.follows_repo_id == repo_id)
3590 return cls.query().filter(cls.follows_repo_id == repo_id)
3591
3591
3592
3592
3593 class CacheKey(Base, BaseModel):
3593 class CacheKey(Base, BaseModel):
3594 __tablename__ = 'cache_invalidation'
3594 __tablename__ = 'cache_invalidation'
3595 __table_args__ = (
3595 __table_args__ = (
3596 UniqueConstraint('cache_key'),
3596 UniqueConstraint('cache_key'),
3597 Index('key_idx', 'cache_key'),
3597 Index('key_idx', 'cache_key'),
3598 base_table_args,
3598 base_table_args,
3599 )
3599 )
3600
3600
3601 CACHE_TYPE_FEED = 'FEED'
3601 CACHE_TYPE_FEED = 'FEED'
3602
3602
3603 # namespaces used to register process/thread aware caches
3603 # namespaces used to register process/thread aware caches
3604 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3604 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3605 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3605 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3606
3606
3607 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3607 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3608 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3608 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3609 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3609 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3610 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3610 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3611 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3611 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3612
3612
3613 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3613 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3614 self.cache_key = cache_key
3614 self.cache_key = cache_key
3615 self.cache_args = cache_args
3615 self.cache_args = cache_args
3616 self.cache_active = False
3616 self.cache_active = False
3617 # first key should be same for all entries, since all workers should share it
3617 # first key should be same for all entries, since all workers should share it
3618 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3618 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3619
3619
3620 def __unicode__(self):
3620 def __unicode__(self):
3621 return u"<%s('%s:%s[%s]')>" % (
3621 return u"<%s('%s:%s[%s]')>" % (
3622 self.__class__.__name__,
3622 self.__class__.__name__,
3623 self.cache_id, self.cache_key, self.cache_active)
3623 self.cache_id, self.cache_key, self.cache_active)
3624
3624
3625 def _cache_key_partition(self):
3625 def _cache_key_partition(self):
3626 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3626 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3627 return prefix, repo_name, suffix
3627 return prefix, repo_name, suffix
3628
3628
3629 def get_prefix(self):
3629 def get_prefix(self):
3630 """
3630 """
3631 Try to extract prefix from existing cache key. The key could consist
3631 Try to extract prefix from existing cache key. The key could consist
3632 of prefix, repo_name, suffix
3632 of prefix, repo_name, suffix
3633 """
3633 """
3634 # this returns prefix, repo_name, suffix
3634 # this returns prefix, repo_name, suffix
3635 return self._cache_key_partition()[0]
3635 return self._cache_key_partition()[0]
3636
3636
3637 def get_suffix(self):
3637 def get_suffix(self):
3638 """
3638 """
3639 get suffix that might have been used in _get_cache_key to
3639 get suffix that might have been used in _get_cache_key to
3640 generate self.cache_key. Only used for informational purposes
3640 generate self.cache_key. Only used for informational purposes
3641 in repo_edit.mako.
3641 in repo_edit.mako.
3642 """
3642 """
3643 # prefix, repo_name, suffix
3643 # prefix, repo_name, suffix
3644 return self._cache_key_partition()[2]
3644 return self._cache_key_partition()[2]
3645
3645
3646 @classmethod
3646 @classmethod
3647 def generate_new_state_uid(cls, based_on=None):
3647 def generate_new_state_uid(cls, based_on=None):
3648 if based_on:
3648 if based_on:
3649 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3649 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3650 else:
3650 else:
3651 return str(uuid.uuid4())
3651 return str(uuid.uuid4())
3652
3652
3653 @classmethod
3653 @classmethod
3654 def delete_all_cache(cls):
3654 def delete_all_cache(cls):
3655 """
3655 """
3656 Delete all cache keys from database.
3656 Delete all cache keys from database.
3657 Should only be run when all instances are down and all entries
3657 Should only be run when all instances are down and all entries
3658 thus stale.
3658 thus stale.
3659 """
3659 """
3660 cls.query().delete()
3660 cls.query().delete()
3661 Session().commit()
3661 Session().commit()
3662
3662
3663 @classmethod
3663 @classmethod
3664 def set_invalidate(cls, cache_uid, delete=False):
3664 def set_invalidate(cls, cache_uid, delete=False):
3665 """
3665 """
3666 Mark all caches of a repo as invalid in the database.
3666 Mark all caches of a repo as invalid in the database.
3667 """
3667 """
3668
3668
3669 try:
3669 try:
3670 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3670 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3671 if delete:
3671 if delete:
3672 qry.delete()
3672 qry.delete()
3673 log.debug('cache objects deleted for cache args %s',
3673 log.debug('cache objects deleted for cache args %s',
3674 safe_str(cache_uid))
3674 safe_str(cache_uid))
3675 else:
3675 else:
3676 qry.update({"cache_active": False,
3676 qry.update({"cache_active": False,
3677 "cache_state_uid": cls.generate_new_state_uid()})
3677 "cache_state_uid": cls.generate_new_state_uid()})
3678 log.debug('cache objects marked as invalid for cache args %s',
3678 log.debug('cache objects marked as invalid for cache args %s',
3679 safe_str(cache_uid))
3679 safe_str(cache_uid))
3680
3680
3681 Session().commit()
3681 Session().commit()
3682 except Exception:
3682 except Exception:
3683 log.exception(
3683 log.exception(
3684 'Cache key invalidation failed for cache args %s',
3684 'Cache key invalidation failed for cache args %s',
3685 safe_str(cache_uid))
3685 safe_str(cache_uid))
3686 Session().rollback()
3686 Session().rollback()
3687
3687
3688 @classmethod
3688 @classmethod
3689 def get_active_cache(cls, cache_key):
3689 def get_active_cache(cls, cache_key):
3690 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3690 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3691 if inv_obj:
3691 if inv_obj:
3692 return inv_obj
3692 return inv_obj
3693 return None
3693 return None
3694
3694
3695 @classmethod
3695 @classmethod
3696 def get_namespace_map(cls, namespace):
3696 def get_namespace_map(cls, namespace):
3697 return {
3697 return {
3698 x.cache_key: x
3698 x.cache_key: x
3699 for x in cls.query().filter(cls.cache_args == namespace)}
3699 for x in cls.query().filter(cls.cache_args == namespace)}
3700
3700
3701
3701
3702 class ChangesetComment(Base, BaseModel):
3702 class ChangesetComment(Base, BaseModel):
3703 __tablename__ = 'changeset_comments'
3703 __tablename__ = 'changeset_comments'
3704 __table_args__ = (
3704 __table_args__ = (
3705 Index('cc_revision_idx', 'revision'),
3705 Index('cc_revision_idx', 'revision'),
3706 base_table_args,
3706 base_table_args,
3707 )
3707 )
3708
3708
3709 COMMENT_OUTDATED = u'comment_outdated'
3709 COMMENT_OUTDATED = u'comment_outdated'
3710 COMMENT_TYPE_NOTE = u'note'
3710 COMMENT_TYPE_NOTE = u'note'
3711 COMMENT_TYPE_TODO = u'todo'
3711 COMMENT_TYPE_TODO = u'todo'
3712 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3712 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3713
3713
3714 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3714 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3715 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3715 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3716 revision = Column('revision', String(40), nullable=True)
3716 revision = Column('revision', String(40), nullable=True)
3717 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3717 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3718 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3718 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3719 line_no = Column('line_no', Unicode(10), nullable=True)
3719 line_no = Column('line_no', Unicode(10), nullable=True)
3720 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3720 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3721 f_path = Column('f_path', Unicode(1000), nullable=True)
3721 f_path = Column('f_path', Unicode(1000), nullable=True)
3722 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3722 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3723 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3723 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3724 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3724 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3725 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3725 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3726 renderer = Column('renderer', Unicode(64), nullable=True)
3726 renderer = Column('renderer', Unicode(64), nullable=True)
3727 display_state = Column('display_state', Unicode(128), nullable=True)
3727 display_state = Column('display_state', Unicode(128), nullable=True)
3728
3728
3729 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3729 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3730 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3730 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3731
3731
3732 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3732 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3733 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3733 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3734
3734
3735 author = relationship('User', lazy='joined')
3735 author = relationship('User', lazy='joined')
3736 repo = relationship('Repository')
3736 repo = relationship('Repository')
3737 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3737 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3738 pull_request = relationship('PullRequest', lazy='joined')
3738 pull_request = relationship('PullRequest', lazy='joined')
3739 pull_request_version = relationship('PullRequestVersion')
3739 pull_request_version = relationship('PullRequestVersion')
3740
3740
3741 @classmethod
3741 @classmethod
3742 def get_users(cls, revision=None, pull_request_id=None):
3742 def get_users(cls, revision=None, pull_request_id=None):
3743 """
3743 """
3744 Returns user associated with this ChangesetComment. ie those
3744 Returns user associated with this ChangesetComment. ie those
3745 who actually commented
3745 who actually commented
3746
3746
3747 :param cls:
3747 :param cls:
3748 :param revision:
3748 :param revision:
3749 """
3749 """
3750 q = Session().query(User)\
3750 q = Session().query(User)\
3751 .join(ChangesetComment.author)
3751 .join(ChangesetComment.author)
3752 if revision:
3752 if revision:
3753 q = q.filter(cls.revision == revision)
3753 q = q.filter(cls.revision == revision)
3754 elif pull_request_id:
3754 elif pull_request_id:
3755 q = q.filter(cls.pull_request_id == pull_request_id)
3755 q = q.filter(cls.pull_request_id == pull_request_id)
3756 return q.all()
3756 return q.all()
3757
3757
3758 @classmethod
3758 @classmethod
3759 def get_index_from_version(cls, pr_version, versions):
3759 def get_index_from_version(cls, pr_version, versions):
3760 num_versions = [x.pull_request_version_id for x in versions]
3760 num_versions = [x.pull_request_version_id for x in versions]
3761 try:
3761 try:
3762 return num_versions.index(pr_version) +1
3762 return num_versions.index(pr_version) +1
3763 except (IndexError, ValueError):
3763 except (IndexError, ValueError):
3764 return
3764 return
3765
3765
3766 @property
3766 @property
3767 def outdated(self):
3767 def outdated(self):
3768 return self.display_state == self.COMMENT_OUTDATED
3768 return self.display_state == self.COMMENT_OUTDATED
3769
3769
3770 def outdated_at_version(self, version):
3770 def outdated_at_version(self, version):
3771 """
3771 """
3772 Checks if comment is outdated for given pull request version
3772 Checks if comment is outdated for given pull request version
3773 """
3773 """
3774 return self.outdated and self.pull_request_version_id != version
3774 return self.outdated and self.pull_request_version_id != version
3775
3775
3776 def older_than_version(self, version):
3776 def older_than_version(self, version):
3777 """
3777 """
3778 Checks if comment is made from previous version than given
3778 Checks if comment is made from previous version than given
3779 """
3779 """
3780 if version is None:
3780 if version is None:
3781 return self.pull_request_version_id is not None
3781 return self.pull_request_version_id is not None
3782
3782
3783 return self.pull_request_version_id < version
3783 return self.pull_request_version_id < version
3784
3784
3785 @property
3785 @property
3786 def resolved(self):
3786 def resolved(self):
3787 return self.resolved_by[0] if self.resolved_by else None
3787 return self.resolved_by[0] if self.resolved_by else None
3788
3788
3789 @property
3789 @property
3790 def is_todo(self):
3790 def is_todo(self):
3791 return self.comment_type == self.COMMENT_TYPE_TODO
3791 return self.comment_type == self.COMMENT_TYPE_TODO
3792
3792
3793 @property
3793 @property
3794 def is_inline(self):
3794 def is_inline(self):
3795 return self.line_no and self.f_path
3795 return self.line_no and self.f_path
3796
3796
3797 def get_index_version(self, versions):
3797 def get_index_version(self, versions):
3798 return self.get_index_from_version(
3798 return self.get_index_from_version(
3799 self.pull_request_version_id, versions)
3799 self.pull_request_version_id, versions)
3800
3800
3801 def __repr__(self):
3801 def __repr__(self):
3802 if self.comment_id:
3802 if self.comment_id:
3803 return '<DB:Comment #%s>' % self.comment_id
3803 return '<DB:Comment #%s>' % self.comment_id
3804 else:
3804 else:
3805 return '<DB:Comment at %#x>' % id(self)
3805 return '<DB:Comment at %#x>' % id(self)
3806
3806
3807 def get_api_data(self):
3807 def get_api_data(self):
3808 comment = self
3808 comment = self
3809 data = {
3809 data = {
3810 'comment_id': comment.comment_id,
3810 'comment_id': comment.comment_id,
3811 'comment_type': comment.comment_type,
3811 'comment_type': comment.comment_type,
3812 'comment_text': comment.text,
3812 'comment_text': comment.text,
3813 'comment_status': comment.status_change,
3813 'comment_status': comment.status_change,
3814 'comment_f_path': comment.f_path,
3814 'comment_f_path': comment.f_path,
3815 'comment_lineno': comment.line_no,
3815 'comment_lineno': comment.line_no,
3816 'comment_author': comment.author,
3816 'comment_author': comment.author,
3817 'comment_created_on': comment.created_on,
3817 'comment_created_on': comment.created_on,
3818 'comment_resolved_by': self.resolved
3818 'comment_resolved_by': self.resolved,
3819 'comment_commit_id': comment.revision,
3820 'comment_pull_request_id': comment.pull_request_id,
3819 }
3821 }
3820 return data
3822 return data
3821
3823
3822 def __json__(self):
3824 def __json__(self):
3823 data = dict()
3825 data = dict()
3824 data.update(self.get_api_data())
3826 data.update(self.get_api_data())
3825 return data
3827 return data
3826
3828
3827
3829
3828 class ChangesetStatus(Base, BaseModel):
3830 class ChangesetStatus(Base, BaseModel):
3829 __tablename__ = 'changeset_statuses'
3831 __tablename__ = 'changeset_statuses'
3830 __table_args__ = (
3832 __table_args__ = (
3831 Index('cs_revision_idx', 'revision'),
3833 Index('cs_revision_idx', 'revision'),
3832 Index('cs_version_idx', 'version'),
3834 Index('cs_version_idx', 'version'),
3833 UniqueConstraint('repo_id', 'revision', 'version'),
3835 UniqueConstraint('repo_id', 'revision', 'version'),
3834 base_table_args
3836 base_table_args
3835 )
3837 )
3836
3838
3837 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3839 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3838 STATUS_APPROVED = 'approved'
3840 STATUS_APPROVED = 'approved'
3839 STATUS_REJECTED = 'rejected'
3841 STATUS_REJECTED = 'rejected'
3840 STATUS_UNDER_REVIEW = 'under_review'
3842 STATUS_UNDER_REVIEW = 'under_review'
3841
3843
3842 STATUSES = [
3844 STATUSES = [
3843 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3845 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3844 (STATUS_APPROVED, _("Approved")),
3846 (STATUS_APPROVED, _("Approved")),
3845 (STATUS_REJECTED, _("Rejected")),
3847 (STATUS_REJECTED, _("Rejected")),
3846 (STATUS_UNDER_REVIEW, _("Under Review")),
3848 (STATUS_UNDER_REVIEW, _("Under Review")),
3847 ]
3849 ]
3848
3850
3849 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3851 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3850 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3852 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3851 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3853 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3852 revision = Column('revision', String(40), nullable=False)
3854 revision = Column('revision', String(40), nullable=False)
3853 status = Column('status', String(128), nullable=False, default=DEFAULT)
3855 status = Column('status', String(128), nullable=False, default=DEFAULT)
3854 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3856 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3855 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3857 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3856 version = Column('version', Integer(), nullable=False, default=0)
3858 version = Column('version', Integer(), nullable=False, default=0)
3857 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3859 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3858
3860
3859 author = relationship('User', lazy='joined')
3861 author = relationship('User', lazy='joined')
3860 repo = relationship('Repository')
3862 repo = relationship('Repository')
3861 comment = relationship('ChangesetComment', lazy='joined')
3863 comment = relationship('ChangesetComment', lazy='joined')
3862 pull_request = relationship('PullRequest', lazy='joined')
3864 pull_request = relationship('PullRequest', lazy='joined')
3863
3865
3864 def __unicode__(self):
3866 def __unicode__(self):
3865 return u"<%s('%s[v%s]:%s')>" % (
3867 return u"<%s('%s[v%s]:%s')>" % (
3866 self.__class__.__name__,
3868 self.__class__.__name__,
3867 self.status, self.version, self.author
3869 self.status, self.version, self.author
3868 )
3870 )
3869
3871
3870 @classmethod
3872 @classmethod
3871 def get_status_lbl(cls, value):
3873 def get_status_lbl(cls, value):
3872 return dict(cls.STATUSES).get(value)
3874 return dict(cls.STATUSES).get(value)
3873
3875
3874 @property
3876 @property
3875 def status_lbl(self):
3877 def status_lbl(self):
3876 return ChangesetStatus.get_status_lbl(self.status)
3878 return ChangesetStatus.get_status_lbl(self.status)
3877
3879
3878 def get_api_data(self):
3880 def get_api_data(self):
3879 status = self
3881 status = self
3880 data = {
3882 data = {
3881 'status_id': status.changeset_status_id,
3883 'status_id': status.changeset_status_id,
3882 'status': status.status,
3884 'status': status.status,
3883 }
3885 }
3884 return data
3886 return data
3885
3887
3886 def __json__(self):
3888 def __json__(self):
3887 data = dict()
3889 data = dict()
3888 data.update(self.get_api_data())
3890 data.update(self.get_api_data())
3889 return data
3891 return data
3890
3892
3891
3893
3892 class _SetState(object):
3894 class _SetState(object):
3893 """
3895 """
3894 Context processor allowing changing state for sensitive operation such as
3896 Context processor allowing changing state for sensitive operation such as
3895 pull request update or merge
3897 pull request update or merge
3896 """
3898 """
3897
3899
3898 def __init__(self, pull_request, pr_state, back_state=None):
3900 def __init__(self, pull_request, pr_state, back_state=None):
3899 self._pr = pull_request
3901 self._pr = pull_request
3900 self._org_state = back_state or pull_request.pull_request_state
3902 self._org_state = back_state or pull_request.pull_request_state
3901 self._pr_state = pr_state
3903 self._pr_state = pr_state
3902 self._current_state = None
3904 self._current_state = None
3903
3905
3904 def __enter__(self):
3906 def __enter__(self):
3905 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3907 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
3906 self._pr, self._pr_state)
3908 self._pr, self._pr_state)
3907 self.set_pr_state(self._pr_state)
3909 self.set_pr_state(self._pr_state)
3908 return self
3910 return self
3909
3911
3910 def __exit__(self, exc_type, exc_val, exc_tb):
3912 def __exit__(self, exc_type, exc_val, exc_tb):
3911 if exc_val is not None:
3913 if exc_val is not None:
3912 log.error(traceback.format_exc(exc_tb))
3914 log.error(traceback.format_exc(exc_tb))
3913 return None
3915 return None
3914
3916
3915 self.set_pr_state(self._org_state)
3917 self.set_pr_state(self._org_state)
3916 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3918 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
3917 self._pr, self._org_state)
3919 self._pr, self._org_state)
3918
3920
3919 @property
3921 @property
3920 def state(self):
3922 def state(self):
3921 return self._current_state
3923 return self._current_state
3922
3924
3923 def set_pr_state(self, pr_state):
3925 def set_pr_state(self, pr_state):
3924 try:
3926 try:
3925 self._pr.pull_request_state = pr_state
3927 self._pr.pull_request_state = pr_state
3926 Session().add(self._pr)
3928 Session().add(self._pr)
3927 Session().commit()
3929 Session().commit()
3928 self._current_state = pr_state
3930 self._current_state = pr_state
3929 except Exception:
3931 except Exception:
3930 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3932 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3931 raise
3933 raise
3932
3934
3933
3935
3934 class _PullRequestBase(BaseModel):
3936 class _PullRequestBase(BaseModel):
3935 """
3937 """
3936 Common attributes of pull request and version entries.
3938 Common attributes of pull request and version entries.
3937 """
3939 """
3938
3940
3939 # .status values
3941 # .status values
3940 STATUS_NEW = u'new'
3942 STATUS_NEW = u'new'
3941 STATUS_OPEN = u'open'
3943 STATUS_OPEN = u'open'
3942 STATUS_CLOSED = u'closed'
3944 STATUS_CLOSED = u'closed'
3943
3945
3944 # available states
3946 # available states
3945 STATE_CREATING = u'creating'
3947 STATE_CREATING = u'creating'
3946 STATE_UPDATING = u'updating'
3948 STATE_UPDATING = u'updating'
3947 STATE_MERGING = u'merging'
3949 STATE_MERGING = u'merging'
3948 STATE_CREATED = u'created'
3950 STATE_CREATED = u'created'
3949
3951
3950 title = Column('title', Unicode(255), nullable=True)
3952 title = Column('title', Unicode(255), nullable=True)
3951 description = Column(
3953 description = Column(
3952 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3954 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3953 nullable=True)
3955 nullable=True)
3954 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3956 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3955
3957
3956 # new/open/closed status of pull request (not approve/reject/etc)
3958 # new/open/closed status of pull request (not approve/reject/etc)
3957 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3959 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3958 created_on = Column(
3960 created_on = Column(
3959 'created_on', DateTime(timezone=False), nullable=False,
3961 'created_on', DateTime(timezone=False), nullable=False,
3960 default=datetime.datetime.now)
3962 default=datetime.datetime.now)
3961 updated_on = Column(
3963 updated_on = Column(
3962 'updated_on', DateTime(timezone=False), nullable=False,
3964 'updated_on', DateTime(timezone=False), nullable=False,
3963 default=datetime.datetime.now)
3965 default=datetime.datetime.now)
3964
3966
3965 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3967 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3966
3968
3967 @declared_attr
3969 @declared_attr
3968 def user_id(cls):
3970 def user_id(cls):
3969 return Column(
3971 return Column(
3970 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3972 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3971 unique=None)
3973 unique=None)
3972
3974
3973 # 500 revisions max
3975 # 500 revisions max
3974 _revisions = Column(
3976 _revisions = Column(
3975 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3977 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3976
3978
3977 @declared_attr
3979 @declared_attr
3978 def source_repo_id(cls):
3980 def source_repo_id(cls):
3979 # TODO: dan: rename column to source_repo_id
3981 # TODO: dan: rename column to source_repo_id
3980 return Column(
3982 return Column(
3981 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3983 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3982 nullable=False)
3984 nullable=False)
3983
3985
3984 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3986 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3985
3987
3986 @hybrid_property
3988 @hybrid_property
3987 def source_ref(self):
3989 def source_ref(self):
3988 return self._source_ref
3990 return self._source_ref
3989
3991
3990 @source_ref.setter
3992 @source_ref.setter
3991 def source_ref(self, val):
3993 def source_ref(self, val):
3992 parts = (val or '').split(':')
3994 parts = (val or '').split(':')
3993 if len(parts) != 3:
3995 if len(parts) != 3:
3994 raise ValueError(
3996 raise ValueError(
3995 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3997 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3996 self._source_ref = safe_unicode(val)
3998 self._source_ref = safe_unicode(val)
3997
3999
3998 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4000 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3999
4001
4000 @hybrid_property
4002 @hybrid_property
4001 def target_ref(self):
4003 def target_ref(self):
4002 return self._target_ref
4004 return self._target_ref
4003
4005
4004 @target_ref.setter
4006 @target_ref.setter
4005 def target_ref(self, val):
4007 def target_ref(self, val):
4006 parts = (val or '').split(':')
4008 parts = (val or '').split(':')
4007 if len(parts) != 3:
4009 if len(parts) != 3:
4008 raise ValueError(
4010 raise ValueError(
4009 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4011 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4010 self._target_ref = safe_unicode(val)
4012 self._target_ref = safe_unicode(val)
4011
4013
4012 @declared_attr
4014 @declared_attr
4013 def target_repo_id(cls):
4015 def target_repo_id(cls):
4014 # TODO: dan: rename column to target_repo_id
4016 # TODO: dan: rename column to target_repo_id
4015 return Column(
4017 return Column(
4016 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4018 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4017 nullable=False)
4019 nullable=False)
4018
4020
4019 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4021 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4020
4022
4021 # TODO: dan: rename column to last_merge_source_rev
4023 # TODO: dan: rename column to last_merge_source_rev
4022 _last_merge_source_rev = Column(
4024 _last_merge_source_rev = Column(
4023 'last_merge_org_rev', String(40), nullable=True)
4025 'last_merge_org_rev', String(40), nullable=True)
4024 # TODO: dan: rename column to last_merge_target_rev
4026 # TODO: dan: rename column to last_merge_target_rev
4025 _last_merge_target_rev = Column(
4027 _last_merge_target_rev = Column(
4026 'last_merge_other_rev', String(40), nullable=True)
4028 'last_merge_other_rev', String(40), nullable=True)
4027 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4029 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4028 last_merge_metadata = Column(
4030 last_merge_metadata = Column(
4029 'last_merge_metadata', MutationObj.as_mutable(
4031 'last_merge_metadata', MutationObj.as_mutable(
4030 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4032 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4031
4033
4032 merge_rev = Column('merge_rev', String(40), nullable=True)
4034 merge_rev = Column('merge_rev', String(40), nullable=True)
4033
4035
4034 reviewer_data = Column(
4036 reviewer_data = Column(
4035 'reviewer_data_json', MutationObj.as_mutable(
4037 'reviewer_data_json', MutationObj.as_mutable(
4036 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4038 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4037
4039
4038 @property
4040 @property
4039 def reviewer_data_json(self):
4041 def reviewer_data_json(self):
4040 return json.dumps(self.reviewer_data)
4042 return json.dumps(self.reviewer_data)
4041
4043
4042 @property
4044 @property
4043 def work_in_progress(self):
4045 def work_in_progress(self):
4044 """checks if pull request is work in progress by checking the title"""
4046 """checks if pull request is work in progress by checking the title"""
4045 title = self.title.upper()
4047 title = self.title.upper()
4046 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4048 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4047 return True
4049 return True
4048 return False
4050 return False
4049
4051
4050 @hybrid_property
4052 @hybrid_property
4051 def description_safe(self):
4053 def description_safe(self):
4052 from rhodecode.lib import helpers as h
4054 from rhodecode.lib import helpers as h
4053 return h.escape(self.description)
4055 return h.escape(self.description)
4054
4056
4055 @hybrid_property
4057 @hybrid_property
4056 def revisions(self):
4058 def revisions(self):
4057 return self._revisions.split(':') if self._revisions else []
4059 return self._revisions.split(':') if self._revisions else []
4058
4060
4059 @revisions.setter
4061 @revisions.setter
4060 def revisions(self, val):
4062 def revisions(self, val):
4061 self._revisions = u':'.join(val)
4063 self._revisions = u':'.join(val)
4062
4064
4063 @hybrid_property
4065 @hybrid_property
4064 def last_merge_status(self):
4066 def last_merge_status(self):
4065 return safe_int(self._last_merge_status)
4067 return safe_int(self._last_merge_status)
4066
4068
4067 @last_merge_status.setter
4069 @last_merge_status.setter
4068 def last_merge_status(self, val):
4070 def last_merge_status(self, val):
4069 self._last_merge_status = val
4071 self._last_merge_status = val
4070
4072
4071 @declared_attr
4073 @declared_attr
4072 def author(cls):
4074 def author(cls):
4073 return relationship('User', lazy='joined')
4075 return relationship('User', lazy='joined')
4074
4076
4075 @declared_attr
4077 @declared_attr
4076 def source_repo(cls):
4078 def source_repo(cls):
4077 return relationship(
4079 return relationship(
4078 'Repository',
4080 'Repository',
4079 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4081 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4080
4082
4081 @property
4083 @property
4082 def source_ref_parts(self):
4084 def source_ref_parts(self):
4083 return self.unicode_to_reference(self.source_ref)
4085 return self.unicode_to_reference(self.source_ref)
4084
4086
4085 @declared_attr
4087 @declared_attr
4086 def target_repo(cls):
4088 def target_repo(cls):
4087 return relationship(
4089 return relationship(
4088 'Repository',
4090 'Repository',
4089 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4091 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4090
4092
4091 @property
4093 @property
4092 def target_ref_parts(self):
4094 def target_ref_parts(self):
4093 return self.unicode_to_reference(self.target_ref)
4095 return self.unicode_to_reference(self.target_ref)
4094
4096
4095 @property
4097 @property
4096 def shadow_merge_ref(self):
4098 def shadow_merge_ref(self):
4097 return self.unicode_to_reference(self._shadow_merge_ref)
4099 return self.unicode_to_reference(self._shadow_merge_ref)
4098
4100
4099 @shadow_merge_ref.setter
4101 @shadow_merge_ref.setter
4100 def shadow_merge_ref(self, ref):
4102 def shadow_merge_ref(self, ref):
4101 self._shadow_merge_ref = self.reference_to_unicode(ref)
4103 self._shadow_merge_ref = self.reference_to_unicode(ref)
4102
4104
4103 @staticmethod
4105 @staticmethod
4104 def unicode_to_reference(raw):
4106 def unicode_to_reference(raw):
4105 """
4107 """
4106 Convert a unicode (or string) to a reference object.
4108 Convert a unicode (or string) to a reference object.
4107 If unicode evaluates to False it returns None.
4109 If unicode evaluates to False it returns None.
4108 """
4110 """
4109 if raw:
4111 if raw:
4110 refs = raw.split(':')
4112 refs = raw.split(':')
4111 return Reference(*refs)
4113 return Reference(*refs)
4112 else:
4114 else:
4113 return None
4115 return None
4114
4116
4115 @staticmethod
4117 @staticmethod
4116 def reference_to_unicode(ref):
4118 def reference_to_unicode(ref):
4117 """
4119 """
4118 Convert a reference object to unicode.
4120 Convert a reference object to unicode.
4119 If reference is None it returns None.
4121 If reference is None it returns None.
4120 """
4122 """
4121 if ref:
4123 if ref:
4122 return u':'.join(ref)
4124 return u':'.join(ref)
4123 else:
4125 else:
4124 return None
4126 return None
4125
4127
4126 def get_api_data(self, with_merge_state=True):
4128 def get_api_data(self, with_merge_state=True):
4127 from rhodecode.model.pull_request import PullRequestModel
4129 from rhodecode.model.pull_request import PullRequestModel
4128
4130
4129 pull_request = self
4131 pull_request = self
4130 if with_merge_state:
4132 if with_merge_state:
4131 merge_response, merge_status, msg = \
4133 merge_response, merge_status, msg = \
4132 PullRequestModel().merge_status(pull_request)
4134 PullRequestModel().merge_status(pull_request)
4133 merge_state = {
4135 merge_state = {
4134 'status': merge_status,
4136 'status': merge_status,
4135 'message': safe_unicode(msg),
4137 'message': safe_unicode(msg),
4136 }
4138 }
4137 else:
4139 else:
4138 merge_state = {'status': 'not_available',
4140 merge_state = {'status': 'not_available',
4139 'message': 'not_available'}
4141 'message': 'not_available'}
4140
4142
4141 merge_data = {
4143 merge_data = {
4142 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4144 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4143 'reference': (
4145 'reference': (
4144 pull_request.shadow_merge_ref._asdict()
4146 pull_request.shadow_merge_ref._asdict()
4145 if pull_request.shadow_merge_ref else None),
4147 if pull_request.shadow_merge_ref else None),
4146 }
4148 }
4147
4149
4148 data = {
4150 data = {
4149 'pull_request_id': pull_request.pull_request_id,
4151 'pull_request_id': pull_request.pull_request_id,
4150 'url': PullRequestModel().get_url(pull_request),
4152 'url': PullRequestModel().get_url(pull_request),
4151 'title': pull_request.title,
4153 'title': pull_request.title,
4152 'description': pull_request.description,
4154 'description': pull_request.description,
4153 'status': pull_request.status,
4155 'status': pull_request.status,
4154 'state': pull_request.pull_request_state,
4156 'state': pull_request.pull_request_state,
4155 'created_on': pull_request.created_on,
4157 'created_on': pull_request.created_on,
4156 'updated_on': pull_request.updated_on,
4158 'updated_on': pull_request.updated_on,
4157 'commit_ids': pull_request.revisions,
4159 'commit_ids': pull_request.revisions,
4158 'review_status': pull_request.calculated_review_status(),
4160 'review_status': pull_request.calculated_review_status(),
4159 'mergeable': merge_state,
4161 'mergeable': merge_state,
4160 'source': {
4162 'source': {
4161 'clone_url': pull_request.source_repo.clone_url(),
4163 'clone_url': pull_request.source_repo.clone_url(),
4162 'repository': pull_request.source_repo.repo_name,
4164 'repository': pull_request.source_repo.repo_name,
4163 'reference': {
4165 'reference': {
4164 'name': pull_request.source_ref_parts.name,
4166 'name': pull_request.source_ref_parts.name,
4165 'type': pull_request.source_ref_parts.type,
4167 'type': pull_request.source_ref_parts.type,
4166 'commit_id': pull_request.source_ref_parts.commit_id,
4168 'commit_id': pull_request.source_ref_parts.commit_id,
4167 },
4169 },
4168 },
4170 },
4169 'target': {
4171 'target': {
4170 'clone_url': pull_request.target_repo.clone_url(),
4172 'clone_url': pull_request.target_repo.clone_url(),
4171 'repository': pull_request.target_repo.repo_name,
4173 'repository': pull_request.target_repo.repo_name,
4172 'reference': {
4174 'reference': {
4173 'name': pull_request.target_ref_parts.name,
4175 'name': pull_request.target_ref_parts.name,
4174 'type': pull_request.target_ref_parts.type,
4176 'type': pull_request.target_ref_parts.type,
4175 'commit_id': pull_request.target_ref_parts.commit_id,
4177 'commit_id': pull_request.target_ref_parts.commit_id,
4176 },
4178 },
4177 },
4179 },
4178 'merge': merge_data,
4180 'merge': merge_data,
4179 'author': pull_request.author.get_api_data(include_secrets=False,
4181 'author': pull_request.author.get_api_data(include_secrets=False,
4180 details='basic'),
4182 details='basic'),
4181 'reviewers': [
4183 'reviewers': [
4182 {
4184 {
4183 'user': reviewer.get_api_data(include_secrets=False,
4185 'user': reviewer.get_api_data(include_secrets=False,
4184 details='basic'),
4186 details='basic'),
4185 'reasons': reasons,
4187 'reasons': reasons,
4186 'review_status': st[0][1].status if st else 'not_reviewed',
4188 'review_status': st[0][1].status if st else 'not_reviewed',
4187 }
4189 }
4188 for obj, reviewer, reasons, mandatory, st in
4190 for obj, reviewer, reasons, mandatory, st in
4189 pull_request.reviewers_statuses()
4191 pull_request.reviewers_statuses()
4190 ]
4192 ]
4191 }
4193 }
4192
4194
4193 return data
4195 return data
4194
4196
4195 def set_state(self, pull_request_state, final_state=None):
4197 def set_state(self, pull_request_state, final_state=None):
4196 """
4198 """
4197 # goes from initial state to updating to initial state.
4199 # goes from initial state to updating to initial state.
4198 # initial state can be changed by specifying back_state=
4200 # initial state can be changed by specifying back_state=
4199 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4201 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4200 pull_request.merge()
4202 pull_request.merge()
4201
4203
4202 :param pull_request_state:
4204 :param pull_request_state:
4203 :param final_state:
4205 :param final_state:
4204
4206
4205 """
4207 """
4206
4208
4207 return _SetState(self, pull_request_state, back_state=final_state)
4209 return _SetState(self, pull_request_state, back_state=final_state)
4208
4210
4209
4211
4210 class PullRequest(Base, _PullRequestBase):
4212 class PullRequest(Base, _PullRequestBase):
4211 __tablename__ = 'pull_requests'
4213 __tablename__ = 'pull_requests'
4212 __table_args__ = (
4214 __table_args__ = (
4213 base_table_args,
4215 base_table_args,
4214 )
4216 )
4215
4217
4216 pull_request_id = Column(
4218 pull_request_id = Column(
4217 'pull_request_id', Integer(), nullable=False, primary_key=True)
4219 'pull_request_id', Integer(), nullable=False, primary_key=True)
4218
4220
4219 def __repr__(self):
4221 def __repr__(self):
4220 if self.pull_request_id:
4222 if self.pull_request_id:
4221 return '<DB:PullRequest #%s>' % self.pull_request_id
4223 return '<DB:PullRequest #%s>' % self.pull_request_id
4222 else:
4224 else:
4223 return '<DB:PullRequest at %#x>' % id(self)
4225 return '<DB:PullRequest at %#x>' % id(self)
4224
4226
4225 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4227 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4226 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4228 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4227 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4229 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4228 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4230 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4229 lazy='dynamic')
4231 lazy='dynamic')
4230
4232
4231 @classmethod
4233 @classmethod
4232 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4234 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4233 internal_methods=None):
4235 internal_methods=None):
4234
4236
4235 class PullRequestDisplay(object):
4237 class PullRequestDisplay(object):
4236 """
4238 """
4237 Special object wrapper for showing PullRequest data via Versions
4239 Special object wrapper for showing PullRequest data via Versions
4238 It mimics PR object as close as possible. This is read only object
4240 It mimics PR object as close as possible. This is read only object
4239 just for display
4241 just for display
4240 """
4242 """
4241
4243
4242 def __init__(self, attrs, internal=None):
4244 def __init__(self, attrs, internal=None):
4243 self.attrs = attrs
4245 self.attrs = attrs
4244 # internal have priority over the given ones via attrs
4246 # internal have priority over the given ones via attrs
4245 self.internal = internal or ['versions']
4247 self.internal = internal or ['versions']
4246
4248
4247 def __getattr__(self, item):
4249 def __getattr__(self, item):
4248 if item in self.internal:
4250 if item in self.internal:
4249 return getattr(self, item)
4251 return getattr(self, item)
4250 try:
4252 try:
4251 return self.attrs[item]
4253 return self.attrs[item]
4252 except KeyError:
4254 except KeyError:
4253 raise AttributeError(
4255 raise AttributeError(
4254 '%s object has no attribute %s' % (self, item))
4256 '%s object has no attribute %s' % (self, item))
4255
4257
4256 def __repr__(self):
4258 def __repr__(self):
4257 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4259 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4258
4260
4259 def versions(self):
4261 def versions(self):
4260 return pull_request_obj.versions.order_by(
4262 return pull_request_obj.versions.order_by(
4261 PullRequestVersion.pull_request_version_id).all()
4263 PullRequestVersion.pull_request_version_id).all()
4262
4264
4263 def is_closed(self):
4265 def is_closed(self):
4264 return pull_request_obj.is_closed()
4266 return pull_request_obj.is_closed()
4265
4267
4266 def is_state_changing(self):
4268 def is_state_changing(self):
4267 return pull_request_obj.is_state_changing()
4269 return pull_request_obj.is_state_changing()
4268
4270
4269 @property
4271 @property
4270 def pull_request_version_id(self):
4272 def pull_request_version_id(self):
4271 return getattr(pull_request_obj, 'pull_request_version_id', None)
4273 return getattr(pull_request_obj, 'pull_request_version_id', None)
4272
4274
4273 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4275 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4274
4276
4275 attrs.author = StrictAttributeDict(
4277 attrs.author = StrictAttributeDict(
4276 pull_request_obj.author.get_api_data())
4278 pull_request_obj.author.get_api_data())
4277 if pull_request_obj.target_repo:
4279 if pull_request_obj.target_repo:
4278 attrs.target_repo = StrictAttributeDict(
4280 attrs.target_repo = StrictAttributeDict(
4279 pull_request_obj.target_repo.get_api_data())
4281 pull_request_obj.target_repo.get_api_data())
4280 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4282 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4281
4283
4282 if pull_request_obj.source_repo:
4284 if pull_request_obj.source_repo:
4283 attrs.source_repo = StrictAttributeDict(
4285 attrs.source_repo = StrictAttributeDict(
4284 pull_request_obj.source_repo.get_api_data())
4286 pull_request_obj.source_repo.get_api_data())
4285 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4287 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4286
4288
4287 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4289 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4288 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4290 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4289 attrs.revisions = pull_request_obj.revisions
4291 attrs.revisions = pull_request_obj.revisions
4290
4292
4291 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4293 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4292 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4294 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4293 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4295 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4294
4296
4295 return PullRequestDisplay(attrs, internal=internal_methods)
4297 return PullRequestDisplay(attrs, internal=internal_methods)
4296
4298
4297 def is_closed(self):
4299 def is_closed(self):
4298 return self.status == self.STATUS_CLOSED
4300 return self.status == self.STATUS_CLOSED
4299
4301
4300 def is_state_changing(self):
4302 def is_state_changing(self):
4301 return self.pull_request_state != PullRequest.STATE_CREATED
4303 return self.pull_request_state != PullRequest.STATE_CREATED
4302
4304
4303 def __json__(self):
4305 def __json__(self):
4304 return {
4306 return {
4305 'revisions': self.revisions,
4307 'revisions': self.revisions,
4306 'versions': self.versions_count
4308 'versions': self.versions_count
4307 }
4309 }
4308
4310
4309 def calculated_review_status(self):
4311 def calculated_review_status(self):
4310 from rhodecode.model.changeset_status import ChangesetStatusModel
4312 from rhodecode.model.changeset_status import ChangesetStatusModel
4311 return ChangesetStatusModel().calculated_review_status(self)
4313 return ChangesetStatusModel().calculated_review_status(self)
4312
4314
4313 def reviewers_statuses(self):
4315 def reviewers_statuses(self):
4314 from rhodecode.model.changeset_status import ChangesetStatusModel
4316 from rhodecode.model.changeset_status import ChangesetStatusModel
4315 return ChangesetStatusModel().reviewers_statuses(self)
4317 return ChangesetStatusModel().reviewers_statuses(self)
4316
4318
4317 @property
4319 @property
4318 def workspace_id(self):
4320 def workspace_id(self):
4319 from rhodecode.model.pull_request import PullRequestModel
4321 from rhodecode.model.pull_request import PullRequestModel
4320 return PullRequestModel()._workspace_id(self)
4322 return PullRequestModel()._workspace_id(self)
4321
4323
4322 def get_shadow_repo(self):
4324 def get_shadow_repo(self):
4323 workspace_id = self.workspace_id
4325 workspace_id = self.workspace_id
4324 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4326 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4325 if os.path.isdir(shadow_repository_path):
4327 if os.path.isdir(shadow_repository_path):
4326 vcs_obj = self.target_repo.scm_instance()
4328 vcs_obj = self.target_repo.scm_instance()
4327 return vcs_obj.get_shadow_instance(shadow_repository_path)
4329 return vcs_obj.get_shadow_instance(shadow_repository_path)
4328
4330
4329 @property
4331 @property
4330 def versions_count(self):
4332 def versions_count(self):
4331 """
4333 """
4332 return number of versions this PR have, e.g a PR that once been
4334 return number of versions this PR have, e.g a PR that once been
4333 updated will have 2 versions
4335 updated will have 2 versions
4334 """
4336 """
4335 return self.versions.count() + 1
4337 return self.versions.count() + 1
4336
4338
4337
4339
4338 class PullRequestVersion(Base, _PullRequestBase):
4340 class PullRequestVersion(Base, _PullRequestBase):
4339 __tablename__ = 'pull_request_versions'
4341 __tablename__ = 'pull_request_versions'
4340 __table_args__ = (
4342 __table_args__ = (
4341 base_table_args,
4343 base_table_args,
4342 )
4344 )
4343
4345
4344 pull_request_version_id = Column(
4346 pull_request_version_id = Column(
4345 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4347 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4346 pull_request_id = Column(
4348 pull_request_id = Column(
4347 'pull_request_id', Integer(),
4349 'pull_request_id', Integer(),
4348 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4350 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4349 pull_request = relationship('PullRequest')
4351 pull_request = relationship('PullRequest')
4350
4352
4351 def __repr__(self):
4353 def __repr__(self):
4352 if self.pull_request_version_id:
4354 if self.pull_request_version_id:
4353 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4355 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4354 else:
4356 else:
4355 return '<DB:PullRequestVersion at %#x>' % id(self)
4357 return '<DB:PullRequestVersion at %#x>' % id(self)
4356
4358
4357 @property
4359 @property
4358 def reviewers(self):
4360 def reviewers(self):
4359 return self.pull_request.reviewers
4361 return self.pull_request.reviewers
4360
4362
4361 @property
4363 @property
4362 def versions(self):
4364 def versions(self):
4363 return self.pull_request.versions
4365 return self.pull_request.versions
4364
4366
4365 def is_closed(self):
4367 def is_closed(self):
4366 # calculate from original
4368 # calculate from original
4367 return self.pull_request.status == self.STATUS_CLOSED
4369 return self.pull_request.status == self.STATUS_CLOSED
4368
4370
4369 def is_state_changing(self):
4371 def is_state_changing(self):
4370 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4372 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4371
4373
4372 def calculated_review_status(self):
4374 def calculated_review_status(self):
4373 return self.pull_request.calculated_review_status()
4375 return self.pull_request.calculated_review_status()
4374
4376
4375 def reviewers_statuses(self):
4377 def reviewers_statuses(self):
4376 return self.pull_request.reviewers_statuses()
4378 return self.pull_request.reviewers_statuses()
4377
4379
4378
4380
4379 class PullRequestReviewers(Base, BaseModel):
4381 class PullRequestReviewers(Base, BaseModel):
4380 __tablename__ = 'pull_request_reviewers'
4382 __tablename__ = 'pull_request_reviewers'
4381 __table_args__ = (
4383 __table_args__ = (
4382 base_table_args,
4384 base_table_args,
4383 )
4385 )
4384
4386
4385 @hybrid_property
4387 @hybrid_property
4386 def reasons(self):
4388 def reasons(self):
4387 if not self._reasons:
4389 if not self._reasons:
4388 return []
4390 return []
4389 return self._reasons
4391 return self._reasons
4390
4392
4391 @reasons.setter
4393 @reasons.setter
4392 def reasons(self, val):
4394 def reasons(self, val):
4393 val = val or []
4395 val = val or []
4394 if any(not isinstance(x, compat.string_types) for x in val):
4396 if any(not isinstance(x, compat.string_types) for x in val):
4395 raise Exception('invalid reasons type, must be list of strings')
4397 raise Exception('invalid reasons type, must be list of strings')
4396 self._reasons = val
4398 self._reasons = val
4397
4399
4398 pull_requests_reviewers_id = Column(
4400 pull_requests_reviewers_id = Column(
4399 'pull_requests_reviewers_id', Integer(), nullable=False,
4401 'pull_requests_reviewers_id', Integer(), nullable=False,
4400 primary_key=True)
4402 primary_key=True)
4401 pull_request_id = Column(
4403 pull_request_id = Column(
4402 "pull_request_id", Integer(),
4404 "pull_request_id", Integer(),
4403 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4405 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4404 user_id = Column(
4406 user_id = Column(
4405 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4407 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4406 _reasons = Column(
4408 _reasons = Column(
4407 'reason', MutationList.as_mutable(
4409 'reason', MutationList.as_mutable(
4408 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4410 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4409
4411
4410 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4412 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4411 user = relationship('User')
4413 user = relationship('User')
4412 pull_request = relationship('PullRequest')
4414 pull_request = relationship('PullRequest')
4413
4415
4414 rule_data = Column(
4416 rule_data = Column(
4415 'rule_data_json',
4417 'rule_data_json',
4416 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4418 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4417
4419
4418 def rule_user_group_data(self):
4420 def rule_user_group_data(self):
4419 """
4421 """
4420 Returns the voting user group rule data for this reviewer
4422 Returns the voting user group rule data for this reviewer
4421 """
4423 """
4422
4424
4423 if self.rule_data and 'vote_rule' in self.rule_data:
4425 if self.rule_data and 'vote_rule' in self.rule_data:
4424 user_group_data = {}
4426 user_group_data = {}
4425 if 'rule_user_group_entry_id' in self.rule_data:
4427 if 'rule_user_group_entry_id' in self.rule_data:
4426 # means a group with voting rules !
4428 # means a group with voting rules !
4427 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4429 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4428 user_group_data['name'] = self.rule_data['rule_name']
4430 user_group_data['name'] = self.rule_data['rule_name']
4429 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4431 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4430
4432
4431 return user_group_data
4433 return user_group_data
4432
4434
4433 def __unicode__(self):
4435 def __unicode__(self):
4434 return u"<%s('id:%s')>" % (self.__class__.__name__,
4436 return u"<%s('id:%s')>" % (self.__class__.__name__,
4435 self.pull_requests_reviewers_id)
4437 self.pull_requests_reviewers_id)
4436
4438
4437
4439
4438 class Notification(Base, BaseModel):
4440 class Notification(Base, BaseModel):
4439 __tablename__ = 'notifications'
4441 __tablename__ = 'notifications'
4440 __table_args__ = (
4442 __table_args__ = (
4441 Index('notification_type_idx', 'type'),
4443 Index('notification_type_idx', 'type'),
4442 base_table_args,
4444 base_table_args,
4443 )
4445 )
4444
4446
4445 TYPE_CHANGESET_COMMENT = u'cs_comment'
4447 TYPE_CHANGESET_COMMENT = u'cs_comment'
4446 TYPE_MESSAGE = u'message'
4448 TYPE_MESSAGE = u'message'
4447 TYPE_MENTION = u'mention'
4449 TYPE_MENTION = u'mention'
4448 TYPE_REGISTRATION = u'registration'
4450 TYPE_REGISTRATION = u'registration'
4449 TYPE_PULL_REQUEST = u'pull_request'
4451 TYPE_PULL_REQUEST = u'pull_request'
4450 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4452 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4451 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4453 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4452
4454
4453 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4455 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4454 subject = Column('subject', Unicode(512), nullable=True)
4456 subject = Column('subject', Unicode(512), nullable=True)
4455 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4457 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4456 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4458 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4457 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4459 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4458 type_ = Column('type', Unicode(255))
4460 type_ = Column('type', Unicode(255))
4459
4461
4460 created_by_user = relationship('User')
4462 created_by_user = relationship('User')
4461 notifications_to_users = relationship('UserNotification', lazy='joined',
4463 notifications_to_users = relationship('UserNotification', lazy='joined',
4462 cascade="all, delete-orphan")
4464 cascade="all, delete-orphan")
4463
4465
4464 @property
4466 @property
4465 def recipients(self):
4467 def recipients(self):
4466 return [x.user for x in UserNotification.query()\
4468 return [x.user for x in UserNotification.query()\
4467 .filter(UserNotification.notification == self)\
4469 .filter(UserNotification.notification == self)\
4468 .order_by(UserNotification.user_id.asc()).all()]
4470 .order_by(UserNotification.user_id.asc()).all()]
4469
4471
4470 @classmethod
4472 @classmethod
4471 def create(cls, created_by, subject, body, recipients, type_=None):
4473 def create(cls, created_by, subject, body, recipients, type_=None):
4472 if type_ is None:
4474 if type_ is None:
4473 type_ = Notification.TYPE_MESSAGE
4475 type_ = Notification.TYPE_MESSAGE
4474
4476
4475 notification = cls()
4477 notification = cls()
4476 notification.created_by_user = created_by
4478 notification.created_by_user = created_by
4477 notification.subject = subject
4479 notification.subject = subject
4478 notification.body = body
4480 notification.body = body
4479 notification.type_ = type_
4481 notification.type_ = type_
4480 notification.created_on = datetime.datetime.now()
4482 notification.created_on = datetime.datetime.now()
4481
4483
4482 # For each recipient link the created notification to his account
4484 # For each recipient link the created notification to his account
4483 for u in recipients:
4485 for u in recipients:
4484 assoc = UserNotification()
4486 assoc = UserNotification()
4485 assoc.user_id = u.user_id
4487 assoc.user_id = u.user_id
4486 assoc.notification = notification
4488 assoc.notification = notification
4487
4489
4488 # if created_by is inside recipients mark his notification
4490 # if created_by is inside recipients mark his notification
4489 # as read
4491 # as read
4490 if u.user_id == created_by.user_id:
4492 if u.user_id == created_by.user_id:
4491 assoc.read = True
4493 assoc.read = True
4492 Session().add(assoc)
4494 Session().add(assoc)
4493
4495
4494 Session().add(notification)
4496 Session().add(notification)
4495
4497
4496 return notification
4498 return notification
4497
4499
4498
4500
4499 class UserNotification(Base, BaseModel):
4501 class UserNotification(Base, BaseModel):
4500 __tablename__ = 'user_to_notification'
4502 __tablename__ = 'user_to_notification'
4501 __table_args__ = (
4503 __table_args__ = (
4502 UniqueConstraint('user_id', 'notification_id'),
4504 UniqueConstraint('user_id', 'notification_id'),
4503 base_table_args
4505 base_table_args
4504 )
4506 )
4505
4507
4506 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4508 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4507 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4509 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4508 read = Column('read', Boolean, default=False)
4510 read = Column('read', Boolean, default=False)
4509 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4511 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4510
4512
4511 user = relationship('User', lazy="joined")
4513 user = relationship('User', lazy="joined")
4512 notification = relationship('Notification', lazy="joined",
4514 notification = relationship('Notification', lazy="joined",
4513 order_by=lambda: Notification.created_on.desc(),)
4515 order_by=lambda: Notification.created_on.desc(),)
4514
4516
4515 def mark_as_read(self):
4517 def mark_as_read(self):
4516 self.read = True
4518 self.read = True
4517 Session().add(self)
4519 Session().add(self)
4518
4520
4519
4521
4520 class UserNotice(Base, BaseModel):
4522 class UserNotice(Base, BaseModel):
4521 __tablename__ = 'user_notices'
4523 __tablename__ = 'user_notices'
4522 __table_args__ = (
4524 __table_args__ = (
4523 base_table_args
4525 base_table_args
4524 )
4526 )
4525
4527
4526 NOTIFICATION_TYPE_MESSAGE = 'message'
4528 NOTIFICATION_TYPE_MESSAGE = 'message'
4527 NOTIFICATION_TYPE_NOTICE = 'notice'
4529 NOTIFICATION_TYPE_NOTICE = 'notice'
4528
4530
4529 NOTIFICATION_LEVEL_INFO = 'info'
4531 NOTIFICATION_LEVEL_INFO = 'info'
4530 NOTIFICATION_LEVEL_WARNING = 'warning'
4532 NOTIFICATION_LEVEL_WARNING = 'warning'
4531 NOTIFICATION_LEVEL_ERROR = 'error'
4533 NOTIFICATION_LEVEL_ERROR = 'error'
4532
4534
4533 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4535 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4534
4536
4535 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4537 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4536 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4538 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4537
4539
4538 notice_read = Column('notice_read', Boolean, default=False)
4540 notice_read = Column('notice_read', Boolean, default=False)
4539
4541
4540 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4542 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4541 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4543 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4542
4544
4543 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4545 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4544 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4546 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4545
4547
4546 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4548 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4547 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4549 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4548
4550
4549 @classmethod
4551 @classmethod
4550 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4552 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4551
4553
4552 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4554 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4553 cls.NOTIFICATION_LEVEL_WARNING,
4555 cls.NOTIFICATION_LEVEL_WARNING,
4554 cls.NOTIFICATION_LEVEL_INFO]:
4556 cls.NOTIFICATION_LEVEL_INFO]:
4555 return
4557 return
4556
4558
4557 from rhodecode.model.user import UserModel
4559 from rhodecode.model.user import UserModel
4558 user = UserModel().get_user(user)
4560 user = UserModel().get_user(user)
4559
4561
4560 new_notice = UserNotice()
4562 new_notice = UserNotice()
4561 if not allow_duplicate:
4563 if not allow_duplicate:
4562 existing_msg = UserNotice().query() \
4564 existing_msg = UserNotice().query() \
4563 .filter(UserNotice.user == user) \
4565 .filter(UserNotice.user == user) \
4564 .filter(UserNotice.notice_body == body) \
4566 .filter(UserNotice.notice_body == body) \
4565 .filter(UserNotice.notice_read == false()) \
4567 .filter(UserNotice.notice_read == false()) \
4566 .scalar()
4568 .scalar()
4567 if existing_msg:
4569 if existing_msg:
4568 log.warning('Ignoring duplicate notice for user %s', user)
4570 log.warning('Ignoring duplicate notice for user %s', user)
4569 return
4571 return
4570
4572
4571 new_notice.user = user
4573 new_notice.user = user
4572 new_notice.notice_subject = subject
4574 new_notice.notice_subject = subject
4573 new_notice.notice_body = body
4575 new_notice.notice_body = body
4574 new_notice.notification_level = notice_level
4576 new_notice.notification_level = notice_level
4575 Session().add(new_notice)
4577 Session().add(new_notice)
4576 Session().commit()
4578 Session().commit()
4577
4579
4578
4580
4579 class Gist(Base, BaseModel):
4581 class Gist(Base, BaseModel):
4580 __tablename__ = 'gists'
4582 __tablename__ = 'gists'
4581 __table_args__ = (
4583 __table_args__ = (
4582 Index('g_gist_access_id_idx', 'gist_access_id'),
4584 Index('g_gist_access_id_idx', 'gist_access_id'),
4583 Index('g_created_on_idx', 'created_on'),
4585 Index('g_created_on_idx', 'created_on'),
4584 base_table_args
4586 base_table_args
4585 )
4587 )
4586
4588
4587 GIST_PUBLIC = u'public'
4589 GIST_PUBLIC = u'public'
4588 GIST_PRIVATE = u'private'
4590 GIST_PRIVATE = u'private'
4589 DEFAULT_FILENAME = u'gistfile1.txt'
4591 DEFAULT_FILENAME = u'gistfile1.txt'
4590
4592
4591 ACL_LEVEL_PUBLIC = u'acl_public'
4593 ACL_LEVEL_PUBLIC = u'acl_public'
4592 ACL_LEVEL_PRIVATE = u'acl_private'
4594 ACL_LEVEL_PRIVATE = u'acl_private'
4593
4595
4594 gist_id = Column('gist_id', Integer(), primary_key=True)
4596 gist_id = Column('gist_id', Integer(), primary_key=True)
4595 gist_access_id = Column('gist_access_id', Unicode(250))
4597 gist_access_id = Column('gist_access_id', Unicode(250))
4596 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4598 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4597 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4599 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4598 gist_expires = Column('gist_expires', Float(53), nullable=False)
4600 gist_expires = Column('gist_expires', Float(53), nullable=False)
4599 gist_type = Column('gist_type', Unicode(128), nullable=False)
4601 gist_type = Column('gist_type', Unicode(128), nullable=False)
4600 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4602 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4601 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4603 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4602 acl_level = Column('acl_level', Unicode(128), nullable=True)
4604 acl_level = Column('acl_level', Unicode(128), nullable=True)
4603
4605
4604 owner = relationship('User')
4606 owner = relationship('User')
4605
4607
4606 def __repr__(self):
4608 def __repr__(self):
4607 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4609 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4608
4610
4609 @hybrid_property
4611 @hybrid_property
4610 def description_safe(self):
4612 def description_safe(self):
4611 from rhodecode.lib import helpers as h
4613 from rhodecode.lib import helpers as h
4612 return h.escape(self.gist_description)
4614 return h.escape(self.gist_description)
4613
4615
4614 @classmethod
4616 @classmethod
4615 def get_or_404(cls, id_):
4617 def get_or_404(cls, id_):
4616 from pyramid.httpexceptions import HTTPNotFound
4618 from pyramid.httpexceptions import HTTPNotFound
4617
4619
4618 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4620 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4619 if not res:
4621 if not res:
4620 raise HTTPNotFound()
4622 raise HTTPNotFound()
4621 return res
4623 return res
4622
4624
4623 @classmethod
4625 @classmethod
4624 def get_by_access_id(cls, gist_access_id):
4626 def get_by_access_id(cls, gist_access_id):
4625 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4627 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4626
4628
4627 def gist_url(self):
4629 def gist_url(self):
4628 from rhodecode.model.gist import GistModel
4630 from rhodecode.model.gist import GistModel
4629 return GistModel().get_url(self)
4631 return GistModel().get_url(self)
4630
4632
4631 @classmethod
4633 @classmethod
4632 def base_path(cls):
4634 def base_path(cls):
4633 """
4635 """
4634 Returns base path when all gists are stored
4636 Returns base path when all gists are stored
4635
4637
4636 :param cls:
4638 :param cls:
4637 """
4639 """
4638 from rhodecode.model.gist import GIST_STORE_LOC
4640 from rhodecode.model.gist import GIST_STORE_LOC
4639 q = Session().query(RhodeCodeUi)\
4641 q = Session().query(RhodeCodeUi)\
4640 .filter(RhodeCodeUi.ui_key == URL_SEP)
4642 .filter(RhodeCodeUi.ui_key == URL_SEP)
4641 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4643 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4642 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4644 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4643
4645
4644 def get_api_data(self):
4646 def get_api_data(self):
4645 """
4647 """
4646 Common function for generating gist related data for API
4648 Common function for generating gist related data for API
4647 """
4649 """
4648 gist = self
4650 gist = self
4649 data = {
4651 data = {
4650 'gist_id': gist.gist_id,
4652 'gist_id': gist.gist_id,
4651 'type': gist.gist_type,
4653 'type': gist.gist_type,
4652 'access_id': gist.gist_access_id,
4654 'access_id': gist.gist_access_id,
4653 'description': gist.gist_description,
4655 'description': gist.gist_description,
4654 'url': gist.gist_url(),
4656 'url': gist.gist_url(),
4655 'expires': gist.gist_expires,
4657 'expires': gist.gist_expires,
4656 'created_on': gist.created_on,
4658 'created_on': gist.created_on,
4657 'modified_at': gist.modified_at,
4659 'modified_at': gist.modified_at,
4658 'content': None,
4660 'content': None,
4659 'acl_level': gist.acl_level,
4661 'acl_level': gist.acl_level,
4660 }
4662 }
4661 return data
4663 return data
4662
4664
4663 def __json__(self):
4665 def __json__(self):
4664 data = dict(
4666 data = dict(
4665 )
4667 )
4666 data.update(self.get_api_data())
4668 data.update(self.get_api_data())
4667 return data
4669 return data
4668 # SCM functions
4670 # SCM functions
4669
4671
4670 def scm_instance(self, **kwargs):
4672 def scm_instance(self, **kwargs):
4671 """
4673 """
4672 Get an instance of VCS Repository
4674 Get an instance of VCS Repository
4673
4675
4674 :param kwargs:
4676 :param kwargs:
4675 """
4677 """
4676 from rhodecode.model.gist import GistModel
4678 from rhodecode.model.gist import GistModel
4677 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4679 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4678 return get_vcs_instance(
4680 return get_vcs_instance(
4679 repo_path=safe_str(full_repo_path), create=False,
4681 repo_path=safe_str(full_repo_path), create=False,
4680 _vcs_alias=GistModel.vcs_backend)
4682 _vcs_alias=GistModel.vcs_backend)
4681
4683
4682
4684
4683 class ExternalIdentity(Base, BaseModel):
4685 class ExternalIdentity(Base, BaseModel):
4684 __tablename__ = 'external_identities'
4686 __tablename__ = 'external_identities'
4685 __table_args__ = (
4687 __table_args__ = (
4686 Index('local_user_id_idx', 'local_user_id'),
4688 Index('local_user_id_idx', 'local_user_id'),
4687 Index('external_id_idx', 'external_id'),
4689 Index('external_id_idx', 'external_id'),
4688 base_table_args
4690 base_table_args
4689 )
4691 )
4690
4692
4691 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4693 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4692 external_username = Column('external_username', Unicode(1024), default=u'')
4694 external_username = Column('external_username', Unicode(1024), default=u'')
4693 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4695 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4694 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4696 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4695 access_token = Column('access_token', String(1024), default=u'')
4697 access_token = Column('access_token', String(1024), default=u'')
4696 alt_token = Column('alt_token', String(1024), default=u'')
4698 alt_token = Column('alt_token', String(1024), default=u'')
4697 token_secret = Column('token_secret', String(1024), default=u'')
4699 token_secret = Column('token_secret', String(1024), default=u'')
4698
4700
4699 @classmethod
4701 @classmethod
4700 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4702 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4701 """
4703 """
4702 Returns ExternalIdentity instance based on search params
4704 Returns ExternalIdentity instance based on search params
4703
4705
4704 :param external_id:
4706 :param external_id:
4705 :param provider_name:
4707 :param provider_name:
4706 :return: ExternalIdentity
4708 :return: ExternalIdentity
4707 """
4709 """
4708 query = cls.query()
4710 query = cls.query()
4709 query = query.filter(cls.external_id == external_id)
4711 query = query.filter(cls.external_id == external_id)
4710 query = query.filter(cls.provider_name == provider_name)
4712 query = query.filter(cls.provider_name == provider_name)
4711 if local_user_id:
4713 if local_user_id:
4712 query = query.filter(cls.local_user_id == local_user_id)
4714 query = query.filter(cls.local_user_id == local_user_id)
4713 return query.first()
4715 return query.first()
4714
4716
4715 @classmethod
4717 @classmethod
4716 def user_by_external_id_and_provider(cls, external_id, provider_name):
4718 def user_by_external_id_and_provider(cls, external_id, provider_name):
4717 """
4719 """
4718 Returns User instance based on search params
4720 Returns User instance based on search params
4719
4721
4720 :param external_id:
4722 :param external_id:
4721 :param provider_name:
4723 :param provider_name:
4722 :return: User
4724 :return: User
4723 """
4725 """
4724 query = User.query()
4726 query = User.query()
4725 query = query.filter(cls.external_id == external_id)
4727 query = query.filter(cls.external_id == external_id)
4726 query = query.filter(cls.provider_name == provider_name)
4728 query = query.filter(cls.provider_name == provider_name)
4727 query = query.filter(User.user_id == cls.local_user_id)
4729 query = query.filter(User.user_id == cls.local_user_id)
4728 return query.first()
4730 return query.first()
4729
4731
4730 @classmethod
4732 @classmethod
4731 def by_local_user_id(cls, local_user_id):
4733 def by_local_user_id(cls, local_user_id):
4732 """
4734 """
4733 Returns all tokens for user
4735 Returns all tokens for user
4734
4736
4735 :param local_user_id:
4737 :param local_user_id:
4736 :return: ExternalIdentity
4738 :return: ExternalIdentity
4737 """
4739 """
4738 query = cls.query()
4740 query = cls.query()
4739 query = query.filter(cls.local_user_id == local_user_id)
4741 query = query.filter(cls.local_user_id == local_user_id)
4740 return query
4742 return query
4741
4743
4742 @classmethod
4744 @classmethod
4743 def load_provider_plugin(cls, plugin_id):
4745 def load_provider_plugin(cls, plugin_id):
4744 from rhodecode.authentication.base import loadplugin
4746 from rhodecode.authentication.base import loadplugin
4745 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4747 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4746 auth_plugin = loadplugin(_plugin_id)
4748 auth_plugin = loadplugin(_plugin_id)
4747 return auth_plugin
4749 return auth_plugin
4748
4750
4749
4751
4750 class Integration(Base, BaseModel):
4752 class Integration(Base, BaseModel):
4751 __tablename__ = 'integrations'
4753 __tablename__ = 'integrations'
4752 __table_args__ = (
4754 __table_args__ = (
4753 base_table_args
4755 base_table_args
4754 )
4756 )
4755
4757
4756 integration_id = Column('integration_id', Integer(), primary_key=True)
4758 integration_id = Column('integration_id', Integer(), primary_key=True)
4757 integration_type = Column('integration_type', String(255))
4759 integration_type = Column('integration_type', String(255))
4758 enabled = Column('enabled', Boolean(), nullable=False)
4760 enabled = Column('enabled', Boolean(), nullable=False)
4759 name = Column('name', String(255), nullable=False)
4761 name = Column('name', String(255), nullable=False)
4760 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4762 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4761 default=False)
4763 default=False)
4762
4764
4763 settings = Column(
4765 settings = Column(
4764 'settings_json', MutationObj.as_mutable(
4766 'settings_json', MutationObj.as_mutable(
4765 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4767 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4766 repo_id = Column(
4768 repo_id = Column(
4767 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4769 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4768 nullable=True, unique=None, default=None)
4770 nullable=True, unique=None, default=None)
4769 repo = relationship('Repository', lazy='joined')
4771 repo = relationship('Repository', lazy='joined')
4770
4772
4771 repo_group_id = Column(
4773 repo_group_id = Column(
4772 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4774 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4773 nullable=True, unique=None, default=None)
4775 nullable=True, unique=None, default=None)
4774 repo_group = relationship('RepoGroup', lazy='joined')
4776 repo_group = relationship('RepoGroup', lazy='joined')
4775
4777
4776 @property
4778 @property
4777 def scope(self):
4779 def scope(self):
4778 if self.repo:
4780 if self.repo:
4779 return repr(self.repo)
4781 return repr(self.repo)
4780 if self.repo_group:
4782 if self.repo_group:
4781 if self.child_repos_only:
4783 if self.child_repos_only:
4782 return repr(self.repo_group) + ' (child repos only)'
4784 return repr(self.repo_group) + ' (child repos only)'
4783 else:
4785 else:
4784 return repr(self.repo_group) + ' (recursive)'
4786 return repr(self.repo_group) + ' (recursive)'
4785 if self.child_repos_only:
4787 if self.child_repos_only:
4786 return 'root_repos'
4788 return 'root_repos'
4787 return 'global'
4789 return 'global'
4788
4790
4789 def __repr__(self):
4791 def __repr__(self):
4790 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4792 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4791
4793
4792
4794
4793 class RepoReviewRuleUser(Base, BaseModel):
4795 class RepoReviewRuleUser(Base, BaseModel):
4794 __tablename__ = 'repo_review_rules_users'
4796 __tablename__ = 'repo_review_rules_users'
4795 __table_args__ = (
4797 __table_args__ = (
4796 base_table_args
4798 base_table_args
4797 )
4799 )
4798
4800
4799 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4801 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4800 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4802 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4801 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4803 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4802 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4804 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4803 user = relationship('User')
4805 user = relationship('User')
4804
4806
4805 def rule_data(self):
4807 def rule_data(self):
4806 return {
4808 return {
4807 'mandatory': self.mandatory
4809 'mandatory': self.mandatory
4808 }
4810 }
4809
4811
4810
4812
4811 class RepoReviewRuleUserGroup(Base, BaseModel):
4813 class RepoReviewRuleUserGroup(Base, BaseModel):
4812 __tablename__ = 'repo_review_rules_users_groups'
4814 __tablename__ = 'repo_review_rules_users_groups'
4813 __table_args__ = (
4815 __table_args__ = (
4814 base_table_args
4816 base_table_args
4815 )
4817 )
4816
4818
4817 VOTE_RULE_ALL = -1
4819 VOTE_RULE_ALL = -1
4818
4820
4819 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4821 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4820 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4822 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4821 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4823 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4822 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4824 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4823 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4825 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4824 users_group = relationship('UserGroup')
4826 users_group = relationship('UserGroup')
4825
4827
4826 def rule_data(self):
4828 def rule_data(self):
4827 return {
4829 return {
4828 'mandatory': self.mandatory,
4830 'mandatory': self.mandatory,
4829 'vote_rule': self.vote_rule
4831 'vote_rule': self.vote_rule
4830 }
4832 }
4831
4833
4832 @property
4834 @property
4833 def vote_rule_label(self):
4835 def vote_rule_label(self):
4834 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4836 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4835 return 'all must vote'
4837 return 'all must vote'
4836 else:
4838 else:
4837 return 'min. vote {}'.format(self.vote_rule)
4839 return 'min. vote {}'.format(self.vote_rule)
4838
4840
4839
4841
4840 class RepoReviewRule(Base, BaseModel):
4842 class RepoReviewRule(Base, BaseModel):
4841 __tablename__ = 'repo_review_rules'
4843 __tablename__ = 'repo_review_rules'
4842 __table_args__ = (
4844 __table_args__ = (
4843 base_table_args
4845 base_table_args
4844 )
4846 )
4845
4847
4846 repo_review_rule_id = Column(
4848 repo_review_rule_id = Column(
4847 'repo_review_rule_id', Integer(), primary_key=True)
4849 'repo_review_rule_id', Integer(), primary_key=True)
4848 repo_id = Column(
4850 repo_id = Column(
4849 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4851 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4850 repo = relationship('Repository', backref='review_rules')
4852 repo = relationship('Repository', backref='review_rules')
4851
4853
4852 review_rule_name = Column('review_rule_name', String(255))
4854 review_rule_name = Column('review_rule_name', String(255))
4853 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4855 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4854 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4856 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4855 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4857 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4856
4858
4857 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4859 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4858 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4860 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4859 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4861 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4860 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4862 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4861
4863
4862 rule_users = relationship('RepoReviewRuleUser')
4864 rule_users = relationship('RepoReviewRuleUser')
4863 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4865 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4864
4866
4865 def _validate_pattern(self, value):
4867 def _validate_pattern(self, value):
4866 re.compile('^' + glob2re(value) + '$')
4868 re.compile('^' + glob2re(value) + '$')
4867
4869
4868 @hybrid_property
4870 @hybrid_property
4869 def source_branch_pattern(self):
4871 def source_branch_pattern(self):
4870 return self._branch_pattern or '*'
4872 return self._branch_pattern or '*'
4871
4873
4872 @source_branch_pattern.setter
4874 @source_branch_pattern.setter
4873 def source_branch_pattern(self, value):
4875 def source_branch_pattern(self, value):
4874 self._validate_pattern(value)
4876 self._validate_pattern(value)
4875 self._branch_pattern = value or '*'
4877 self._branch_pattern = value or '*'
4876
4878
4877 @hybrid_property
4879 @hybrid_property
4878 def target_branch_pattern(self):
4880 def target_branch_pattern(self):
4879 return self._target_branch_pattern or '*'
4881 return self._target_branch_pattern or '*'
4880
4882
4881 @target_branch_pattern.setter
4883 @target_branch_pattern.setter
4882 def target_branch_pattern(self, value):
4884 def target_branch_pattern(self, value):
4883 self._validate_pattern(value)
4885 self._validate_pattern(value)
4884 self._target_branch_pattern = value or '*'
4886 self._target_branch_pattern = value or '*'
4885
4887
4886 @hybrid_property
4888 @hybrid_property
4887 def file_pattern(self):
4889 def file_pattern(self):
4888 return self._file_pattern or '*'
4890 return self._file_pattern or '*'
4889
4891
4890 @file_pattern.setter
4892 @file_pattern.setter
4891 def file_pattern(self, value):
4893 def file_pattern(self, value):
4892 self._validate_pattern(value)
4894 self._validate_pattern(value)
4893 self._file_pattern = value or '*'
4895 self._file_pattern = value or '*'
4894
4896
4895 def matches(self, source_branch, target_branch, files_changed):
4897 def matches(self, source_branch, target_branch, files_changed):
4896 """
4898 """
4897 Check if this review rule matches a branch/files in a pull request
4899 Check if this review rule matches a branch/files in a pull request
4898
4900
4899 :param source_branch: source branch name for the commit
4901 :param source_branch: source branch name for the commit
4900 :param target_branch: target branch name for the commit
4902 :param target_branch: target branch name for the commit
4901 :param files_changed: list of file paths changed in the pull request
4903 :param files_changed: list of file paths changed in the pull request
4902 """
4904 """
4903
4905
4904 source_branch = source_branch or ''
4906 source_branch = source_branch or ''
4905 target_branch = target_branch or ''
4907 target_branch = target_branch or ''
4906 files_changed = files_changed or []
4908 files_changed = files_changed or []
4907
4909
4908 branch_matches = True
4910 branch_matches = True
4909 if source_branch or target_branch:
4911 if source_branch or target_branch:
4910 if self.source_branch_pattern == '*':
4912 if self.source_branch_pattern == '*':
4911 source_branch_match = True
4913 source_branch_match = True
4912 else:
4914 else:
4913 if self.source_branch_pattern.startswith('re:'):
4915 if self.source_branch_pattern.startswith('re:'):
4914 source_pattern = self.source_branch_pattern[3:]
4916 source_pattern = self.source_branch_pattern[3:]
4915 else:
4917 else:
4916 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4918 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4917 source_branch_regex = re.compile(source_pattern)
4919 source_branch_regex = re.compile(source_pattern)
4918 source_branch_match = bool(source_branch_regex.search(source_branch))
4920 source_branch_match = bool(source_branch_regex.search(source_branch))
4919 if self.target_branch_pattern == '*':
4921 if self.target_branch_pattern == '*':
4920 target_branch_match = True
4922 target_branch_match = True
4921 else:
4923 else:
4922 if self.target_branch_pattern.startswith('re:'):
4924 if self.target_branch_pattern.startswith('re:'):
4923 target_pattern = self.target_branch_pattern[3:]
4925 target_pattern = self.target_branch_pattern[3:]
4924 else:
4926 else:
4925 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4927 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4926 target_branch_regex = re.compile(target_pattern)
4928 target_branch_regex = re.compile(target_pattern)
4927 target_branch_match = bool(target_branch_regex.search(target_branch))
4929 target_branch_match = bool(target_branch_regex.search(target_branch))
4928
4930
4929 branch_matches = source_branch_match and target_branch_match
4931 branch_matches = source_branch_match and target_branch_match
4930
4932
4931 files_matches = True
4933 files_matches = True
4932 if self.file_pattern != '*':
4934 if self.file_pattern != '*':
4933 files_matches = False
4935 files_matches = False
4934 if self.file_pattern.startswith('re:'):
4936 if self.file_pattern.startswith('re:'):
4935 file_pattern = self.file_pattern[3:]
4937 file_pattern = self.file_pattern[3:]
4936 else:
4938 else:
4937 file_pattern = glob2re(self.file_pattern)
4939 file_pattern = glob2re(self.file_pattern)
4938 file_regex = re.compile(file_pattern)
4940 file_regex = re.compile(file_pattern)
4939 for filename in files_changed:
4941 for filename in files_changed:
4940 if file_regex.search(filename):
4942 if file_regex.search(filename):
4941 files_matches = True
4943 files_matches = True
4942 break
4944 break
4943
4945
4944 return branch_matches and files_matches
4946 return branch_matches and files_matches
4945
4947
4946 @property
4948 @property
4947 def review_users(self):
4949 def review_users(self):
4948 """ Returns the users which this rule applies to """
4950 """ Returns the users which this rule applies to """
4949
4951
4950 users = collections.OrderedDict()
4952 users = collections.OrderedDict()
4951
4953
4952 for rule_user in self.rule_users:
4954 for rule_user in self.rule_users:
4953 if rule_user.user.active:
4955 if rule_user.user.active:
4954 if rule_user.user not in users:
4956 if rule_user.user not in users:
4955 users[rule_user.user.username] = {
4957 users[rule_user.user.username] = {
4956 'user': rule_user.user,
4958 'user': rule_user.user,
4957 'source': 'user',
4959 'source': 'user',
4958 'source_data': {},
4960 'source_data': {},
4959 'data': rule_user.rule_data()
4961 'data': rule_user.rule_data()
4960 }
4962 }
4961
4963
4962 for rule_user_group in self.rule_user_groups:
4964 for rule_user_group in self.rule_user_groups:
4963 source_data = {
4965 source_data = {
4964 'user_group_id': rule_user_group.users_group.users_group_id,
4966 'user_group_id': rule_user_group.users_group.users_group_id,
4965 'name': rule_user_group.users_group.users_group_name,
4967 'name': rule_user_group.users_group.users_group_name,
4966 'members': len(rule_user_group.users_group.members)
4968 'members': len(rule_user_group.users_group.members)
4967 }
4969 }
4968 for member in rule_user_group.users_group.members:
4970 for member in rule_user_group.users_group.members:
4969 if member.user.active:
4971 if member.user.active:
4970 key = member.user.username
4972 key = member.user.username
4971 if key in users:
4973 if key in users:
4972 # skip this member as we have him already
4974 # skip this member as we have him already
4973 # this prevents from override the "first" matched
4975 # this prevents from override the "first" matched
4974 # users with duplicates in multiple groups
4976 # users with duplicates in multiple groups
4975 continue
4977 continue
4976
4978
4977 users[key] = {
4979 users[key] = {
4978 'user': member.user,
4980 'user': member.user,
4979 'source': 'user_group',
4981 'source': 'user_group',
4980 'source_data': source_data,
4982 'source_data': source_data,
4981 'data': rule_user_group.rule_data()
4983 'data': rule_user_group.rule_data()
4982 }
4984 }
4983
4985
4984 return users
4986 return users
4985
4987
4986 def user_group_vote_rule(self, user_id):
4988 def user_group_vote_rule(self, user_id):
4987
4989
4988 rules = []
4990 rules = []
4989 if not self.rule_user_groups:
4991 if not self.rule_user_groups:
4990 return rules
4992 return rules
4991
4993
4992 for user_group in self.rule_user_groups:
4994 for user_group in self.rule_user_groups:
4993 user_group_members = [x.user_id for x in user_group.users_group.members]
4995 user_group_members = [x.user_id for x in user_group.users_group.members]
4994 if user_id in user_group_members:
4996 if user_id in user_group_members:
4995 rules.append(user_group)
4997 rules.append(user_group)
4996 return rules
4998 return rules
4997
4999
4998 def __repr__(self):
5000 def __repr__(self):
4999 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5001 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5000 self.repo_review_rule_id, self.repo)
5002 self.repo_review_rule_id, self.repo)
5001
5003
5002
5004
5003 class ScheduleEntry(Base, BaseModel):
5005 class ScheduleEntry(Base, BaseModel):
5004 __tablename__ = 'schedule_entries'
5006 __tablename__ = 'schedule_entries'
5005 __table_args__ = (
5007 __table_args__ = (
5006 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5008 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5007 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5009 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5008 base_table_args,
5010 base_table_args,
5009 )
5011 )
5010
5012
5011 schedule_types = ['crontab', 'timedelta', 'integer']
5013 schedule_types = ['crontab', 'timedelta', 'integer']
5012 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5014 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5013
5015
5014 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5016 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5015 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5017 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5016 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5018 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5017
5019
5018 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5020 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5019 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5021 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5020
5022
5021 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5023 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5022 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5024 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5023
5025
5024 # task
5026 # task
5025 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5027 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5026 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5028 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5027 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5029 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5028 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5030 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5029
5031
5030 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5032 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5031 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5033 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5032
5034
5033 @hybrid_property
5035 @hybrid_property
5034 def schedule_type(self):
5036 def schedule_type(self):
5035 return self._schedule_type
5037 return self._schedule_type
5036
5038
5037 @schedule_type.setter
5039 @schedule_type.setter
5038 def schedule_type(self, val):
5040 def schedule_type(self, val):
5039 if val not in self.schedule_types:
5041 if val not in self.schedule_types:
5040 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5042 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5041 val, self.schedule_type))
5043 val, self.schedule_type))
5042
5044
5043 self._schedule_type = val
5045 self._schedule_type = val
5044
5046
5045 @classmethod
5047 @classmethod
5046 def get_uid(cls, obj):
5048 def get_uid(cls, obj):
5047 args = obj.task_args
5049 args = obj.task_args
5048 kwargs = obj.task_kwargs
5050 kwargs = obj.task_kwargs
5049 if isinstance(args, JsonRaw):
5051 if isinstance(args, JsonRaw):
5050 try:
5052 try:
5051 args = json.loads(args)
5053 args = json.loads(args)
5052 except ValueError:
5054 except ValueError:
5053 args = tuple()
5055 args = tuple()
5054
5056
5055 if isinstance(kwargs, JsonRaw):
5057 if isinstance(kwargs, JsonRaw):
5056 try:
5058 try:
5057 kwargs = json.loads(kwargs)
5059 kwargs = json.loads(kwargs)
5058 except ValueError:
5060 except ValueError:
5059 kwargs = dict()
5061 kwargs = dict()
5060
5062
5061 dot_notation = obj.task_dot_notation
5063 dot_notation = obj.task_dot_notation
5062 val = '.'.join(map(safe_str, [
5064 val = '.'.join(map(safe_str, [
5063 sorted(dot_notation), args, sorted(kwargs.items())]))
5065 sorted(dot_notation), args, sorted(kwargs.items())]))
5064 return hashlib.sha1(val).hexdigest()
5066 return hashlib.sha1(val).hexdigest()
5065
5067
5066 @classmethod
5068 @classmethod
5067 def get_by_schedule_name(cls, schedule_name):
5069 def get_by_schedule_name(cls, schedule_name):
5068 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5070 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5069
5071
5070 @classmethod
5072 @classmethod
5071 def get_by_schedule_id(cls, schedule_id):
5073 def get_by_schedule_id(cls, schedule_id):
5072 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5074 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5073
5075
5074 @property
5076 @property
5075 def task(self):
5077 def task(self):
5076 return self.task_dot_notation
5078 return self.task_dot_notation
5077
5079
5078 @property
5080 @property
5079 def schedule(self):
5081 def schedule(self):
5080 from rhodecode.lib.celerylib.utils import raw_2_schedule
5082 from rhodecode.lib.celerylib.utils import raw_2_schedule
5081 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5083 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5082 return schedule
5084 return schedule
5083
5085
5084 @property
5086 @property
5085 def args(self):
5087 def args(self):
5086 try:
5088 try:
5087 return list(self.task_args or [])
5089 return list(self.task_args or [])
5088 except ValueError:
5090 except ValueError:
5089 return list()
5091 return list()
5090
5092
5091 @property
5093 @property
5092 def kwargs(self):
5094 def kwargs(self):
5093 try:
5095 try:
5094 return dict(self.task_kwargs or {})
5096 return dict(self.task_kwargs or {})
5095 except ValueError:
5097 except ValueError:
5096 return dict()
5098 return dict()
5097
5099
5098 def _as_raw(self, val):
5100 def _as_raw(self, val):
5099 if hasattr(val, 'de_coerce'):
5101 if hasattr(val, 'de_coerce'):
5100 val = val.de_coerce()
5102 val = val.de_coerce()
5101 if val:
5103 if val:
5102 val = json.dumps(val)
5104 val = json.dumps(val)
5103
5105
5104 return val
5106 return val
5105
5107
5106 @property
5108 @property
5107 def schedule_definition_raw(self):
5109 def schedule_definition_raw(self):
5108 return self._as_raw(self.schedule_definition)
5110 return self._as_raw(self.schedule_definition)
5109
5111
5110 @property
5112 @property
5111 def args_raw(self):
5113 def args_raw(self):
5112 return self._as_raw(self.task_args)
5114 return self._as_raw(self.task_args)
5113
5115
5114 @property
5116 @property
5115 def kwargs_raw(self):
5117 def kwargs_raw(self):
5116 return self._as_raw(self.task_kwargs)
5118 return self._as_raw(self.task_kwargs)
5117
5119
5118 def __repr__(self):
5120 def __repr__(self):
5119 return '<DB:ScheduleEntry({}:{})>'.format(
5121 return '<DB:ScheduleEntry({}:{})>'.format(
5120 self.schedule_entry_id, self.schedule_name)
5122 self.schedule_entry_id, self.schedule_name)
5121
5123
5122
5124
5123 @event.listens_for(ScheduleEntry, 'before_update')
5125 @event.listens_for(ScheduleEntry, 'before_update')
5124 def update_task_uid(mapper, connection, target):
5126 def update_task_uid(mapper, connection, target):
5125 target.task_uid = ScheduleEntry.get_uid(target)
5127 target.task_uid = ScheduleEntry.get_uid(target)
5126
5128
5127
5129
5128 @event.listens_for(ScheduleEntry, 'before_insert')
5130 @event.listens_for(ScheduleEntry, 'before_insert')
5129 def set_task_uid(mapper, connection, target):
5131 def set_task_uid(mapper, connection, target):
5130 target.task_uid = ScheduleEntry.get_uid(target)
5132 target.task_uid = ScheduleEntry.get_uid(target)
5131
5133
5132
5134
5133 class _BaseBranchPerms(BaseModel):
5135 class _BaseBranchPerms(BaseModel):
5134 @classmethod
5136 @classmethod
5135 def compute_hash(cls, value):
5137 def compute_hash(cls, value):
5136 return sha1_safe(value)
5138 return sha1_safe(value)
5137
5139
5138 @hybrid_property
5140 @hybrid_property
5139 def branch_pattern(self):
5141 def branch_pattern(self):
5140 return self._branch_pattern or '*'
5142 return self._branch_pattern or '*'
5141
5143
5142 @hybrid_property
5144 @hybrid_property
5143 def branch_hash(self):
5145 def branch_hash(self):
5144 return self._branch_hash
5146 return self._branch_hash
5145
5147
5146 def _validate_glob(self, value):
5148 def _validate_glob(self, value):
5147 re.compile('^' + glob2re(value) + '$')
5149 re.compile('^' + glob2re(value) + '$')
5148
5150
5149 @branch_pattern.setter
5151 @branch_pattern.setter
5150 def branch_pattern(self, value):
5152 def branch_pattern(self, value):
5151 self._validate_glob(value)
5153 self._validate_glob(value)
5152 self._branch_pattern = value or '*'
5154 self._branch_pattern = value or '*'
5153 # set the Hash when setting the branch pattern
5155 # set the Hash when setting the branch pattern
5154 self._branch_hash = self.compute_hash(self._branch_pattern)
5156 self._branch_hash = self.compute_hash(self._branch_pattern)
5155
5157
5156 def matches(self, branch):
5158 def matches(self, branch):
5157 """
5159 """
5158 Check if this the branch matches entry
5160 Check if this the branch matches entry
5159
5161
5160 :param branch: branch name for the commit
5162 :param branch: branch name for the commit
5161 """
5163 """
5162
5164
5163 branch = branch or ''
5165 branch = branch or ''
5164
5166
5165 branch_matches = True
5167 branch_matches = True
5166 if branch:
5168 if branch:
5167 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5169 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5168 branch_matches = bool(branch_regex.search(branch))
5170 branch_matches = bool(branch_regex.search(branch))
5169
5171
5170 return branch_matches
5172 return branch_matches
5171
5173
5172
5174
5173 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5175 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5174 __tablename__ = 'user_to_repo_branch_permissions'
5176 __tablename__ = 'user_to_repo_branch_permissions'
5175 __table_args__ = (
5177 __table_args__ = (
5176 base_table_args
5178 base_table_args
5177 )
5179 )
5178
5180
5179 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5181 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5180
5182
5181 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5183 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5182 repo = relationship('Repository', backref='user_branch_perms')
5184 repo = relationship('Repository', backref='user_branch_perms')
5183
5185
5184 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5186 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5185 permission = relationship('Permission')
5187 permission = relationship('Permission')
5186
5188
5187 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5189 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5188 user_repo_to_perm = relationship('UserRepoToPerm')
5190 user_repo_to_perm = relationship('UserRepoToPerm')
5189
5191
5190 rule_order = Column('rule_order', Integer(), nullable=False)
5192 rule_order = Column('rule_order', Integer(), nullable=False)
5191 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5193 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5192 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5194 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5193
5195
5194 def __unicode__(self):
5196 def __unicode__(self):
5195 return u'<UserBranchPermission(%s => %r)>' % (
5197 return u'<UserBranchPermission(%s => %r)>' % (
5196 self.user_repo_to_perm, self.branch_pattern)
5198 self.user_repo_to_perm, self.branch_pattern)
5197
5199
5198
5200
5199 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5201 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5200 __tablename__ = 'user_group_to_repo_branch_permissions'
5202 __tablename__ = 'user_group_to_repo_branch_permissions'
5201 __table_args__ = (
5203 __table_args__ = (
5202 base_table_args
5204 base_table_args
5203 )
5205 )
5204
5206
5205 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5207 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5206
5208
5207 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5209 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5208 repo = relationship('Repository', backref='user_group_branch_perms')
5210 repo = relationship('Repository', backref='user_group_branch_perms')
5209
5211
5210 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5212 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5211 permission = relationship('Permission')
5213 permission = relationship('Permission')
5212
5214
5213 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)
5215 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)
5214 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5216 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5215
5217
5216 rule_order = Column('rule_order', Integer(), nullable=False)
5218 rule_order = Column('rule_order', Integer(), nullable=False)
5217 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5219 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5218 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5220 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5219
5221
5220 def __unicode__(self):
5222 def __unicode__(self):
5221 return u'<UserBranchPermission(%s => %r)>' % (
5223 return u'<UserBranchPermission(%s => %r)>' % (
5222 self.user_group_repo_to_perm, self.branch_pattern)
5224 self.user_group_repo_to_perm, self.branch_pattern)
5223
5225
5224
5226
5225 class UserBookmark(Base, BaseModel):
5227 class UserBookmark(Base, BaseModel):
5226 __tablename__ = 'user_bookmarks'
5228 __tablename__ = 'user_bookmarks'
5227 __table_args__ = (
5229 __table_args__ = (
5228 UniqueConstraint('user_id', 'bookmark_repo_id'),
5230 UniqueConstraint('user_id', 'bookmark_repo_id'),
5229 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5231 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5230 UniqueConstraint('user_id', 'bookmark_position'),
5232 UniqueConstraint('user_id', 'bookmark_position'),
5231 base_table_args
5233 base_table_args
5232 )
5234 )
5233
5235
5234 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5236 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5235 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5237 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5236 position = Column("bookmark_position", Integer(), nullable=False)
5238 position = Column("bookmark_position", Integer(), nullable=False)
5237 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5239 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5238 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5240 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5239 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5241 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5240
5242
5241 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5243 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5242 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5244 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5243
5245
5244 user = relationship("User")
5246 user = relationship("User")
5245
5247
5246 repository = relationship("Repository")
5248 repository = relationship("Repository")
5247 repository_group = relationship("RepoGroup")
5249 repository_group = relationship("RepoGroup")
5248
5250
5249 @classmethod
5251 @classmethod
5250 def get_by_position_for_user(cls, position, user_id):
5252 def get_by_position_for_user(cls, position, user_id):
5251 return cls.query() \
5253 return cls.query() \
5252 .filter(UserBookmark.user_id == user_id) \
5254 .filter(UserBookmark.user_id == user_id) \
5253 .filter(UserBookmark.position == position).scalar()
5255 .filter(UserBookmark.position == position).scalar()
5254
5256
5255 @classmethod
5257 @classmethod
5256 def get_bookmarks_for_user(cls, user_id, cache=True):
5258 def get_bookmarks_for_user(cls, user_id, cache=True):
5257 bookmarks = cls.query() \
5259 bookmarks = cls.query() \
5258 .filter(UserBookmark.user_id == user_id) \
5260 .filter(UserBookmark.user_id == user_id) \
5259 .options(joinedload(UserBookmark.repository)) \
5261 .options(joinedload(UserBookmark.repository)) \
5260 .options(joinedload(UserBookmark.repository_group)) \
5262 .options(joinedload(UserBookmark.repository_group)) \
5261 .order_by(UserBookmark.position.asc())
5263 .order_by(UserBookmark.position.asc())
5262
5264
5263 if cache:
5265 if cache:
5264 bookmarks = bookmarks.options(
5266 bookmarks = bookmarks.options(
5265 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5267 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5266 )
5268 )
5267
5269
5268 return bookmarks.all()
5270 return bookmarks.all()
5269
5271
5270 def __unicode__(self):
5272 def __unicode__(self):
5271 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5273 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5272
5274
5273
5275
5274 class FileStore(Base, BaseModel):
5276 class FileStore(Base, BaseModel):
5275 __tablename__ = 'file_store'
5277 __tablename__ = 'file_store'
5276 __table_args__ = (
5278 __table_args__ = (
5277 base_table_args
5279 base_table_args
5278 )
5280 )
5279
5281
5280 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5282 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5281 file_uid = Column('file_uid', String(1024), nullable=False)
5283 file_uid = Column('file_uid', String(1024), nullable=False)
5282 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5284 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5283 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5285 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5284 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5286 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5285
5287
5286 # sha256 hash
5288 # sha256 hash
5287 file_hash = Column('file_hash', String(512), nullable=False)
5289 file_hash = Column('file_hash', String(512), nullable=False)
5288 file_size = Column('file_size', BigInteger(), nullable=False)
5290 file_size = Column('file_size', BigInteger(), nullable=False)
5289
5291
5290 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5292 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5291 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5293 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5292 accessed_count = Column('accessed_count', Integer(), default=0)
5294 accessed_count = Column('accessed_count', Integer(), default=0)
5293
5295
5294 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5296 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5295
5297
5296 # if repo/repo_group reference is set, check for permissions
5298 # if repo/repo_group reference is set, check for permissions
5297 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5299 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5298
5300
5299 # hidden defines an attachment that should be hidden from showing in artifact listing
5301 # hidden defines an attachment that should be hidden from showing in artifact listing
5300 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5302 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5301
5303
5302 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5304 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5303 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5305 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5304
5306
5305 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5307 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5306
5308
5307 # scope limited to user, which requester have access to
5309 # scope limited to user, which requester have access to
5308 scope_user_id = Column(
5310 scope_user_id = Column(
5309 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5311 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5310 nullable=True, unique=None, default=None)
5312 nullable=True, unique=None, default=None)
5311 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5313 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5312
5314
5313 # scope limited to user group, which requester have access to
5315 # scope limited to user group, which requester have access to
5314 scope_user_group_id = Column(
5316 scope_user_group_id = Column(
5315 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5317 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5316 nullable=True, unique=None, default=None)
5318 nullable=True, unique=None, default=None)
5317 user_group = relationship('UserGroup', lazy='joined')
5319 user_group = relationship('UserGroup', lazy='joined')
5318
5320
5319 # scope limited to repo, which requester have access to
5321 # scope limited to repo, which requester have access to
5320 scope_repo_id = Column(
5322 scope_repo_id = Column(
5321 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5323 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5322 nullable=True, unique=None, default=None)
5324 nullable=True, unique=None, default=None)
5323 repo = relationship('Repository', lazy='joined')
5325 repo = relationship('Repository', lazy='joined')
5324
5326
5325 # scope limited to repo group, which requester have access to
5327 # scope limited to repo group, which requester have access to
5326 scope_repo_group_id = Column(
5328 scope_repo_group_id = Column(
5327 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5329 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5328 nullable=True, unique=None, default=None)
5330 nullable=True, unique=None, default=None)
5329 repo_group = relationship('RepoGroup', lazy='joined')
5331 repo_group = relationship('RepoGroup', lazy='joined')
5330
5332
5331 @classmethod
5333 @classmethod
5332 def get_by_store_uid(cls, file_store_uid):
5334 def get_by_store_uid(cls, file_store_uid):
5333 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5335 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5334
5336
5335 @classmethod
5337 @classmethod
5336 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5338 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5337 file_description='', enabled=True, hidden=False, check_acl=True,
5339 file_description='', enabled=True, hidden=False, check_acl=True,
5338 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5340 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5339
5341
5340 store_entry = FileStore()
5342 store_entry = FileStore()
5341 store_entry.file_uid = file_uid
5343 store_entry.file_uid = file_uid
5342 store_entry.file_display_name = file_display_name
5344 store_entry.file_display_name = file_display_name
5343 store_entry.file_org_name = filename
5345 store_entry.file_org_name = filename
5344 store_entry.file_size = file_size
5346 store_entry.file_size = file_size
5345 store_entry.file_hash = file_hash
5347 store_entry.file_hash = file_hash
5346 store_entry.file_description = file_description
5348 store_entry.file_description = file_description
5347
5349
5348 store_entry.check_acl = check_acl
5350 store_entry.check_acl = check_acl
5349 store_entry.enabled = enabled
5351 store_entry.enabled = enabled
5350 store_entry.hidden = hidden
5352 store_entry.hidden = hidden
5351
5353
5352 store_entry.user_id = user_id
5354 store_entry.user_id = user_id
5353 store_entry.scope_user_id = scope_user_id
5355 store_entry.scope_user_id = scope_user_id
5354 store_entry.scope_repo_id = scope_repo_id
5356 store_entry.scope_repo_id = scope_repo_id
5355 store_entry.scope_repo_group_id = scope_repo_group_id
5357 store_entry.scope_repo_group_id = scope_repo_group_id
5356
5358
5357 return store_entry
5359 return store_entry
5358
5360
5359 @classmethod
5361 @classmethod
5360 def store_metadata(cls, file_store_id, args, commit=True):
5362 def store_metadata(cls, file_store_id, args, commit=True):
5361 file_store = FileStore.get(file_store_id)
5363 file_store = FileStore.get(file_store_id)
5362 if file_store is None:
5364 if file_store is None:
5363 return
5365 return
5364
5366
5365 for section, key, value, value_type in args:
5367 for section, key, value, value_type in args:
5366 has_key = FileStoreMetadata().query() \
5368 has_key = FileStoreMetadata().query() \
5367 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5369 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5368 .filter(FileStoreMetadata.file_store_meta_section == section) \
5370 .filter(FileStoreMetadata.file_store_meta_section == section) \
5369 .filter(FileStoreMetadata.file_store_meta_key == key) \
5371 .filter(FileStoreMetadata.file_store_meta_key == key) \
5370 .scalar()
5372 .scalar()
5371 if has_key:
5373 if has_key:
5372 msg = 'key `{}` already defined under section `{}` for this file.'\
5374 msg = 'key `{}` already defined under section `{}` for this file.'\
5373 .format(key, section)
5375 .format(key, section)
5374 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5376 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5375
5377
5376 # NOTE(marcink): raises ArtifactMetadataBadValueType
5378 # NOTE(marcink): raises ArtifactMetadataBadValueType
5377 FileStoreMetadata.valid_value_type(value_type)
5379 FileStoreMetadata.valid_value_type(value_type)
5378
5380
5379 meta_entry = FileStoreMetadata()
5381 meta_entry = FileStoreMetadata()
5380 meta_entry.file_store = file_store
5382 meta_entry.file_store = file_store
5381 meta_entry.file_store_meta_section = section
5383 meta_entry.file_store_meta_section = section
5382 meta_entry.file_store_meta_key = key
5384 meta_entry.file_store_meta_key = key
5383 meta_entry.file_store_meta_value_type = value_type
5385 meta_entry.file_store_meta_value_type = value_type
5384 meta_entry.file_store_meta_value = value
5386 meta_entry.file_store_meta_value = value
5385
5387
5386 Session().add(meta_entry)
5388 Session().add(meta_entry)
5387
5389
5388 try:
5390 try:
5389 if commit:
5391 if commit:
5390 Session().commit()
5392 Session().commit()
5391 except IntegrityError:
5393 except IntegrityError:
5392 Session().rollback()
5394 Session().rollback()
5393 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5395 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5394
5396
5395 @classmethod
5397 @classmethod
5396 def bump_access_counter(cls, file_uid, commit=True):
5398 def bump_access_counter(cls, file_uid, commit=True):
5397 FileStore().query()\
5399 FileStore().query()\
5398 .filter(FileStore.file_uid == file_uid)\
5400 .filter(FileStore.file_uid == file_uid)\
5399 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5401 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5400 FileStore.accessed_on: datetime.datetime.now()})
5402 FileStore.accessed_on: datetime.datetime.now()})
5401 if commit:
5403 if commit:
5402 Session().commit()
5404 Session().commit()
5403
5405
5404 def __json__(self):
5406 def __json__(self):
5405 data = {
5407 data = {
5406 'filename': self.file_display_name,
5408 'filename': self.file_display_name,
5407 'filename_org': self.file_org_name,
5409 'filename_org': self.file_org_name,
5408 'file_uid': self.file_uid,
5410 'file_uid': self.file_uid,
5409 'description': self.file_description,
5411 'description': self.file_description,
5410 'hidden': self.hidden,
5412 'hidden': self.hidden,
5411 'size': self.file_size,
5413 'size': self.file_size,
5412 'created_on': self.created_on,
5414 'created_on': self.created_on,
5413 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5415 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5414 'downloaded_times': self.accessed_count,
5416 'downloaded_times': self.accessed_count,
5415 'sha256': self.file_hash,
5417 'sha256': self.file_hash,
5416 'metadata': self.file_metadata,
5418 'metadata': self.file_metadata,
5417 }
5419 }
5418
5420
5419 return data
5421 return data
5420
5422
5421 def __repr__(self):
5423 def __repr__(self):
5422 return '<FileStore({})>'.format(self.file_store_id)
5424 return '<FileStore({})>'.format(self.file_store_id)
5423
5425
5424
5426
5425 class FileStoreMetadata(Base, BaseModel):
5427 class FileStoreMetadata(Base, BaseModel):
5426 __tablename__ = 'file_store_metadata'
5428 __tablename__ = 'file_store_metadata'
5427 __table_args__ = (
5429 __table_args__ = (
5428 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5430 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5429 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5431 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5430 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5432 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5431 base_table_args
5433 base_table_args
5432 )
5434 )
5433 SETTINGS_TYPES = {
5435 SETTINGS_TYPES = {
5434 'str': safe_str,
5436 'str': safe_str,
5435 'int': safe_int,
5437 'int': safe_int,
5436 'unicode': safe_unicode,
5438 'unicode': safe_unicode,
5437 'bool': str2bool,
5439 'bool': str2bool,
5438 'list': functools.partial(aslist, sep=',')
5440 'list': functools.partial(aslist, sep=',')
5439 }
5441 }
5440
5442
5441 file_store_meta_id = Column(
5443 file_store_meta_id = Column(
5442 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5444 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5443 primary_key=True)
5445 primary_key=True)
5444 _file_store_meta_section = Column(
5446 _file_store_meta_section = Column(
5445 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5447 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5446 nullable=True, unique=None, default=None)
5448 nullable=True, unique=None, default=None)
5447 _file_store_meta_section_hash = Column(
5449 _file_store_meta_section_hash = Column(
5448 "file_store_meta_section_hash", String(255),
5450 "file_store_meta_section_hash", String(255),
5449 nullable=True, unique=None, default=None)
5451 nullable=True, unique=None, default=None)
5450 _file_store_meta_key = Column(
5452 _file_store_meta_key = Column(
5451 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5453 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5452 nullable=True, unique=None, default=None)
5454 nullable=True, unique=None, default=None)
5453 _file_store_meta_key_hash = Column(
5455 _file_store_meta_key_hash = Column(
5454 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5456 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5455 _file_store_meta_value = Column(
5457 _file_store_meta_value = Column(
5456 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5458 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5457 nullable=True, unique=None, default=None)
5459 nullable=True, unique=None, default=None)
5458 _file_store_meta_value_type = Column(
5460 _file_store_meta_value_type = Column(
5459 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5461 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5460 default='unicode')
5462 default='unicode')
5461
5463
5462 file_store_id = Column(
5464 file_store_id = Column(
5463 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5465 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5464 nullable=True, unique=None, default=None)
5466 nullable=True, unique=None, default=None)
5465
5467
5466 file_store = relationship('FileStore', lazy='joined')
5468 file_store = relationship('FileStore', lazy='joined')
5467
5469
5468 @classmethod
5470 @classmethod
5469 def valid_value_type(cls, value):
5471 def valid_value_type(cls, value):
5470 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5472 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5471 raise ArtifactMetadataBadValueType(
5473 raise ArtifactMetadataBadValueType(
5472 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5474 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5473
5475
5474 @hybrid_property
5476 @hybrid_property
5475 def file_store_meta_section(self):
5477 def file_store_meta_section(self):
5476 return self._file_store_meta_section
5478 return self._file_store_meta_section
5477
5479
5478 @file_store_meta_section.setter
5480 @file_store_meta_section.setter
5479 def file_store_meta_section(self, value):
5481 def file_store_meta_section(self, value):
5480 self._file_store_meta_section = value
5482 self._file_store_meta_section = value
5481 self._file_store_meta_section_hash = _hash_key(value)
5483 self._file_store_meta_section_hash = _hash_key(value)
5482
5484
5483 @hybrid_property
5485 @hybrid_property
5484 def file_store_meta_key(self):
5486 def file_store_meta_key(self):
5485 return self._file_store_meta_key
5487 return self._file_store_meta_key
5486
5488
5487 @file_store_meta_key.setter
5489 @file_store_meta_key.setter
5488 def file_store_meta_key(self, value):
5490 def file_store_meta_key(self, value):
5489 self._file_store_meta_key = value
5491 self._file_store_meta_key = value
5490 self._file_store_meta_key_hash = _hash_key(value)
5492 self._file_store_meta_key_hash = _hash_key(value)
5491
5493
5492 @hybrid_property
5494 @hybrid_property
5493 def file_store_meta_value(self):
5495 def file_store_meta_value(self):
5494 val = self._file_store_meta_value
5496 val = self._file_store_meta_value
5495
5497
5496 if self._file_store_meta_value_type:
5498 if self._file_store_meta_value_type:
5497 # e.g unicode.encrypted == unicode
5499 # e.g unicode.encrypted == unicode
5498 _type = self._file_store_meta_value_type.split('.')[0]
5500 _type = self._file_store_meta_value_type.split('.')[0]
5499 # decode the encrypted value if it's encrypted field type
5501 # decode the encrypted value if it's encrypted field type
5500 if '.encrypted' in self._file_store_meta_value_type:
5502 if '.encrypted' in self._file_store_meta_value_type:
5501 cipher = EncryptedTextValue()
5503 cipher = EncryptedTextValue()
5502 val = safe_unicode(cipher.process_result_value(val, None))
5504 val = safe_unicode(cipher.process_result_value(val, None))
5503 # do final type conversion
5505 # do final type conversion
5504 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5506 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5505 val = converter(val)
5507 val = converter(val)
5506
5508
5507 return val
5509 return val
5508
5510
5509 @file_store_meta_value.setter
5511 @file_store_meta_value.setter
5510 def file_store_meta_value(self, val):
5512 def file_store_meta_value(self, val):
5511 val = safe_unicode(val)
5513 val = safe_unicode(val)
5512 # encode the encrypted value
5514 # encode the encrypted value
5513 if '.encrypted' in self.file_store_meta_value_type:
5515 if '.encrypted' in self.file_store_meta_value_type:
5514 cipher = EncryptedTextValue()
5516 cipher = EncryptedTextValue()
5515 val = safe_unicode(cipher.process_bind_param(val, None))
5517 val = safe_unicode(cipher.process_bind_param(val, None))
5516 self._file_store_meta_value = val
5518 self._file_store_meta_value = val
5517
5519
5518 @hybrid_property
5520 @hybrid_property
5519 def file_store_meta_value_type(self):
5521 def file_store_meta_value_type(self):
5520 return self._file_store_meta_value_type
5522 return self._file_store_meta_value_type
5521
5523
5522 @file_store_meta_value_type.setter
5524 @file_store_meta_value_type.setter
5523 def file_store_meta_value_type(self, val):
5525 def file_store_meta_value_type(self, val):
5524 # e.g unicode.encrypted
5526 # e.g unicode.encrypted
5525 self.valid_value_type(val)
5527 self.valid_value_type(val)
5526 self._file_store_meta_value_type = val
5528 self._file_store_meta_value_type = val
5527
5529
5528 def __json__(self):
5530 def __json__(self):
5529 data = {
5531 data = {
5530 'artifact': self.file_store.file_uid,
5532 'artifact': self.file_store.file_uid,
5531 'section': self.file_store_meta_section,
5533 'section': self.file_store_meta_section,
5532 'key': self.file_store_meta_key,
5534 'key': self.file_store_meta_key,
5533 'value': self.file_store_meta_value,
5535 'value': self.file_store_meta_value,
5534 }
5536 }
5535
5537
5536 return data
5538 return data
5537
5539
5538 def __repr__(self):
5540 def __repr__(self):
5539 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5541 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5540 self.file_store_meta_key, self.file_store_meta_value)
5542 self.file_store_meta_key, self.file_store_meta_value)
5541
5543
5542
5544
5543 class DbMigrateVersion(Base, BaseModel):
5545 class DbMigrateVersion(Base, BaseModel):
5544 __tablename__ = 'db_migrate_version'
5546 __tablename__ = 'db_migrate_version'
5545 __table_args__ = (
5547 __table_args__ = (
5546 base_table_args,
5548 base_table_args,
5547 )
5549 )
5548
5550
5549 repository_id = Column('repository_id', String(250), primary_key=True)
5551 repository_id = Column('repository_id', String(250), primary_key=True)
5550 repository_path = Column('repository_path', Text)
5552 repository_path = Column('repository_path', Text)
5551 version = Column('version', Integer)
5553 version = Column('version', Integer)
5552
5554
5553 @classmethod
5555 @classmethod
5554 def set_version(cls, version):
5556 def set_version(cls, version):
5555 """
5557 """
5556 Helper for forcing a different version, usually for debugging purposes via ishell.
5558 Helper for forcing a different version, usually for debugging purposes via ishell.
5557 """
5559 """
5558 ver = DbMigrateVersion.query().first()
5560 ver = DbMigrateVersion.query().first()
5559 ver.version = version
5561 ver.version = version
5560 Session().commit()
5562 Session().commit()
5561
5563
5562
5564
5563 class DbSession(Base, BaseModel):
5565 class DbSession(Base, BaseModel):
5564 __tablename__ = 'db_session'
5566 __tablename__ = 'db_session'
5565 __table_args__ = (
5567 __table_args__ = (
5566 base_table_args,
5568 base_table_args,
5567 )
5569 )
5568
5570
5569 def __repr__(self):
5571 def __repr__(self):
5570 return '<DB:DbSession({})>'.format(self.id)
5572 return '<DB:DbSession({})>'.format(self.id)
5571
5573
5572 id = Column('id', Integer())
5574 id = Column('id', Integer())
5573 namespace = Column('namespace', String(255), primary_key=True)
5575 namespace = Column('namespace', String(255), primary_key=True)
5574 accessed = Column('accessed', DateTime, nullable=False)
5576 accessed = Column('accessed', DateTime, nullable=False)
5575 created = Column('created', DateTime, nullable=False)
5577 created = Column('created', DateTime, nullable=False)
5576 data = Column('data', PickleType, nullable=False)
5578 data = Column('data', PickleType, nullable=False)
General Comments 0
You need to be logged in to leave comments. Login now