##// END OF EJS Templates
comments: changed signature of create method of ChangesetComment....
marcink -
r1322:50f14062 default
parent child Browse files
Show More
@@ -1,1960 +1,1960 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
32 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
33 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
33 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
34 from rhodecode.lib.utils2 import str2bool, time_to_datetime
34 from rhodecode.lib.utils2 import str2bool, time_to_datetime
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.model.changeset_status import ChangesetStatusModel
36 from rhodecode.model.changeset_status import ChangesetStatusModel
37 from rhodecode.model.comment import ChangesetCommentsModel
37 from rhodecode.model.comment import ChangesetCommentsModel
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup)
39 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup)
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.scm import ScmModel, RepoList
41 from rhodecode.model.scm import ScmModel, RepoList
42 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
42 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
43 from rhodecode.model import validation_schema
43 from rhodecode.model import validation_schema
44 from rhodecode.model.validation_schema.schemas import repo_schema
44 from rhodecode.model.validation_schema.schemas import repo_schema
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 @jsonrpc_method()
49 @jsonrpc_method()
50 def get_repo(request, apiuser, repoid, cache=Optional(True)):
50 def get_repo(request, apiuser, repoid, cache=Optional(True)):
51 """
51 """
52 Gets an existing repository by its name or repository_id.
52 Gets an existing repository by its name or repository_id.
53
53
54 The members section so the output returns users groups or users
54 The members section so the output returns users groups or users
55 associated with that repository.
55 associated with that repository.
56
56
57 This command can only be run using an |authtoken| with admin rights,
57 This command can only be run using an |authtoken| with admin rights,
58 or users with at least read rights to the |repo|.
58 or users with at least read rights to the |repo|.
59
59
60 :param apiuser: This is filled automatically from the |authtoken|.
60 :param apiuser: This is filled automatically from the |authtoken|.
61 :type apiuser: AuthUser
61 :type apiuser: AuthUser
62 :param repoid: The repository name or repository id.
62 :param repoid: The repository name or repository id.
63 :type repoid: str or int
63 :type repoid: str or int
64 :param cache: use the cached value for last changeset
64 :param cache: use the cached value for last changeset
65 :type: cache: Optional(bool)
65 :type: cache: Optional(bool)
66
66
67 Example output:
67 Example output:
68
68
69 .. code-block:: bash
69 .. code-block:: bash
70
70
71 {
71 {
72 "error": null,
72 "error": null,
73 "id": <repo_id>,
73 "id": <repo_id>,
74 "result": {
74 "result": {
75 "clone_uri": null,
75 "clone_uri": null,
76 "created_on": "timestamp",
76 "created_on": "timestamp",
77 "description": "repo description",
77 "description": "repo description",
78 "enable_downloads": false,
78 "enable_downloads": false,
79 "enable_locking": false,
79 "enable_locking": false,
80 "enable_statistics": false,
80 "enable_statistics": false,
81 "followers": [
81 "followers": [
82 {
82 {
83 "active": true,
83 "active": true,
84 "admin": false,
84 "admin": false,
85 "api_key": "****************************************",
85 "api_key": "****************************************",
86 "api_keys": [
86 "api_keys": [
87 "****************************************"
87 "****************************************"
88 ],
88 ],
89 "email": "user@example.com",
89 "email": "user@example.com",
90 "emails": [
90 "emails": [
91 "user@example.com"
91 "user@example.com"
92 ],
92 ],
93 "extern_name": "rhodecode",
93 "extern_name": "rhodecode",
94 "extern_type": "rhodecode",
94 "extern_type": "rhodecode",
95 "firstname": "username",
95 "firstname": "username",
96 "ip_addresses": [],
96 "ip_addresses": [],
97 "language": null,
97 "language": null,
98 "last_login": "2015-09-16T17:16:35.854",
98 "last_login": "2015-09-16T17:16:35.854",
99 "lastname": "surname",
99 "lastname": "surname",
100 "user_id": <user_id>,
100 "user_id": <user_id>,
101 "username": "name"
101 "username": "name"
102 }
102 }
103 ],
103 ],
104 "fork_of": "parent-repo",
104 "fork_of": "parent-repo",
105 "landing_rev": [
105 "landing_rev": [
106 "rev",
106 "rev",
107 "tip"
107 "tip"
108 ],
108 ],
109 "last_changeset": {
109 "last_changeset": {
110 "author": "User <user@example.com>",
110 "author": "User <user@example.com>",
111 "branch": "default",
111 "branch": "default",
112 "date": "timestamp",
112 "date": "timestamp",
113 "message": "last commit message",
113 "message": "last commit message",
114 "parents": [
114 "parents": [
115 {
115 {
116 "raw_id": "commit-id"
116 "raw_id": "commit-id"
117 }
117 }
118 ],
118 ],
119 "raw_id": "commit-id",
119 "raw_id": "commit-id",
120 "revision": <revision number>,
120 "revision": <revision number>,
121 "short_id": "short id"
121 "short_id": "short id"
122 },
122 },
123 "lock_reason": null,
123 "lock_reason": null,
124 "locked_by": null,
124 "locked_by": null,
125 "locked_date": null,
125 "locked_date": null,
126 "members": [
126 "members": [
127 {
127 {
128 "name": "super-admin-name",
128 "name": "super-admin-name",
129 "origin": "super-admin",
129 "origin": "super-admin",
130 "permission": "repository.admin",
130 "permission": "repository.admin",
131 "type": "user"
131 "type": "user"
132 },
132 },
133 {
133 {
134 "name": "owner-name",
134 "name": "owner-name",
135 "origin": "owner",
135 "origin": "owner",
136 "permission": "repository.admin",
136 "permission": "repository.admin",
137 "type": "user"
137 "type": "user"
138 },
138 },
139 {
139 {
140 "name": "user-group-name",
140 "name": "user-group-name",
141 "origin": "permission",
141 "origin": "permission",
142 "permission": "repository.write",
142 "permission": "repository.write",
143 "type": "user_group"
143 "type": "user_group"
144 }
144 }
145 ],
145 ],
146 "owner": "owner-name",
146 "owner": "owner-name",
147 "permissions": [
147 "permissions": [
148 {
148 {
149 "name": "super-admin-name",
149 "name": "super-admin-name",
150 "origin": "super-admin",
150 "origin": "super-admin",
151 "permission": "repository.admin",
151 "permission": "repository.admin",
152 "type": "user"
152 "type": "user"
153 },
153 },
154 {
154 {
155 "name": "owner-name",
155 "name": "owner-name",
156 "origin": "owner",
156 "origin": "owner",
157 "permission": "repository.admin",
157 "permission": "repository.admin",
158 "type": "user"
158 "type": "user"
159 },
159 },
160 {
160 {
161 "name": "user-group-name",
161 "name": "user-group-name",
162 "origin": "permission",
162 "origin": "permission",
163 "permission": "repository.write",
163 "permission": "repository.write",
164 "type": "user_group"
164 "type": "user_group"
165 }
165 }
166 ],
166 ],
167 "private": true,
167 "private": true,
168 "repo_id": 676,
168 "repo_id": 676,
169 "repo_name": "user-group/repo-name",
169 "repo_name": "user-group/repo-name",
170 "repo_type": "hg"
170 "repo_type": "hg"
171 }
171 }
172 }
172 }
173 """
173 """
174
174
175 repo = get_repo_or_error(repoid)
175 repo = get_repo_or_error(repoid)
176 cache = Optional.extract(cache)
176 cache = Optional.extract(cache)
177
177
178 include_secrets = False
178 include_secrets = False
179 if has_superadmin_permission(apiuser):
179 if has_superadmin_permission(apiuser):
180 include_secrets = True
180 include_secrets = True
181 else:
181 else:
182 # check if we have at least read permission for this repo !
182 # check if we have at least read permission for this repo !
183 _perms = (
183 _perms = (
184 'repository.admin', 'repository.write', 'repository.read',)
184 'repository.admin', 'repository.write', 'repository.read',)
185 validate_repo_permissions(apiuser, repoid, repo, _perms)
185 validate_repo_permissions(apiuser, repoid, repo, _perms)
186
186
187 permissions = []
187 permissions = []
188 for _user in repo.permissions():
188 for _user in repo.permissions():
189 user_data = {
189 user_data = {
190 'name': _user.username,
190 'name': _user.username,
191 'permission': _user.permission,
191 'permission': _user.permission,
192 'origin': get_origin(_user),
192 'origin': get_origin(_user),
193 'type': "user",
193 'type': "user",
194 }
194 }
195 permissions.append(user_data)
195 permissions.append(user_data)
196
196
197 for _user_group in repo.permission_user_groups():
197 for _user_group in repo.permission_user_groups():
198 user_group_data = {
198 user_group_data = {
199 'name': _user_group.users_group_name,
199 'name': _user_group.users_group_name,
200 'permission': _user_group.permission,
200 'permission': _user_group.permission,
201 'origin': get_origin(_user_group),
201 'origin': get_origin(_user_group),
202 'type': "user_group",
202 'type': "user_group",
203 }
203 }
204 permissions.append(user_group_data)
204 permissions.append(user_group_data)
205
205
206 following_users = [
206 following_users = [
207 user.user.get_api_data(include_secrets=include_secrets)
207 user.user.get_api_data(include_secrets=include_secrets)
208 for user in repo.followers]
208 for user in repo.followers]
209
209
210 if not cache:
210 if not cache:
211 repo.update_commit_cache()
211 repo.update_commit_cache()
212 data = repo.get_api_data(include_secrets=include_secrets)
212 data = repo.get_api_data(include_secrets=include_secrets)
213 data['members'] = permissions # TODO: this should be deprecated soon
213 data['members'] = permissions # TODO: this should be deprecated soon
214 data['permissions'] = permissions
214 data['permissions'] = permissions
215 data['followers'] = following_users
215 data['followers'] = following_users
216 return data
216 return data
217
217
218
218
219 @jsonrpc_method()
219 @jsonrpc_method()
220 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
220 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
221 """
221 """
222 Lists all existing repositories.
222 Lists all existing repositories.
223
223
224 This command can only be run using an |authtoken| with admin rights,
224 This command can only be run using an |authtoken| with admin rights,
225 or users with at least read rights to |repos|.
225 or users with at least read rights to |repos|.
226
226
227 :param apiuser: This is filled automatically from the |authtoken|.
227 :param apiuser: This is filled automatically from the |authtoken|.
228 :type apiuser: AuthUser
228 :type apiuser: AuthUser
229 :param root: specify root repository group to fetch repositories.
229 :param root: specify root repository group to fetch repositories.
230 filters the returned repositories to be members of given root group.
230 filters the returned repositories to be members of given root group.
231 :type root: Optional(None)
231 :type root: Optional(None)
232 :param traverse: traverse given root into subrepositories. With this flag
232 :param traverse: traverse given root into subrepositories. With this flag
233 set to False, it will only return top-level repositories from `root`.
233 set to False, it will only return top-level repositories from `root`.
234 if root is empty it will return just top-level repositories.
234 if root is empty it will return just top-level repositories.
235 :type traverse: Optional(True)
235 :type traverse: Optional(True)
236
236
237
237
238 Example output:
238 Example output:
239
239
240 .. code-block:: bash
240 .. code-block:: bash
241
241
242 id : <id_given_in_input>
242 id : <id_given_in_input>
243 result: [
243 result: [
244 {
244 {
245 "repo_id" : "<repo_id>",
245 "repo_id" : "<repo_id>",
246 "repo_name" : "<reponame>"
246 "repo_name" : "<reponame>"
247 "repo_type" : "<repo_type>",
247 "repo_type" : "<repo_type>",
248 "clone_uri" : "<clone_uri>",
248 "clone_uri" : "<clone_uri>",
249 "private": : "<bool>",
249 "private": : "<bool>",
250 "created_on" : "<datetimecreated>",
250 "created_on" : "<datetimecreated>",
251 "description" : "<description>",
251 "description" : "<description>",
252 "landing_rev": "<landing_rev>",
252 "landing_rev": "<landing_rev>",
253 "owner": "<repo_owner>",
253 "owner": "<repo_owner>",
254 "fork_of": "<name_of_fork_parent>",
254 "fork_of": "<name_of_fork_parent>",
255 "enable_downloads": "<bool>",
255 "enable_downloads": "<bool>",
256 "enable_locking": "<bool>",
256 "enable_locking": "<bool>",
257 "enable_statistics": "<bool>",
257 "enable_statistics": "<bool>",
258 },
258 },
259 ...
259 ...
260 ]
260 ]
261 error: null
261 error: null
262 """
262 """
263
263
264 include_secrets = has_superadmin_permission(apiuser)
264 include_secrets = has_superadmin_permission(apiuser)
265 _perms = ('repository.read', 'repository.write', 'repository.admin',)
265 _perms = ('repository.read', 'repository.write', 'repository.admin',)
266 extras = {'user': apiuser}
266 extras = {'user': apiuser}
267
267
268 root = Optional.extract(root)
268 root = Optional.extract(root)
269 traverse = Optional.extract(traverse, binary=True)
269 traverse = Optional.extract(traverse, binary=True)
270
270
271 if root:
271 if root:
272 # verify parent existance, if it's empty return an error
272 # verify parent existance, if it's empty return an error
273 parent = RepoGroup.get_by_group_name(root)
273 parent = RepoGroup.get_by_group_name(root)
274 if not parent:
274 if not parent:
275 raise JSONRPCError(
275 raise JSONRPCError(
276 'Root repository group `{}` does not exist'.format(root))
276 'Root repository group `{}` does not exist'.format(root))
277
277
278 if traverse:
278 if traverse:
279 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
279 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
280 else:
280 else:
281 repos = RepoModel().get_repos_for_root(root=parent)
281 repos = RepoModel().get_repos_for_root(root=parent)
282 else:
282 else:
283 if traverse:
283 if traverse:
284 repos = RepoModel().get_all()
284 repos = RepoModel().get_all()
285 else:
285 else:
286 # return just top-level
286 # return just top-level
287 repos = RepoModel().get_repos_for_root(root=None)
287 repos = RepoModel().get_repos_for_root(root=None)
288
288
289 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
289 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
290 return [repo.get_api_data(include_secrets=include_secrets)
290 return [repo.get_api_data(include_secrets=include_secrets)
291 for repo in repo_list]
291 for repo in repo_list]
292
292
293
293
294 @jsonrpc_method()
294 @jsonrpc_method()
295 def get_repo_changeset(request, apiuser, repoid, revision,
295 def get_repo_changeset(request, apiuser, repoid, revision,
296 details=Optional('basic')):
296 details=Optional('basic')):
297 """
297 """
298 Returns information about a changeset.
298 Returns information about a changeset.
299
299
300 Additionally parameters define the amount of details returned by
300 Additionally parameters define the amount of details returned by
301 this function.
301 this function.
302
302
303 This command can only be run using an |authtoken| with admin rights,
303 This command can only be run using an |authtoken| with admin rights,
304 or users with at least read rights to the |repo|.
304 or users with at least read rights to the |repo|.
305
305
306 :param apiuser: This is filled automatically from the |authtoken|.
306 :param apiuser: This is filled automatically from the |authtoken|.
307 :type apiuser: AuthUser
307 :type apiuser: AuthUser
308 :param repoid: The repository name or repository id
308 :param repoid: The repository name or repository id
309 :type repoid: str or int
309 :type repoid: str or int
310 :param revision: revision for which listing should be done
310 :param revision: revision for which listing should be done
311 :type revision: str
311 :type revision: str
312 :param details: details can be 'basic|extended|full' full gives diff
312 :param details: details can be 'basic|extended|full' full gives diff
313 info details like the diff itself, and number of changed files etc.
313 info details like the diff itself, and number of changed files etc.
314 :type details: Optional(str)
314 :type details: Optional(str)
315
315
316 """
316 """
317 repo = get_repo_or_error(repoid)
317 repo = get_repo_or_error(repoid)
318 if not has_superadmin_permission(apiuser):
318 if not has_superadmin_permission(apiuser):
319 _perms = (
319 _perms = (
320 'repository.admin', 'repository.write', 'repository.read',)
320 'repository.admin', 'repository.write', 'repository.read',)
321 validate_repo_permissions(apiuser, repoid, repo, _perms)
321 validate_repo_permissions(apiuser, repoid, repo, _perms)
322
322
323 changes_details = Optional.extract(details)
323 changes_details = Optional.extract(details)
324 _changes_details_types = ['basic', 'extended', 'full']
324 _changes_details_types = ['basic', 'extended', 'full']
325 if changes_details not in _changes_details_types:
325 if changes_details not in _changes_details_types:
326 raise JSONRPCError(
326 raise JSONRPCError(
327 'ret_type must be one of %s' % (
327 'ret_type must be one of %s' % (
328 ','.join(_changes_details_types)))
328 ','.join(_changes_details_types)))
329
329
330 pre_load = ['author', 'branch', 'date', 'message', 'parents',
330 pre_load = ['author', 'branch', 'date', 'message', 'parents',
331 'status', '_commit', '_file_paths']
331 'status', '_commit', '_file_paths']
332
332
333 try:
333 try:
334 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
334 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
335 except TypeError as e:
335 except TypeError as e:
336 raise JSONRPCError(e.message)
336 raise JSONRPCError(e.message)
337 _cs_json = cs.__json__()
337 _cs_json = cs.__json__()
338 _cs_json['diff'] = build_commit_data(cs, changes_details)
338 _cs_json['diff'] = build_commit_data(cs, changes_details)
339 if changes_details == 'full':
339 if changes_details == 'full':
340 _cs_json['refs'] = {
340 _cs_json['refs'] = {
341 'branches': [cs.branch],
341 'branches': [cs.branch],
342 'bookmarks': getattr(cs, 'bookmarks', []),
342 'bookmarks': getattr(cs, 'bookmarks', []),
343 'tags': cs.tags
343 'tags': cs.tags
344 }
344 }
345 return _cs_json
345 return _cs_json
346
346
347
347
348 @jsonrpc_method()
348 @jsonrpc_method()
349 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
349 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
350 details=Optional('basic')):
350 details=Optional('basic')):
351 """
351 """
352 Returns a set of commits limited by the number starting
352 Returns a set of commits limited by the number starting
353 from the `start_rev` option.
353 from the `start_rev` option.
354
354
355 Additional parameters define the amount of details returned by this
355 Additional parameters define the amount of details returned by this
356 function.
356 function.
357
357
358 This command can only be run using an |authtoken| with admin rights,
358 This command can only be run using an |authtoken| with admin rights,
359 or users with at least read rights to |repos|.
359 or users with at least read rights to |repos|.
360
360
361 :param apiuser: This is filled automatically from the |authtoken|.
361 :param apiuser: This is filled automatically from the |authtoken|.
362 :type apiuser: AuthUser
362 :type apiuser: AuthUser
363 :param repoid: The repository name or repository ID.
363 :param repoid: The repository name or repository ID.
364 :type repoid: str or int
364 :type repoid: str or int
365 :param start_rev: The starting revision from where to get changesets.
365 :param start_rev: The starting revision from where to get changesets.
366 :type start_rev: str
366 :type start_rev: str
367 :param limit: Limit the number of commits to this amount
367 :param limit: Limit the number of commits to this amount
368 :type limit: str or int
368 :type limit: str or int
369 :param details: Set the level of detail returned. Valid option are:
369 :param details: Set the level of detail returned. Valid option are:
370 ``basic``, ``extended`` and ``full``.
370 ``basic``, ``extended`` and ``full``.
371 :type details: Optional(str)
371 :type details: Optional(str)
372
372
373 .. note::
373 .. note::
374
374
375 Setting the parameter `details` to the value ``full`` is extensive
375 Setting the parameter `details` to the value ``full`` is extensive
376 and returns details like the diff itself, and the number
376 and returns details like the diff itself, and the number
377 of changed files.
377 of changed files.
378
378
379 """
379 """
380 repo = get_repo_or_error(repoid)
380 repo = get_repo_or_error(repoid)
381 if not has_superadmin_permission(apiuser):
381 if not has_superadmin_permission(apiuser):
382 _perms = (
382 _perms = (
383 'repository.admin', 'repository.write', 'repository.read',)
383 'repository.admin', 'repository.write', 'repository.read',)
384 validate_repo_permissions(apiuser, repoid, repo, _perms)
384 validate_repo_permissions(apiuser, repoid, repo, _perms)
385
385
386 changes_details = Optional.extract(details)
386 changes_details = Optional.extract(details)
387 _changes_details_types = ['basic', 'extended', 'full']
387 _changes_details_types = ['basic', 'extended', 'full']
388 if changes_details not in _changes_details_types:
388 if changes_details not in _changes_details_types:
389 raise JSONRPCError(
389 raise JSONRPCError(
390 'ret_type must be one of %s' % (
390 'ret_type must be one of %s' % (
391 ','.join(_changes_details_types)))
391 ','.join(_changes_details_types)))
392
392
393 limit = int(limit)
393 limit = int(limit)
394 pre_load = ['author', 'branch', 'date', 'message', 'parents',
394 pre_load = ['author', 'branch', 'date', 'message', 'parents',
395 'status', '_commit', '_file_paths']
395 'status', '_commit', '_file_paths']
396
396
397 vcs_repo = repo.scm_instance()
397 vcs_repo = repo.scm_instance()
398 # SVN needs a special case to distinguish its index and commit id
398 # SVN needs a special case to distinguish its index and commit id
399 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
399 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
400 start_rev = vcs_repo.commit_ids[0]
400 start_rev = vcs_repo.commit_ids[0]
401
401
402 try:
402 try:
403 commits = vcs_repo.get_commits(
403 commits = vcs_repo.get_commits(
404 start_id=start_rev, pre_load=pre_load)
404 start_id=start_rev, pre_load=pre_load)
405 except TypeError as e:
405 except TypeError as e:
406 raise JSONRPCError(e.message)
406 raise JSONRPCError(e.message)
407 except Exception:
407 except Exception:
408 log.exception('Fetching of commits failed')
408 log.exception('Fetching of commits failed')
409 raise JSONRPCError('Error occurred during commit fetching')
409 raise JSONRPCError('Error occurred during commit fetching')
410
410
411 ret = []
411 ret = []
412 for cnt, commit in enumerate(commits):
412 for cnt, commit in enumerate(commits):
413 if cnt >= limit != -1:
413 if cnt >= limit != -1:
414 break
414 break
415 _cs_json = commit.__json__()
415 _cs_json = commit.__json__()
416 _cs_json['diff'] = build_commit_data(commit, changes_details)
416 _cs_json['diff'] = build_commit_data(commit, changes_details)
417 if changes_details == 'full':
417 if changes_details == 'full':
418 _cs_json['refs'] = {
418 _cs_json['refs'] = {
419 'branches': [commit.branch],
419 'branches': [commit.branch],
420 'bookmarks': getattr(commit, 'bookmarks', []),
420 'bookmarks': getattr(commit, 'bookmarks', []),
421 'tags': commit.tags
421 'tags': commit.tags
422 }
422 }
423 ret.append(_cs_json)
423 ret.append(_cs_json)
424 return ret
424 return ret
425
425
426
426
427 @jsonrpc_method()
427 @jsonrpc_method()
428 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
428 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
429 ret_type=Optional('all'), details=Optional('basic'),
429 ret_type=Optional('all'), details=Optional('basic'),
430 max_file_bytes=Optional(None)):
430 max_file_bytes=Optional(None)):
431 """
431 """
432 Returns a list of nodes and children in a flat list for a given
432 Returns a list of nodes and children in a flat list for a given
433 path at given revision.
433 path at given revision.
434
434
435 It's possible to specify ret_type to show only `files` or `dirs`.
435 It's possible to specify ret_type to show only `files` or `dirs`.
436
436
437 This command can only be run using an |authtoken| with admin rights,
437 This command can only be run using an |authtoken| with admin rights,
438 or users with at least read rights to |repos|.
438 or users with at least read rights to |repos|.
439
439
440 :param apiuser: This is filled automatically from the |authtoken|.
440 :param apiuser: This is filled automatically from the |authtoken|.
441 :type apiuser: AuthUser
441 :type apiuser: AuthUser
442 :param repoid: The repository name or repository ID.
442 :param repoid: The repository name or repository ID.
443 :type repoid: str or int
443 :type repoid: str or int
444 :param revision: The revision for which listing should be done.
444 :param revision: The revision for which listing should be done.
445 :type revision: str
445 :type revision: str
446 :param root_path: The path from which to start displaying.
446 :param root_path: The path from which to start displaying.
447 :type root_path: str
447 :type root_path: str
448 :param ret_type: Set the return type. Valid options are
448 :param ret_type: Set the return type. Valid options are
449 ``all`` (default), ``files`` and ``dirs``.
449 ``all`` (default), ``files`` and ``dirs``.
450 :type ret_type: Optional(str)
450 :type ret_type: Optional(str)
451 :param details: Returns extended information about nodes, such as
451 :param details: Returns extended information about nodes, such as
452 md5, binary, and or content. The valid options are ``basic`` and
452 md5, binary, and or content. The valid options are ``basic`` and
453 ``full``.
453 ``full``.
454 :type details: Optional(str)
454 :type details: Optional(str)
455 :param max_file_bytes: Only return file content under this file size bytes
455 :param max_file_bytes: Only return file content under this file size bytes
456 :type details: Optional(int)
456 :type details: Optional(int)
457
457
458 Example output:
458 Example output:
459
459
460 .. code-block:: bash
460 .. code-block:: bash
461
461
462 id : <id_given_in_input>
462 id : <id_given_in_input>
463 result: [
463 result: [
464 {
464 {
465 "name" : "<name>"
465 "name" : "<name>"
466 "type" : "<type>",
466 "type" : "<type>",
467 "binary": "<true|false>" (only in extended mode)
467 "binary": "<true|false>" (only in extended mode)
468 "md5" : "<md5 of file content>" (only in extended mode)
468 "md5" : "<md5 of file content>" (only in extended mode)
469 },
469 },
470 ...
470 ...
471 ]
471 ]
472 error: null
472 error: null
473 """
473 """
474
474
475 repo = get_repo_or_error(repoid)
475 repo = get_repo_or_error(repoid)
476 if not has_superadmin_permission(apiuser):
476 if not has_superadmin_permission(apiuser):
477 _perms = (
477 _perms = (
478 'repository.admin', 'repository.write', 'repository.read',)
478 'repository.admin', 'repository.write', 'repository.read',)
479 validate_repo_permissions(apiuser, repoid, repo, _perms)
479 validate_repo_permissions(apiuser, repoid, repo, _perms)
480
480
481 ret_type = Optional.extract(ret_type)
481 ret_type = Optional.extract(ret_type)
482 details = Optional.extract(details)
482 details = Optional.extract(details)
483 _extended_types = ['basic', 'full']
483 _extended_types = ['basic', 'full']
484 if details not in _extended_types:
484 if details not in _extended_types:
485 raise JSONRPCError(
485 raise JSONRPCError(
486 'ret_type must be one of %s' % (','.join(_extended_types)))
486 'ret_type must be one of %s' % (','.join(_extended_types)))
487 extended_info = False
487 extended_info = False
488 content = False
488 content = False
489 if details == 'basic':
489 if details == 'basic':
490 extended_info = True
490 extended_info = True
491
491
492 if details == 'full':
492 if details == 'full':
493 extended_info = content = True
493 extended_info = content = True
494
494
495 _map = {}
495 _map = {}
496 try:
496 try:
497 # check if repo is not empty by any chance, skip quicker if it is.
497 # check if repo is not empty by any chance, skip quicker if it is.
498 _scm = repo.scm_instance()
498 _scm = repo.scm_instance()
499 if _scm.is_empty():
499 if _scm.is_empty():
500 return []
500 return []
501
501
502 _d, _f = ScmModel().get_nodes(
502 _d, _f = ScmModel().get_nodes(
503 repo, revision, root_path, flat=False,
503 repo, revision, root_path, flat=False,
504 extended_info=extended_info, content=content,
504 extended_info=extended_info, content=content,
505 max_file_bytes=max_file_bytes)
505 max_file_bytes=max_file_bytes)
506 _map = {
506 _map = {
507 'all': _d + _f,
507 'all': _d + _f,
508 'files': _f,
508 'files': _f,
509 'dirs': _d,
509 'dirs': _d,
510 }
510 }
511 return _map[ret_type]
511 return _map[ret_type]
512 except KeyError:
512 except KeyError:
513 raise JSONRPCError(
513 raise JSONRPCError(
514 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
514 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
515 except Exception:
515 except Exception:
516 log.exception("Exception occurred while trying to get repo nodes")
516 log.exception("Exception occurred while trying to get repo nodes")
517 raise JSONRPCError(
517 raise JSONRPCError(
518 'failed to get repo: `%s` nodes' % repo.repo_name
518 'failed to get repo: `%s` nodes' % repo.repo_name
519 )
519 )
520
520
521
521
522 @jsonrpc_method()
522 @jsonrpc_method()
523 def get_repo_refs(request, apiuser, repoid):
523 def get_repo_refs(request, apiuser, repoid):
524 """
524 """
525 Returns a dictionary of current references. It returns
525 Returns a dictionary of current references. It returns
526 bookmarks, branches, closed_branches, and tags for given repository
526 bookmarks, branches, closed_branches, and tags for given repository
527
527
528 It's possible to specify ret_type to show only `files` or `dirs`.
528 It's possible to specify ret_type to show only `files` or `dirs`.
529
529
530 This command can only be run using an |authtoken| with admin rights,
530 This command can only be run using an |authtoken| with admin rights,
531 or users with at least read rights to |repos|.
531 or users with at least read rights to |repos|.
532
532
533 :param apiuser: This is filled automatically from the |authtoken|.
533 :param apiuser: This is filled automatically from the |authtoken|.
534 :type apiuser: AuthUser
534 :type apiuser: AuthUser
535 :param repoid: The repository name or repository ID.
535 :param repoid: The repository name or repository ID.
536 :type repoid: str or int
536 :type repoid: str or int
537
537
538 Example output:
538 Example output:
539
539
540 .. code-block:: bash
540 .. code-block:: bash
541
541
542 id : <id_given_in_input>
542 id : <id_given_in_input>
543 "result": {
543 "result": {
544 "bookmarks": {
544 "bookmarks": {
545 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
545 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
546 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
546 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
547 },
547 },
548 "branches": {
548 "branches": {
549 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
549 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
550 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
550 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
551 },
551 },
552 "branches_closed": {},
552 "branches_closed": {},
553 "tags": {
553 "tags": {
554 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
554 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
555 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
555 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
556 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
556 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
557 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
557 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
558 }
558 }
559 }
559 }
560 error: null
560 error: null
561 """
561 """
562
562
563 repo = get_repo_or_error(repoid)
563 repo = get_repo_or_error(repoid)
564 if not has_superadmin_permission(apiuser):
564 if not has_superadmin_permission(apiuser):
565 _perms = ('repository.admin', 'repository.write', 'repository.read',)
565 _perms = ('repository.admin', 'repository.write', 'repository.read',)
566 validate_repo_permissions(apiuser, repoid, repo, _perms)
566 validate_repo_permissions(apiuser, repoid, repo, _perms)
567
567
568 try:
568 try:
569 # check if repo is not empty by any chance, skip quicker if it is.
569 # check if repo is not empty by any chance, skip quicker if it is.
570 vcs_instance = repo.scm_instance()
570 vcs_instance = repo.scm_instance()
571 refs = vcs_instance.refs()
571 refs = vcs_instance.refs()
572 return refs
572 return refs
573 except Exception:
573 except Exception:
574 log.exception("Exception occurred while trying to get repo refs")
574 log.exception("Exception occurred while trying to get repo refs")
575 raise JSONRPCError(
575 raise JSONRPCError(
576 'failed to get repo: `%s` references' % repo.repo_name
576 'failed to get repo: `%s` references' % repo.repo_name
577 )
577 )
578
578
579
579
580 @jsonrpc_method()
580 @jsonrpc_method()
581 def create_repo(
581 def create_repo(
582 request, apiuser, repo_name, repo_type,
582 request, apiuser, repo_name, repo_type,
583 owner=Optional(OAttr('apiuser')),
583 owner=Optional(OAttr('apiuser')),
584 description=Optional(''),
584 description=Optional(''),
585 private=Optional(False),
585 private=Optional(False),
586 clone_uri=Optional(None),
586 clone_uri=Optional(None),
587 landing_rev=Optional('rev:tip'),
587 landing_rev=Optional('rev:tip'),
588 enable_statistics=Optional(False),
588 enable_statistics=Optional(False),
589 enable_locking=Optional(False),
589 enable_locking=Optional(False),
590 enable_downloads=Optional(False),
590 enable_downloads=Optional(False),
591 copy_permissions=Optional(False)):
591 copy_permissions=Optional(False)):
592 """
592 """
593 Creates a repository.
593 Creates a repository.
594
594
595 * If the repository name contains "/", repository will be created inside
595 * If the repository name contains "/", repository will be created inside
596 a repository group or nested repository groups
596 a repository group or nested repository groups
597
597
598 For example "foo/bar/repo1" will create |repo| called "repo1" inside
598 For example "foo/bar/repo1" will create |repo| called "repo1" inside
599 group "foo/bar". You have to have permissions to access and write to
599 group "foo/bar". You have to have permissions to access and write to
600 the last repository group ("bar" in this example)
600 the last repository group ("bar" in this example)
601
601
602 This command can only be run using an |authtoken| with at least
602 This command can only be run using an |authtoken| with at least
603 permissions to create repositories, or write permissions to
603 permissions to create repositories, or write permissions to
604 parent repository groups.
604 parent repository groups.
605
605
606 :param apiuser: This is filled automatically from the |authtoken|.
606 :param apiuser: This is filled automatically from the |authtoken|.
607 :type apiuser: AuthUser
607 :type apiuser: AuthUser
608 :param repo_name: Set the repository name.
608 :param repo_name: Set the repository name.
609 :type repo_name: str
609 :type repo_name: str
610 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
610 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
611 :type repo_type: str
611 :type repo_type: str
612 :param owner: user_id or username
612 :param owner: user_id or username
613 :type owner: Optional(str)
613 :type owner: Optional(str)
614 :param description: Set the repository description.
614 :param description: Set the repository description.
615 :type description: Optional(str)
615 :type description: Optional(str)
616 :param private: set repository as private
616 :param private: set repository as private
617 :type private: bool
617 :type private: bool
618 :param clone_uri: set clone_uri
618 :param clone_uri: set clone_uri
619 :type clone_uri: str
619 :type clone_uri: str
620 :param landing_rev: <rev_type>:<rev>
620 :param landing_rev: <rev_type>:<rev>
621 :type landing_rev: str
621 :type landing_rev: str
622 :param enable_locking:
622 :param enable_locking:
623 :type enable_locking: bool
623 :type enable_locking: bool
624 :param enable_downloads:
624 :param enable_downloads:
625 :type enable_downloads: bool
625 :type enable_downloads: bool
626 :param enable_statistics:
626 :param enable_statistics:
627 :type enable_statistics: bool
627 :type enable_statistics: bool
628 :param copy_permissions: Copy permission from group in which the
628 :param copy_permissions: Copy permission from group in which the
629 repository is being created.
629 repository is being created.
630 :type copy_permissions: bool
630 :type copy_permissions: bool
631
631
632
632
633 Example output:
633 Example output:
634
634
635 .. code-block:: bash
635 .. code-block:: bash
636
636
637 id : <id_given_in_input>
637 id : <id_given_in_input>
638 result: {
638 result: {
639 "msg": "Created new repository `<reponame>`",
639 "msg": "Created new repository `<reponame>`",
640 "success": true,
640 "success": true,
641 "task": "<celery task id or None if done sync>"
641 "task": "<celery task id or None if done sync>"
642 }
642 }
643 error: null
643 error: null
644
644
645
645
646 Example error output:
646 Example error output:
647
647
648 .. code-block:: bash
648 .. code-block:: bash
649
649
650 id : <id_given_in_input>
650 id : <id_given_in_input>
651 result : null
651 result : null
652 error : {
652 error : {
653 'failed to create repository `<repo_name>`'
653 'failed to create repository `<repo_name>`'
654 }
654 }
655
655
656 """
656 """
657
657
658 owner = validate_set_owner_permissions(apiuser, owner)
658 owner = validate_set_owner_permissions(apiuser, owner)
659
659
660 description = Optional.extract(description)
660 description = Optional.extract(description)
661 copy_permissions = Optional.extract(copy_permissions)
661 copy_permissions = Optional.extract(copy_permissions)
662 clone_uri = Optional.extract(clone_uri)
662 clone_uri = Optional.extract(clone_uri)
663 landing_commit_ref = Optional.extract(landing_rev)
663 landing_commit_ref = Optional.extract(landing_rev)
664
664
665 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
665 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
666 if isinstance(private, Optional):
666 if isinstance(private, Optional):
667 private = defs.get('repo_private') or Optional.extract(private)
667 private = defs.get('repo_private') or Optional.extract(private)
668 if isinstance(repo_type, Optional):
668 if isinstance(repo_type, Optional):
669 repo_type = defs.get('repo_type')
669 repo_type = defs.get('repo_type')
670 if isinstance(enable_statistics, Optional):
670 if isinstance(enable_statistics, Optional):
671 enable_statistics = defs.get('repo_enable_statistics')
671 enable_statistics = defs.get('repo_enable_statistics')
672 if isinstance(enable_locking, Optional):
672 if isinstance(enable_locking, Optional):
673 enable_locking = defs.get('repo_enable_locking')
673 enable_locking = defs.get('repo_enable_locking')
674 if isinstance(enable_downloads, Optional):
674 if isinstance(enable_downloads, Optional):
675 enable_downloads = defs.get('repo_enable_downloads')
675 enable_downloads = defs.get('repo_enable_downloads')
676
676
677 schema = repo_schema.RepoSchema().bind(
677 schema = repo_schema.RepoSchema().bind(
678 repo_type_options=rhodecode.BACKENDS.keys(),
678 repo_type_options=rhodecode.BACKENDS.keys(),
679 # user caller
679 # user caller
680 user=apiuser)
680 user=apiuser)
681
681
682 try:
682 try:
683 schema_data = schema.deserialize(dict(
683 schema_data = schema.deserialize(dict(
684 repo_name=repo_name,
684 repo_name=repo_name,
685 repo_type=repo_type,
685 repo_type=repo_type,
686 repo_owner=owner.username,
686 repo_owner=owner.username,
687 repo_description=description,
687 repo_description=description,
688 repo_landing_commit_ref=landing_commit_ref,
688 repo_landing_commit_ref=landing_commit_ref,
689 repo_clone_uri=clone_uri,
689 repo_clone_uri=clone_uri,
690 repo_private=private,
690 repo_private=private,
691 repo_copy_permissions=copy_permissions,
691 repo_copy_permissions=copy_permissions,
692 repo_enable_statistics=enable_statistics,
692 repo_enable_statistics=enable_statistics,
693 repo_enable_downloads=enable_downloads,
693 repo_enable_downloads=enable_downloads,
694 repo_enable_locking=enable_locking))
694 repo_enable_locking=enable_locking))
695 except validation_schema.Invalid as err:
695 except validation_schema.Invalid as err:
696 raise JSONRPCValidationError(colander_exc=err)
696 raise JSONRPCValidationError(colander_exc=err)
697
697
698 try:
698 try:
699 data = {
699 data = {
700 'owner': owner,
700 'owner': owner,
701 'repo_name': schema_data['repo_group']['repo_name_without_group'],
701 'repo_name': schema_data['repo_group']['repo_name_without_group'],
702 'repo_name_full': schema_data['repo_name'],
702 'repo_name_full': schema_data['repo_name'],
703 'repo_group': schema_data['repo_group']['repo_group_id'],
703 'repo_group': schema_data['repo_group']['repo_group_id'],
704 'repo_type': schema_data['repo_type'],
704 'repo_type': schema_data['repo_type'],
705 'repo_description': schema_data['repo_description'],
705 'repo_description': schema_data['repo_description'],
706 'repo_private': schema_data['repo_private'],
706 'repo_private': schema_data['repo_private'],
707 'clone_uri': schema_data['repo_clone_uri'],
707 'clone_uri': schema_data['repo_clone_uri'],
708 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
708 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
709 'enable_statistics': schema_data['repo_enable_statistics'],
709 'enable_statistics': schema_data['repo_enable_statistics'],
710 'enable_locking': schema_data['repo_enable_locking'],
710 'enable_locking': schema_data['repo_enable_locking'],
711 'enable_downloads': schema_data['repo_enable_downloads'],
711 'enable_downloads': schema_data['repo_enable_downloads'],
712 'repo_copy_permissions': schema_data['repo_copy_permissions'],
712 'repo_copy_permissions': schema_data['repo_copy_permissions'],
713 }
713 }
714
714
715 task = RepoModel().create(form_data=data, cur_user=owner)
715 task = RepoModel().create(form_data=data, cur_user=owner)
716 from celery.result import BaseAsyncResult
716 from celery.result import BaseAsyncResult
717 task_id = None
717 task_id = None
718 if isinstance(task, BaseAsyncResult):
718 if isinstance(task, BaseAsyncResult):
719 task_id = task.task_id
719 task_id = task.task_id
720 # no commit, it's done in RepoModel, or async via celery
720 # no commit, it's done in RepoModel, or async via celery
721 return {
721 return {
722 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
722 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
723 'success': True, # cannot return the repo data here since fork
723 'success': True, # cannot return the repo data here since fork
724 # can be done async
724 # can be done async
725 'task': task_id
725 'task': task_id
726 }
726 }
727 except Exception:
727 except Exception:
728 log.exception(
728 log.exception(
729 u"Exception while trying to create the repository %s",
729 u"Exception while trying to create the repository %s",
730 schema_data['repo_name'])
730 schema_data['repo_name'])
731 raise JSONRPCError(
731 raise JSONRPCError(
732 'failed to create repository `%s`' % (schema_data['repo_name'],))
732 'failed to create repository `%s`' % (schema_data['repo_name'],))
733
733
734
734
735 @jsonrpc_method()
735 @jsonrpc_method()
736 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
736 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
737 description=Optional('')):
737 description=Optional('')):
738 """
738 """
739 Adds an extra field to a repository.
739 Adds an extra field to a repository.
740
740
741 This command can only be run using an |authtoken| with at least
741 This command can only be run using an |authtoken| with at least
742 write permissions to the |repo|.
742 write permissions to the |repo|.
743
743
744 :param apiuser: This is filled automatically from the |authtoken|.
744 :param apiuser: This is filled automatically from the |authtoken|.
745 :type apiuser: AuthUser
745 :type apiuser: AuthUser
746 :param repoid: Set the repository name or repository id.
746 :param repoid: Set the repository name or repository id.
747 :type repoid: str or int
747 :type repoid: str or int
748 :param key: Create a unique field key for this repository.
748 :param key: Create a unique field key for this repository.
749 :type key: str
749 :type key: str
750 :param label:
750 :param label:
751 :type label: Optional(str)
751 :type label: Optional(str)
752 :param description:
752 :param description:
753 :type description: Optional(str)
753 :type description: Optional(str)
754 """
754 """
755 repo = get_repo_or_error(repoid)
755 repo = get_repo_or_error(repoid)
756 if not has_superadmin_permission(apiuser):
756 if not has_superadmin_permission(apiuser):
757 _perms = ('repository.admin',)
757 _perms = ('repository.admin',)
758 validate_repo_permissions(apiuser, repoid, repo, _perms)
758 validate_repo_permissions(apiuser, repoid, repo, _perms)
759
759
760 label = Optional.extract(label) or key
760 label = Optional.extract(label) or key
761 description = Optional.extract(description)
761 description = Optional.extract(description)
762
762
763 field = RepositoryField.get_by_key_name(key, repo)
763 field = RepositoryField.get_by_key_name(key, repo)
764 if field:
764 if field:
765 raise JSONRPCError('Field with key '
765 raise JSONRPCError('Field with key '
766 '`%s` exists for repo `%s`' % (key, repoid))
766 '`%s` exists for repo `%s`' % (key, repoid))
767
767
768 try:
768 try:
769 RepoModel().add_repo_field(repo, key, field_label=label,
769 RepoModel().add_repo_field(repo, key, field_label=label,
770 field_desc=description)
770 field_desc=description)
771 Session().commit()
771 Session().commit()
772 return {
772 return {
773 'msg': "Added new repository field `%s`" % (key,),
773 'msg': "Added new repository field `%s`" % (key,),
774 'success': True,
774 'success': True,
775 }
775 }
776 except Exception:
776 except Exception:
777 log.exception("Exception occurred while trying to add field to repo")
777 log.exception("Exception occurred while trying to add field to repo")
778 raise JSONRPCError(
778 raise JSONRPCError(
779 'failed to create new field for repository `%s`' % (repoid,))
779 'failed to create new field for repository `%s`' % (repoid,))
780
780
781
781
782 @jsonrpc_method()
782 @jsonrpc_method()
783 def remove_field_from_repo(request, apiuser, repoid, key):
783 def remove_field_from_repo(request, apiuser, repoid, key):
784 """
784 """
785 Removes an extra field from a repository.
785 Removes an extra field from a repository.
786
786
787 This command can only be run using an |authtoken| with at least
787 This command can only be run using an |authtoken| with at least
788 write permissions to the |repo|.
788 write permissions to the |repo|.
789
789
790 :param apiuser: This is filled automatically from the |authtoken|.
790 :param apiuser: This is filled automatically from the |authtoken|.
791 :type apiuser: AuthUser
791 :type apiuser: AuthUser
792 :param repoid: Set the repository name or repository ID.
792 :param repoid: Set the repository name or repository ID.
793 :type repoid: str or int
793 :type repoid: str or int
794 :param key: Set the unique field key for this repository.
794 :param key: Set the unique field key for this repository.
795 :type key: str
795 :type key: str
796 """
796 """
797
797
798 repo = get_repo_or_error(repoid)
798 repo = get_repo_or_error(repoid)
799 if not has_superadmin_permission(apiuser):
799 if not has_superadmin_permission(apiuser):
800 _perms = ('repository.admin',)
800 _perms = ('repository.admin',)
801 validate_repo_permissions(apiuser, repoid, repo, _perms)
801 validate_repo_permissions(apiuser, repoid, repo, _perms)
802
802
803 field = RepositoryField.get_by_key_name(key, repo)
803 field = RepositoryField.get_by_key_name(key, repo)
804 if not field:
804 if not field:
805 raise JSONRPCError('Field with key `%s` does not '
805 raise JSONRPCError('Field with key `%s` does not '
806 'exists for repo `%s`' % (key, repoid))
806 'exists for repo `%s`' % (key, repoid))
807
807
808 try:
808 try:
809 RepoModel().delete_repo_field(repo, field_key=key)
809 RepoModel().delete_repo_field(repo, field_key=key)
810 Session().commit()
810 Session().commit()
811 return {
811 return {
812 'msg': "Deleted repository field `%s`" % (key,),
812 'msg': "Deleted repository field `%s`" % (key,),
813 'success': True,
813 'success': True,
814 }
814 }
815 except Exception:
815 except Exception:
816 log.exception(
816 log.exception(
817 "Exception occurred while trying to delete field from repo")
817 "Exception occurred while trying to delete field from repo")
818 raise JSONRPCError(
818 raise JSONRPCError(
819 'failed to delete field for repository `%s`' % (repoid,))
819 'failed to delete field for repository `%s`' % (repoid,))
820
820
821
821
822 @jsonrpc_method()
822 @jsonrpc_method()
823 def update_repo(
823 def update_repo(
824 request, apiuser, repoid, repo_name=Optional(None),
824 request, apiuser, repoid, repo_name=Optional(None),
825 owner=Optional(OAttr('apiuser')), description=Optional(''),
825 owner=Optional(OAttr('apiuser')), description=Optional(''),
826 private=Optional(False), clone_uri=Optional(None),
826 private=Optional(False), clone_uri=Optional(None),
827 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
827 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
828 enable_statistics=Optional(False),
828 enable_statistics=Optional(False),
829 enable_locking=Optional(False),
829 enable_locking=Optional(False),
830 enable_downloads=Optional(False), fields=Optional('')):
830 enable_downloads=Optional(False), fields=Optional('')):
831 """
831 """
832 Updates a repository with the given information.
832 Updates a repository with the given information.
833
833
834 This command can only be run using an |authtoken| with at least
834 This command can only be run using an |authtoken| with at least
835 admin permissions to the |repo|.
835 admin permissions to the |repo|.
836
836
837 * If the repository name contains "/", repository will be updated
837 * If the repository name contains "/", repository will be updated
838 accordingly with a repository group or nested repository groups
838 accordingly with a repository group or nested repository groups
839
839
840 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
840 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
841 called "repo-test" and place it inside group "foo/bar".
841 called "repo-test" and place it inside group "foo/bar".
842 You have to have permissions to access and write to the last repository
842 You have to have permissions to access and write to the last repository
843 group ("bar" in this example)
843 group ("bar" in this example)
844
844
845 :param apiuser: This is filled automatically from the |authtoken|.
845 :param apiuser: This is filled automatically from the |authtoken|.
846 :type apiuser: AuthUser
846 :type apiuser: AuthUser
847 :param repoid: repository name or repository ID.
847 :param repoid: repository name or repository ID.
848 :type repoid: str or int
848 :type repoid: str or int
849 :param repo_name: Update the |repo| name, including the
849 :param repo_name: Update the |repo| name, including the
850 repository group it's in.
850 repository group it's in.
851 :type repo_name: str
851 :type repo_name: str
852 :param owner: Set the |repo| owner.
852 :param owner: Set the |repo| owner.
853 :type owner: str
853 :type owner: str
854 :param fork_of: Set the |repo| as fork of another |repo|.
854 :param fork_of: Set the |repo| as fork of another |repo|.
855 :type fork_of: str
855 :type fork_of: str
856 :param description: Update the |repo| description.
856 :param description: Update the |repo| description.
857 :type description: str
857 :type description: str
858 :param private: Set the |repo| as private. (True | False)
858 :param private: Set the |repo| as private. (True | False)
859 :type private: bool
859 :type private: bool
860 :param clone_uri: Update the |repo| clone URI.
860 :param clone_uri: Update the |repo| clone URI.
861 :type clone_uri: str
861 :type clone_uri: str
862 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
862 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
863 :type landing_rev: str
863 :type landing_rev: str
864 :param enable_statistics: Enable statistics on the |repo|, (True | False).
864 :param enable_statistics: Enable statistics on the |repo|, (True | False).
865 :type enable_statistics: bool
865 :type enable_statistics: bool
866 :param enable_locking: Enable |repo| locking.
866 :param enable_locking: Enable |repo| locking.
867 :type enable_locking: bool
867 :type enable_locking: bool
868 :param enable_downloads: Enable downloads from the |repo|, (True | False).
868 :param enable_downloads: Enable downloads from the |repo|, (True | False).
869 :type enable_downloads: bool
869 :type enable_downloads: bool
870 :param fields: Add extra fields to the |repo|. Use the following
870 :param fields: Add extra fields to the |repo|. Use the following
871 example format: ``field_key=field_val,field_key2=fieldval2``.
871 example format: ``field_key=field_val,field_key2=fieldval2``.
872 Escape ', ' with \,
872 Escape ', ' with \,
873 :type fields: str
873 :type fields: str
874 """
874 """
875
875
876 repo = get_repo_or_error(repoid)
876 repo = get_repo_or_error(repoid)
877
877
878 include_secrets = False
878 include_secrets = False
879 if not has_superadmin_permission(apiuser):
879 if not has_superadmin_permission(apiuser):
880 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
880 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
881 else:
881 else:
882 include_secrets = True
882 include_secrets = True
883
883
884 updates = dict(
884 updates = dict(
885 repo_name=repo_name
885 repo_name=repo_name
886 if not isinstance(repo_name, Optional) else repo.repo_name,
886 if not isinstance(repo_name, Optional) else repo.repo_name,
887
887
888 fork_id=fork_of
888 fork_id=fork_of
889 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
889 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
890
890
891 user=owner
891 user=owner
892 if not isinstance(owner, Optional) else repo.user.username,
892 if not isinstance(owner, Optional) else repo.user.username,
893
893
894 repo_description=description
894 repo_description=description
895 if not isinstance(description, Optional) else repo.description,
895 if not isinstance(description, Optional) else repo.description,
896
896
897 repo_private=private
897 repo_private=private
898 if not isinstance(private, Optional) else repo.private,
898 if not isinstance(private, Optional) else repo.private,
899
899
900 clone_uri=clone_uri
900 clone_uri=clone_uri
901 if not isinstance(clone_uri, Optional) else repo.clone_uri,
901 if not isinstance(clone_uri, Optional) else repo.clone_uri,
902
902
903 repo_landing_rev=landing_rev
903 repo_landing_rev=landing_rev
904 if not isinstance(landing_rev, Optional) else repo._landing_revision,
904 if not isinstance(landing_rev, Optional) else repo._landing_revision,
905
905
906 repo_enable_statistics=enable_statistics
906 repo_enable_statistics=enable_statistics
907 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
907 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
908
908
909 repo_enable_locking=enable_locking
909 repo_enable_locking=enable_locking
910 if not isinstance(enable_locking, Optional) else repo.enable_locking,
910 if not isinstance(enable_locking, Optional) else repo.enable_locking,
911
911
912 repo_enable_downloads=enable_downloads
912 repo_enable_downloads=enable_downloads
913 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
913 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
914
914
915 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
915 ref_choices, _labels = ScmModel().get_repo_landing_revs(repo=repo)
916
916
917 schema = repo_schema.RepoSchema().bind(
917 schema = repo_schema.RepoSchema().bind(
918 repo_type_options=rhodecode.BACKENDS.keys(),
918 repo_type_options=rhodecode.BACKENDS.keys(),
919 repo_ref_options=ref_choices,
919 repo_ref_options=ref_choices,
920 # user caller
920 # user caller
921 user=apiuser,
921 user=apiuser,
922 old_values=repo.get_api_data())
922 old_values=repo.get_api_data())
923 try:
923 try:
924 schema_data = schema.deserialize(dict(
924 schema_data = schema.deserialize(dict(
925 # we save old value, users cannot change type
925 # we save old value, users cannot change type
926 repo_type=repo.repo_type,
926 repo_type=repo.repo_type,
927
927
928 repo_name=updates['repo_name'],
928 repo_name=updates['repo_name'],
929 repo_owner=updates['user'],
929 repo_owner=updates['user'],
930 repo_description=updates['repo_description'],
930 repo_description=updates['repo_description'],
931 repo_clone_uri=updates['clone_uri'],
931 repo_clone_uri=updates['clone_uri'],
932 repo_fork_of=updates['fork_id'],
932 repo_fork_of=updates['fork_id'],
933 repo_private=updates['repo_private'],
933 repo_private=updates['repo_private'],
934 repo_landing_commit_ref=updates['repo_landing_rev'],
934 repo_landing_commit_ref=updates['repo_landing_rev'],
935 repo_enable_statistics=updates['repo_enable_statistics'],
935 repo_enable_statistics=updates['repo_enable_statistics'],
936 repo_enable_downloads=updates['repo_enable_downloads'],
936 repo_enable_downloads=updates['repo_enable_downloads'],
937 repo_enable_locking=updates['repo_enable_locking']))
937 repo_enable_locking=updates['repo_enable_locking']))
938 except validation_schema.Invalid as err:
938 except validation_schema.Invalid as err:
939 raise JSONRPCValidationError(colander_exc=err)
939 raise JSONRPCValidationError(colander_exc=err)
940
940
941 # save validated data back into the updates dict
941 # save validated data back into the updates dict
942 validated_updates = dict(
942 validated_updates = dict(
943 repo_name=schema_data['repo_group']['repo_name_without_group'],
943 repo_name=schema_data['repo_group']['repo_name_without_group'],
944 repo_group=schema_data['repo_group']['repo_group_id'],
944 repo_group=schema_data['repo_group']['repo_group_id'],
945
945
946 user=schema_data['repo_owner'],
946 user=schema_data['repo_owner'],
947 repo_description=schema_data['repo_description'],
947 repo_description=schema_data['repo_description'],
948 repo_private=schema_data['repo_private'],
948 repo_private=schema_data['repo_private'],
949 clone_uri=schema_data['repo_clone_uri'],
949 clone_uri=schema_data['repo_clone_uri'],
950 repo_landing_rev=schema_data['repo_landing_commit_ref'],
950 repo_landing_rev=schema_data['repo_landing_commit_ref'],
951 repo_enable_statistics=schema_data['repo_enable_statistics'],
951 repo_enable_statistics=schema_data['repo_enable_statistics'],
952 repo_enable_locking=schema_data['repo_enable_locking'],
952 repo_enable_locking=schema_data['repo_enable_locking'],
953 repo_enable_downloads=schema_data['repo_enable_downloads'],
953 repo_enable_downloads=schema_data['repo_enable_downloads'],
954 )
954 )
955
955
956 if schema_data['repo_fork_of']:
956 if schema_data['repo_fork_of']:
957 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
957 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
958 validated_updates['fork_id'] = fork_repo.repo_id
958 validated_updates['fork_id'] = fork_repo.repo_id
959
959
960 # extra fields
960 # extra fields
961 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
961 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
962 if fields:
962 if fields:
963 validated_updates.update(fields)
963 validated_updates.update(fields)
964
964
965 try:
965 try:
966 RepoModel().update(repo, **validated_updates)
966 RepoModel().update(repo, **validated_updates)
967 Session().commit()
967 Session().commit()
968 return {
968 return {
969 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
969 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
970 'repository': repo.get_api_data(include_secrets=include_secrets)
970 'repository': repo.get_api_data(include_secrets=include_secrets)
971 }
971 }
972 except Exception:
972 except Exception:
973 log.exception(
973 log.exception(
974 u"Exception while trying to update the repository %s",
974 u"Exception while trying to update the repository %s",
975 repoid)
975 repoid)
976 raise JSONRPCError('failed to update repo `%s`' % repoid)
976 raise JSONRPCError('failed to update repo `%s`' % repoid)
977
977
978
978
979 @jsonrpc_method()
979 @jsonrpc_method()
980 def fork_repo(request, apiuser, repoid, fork_name,
980 def fork_repo(request, apiuser, repoid, fork_name,
981 owner=Optional(OAttr('apiuser')),
981 owner=Optional(OAttr('apiuser')),
982 description=Optional(''),
982 description=Optional(''),
983 private=Optional(False),
983 private=Optional(False),
984 clone_uri=Optional(None),
984 clone_uri=Optional(None),
985 landing_rev=Optional('rev:tip'),
985 landing_rev=Optional('rev:tip'),
986 copy_permissions=Optional(False)):
986 copy_permissions=Optional(False)):
987 """
987 """
988 Creates a fork of the specified |repo|.
988 Creates a fork of the specified |repo|.
989
989
990 * If the fork_name contains "/", fork will be created inside
990 * If the fork_name contains "/", fork will be created inside
991 a repository group or nested repository groups
991 a repository group or nested repository groups
992
992
993 For example "foo/bar/fork-repo" will create fork called "fork-repo"
993 For example "foo/bar/fork-repo" will create fork called "fork-repo"
994 inside group "foo/bar". You have to have permissions to access and
994 inside group "foo/bar". You have to have permissions to access and
995 write to the last repository group ("bar" in this example)
995 write to the last repository group ("bar" in this example)
996
996
997 This command can only be run using an |authtoken| with minimum
997 This command can only be run using an |authtoken| with minimum
998 read permissions of the forked repo, create fork permissions for an user.
998 read permissions of the forked repo, create fork permissions for an user.
999
999
1000 :param apiuser: This is filled automatically from the |authtoken|.
1000 :param apiuser: This is filled automatically from the |authtoken|.
1001 :type apiuser: AuthUser
1001 :type apiuser: AuthUser
1002 :param repoid: Set repository name or repository ID.
1002 :param repoid: Set repository name or repository ID.
1003 :type repoid: str or int
1003 :type repoid: str or int
1004 :param fork_name: Set the fork name, including it's repository group membership.
1004 :param fork_name: Set the fork name, including it's repository group membership.
1005 :type fork_name: str
1005 :type fork_name: str
1006 :param owner: Set the fork owner.
1006 :param owner: Set the fork owner.
1007 :type owner: str
1007 :type owner: str
1008 :param description: Set the fork description.
1008 :param description: Set the fork description.
1009 :type description: str
1009 :type description: str
1010 :param copy_permissions: Copy permissions from parent |repo|. The
1010 :param copy_permissions: Copy permissions from parent |repo|. The
1011 default is False.
1011 default is False.
1012 :type copy_permissions: bool
1012 :type copy_permissions: bool
1013 :param private: Make the fork private. The default is False.
1013 :param private: Make the fork private. The default is False.
1014 :type private: bool
1014 :type private: bool
1015 :param landing_rev: Set the landing revision. The default is tip.
1015 :param landing_rev: Set the landing revision. The default is tip.
1016
1016
1017 Example output:
1017 Example output:
1018
1018
1019 .. code-block:: bash
1019 .. code-block:: bash
1020
1020
1021 id : <id_for_response>
1021 id : <id_for_response>
1022 api_key : "<api_key>"
1022 api_key : "<api_key>"
1023 args: {
1023 args: {
1024 "repoid" : "<reponame or repo_id>",
1024 "repoid" : "<reponame or repo_id>",
1025 "fork_name": "<forkname>",
1025 "fork_name": "<forkname>",
1026 "owner": "<username or user_id = Optional(=apiuser)>",
1026 "owner": "<username or user_id = Optional(=apiuser)>",
1027 "description": "<description>",
1027 "description": "<description>",
1028 "copy_permissions": "<bool>",
1028 "copy_permissions": "<bool>",
1029 "private": "<bool>",
1029 "private": "<bool>",
1030 "landing_rev": "<landing_rev>"
1030 "landing_rev": "<landing_rev>"
1031 }
1031 }
1032
1032
1033 Example error output:
1033 Example error output:
1034
1034
1035 .. code-block:: bash
1035 .. code-block:: bash
1036
1036
1037 id : <id_given_in_input>
1037 id : <id_given_in_input>
1038 result: {
1038 result: {
1039 "msg": "Created fork of `<reponame>` as `<forkname>`",
1039 "msg": "Created fork of `<reponame>` as `<forkname>`",
1040 "success": true,
1040 "success": true,
1041 "task": "<celery task id or None if done sync>"
1041 "task": "<celery task id or None if done sync>"
1042 }
1042 }
1043 error: null
1043 error: null
1044
1044
1045 """
1045 """
1046
1046
1047 repo = get_repo_or_error(repoid)
1047 repo = get_repo_or_error(repoid)
1048 repo_name = repo.repo_name
1048 repo_name = repo.repo_name
1049
1049
1050 if not has_superadmin_permission(apiuser):
1050 if not has_superadmin_permission(apiuser):
1051 # check if we have at least read permission for
1051 # check if we have at least read permission for
1052 # this repo that we fork !
1052 # this repo that we fork !
1053 _perms = (
1053 _perms = (
1054 'repository.admin', 'repository.write', 'repository.read')
1054 'repository.admin', 'repository.write', 'repository.read')
1055 validate_repo_permissions(apiuser, repoid, repo, _perms)
1055 validate_repo_permissions(apiuser, repoid, repo, _perms)
1056
1056
1057 # check if the regular user has at least fork permissions as well
1057 # check if the regular user has at least fork permissions as well
1058 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1058 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1059 raise JSONRPCForbidden()
1059 raise JSONRPCForbidden()
1060
1060
1061 # check if user can set owner parameter
1061 # check if user can set owner parameter
1062 owner = validate_set_owner_permissions(apiuser, owner)
1062 owner = validate_set_owner_permissions(apiuser, owner)
1063
1063
1064 description = Optional.extract(description)
1064 description = Optional.extract(description)
1065 copy_permissions = Optional.extract(copy_permissions)
1065 copy_permissions = Optional.extract(copy_permissions)
1066 clone_uri = Optional.extract(clone_uri)
1066 clone_uri = Optional.extract(clone_uri)
1067 landing_commit_ref = Optional.extract(landing_rev)
1067 landing_commit_ref = Optional.extract(landing_rev)
1068 private = Optional.extract(private)
1068 private = Optional.extract(private)
1069
1069
1070 schema = repo_schema.RepoSchema().bind(
1070 schema = repo_schema.RepoSchema().bind(
1071 repo_type_options=rhodecode.BACKENDS.keys(),
1071 repo_type_options=rhodecode.BACKENDS.keys(),
1072 # user caller
1072 # user caller
1073 user=apiuser)
1073 user=apiuser)
1074
1074
1075 try:
1075 try:
1076 schema_data = schema.deserialize(dict(
1076 schema_data = schema.deserialize(dict(
1077 repo_name=fork_name,
1077 repo_name=fork_name,
1078 repo_type=repo.repo_type,
1078 repo_type=repo.repo_type,
1079 repo_owner=owner.username,
1079 repo_owner=owner.username,
1080 repo_description=description,
1080 repo_description=description,
1081 repo_landing_commit_ref=landing_commit_ref,
1081 repo_landing_commit_ref=landing_commit_ref,
1082 repo_clone_uri=clone_uri,
1082 repo_clone_uri=clone_uri,
1083 repo_private=private,
1083 repo_private=private,
1084 repo_copy_permissions=copy_permissions))
1084 repo_copy_permissions=copy_permissions))
1085 except validation_schema.Invalid as err:
1085 except validation_schema.Invalid as err:
1086 raise JSONRPCValidationError(colander_exc=err)
1086 raise JSONRPCValidationError(colander_exc=err)
1087
1087
1088 try:
1088 try:
1089 data = {
1089 data = {
1090 'fork_parent_id': repo.repo_id,
1090 'fork_parent_id': repo.repo_id,
1091
1091
1092 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1092 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1093 'repo_name_full': schema_data['repo_name'],
1093 'repo_name_full': schema_data['repo_name'],
1094 'repo_group': schema_data['repo_group']['repo_group_id'],
1094 'repo_group': schema_data['repo_group']['repo_group_id'],
1095 'repo_type': schema_data['repo_type'],
1095 'repo_type': schema_data['repo_type'],
1096 'description': schema_data['repo_description'],
1096 'description': schema_data['repo_description'],
1097 'private': schema_data['repo_private'],
1097 'private': schema_data['repo_private'],
1098 'copy_permissions': schema_data['repo_copy_permissions'],
1098 'copy_permissions': schema_data['repo_copy_permissions'],
1099 'landing_rev': schema_data['repo_landing_commit_ref'],
1099 'landing_rev': schema_data['repo_landing_commit_ref'],
1100 }
1100 }
1101
1101
1102 task = RepoModel().create_fork(data, cur_user=owner)
1102 task = RepoModel().create_fork(data, cur_user=owner)
1103 # no commit, it's done in RepoModel, or async via celery
1103 # no commit, it's done in RepoModel, or async via celery
1104 from celery.result import BaseAsyncResult
1104 from celery.result import BaseAsyncResult
1105 task_id = None
1105 task_id = None
1106 if isinstance(task, BaseAsyncResult):
1106 if isinstance(task, BaseAsyncResult):
1107 task_id = task.task_id
1107 task_id = task.task_id
1108 return {
1108 return {
1109 'msg': 'Created fork of `%s` as `%s`' % (
1109 'msg': 'Created fork of `%s` as `%s`' % (
1110 repo.repo_name, schema_data['repo_name']),
1110 repo.repo_name, schema_data['repo_name']),
1111 'success': True, # cannot return the repo data here since fork
1111 'success': True, # cannot return the repo data here since fork
1112 # can be done async
1112 # can be done async
1113 'task': task_id
1113 'task': task_id
1114 }
1114 }
1115 except Exception:
1115 except Exception:
1116 log.exception(
1116 log.exception(
1117 u"Exception while trying to create fork %s",
1117 u"Exception while trying to create fork %s",
1118 schema_data['repo_name'])
1118 schema_data['repo_name'])
1119 raise JSONRPCError(
1119 raise JSONRPCError(
1120 'failed to fork repository `%s` as `%s`' % (
1120 'failed to fork repository `%s` as `%s`' % (
1121 repo_name, schema_data['repo_name']))
1121 repo_name, schema_data['repo_name']))
1122
1122
1123
1123
1124 @jsonrpc_method()
1124 @jsonrpc_method()
1125 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1125 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1126 """
1126 """
1127 Deletes a repository.
1127 Deletes a repository.
1128
1128
1129 * When the `forks` parameter is set it's possible to detach or delete
1129 * When the `forks` parameter is set it's possible to detach or delete
1130 forks of deleted repository.
1130 forks of deleted repository.
1131
1131
1132 This command can only be run using an |authtoken| with admin
1132 This command can only be run using an |authtoken| with admin
1133 permissions on the |repo|.
1133 permissions on the |repo|.
1134
1134
1135 :param apiuser: This is filled automatically from the |authtoken|.
1135 :param apiuser: This is filled automatically from the |authtoken|.
1136 :type apiuser: AuthUser
1136 :type apiuser: AuthUser
1137 :param repoid: Set the repository name or repository ID.
1137 :param repoid: Set the repository name or repository ID.
1138 :type repoid: str or int
1138 :type repoid: str or int
1139 :param forks: Set to `detach` or `delete` forks from the |repo|.
1139 :param forks: Set to `detach` or `delete` forks from the |repo|.
1140 :type forks: Optional(str)
1140 :type forks: Optional(str)
1141
1141
1142 Example error output:
1142 Example error output:
1143
1143
1144 .. code-block:: bash
1144 .. code-block:: bash
1145
1145
1146 id : <id_given_in_input>
1146 id : <id_given_in_input>
1147 result: {
1147 result: {
1148 "msg": "Deleted repository `<reponame>`",
1148 "msg": "Deleted repository `<reponame>`",
1149 "success": true
1149 "success": true
1150 }
1150 }
1151 error: null
1151 error: null
1152 """
1152 """
1153
1153
1154 repo = get_repo_or_error(repoid)
1154 repo = get_repo_or_error(repoid)
1155 if not has_superadmin_permission(apiuser):
1155 if not has_superadmin_permission(apiuser):
1156 _perms = ('repository.admin',)
1156 _perms = ('repository.admin',)
1157 validate_repo_permissions(apiuser, repoid, repo, _perms)
1157 validate_repo_permissions(apiuser, repoid, repo, _perms)
1158
1158
1159 try:
1159 try:
1160 handle_forks = Optional.extract(forks)
1160 handle_forks = Optional.extract(forks)
1161 _forks_msg = ''
1161 _forks_msg = ''
1162 _forks = [f for f in repo.forks]
1162 _forks = [f for f in repo.forks]
1163 if handle_forks == 'detach':
1163 if handle_forks == 'detach':
1164 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1164 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1165 elif handle_forks == 'delete':
1165 elif handle_forks == 'delete':
1166 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1166 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1167 elif _forks:
1167 elif _forks:
1168 raise JSONRPCError(
1168 raise JSONRPCError(
1169 'Cannot delete `%s` it still contains attached forks' %
1169 'Cannot delete `%s` it still contains attached forks' %
1170 (repo.repo_name,)
1170 (repo.repo_name,)
1171 )
1171 )
1172
1172
1173 RepoModel().delete(repo, forks=forks)
1173 RepoModel().delete(repo, forks=forks)
1174 Session().commit()
1174 Session().commit()
1175 return {
1175 return {
1176 'msg': 'Deleted repository `%s`%s' % (
1176 'msg': 'Deleted repository `%s`%s' % (
1177 repo.repo_name, _forks_msg),
1177 repo.repo_name, _forks_msg),
1178 'success': True
1178 'success': True
1179 }
1179 }
1180 except Exception:
1180 except Exception:
1181 log.exception("Exception occurred while trying to delete repo")
1181 log.exception("Exception occurred while trying to delete repo")
1182 raise JSONRPCError(
1182 raise JSONRPCError(
1183 'failed to delete repository `%s`' % (repo.repo_name,)
1183 'failed to delete repository `%s`' % (repo.repo_name,)
1184 )
1184 )
1185
1185
1186
1186
1187 #TODO: marcink, change name ?
1187 #TODO: marcink, change name ?
1188 @jsonrpc_method()
1188 @jsonrpc_method()
1189 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1189 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1190 """
1190 """
1191 Invalidates the cache for the specified repository.
1191 Invalidates the cache for the specified repository.
1192
1192
1193 This command can only be run using an |authtoken| with admin rights to
1193 This command can only be run using an |authtoken| with admin rights to
1194 the specified repository.
1194 the specified repository.
1195
1195
1196 This command takes the following options:
1196 This command takes the following options:
1197
1197
1198 :param apiuser: This is filled automatically from |authtoken|.
1198 :param apiuser: This is filled automatically from |authtoken|.
1199 :type apiuser: AuthUser
1199 :type apiuser: AuthUser
1200 :param repoid: Sets the repository name or repository ID.
1200 :param repoid: Sets the repository name or repository ID.
1201 :type repoid: str or int
1201 :type repoid: str or int
1202 :param delete_keys: This deletes the invalidated keys instead of
1202 :param delete_keys: This deletes the invalidated keys instead of
1203 just flagging them.
1203 just flagging them.
1204 :type delete_keys: Optional(``True`` | ``False``)
1204 :type delete_keys: Optional(``True`` | ``False``)
1205
1205
1206 Example output:
1206 Example output:
1207
1207
1208 .. code-block:: bash
1208 .. code-block:: bash
1209
1209
1210 id : <id_given_in_input>
1210 id : <id_given_in_input>
1211 result : {
1211 result : {
1212 'msg': Cache for repository `<repository name>` was invalidated,
1212 'msg': Cache for repository `<repository name>` was invalidated,
1213 'repository': <repository name>
1213 'repository': <repository name>
1214 }
1214 }
1215 error : null
1215 error : null
1216
1216
1217 Example error output:
1217 Example error output:
1218
1218
1219 .. code-block:: bash
1219 .. code-block:: bash
1220
1220
1221 id : <id_given_in_input>
1221 id : <id_given_in_input>
1222 result : null
1222 result : null
1223 error : {
1223 error : {
1224 'Error occurred during cache invalidation action'
1224 'Error occurred during cache invalidation action'
1225 }
1225 }
1226
1226
1227 """
1227 """
1228
1228
1229 repo = get_repo_or_error(repoid)
1229 repo = get_repo_or_error(repoid)
1230 if not has_superadmin_permission(apiuser):
1230 if not has_superadmin_permission(apiuser):
1231 _perms = ('repository.admin', 'repository.write',)
1231 _perms = ('repository.admin', 'repository.write',)
1232 validate_repo_permissions(apiuser, repoid, repo, _perms)
1232 validate_repo_permissions(apiuser, repoid, repo, _perms)
1233
1233
1234 delete = Optional.extract(delete_keys)
1234 delete = Optional.extract(delete_keys)
1235 try:
1235 try:
1236 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1236 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1237 return {
1237 return {
1238 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1238 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1239 'repository': repo.repo_name
1239 'repository': repo.repo_name
1240 }
1240 }
1241 except Exception:
1241 except Exception:
1242 log.exception(
1242 log.exception(
1243 "Exception occurred while trying to invalidate repo cache")
1243 "Exception occurred while trying to invalidate repo cache")
1244 raise JSONRPCError(
1244 raise JSONRPCError(
1245 'Error occurred during cache invalidation action'
1245 'Error occurred during cache invalidation action'
1246 )
1246 )
1247
1247
1248
1248
1249 #TODO: marcink, change name ?
1249 #TODO: marcink, change name ?
1250 @jsonrpc_method()
1250 @jsonrpc_method()
1251 def lock(request, apiuser, repoid, locked=Optional(None),
1251 def lock(request, apiuser, repoid, locked=Optional(None),
1252 userid=Optional(OAttr('apiuser'))):
1252 userid=Optional(OAttr('apiuser'))):
1253 """
1253 """
1254 Sets the lock state of the specified |repo| by the given user.
1254 Sets the lock state of the specified |repo| by the given user.
1255 From more information, see :ref:`repo-locking`.
1255 From more information, see :ref:`repo-locking`.
1256
1256
1257 * If the ``userid`` option is not set, the repository is locked to the
1257 * If the ``userid`` option is not set, the repository is locked to the
1258 user who called the method.
1258 user who called the method.
1259 * If the ``locked`` parameter is not set, the current lock state of the
1259 * If the ``locked`` parameter is not set, the current lock state of the
1260 repository is displayed.
1260 repository is displayed.
1261
1261
1262 This command can only be run using an |authtoken| with admin rights to
1262 This command can only be run using an |authtoken| with admin rights to
1263 the specified repository.
1263 the specified repository.
1264
1264
1265 This command takes the following options:
1265 This command takes the following options:
1266
1266
1267 :param apiuser: This is filled automatically from the |authtoken|.
1267 :param apiuser: This is filled automatically from the |authtoken|.
1268 :type apiuser: AuthUser
1268 :type apiuser: AuthUser
1269 :param repoid: Sets the repository name or repository ID.
1269 :param repoid: Sets the repository name or repository ID.
1270 :type repoid: str or int
1270 :type repoid: str or int
1271 :param locked: Sets the lock state.
1271 :param locked: Sets the lock state.
1272 :type locked: Optional(``True`` | ``False``)
1272 :type locked: Optional(``True`` | ``False``)
1273 :param userid: Set the repository lock to this user.
1273 :param userid: Set the repository lock to this user.
1274 :type userid: Optional(str or int)
1274 :type userid: Optional(str or int)
1275
1275
1276 Example error output:
1276 Example error output:
1277
1277
1278 .. code-block:: bash
1278 .. code-block:: bash
1279
1279
1280 id : <id_given_in_input>
1280 id : <id_given_in_input>
1281 result : {
1281 result : {
1282 'repo': '<reponame>',
1282 'repo': '<reponame>',
1283 'locked': <bool: lock state>,
1283 'locked': <bool: lock state>,
1284 'locked_since': <int: lock timestamp>,
1284 'locked_since': <int: lock timestamp>,
1285 'locked_by': <username of person who made the lock>,
1285 'locked_by': <username of person who made the lock>,
1286 'lock_reason': <str: reason for locking>,
1286 'lock_reason': <str: reason for locking>,
1287 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1287 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1288 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1288 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1289 or
1289 or
1290 'msg': 'Repo `<repository name>` not locked.'
1290 'msg': 'Repo `<repository name>` not locked.'
1291 or
1291 or
1292 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1292 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1293 }
1293 }
1294 error : null
1294 error : null
1295
1295
1296 Example error output:
1296 Example error output:
1297
1297
1298 .. code-block:: bash
1298 .. code-block:: bash
1299
1299
1300 id : <id_given_in_input>
1300 id : <id_given_in_input>
1301 result : null
1301 result : null
1302 error : {
1302 error : {
1303 'Error occurred locking repository `<reponame>`'
1303 'Error occurred locking repository `<reponame>`'
1304 }
1304 }
1305 """
1305 """
1306
1306
1307 repo = get_repo_or_error(repoid)
1307 repo = get_repo_or_error(repoid)
1308 if not has_superadmin_permission(apiuser):
1308 if not has_superadmin_permission(apiuser):
1309 # check if we have at least write permission for this repo !
1309 # check if we have at least write permission for this repo !
1310 _perms = ('repository.admin', 'repository.write',)
1310 _perms = ('repository.admin', 'repository.write',)
1311 validate_repo_permissions(apiuser, repoid, repo, _perms)
1311 validate_repo_permissions(apiuser, repoid, repo, _perms)
1312
1312
1313 # make sure normal user does not pass someone else userid,
1313 # make sure normal user does not pass someone else userid,
1314 # he is not allowed to do that
1314 # he is not allowed to do that
1315 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1315 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1316 raise JSONRPCError('userid is not the same as your user')
1316 raise JSONRPCError('userid is not the same as your user')
1317
1317
1318 if isinstance(userid, Optional):
1318 if isinstance(userid, Optional):
1319 userid = apiuser.user_id
1319 userid = apiuser.user_id
1320
1320
1321 user = get_user_or_error(userid)
1321 user = get_user_or_error(userid)
1322
1322
1323 if isinstance(locked, Optional):
1323 if isinstance(locked, Optional):
1324 lockobj = repo.locked
1324 lockobj = repo.locked
1325
1325
1326 if lockobj[0] is None:
1326 if lockobj[0] is None:
1327 _d = {
1327 _d = {
1328 'repo': repo.repo_name,
1328 'repo': repo.repo_name,
1329 'locked': False,
1329 'locked': False,
1330 'locked_since': None,
1330 'locked_since': None,
1331 'locked_by': None,
1331 'locked_by': None,
1332 'lock_reason': None,
1332 'lock_reason': None,
1333 'lock_state_changed': False,
1333 'lock_state_changed': False,
1334 'msg': 'Repo `%s` not locked.' % repo.repo_name
1334 'msg': 'Repo `%s` not locked.' % repo.repo_name
1335 }
1335 }
1336 return _d
1336 return _d
1337 else:
1337 else:
1338 _user_id, _time, _reason = lockobj
1338 _user_id, _time, _reason = lockobj
1339 lock_user = get_user_or_error(userid)
1339 lock_user = get_user_or_error(userid)
1340 _d = {
1340 _d = {
1341 'repo': repo.repo_name,
1341 'repo': repo.repo_name,
1342 'locked': True,
1342 'locked': True,
1343 'locked_since': _time,
1343 'locked_since': _time,
1344 'locked_by': lock_user.username,
1344 'locked_by': lock_user.username,
1345 'lock_reason': _reason,
1345 'lock_reason': _reason,
1346 'lock_state_changed': False,
1346 'lock_state_changed': False,
1347 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1347 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1348 % (repo.repo_name, lock_user.username,
1348 % (repo.repo_name, lock_user.username,
1349 json.dumps(time_to_datetime(_time))))
1349 json.dumps(time_to_datetime(_time))))
1350 }
1350 }
1351 return _d
1351 return _d
1352
1352
1353 # force locked state through a flag
1353 # force locked state through a flag
1354 else:
1354 else:
1355 locked = str2bool(locked)
1355 locked = str2bool(locked)
1356 lock_reason = Repository.LOCK_API
1356 lock_reason = Repository.LOCK_API
1357 try:
1357 try:
1358 if locked:
1358 if locked:
1359 lock_time = time.time()
1359 lock_time = time.time()
1360 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1360 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1361 else:
1361 else:
1362 lock_time = None
1362 lock_time = None
1363 Repository.unlock(repo)
1363 Repository.unlock(repo)
1364 _d = {
1364 _d = {
1365 'repo': repo.repo_name,
1365 'repo': repo.repo_name,
1366 'locked': locked,
1366 'locked': locked,
1367 'locked_since': lock_time,
1367 'locked_since': lock_time,
1368 'locked_by': user.username,
1368 'locked_by': user.username,
1369 'lock_reason': lock_reason,
1369 'lock_reason': lock_reason,
1370 'lock_state_changed': True,
1370 'lock_state_changed': True,
1371 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1371 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1372 % (user.username, repo.repo_name, locked))
1372 % (user.username, repo.repo_name, locked))
1373 }
1373 }
1374 return _d
1374 return _d
1375 except Exception:
1375 except Exception:
1376 log.exception(
1376 log.exception(
1377 "Exception occurred while trying to lock repository")
1377 "Exception occurred while trying to lock repository")
1378 raise JSONRPCError(
1378 raise JSONRPCError(
1379 'Error occurred locking repository `%s`' % repo.repo_name
1379 'Error occurred locking repository `%s`' % repo.repo_name
1380 )
1380 )
1381
1381
1382
1382
1383 @jsonrpc_method()
1383 @jsonrpc_method()
1384 def comment_commit(
1384 def comment_commit(
1385 request, apiuser, repoid, commit_id, message,
1385 request, apiuser, repoid, commit_id, message,
1386 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1386 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1387 """
1387 """
1388 Set a commit comment, and optionally change the status of the commit.
1388 Set a commit comment, and optionally change the status of the commit.
1389
1389
1390 :param apiuser: This is filled automatically from the |authtoken|.
1390 :param apiuser: This is filled automatically from the |authtoken|.
1391 :type apiuser: AuthUser
1391 :type apiuser: AuthUser
1392 :param repoid: Set the repository name or repository ID.
1392 :param repoid: Set the repository name or repository ID.
1393 :type repoid: str or int
1393 :type repoid: str or int
1394 :param commit_id: Specify the commit_id for which to set a comment.
1394 :param commit_id: Specify the commit_id for which to set a comment.
1395 :type commit_id: str
1395 :type commit_id: str
1396 :param message: The comment text.
1396 :param message: The comment text.
1397 :type message: str
1397 :type message: str
1398 :param userid: Set the user name of the comment creator.
1398 :param userid: Set the user name of the comment creator.
1399 :type userid: Optional(str or int)
1399 :type userid: Optional(str or int)
1400 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
1400 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
1401 'under_review'
1401 'under_review'
1402 :type status: str
1402 :type status: str
1403
1403
1404 Example error output:
1404 Example error output:
1405
1405
1406 .. code-block:: json
1406 .. code-block:: json
1407
1407
1408 {
1408 {
1409 "id" : <id_given_in_input>,
1409 "id" : <id_given_in_input>,
1410 "result" : {
1410 "result" : {
1411 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1411 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1412 "status_change": null or <status>,
1412 "status_change": null or <status>,
1413 "success": true
1413 "success": true
1414 },
1414 },
1415 "error" : null
1415 "error" : null
1416 }
1416 }
1417
1417
1418 """
1418 """
1419 repo = get_repo_or_error(repoid)
1419 repo = get_repo_or_error(repoid)
1420 if not has_superadmin_permission(apiuser):
1420 if not has_superadmin_permission(apiuser):
1421 _perms = ('repository.read', 'repository.write', 'repository.admin')
1421 _perms = ('repository.read', 'repository.write', 'repository.admin')
1422 validate_repo_permissions(apiuser, repoid, repo, _perms)
1422 validate_repo_permissions(apiuser, repoid, repo, _perms)
1423
1423
1424 if isinstance(userid, Optional):
1424 if isinstance(userid, Optional):
1425 userid = apiuser.user_id
1425 userid = apiuser.user_id
1426
1426
1427 user = get_user_or_error(userid)
1427 user = get_user_or_error(userid)
1428 status = Optional.extract(status)
1428 status = Optional.extract(status)
1429
1429
1430 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1430 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1431 if status and status not in allowed_statuses:
1431 if status and status not in allowed_statuses:
1432 raise JSONRPCError('Bad status, must be on '
1432 raise JSONRPCError('Bad status, must be on '
1433 'of %s got %s' % (allowed_statuses, status,))
1433 'of %s got %s' % (allowed_statuses, status,))
1434
1434
1435 try:
1435 try:
1436 rc_config = SettingsModel().get_all_settings()
1436 rc_config = SettingsModel().get_all_settings()
1437 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1437 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1438 status_change_label = ChangesetStatus.get_status_lbl(status)
1438 status_change_label = ChangesetStatus.get_status_lbl(status)
1439 comm = ChangesetCommentsModel().create(
1439 comm = ChangesetCommentsModel().create(
1440 message, repo, user, revision=commit_id,
1440 message, repo, user, commit_id=commit_id,
1441 status_change=status_change_label,
1441 status_change=status_change_label,
1442 status_change_type=status,
1442 status_change_type=status,
1443 renderer=renderer)
1443 renderer=renderer)
1444 if status:
1444 if status:
1445 # also do a status change
1445 # also do a status change
1446 try:
1446 try:
1447 ChangesetStatusModel().set_status(
1447 ChangesetStatusModel().set_status(
1448 repo, status, user, comm, revision=commit_id,
1448 repo, status, user, comm, revision=commit_id,
1449 dont_allow_on_closed_pull_request=True
1449 dont_allow_on_closed_pull_request=True
1450 )
1450 )
1451 except StatusChangeOnClosedPullRequestError:
1451 except StatusChangeOnClosedPullRequestError:
1452 log.exception(
1452 log.exception(
1453 "Exception occurred while trying to change repo commit status")
1453 "Exception occurred while trying to change repo commit status")
1454 msg = ('Changing status on a changeset associated with '
1454 msg = ('Changing status on a changeset associated with '
1455 'a closed pull request is not allowed')
1455 'a closed pull request is not allowed')
1456 raise JSONRPCError(msg)
1456 raise JSONRPCError(msg)
1457
1457
1458 Session().commit()
1458 Session().commit()
1459 return {
1459 return {
1460 'msg': (
1460 'msg': (
1461 'Commented on commit `%s` for repository `%s`' % (
1461 'Commented on commit `%s` for repository `%s`' % (
1462 comm.revision, repo.repo_name)),
1462 comm.revision, repo.repo_name)),
1463 'status_change': status,
1463 'status_change': status,
1464 'success': True,
1464 'success': True,
1465 }
1465 }
1466 except JSONRPCError:
1466 except JSONRPCError:
1467 # catch any inside errors, and re-raise them to prevent from
1467 # catch any inside errors, and re-raise them to prevent from
1468 # below global catch to silence them
1468 # below global catch to silence them
1469 raise
1469 raise
1470 except Exception:
1470 except Exception:
1471 log.exception("Exception occurred while trying to comment on commit")
1471 log.exception("Exception occurred while trying to comment on commit")
1472 raise JSONRPCError(
1472 raise JSONRPCError(
1473 'failed to set comment on repository `%s`' % (repo.repo_name,)
1473 'failed to set comment on repository `%s`' % (repo.repo_name,)
1474 )
1474 )
1475
1475
1476
1476
1477 @jsonrpc_method()
1477 @jsonrpc_method()
1478 def grant_user_permission(request, apiuser, repoid, userid, perm):
1478 def grant_user_permission(request, apiuser, repoid, userid, perm):
1479 """
1479 """
1480 Grant permissions for the specified user on the given repository,
1480 Grant permissions for the specified user on the given repository,
1481 or update existing permissions if found.
1481 or update existing permissions if found.
1482
1482
1483 This command can only be run using an |authtoken| with admin
1483 This command can only be run using an |authtoken| with admin
1484 permissions on the |repo|.
1484 permissions on the |repo|.
1485
1485
1486 :param apiuser: This is filled automatically from the |authtoken|.
1486 :param apiuser: This is filled automatically from the |authtoken|.
1487 :type apiuser: AuthUser
1487 :type apiuser: AuthUser
1488 :param repoid: Set the repository name or repository ID.
1488 :param repoid: Set the repository name or repository ID.
1489 :type repoid: str or int
1489 :type repoid: str or int
1490 :param userid: Set the user name.
1490 :param userid: Set the user name.
1491 :type userid: str
1491 :type userid: str
1492 :param perm: Set the user permissions, using the following format
1492 :param perm: Set the user permissions, using the following format
1493 ``(repository.(none|read|write|admin))``
1493 ``(repository.(none|read|write|admin))``
1494 :type perm: str
1494 :type perm: str
1495
1495
1496 Example output:
1496 Example output:
1497
1497
1498 .. code-block:: bash
1498 .. code-block:: bash
1499
1499
1500 id : <id_given_in_input>
1500 id : <id_given_in_input>
1501 result: {
1501 result: {
1502 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1502 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1503 "success": true
1503 "success": true
1504 }
1504 }
1505 error: null
1505 error: null
1506 """
1506 """
1507
1507
1508 repo = get_repo_or_error(repoid)
1508 repo = get_repo_or_error(repoid)
1509 user = get_user_or_error(userid)
1509 user = get_user_or_error(userid)
1510 perm = get_perm_or_error(perm)
1510 perm = get_perm_or_error(perm)
1511 if not has_superadmin_permission(apiuser):
1511 if not has_superadmin_permission(apiuser):
1512 _perms = ('repository.admin',)
1512 _perms = ('repository.admin',)
1513 validate_repo_permissions(apiuser, repoid, repo, _perms)
1513 validate_repo_permissions(apiuser, repoid, repo, _perms)
1514
1514
1515 try:
1515 try:
1516
1516
1517 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1517 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1518
1518
1519 Session().commit()
1519 Session().commit()
1520 return {
1520 return {
1521 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1521 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1522 perm.permission_name, user.username, repo.repo_name
1522 perm.permission_name, user.username, repo.repo_name
1523 ),
1523 ),
1524 'success': True
1524 'success': True
1525 }
1525 }
1526 except Exception:
1526 except Exception:
1527 log.exception(
1527 log.exception(
1528 "Exception occurred while trying edit permissions for repo")
1528 "Exception occurred while trying edit permissions for repo")
1529 raise JSONRPCError(
1529 raise JSONRPCError(
1530 'failed to edit permission for user: `%s` in repo: `%s`' % (
1530 'failed to edit permission for user: `%s` in repo: `%s`' % (
1531 userid, repoid
1531 userid, repoid
1532 )
1532 )
1533 )
1533 )
1534
1534
1535
1535
1536 @jsonrpc_method()
1536 @jsonrpc_method()
1537 def revoke_user_permission(request, apiuser, repoid, userid):
1537 def revoke_user_permission(request, apiuser, repoid, userid):
1538 """
1538 """
1539 Revoke permission for a user on the specified repository.
1539 Revoke permission for a user on the specified repository.
1540
1540
1541 This command can only be run using an |authtoken| with admin
1541 This command can only be run using an |authtoken| with admin
1542 permissions on the |repo|.
1542 permissions on the |repo|.
1543
1543
1544 :param apiuser: This is filled automatically from the |authtoken|.
1544 :param apiuser: This is filled automatically from the |authtoken|.
1545 :type apiuser: AuthUser
1545 :type apiuser: AuthUser
1546 :param repoid: Set the repository name or repository ID.
1546 :param repoid: Set the repository name or repository ID.
1547 :type repoid: str or int
1547 :type repoid: str or int
1548 :param userid: Set the user name of revoked user.
1548 :param userid: Set the user name of revoked user.
1549 :type userid: str or int
1549 :type userid: str or int
1550
1550
1551 Example error output:
1551 Example error output:
1552
1552
1553 .. code-block:: bash
1553 .. code-block:: bash
1554
1554
1555 id : <id_given_in_input>
1555 id : <id_given_in_input>
1556 result: {
1556 result: {
1557 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1557 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1558 "success": true
1558 "success": true
1559 }
1559 }
1560 error: null
1560 error: null
1561 """
1561 """
1562
1562
1563 repo = get_repo_or_error(repoid)
1563 repo = get_repo_or_error(repoid)
1564 user = get_user_or_error(userid)
1564 user = get_user_or_error(userid)
1565 if not has_superadmin_permission(apiuser):
1565 if not has_superadmin_permission(apiuser):
1566 _perms = ('repository.admin',)
1566 _perms = ('repository.admin',)
1567 validate_repo_permissions(apiuser, repoid, repo, _perms)
1567 validate_repo_permissions(apiuser, repoid, repo, _perms)
1568
1568
1569 try:
1569 try:
1570 RepoModel().revoke_user_permission(repo=repo, user=user)
1570 RepoModel().revoke_user_permission(repo=repo, user=user)
1571 Session().commit()
1571 Session().commit()
1572 return {
1572 return {
1573 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1573 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1574 user.username, repo.repo_name
1574 user.username, repo.repo_name
1575 ),
1575 ),
1576 'success': True
1576 'success': True
1577 }
1577 }
1578 except Exception:
1578 except Exception:
1579 log.exception(
1579 log.exception(
1580 "Exception occurred while trying revoke permissions to repo")
1580 "Exception occurred while trying revoke permissions to repo")
1581 raise JSONRPCError(
1581 raise JSONRPCError(
1582 'failed to edit permission for user: `%s` in repo: `%s`' % (
1582 'failed to edit permission for user: `%s` in repo: `%s`' % (
1583 userid, repoid
1583 userid, repoid
1584 )
1584 )
1585 )
1585 )
1586
1586
1587
1587
1588 @jsonrpc_method()
1588 @jsonrpc_method()
1589 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1589 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1590 """
1590 """
1591 Grant permission for a user group on the specified repository,
1591 Grant permission for a user group on the specified repository,
1592 or update existing permissions.
1592 or update existing permissions.
1593
1593
1594 This command can only be run using an |authtoken| with admin
1594 This command can only be run using an |authtoken| with admin
1595 permissions on the |repo|.
1595 permissions on the |repo|.
1596
1596
1597 :param apiuser: This is filled automatically from the |authtoken|.
1597 :param apiuser: This is filled automatically from the |authtoken|.
1598 :type apiuser: AuthUser
1598 :type apiuser: AuthUser
1599 :param repoid: Set the repository name or repository ID.
1599 :param repoid: Set the repository name or repository ID.
1600 :type repoid: str or int
1600 :type repoid: str or int
1601 :param usergroupid: Specify the ID of the user group.
1601 :param usergroupid: Specify the ID of the user group.
1602 :type usergroupid: str or int
1602 :type usergroupid: str or int
1603 :param perm: Set the user group permissions using the following
1603 :param perm: Set the user group permissions using the following
1604 format: (repository.(none|read|write|admin))
1604 format: (repository.(none|read|write|admin))
1605 :type perm: str
1605 :type perm: str
1606
1606
1607 Example output:
1607 Example output:
1608
1608
1609 .. code-block:: bash
1609 .. code-block:: bash
1610
1610
1611 id : <id_given_in_input>
1611 id : <id_given_in_input>
1612 result : {
1612 result : {
1613 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1613 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1614 "success": true
1614 "success": true
1615
1615
1616 }
1616 }
1617 error : null
1617 error : null
1618
1618
1619 Example error output:
1619 Example error output:
1620
1620
1621 .. code-block:: bash
1621 .. code-block:: bash
1622
1622
1623 id : <id_given_in_input>
1623 id : <id_given_in_input>
1624 result : null
1624 result : null
1625 error : {
1625 error : {
1626 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1626 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1627 }
1627 }
1628
1628
1629 """
1629 """
1630
1630
1631 repo = get_repo_or_error(repoid)
1631 repo = get_repo_or_error(repoid)
1632 perm = get_perm_or_error(perm)
1632 perm = get_perm_or_error(perm)
1633 if not has_superadmin_permission(apiuser):
1633 if not has_superadmin_permission(apiuser):
1634 _perms = ('repository.admin',)
1634 _perms = ('repository.admin',)
1635 validate_repo_permissions(apiuser, repoid, repo, _perms)
1635 validate_repo_permissions(apiuser, repoid, repo, _perms)
1636
1636
1637 user_group = get_user_group_or_error(usergroupid)
1637 user_group = get_user_group_or_error(usergroupid)
1638 if not has_superadmin_permission(apiuser):
1638 if not has_superadmin_permission(apiuser):
1639 # check if we have at least read permission for this user group !
1639 # check if we have at least read permission for this user group !
1640 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1640 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1641 if not HasUserGroupPermissionAnyApi(*_perms)(
1641 if not HasUserGroupPermissionAnyApi(*_perms)(
1642 user=apiuser, user_group_name=user_group.users_group_name):
1642 user=apiuser, user_group_name=user_group.users_group_name):
1643 raise JSONRPCError(
1643 raise JSONRPCError(
1644 'user group `%s` does not exist' % (usergroupid,))
1644 'user group `%s` does not exist' % (usergroupid,))
1645
1645
1646 try:
1646 try:
1647 RepoModel().grant_user_group_permission(
1647 RepoModel().grant_user_group_permission(
1648 repo=repo, group_name=user_group, perm=perm)
1648 repo=repo, group_name=user_group, perm=perm)
1649
1649
1650 Session().commit()
1650 Session().commit()
1651 return {
1651 return {
1652 'msg': 'Granted perm: `%s` for user group: `%s` in '
1652 'msg': 'Granted perm: `%s` for user group: `%s` in '
1653 'repo: `%s`' % (
1653 'repo: `%s`' % (
1654 perm.permission_name, user_group.users_group_name,
1654 perm.permission_name, user_group.users_group_name,
1655 repo.repo_name
1655 repo.repo_name
1656 ),
1656 ),
1657 'success': True
1657 'success': True
1658 }
1658 }
1659 except Exception:
1659 except Exception:
1660 log.exception(
1660 log.exception(
1661 "Exception occurred while trying change permission on repo")
1661 "Exception occurred while trying change permission on repo")
1662 raise JSONRPCError(
1662 raise JSONRPCError(
1663 'failed to edit permission for user group: `%s` in '
1663 'failed to edit permission for user group: `%s` in '
1664 'repo: `%s`' % (
1664 'repo: `%s`' % (
1665 usergroupid, repo.repo_name
1665 usergroupid, repo.repo_name
1666 )
1666 )
1667 )
1667 )
1668
1668
1669
1669
1670 @jsonrpc_method()
1670 @jsonrpc_method()
1671 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1671 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1672 """
1672 """
1673 Revoke the permissions of a user group on a given repository.
1673 Revoke the permissions of a user group on a given repository.
1674
1674
1675 This command can only be run using an |authtoken| with admin
1675 This command can only be run using an |authtoken| with admin
1676 permissions on the |repo|.
1676 permissions on the |repo|.
1677
1677
1678 :param apiuser: This is filled automatically from the |authtoken|.
1678 :param apiuser: This is filled automatically from the |authtoken|.
1679 :type apiuser: AuthUser
1679 :type apiuser: AuthUser
1680 :param repoid: Set the repository name or repository ID.
1680 :param repoid: Set the repository name or repository ID.
1681 :type repoid: str or int
1681 :type repoid: str or int
1682 :param usergroupid: Specify the user group ID.
1682 :param usergroupid: Specify the user group ID.
1683 :type usergroupid: str or int
1683 :type usergroupid: str or int
1684
1684
1685 Example output:
1685 Example output:
1686
1686
1687 .. code-block:: bash
1687 .. code-block:: bash
1688
1688
1689 id : <id_given_in_input>
1689 id : <id_given_in_input>
1690 result: {
1690 result: {
1691 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1691 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1692 "success": true
1692 "success": true
1693 }
1693 }
1694 error: null
1694 error: null
1695 """
1695 """
1696
1696
1697 repo = get_repo_or_error(repoid)
1697 repo = get_repo_or_error(repoid)
1698 if not has_superadmin_permission(apiuser):
1698 if not has_superadmin_permission(apiuser):
1699 _perms = ('repository.admin',)
1699 _perms = ('repository.admin',)
1700 validate_repo_permissions(apiuser, repoid, repo, _perms)
1700 validate_repo_permissions(apiuser, repoid, repo, _perms)
1701
1701
1702 user_group = get_user_group_or_error(usergroupid)
1702 user_group = get_user_group_or_error(usergroupid)
1703 if not has_superadmin_permission(apiuser):
1703 if not has_superadmin_permission(apiuser):
1704 # check if we have at least read permission for this user group !
1704 # check if we have at least read permission for this user group !
1705 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1705 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1706 if not HasUserGroupPermissionAnyApi(*_perms)(
1706 if not HasUserGroupPermissionAnyApi(*_perms)(
1707 user=apiuser, user_group_name=user_group.users_group_name):
1707 user=apiuser, user_group_name=user_group.users_group_name):
1708 raise JSONRPCError(
1708 raise JSONRPCError(
1709 'user group `%s` does not exist' % (usergroupid,))
1709 'user group `%s` does not exist' % (usergroupid,))
1710
1710
1711 try:
1711 try:
1712 RepoModel().revoke_user_group_permission(
1712 RepoModel().revoke_user_group_permission(
1713 repo=repo, group_name=user_group)
1713 repo=repo, group_name=user_group)
1714
1714
1715 Session().commit()
1715 Session().commit()
1716 return {
1716 return {
1717 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1717 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1718 user_group.users_group_name, repo.repo_name
1718 user_group.users_group_name, repo.repo_name
1719 ),
1719 ),
1720 'success': True
1720 'success': True
1721 }
1721 }
1722 except Exception:
1722 except Exception:
1723 log.exception("Exception occurred while trying revoke "
1723 log.exception("Exception occurred while trying revoke "
1724 "user group permission on repo")
1724 "user group permission on repo")
1725 raise JSONRPCError(
1725 raise JSONRPCError(
1726 'failed to edit permission for user group: `%s` in '
1726 'failed to edit permission for user group: `%s` in '
1727 'repo: `%s`' % (
1727 'repo: `%s`' % (
1728 user_group.users_group_name, repo.repo_name
1728 user_group.users_group_name, repo.repo_name
1729 )
1729 )
1730 )
1730 )
1731
1731
1732
1732
1733 @jsonrpc_method()
1733 @jsonrpc_method()
1734 def pull(request, apiuser, repoid):
1734 def pull(request, apiuser, repoid):
1735 """
1735 """
1736 Triggers a pull on the given repository from a remote location. You
1736 Triggers a pull on the given repository from a remote location. You
1737 can use this to keep remote repositories up-to-date.
1737 can use this to keep remote repositories up-to-date.
1738
1738
1739 This command can only be run using an |authtoken| with admin
1739 This command can only be run using an |authtoken| with admin
1740 rights to the specified repository. For more information,
1740 rights to the specified repository. For more information,
1741 see :ref:`config-token-ref`.
1741 see :ref:`config-token-ref`.
1742
1742
1743 This command takes the following options:
1743 This command takes the following options:
1744
1744
1745 :param apiuser: This is filled automatically from the |authtoken|.
1745 :param apiuser: This is filled automatically from the |authtoken|.
1746 :type apiuser: AuthUser
1746 :type apiuser: AuthUser
1747 :param repoid: The repository name or repository ID.
1747 :param repoid: The repository name or repository ID.
1748 :type repoid: str or int
1748 :type repoid: str or int
1749
1749
1750 Example output:
1750 Example output:
1751
1751
1752 .. code-block:: bash
1752 .. code-block:: bash
1753
1753
1754 id : <id_given_in_input>
1754 id : <id_given_in_input>
1755 result : {
1755 result : {
1756 "msg": "Pulled from `<repository name>`"
1756 "msg": "Pulled from `<repository name>`"
1757 "repository": "<repository name>"
1757 "repository": "<repository name>"
1758 }
1758 }
1759 error : null
1759 error : null
1760
1760
1761 Example error output:
1761 Example error output:
1762
1762
1763 .. code-block:: bash
1763 .. code-block:: bash
1764
1764
1765 id : <id_given_in_input>
1765 id : <id_given_in_input>
1766 result : null
1766 result : null
1767 error : {
1767 error : {
1768 "Unable to pull changes from `<reponame>`"
1768 "Unable to pull changes from `<reponame>`"
1769 }
1769 }
1770
1770
1771 """
1771 """
1772
1772
1773 repo = get_repo_or_error(repoid)
1773 repo = get_repo_or_error(repoid)
1774 if not has_superadmin_permission(apiuser):
1774 if not has_superadmin_permission(apiuser):
1775 _perms = ('repository.admin',)
1775 _perms = ('repository.admin',)
1776 validate_repo_permissions(apiuser, repoid, repo, _perms)
1776 validate_repo_permissions(apiuser, repoid, repo, _perms)
1777
1777
1778 try:
1778 try:
1779 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1779 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1780 return {
1780 return {
1781 'msg': 'Pulled from `%s`' % repo.repo_name,
1781 'msg': 'Pulled from `%s`' % repo.repo_name,
1782 'repository': repo.repo_name
1782 'repository': repo.repo_name
1783 }
1783 }
1784 except Exception:
1784 except Exception:
1785 log.exception("Exception occurred while trying to "
1785 log.exception("Exception occurred while trying to "
1786 "pull changes from remote location")
1786 "pull changes from remote location")
1787 raise JSONRPCError(
1787 raise JSONRPCError(
1788 'Unable to pull changes from `%s`' % repo.repo_name
1788 'Unable to pull changes from `%s`' % repo.repo_name
1789 )
1789 )
1790
1790
1791
1791
1792 @jsonrpc_method()
1792 @jsonrpc_method()
1793 def strip(request, apiuser, repoid, revision, branch):
1793 def strip(request, apiuser, repoid, revision, branch):
1794 """
1794 """
1795 Strips the given revision from the specified repository.
1795 Strips the given revision from the specified repository.
1796
1796
1797 * This will remove the revision and all of its decendants.
1797 * This will remove the revision and all of its decendants.
1798
1798
1799 This command can only be run using an |authtoken| with admin rights to
1799 This command can only be run using an |authtoken| with admin rights to
1800 the specified repository.
1800 the specified repository.
1801
1801
1802 This command takes the following options:
1802 This command takes the following options:
1803
1803
1804 :param apiuser: This is filled automatically from the |authtoken|.
1804 :param apiuser: This is filled automatically from the |authtoken|.
1805 :type apiuser: AuthUser
1805 :type apiuser: AuthUser
1806 :param repoid: The repository name or repository ID.
1806 :param repoid: The repository name or repository ID.
1807 :type repoid: str or int
1807 :type repoid: str or int
1808 :param revision: The revision you wish to strip.
1808 :param revision: The revision you wish to strip.
1809 :type revision: str
1809 :type revision: str
1810 :param branch: The branch from which to strip the revision.
1810 :param branch: The branch from which to strip the revision.
1811 :type branch: str
1811 :type branch: str
1812
1812
1813 Example output:
1813 Example output:
1814
1814
1815 .. code-block:: bash
1815 .. code-block:: bash
1816
1816
1817 id : <id_given_in_input>
1817 id : <id_given_in_input>
1818 result : {
1818 result : {
1819 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1819 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1820 "repository": "<repository name>"
1820 "repository": "<repository name>"
1821 }
1821 }
1822 error : null
1822 error : null
1823
1823
1824 Example error output:
1824 Example error output:
1825
1825
1826 .. code-block:: bash
1826 .. code-block:: bash
1827
1827
1828 id : <id_given_in_input>
1828 id : <id_given_in_input>
1829 result : null
1829 result : null
1830 error : {
1830 error : {
1831 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1831 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1832 }
1832 }
1833
1833
1834 """
1834 """
1835
1835
1836 repo = get_repo_or_error(repoid)
1836 repo = get_repo_or_error(repoid)
1837 if not has_superadmin_permission(apiuser):
1837 if not has_superadmin_permission(apiuser):
1838 _perms = ('repository.admin',)
1838 _perms = ('repository.admin',)
1839 validate_repo_permissions(apiuser, repoid, repo, _perms)
1839 validate_repo_permissions(apiuser, repoid, repo, _perms)
1840
1840
1841 try:
1841 try:
1842 ScmModel().strip(repo, revision, branch)
1842 ScmModel().strip(repo, revision, branch)
1843 return {
1843 return {
1844 'msg': 'Stripped commit %s from repo `%s`' % (
1844 'msg': 'Stripped commit %s from repo `%s`' % (
1845 revision, repo.repo_name),
1845 revision, repo.repo_name),
1846 'repository': repo.repo_name
1846 'repository': repo.repo_name
1847 }
1847 }
1848 except Exception:
1848 except Exception:
1849 log.exception("Exception while trying to strip")
1849 log.exception("Exception while trying to strip")
1850 raise JSONRPCError(
1850 raise JSONRPCError(
1851 'Unable to strip commit %s from repo `%s`' % (
1851 'Unable to strip commit %s from repo `%s`' % (
1852 revision, repo.repo_name)
1852 revision, repo.repo_name)
1853 )
1853 )
1854
1854
1855
1855
1856 @jsonrpc_method()
1856 @jsonrpc_method()
1857 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1857 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1858 """
1858 """
1859 Returns all settings for a repository. If key is given it only returns the
1859 Returns all settings for a repository. If key is given it only returns the
1860 setting identified by the key or null.
1860 setting identified by the key or null.
1861
1861
1862 :param apiuser: This is filled automatically from the |authtoken|.
1862 :param apiuser: This is filled automatically from the |authtoken|.
1863 :type apiuser: AuthUser
1863 :type apiuser: AuthUser
1864 :param repoid: The repository name or repository id.
1864 :param repoid: The repository name or repository id.
1865 :type repoid: str or int
1865 :type repoid: str or int
1866 :param key: Key of the setting to return.
1866 :param key: Key of the setting to return.
1867 :type: key: Optional(str)
1867 :type: key: Optional(str)
1868
1868
1869 Example output:
1869 Example output:
1870
1870
1871 .. code-block:: bash
1871 .. code-block:: bash
1872
1872
1873 {
1873 {
1874 "error": null,
1874 "error": null,
1875 "id": 237,
1875 "id": 237,
1876 "result": {
1876 "result": {
1877 "extensions_largefiles": true,
1877 "extensions_largefiles": true,
1878 "hooks_changegroup_push_logger": true,
1878 "hooks_changegroup_push_logger": true,
1879 "hooks_changegroup_repo_size": false,
1879 "hooks_changegroup_repo_size": false,
1880 "hooks_outgoing_pull_logger": true,
1880 "hooks_outgoing_pull_logger": true,
1881 "phases_publish": "True",
1881 "phases_publish": "True",
1882 "rhodecode_hg_use_rebase_for_merging": true,
1882 "rhodecode_hg_use_rebase_for_merging": true,
1883 "rhodecode_pr_merge_enabled": true,
1883 "rhodecode_pr_merge_enabled": true,
1884 "rhodecode_use_outdated_comments": true
1884 "rhodecode_use_outdated_comments": true
1885 }
1885 }
1886 }
1886 }
1887 """
1887 """
1888
1888
1889 # Restrict access to this api method to admins only.
1889 # Restrict access to this api method to admins only.
1890 if not has_superadmin_permission(apiuser):
1890 if not has_superadmin_permission(apiuser):
1891 raise JSONRPCForbidden()
1891 raise JSONRPCForbidden()
1892
1892
1893 try:
1893 try:
1894 repo = get_repo_or_error(repoid)
1894 repo = get_repo_or_error(repoid)
1895 settings_model = VcsSettingsModel(repo=repo)
1895 settings_model = VcsSettingsModel(repo=repo)
1896 settings = settings_model.get_global_settings()
1896 settings = settings_model.get_global_settings()
1897 settings.update(settings_model.get_repo_settings())
1897 settings.update(settings_model.get_repo_settings())
1898
1898
1899 # If only a single setting is requested fetch it from all settings.
1899 # If only a single setting is requested fetch it from all settings.
1900 key = Optional.extract(key)
1900 key = Optional.extract(key)
1901 if key is not None:
1901 if key is not None:
1902 settings = settings.get(key, None)
1902 settings = settings.get(key, None)
1903 except Exception:
1903 except Exception:
1904 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1904 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1905 log.exception(msg)
1905 log.exception(msg)
1906 raise JSONRPCError(msg)
1906 raise JSONRPCError(msg)
1907
1907
1908 return settings
1908 return settings
1909
1909
1910
1910
1911 @jsonrpc_method()
1911 @jsonrpc_method()
1912 def set_repo_settings(request, apiuser, repoid, settings):
1912 def set_repo_settings(request, apiuser, repoid, settings):
1913 """
1913 """
1914 Update repository settings. Returns true on success.
1914 Update repository settings. Returns true on success.
1915
1915
1916 :param apiuser: This is filled automatically from the |authtoken|.
1916 :param apiuser: This is filled automatically from the |authtoken|.
1917 :type apiuser: AuthUser
1917 :type apiuser: AuthUser
1918 :param repoid: The repository name or repository id.
1918 :param repoid: The repository name or repository id.
1919 :type repoid: str or int
1919 :type repoid: str or int
1920 :param settings: The new settings for the repository.
1920 :param settings: The new settings for the repository.
1921 :type: settings: dict
1921 :type: settings: dict
1922
1922
1923 Example output:
1923 Example output:
1924
1924
1925 .. code-block:: bash
1925 .. code-block:: bash
1926
1926
1927 {
1927 {
1928 "error": null,
1928 "error": null,
1929 "id": 237,
1929 "id": 237,
1930 "result": true
1930 "result": true
1931 }
1931 }
1932 """
1932 """
1933 # Restrict access to this api method to admins only.
1933 # Restrict access to this api method to admins only.
1934 if not has_superadmin_permission(apiuser):
1934 if not has_superadmin_permission(apiuser):
1935 raise JSONRPCForbidden()
1935 raise JSONRPCForbidden()
1936
1936
1937 if type(settings) is not dict:
1937 if type(settings) is not dict:
1938 raise JSONRPCError('Settings have to be a JSON Object.')
1938 raise JSONRPCError('Settings have to be a JSON Object.')
1939
1939
1940 try:
1940 try:
1941 settings_model = VcsSettingsModel(repo=repoid)
1941 settings_model = VcsSettingsModel(repo=repoid)
1942
1942
1943 # Merge global, repo and incoming settings.
1943 # Merge global, repo and incoming settings.
1944 new_settings = settings_model.get_global_settings()
1944 new_settings = settings_model.get_global_settings()
1945 new_settings.update(settings_model.get_repo_settings())
1945 new_settings.update(settings_model.get_repo_settings())
1946 new_settings.update(settings)
1946 new_settings.update(settings)
1947
1947
1948 # Update the settings.
1948 # Update the settings.
1949 inherit_global_settings = new_settings.get(
1949 inherit_global_settings = new_settings.get(
1950 'inherit_global_settings', False)
1950 'inherit_global_settings', False)
1951 settings_model.create_or_update_repo_settings(
1951 settings_model.create_or_update_repo_settings(
1952 new_settings, inherit_global_settings=inherit_global_settings)
1952 new_settings, inherit_global_settings=inherit_global_settings)
1953 Session().commit()
1953 Session().commit()
1954 except Exception:
1954 except Exception:
1955 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1955 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1956 log.exception(msg)
1956 log.exception(msg)
1957 raise JSONRPCError(msg)
1957 raise JSONRPCError(msg)
1958
1958
1959 # Indicate success.
1959 # Indicate success.
1960 return True
1960 return True
@@ -1,470 +1,470 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 commit controller for RhodeCode showing changes between commits
22 commit controller for RhodeCode showing changes between commits
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from collections import defaultdict
27 from collections import defaultdict
28 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
28 from webob.exc import HTTPForbidden, HTTPBadRequest, HTTPNotFound
29
29
30 from pylons import tmpl_context as c, request, response
30 from pylons import tmpl_context as c, request, response
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from pylons.controllers.util import redirect
32 from pylons.controllers.util import redirect
33
33
34 from rhodecode.lib import auth
34 from rhodecode.lib import auth
35 from rhodecode.lib import diffs, codeblocks
35 from rhodecode.lib import diffs, codeblocks
36 from rhodecode.lib.auth import (
36 from rhodecode.lib.auth import (
37 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous)
37 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous)
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
40 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
41 import rhodecode.lib.helpers as h
41 import rhodecode.lib.helpers as h
42 from rhodecode.lib.utils import action_logger, jsonify
42 from rhodecode.lib.utils import action_logger, jsonify
43 from rhodecode.lib.utils2 import safe_unicode
43 from rhodecode.lib.utils2 import safe_unicode
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
46 RepositoryError, CommitDoesNotExistError, NodeDoesNotExistError)
47 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.db import ChangesetComment, ChangesetStatus
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.comment import ChangesetCommentsModel
49 from rhodecode.model.comment import ChangesetCommentsModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52
52
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 def _update_with_GET(params, GET):
57 def _update_with_GET(params, GET):
58 for k in ['diff1', 'diff2', 'diff']:
58 for k in ['diff1', 'diff2', 'diff']:
59 params[k] += GET.getall(k)
59 params[k] += GET.getall(k)
60
60
61
61
62 def get_ignore_ws(fid, GET):
62 def get_ignore_ws(fid, GET):
63 ig_ws_global = GET.get('ignorews')
63 ig_ws_global = GET.get('ignorews')
64 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
64 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
65 if ig_ws:
65 if ig_ws:
66 try:
66 try:
67 return int(ig_ws[0].split(':')[-1])
67 return int(ig_ws[0].split(':')[-1])
68 except Exception:
68 except Exception:
69 pass
69 pass
70 return ig_ws_global
70 return ig_ws_global
71
71
72
72
73 def _ignorews_url(GET, fileid=None):
73 def _ignorews_url(GET, fileid=None):
74 fileid = str(fileid) if fileid else None
74 fileid = str(fileid) if fileid else None
75 params = defaultdict(list)
75 params = defaultdict(list)
76 _update_with_GET(params, GET)
76 _update_with_GET(params, GET)
77 label = _('Show whitespace')
77 label = _('Show whitespace')
78 tooltiplbl = _('Show whitespace for all diffs')
78 tooltiplbl = _('Show whitespace for all diffs')
79 ig_ws = get_ignore_ws(fileid, GET)
79 ig_ws = get_ignore_ws(fileid, GET)
80 ln_ctx = get_line_ctx(fileid, GET)
80 ln_ctx = get_line_ctx(fileid, GET)
81
81
82 if ig_ws is None:
82 if ig_ws is None:
83 params['ignorews'] += [1]
83 params['ignorews'] += [1]
84 label = _('Ignore whitespace')
84 label = _('Ignore whitespace')
85 tooltiplbl = _('Ignore whitespace for all diffs')
85 tooltiplbl = _('Ignore whitespace for all diffs')
86 ctx_key = 'context'
86 ctx_key = 'context'
87 ctx_val = ln_ctx
87 ctx_val = ln_ctx
88
88
89 # if we have passed in ln_ctx pass it along to our params
89 # if we have passed in ln_ctx pass it along to our params
90 if ln_ctx:
90 if ln_ctx:
91 params[ctx_key] += [ctx_val]
91 params[ctx_key] += [ctx_val]
92
92
93 if fileid:
93 if fileid:
94 params['anchor'] = 'a_' + fileid
94 params['anchor'] = 'a_' + fileid
95 return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip')
95 return h.link_to(label, h.url.current(**params), title=tooltiplbl, class_='tooltip')
96
96
97
97
98 def get_line_ctx(fid, GET):
98 def get_line_ctx(fid, GET):
99 ln_ctx_global = GET.get('context')
99 ln_ctx_global = GET.get('context')
100 if fid:
100 if fid:
101 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
101 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
102 else:
102 else:
103 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
103 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
104 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
104 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
105 if ln_ctx:
105 if ln_ctx:
106 ln_ctx = [ln_ctx]
106 ln_ctx = [ln_ctx]
107
107
108 if ln_ctx:
108 if ln_ctx:
109 retval = ln_ctx[0].split(':')[-1]
109 retval = ln_ctx[0].split(':')[-1]
110 else:
110 else:
111 retval = ln_ctx_global
111 retval = ln_ctx_global
112
112
113 try:
113 try:
114 return int(retval)
114 return int(retval)
115 except Exception:
115 except Exception:
116 return 3
116 return 3
117
117
118
118
119 def _context_url(GET, fileid=None):
119 def _context_url(GET, fileid=None):
120 """
120 """
121 Generates a url for context lines.
121 Generates a url for context lines.
122
122
123 :param fileid:
123 :param fileid:
124 """
124 """
125
125
126 fileid = str(fileid) if fileid else None
126 fileid = str(fileid) if fileid else None
127 ig_ws = get_ignore_ws(fileid, GET)
127 ig_ws = get_ignore_ws(fileid, GET)
128 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
128 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
129
129
130 params = defaultdict(list)
130 params = defaultdict(list)
131 _update_with_GET(params, GET)
131 _update_with_GET(params, GET)
132
132
133 if ln_ctx > 0:
133 if ln_ctx > 0:
134 params['context'] += [ln_ctx]
134 params['context'] += [ln_ctx]
135
135
136 if ig_ws:
136 if ig_ws:
137 ig_ws_key = 'ignorews'
137 ig_ws_key = 'ignorews'
138 ig_ws_val = 1
138 ig_ws_val = 1
139 params[ig_ws_key] += [ig_ws_val]
139 params[ig_ws_key] += [ig_ws_val]
140
140
141 lbl = _('Increase context')
141 lbl = _('Increase context')
142 tooltiplbl = _('Increase context for all diffs')
142 tooltiplbl = _('Increase context for all diffs')
143
143
144 if fileid:
144 if fileid:
145 params['anchor'] = 'a_' + fileid
145 params['anchor'] = 'a_' + fileid
146 return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip')
146 return h.link_to(lbl, h.url.current(**params), title=tooltiplbl, class_='tooltip')
147
147
148
148
149 class ChangesetController(BaseRepoController):
149 class ChangesetController(BaseRepoController):
150
150
151 def __before__(self):
151 def __before__(self):
152 super(ChangesetController, self).__before__()
152 super(ChangesetController, self).__before__()
153 c.affected_files_cut_off = 60
153 c.affected_files_cut_off = 60
154
154
155 def _index(self, commit_id_range, method):
155 def _index(self, commit_id_range, method):
156 c.ignorews_url = _ignorews_url
156 c.ignorews_url = _ignorews_url
157 c.context_url = _context_url
157 c.context_url = _context_url
158 c.fulldiff = fulldiff = request.GET.get('fulldiff')
158 c.fulldiff = fulldiff = request.GET.get('fulldiff')
159
159
160 # fetch global flags of ignore ws or context lines
160 # fetch global flags of ignore ws or context lines
161 context_lcl = get_line_ctx('', request.GET)
161 context_lcl = get_line_ctx('', request.GET)
162 ign_whitespace_lcl = get_ignore_ws('', request.GET)
162 ign_whitespace_lcl = get_ignore_ws('', request.GET)
163
163
164 # diff_limit will cut off the whole diff if the limit is applied
164 # diff_limit will cut off the whole diff if the limit is applied
165 # otherwise it will just hide the big files from the front-end
165 # otherwise it will just hide the big files from the front-end
166 diff_limit = self.cut_off_limit_diff
166 diff_limit = self.cut_off_limit_diff
167 file_limit = self.cut_off_limit_file
167 file_limit = self.cut_off_limit_file
168
168
169 # get ranges of commit ids if preset
169 # get ranges of commit ids if preset
170 commit_range = commit_id_range.split('...')[:2]
170 commit_range = commit_id_range.split('...')[:2]
171
171
172 try:
172 try:
173 pre_load = ['affected_files', 'author', 'branch', 'date',
173 pre_load = ['affected_files', 'author', 'branch', 'date',
174 'message', 'parents']
174 'message', 'parents']
175
175
176 if len(commit_range) == 2:
176 if len(commit_range) == 2:
177 commits = c.rhodecode_repo.get_commits(
177 commits = c.rhodecode_repo.get_commits(
178 start_id=commit_range[0], end_id=commit_range[1],
178 start_id=commit_range[0], end_id=commit_range[1],
179 pre_load=pre_load)
179 pre_load=pre_load)
180 commits = list(commits)
180 commits = list(commits)
181 else:
181 else:
182 commits = [c.rhodecode_repo.get_commit(
182 commits = [c.rhodecode_repo.get_commit(
183 commit_id=commit_id_range, pre_load=pre_load)]
183 commit_id=commit_id_range, pre_load=pre_load)]
184
184
185 c.commit_ranges = commits
185 c.commit_ranges = commits
186 if not c.commit_ranges:
186 if not c.commit_ranges:
187 raise RepositoryError(
187 raise RepositoryError(
188 'The commit range returned an empty result')
188 'The commit range returned an empty result')
189 except CommitDoesNotExistError:
189 except CommitDoesNotExistError:
190 msg = _('No such commit exists for this repository')
190 msg = _('No such commit exists for this repository')
191 h.flash(msg, category='error')
191 h.flash(msg, category='error')
192 raise HTTPNotFound()
192 raise HTTPNotFound()
193 except Exception:
193 except Exception:
194 log.exception("General failure")
194 log.exception("General failure")
195 raise HTTPNotFound()
195 raise HTTPNotFound()
196
196
197 c.changes = OrderedDict()
197 c.changes = OrderedDict()
198 c.lines_added = 0
198 c.lines_added = 0
199 c.lines_deleted = 0
199 c.lines_deleted = 0
200
200
201 # auto collapse if we have more than limit
201 # auto collapse if we have more than limit
202 collapse_limit = diffs.DiffProcessor._collapse_commits_over
202 collapse_limit = diffs.DiffProcessor._collapse_commits_over
203 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
203 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
204
204
205 c.commit_statuses = ChangesetStatus.STATUSES
205 c.commit_statuses = ChangesetStatus.STATUSES
206 c.inline_comments = []
206 c.inline_comments = []
207 c.files = []
207 c.files = []
208
208
209 c.statuses = []
209 c.statuses = []
210 c.comments = []
210 c.comments = []
211 if len(c.commit_ranges) == 1:
211 if len(c.commit_ranges) == 1:
212 commit = c.commit_ranges[0]
212 commit = c.commit_ranges[0]
213 c.comments = ChangesetCommentsModel().get_comments(
213 c.comments = ChangesetCommentsModel().get_comments(
214 c.rhodecode_db_repo.repo_id,
214 c.rhodecode_db_repo.repo_id,
215 revision=commit.raw_id)
215 revision=commit.raw_id)
216 c.statuses.append(ChangesetStatusModel().get_status(
216 c.statuses.append(ChangesetStatusModel().get_status(
217 c.rhodecode_db_repo.repo_id, commit.raw_id))
217 c.rhodecode_db_repo.repo_id, commit.raw_id))
218 # comments from PR
218 # comments from PR
219 statuses = ChangesetStatusModel().get_statuses(
219 statuses = ChangesetStatusModel().get_statuses(
220 c.rhodecode_db_repo.repo_id, commit.raw_id,
220 c.rhodecode_db_repo.repo_id, commit.raw_id,
221 with_revisions=True)
221 with_revisions=True)
222 prs = set(st.pull_request for st in statuses
222 prs = set(st.pull_request for st in statuses
223 if st.pull_request is not None)
223 if st.pull_request is not None)
224 # from associated statuses, check the pull requests, and
224 # from associated statuses, check the pull requests, and
225 # show comments from them
225 # show comments from them
226 for pr in prs:
226 for pr in prs:
227 c.comments.extend(pr.comments)
227 c.comments.extend(pr.comments)
228
228
229 # Iterate over ranges (default commit view is always one commit)
229 # Iterate over ranges (default commit view is always one commit)
230 for commit in c.commit_ranges:
230 for commit in c.commit_ranges:
231 c.changes[commit.raw_id] = []
231 c.changes[commit.raw_id] = []
232
232
233 commit2 = commit
233 commit2 = commit
234 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
234 commit1 = commit.parents[0] if commit.parents else EmptyCommit()
235
235
236 _diff = c.rhodecode_repo.get_diff(
236 _diff = c.rhodecode_repo.get_diff(
237 commit1, commit2,
237 commit1, commit2,
238 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
238 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
239 diff_processor = diffs.DiffProcessor(
239 diff_processor = diffs.DiffProcessor(
240 _diff, format='newdiff', diff_limit=diff_limit,
240 _diff, format='newdiff', diff_limit=diff_limit,
241 file_limit=file_limit, show_full_diff=fulldiff)
241 file_limit=file_limit, show_full_diff=fulldiff)
242
242
243 commit_changes = OrderedDict()
243 commit_changes = OrderedDict()
244 if method == 'show':
244 if method == 'show':
245 _parsed = diff_processor.prepare()
245 _parsed = diff_processor.prepare()
246 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
246 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
247
247
248 _parsed = diff_processor.prepare()
248 _parsed = diff_processor.prepare()
249
249
250 def _node_getter(commit):
250 def _node_getter(commit):
251 def get_node(fname):
251 def get_node(fname):
252 try:
252 try:
253 return commit.get_node(fname)
253 return commit.get_node(fname)
254 except NodeDoesNotExistError:
254 except NodeDoesNotExistError:
255 return None
255 return None
256 return get_node
256 return get_node
257
257
258 inline_comments = ChangesetCommentsModel().get_inline_comments(
258 inline_comments = ChangesetCommentsModel().get_inline_comments(
259 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
259 c.rhodecode_db_repo.repo_id, revision=commit.raw_id)
260 c.inline_cnt = ChangesetCommentsModel().get_inline_comments_count(
260 c.inline_cnt = ChangesetCommentsModel().get_inline_comments_count(
261 inline_comments)
261 inline_comments)
262
262
263 diffset = codeblocks.DiffSet(
263 diffset = codeblocks.DiffSet(
264 repo_name=c.repo_name,
264 repo_name=c.repo_name,
265 source_node_getter=_node_getter(commit1),
265 source_node_getter=_node_getter(commit1),
266 target_node_getter=_node_getter(commit2),
266 target_node_getter=_node_getter(commit2),
267 comments=inline_comments
267 comments=inline_comments
268 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
268 ).render_patchset(_parsed, commit1.raw_id, commit2.raw_id)
269 c.changes[commit.raw_id] = diffset
269 c.changes[commit.raw_id] = diffset
270 else:
270 else:
271 # downloads/raw we only need RAW diff nothing else
271 # downloads/raw we only need RAW diff nothing else
272 diff = diff_processor.as_raw()
272 diff = diff_processor.as_raw()
273 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
273 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
274
274
275 # sort comments by how they were generated
275 # sort comments by how they were generated
276 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
276 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
277
277
278
278
279 if len(c.commit_ranges) == 1:
279 if len(c.commit_ranges) == 1:
280 c.commit = c.commit_ranges[0]
280 c.commit = c.commit_ranges[0]
281 c.parent_tmpl = ''.join(
281 c.parent_tmpl = ''.join(
282 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
282 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
283 if method == 'download':
283 if method == 'download':
284 response.content_type = 'text/plain'
284 response.content_type = 'text/plain'
285 response.content_disposition = (
285 response.content_disposition = (
286 'attachment; filename=%s.diff' % commit_id_range[:12])
286 'attachment; filename=%s.diff' % commit_id_range[:12])
287 return diff
287 return diff
288 elif method == 'patch':
288 elif method == 'patch':
289 response.content_type = 'text/plain'
289 response.content_type = 'text/plain'
290 c.diff = safe_unicode(diff)
290 c.diff = safe_unicode(diff)
291 return render('changeset/patch_changeset.mako')
291 return render('changeset/patch_changeset.mako')
292 elif method == 'raw':
292 elif method == 'raw':
293 response.content_type = 'text/plain'
293 response.content_type = 'text/plain'
294 return diff
294 return diff
295 elif method == 'show':
295 elif method == 'show':
296 if len(c.commit_ranges) == 1:
296 if len(c.commit_ranges) == 1:
297 return render('changeset/changeset.mako')
297 return render('changeset/changeset.mako')
298 else:
298 else:
299 c.ancestor = None
299 c.ancestor = None
300 c.target_repo = c.rhodecode_db_repo
300 c.target_repo = c.rhodecode_db_repo
301 return render('changeset/changeset_range.mako')
301 return render('changeset/changeset_range.mako')
302
302
303 @LoginRequired()
303 @LoginRequired()
304 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
304 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
305 'repository.admin')
305 'repository.admin')
306 def index(self, revision, method='show'):
306 def index(self, revision, method='show'):
307 return self._index(revision, method=method)
307 return self._index(revision, method=method)
308
308
309 @LoginRequired()
309 @LoginRequired()
310 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
310 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
311 'repository.admin')
311 'repository.admin')
312 def changeset_raw(self, revision):
312 def changeset_raw(self, revision):
313 return self._index(revision, method='raw')
313 return self._index(revision, method='raw')
314
314
315 @LoginRequired()
315 @LoginRequired()
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
316 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
317 'repository.admin')
317 'repository.admin')
318 def changeset_patch(self, revision):
318 def changeset_patch(self, revision):
319 return self._index(revision, method='patch')
319 return self._index(revision, method='patch')
320
320
321 @LoginRequired()
321 @LoginRequired()
322 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
322 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
323 'repository.admin')
323 'repository.admin')
324 def changeset_download(self, revision):
324 def changeset_download(self, revision):
325 return self._index(revision, method='download')
325 return self._index(revision, method='download')
326
326
327 @LoginRequired()
327 @LoginRequired()
328 @NotAnonymous()
328 @NotAnonymous()
329 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
329 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
330 'repository.admin')
330 'repository.admin')
331 @auth.CSRFRequired()
331 @auth.CSRFRequired()
332 @jsonify
332 @jsonify
333 def comment(self, repo_name, revision):
333 def comment(self, repo_name, revision):
334 commit_id = revision
334 commit_id = revision
335 status = request.POST.get('changeset_status', None)
335 status = request.POST.get('changeset_status', None)
336 text = request.POST.get('text')
336 text = request.POST.get('text')
337 if status:
337 if status:
338 text = text or (_('Status change %(transition_icon)s %(status)s')
338 text = text or (_('Status change %(transition_icon)s %(status)s')
339 % {'transition_icon': '>',
339 % {'transition_icon': '>',
340 'status': ChangesetStatus.get_status_lbl(status)})
340 'status': ChangesetStatus.get_status_lbl(status)})
341
341
342 multi_commit_ids = filter(
342 multi_commit_ids = filter(
343 lambda s: s not in ['', None],
343 lambda s: s not in ['', None],
344 request.POST.get('commit_ids', '').split(','),)
344 request.POST.get('commit_ids', '').split(','),)
345
345
346 commit_ids = multi_commit_ids or [commit_id]
346 commit_ids = multi_commit_ids or [commit_id]
347 comment = None
347 comment = None
348 for current_id in filter(None, commit_ids):
348 for current_id in filter(None, commit_ids):
349 c.co = comment = ChangesetCommentsModel().create(
349 c.co = comment = ChangesetCommentsModel().create(
350 text=text,
350 text=text,
351 repo=c.rhodecode_db_repo.repo_id,
351 repo=c.rhodecode_db_repo.repo_id,
352 user=c.rhodecode_user.user_id,
352 user=c.rhodecode_user.user_id,
353 revision=current_id,
353 commit_id=current_id,
354 f_path=request.POST.get('f_path'),
354 f_path=request.POST.get('f_path'),
355 line_no=request.POST.get('line'),
355 line_no=request.POST.get('line'),
356 status_change=(ChangesetStatus.get_status_lbl(status)
356 status_change=(ChangesetStatus.get_status_lbl(status)
357 if status else None),
357 if status else None),
358 status_change_type=status
358 status_change_type=status
359 )
359 )
360 c.inline_comment = True if comment.line_no else False
360 c.inline_comment = True if comment.line_no else False
361
361
362 # get status if set !
362 # get status if set !
363 if status:
363 if status:
364 # if latest status was from pull request and it's closed
364 # if latest status was from pull request and it's closed
365 # disallow changing status !
365 # disallow changing status !
366 # dont_allow_on_closed_pull_request = True !
366 # dont_allow_on_closed_pull_request = True !
367
367
368 try:
368 try:
369 ChangesetStatusModel().set_status(
369 ChangesetStatusModel().set_status(
370 c.rhodecode_db_repo.repo_id,
370 c.rhodecode_db_repo.repo_id,
371 status,
371 status,
372 c.rhodecode_user.user_id,
372 c.rhodecode_user.user_id,
373 comment,
373 comment,
374 revision=current_id,
374 revision=current_id,
375 dont_allow_on_closed_pull_request=True
375 dont_allow_on_closed_pull_request=True
376 )
376 )
377 except StatusChangeOnClosedPullRequestError:
377 except StatusChangeOnClosedPullRequestError:
378 msg = _('Changing the status of a commit associated with '
378 msg = _('Changing the status of a commit associated with '
379 'a closed pull request is not allowed')
379 'a closed pull request is not allowed')
380 log.exception(msg)
380 log.exception(msg)
381 h.flash(msg, category='warning')
381 h.flash(msg, category='warning')
382 return redirect(h.url(
382 return redirect(h.url(
383 'changeset_home', repo_name=repo_name,
383 'changeset_home', repo_name=repo_name,
384 revision=current_id))
384 revision=current_id))
385
385
386 # finalize, commit and redirect
386 # finalize, commit and redirect
387 Session().commit()
387 Session().commit()
388
388
389 data = {
389 data = {
390 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
390 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
391 }
391 }
392 if comment:
392 if comment:
393 data.update(comment.get_dict())
393 data.update(comment.get_dict())
394 data.update({'rendered_text':
394 data.update({'rendered_text':
395 render('changeset/changeset_comment_block.mako')})
395 render('changeset/changeset_comment_block.mako')})
396
396
397 return data
397 return data
398
398
399 @LoginRequired()
399 @LoginRequired()
400 @NotAnonymous()
400 @NotAnonymous()
401 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
401 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
402 'repository.admin')
402 'repository.admin')
403 @auth.CSRFRequired()
403 @auth.CSRFRequired()
404 def preview_comment(self):
404 def preview_comment(self):
405 # Technically a CSRF token is not needed as no state changes with this
405 # Technically a CSRF token is not needed as no state changes with this
406 # call. However, as this is a POST is better to have it, so automated
406 # call. However, as this is a POST is better to have it, so automated
407 # tools don't flag it as potential CSRF.
407 # tools don't flag it as potential CSRF.
408 # Post is required because the payload could be bigger than the maximum
408 # Post is required because the payload could be bigger than the maximum
409 # allowed by GET.
409 # allowed by GET.
410 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
410 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
411 raise HTTPBadRequest()
411 raise HTTPBadRequest()
412 text = request.POST.get('text')
412 text = request.POST.get('text')
413 renderer = request.POST.get('renderer') or 'rst'
413 renderer = request.POST.get('renderer') or 'rst'
414 if text:
414 if text:
415 return h.render(text, renderer=renderer, mentions=True)
415 return h.render(text, renderer=renderer, mentions=True)
416 return ''
416 return ''
417
417
418 @LoginRequired()
418 @LoginRequired()
419 @NotAnonymous()
419 @NotAnonymous()
420 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
420 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
421 'repository.admin')
421 'repository.admin')
422 @auth.CSRFRequired()
422 @auth.CSRFRequired()
423 @jsonify
423 @jsonify
424 def delete_comment(self, repo_name, comment_id):
424 def delete_comment(self, repo_name, comment_id):
425 comment = ChangesetComment.get(comment_id)
425 comment = ChangesetComment.get(comment_id)
426 owner = (comment.author.user_id == c.rhodecode_user.user_id)
426 owner = (comment.author.user_id == c.rhodecode_user.user_id)
427 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
427 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(c.repo_name)
428 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
428 if h.HasPermissionAny('hg.admin')() or is_repo_admin or owner:
429 ChangesetCommentsModel().delete(comment=comment)
429 ChangesetCommentsModel().delete(comment=comment)
430 Session().commit()
430 Session().commit()
431 return True
431 return True
432 else:
432 else:
433 raise HTTPForbidden()
433 raise HTTPForbidden()
434
434
435 @LoginRequired()
435 @LoginRequired()
436 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
436 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
437 'repository.admin')
437 'repository.admin')
438 @jsonify
438 @jsonify
439 def changeset_info(self, repo_name, revision):
439 def changeset_info(self, repo_name, revision):
440 if request.is_xhr:
440 if request.is_xhr:
441 try:
441 try:
442 return c.rhodecode_repo.get_commit(commit_id=revision)
442 return c.rhodecode_repo.get_commit(commit_id=revision)
443 except CommitDoesNotExistError as e:
443 except CommitDoesNotExistError as e:
444 return EmptyCommit(message=str(e))
444 return EmptyCommit(message=str(e))
445 else:
445 else:
446 raise HTTPBadRequest()
446 raise HTTPBadRequest()
447
447
448 @LoginRequired()
448 @LoginRequired()
449 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
449 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
450 'repository.admin')
450 'repository.admin')
451 @jsonify
451 @jsonify
452 def changeset_children(self, repo_name, revision):
452 def changeset_children(self, repo_name, revision):
453 if request.is_xhr:
453 if request.is_xhr:
454 commit = c.rhodecode_repo.get_commit(commit_id=revision)
454 commit = c.rhodecode_repo.get_commit(commit_id=revision)
455 result = {"results": commit.children}
455 result = {"results": commit.children}
456 return result
456 return result
457 else:
457 else:
458 raise HTTPBadRequest()
458 raise HTTPBadRequest()
459
459
460 @LoginRequired()
460 @LoginRequired()
461 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
461 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
462 'repository.admin')
462 'repository.admin')
463 @jsonify
463 @jsonify
464 def changeset_parents(self, repo_name, revision):
464 def changeset_parents(self, repo_name, revision):
465 if request.is_xhr:
465 if request.is_xhr:
466 commit = c.rhodecode_repo.get_commit(commit_id=revision)
466 commit = c.rhodecode_repo.get_commit(commit_id=revision)
467 result = {"results": commit.parents}
467 result = {"results": commit.parents}
468 return result
468 return result
469 else:
469 else:
470 raise HTTPBadRequest()
470 raise HTTPBadRequest()
@@ -1,530 +1,530 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 comments model for RhodeCode
22 comments model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import collections
27 import collections
28
28
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from pyramid.threadlocal import get_current_registry
32 from pyramid.threadlocal import get_current_registry
33 from sqlalchemy.sql.expression import null
33 from sqlalchemy.sql.expression import null
34 from sqlalchemy.sql.functions import coalesce
34 from sqlalchemy.sql.functions import coalesce
35
35
36 from rhodecode.lib import helpers as h, diffs
36 from rhodecode.lib import helpers as h, diffs
37 from rhodecode.lib.channelstream import channelstream_request
37 from rhodecode.lib.channelstream import channelstream_request
38 from rhodecode.lib.utils import action_logger
38 from rhodecode.lib.utils import action_logger
39 from rhodecode.lib.utils2 import extract_mentioned_users
39 from rhodecode.lib.utils2 import extract_mentioned_users
40 from rhodecode.model import BaseModel
40 from rhodecode.model import BaseModel
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 ChangesetComment, User, Notification, PullRequest)
42 ChangesetComment, User, Notification, PullRequest)
43 from rhodecode.model.notification import NotificationModel
43 from rhodecode.model.notification import NotificationModel
44 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
45 from rhodecode.model.settings import VcsSettingsModel
45 from rhodecode.model.settings import VcsSettingsModel
46 from rhodecode.model.notification import EmailNotificationModel
46 from rhodecode.model.notification import EmailNotificationModel
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class ChangesetCommentsModel(BaseModel):
51 class ChangesetCommentsModel(BaseModel):
52
52
53 cls = ChangesetComment
53 cls = ChangesetComment
54
54
55 DIFF_CONTEXT_BEFORE = 3
55 DIFF_CONTEXT_BEFORE = 3
56 DIFF_CONTEXT_AFTER = 3
56 DIFF_CONTEXT_AFTER = 3
57
57
58 def __get_commit_comment(self, changeset_comment):
58 def __get_commit_comment(self, changeset_comment):
59 return self._get_instance(ChangesetComment, changeset_comment)
59 return self._get_instance(ChangesetComment, changeset_comment)
60
60
61 def __get_pull_request(self, pull_request):
61 def __get_pull_request(self, pull_request):
62 return self._get_instance(PullRequest, pull_request)
62 return self._get_instance(PullRequest, pull_request)
63
63
64 def _extract_mentions(self, s):
64 def _extract_mentions(self, s):
65 user_objects = []
65 user_objects = []
66 for username in extract_mentioned_users(s):
66 for username in extract_mentioned_users(s):
67 user_obj = User.get_by_username(username, case_insensitive=True)
67 user_obj = User.get_by_username(username, case_insensitive=True)
68 if user_obj:
68 if user_obj:
69 user_objects.append(user_obj)
69 user_objects.append(user_obj)
70 return user_objects
70 return user_objects
71
71
72 def _get_renderer(self, global_renderer='rst'):
72 def _get_renderer(self, global_renderer='rst'):
73 try:
73 try:
74 # try reading from visual context
74 # try reading from visual context
75 from pylons import tmpl_context
75 from pylons import tmpl_context
76 global_renderer = tmpl_context.visual.default_renderer
76 global_renderer = tmpl_context.visual.default_renderer
77 except AttributeError:
77 except AttributeError:
78 log.debug("Renderer not set, falling back "
78 log.debug("Renderer not set, falling back "
79 "to default renderer '%s'", global_renderer)
79 "to default renderer '%s'", global_renderer)
80 except Exception:
80 except Exception:
81 log.error(traceback.format_exc())
81 log.error(traceback.format_exc())
82 return global_renderer
82 return global_renderer
83
83
84 def create(self, text, repo, user, revision=None, pull_request=None,
84 def create(self, text, repo, user, commit_id=None, pull_request=None,
85 f_path=None, line_no=None, status_change=None,
85 f_path=None, line_no=None, status_change=None, comment_type=None,
86 status_change_type=None, closing_pr=False,
86 status_change_type=None, closing_pr=False,
87 send_email=True, renderer=None):
87 send_email=True, renderer=None):
88 """
88 """
89 Creates new comment for commit or pull request.
89 Creates new comment for commit or pull request.
90 IF status_change is not none this comment is associated with a
90 IF status_change is not none this comment is associated with a
91 status change of commit or commit associated with pull request
91 status change of commit or commit associated with pull request
92
92
93 :param text:
93 :param text:
94 :param repo:
94 :param repo:
95 :param user:
95 :param user:
96 :param revision:
96 :param commit_id:
97 :param pull_request:
97 :param pull_request:
98 :param f_path:
98 :param f_path:
99 :param line_no:
99 :param line_no:
100 :param status_change: Label for status change
100 :param status_change: Label for status change
101 :param comment_type: Type of comment
101 :param status_change_type: type of status change
102 :param status_change_type: type of status change
102 :param closing_pr:
103 :param closing_pr:
103 :param send_email:
104 :param send_email:
105 :param renderer: pick renderer for this comment
104 """
106 """
105 if not text:
107 if not text:
106 log.warning('Missing text for comment, skipping...')
108 log.warning('Missing text for comment, skipping...')
107 return
109 return
108
110
109 if not renderer:
111 if not renderer:
110 renderer = self._get_renderer()
112 renderer = self._get_renderer()
111
113
112 repo = self._get_repo(repo)
114 repo = self._get_repo(repo)
113 user = self._get_user(user)
115 user = self._get_user(user)
114 comment = ChangesetComment()
116 comment = ChangesetComment()
115 comment.renderer = renderer
117 comment.renderer = renderer
116 comment.repo = repo
118 comment.repo = repo
117 comment.author = user
119 comment.author = user
118 comment.text = text
120 comment.text = text
119 comment.f_path = f_path
121 comment.f_path = f_path
120 comment.line_no = line_no
122 comment.line_no = line_no
121
123
122 #TODO (marcink): fix this and remove revision as param
123 commit_id = revision
124 pull_request_id = pull_request
124 pull_request_id = pull_request
125
125
126 commit_obj = None
126 commit_obj = None
127 pull_request_obj = None
127 pull_request_obj = None
128
128
129 if commit_id:
129 if commit_id:
130 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
130 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
131 # do a lookup, so we don't pass something bad here
131 # do a lookup, so we don't pass something bad here
132 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
132 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
133 comment.revision = commit_obj.raw_id
133 comment.revision = commit_obj.raw_id
134
134
135 elif pull_request_id:
135 elif pull_request_id:
136 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
136 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
137 pull_request_obj = self.__get_pull_request(pull_request_id)
137 pull_request_obj = self.__get_pull_request(pull_request_id)
138 comment.pull_request = pull_request_obj
138 comment.pull_request = pull_request_obj
139 else:
139 else:
140 raise Exception('Please specify commit or pull_request_id')
140 raise Exception('Please specify commit or pull_request_id')
141
141
142 Session().add(comment)
142 Session().add(comment)
143 Session().flush()
143 Session().flush()
144 kwargs = {
144 kwargs = {
145 'user': user,
145 'user': user,
146 'renderer_type': renderer,
146 'renderer_type': renderer,
147 'repo_name': repo.repo_name,
147 'repo_name': repo.repo_name,
148 'status_change': status_change,
148 'status_change': status_change,
149 'status_change_type': status_change_type,
149 'status_change_type': status_change_type,
150 'comment_body': text,
150 'comment_body': text,
151 'comment_file': f_path,
151 'comment_file': f_path,
152 'comment_line': line_no,
152 'comment_line': line_no,
153 }
153 }
154
154
155 if commit_obj:
155 if commit_obj:
156 recipients = ChangesetComment.get_users(
156 recipients = ChangesetComment.get_users(
157 revision=commit_obj.raw_id)
157 revision=commit_obj.raw_id)
158 # add commit author if it's in RhodeCode system
158 # add commit author if it's in RhodeCode system
159 cs_author = User.get_from_cs_author(commit_obj.author)
159 cs_author = User.get_from_cs_author(commit_obj.author)
160 if not cs_author:
160 if not cs_author:
161 # use repo owner if we cannot extract the author correctly
161 # use repo owner if we cannot extract the author correctly
162 cs_author = repo.user
162 cs_author = repo.user
163 recipients += [cs_author]
163 recipients += [cs_author]
164
164
165 commit_comment_url = self.get_url(comment)
165 commit_comment_url = self.get_url(comment)
166
166
167 target_repo_url = h.link_to(
167 target_repo_url = h.link_to(
168 repo.repo_name,
168 repo.repo_name,
169 h.url('summary_home',
169 h.url('summary_home',
170 repo_name=repo.repo_name, qualified=True))
170 repo_name=repo.repo_name, qualified=True))
171
171
172 # commit specifics
172 # commit specifics
173 kwargs.update({
173 kwargs.update({
174 'commit': commit_obj,
174 'commit': commit_obj,
175 'commit_message': commit_obj.message,
175 'commit_message': commit_obj.message,
176 'commit_target_repo': target_repo_url,
176 'commit_target_repo': target_repo_url,
177 'commit_comment_url': commit_comment_url,
177 'commit_comment_url': commit_comment_url,
178 })
178 })
179
179
180 elif pull_request_obj:
180 elif pull_request_obj:
181 # get the current participants of this pull request
181 # get the current participants of this pull request
182 recipients = ChangesetComment.get_users(
182 recipients = ChangesetComment.get_users(
183 pull_request_id=pull_request_obj.pull_request_id)
183 pull_request_id=pull_request_obj.pull_request_id)
184 # add pull request author
184 # add pull request author
185 recipients += [pull_request_obj.author]
185 recipients += [pull_request_obj.author]
186
186
187 # add the reviewers to notification
187 # add the reviewers to notification
188 recipients += [x.user for x in pull_request_obj.reviewers]
188 recipients += [x.user for x in pull_request_obj.reviewers]
189
189
190 pr_target_repo = pull_request_obj.target_repo
190 pr_target_repo = pull_request_obj.target_repo
191 pr_source_repo = pull_request_obj.source_repo
191 pr_source_repo = pull_request_obj.source_repo
192
192
193 pr_comment_url = h.url(
193 pr_comment_url = h.url(
194 'pullrequest_show',
194 'pullrequest_show',
195 repo_name=pr_target_repo.repo_name,
195 repo_name=pr_target_repo.repo_name,
196 pull_request_id=pull_request_obj.pull_request_id,
196 pull_request_id=pull_request_obj.pull_request_id,
197 anchor='comment-%s' % comment.comment_id,
197 anchor='comment-%s' % comment.comment_id,
198 qualified=True,)
198 qualified=True,)
199
199
200 # set some variables for email notification
200 # set some variables for email notification
201 pr_target_repo_url = h.url(
201 pr_target_repo_url = h.url(
202 'summary_home', repo_name=pr_target_repo.repo_name,
202 'summary_home', repo_name=pr_target_repo.repo_name,
203 qualified=True)
203 qualified=True)
204
204
205 pr_source_repo_url = h.url(
205 pr_source_repo_url = h.url(
206 'summary_home', repo_name=pr_source_repo.repo_name,
206 'summary_home', repo_name=pr_source_repo.repo_name,
207 qualified=True)
207 qualified=True)
208
208
209 # pull request specifics
209 # pull request specifics
210 kwargs.update({
210 kwargs.update({
211 'pull_request': pull_request_obj,
211 'pull_request': pull_request_obj,
212 'pr_id': pull_request_obj.pull_request_id,
212 'pr_id': pull_request_obj.pull_request_id,
213 'pr_target_repo': pr_target_repo,
213 'pr_target_repo': pr_target_repo,
214 'pr_target_repo_url': pr_target_repo_url,
214 'pr_target_repo_url': pr_target_repo_url,
215 'pr_source_repo': pr_source_repo,
215 'pr_source_repo': pr_source_repo,
216 'pr_source_repo_url': pr_source_repo_url,
216 'pr_source_repo_url': pr_source_repo_url,
217 'pr_comment_url': pr_comment_url,
217 'pr_comment_url': pr_comment_url,
218 'pr_closing': closing_pr,
218 'pr_closing': closing_pr,
219 })
219 })
220 if send_email:
220 if send_email:
221 # pre-generate the subject for notification itself
221 # pre-generate the subject for notification itself
222 (subject,
222 (subject,
223 _h, _e, # we don't care about those
223 _h, _e, # we don't care about those
224 body_plaintext) = EmailNotificationModel().render_email(
224 body_plaintext) = EmailNotificationModel().render_email(
225 notification_type, **kwargs)
225 notification_type, **kwargs)
226
226
227 mention_recipients = set(
227 mention_recipients = set(
228 self._extract_mentions(text)).difference(recipients)
228 self._extract_mentions(text)).difference(recipients)
229
229
230 # create notification objects, and emails
230 # create notification objects, and emails
231 NotificationModel().create(
231 NotificationModel().create(
232 created_by=user,
232 created_by=user,
233 notification_subject=subject,
233 notification_subject=subject,
234 notification_body=body_plaintext,
234 notification_body=body_plaintext,
235 notification_type=notification_type,
235 notification_type=notification_type,
236 recipients=recipients,
236 recipients=recipients,
237 mention_recipients=mention_recipients,
237 mention_recipients=mention_recipients,
238 email_kwargs=kwargs,
238 email_kwargs=kwargs,
239 )
239 )
240
240
241 action = (
241 action = (
242 'user_commented_pull_request:{}'.format(
242 'user_commented_pull_request:{}'.format(
243 comment.pull_request.pull_request_id)
243 comment.pull_request.pull_request_id)
244 if comment.pull_request
244 if comment.pull_request
245 else 'user_commented_revision:{}'.format(comment.revision)
245 else 'user_commented_revision:{}'.format(comment.revision)
246 )
246 )
247 action_logger(user, action, comment.repo)
247 action_logger(user, action, comment.repo)
248
248
249 registry = get_current_registry()
249 registry = get_current_registry()
250 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
250 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
251 channelstream_config = rhodecode_plugins.get('channelstream', {})
251 channelstream_config = rhodecode_plugins.get('channelstream', {})
252 msg_url = ''
252 msg_url = ''
253 if commit_obj:
253 if commit_obj:
254 msg_url = commit_comment_url
254 msg_url = commit_comment_url
255 repo_name = repo.repo_name
255 repo_name = repo.repo_name
256 elif pull_request_obj:
256 elif pull_request_obj:
257 msg_url = pr_comment_url
257 msg_url = pr_comment_url
258 repo_name = pr_target_repo.repo_name
258 repo_name = pr_target_repo.repo_name
259
259
260 if channelstream_config.get('enabled'):
260 if channelstream_config.get('enabled'):
261 message = '<strong>{}</strong> {} - ' \
261 message = '<strong>{}</strong> {} - ' \
262 '<a onclick="window.location=\'{}\';' \
262 '<a onclick="window.location=\'{}\';' \
263 'window.location.reload()">' \
263 'window.location.reload()">' \
264 '<strong>{}</strong></a>'
264 '<strong>{}</strong></a>'
265 message = message.format(
265 message = message.format(
266 user.username, _('made a comment'), msg_url,
266 user.username, _('made a comment'), msg_url,
267 _('Show it now'))
267 _('Show it now'))
268 channel = '/repo${}$/pr/{}'.format(
268 channel = '/repo${}$/pr/{}'.format(
269 repo_name,
269 repo_name,
270 pull_request_id
270 pull_request_id
271 )
271 )
272 payload = {
272 payload = {
273 'type': 'message',
273 'type': 'message',
274 'timestamp': datetime.utcnow(),
274 'timestamp': datetime.utcnow(),
275 'user': 'system',
275 'user': 'system',
276 'exclude_users': [user.username],
276 'exclude_users': [user.username],
277 'channel': channel,
277 'channel': channel,
278 'message': {
278 'message': {
279 'message': message,
279 'message': message,
280 'level': 'info',
280 'level': 'info',
281 'topic': '/notifications'
281 'topic': '/notifications'
282 }
282 }
283 }
283 }
284 channelstream_request(channelstream_config, [payload],
284 channelstream_request(channelstream_config, [payload],
285 '/message', raise_exc=False)
285 '/message', raise_exc=False)
286
286
287 return comment
287 return comment
288
288
289 def delete(self, comment):
289 def delete(self, comment):
290 """
290 """
291 Deletes given comment
291 Deletes given comment
292
292
293 :param comment_id:
293 :param comment_id:
294 """
294 """
295 comment = self.__get_commit_comment(comment)
295 comment = self.__get_commit_comment(comment)
296 Session().delete(comment)
296 Session().delete(comment)
297
297
298 return comment
298 return comment
299
299
300 def get_all_comments(self, repo_id, revision=None, pull_request=None):
300 def get_all_comments(self, repo_id, revision=None, pull_request=None):
301 q = ChangesetComment.query()\
301 q = ChangesetComment.query()\
302 .filter(ChangesetComment.repo_id == repo_id)
302 .filter(ChangesetComment.repo_id == repo_id)
303 if revision:
303 if revision:
304 q = q.filter(ChangesetComment.revision == revision)
304 q = q.filter(ChangesetComment.revision == revision)
305 elif pull_request:
305 elif pull_request:
306 pull_request = self.__get_pull_request(pull_request)
306 pull_request = self.__get_pull_request(pull_request)
307 q = q.filter(ChangesetComment.pull_request == pull_request)
307 q = q.filter(ChangesetComment.pull_request == pull_request)
308 else:
308 else:
309 raise Exception('Please specify commit or pull_request')
309 raise Exception('Please specify commit or pull_request')
310 q = q.order_by(ChangesetComment.created_on)
310 q = q.order_by(ChangesetComment.created_on)
311 return q.all()
311 return q.all()
312
312
313 def get_url(self, comment):
313 def get_url(self, comment):
314 comment = self.__get_commit_comment(comment)
314 comment = self.__get_commit_comment(comment)
315 if comment.pull_request:
315 if comment.pull_request:
316 return h.url(
316 return h.url(
317 'pullrequest_show',
317 'pullrequest_show',
318 repo_name=comment.pull_request.target_repo.repo_name,
318 repo_name=comment.pull_request.target_repo.repo_name,
319 pull_request_id=comment.pull_request.pull_request_id,
319 pull_request_id=comment.pull_request.pull_request_id,
320 anchor='comment-%s' % comment.comment_id,
320 anchor='comment-%s' % comment.comment_id,
321 qualified=True,)
321 qualified=True,)
322 else:
322 else:
323 return h.url(
323 return h.url(
324 'changeset_home',
324 'changeset_home',
325 repo_name=comment.repo.repo_name,
325 repo_name=comment.repo.repo_name,
326 revision=comment.revision,
326 revision=comment.revision,
327 anchor='comment-%s' % comment.comment_id,
327 anchor='comment-%s' % comment.comment_id,
328 qualified=True,)
328 qualified=True,)
329
329
330 def get_comments(self, repo_id, revision=None, pull_request=None):
330 def get_comments(self, repo_id, revision=None, pull_request=None):
331 """
331 """
332 Gets main comments based on revision or pull_request_id
332 Gets main comments based on revision or pull_request_id
333
333
334 :param repo_id:
334 :param repo_id:
335 :param revision:
335 :param revision:
336 :param pull_request:
336 :param pull_request:
337 """
337 """
338
338
339 q = ChangesetComment.query()\
339 q = ChangesetComment.query()\
340 .filter(ChangesetComment.repo_id == repo_id)\
340 .filter(ChangesetComment.repo_id == repo_id)\
341 .filter(ChangesetComment.line_no == None)\
341 .filter(ChangesetComment.line_no == None)\
342 .filter(ChangesetComment.f_path == None)
342 .filter(ChangesetComment.f_path == None)
343 if revision:
343 if revision:
344 q = q.filter(ChangesetComment.revision == revision)
344 q = q.filter(ChangesetComment.revision == revision)
345 elif pull_request:
345 elif pull_request:
346 pull_request = self.__get_pull_request(pull_request)
346 pull_request = self.__get_pull_request(pull_request)
347 q = q.filter(ChangesetComment.pull_request == pull_request)
347 q = q.filter(ChangesetComment.pull_request == pull_request)
348 else:
348 else:
349 raise Exception('Please specify commit or pull_request')
349 raise Exception('Please specify commit or pull_request')
350 q = q.order_by(ChangesetComment.created_on)
350 q = q.order_by(ChangesetComment.created_on)
351 return q.all()
351 return q.all()
352
352
353 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
353 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
354 q = self._get_inline_comments_query(repo_id, revision, pull_request)
354 q = self._get_inline_comments_query(repo_id, revision, pull_request)
355 return self._group_comments_by_path_and_line_number(q)
355 return self._group_comments_by_path_and_line_number(q)
356
356
357 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
357 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
358 version=None, include_aggregates=False):
358 version=None, include_aggregates=False):
359 version_aggregates = collections.defaultdict(list)
359 version_aggregates = collections.defaultdict(list)
360 inline_cnt = 0
360 inline_cnt = 0
361 for fname, per_line_comments in inline_comments.iteritems():
361 for fname, per_line_comments in inline_comments.iteritems():
362 for lno, comments in per_line_comments.iteritems():
362 for lno, comments in per_line_comments.iteritems():
363 for comm in comments:
363 for comm in comments:
364 version_aggregates[comm.pull_request_version_id].append(comm)
364 version_aggregates[comm.pull_request_version_id].append(comm)
365 if not comm.outdated_at_version(version) and skip_outdated:
365 if not comm.outdated_at_version(version) and skip_outdated:
366 inline_cnt += 1
366 inline_cnt += 1
367
367
368 if include_aggregates:
368 if include_aggregates:
369 return inline_cnt, version_aggregates
369 return inline_cnt, version_aggregates
370 return inline_cnt
370 return inline_cnt
371
371
372 def get_outdated_comments(self, repo_id, pull_request):
372 def get_outdated_comments(self, repo_id, pull_request):
373 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
373 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
374 # of a pull request.
374 # of a pull request.
375 q = self._all_inline_comments_of_pull_request(pull_request)
375 q = self._all_inline_comments_of_pull_request(pull_request)
376 q = q.filter(
376 q = q.filter(
377 ChangesetComment.display_state ==
377 ChangesetComment.display_state ==
378 ChangesetComment.COMMENT_OUTDATED
378 ChangesetComment.COMMENT_OUTDATED
379 ).order_by(ChangesetComment.comment_id.asc())
379 ).order_by(ChangesetComment.comment_id.asc())
380
380
381 return self._group_comments_by_path_and_line_number(q)
381 return self._group_comments_by_path_and_line_number(q)
382
382
383 def _get_inline_comments_query(self, repo_id, revision, pull_request):
383 def _get_inline_comments_query(self, repo_id, revision, pull_request):
384 # TODO: johbo: Split this into two methods: One for PR and one for
384 # TODO: johbo: Split this into two methods: One for PR and one for
385 # commit.
385 # commit.
386 if revision:
386 if revision:
387 q = Session().query(ChangesetComment).filter(
387 q = Session().query(ChangesetComment).filter(
388 ChangesetComment.repo_id == repo_id,
388 ChangesetComment.repo_id == repo_id,
389 ChangesetComment.line_no != null(),
389 ChangesetComment.line_no != null(),
390 ChangesetComment.f_path != null(),
390 ChangesetComment.f_path != null(),
391 ChangesetComment.revision == revision)
391 ChangesetComment.revision == revision)
392
392
393 elif pull_request:
393 elif pull_request:
394 pull_request = self.__get_pull_request(pull_request)
394 pull_request = self.__get_pull_request(pull_request)
395 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
395 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
396 q = self._visible_inline_comments_of_pull_request(pull_request)
396 q = self._visible_inline_comments_of_pull_request(pull_request)
397 else:
397 else:
398 q = self._all_inline_comments_of_pull_request(pull_request)
398 q = self._all_inline_comments_of_pull_request(pull_request)
399
399
400 else:
400 else:
401 raise Exception('Please specify commit or pull_request_id')
401 raise Exception('Please specify commit or pull_request_id')
402 q = q.order_by(ChangesetComment.comment_id.asc())
402 q = q.order_by(ChangesetComment.comment_id.asc())
403 return q
403 return q
404
404
405 def _group_comments_by_path_and_line_number(self, q):
405 def _group_comments_by_path_and_line_number(self, q):
406 comments = q.all()
406 comments = q.all()
407 paths = collections.defaultdict(lambda: collections.defaultdict(list))
407 paths = collections.defaultdict(lambda: collections.defaultdict(list))
408 for co in comments:
408 for co in comments:
409 paths[co.f_path][co.line_no].append(co)
409 paths[co.f_path][co.line_no].append(co)
410 return paths
410 return paths
411
411
412 @classmethod
412 @classmethod
413 def needed_extra_diff_context(cls):
413 def needed_extra_diff_context(cls):
414 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
414 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
415
415
416 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
416 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
417 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
417 if not ChangesetCommentsModel.use_outdated_comments(pull_request):
418 return
418 return
419
419
420 comments = self._visible_inline_comments_of_pull_request(pull_request)
420 comments = self._visible_inline_comments_of_pull_request(pull_request)
421 comments_to_outdate = comments.all()
421 comments_to_outdate = comments.all()
422
422
423 for comment in comments_to_outdate:
423 for comment in comments_to_outdate:
424 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
424 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
425
425
426 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
426 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
427 diff_line = _parse_comment_line_number(comment.line_no)
427 diff_line = _parse_comment_line_number(comment.line_no)
428
428
429 try:
429 try:
430 old_context = old_diff_proc.get_context_of_line(
430 old_context = old_diff_proc.get_context_of_line(
431 path=comment.f_path, diff_line=diff_line)
431 path=comment.f_path, diff_line=diff_line)
432 new_context = new_diff_proc.get_context_of_line(
432 new_context = new_diff_proc.get_context_of_line(
433 path=comment.f_path, diff_line=diff_line)
433 path=comment.f_path, diff_line=diff_line)
434 except (diffs.LineNotInDiffException,
434 except (diffs.LineNotInDiffException,
435 diffs.FileNotInDiffException):
435 diffs.FileNotInDiffException):
436 comment.display_state = ChangesetComment.COMMENT_OUTDATED
436 comment.display_state = ChangesetComment.COMMENT_OUTDATED
437 return
437 return
438
438
439 if old_context == new_context:
439 if old_context == new_context:
440 return
440 return
441
441
442 if self._should_relocate_diff_line(diff_line):
442 if self._should_relocate_diff_line(diff_line):
443 new_diff_lines = new_diff_proc.find_context(
443 new_diff_lines = new_diff_proc.find_context(
444 path=comment.f_path, context=old_context,
444 path=comment.f_path, context=old_context,
445 offset=self.DIFF_CONTEXT_BEFORE)
445 offset=self.DIFF_CONTEXT_BEFORE)
446 if not new_diff_lines:
446 if not new_diff_lines:
447 comment.display_state = ChangesetComment.COMMENT_OUTDATED
447 comment.display_state = ChangesetComment.COMMENT_OUTDATED
448 else:
448 else:
449 new_diff_line = self._choose_closest_diff_line(
449 new_diff_line = self._choose_closest_diff_line(
450 diff_line, new_diff_lines)
450 diff_line, new_diff_lines)
451 comment.line_no = _diff_to_comment_line_number(new_diff_line)
451 comment.line_no = _diff_to_comment_line_number(new_diff_line)
452 else:
452 else:
453 comment.display_state = ChangesetComment.COMMENT_OUTDATED
453 comment.display_state = ChangesetComment.COMMENT_OUTDATED
454
454
455 def _should_relocate_diff_line(self, diff_line):
455 def _should_relocate_diff_line(self, diff_line):
456 """
456 """
457 Checks if relocation shall be tried for the given `diff_line`.
457 Checks if relocation shall be tried for the given `diff_line`.
458
458
459 If a comment points into the first lines, then we can have a situation
459 If a comment points into the first lines, then we can have a situation
460 that after an update another line has been added on top. In this case
460 that after an update another line has been added on top. In this case
461 we would find the context still and move the comment around. This
461 we would find the context still and move the comment around. This
462 would be wrong.
462 would be wrong.
463 """
463 """
464 should_relocate = (
464 should_relocate = (
465 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
465 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
466 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
466 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
467 return should_relocate
467 return should_relocate
468
468
469 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
469 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
470 candidate = new_diff_lines[0]
470 candidate = new_diff_lines[0]
471 best_delta = _diff_line_delta(diff_line, candidate)
471 best_delta = _diff_line_delta(diff_line, candidate)
472 for new_diff_line in new_diff_lines[1:]:
472 for new_diff_line in new_diff_lines[1:]:
473 delta = _diff_line_delta(diff_line, new_diff_line)
473 delta = _diff_line_delta(diff_line, new_diff_line)
474 if delta < best_delta:
474 if delta < best_delta:
475 candidate = new_diff_line
475 candidate = new_diff_line
476 best_delta = delta
476 best_delta = delta
477 return candidate
477 return candidate
478
478
479 def _visible_inline_comments_of_pull_request(self, pull_request):
479 def _visible_inline_comments_of_pull_request(self, pull_request):
480 comments = self._all_inline_comments_of_pull_request(pull_request)
480 comments = self._all_inline_comments_of_pull_request(pull_request)
481 comments = comments.filter(
481 comments = comments.filter(
482 coalesce(ChangesetComment.display_state, '') !=
482 coalesce(ChangesetComment.display_state, '') !=
483 ChangesetComment.COMMENT_OUTDATED)
483 ChangesetComment.COMMENT_OUTDATED)
484 return comments
484 return comments
485
485
486 def _all_inline_comments_of_pull_request(self, pull_request):
486 def _all_inline_comments_of_pull_request(self, pull_request):
487 comments = Session().query(ChangesetComment)\
487 comments = Session().query(ChangesetComment)\
488 .filter(ChangesetComment.line_no != None)\
488 .filter(ChangesetComment.line_no != None)\
489 .filter(ChangesetComment.f_path != None)\
489 .filter(ChangesetComment.f_path != None)\
490 .filter(ChangesetComment.pull_request == pull_request)
490 .filter(ChangesetComment.pull_request == pull_request)
491 return comments
491 return comments
492
492
493 @staticmethod
493 @staticmethod
494 def use_outdated_comments(pull_request):
494 def use_outdated_comments(pull_request):
495 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
495 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
496 settings = settings_model.get_general_settings()
496 settings = settings_model.get_general_settings()
497 return settings.get('rhodecode_use_outdated_comments', False)
497 return settings.get('rhodecode_use_outdated_comments', False)
498
498
499
499
500 def _parse_comment_line_number(line_no):
500 def _parse_comment_line_number(line_no):
501 """
501 """
502 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
502 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
503 """
503 """
504 old_line = None
504 old_line = None
505 new_line = None
505 new_line = None
506 if line_no.startswith('o'):
506 if line_no.startswith('o'):
507 old_line = int(line_no[1:])
507 old_line = int(line_no[1:])
508 elif line_no.startswith('n'):
508 elif line_no.startswith('n'):
509 new_line = int(line_no[1:])
509 new_line = int(line_no[1:])
510 else:
510 else:
511 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
511 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
512 return diffs.DiffLineNumber(old_line, new_line)
512 return diffs.DiffLineNumber(old_line, new_line)
513
513
514
514
515 def _diff_to_comment_line_number(diff_line):
515 def _diff_to_comment_line_number(diff_line):
516 if diff_line.new is not None:
516 if diff_line.new is not None:
517 return u'n{}'.format(diff_line.new)
517 return u'n{}'.format(diff_line.new)
518 elif diff_line.old is not None:
518 elif diff_line.old is not None:
519 return u'o{}'.format(diff_line.old)
519 return u'o{}'.format(diff_line.old)
520 return u''
520 return u''
521
521
522
522
523 def _diff_line_delta(a, b):
523 def _diff_line_delta(a, b):
524 if None not in (a.new, b.new):
524 if None not in (a.new, b.new):
525 return abs(a.new - b.new)
525 return abs(a.new - b.new)
526 elif None not in (a.old, b.old):
526 elif None not in (a.old, b.old):
527 return abs(a.old - b.old)
527 return abs(a.old - b.old)
528 else:
528 else:
529 raise ValueError(
529 raise ValueError(
530 "Cannot compute delta between {} and {}".format(a, b))
530 "Cannot compute delta between {} and {}".format(a, b))
General Comments 0
You need to be logged in to leave comments. Login now