##// 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

The requested changes are too big and content was truncated. Show full diff

@@ -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 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now