##// END OF EJS Templates
db: always use Session() for compatibility, Using Session is actually the...
marcink -
r506:4c6b9282 default
parent child Browse files
Show More
@@ -1,95 +1,95 b''
1 .. _rhodecode-reset-ref:
1 .. _rhodecode-reset-ref:
2
2
3 Settings Management
3 Settings Management
4 -------------------
4 -------------------
5
5
6 All |RCE| settings can be set from the user interface, but in the event that
6 All |RCE| settings can be set from the user interface, but in the event that
7 it somehow becomes unavailable you can use ``ishell`` inside your |RCE|
7 it somehow becomes unavailable you can use ``ishell`` inside your |RCE|
8 ``virtualenv`` to carry out emergency measures.
8 ``virtualenv`` to carry out emergency measures.
9
9
10 .. warning::
10 .. warning::
11
11
12 Logging into the |RCE| database with ``iShell`` should only be done by an
12 Logging into the |RCE| database with ``iShell`` should only be done by an
13 experienced and knowledgeable database administrator.
13 experienced and knowledgeable database administrator.
14
14
15 Reset Admin Account Privileges
15 Reset Admin Account Privileges
16 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17
17
18 If you accidentally remove your admin privileges from the admin account you
18 If you accidentally remove your admin privileges from the admin account you
19 can restore them using ``ishell``. Use the following example to reset your
19 can restore them using ``ishell``. Use the following example to reset your
20 account permissions.
20 account permissions.
21
21
22 .. code-block:: bash
22 .. code-block:: bash
23
23
24 # Open iShell from the terminal
24 # Open iShell from the terminal
25 $ .rccontrol/enterprise-5/profile/bin/paster \
25 $ .rccontrol/enterprise-5/profile/bin/paster \
26 ishell .rccontrol/enterprise-5/rhodecode.ini
26 ishell .rccontrol/enterprise-5/rhodecode.ini
27
27
28 .. code-block:: mysql
28 .. code-block:: mysql
29
29
30 # Use this example to change user permissions
30 # Use this example to change user permissions
31 In [1]: adminuser = User.get_by_username('username')
31 In [1]: adminuser = User.get_by_username('username')
32 In [2]: adminuser.admin = True
32 In [2]: adminuser.admin = True
33 In [3]: Session.add(adminuser);Session().commit()
33 In [3]: Session().add(adminuser);Session().commit()
34 In [4]: exit()
34 In [4]: exit()
35
35
36 Set to read global ``.hgrc`` file
36 Set to read global ``.hgrc`` file
37 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
37 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
38
38
39 By default, |RCE| does not read global ``hgrc`` files in
39 By default, |RCE| does not read global ``hgrc`` files in
40 ``/etc/mercurial/hgrc`` or ``/etc/mercurial/hgrc.d`` because it
40 ``/etc/mercurial/hgrc`` or ``/etc/mercurial/hgrc.d`` because it
41 can lead to issues. This is set in the ``rhodecode_ui`` table for which
41 can lead to issues. This is set in the ``rhodecode_ui`` table for which
42 there is no UI. If you need to edit this you can
42 there is no UI. If you need to edit this you can
43 manually change the settings using SQL statements with ``ishell``. Use the
43 manually change the settings using SQL statements with ``ishell``. Use the
44 following example to make changes to this table.
44 following example to make changes to this table.
45
45
46 .. code-block:: bash
46 .. code-block:: bash
47
47
48 # Open iShell from the terminal
48 # Open iShell from the terminal
49 $ .rccontrol/enterprise-5/profile/bin/paster \
49 $ .rccontrol/enterprise-5/profile/bin/paster \
50 ishell.rccontrol/enterprise-5/rhodecode.ini
50 ishell.rccontrol/enterprise-5/rhodecode.ini
51
51
52 .. code-block:: mysql
52 .. code-block:: mysql
53
53
54 # Use this example to enable global .hgrc access
54 # Use this example to enable global .hgrc access
55 In [4]: new_option = RhodeCodeUi()
55 In [4]: new_option = RhodeCodeUi()
56 In [5]: new_option.ui_section='web'
56 In [5]: new_option.ui_section='web'
57 In [6]: new_option.ui_key='allow_push'
57 In [6]: new_option.ui_key='allow_push'
58 In [7]: new_option.ui_value='*'
58 In [7]: new_option.ui_value='*'
59 In [8]: Session().add(new_option);Session().commit()
59 In [8]: Session().add(new_option);Session().commit()
60
60
61 Manually Reset Password
61 Manually Reset Password
62 ^^^^^^^^^^^^^^^^^^^^^^^
62 ^^^^^^^^^^^^^^^^^^^^^^^
63
63
64 If you need to manually reset a user password, use the following steps.
64 If you need to manually reset a user password, use the following steps.
65
65
66 1. Navigate to your |RCE| install location.
66 1. Navigate to your |RCE| install location.
67 2. Run the interactive ``ishell`` prompt.
67 2. Run the interactive ``ishell`` prompt.
68 3. Set a new password.
68 3. Set a new password.
69
69
70 Use the following code example to carry out these steps.
70 Use the following code example to carry out these steps.
71
71
72 .. code-block:: bash
72 .. code-block:: bash
73
73
74 # starts the ishell interactive prompt
74 # starts the ishell interactive prompt
75 $ .rccontrol/enterprise-5/profile/bin/paster \
75 $ .rccontrol/enterprise-5/profile/bin/paster \
76 ishell .rccontrol/enterprise-5/rhodecode.ini
76 ishell .rccontrol/enterprise-5/rhodecode.ini
77
77
78 .. code-block:: mysql
78 .. code-block:: mysql
79
79
80 from rhodecode.lib.auth import generate_auth_token
80 from rhodecode.lib.auth import generate_auth_token
81 from rhodecode.lib.auth import get_crypt_password
81 from rhodecode.lib.auth import get_crypt_password
82
82
83 # Enter the user name whose password you wish to change
83 # Enter the user name whose password you wish to change
84 my_user = 'USERNAME'
84 my_user = 'USERNAME'
85 u = User.get_by_username(my_user)
85 u = User.get_by_username(my_user)
86
86
87 # If this fails then the user does not exist
87 # If this fails then the user does not exist
88 u.auth_token = generate_auth_token(my_user)
88 u.auth_token = generate_auth_token(my_user)
89
89
90 # Set the new password
90 # Set the new password
91 u.password = get_crypt_password('PASSWORD')
91 u.password = get_crypt_password('PASSWORD')
92
92
93 Session().add(u)
93 Session().add(u)
94 Session().commit()
94 Session().commit()
95 exit
95 exit
@@ -1,633 +1,633 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import jsonrpc_method, JSONRPCError
24 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 has_repo_permissions, resolve_ref_or_error)
28 has_repo_permissions, resolve_ref_or_error)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
29 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.base import vcs_operation_context
30 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.model.changeset_status import ChangesetStatusModel
32 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.comment import ChangesetCommentsModel
33 from rhodecode.model.comment import ChangesetCommentsModel
34 from rhodecode.model.db import Session, ChangesetStatus
34 from rhodecode.model.db import Session, ChangesetStatus
35 from rhodecode.model.pull_request import PullRequestModel
35 from rhodecode.model.pull_request import PullRequestModel
36 from rhodecode.model.settings import SettingsModel
36 from rhodecode.model.settings import SettingsModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 @jsonrpc_method()
41 @jsonrpc_method()
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
42 def get_pull_request(request, apiuser, repoid, pullrequestid):
43 """
43 """
44 Get a pull request based on the given ID.
44 Get a pull request based on the given ID.
45
45
46 :param apiuser: This is filled automatically from the |authtoken|.
46 :param apiuser: This is filled automatically from the |authtoken|.
47 :type apiuser: AuthUser
47 :type apiuser: AuthUser
48 :param repoid: Repository name or repository ID from where the pull
48 :param repoid: Repository name or repository ID from where the pull
49 request was opened.
49 request was opened.
50 :type repoid: str or int
50 :type repoid: str or int
51 :param pullrequestid: ID of the requested pull request.
51 :param pullrequestid: ID of the requested pull request.
52 :type pullrequestid: int
52 :type pullrequestid: int
53
53
54 Example output:
54 Example output:
55
55
56 .. code-block:: bash
56 .. code-block:: bash
57
57
58 "id": <id_given_in_input>,
58 "id": <id_given_in_input>,
59 "result":
59 "result":
60 {
60 {
61 "pull_request_id": "<pull_request_id>",
61 "pull_request_id": "<pull_request_id>",
62 "url": "<url>",
62 "url": "<url>",
63 "title": "<title>",
63 "title": "<title>",
64 "description": "<description>",
64 "description": "<description>",
65 "status" : "<status>",
65 "status" : "<status>",
66 "created_on": "<date_time_created>",
66 "created_on": "<date_time_created>",
67 "updated_on": "<date_time_updated>",
67 "updated_on": "<date_time_updated>",
68 "commit_ids": [
68 "commit_ids": [
69 ...
69 ...
70 "<commit_id>",
70 "<commit_id>",
71 "<commit_id>",
71 "<commit_id>",
72 ...
72 ...
73 ],
73 ],
74 "review_status": "<review_status>",
74 "review_status": "<review_status>",
75 "mergeable": {
75 "mergeable": {
76 "status": "<bool>",
76 "status": "<bool>",
77 "message": "<message>",
77 "message": "<message>",
78 },
78 },
79 "source": {
79 "source": {
80 "clone_url": "<clone_url>",
80 "clone_url": "<clone_url>",
81 "repository": "<repository_name>",
81 "repository": "<repository_name>",
82 "reference":
82 "reference":
83 {
83 {
84 "name": "<name>",
84 "name": "<name>",
85 "type": "<type>",
85 "type": "<type>",
86 "commit_id": "<commit_id>",
86 "commit_id": "<commit_id>",
87 }
87 }
88 },
88 },
89 "target": {
89 "target": {
90 "clone_url": "<clone_url>",
90 "clone_url": "<clone_url>",
91 "repository": "<repository_name>",
91 "repository": "<repository_name>",
92 "reference":
92 "reference":
93 {
93 {
94 "name": "<name>",
94 "name": "<name>",
95 "type": "<type>",
95 "type": "<type>",
96 "commit_id": "<commit_id>",
96 "commit_id": "<commit_id>",
97 }
97 }
98 },
98 },
99 "author": <user_obj>,
99 "author": <user_obj>,
100 "reviewers": [
100 "reviewers": [
101 ...
101 ...
102 {
102 {
103 "user": "<user_obj>",
103 "user": "<user_obj>",
104 "review_status": "<review_status>",
104 "review_status": "<review_status>",
105 }
105 }
106 ...
106 ...
107 ]
107 ]
108 },
108 },
109 "error": null
109 "error": null
110 """
110 """
111 get_repo_or_error(repoid)
111 get_repo_or_error(repoid)
112 pull_request = get_pull_request_or_error(pullrequestid)
112 pull_request = get_pull_request_or_error(pullrequestid)
113 if not PullRequestModel().check_user_read(
113 if not PullRequestModel().check_user_read(
114 pull_request, apiuser, api=True):
114 pull_request, apiuser, api=True):
115 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
115 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
116 data = pull_request.get_api_data()
116 data = pull_request.get_api_data()
117 return data
117 return data
118
118
119
119
120 @jsonrpc_method()
120 @jsonrpc_method()
121 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
121 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
122 """
122 """
123 Get all pull requests from the repository specified in `repoid`.
123 Get all pull requests from the repository specified in `repoid`.
124
124
125 :param apiuser: This is filled automatically from the |authtoken|.
125 :param apiuser: This is filled automatically from the |authtoken|.
126 :type apiuser: AuthUser
126 :type apiuser: AuthUser
127 :param repoid: Repository name or repository ID.
127 :param repoid: Repository name or repository ID.
128 :type repoid: str or int
128 :type repoid: str or int
129 :param status: Only return pull requests with the specified status.
129 :param status: Only return pull requests with the specified status.
130 Valid options are.
130 Valid options are.
131 * ``new`` (default)
131 * ``new`` (default)
132 * ``open``
132 * ``open``
133 * ``closed``
133 * ``closed``
134 :type status: str
134 :type status: str
135
135
136 Example output:
136 Example output:
137
137
138 .. code-block:: bash
138 .. code-block:: bash
139
139
140 "id": <id_given_in_input>,
140 "id": <id_given_in_input>,
141 "result":
141 "result":
142 [
142 [
143 ...
143 ...
144 {
144 {
145 "pull_request_id": "<pull_request_id>",
145 "pull_request_id": "<pull_request_id>",
146 "url": "<url>",
146 "url": "<url>",
147 "title" : "<title>",
147 "title" : "<title>",
148 "description": "<description>",
148 "description": "<description>",
149 "status": "<status>",
149 "status": "<status>",
150 "created_on": "<date_time_created>",
150 "created_on": "<date_time_created>",
151 "updated_on": "<date_time_updated>",
151 "updated_on": "<date_time_updated>",
152 "commit_ids": [
152 "commit_ids": [
153 ...
153 ...
154 "<commit_id>",
154 "<commit_id>",
155 "<commit_id>",
155 "<commit_id>",
156 ...
156 ...
157 ],
157 ],
158 "review_status": "<review_status>",
158 "review_status": "<review_status>",
159 "mergeable": {
159 "mergeable": {
160 "status": "<bool>",
160 "status": "<bool>",
161 "message: "<message>",
161 "message: "<message>",
162 },
162 },
163 "source": {
163 "source": {
164 "clone_url": "<clone_url>",
164 "clone_url": "<clone_url>",
165 "reference":
165 "reference":
166 {
166 {
167 "name": "<name>",
167 "name": "<name>",
168 "type": "<type>",
168 "type": "<type>",
169 "commit_id": "<commit_id>",
169 "commit_id": "<commit_id>",
170 }
170 }
171 },
171 },
172 "target": {
172 "target": {
173 "clone_url": "<clone_url>",
173 "clone_url": "<clone_url>",
174 "reference":
174 "reference":
175 {
175 {
176 "name": "<name>",
176 "name": "<name>",
177 "type": "<type>",
177 "type": "<type>",
178 "commit_id": "<commit_id>",
178 "commit_id": "<commit_id>",
179 }
179 }
180 },
180 },
181 "author": <user_obj>,
181 "author": <user_obj>,
182 "reviewers": [
182 "reviewers": [
183 ...
183 ...
184 {
184 {
185 "user": "<user_obj>",
185 "user": "<user_obj>",
186 "review_status": "<review_status>",
186 "review_status": "<review_status>",
187 }
187 }
188 ...
188 ...
189 ]
189 ]
190 }
190 }
191 ...
191 ...
192 ],
192 ],
193 "error": null
193 "error": null
194
194
195 """
195 """
196 repo = get_repo_or_error(repoid)
196 repo = get_repo_or_error(repoid)
197 if not has_superadmin_permission(apiuser):
197 if not has_superadmin_permission(apiuser):
198 _perms = (
198 _perms = (
199 'repository.admin', 'repository.write', 'repository.read',)
199 'repository.admin', 'repository.write', 'repository.read',)
200 has_repo_permissions(apiuser, repoid, repo, _perms)
200 has_repo_permissions(apiuser, repoid, repo, _perms)
201
201
202 status = Optional.extract(status)
202 status = Optional.extract(status)
203 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
203 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
204 data = [pr.get_api_data() for pr in pull_requests]
204 data = [pr.get_api_data() for pr in pull_requests]
205 return data
205 return data
206
206
207
207
208 @jsonrpc_method()
208 @jsonrpc_method()
209 def merge_pull_request(request, apiuser, repoid, pullrequestid,
209 def merge_pull_request(request, apiuser, repoid, pullrequestid,
210 userid=Optional(OAttr('apiuser'))):
210 userid=Optional(OAttr('apiuser'))):
211 """
211 """
212 Merge the pull request specified by `pullrequestid` into its target
212 Merge the pull request specified by `pullrequestid` into its target
213 repository.
213 repository.
214
214
215 :param apiuser: This is filled automatically from the |authtoken|.
215 :param apiuser: This is filled automatically from the |authtoken|.
216 :type apiuser: AuthUser
216 :type apiuser: AuthUser
217 :param repoid: The Repository name or repository ID of the
217 :param repoid: The Repository name or repository ID of the
218 target repository to which the |pr| is to be merged.
218 target repository to which the |pr| is to be merged.
219 :type repoid: str or int
219 :type repoid: str or int
220 :param pullrequestid: ID of the pull request which shall be merged.
220 :param pullrequestid: ID of the pull request which shall be merged.
221 :type pullrequestid: int
221 :type pullrequestid: int
222 :param userid: Merge the pull request as this user.
222 :param userid: Merge the pull request as this user.
223 :type userid: Optional(str or int)
223 :type userid: Optional(str or int)
224
224
225 Example output:
225 Example output:
226
226
227 .. code-block:: bash
227 .. code-block:: bash
228
228
229 "id": <id_given_in_input>,
229 "id": <id_given_in_input>,
230 "result":
230 "result":
231 {
231 {
232 "executed": "<bool>",
232 "executed": "<bool>",
233 "failure_reason": "<int>",
233 "failure_reason": "<int>",
234 "merge_commit_id": "<merge_commit_id>",
234 "merge_commit_id": "<merge_commit_id>",
235 "possible": "<bool>"
235 "possible": "<bool>"
236 },
236 },
237 "error": null
237 "error": null
238
238
239 """
239 """
240 repo = get_repo_or_error(repoid)
240 repo = get_repo_or_error(repoid)
241 if not isinstance(userid, Optional):
241 if not isinstance(userid, Optional):
242 if (has_superadmin_permission(apiuser) or
242 if (has_superadmin_permission(apiuser) or
243 HasRepoPermissionAnyApi('repository.admin')(
243 HasRepoPermissionAnyApi('repository.admin')(
244 user=apiuser, repo_name=repo.repo_name)):
244 user=apiuser, repo_name=repo.repo_name)):
245 apiuser = get_user_or_error(userid)
245 apiuser = get_user_or_error(userid)
246 else:
246 else:
247 raise JSONRPCError('userid is not the same as your user')
247 raise JSONRPCError('userid is not the same as your user')
248
248
249 pull_request = get_pull_request_or_error(pullrequestid)
249 pull_request = get_pull_request_or_error(pullrequestid)
250 if not PullRequestModel().check_user_merge(
250 if not PullRequestModel().check_user_merge(
251 pull_request, apiuser, api=True):
251 pull_request, apiuser, api=True):
252 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
252 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
253 if pull_request.is_closed():
253 if pull_request.is_closed():
254 raise JSONRPCError(
254 raise JSONRPCError(
255 'pull request `%s` merge failed, pull request is closed' % (
255 'pull request `%s` merge failed, pull request is closed' % (
256 pullrequestid,))
256 pullrequestid,))
257
257
258 target_repo = pull_request.target_repo
258 target_repo = pull_request.target_repo
259 extras = vcs_operation_context(
259 extras = vcs_operation_context(
260 request.environ, repo_name=target_repo.repo_name,
260 request.environ, repo_name=target_repo.repo_name,
261 username=apiuser.username, action='push',
261 username=apiuser.username, action='push',
262 scm=target_repo.repo_type)
262 scm=target_repo.repo_type)
263 data = PullRequestModel().merge(pull_request, apiuser, extras=extras)
263 data = PullRequestModel().merge(pull_request, apiuser, extras=extras)
264 if data.executed:
264 if data.executed:
265 PullRequestModel().close_pull_request(
265 PullRequestModel().close_pull_request(
266 pull_request.pull_request_id, apiuser)
266 pull_request.pull_request_id, apiuser)
267
267
268 Session.commit()
268 Session().commit()
269 return data
269 return data
270
270
271
271
272 @jsonrpc_method()
272 @jsonrpc_method()
273 def close_pull_request(request, apiuser, repoid, pullrequestid,
273 def close_pull_request(request, apiuser, repoid, pullrequestid,
274 userid=Optional(OAttr('apiuser'))):
274 userid=Optional(OAttr('apiuser'))):
275 """
275 """
276 Close the pull request specified by `pullrequestid`.
276 Close the pull request specified by `pullrequestid`.
277
277
278 :param apiuser: This is filled automatically from the |authtoken|.
278 :param apiuser: This is filled automatically from the |authtoken|.
279 :type apiuser: AuthUser
279 :type apiuser: AuthUser
280 :param repoid: Repository name or repository ID to which the pull
280 :param repoid: Repository name or repository ID to which the pull
281 request belongs.
281 request belongs.
282 :type repoid: str or int
282 :type repoid: str or int
283 :param pullrequestid: ID of the pull request to be closed.
283 :param pullrequestid: ID of the pull request to be closed.
284 :type pullrequestid: int
284 :type pullrequestid: int
285 :param userid: Close the pull request as this user.
285 :param userid: Close the pull request as this user.
286 :type userid: Optional(str or int)
286 :type userid: Optional(str or int)
287
287
288 Example output:
288 Example output:
289
289
290 .. code-block:: bash
290 .. code-block:: bash
291
291
292 "id": <id_given_in_input>,
292 "id": <id_given_in_input>,
293 "result":
293 "result":
294 {
294 {
295 "pull_request_id": "<int>",
295 "pull_request_id": "<int>",
296 "closed": "<bool>"
296 "closed": "<bool>"
297 },
297 },
298 "error": null
298 "error": null
299
299
300 """
300 """
301 repo = get_repo_or_error(repoid)
301 repo = get_repo_or_error(repoid)
302 if not isinstance(userid, Optional):
302 if not isinstance(userid, Optional):
303 if (has_superadmin_permission(apiuser) or
303 if (has_superadmin_permission(apiuser) or
304 HasRepoPermissionAnyApi('repository.admin')(
304 HasRepoPermissionAnyApi('repository.admin')(
305 user=apiuser, repo_name=repo.repo_name)):
305 user=apiuser, repo_name=repo.repo_name)):
306 apiuser = get_user_or_error(userid)
306 apiuser = get_user_or_error(userid)
307 else:
307 else:
308 raise JSONRPCError('userid is not the same as your user')
308 raise JSONRPCError('userid is not the same as your user')
309
309
310 pull_request = get_pull_request_or_error(pullrequestid)
310 pull_request = get_pull_request_or_error(pullrequestid)
311 if not PullRequestModel().check_user_update(
311 if not PullRequestModel().check_user_update(
312 pull_request, apiuser, api=True):
312 pull_request, apiuser, api=True):
313 raise JSONRPCError(
313 raise JSONRPCError(
314 'pull request `%s` close failed, no permission to close.' % (
314 'pull request `%s` close failed, no permission to close.' % (
315 pullrequestid,))
315 pullrequestid,))
316 if pull_request.is_closed():
316 if pull_request.is_closed():
317 raise JSONRPCError(
317 raise JSONRPCError(
318 'pull request `%s` is already closed' % (pullrequestid,))
318 'pull request `%s` is already closed' % (pullrequestid,))
319
319
320 PullRequestModel().close_pull_request(
320 PullRequestModel().close_pull_request(
321 pull_request.pull_request_id, apiuser)
321 pull_request.pull_request_id, apiuser)
322 Session.commit()
322 Session().commit()
323 data = {
323 data = {
324 'pull_request_id': pull_request.pull_request_id,
324 'pull_request_id': pull_request.pull_request_id,
325 'closed': True,
325 'closed': True,
326 }
326 }
327 return data
327 return data
328
328
329
329
330 @jsonrpc_method()
330 @jsonrpc_method()
331 def comment_pull_request(request, apiuser, repoid, pullrequestid,
331 def comment_pull_request(request, apiuser, repoid, pullrequestid,
332 message=Optional(None), status=Optional(None),
332 message=Optional(None), status=Optional(None),
333 userid=Optional(OAttr('apiuser'))):
333 userid=Optional(OAttr('apiuser'))):
334 """
334 """
335 Comment on the pull request specified with the `pullrequestid`,
335 Comment on the pull request specified with the `pullrequestid`,
336 in the |repo| specified by the `repoid`, and optionally change the
336 in the |repo| specified by the `repoid`, and optionally change the
337 review status.
337 review status.
338
338
339 :param apiuser: This is filled automatically from the |authtoken|.
339 :param apiuser: This is filled automatically from the |authtoken|.
340 :type apiuser: AuthUser
340 :type apiuser: AuthUser
341 :param repoid: The repository name or repository ID.
341 :param repoid: The repository name or repository ID.
342 :type repoid: str or int
342 :type repoid: str or int
343 :param pullrequestid: The pull request ID.
343 :param pullrequestid: The pull request ID.
344 :type pullrequestid: int
344 :type pullrequestid: int
345 :param message: The text content of the comment.
345 :param message: The text content of the comment.
346 :type message: str
346 :type message: str
347 :param status: (**Optional**) Set the approval status of the pull
347 :param status: (**Optional**) Set the approval status of the pull
348 request. Valid options are:
348 request. Valid options are:
349 * not_reviewed
349 * not_reviewed
350 * approved
350 * approved
351 * rejected
351 * rejected
352 * under_review
352 * under_review
353 :type status: str
353 :type status: str
354 :param userid: Comment on the pull request as this user
354 :param userid: Comment on the pull request as this user
355 :type userid: Optional(str or int)
355 :type userid: Optional(str or int)
356
356
357 Example output:
357 Example output:
358
358
359 .. code-block:: bash
359 .. code-block:: bash
360
360
361 id : <id_given_in_input>
361 id : <id_given_in_input>
362 result :
362 result :
363 {
363 {
364 "pull_request_id": "<Integer>",
364 "pull_request_id": "<Integer>",
365 "comment_id": "<Integer>"
365 "comment_id": "<Integer>"
366 }
366 }
367 error : null
367 error : null
368 """
368 """
369 repo = get_repo_or_error(repoid)
369 repo = get_repo_or_error(repoid)
370 if not isinstance(userid, Optional):
370 if not isinstance(userid, Optional):
371 if (has_superadmin_permission(apiuser) or
371 if (has_superadmin_permission(apiuser) or
372 HasRepoPermissionAnyApi('repository.admin')(
372 HasRepoPermissionAnyApi('repository.admin')(
373 user=apiuser, repo_name=repo.repo_name)):
373 user=apiuser, repo_name=repo.repo_name)):
374 apiuser = get_user_or_error(userid)
374 apiuser = get_user_or_error(userid)
375 else:
375 else:
376 raise JSONRPCError('userid is not the same as your user')
376 raise JSONRPCError('userid is not the same as your user')
377
377
378 pull_request = get_pull_request_or_error(pullrequestid)
378 pull_request = get_pull_request_or_error(pullrequestid)
379 if not PullRequestModel().check_user_read(
379 if not PullRequestModel().check_user_read(
380 pull_request, apiuser, api=True):
380 pull_request, apiuser, api=True):
381 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
381 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
382 message = Optional.extract(message)
382 message = Optional.extract(message)
383 status = Optional.extract(status)
383 status = Optional.extract(status)
384 if not message and not status:
384 if not message and not status:
385 raise JSONRPCError('message and status parameter missing')
385 raise JSONRPCError('message and status parameter missing')
386
386
387 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
387 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
388 status is not None):
388 status is not None):
389 raise JSONRPCError('unknown comment status`%s`' % status)
389 raise JSONRPCError('unknown comment status`%s`' % status)
390
390
391 allowed_to_change_status = PullRequestModel().check_user_change_status(
391 allowed_to_change_status = PullRequestModel().check_user_change_status(
392 pull_request, apiuser)
392 pull_request, apiuser)
393 text = message
393 text = message
394 if status and allowed_to_change_status:
394 if status and allowed_to_change_status:
395 st_message = (('Status change %(transition_icon)s %(status)s')
395 st_message = (('Status change %(transition_icon)s %(status)s')
396 % {'transition_icon': '>',
396 % {'transition_icon': '>',
397 'status': ChangesetStatus.get_status_lbl(status)})
397 'status': ChangesetStatus.get_status_lbl(status)})
398 text = message or st_message
398 text = message or st_message
399
399
400 rc_config = SettingsModel().get_all_settings()
400 rc_config = SettingsModel().get_all_settings()
401 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
401 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
402 comment = ChangesetCommentsModel().create(
402 comment = ChangesetCommentsModel().create(
403 text=text,
403 text=text,
404 repo=pull_request.target_repo.repo_id,
404 repo=pull_request.target_repo.repo_id,
405 user=apiuser.user_id,
405 user=apiuser.user_id,
406 pull_request=pull_request.pull_request_id,
406 pull_request=pull_request.pull_request_id,
407 f_path=None,
407 f_path=None,
408 line_no=None,
408 line_no=None,
409 status_change=(ChangesetStatus.get_status_lbl(status)
409 status_change=(ChangesetStatus.get_status_lbl(status)
410 if status and allowed_to_change_status else None),
410 if status and allowed_to_change_status else None),
411 closing_pr=False,
411 closing_pr=False,
412 renderer=renderer
412 renderer=renderer
413 )
413 )
414
414
415 if allowed_to_change_status and status:
415 if allowed_to_change_status and status:
416 ChangesetStatusModel().set_status(
416 ChangesetStatusModel().set_status(
417 pull_request.target_repo.repo_id,
417 pull_request.target_repo.repo_id,
418 status,
418 status,
419 apiuser.user_id,
419 apiuser.user_id,
420 comment,
420 comment,
421 pull_request=pull_request.pull_request_id
421 pull_request=pull_request.pull_request_id
422 )
422 )
423 Session().flush()
423 Session().flush()
424
424
425 Session().commit()
425 Session().commit()
426 data = {
426 data = {
427 'pull_request_id': pull_request.pull_request_id,
427 'pull_request_id': pull_request.pull_request_id,
428 'comment_id': comment.comment_id,
428 'comment_id': comment.comment_id,
429 'status': status
429 'status': status
430 }
430 }
431 return data
431 return data
432
432
433
433
434 @jsonrpc_method()
434 @jsonrpc_method()
435 def create_pull_request(
435 def create_pull_request(
436 request, apiuser, source_repo, target_repo, source_ref, target_ref,
436 request, apiuser, source_repo, target_repo, source_ref, target_ref,
437 title, description=Optional(''), reviewers=Optional(None)):
437 title, description=Optional(''), reviewers=Optional(None)):
438 """
438 """
439 Creates a new pull request.
439 Creates a new pull request.
440
440
441 Accepts refs in the following formats:
441 Accepts refs in the following formats:
442
442
443 * branch:<branch_name>:<sha>
443 * branch:<branch_name>:<sha>
444 * branch:<branch_name>
444 * branch:<branch_name>
445 * bookmark:<bookmark_name>:<sha> (Mercurial only)
445 * bookmark:<bookmark_name>:<sha> (Mercurial only)
446 * bookmark:<bookmark_name> (Mercurial only)
446 * bookmark:<bookmark_name> (Mercurial only)
447
447
448 :param apiuser: This is filled automatically from the |authtoken|.
448 :param apiuser: This is filled automatically from the |authtoken|.
449 :type apiuser: AuthUser
449 :type apiuser: AuthUser
450 :param source_repo: Set the source repository name.
450 :param source_repo: Set the source repository name.
451 :type source_repo: str
451 :type source_repo: str
452 :param target_repo: Set the target repository name.
452 :param target_repo: Set the target repository name.
453 :type target_repo: str
453 :type target_repo: str
454 :param source_ref: Set the source ref name.
454 :param source_ref: Set the source ref name.
455 :type source_ref: str
455 :type source_ref: str
456 :param target_ref: Set the target ref name.
456 :param target_ref: Set the target ref name.
457 :type target_ref: str
457 :type target_ref: str
458 :param title: Set the pull request title.
458 :param title: Set the pull request title.
459 :type title: str
459 :type title: str
460 :param description: Set the pull request description.
460 :param description: Set the pull request description.
461 :type description: Optional(str)
461 :type description: Optional(str)
462 :param reviewers: Set the new pull request reviewers list.
462 :param reviewers: Set the new pull request reviewers list.
463 :type reviewers: Optional(list)
463 :type reviewers: Optional(list)
464 """
464 """
465 source = get_repo_or_error(source_repo)
465 source = get_repo_or_error(source_repo)
466 target = get_repo_or_error(target_repo)
466 target = get_repo_or_error(target_repo)
467 if not has_superadmin_permission(apiuser):
467 if not has_superadmin_permission(apiuser):
468 _perms = ('repository.admin', 'repository.write', 'repository.read',)
468 _perms = ('repository.admin', 'repository.write', 'repository.read',)
469 has_repo_permissions(apiuser, source_repo, source, _perms)
469 has_repo_permissions(apiuser, source_repo, source, _perms)
470
470
471 full_source_ref = resolve_ref_or_error(source_ref, source)
471 full_source_ref = resolve_ref_or_error(source_ref, source)
472 full_target_ref = resolve_ref_or_error(target_ref, target)
472 full_target_ref = resolve_ref_or_error(target_ref, target)
473 source_commit = get_commit_or_error(full_source_ref, source)
473 source_commit = get_commit_or_error(full_source_ref, source)
474 target_commit = get_commit_or_error(full_target_ref, target)
474 target_commit = get_commit_or_error(full_target_ref, target)
475 source_scm = source.scm_instance()
475 source_scm = source.scm_instance()
476 target_scm = target.scm_instance()
476 target_scm = target.scm_instance()
477
477
478 commit_ranges = target_scm.compare(
478 commit_ranges = target_scm.compare(
479 target_commit.raw_id, source_commit.raw_id, source_scm,
479 target_commit.raw_id, source_commit.raw_id, source_scm,
480 merge=True, pre_load=[])
480 merge=True, pre_load=[])
481
481
482 ancestor = target_scm.get_common_ancestor(
482 ancestor = target_scm.get_common_ancestor(
483 target_commit.raw_id, source_commit.raw_id, source_scm)
483 target_commit.raw_id, source_commit.raw_id, source_scm)
484
484
485 if not commit_ranges:
485 if not commit_ranges:
486 raise JSONRPCError('no commits found')
486 raise JSONRPCError('no commits found')
487
487
488 if not ancestor:
488 if not ancestor:
489 raise JSONRPCError('no common ancestor found')
489 raise JSONRPCError('no common ancestor found')
490
490
491 reviewer_names = Optional.extract(reviewers) or []
491 reviewer_names = Optional.extract(reviewers) or []
492 if not isinstance(reviewer_names, list):
492 if not isinstance(reviewer_names, list):
493 raise JSONRPCError('reviewers should be specified as a list')
493 raise JSONRPCError('reviewers should be specified as a list')
494
494
495 reviewer_users = [get_user_or_error(n) for n in reviewer_names]
495 reviewer_users = [get_user_or_error(n) for n in reviewer_names]
496 reviewer_ids = [u.user_id for u in reviewer_users]
496 reviewer_ids = [u.user_id for u in reviewer_users]
497
497
498 pull_request_model = PullRequestModel()
498 pull_request_model = PullRequestModel()
499 pull_request = pull_request_model.create(
499 pull_request = pull_request_model.create(
500 created_by=apiuser.user_id,
500 created_by=apiuser.user_id,
501 source_repo=source_repo,
501 source_repo=source_repo,
502 source_ref=full_source_ref,
502 source_ref=full_source_ref,
503 target_repo=target_repo,
503 target_repo=target_repo,
504 target_ref=full_target_ref,
504 target_ref=full_target_ref,
505 revisions=reversed(
505 revisions=reversed(
506 [commit.raw_id for commit in reversed(commit_ranges)]),
506 [commit.raw_id for commit in reversed(commit_ranges)]),
507 reviewers=reviewer_ids,
507 reviewers=reviewer_ids,
508 title=title,
508 title=title,
509 description=Optional.extract(description)
509 description=Optional.extract(description)
510 )
510 )
511
511
512 Session().commit()
512 Session().commit()
513 data = {
513 data = {
514 'msg': 'Created new pull request `{}`'.format(title),
514 'msg': 'Created new pull request `{}`'.format(title),
515 'pull_request_id': pull_request.pull_request_id,
515 'pull_request_id': pull_request.pull_request_id,
516 }
516 }
517 return data
517 return data
518
518
519
519
520 @jsonrpc_method()
520 @jsonrpc_method()
521 def update_pull_request(
521 def update_pull_request(
522 request, apiuser, repoid, pullrequestid, title=Optional(''),
522 request, apiuser, repoid, pullrequestid, title=Optional(''),
523 description=Optional(''), reviewers=Optional(None),
523 description=Optional(''), reviewers=Optional(None),
524 update_commits=Optional(None), close_pull_request=Optional(None)):
524 update_commits=Optional(None), close_pull_request=Optional(None)):
525 """
525 """
526 Updates a pull request.
526 Updates a pull request.
527
527
528 :param apiuser: This is filled automatically from the |authtoken|.
528 :param apiuser: This is filled automatically from the |authtoken|.
529 :type apiuser: AuthUser
529 :type apiuser: AuthUser
530 :param repoid: The repository name or repository ID.
530 :param repoid: The repository name or repository ID.
531 :type repoid: str or int
531 :type repoid: str or int
532 :param pullrequestid: The pull request ID.
532 :param pullrequestid: The pull request ID.
533 :type pullrequestid: int
533 :type pullrequestid: int
534 :param title: Set the pull request title.
534 :param title: Set the pull request title.
535 :type title: str
535 :type title: str
536 :param description: Update pull request description.
536 :param description: Update pull request description.
537 :type description: Optional(str)
537 :type description: Optional(str)
538 :param reviewers: Update pull request reviewers list with new value.
538 :param reviewers: Update pull request reviewers list with new value.
539 :type reviewers: Optional(list)
539 :type reviewers: Optional(list)
540 :param update_commits: Trigger update of commits for this pull request
540 :param update_commits: Trigger update of commits for this pull request
541 :type: update_commits: Optional(bool)
541 :type: update_commits: Optional(bool)
542 :param close_pull_request: Close this pull request with rejected state
542 :param close_pull_request: Close this pull request with rejected state
543 :type: close_pull_request: Optional(bool)
543 :type: close_pull_request: Optional(bool)
544
544
545 Example output:
545 Example output:
546
546
547 .. code-block:: bash
547 .. code-block:: bash
548
548
549 id : <id_given_in_input>
549 id : <id_given_in_input>
550 result :
550 result :
551 {
551 {
552 "msg": "Updated pull request `63`",
552 "msg": "Updated pull request `63`",
553 "pull_request": <pull_request_object>,
553 "pull_request": <pull_request_object>,
554 "updated_reviewers": {
554 "updated_reviewers": {
555 "added": [
555 "added": [
556 "username"
556 "username"
557 ],
557 ],
558 "removed": []
558 "removed": []
559 },
559 },
560 "updated_commits": {
560 "updated_commits": {
561 "added": [
561 "added": [
562 "<sha1_hash>"
562 "<sha1_hash>"
563 ],
563 ],
564 "common": [
564 "common": [
565 "<sha1_hash>",
565 "<sha1_hash>",
566 "<sha1_hash>",
566 "<sha1_hash>",
567 ],
567 ],
568 "removed": []
568 "removed": []
569 }
569 }
570 }
570 }
571 error : null
571 error : null
572 """
572 """
573
573
574 repo = get_repo_or_error(repoid)
574 repo = get_repo_or_error(repoid)
575 pull_request = get_pull_request_or_error(pullrequestid)
575 pull_request = get_pull_request_or_error(pullrequestid)
576 if not PullRequestModel().check_user_update(
576 if not PullRequestModel().check_user_update(
577 pull_request, apiuser, api=True):
577 pull_request, apiuser, api=True):
578 raise JSONRPCError(
578 raise JSONRPCError(
579 'pull request `%s` update failed, no permission to update.' % (
579 'pull request `%s` update failed, no permission to update.' % (
580 pullrequestid,))
580 pullrequestid,))
581 if pull_request.is_closed():
581 if pull_request.is_closed():
582 raise JSONRPCError(
582 raise JSONRPCError(
583 'pull request `%s` update failed, pull request is closed' % (
583 'pull request `%s` update failed, pull request is closed' % (
584 pullrequestid,))
584 pullrequestid,))
585
585
586 reviewer_names = Optional.extract(reviewers) or []
586 reviewer_names = Optional.extract(reviewers) or []
587 if not isinstance(reviewer_names, list):
587 if not isinstance(reviewer_names, list):
588 raise JSONRPCError('reviewers should be specified as a list')
588 raise JSONRPCError('reviewers should be specified as a list')
589
589
590 reviewer_users = [get_user_or_error(n) for n in reviewer_names]
590 reviewer_users = [get_user_or_error(n) for n in reviewer_names]
591 reviewer_ids = [u.user_id for u in reviewer_users]
591 reviewer_ids = [u.user_id for u in reviewer_users]
592
592
593 title = Optional.extract(title)
593 title = Optional.extract(title)
594 description = Optional.extract(description)
594 description = Optional.extract(description)
595 if title or description:
595 if title or description:
596 PullRequestModel().edit(
596 PullRequestModel().edit(
597 pull_request, title or pull_request.title,
597 pull_request, title or pull_request.title,
598 description or pull_request.description)
598 description or pull_request.description)
599 Session().commit()
599 Session().commit()
600
600
601 commit_changes = {"added": [], "common": [], "removed": []}
601 commit_changes = {"added": [], "common": [], "removed": []}
602 if str2bool(Optional.extract(update_commits)):
602 if str2bool(Optional.extract(update_commits)):
603 if PullRequestModel().has_valid_update_type(pull_request):
603 if PullRequestModel().has_valid_update_type(pull_request):
604 _version, _commit_changes = PullRequestModel().update_commits(
604 _version, _commit_changes = PullRequestModel().update_commits(
605 pull_request)
605 pull_request)
606 commit_changes = _commit_changes or commit_changes
606 commit_changes = _commit_changes or commit_changes
607 Session().commit()
607 Session().commit()
608
608
609 reviewers_changes = {"added": [], "removed": []}
609 reviewers_changes = {"added": [], "removed": []}
610 if reviewer_ids:
610 if reviewer_ids:
611 added_reviewers, removed_reviewers = \
611 added_reviewers, removed_reviewers = \
612 PullRequestModel().update_reviewers(pull_request, reviewer_ids)
612 PullRequestModel().update_reviewers(pull_request, reviewer_ids)
613
613
614 reviewers_changes['added'] = sorted(
614 reviewers_changes['added'] = sorted(
615 [get_user_or_error(n).username for n in added_reviewers])
615 [get_user_or_error(n).username for n in added_reviewers])
616 reviewers_changes['removed'] = sorted(
616 reviewers_changes['removed'] = sorted(
617 [get_user_or_error(n).username for n in removed_reviewers])
617 [get_user_or_error(n).username for n in removed_reviewers])
618 Session().commit()
618 Session().commit()
619
619
620 if str2bool(Optional.extract(close_pull_request)):
620 if str2bool(Optional.extract(close_pull_request)):
621 PullRequestModel().close_pull_request_with_comment(
621 PullRequestModel().close_pull_request_with_comment(
622 pull_request, apiuser, repo)
622 pull_request, apiuser, repo)
623 Session().commit()
623 Session().commit()
624
624
625 data = {
625 data = {
626 'msg': 'Updated pull request `{}`'.format(
626 'msg': 'Updated pull request `{}`'.format(
627 pull_request.pull_request_id),
627 pull_request.pull_request_id),
628 'pull_request': pull_request.get_api_data(),
628 'pull_request': pull_request.get_api_data(),
629 'updated_commits': commit_changes,
629 'updated_commits': commit_changes,
630 'updated_reviewers': reviewers_changes
630 'updated_reviewers': reviewers_changes
631 }
631 }
632 return data
632 return data
633
633
@@ -1,192 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 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 colander
21 import colander
22 import formencode.htmlfill
22 import formencode.htmlfill
23 import logging
23 import logging
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.authentication.base import (
29 from rhodecode.authentication.base import (
30 get_auth_cache_manager, get_authn_registry)
30 get_auth_cache_manager, get_authn_registry)
31 from rhodecode.lib import auth
31 from rhodecode.lib import auth
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 from rhodecode.model.forms import AuthSettingsForm
33 from rhodecode.model.forms import AuthSettingsForm
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35 from rhodecode.model.settings import SettingsModel
35 from rhodecode.model.settings import SettingsModel
36 from rhodecode.translation import _
36 from rhodecode.translation import _
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class AuthnPluginViewBase(object):
41 class AuthnPluginViewBase(object):
42
42
43 def __init__(self, context, request):
43 def __init__(self, context, request):
44 self.request = request
44 self.request = request
45 self.context = context
45 self.context = context
46 self.plugin = context.plugin
46 self.plugin = context.plugin
47 self._rhodecode_user = request.user
47 self._rhodecode_user = request.user
48
48
49 @LoginRequired()
49 @LoginRequired()
50 @HasPermissionAllDecorator('hg.admin')
50 @HasPermissionAllDecorator('hg.admin')
51 def settings_get(self, defaults=None, errors=None):
51 def settings_get(self, defaults=None, errors=None):
52 """
52 """
53 View that displays the plugin settings as a form.
53 View that displays the plugin settings as a form.
54 """
54 """
55 defaults = defaults or {}
55 defaults = defaults or {}
56 errors = errors or {}
56 errors = errors or {}
57 schema = self.plugin.get_settings_schema()
57 schema = self.plugin.get_settings_schema()
58
58
59 # Compute default values for the form. Priority is:
59 # Compute default values for the form. Priority is:
60 # 1. Passed to this method 2. DB value 3. Schema default
60 # 1. Passed to this method 2. DB value 3. Schema default
61 for node in schema:
61 for node in schema:
62 if node.name not in defaults:
62 if node.name not in defaults:
63 defaults[node.name] = self.plugin.get_setting_by_name(
63 defaults[node.name] = self.plugin.get_setting_by_name(
64 node.name, node.default)
64 node.name, node.default)
65
65
66 template_context = {
66 template_context = {
67 'defaults': defaults,
67 'defaults': defaults,
68 'errors': errors,
68 'errors': errors,
69 'plugin': self.context.plugin,
69 'plugin': self.context.plugin,
70 'resource': self.context,
70 'resource': self.context,
71 }
71 }
72
72
73 return template_context
73 return template_context
74
74
75 @LoginRequired()
75 @LoginRequired()
76 @HasPermissionAllDecorator('hg.admin')
76 @HasPermissionAllDecorator('hg.admin')
77 @auth.CSRFRequired()
77 @auth.CSRFRequired()
78 def settings_post(self):
78 def settings_post(self):
79 """
79 """
80 View that validates and stores the plugin settings.
80 View that validates and stores the plugin settings.
81 """
81 """
82 schema = self.plugin.get_settings_schema()
82 schema = self.plugin.get_settings_schema()
83 data = self.request.params
83 data = self.request.params
84
84
85 try:
85 try:
86 valid_data = schema.deserialize(data)
86 valid_data = schema.deserialize(data)
87 except colander.Invalid, e:
87 except colander.Invalid, e:
88 # Display error message and display form again.
88 # Display error message and display form again.
89 self.request.session.flash(
89 self.request.session.flash(
90 _('Errors exist when saving plugin settings. '
90 _('Errors exist when saving plugin settings. '
91 'Please check the form inputs.'),
91 'Please check the form inputs.'),
92 queue='error')
92 queue='error')
93 defaults = {key: data[key] for key in data if key in schema}
93 defaults = {key: data[key] for key in data if key in schema}
94 return self.settings_get(errors=e.asdict(), defaults=defaults)
94 return self.settings_get(errors=e.asdict(), defaults=defaults)
95
95
96 # Store validated data.
96 # Store validated data.
97 for name, value in valid_data.items():
97 for name, value in valid_data.items():
98 self.plugin.create_or_update_setting(name, value)
98 self.plugin.create_or_update_setting(name, value)
99 Session.commit()
99 Session().commit()
100
100
101 # Display success message and redirect.
101 # Display success message and redirect.
102 self.request.session.flash(
102 self.request.session.flash(
103 _('Auth settings updated successfully.'),
103 _('Auth settings updated successfully.'),
104 queue='success')
104 queue='success')
105 redirect_to = self.request.resource_path(
105 redirect_to = self.request.resource_path(
106 self.context, route_name='auth_home')
106 self.context, route_name='auth_home')
107 return HTTPFound(redirect_to)
107 return HTTPFound(redirect_to)
108
108
109
109
110 # TODO: Ongoing migration in these views.
110 # TODO: Ongoing migration in these views.
111 # - Maybe we should also use a colander schema for these views.
111 # - Maybe we should also use a colander schema for these views.
112 class AuthSettingsView(object):
112 class AuthSettingsView(object):
113 def __init__(self, context, request):
113 def __init__(self, context, request):
114 self.context = context
114 self.context = context
115 self.request = request
115 self.request = request
116
116
117 # TODO: Move this into a utility function. It is needed in all view
117 # TODO: Move this into a utility function. It is needed in all view
118 # classes during migration. Maybe a mixin?
118 # classes during migration. Maybe a mixin?
119
119
120 # Some of the decorators rely on this attribute to be present on the
120 # Some of the decorators rely on this attribute to be present on the
121 # class of the decorated method.
121 # class of the decorated method.
122 self._rhodecode_user = request.user
122 self._rhodecode_user = request.user
123
123
124 @LoginRequired()
124 @LoginRequired()
125 @HasPermissionAllDecorator('hg.admin')
125 @HasPermissionAllDecorator('hg.admin')
126 def index(self, defaults=None, errors=None, prefix_error=False):
126 def index(self, defaults=None, errors=None, prefix_error=False):
127 defaults = defaults or {}
127 defaults = defaults or {}
128 authn_registry = get_authn_registry(self.request.registry)
128 authn_registry = get_authn_registry(self.request.registry)
129 enabled_plugins = SettingsModel().get_auth_plugins()
129 enabled_plugins = SettingsModel().get_auth_plugins()
130
130
131 # Create template context and render it.
131 # Create template context and render it.
132 template_context = {
132 template_context = {
133 'resource': self.context,
133 'resource': self.context,
134 'available_plugins': authn_registry.get_plugins(),
134 'available_plugins': authn_registry.get_plugins(),
135 'enabled_plugins': enabled_plugins,
135 'enabled_plugins': enabled_plugins,
136 }
136 }
137 html = render('rhodecode:templates/admin/auth/auth_settings.html',
137 html = render('rhodecode:templates/admin/auth/auth_settings.html',
138 template_context,
138 template_context,
139 request=self.request)
139 request=self.request)
140
140
141 # Create form default values and fill the form.
141 # Create form default values and fill the form.
142 form_defaults = {
142 form_defaults = {
143 'auth_plugins': ','.join(enabled_plugins)
143 'auth_plugins': ','.join(enabled_plugins)
144 }
144 }
145 form_defaults.update(defaults)
145 form_defaults.update(defaults)
146 html = formencode.htmlfill.render(
146 html = formencode.htmlfill.render(
147 html,
147 html,
148 defaults=form_defaults,
148 defaults=form_defaults,
149 errors=errors,
149 errors=errors,
150 prefix_error=prefix_error,
150 prefix_error=prefix_error,
151 encoding="UTF-8",
151 encoding="UTF-8",
152 force_defaults=False)
152 force_defaults=False)
153
153
154 return Response(html)
154 return Response(html)
155
155
156 @LoginRequired()
156 @LoginRequired()
157 @HasPermissionAllDecorator('hg.admin')
157 @HasPermissionAllDecorator('hg.admin')
158 @auth.CSRFRequired()
158 @auth.CSRFRequired()
159 def auth_settings(self):
159 def auth_settings(self):
160 try:
160 try:
161 form = AuthSettingsForm()()
161 form = AuthSettingsForm()()
162 form_result = form.to_python(self.request.params)
162 form_result = form.to_python(self.request.params)
163 plugins = ','.join(form_result['auth_plugins'])
163 plugins = ','.join(form_result['auth_plugins'])
164 setting = SettingsModel().create_or_update_setting(
164 setting = SettingsModel().create_or_update_setting(
165 'auth_plugins', plugins)
165 'auth_plugins', plugins)
166 Session().add(setting)
166 Session().add(setting)
167 Session().commit()
167 Session().commit()
168
168
169 cache_manager = get_auth_cache_manager()
169 cache_manager = get_auth_cache_manager()
170 cache_manager.clear()
170 cache_manager.clear()
171 self.request.session.flash(
171 self.request.session.flash(
172 _('Auth settings updated successfully.'),
172 _('Auth settings updated successfully.'),
173 queue='success')
173 queue='success')
174 except formencode.Invalid as errors:
174 except formencode.Invalid as errors:
175 e = errors.error_dict or {}
175 e = errors.error_dict or {}
176 self.request.session.flash(
176 self.request.session.flash(
177 _('Errors exist when saving plugin setting. '
177 _('Errors exist when saving plugin setting. '
178 'Please check the form inputs.'),
178 'Please check the form inputs.'),
179 queue='error')
179 queue='error')
180 return self.index(
180 return self.index(
181 defaults=errors.value,
181 defaults=errors.value,
182 errors=e,
182 errors=e,
183 prefix_error=False)
183 prefix_error=False)
184 except Exception:
184 except Exception:
185 log.exception('Exception in auth_settings')
185 log.exception('Exception in auth_settings')
186 self.request.session.flash(
186 self.request.session.flash(
187 _('Error occurred during update of auth settings.'),
187 _('Error occurred during update of auth settings.'),
188 queue='error')
188 queue='error')
189
189
190 redirect_to = self.request.resource_path(
190 redirect_to = self.request.resource_path(
191 self.context, route_name='auth_home')
191 self.context, route_name='auth_home')
192 return HTTPFound(redirect_to)
192 return HTTPFound(redirect_to)
@@ -1,306 +1,306 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 Journal / user event log controller for rhodecode
22 Journal / user event log controller for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 from itertools import groupby
26 from itertools import groupby
27
27
28 from sqlalchemy import or_
28 from sqlalchemy import or_
29 from sqlalchemy.orm import joinedload
29 from sqlalchemy.orm import joinedload
30
30
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32
32
33 from webob.exc import HTTPBadRequest
33 from webob.exc import HTTPBadRequest
34 from pylons import request, tmpl_context as c, response, url
34 from pylons import request, tmpl_context as c, response, url
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.controllers.admin.admin import _journal_filter
37 from rhodecode.controllers.admin.admin import _journal_filter
38 from rhodecode.model.db import UserLog, UserFollowing, User
38 from rhodecode.model.db import UserLog, UserFollowing, User
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
42 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
43 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class JournalController(BaseController):
49 class JournalController(BaseController):
50
50
51 def __before__(self):
51 def __before__(self):
52 super(JournalController, self).__before__()
52 super(JournalController, self).__before__()
53 self.language = 'en-us'
53 self.language = 'en-us'
54 self.ttl = "5"
54 self.ttl = "5"
55 self.feed_nr = 20
55 self.feed_nr = 20
56 c.search_term = request.GET.get('filter')
56 c.search_term = request.GET.get('filter')
57
57
58 def _get_daily_aggregate(self, journal):
58 def _get_daily_aggregate(self, journal):
59 groups = []
59 groups = []
60 for k, g in groupby(journal, lambda x: x.action_as_day):
60 for k, g in groupby(journal, lambda x: x.action_as_day):
61 user_group = []
61 user_group = []
62 #groupby username if it's a present value, else fallback to journal username
62 #groupby username if it's a present value, else fallback to journal username
63 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
63 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
64 l = list(g2)
64 l = list(g2)
65 user_group.append((l[0].user, l))
65 user_group.append((l[0].user, l))
66
66
67 groups.append((k, user_group,))
67 groups.append((k, user_group,))
68
68
69 return groups
69 return groups
70
70
71 def _get_journal_data(self, following_repos):
71 def _get_journal_data(self, following_repos):
72 repo_ids = [x.follows_repository.repo_id for x in following_repos
72 repo_ids = [x.follows_repository.repo_id for x in following_repos
73 if x.follows_repository is not None]
73 if x.follows_repository is not None]
74 user_ids = [x.follows_user.user_id for x in following_repos
74 user_ids = [x.follows_user.user_id for x in following_repos
75 if x.follows_user is not None]
75 if x.follows_user is not None]
76
76
77 filtering_criterion = None
77 filtering_criterion = None
78
78
79 if repo_ids and user_ids:
79 if repo_ids and user_ids:
80 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
80 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
81 UserLog.user_id.in_(user_ids))
81 UserLog.user_id.in_(user_ids))
82 if repo_ids and not user_ids:
82 if repo_ids and not user_ids:
83 filtering_criterion = UserLog.repository_id.in_(repo_ids)
83 filtering_criterion = UserLog.repository_id.in_(repo_ids)
84 if not repo_ids and user_ids:
84 if not repo_ids and user_ids:
85 filtering_criterion = UserLog.user_id.in_(user_ids)
85 filtering_criterion = UserLog.user_id.in_(user_ids)
86 if filtering_criterion is not None:
86 if filtering_criterion is not None:
87 journal = self.sa.query(UserLog)\
87 journal = self.sa.query(UserLog)\
88 .options(joinedload(UserLog.user))\
88 .options(joinedload(UserLog.user))\
89 .options(joinedload(UserLog.repository))
89 .options(joinedload(UserLog.repository))
90 #filter
90 #filter
91 try:
91 try:
92 journal = _journal_filter(journal, c.search_term)
92 journal = _journal_filter(journal, c.search_term)
93 except Exception:
93 except Exception:
94 # we want this to crash for now
94 # we want this to crash for now
95 raise
95 raise
96 journal = journal.filter(filtering_criterion)\
96 journal = journal.filter(filtering_criterion)\
97 .order_by(UserLog.action_date.desc())
97 .order_by(UserLog.action_date.desc())
98 else:
98 else:
99 journal = []
99 journal = []
100
100
101 return journal
101 return journal
102
102
103 def _atom_feed(self, repos, public=True):
103 def _atom_feed(self, repos, public=True):
104 journal = self._get_journal_data(repos)
104 journal = self._get_journal_data(repos)
105 if public:
105 if public:
106 _link = url('public_journal_atom', qualified=True)
106 _link = url('public_journal_atom', qualified=True)
107 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
107 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
108 'atom feed')
108 'atom feed')
109 else:
109 else:
110 _link = url('journal_atom', qualified=True)
110 _link = url('journal_atom', qualified=True)
111 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
111 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
112
112
113 feed = Atom1Feed(title=_desc,
113 feed = Atom1Feed(title=_desc,
114 link=_link,
114 link=_link,
115 description=_desc,
115 description=_desc,
116 language=self.language,
116 language=self.language,
117 ttl=self.ttl)
117 ttl=self.ttl)
118
118
119 for entry in journal[:self.feed_nr]:
119 for entry in journal[:self.feed_nr]:
120 user = entry.user
120 user = entry.user
121 if user is None:
121 if user is None:
122 #fix deleted users
122 #fix deleted users
123 user = AttributeDict({'short_contact': entry.username,
123 user = AttributeDict({'short_contact': entry.username,
124 'email': '',
124 'email': '',
125 'full_contact': ''})
125 'full_contact': ''})
126 action, action_extra, ico = h.action_parser(entry, feed=True)
126 action, action_extra, ico = h.action_parser(entry, feed=True)
127 title = "%s - %s %s" % (user.short_contact, action(),
127 title = "%s - %s %s" % (user.short_contact, action(),
128 entry.repository.repo_name)
128 entry.repository.repo_name)
129 desc = action_extra()
129 desc = action_extra()
130 _url = None
130 _url = None
131 if entry.repository is not None:
131 if entry.repository is not None:
132 _url = url('changelog_home',
132 _url = url('changelog_home',
133 repo_name=entry.repository.repo_name,
133 repo_name=entry.repository.repo_name,
134 qualified=True)
134 qualified=True)
135
135
136 feed.add_item(title=title,
136 feed.add_item(title=title,
137 pubdate=entry.action_date,
137 pubdate=entry.action_date,
138 link=_url or url('', qualified=True),
138 link=_url or url('', qualified=True),
139 author_email=user.email,
139 author_email=user.email,
140 author_name=user.full_contact,
140 author_name=user.full_contact,
141 description=desc)
141 description=desc)
142
142
143 response.content_type = feed.mime_type
143 response.content_type = feed.mime_type
144 return feed.writeString('utf-8')
144 return feed.writeString('utf-8')
145
145
146 def _rss_feed(self, repos, public=True):
146 def _rss_feed(self, repos, public=True):
147 journal = self._get_journal_data(repos)
147 journal = self._get_journal_data(repos)
148 if public:
148 if public:
149 _link = url('public_journal_atom', qualified=True)
149 _link = url('public_journal_atom', qualified=True)
150 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
150 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
151 'rss feed')
151 'rss feed')
152 else:
152 else:
153 _link = url('journal_atom', qualified=True)
153 _link = url('journal_atom', qualified=True)
154 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
154 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
155
155
156 feed = Rss201rev2Feed(title=_desc,
156 feed = Rss201rev2Feed(title=_desc,
157 link=_link,
157 link=_link,
158 description=_desc,
158 description=_desc,
159 language=self.language,
159 language=self.language,
160 ttl=self.ttl)
160 ttl=self.ttl)
161
161
162 for entry in journal[:self.feed_nr]:
162 for entry in journal[:self.feed_nr]:
163 user = entry.user
163 user = entry.user
164 if user is None:
164 if user is None:
165 #fix deleted users
165 #fix deleted users
166 user = AttributeDict({'short_contact': entry.username,
166 user = AttributeDict({'short_contact': entry.username,
167 'email': '',
167 'email': '',
168 'full_contact': ''})
168 'full_contact': ''})
169 action, action_extra, ico = h.action_parser(entry, feed=True)
169 action, action_extra, ico = h.action_parser(entry, feed=True)
170 title = "%s - %s %s" % (user.short_contact, action(),
170 title = "%s - %s %s" % (user.short_contact, action(),
171 entry.repository.repo_name)
171 entry.repository.repo_name)
172 desc = action_extra()
172 desc = action_extra()
173 _url = None
173 _url = None
174 if entry.repository is not None:
174 if entry.repository is not None:
175 _url = url('changelog_home',
175 _url = url('changelog_home',
176 repo_name=entry.repository.repo_name,
176 repo_name=entry.repository.repo_name,
177 qualified=True)
177 qualified=True)
178
178
179 feed.add_item(title=title,
179 feed.add_item(title=title,
180 pubdate=entry.action_date,
180 pubdate=entry.action_date,
181 link=_url or url('', qualified=True),
181 link=_url or url('', qualified=True),
182 author_email=user.email,
182 author_email=user.email,
183 author_name=user.full_contact,
183 author_name=user.full_contact,
184 description=desc)
184 description=desc)
185
185
186 response.content_type = feed.mime_type
186 response.content_type = feed.mime_type
187 return feed.writeString('utf-8')
187 return feed.writeString('utf-8')
188
188
189 @LoginRequired()
189 @LoginRequired()
190 @NotAnonymous()
190 @NotAnonymous()
191 def index(self):
191 def index(self):
192 # Return a rendered template
192 # Return a rendered template
193 p = safe_int(request.GET.get('page', 1), 1)
193 p = safe_int(request.GET.get('page', 1), 1)
194 c.user = User.get(c.rhodecode_user.user_id)
194 c.user = User.get(c.rhodecode_user.user_id)
195 following = self.sa.query(UserFollowing)\
195 following = self.sa.query(UserFollowing)\
196 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
196 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
197 .options(joinedload(UserFollowing.follows_repository))\
197 .options(joinedload(UserFollowing.follows_repository))\
198 .all()
198 .all()
199
199
200 journal = self._get_journal_data(following)
200 journal = self._get_journal_data(following)
201
201
202 def url_generator(**kw):
202 def url_generator(**kw):
203 return url.current(filter=c.search_term, **kw)
203 return url.current(filter=c.search_term, **kw)
204
204
205 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
205 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
206 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
206 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
207
207
208 c.journal_data = render('journal/journal_data.html')
208 c.journal_data = render('journal/journal_data.html')
209 if request.is_xhr:
209 if request.is_xhr:
210 return c.journal_data
210 return c.journal_data
211
211
212 return render('journal/journal.html')
212 return render('journal/journal.html')
213
213
214 @LoginRequired(auth_token_access=True)
214 @LoginRequired(auth_token_access=True)
215 @NotAnonymous()
215 @NotAnonymous()
216 def journal_atom(self):
216 def journal_atom(self):
217 """
217 """
218 Produce an atom-1.0 feed via feedgenerator module
218 Produce an atom-1.0 feed via feedgenerator module
219 """
219 """
220 following = self.sa.query(UserFollowing)\
220 following = self.sa.query(UserFollowing)\
221 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
221 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
222 .options(joinedload(UserFollowing.follows_repository))\
222 .options(joinedload(UserFollowing.follows_repository))\
223 .all()
223 .all()
224 return self._atom_feed(following, public=False)
224 return self._atom_feed(following, public=False)
225
225
226 @LoginRequired(auth_token_access=True)
226 @LoginRequired(auth_token_access=True)
227 @NotAnonymous()
227 @NotAnonymous()
228 def journal_rss(self):
228 def journal_rss(self):
229 """
229 """
230 Produce an rss feed via feedgenerator module
230 Produce an rss feed via feedgenerator module
231 """
231 """
232 following = self.sa.query(UserFollowing)\
232 following = self.sa.query(UserFollowing)\
233 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
233 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
234 .options(joinedload(UserFollowing.follows_repository))\
234 .options(joinedload(UserFollowing.follows_repository))\
235 .all()
235 .all()
236 return self._rss_feed(following, public=False)
236 return self._rss_feed(following, public=False)
237
237
238 @CSRFRequired()
238 @CSRFRequired()
239 @LoginRequired()
239 @LoginRequired()
240 @NotAnonymous()
240 @NotAnonymous()
241 def toggle_following(self):
241 def toggle_following(self):
242 user_id = request.POST.get('follows_user_id')
242 user_id = request.POST.get('follows_user_id')
243 if user_id:
243 if user_id:
244 try:
244 try:
245 self.scm_model.toggle_following_user(
245 self.scm_model.toggle_following_user(
246 user_id, c.rhodecode_user.user_id)
246 user_id, c.rhodecode_user.user_id)
247 Session.commit()
247 Session().commit()
248 return 'ok'
248 return 'ok'
249 except Exception:
249 except Exception:
250 raise HTTPBadRequest()
250 raise HTTPBadRequest()
251
251
252 repo_id = request.POST.get('follows_repo_id')
252 repo_id = request.POST.get('follows_repo_id')
253 if repo_id:
253 if repo_id:
254 try:
254 try:
255 self.scm_model.toggle_following_repo(
255 self.scm_model.toggle_following_repo(
256 repo_id, c.rhodecode_user.user_id)
256 repo_id, c.rhodecode_user.user_id)
257 Session.commit()
257 Session().commit()
258 return 'ok'
258 return 'ok'
259 except Exception:
259 except Exception:
260 raise HTTPBadRequest()
260 raise HTTPBadRequest()
261
261
262
262
263 @LoginRequired()
263 @LoginRequired()
264 def public_journal(self):
264 def public_journal(self):
265 # Return a rendered template
265 # Return a rendered template
266 p = safe_int(request.GET.get('page', 1), 1)
266 p = safe_int(request.GET.get('page', 1), 1)
267
267
268 c.following = self.sa.query(UserFollowing)\
268 c.following = self.sa.query(UserFollowing)\
269 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
269 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
270 .options(joinedload(UserFollowing.follows_repository))\
270 .options(joinedload(UserFollowing.follows_repository))\
271 .all()
271 .all()
272
272
273 journal = self._get_journal_data(c.following)
273 journal = self._get_journal_data(c.following)
274
274
275 c.journal_pager = Page(journal, page=p, items_per_page=20)
275 c.journal_pager = Page(journal, page=p, items_per_page=20)
276
276
277 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
277 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
278
278
279 c.journal_data = render('journal/journal_data.html')
279 c.journal_data = render('journal/journal_data.html')
280 if request.is_xhr:
280 if request.is_xhr:
281 return c.journal_data
281 return c.journal_data
282 return render('journal/public_journal.html')
282 return render('journal/public_journal.html')
283
283
284 @LoginRequired(auth_token_access=True)
284 @LoginRequired(auth_token_access=True)
285 def public_journal_atom(self):
285 def public_journal_atom(self):
286 """
286 """
287 Produce an atom-1.0 feed via feedgenerator module
287 Produce an atom-1.0 feed via feedgenerator module
288 """
288 """
289 c.following = self.sa.query(UserFollowing)\
289 c.following = self.sa.query(UserFollowing)\
290 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
290 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
291 .options(joinedload(UserFollowing.follows_repository))\
291 .options(joinedload(UserFollowing.follows_repository))\
292 .all()
292 .all()
293
293
294 return self._atom_feed(c.following)
294 return self._atom_feed(c.following)
295
295
296 @LoginRequired(auth_token_access=True)
296 @LoginRequired(auth_token_access=True)
297 def public_journal_rss(self):
297 def public_journal_rss(self):
298 """
298 """
299 Produce an rss2 feed via feedgenerator module
299 Produce an rss2 feed via feedgenerator module
300 """
300 """
301 c.following = self.sa.query(UserFollowing)\
301 c.following = self.sa.query(UserFollowing)\
302 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
302 .filter(UserFollowing.user_id == c.rhodecode_user.user_id)\
303 .options(joinedload(UserFollowing.follows_repository))\
303 .options(joinedload(UserFollowing.follows_repository))\
304 .all()
304 .all()
305
305
306 return self._rss_feed(c.following)
306 return self._rss_feed(c.following)
@@ -1,698 +1,698 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 hashlib
21 import hashlib
22 import logging
22 import logging
23 from collections import namedtuple
23 from collections import namedtuple
24 from functools import wraps
24 from functools import wraps
25
25
26 from rhodecode.lib import caches
26 from rhodecode.lib import caches
27 from rhodecode.lib.caching_query import FromCache
27 from rhodecode.lib.caching_query import FromCache
28 from rhodecode.lib.utils2 import (
28 from rhodecode.lib.utils2 import (
29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
30 from rhodecode.model import BaseModel
30 from rhodecode.model import BaseModel
31 from rhodecode.model.db import (
31 from rhodecode.model.db import (
32 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
32 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34
34
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 UiSetting = namedtuple(
39 UiSetting = namedtuple(
40 'UiSetting', ['section', 'key', 'value', 'active'])
40 'UiSetting', ['section', 'key', 'value', 'active'])
41
41
42 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
42 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
43
43
44
44
45 class SettingNotFound(Exception):
45 class SettingNotFound(Exception):
46 def __init__(self):
46 def __init__(self):
47 super(SettingNotFound, self).__init__('Setting is not found')
47 super(SettingNotFound, self).__init__('Setting is not found')
48
48
49
49
50 class SettingsModel(BaseModel):
50 class SettingsModel(BaseModel):
51 BUILTIN_HOOKS = (
51 BUILTIN_HOOKS = (
52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
52 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PULL,
53 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PULL,
54 RhodeCodeUi.HOOK_PRE_PULL)
54 RhodeCodeUi.HOOK_PRE_PULL)
55 HOOKS_SECTION = 'hooks'
55 HOOKS_SECTION = 'hooks'
56
56
57 def __init__(self, sa=None, repo=None):
57 def __init__(self, sa=None, repo=None):
58 self.repo = repo
58 self.repo = repo
59 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
59 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
60 self.SettingsDbModel = (
60 self.SettingsDbModel = (
61 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
61 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
62 super(SettingsModel, self).__init__(sa)
62 super(SettingsModel, self).__init__(sa)
63
63
64 def get_ui_by_key(self, key):
64 def get_ui_by_key(self, key):
65 q = self.UiDbModel.query()
65 q = self.UiDbModel.query()
66 q = q.filter(self.UiDbModel.ui_key == key)
66 q = q.filter(self.UiDbModel.ui_key == key)
67 q = self._filter_by_repo(RepoRhodeCodeUi, q)
67 q = self._filter_by_repo(RepoRhodeCodeUi, q)
68 return q.scalar()
68 return q.scalar()
69
69
70 def get_ui_by_section(self, section):
70 def get_ui_by_section(self, section):
71 q = self.UiDbModel.query()
71 q = self.UiDbModel.query()
72 q = q.filter(self.UiDbModel.ui_section == section)
72 q = q.filter(self.UiDbModel.ui_section == section)
73 q = self._filter_by_repo(RepoRhodeCodeUi, q)
73 q = self._filter_by_repo(RepoRhodeCodeUi, q)
74 return q.all()
74 return q.all()
75
75
76 def get_ui_by_section_and_key(self, section, key):
76 def get_ui_by_section_and_key(self, section, key):
77 q = self.UiDbModel.query()
77 q = self.UiDbModel.query()
78 q = q.filter(self.UiDbModel.ui_section == section)
78 q = q.filter(self.UiDbModel.ui_section == section)
79 q = q.filter(self.UiDbModel.ui_key == key)
79 q = q.filter(self.UiDbModel.ui_key == key)
80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
80 q = self._filter_by_repo(RepoRhodeCodeUi, q)
81 return q.scalar()
81 return q.scalar()
82
82
83 def get_ui(self, section=None, key=None):
83 def get_ui(self, section=None, key=None):
84 q = self.UiDbModel.query()
84 q = self.UiDbModel.query()
85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
85 q = self._filter_by_repo(RepoRhodeCodeUi, q)
86
86
87 if section:
87 if section:
88 q = q.filter(self.UiDbModel.ui_section == section)
88 q = q.filter(self.UiDbModel.ui_section == section)
89 if key:
89 if key:
90 q = q.filter(self.UiDbModel.ui_key == key)
90 q = q.filter(self.UiDbModel.ui_key == key)
91
91
92 # TODO: mikhail: add caching
92 # TODO: mikhail: add caching
93 result = [
93 result = [
94 UiSetting(
94 UiSetting(
95 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
95 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
96 value=safe_str(r.ui_value), active=r.ui_active
96 value=safe_str(r.ui_value), active=r.ui_active
97 )
97 )
98 for r in q.all()
98 for r in q.all()
99 ]
99 ]
100 return result
100 return result
101
101
102 def get_builtin_hooks(self):
102 def get_builtin_hooks(self):
103 q = self.UiDbModel.query()
103 q = self.UiDbModel.query()
104 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
104 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
105 return self._get_hooks(q)
105 return self._get_hooks(q)
106
106
107 def get_custom_hooks(self):
107 def get_custom_hooks(self):
108 q = self.UiDbModel.query()
108 q = self.UiDbModel.query()
109 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
109 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
110 return self._get_hooks(q)
110 return self._get_hooks(q)
111
111
112 def create_ui_section_value(self, section, val, key=None, active=True):
112 def create_ui_section_value(self, section, val, key=None, active=True):
113 new_ui = self.UiDbModel()
113 new_ui = self.UiDbModel()
114 new_ui.ui_section = section
114 new_ui.ui_section = section
115 new_ui.ui_value = val
115 new_ui.ui_value = val
116 new_ui.ui_active = active
116 new_ui.ui_active = active
117
117
118 if self.repo:
118 if self.repo:
119 repo = self._get_repo(self.repo)
119 repo = self._get_repo(self.repo)
120 repository_id = repo.repo_id
120 repository_id = repo.repo_id
121 new_ui.repository_id = repository_id
121 new_ui.repository_id = repository_id
122
122
123 if not key:
123 if not key:
124 # keys are unique so they need appended info
124 # keys are unique so they need appended info
125 if self.repo:
125 if self.repo:
126 key = hashlib.sha1(
126 key = hashlib.sha1(
127 '{}{}{}'.format(section, val, repository_id)).hexdigest()
127 '{}{}{}'.format(section, val, repository_id)).hexdigest()
128 else:
128 else:
129 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
129 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
130
130
131 new_ui.ui_key = key
131 new_ui.ui_key = key
132
132
133 Session().add(new_ui)
133 Session().add(new_ui)
134 return new_ui
134 return new_ui
135
135
136 def create_or_update_hook(self, key, value):
136 def create_or_update_hook(self, key, value):
137 ui = (
137 ui = (
138 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
138 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
139 self.UiDbModel())
139 self.UiDbModel())
140 ui.ui_section = self.HOOKS_SECTION
140 ui.ui_section = self.HOOKS_SECTION
141 ui.ui_active = True
141 ui.ui_active = True
142 ui.ui_key = key
142 ui.ui_key = key
143 ui.ui_value = value
143 ui.ui_value = value
144
144
145 if self.repo:
145 if self.repo:
146 repo = self._get_repo(self.repo)
146 repo = self._get_repo(self.repo)
147 repository_id = repo.repo_id
147 repository_id = repo.repo_id
148 ui.repository_id = repository_id
148 ui.repository_id = repository_id
149
149
150 Session().add(ui)
150 Session().add(ui)
151 return ui
151 return ui
152
152
153 def delete_ui(self, id_):
153 def delete_ui(self, id_):
154 ui = self.UiDbModel.get(id_)
154 ui = self.UiDbModel.get(id_)
155 if not ui:
155 if not ui:
156 raise SettingNotFound()
156 raise SettingNotFound()
157 Session().delete(ui)
157 Session().delete(ui)
158
158
159 def get_setting_by_name(self, name):
159 def get_setting_by_name(self, name):
160 q = self._get_settings_query()
160 q = self._get_settings_query()
161 q = q.filter(self.SettingsDbModel.app_settings_name == name)
161 q = q.filter(self.SettingsDbModel.app_settings_name == name)
162 return q.scalar()
162 return q.scalar()
163
163
164 def create_or_update_setting(
164 def create_or_update_setting(
165 self, name, val=Optional(''), type_=Optional('unicode')):
165 self, name, val=Optional(''), type_=Optional('unicode')):
166 """
166 """
167 Creates or updates RhodeCode setting. If updates is triggered it will
167 Creates or updates RhodeCode setting. If updates is triggered it will
168 only update parameters that are explicityl set Optional instance will
168 only update parameters that are explicityl set Optional instance will
169 be skipped
169 be skipped
170
170
171 :param name:
171 :param name:
172 :param val:
172 :param val:
173 :param type_:
173 :param type_:
174 :return:
174 :return:
175 """
175 """
176
176
177 res = self.get_setting_by_name(name)
177 res = self.get_setting_by_name(name)
178 repo = self._get_repo(self.repo) if self.repo else None
178 repo = self._get_repo(self.repo) if self.repo else None
179
179
180 if not res:
180 if not res:
181 val = Optional.extract(val)
181 val = Optional.extract(val)
182 type_ = Optional.extract(type_)
182 type_ = Optional.extract(type_)
183
183
184 args = (
184 args = (
185 (repo.repo_id, name, val, type_)
185 (repo.repo_id, name, val, type_)
186 if repo else (name, val, type_))
186 if repo else (name, val, type_))
187 res = self.SettingsDbModel(*args)
187 res = self.SettingsDbModel(*args)
188
188
189 else:
189 else:
190 if self.repo:
190 if self.repo:
191 res.repository_id = repo.repo_id
191 res.repository_id = repo.repo_id
192
192
193 res.app_settings_name = name
193 res.app_settings_name = name
194 if not isinstance(type_, Optional):
194 if not isinstance(type_, Optional):
195 # update if set
195 # update if set
196 res.app_settings_type = type_
196 res.app_settings_type = type_
197 if not isinstance(val, Optional):
197 if not isinstance(val, Optional):
198 # update if set
198 # update if set
199 res.app_settings_value = val
199 res.app_settings_value = val
200
200
201 Session.add(res)
201 Session().add(res)
202 return res
202 return res
203
203
204 def invalidate_settings_cache(self):
204 def invalidate_settings_cache(self):
205 namespace = 'rhodecode_settings'
205 namespace = 'rhodecode_settings'
206 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
206 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
207 caches.clear_cache_manager(cache_manager)
207 caches.clear_cache_manager(cache_manager)
208
208
209 def get_all_settings(self, cache=False):
209 def get_all_settings(self, cache=False):
210 def _compute():
210 def _compute():
211 q = self._get_settings_query()
211 q = self._get_settings_query()
212 if not q:
212 if not q:
213 raise Exception('Could not get application settings !')
213 raise Exception('Could not get application settings !')
214
214
215 settings = {
215 settings = {
216 'rhodecode_' + result.app_settings_name: result.app_settings_value
216 'rhodecode_' + result.app_settings_name: result.app_settings_value
217 for result in q
217 for result in q
218 }
218 }
219 return settings
219 return settings
220
220
221 if cache:
221 if cache:
222 log.debug('Fetching app settings using cache')
222 log.debug('Fetching app settings using cache')
223 repo = self._get_repo(self.repo) if self.repo else None
223 repo = self._get_repo(self.repo) if self.repo else None
224 namespace = 'rhodecode_settings'
224 namespace = 'rhodecode_settings'
225 cache_manager = caches.get_cache_manager(
225 cache_manager = caches.get_cache_manager(
226 'sql_cache_short', namespace)
226 'sql_cache_short', namespace)
227 _cache_key = (
227 _cache_key = (
228 "get_repo_{}_settings".format(repo.repo_id)
228 "get_repo_{}_settings".format(repo.repo_id)
229 if repo else "get_app_settings")
229 if repo else "get_app_settings")
230
230
231 return cache_manager.get(_cache_key, createfunc=_compute)
231 return cache_manager.get(_cache_key, createfunc=_compute)
232
232
233 else:
233 else:
234 return _compute()
234 return _compute()
235
235
236 def get_auth_settings(self):
236 def get_auth_settings(self):
237 q = self._get_settings_query()
237 q = self._get_settings_query()
238 q = q.filter(
238 q = q.filter(
239 self.SettingsDbModel.app_settings_name.startswith('auth_'))
239 self.SettingsDbModel.app_settings_name.startswith('auth_'))
240 rows = q.all()
240 rows = q.all()
241 auth_settings = {
241 auth_settings = {
242 row.app_settings_name: row.app_settings_value for row in rows}
242 row.app_settings_name: row.app_settings_value for row in rows}
243 return auth_settings
243 return auth_settings
244
244
245 def get_auth_plugins(self):
245 def get_auth_plugins(self):
246 auth_plugins = self.get_setting_by_name("auth_plugins")
246 auth_plugins = self.get_setting_by_name("auth_plugins")
247 return auth_plugins.app_settings_value
247 return auth_plugins.app_settings_value
248
248
249 def get_default_repo_settings(self, strip_prefix=False):
249 def get_default_repo_settings(self, strip_prefix=False):
250 q = self._get_settings_query()
250 q = self._get_settings_query()
251 q = q.filter(
251 q = q.filter(
252 self.SettingsDbModel.app_settings_name.startswith('default_'))
252 self.SettingsDbModel.app_settings_name.startswith('default_'))
253 rows = q.all()
253 rows = q.all()
254
254
255 result = {}
255 result = {}
256 for row in rows:
256 for row in rows:
257 key = row.app_settings_name
257 key = row.app_settings_name
258 if strip_prefix:
258 if strip_prefix:
259 key = remove_prefix(key, prefix='default_')
259 key = remove_prefix(key, prefix='default_')
260 result.update({key: row.app_settings_value})
260 result.update({key: row.app_settings_value})
261 return result
261 return result
262
262
263 def get_repo(self):
263 def get_repo(self):
264 repo = self._get_repo(self.repo)
264 repo = self._get_repo(self.repo)
265 if not repo:
265 if not repo:
266 raise Exception(
266 raise Exception(
267 'Repository {} cannot be found'.format(self.repo))
267 'Repository {} cannot be found'.format(self.repo))
268 return repo
268 return repo
269
269
270 def _filter_by_repo(self, model, query):
270 def _filter_by_repo(self, model, query):
271 if self.repo:
271 if self.repo:
272 repo = self.get_repo()
272 repo = self.get_repo()
273 query = query.filter(model.repository_id == repo.repo_id)
273 query = query.filter(model.repository_id == repo.repo_id)
274 return query
274 return query
275
275
276 def _get_hooks(self, query):
276 def _get_hooks(self, query):
277 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
277 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
278 query = self._filter_by_repo(RepoRhodeCodeUi, query)
278 query = self._filter_by_repo(RepoRhodeCodeUi, query)
279 return query.all()
279 return query.all()
280
280
281 def _get_settings_query(self):
281 def _get_settings_query(self):
282 q = self.SettingsDbModel.query()
282 q = self.SettingsDbModel.query()
283 return self._filter_by_repo(RepoRhodeCodeSetting, q)
283 return self._filter_by_repo(RepoRhodeCodeSetting, q)
284
284
285 def list_enabled_social_plugins(self, settings):
285 def list_enabled_social_plugins(self, settings):
286 enabled = []
286 enabled = []
287 for plug in SOCIAL_PLUGINS_LIST:
287 for plug in SOCIAL_PLUGINS_LIST:
288 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
288 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
289 )):
289 )):
290 enabled.append(plug)
290 enabled.append(plug)
291 return enabled
291 return enabled
292
292
293
293
294 def assert_repo_settings(func):
294 def assert_repo_settings(func):
295 @wraps(func)
295 @wraps(func)
296 def _wrapper(self, *args, **kwargs):
296 def _wrapper(self, *args, **kwargs):
297 if not self.repo_settings:
297 if not self.repo_settings:
298 raise Exception('Repository is not specified')
298 raise Exception('Repository is not specified')
299 return func(self, *args, **kwargs)
299 return func(self, *args, **kwargs)
300 return _wrapper
300 return _wrapper
301
301
302
302
303 class IssueTrackerSettingsModel(object):
303 class IssueTrackerSettingsModel(object):
304 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
304 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
305 SETTINGS_PREFIX = 'issuetracker_'
305 SETTINGS_PREFIX = 'issuetracker_'
306
306
307 def __init__(self, sa=None, repo=None):
307 def __init__(self, sa=None, repo=None):
308 self.global_settings = SettingsModel(sa=sa)
308 self.global_settings = SettingsModel(sa=sa)
309 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
309 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
310
310
311 @property
311 @property
312 def inherit_global_settings(self):
312 def inherit_global_settings(self):
313 if not self.repo_settings:
313 if not self.repo_settings:
314 return True
314 return True
315 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
315 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
316 return setting.app_settings_value if setting else True
316 return setting.app_settings_value if setting else True
317
317
318 @inherit_global_settings.setter
318 @inherit_global_settings.setter
319 def inherit_global_settings(self, value):
319 def inherit_global_settings(self, value):
320 if self.repo_settings:
320 if self.repo_settings:
321 settings = self.repo_settings.create_or_update_setting(
321 settings = self.repo_settings.create_or_update_setting(
322 self.INHERIT_SETTINGS, value, type_='bool')
322 self.INHERIT_SETTINGS, value, type_='bool')
323 Session().add(settings)
323 Session().add(settings)
324
324
325 def _get_keyname(self, key, uid, prefix=''):
325 def _get_keyname(self, key, uid, prefix=''):
326 return '{0}{1}{2}_{3}'.format(
326 return '{0}{1}{2}_{3}'.format(
327 prefix, self.SETTINGS_PREFIX, key, uid)
327 prefix, self.SETTINGS_PREFIX, key, uid)
328
328
329 def _make_dict_for_settings(self, qs):
329 def _make_dict_for_settings(self, qs):
330 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
330 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
331
331
332 issuetracker_entries = {}
332 issuetracker_entries = {}
333 # create keys
333 # create keys
334 for k, v in qs.items():
334 for k, v in qs.items():
335 if k.startswith(prefix_match):
335 if k.startswith(prefix_match):
336 uid = k[len(prefix_match):]
336 uid = k[len(prefix_match):]
337 issuetracker_entries[uid] = None
337 issuetracker_entries[uid] = None
338
338
339 # populate
339 # populate
340 for uid in issuetracker_entries:
340 for uid in issuetracker_entries:
341 issuetracker_entries[uid] = AttributeDict({
341 issuetracker_entries[uid] = AttributeDict({
342 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
342 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
343 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
343 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
344 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
344 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
345 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
345 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
346 })
346 })
347 return issuetracker_entries
347 return issuetracker_entries
348
348
349 def get_global_settings(self, cache=False):
349 def get_global_settings(self, cache=False):
350 """
350 """
351 Returns list of global issue tracker settings
351 Returns list of global issue tracker settings
352 """
352 """
353 defaults = self.global_settings.get_all_settings(cache=cache)
353 defaults = self.global_settings.get_all_settings(cache=cache)
354 settings = self._make_dict_for_settings(defaults)
354 settings = self._make_dict_for_settings(defaults)
355 return settings
355 return settings
356
356
357 def get_repo_settings(self, cache=False):
357 def get_repo_settings(self, cache=False):
358 """
358 """
359 Returns list of issue tracker settings per repository
359 Returns list of issue tracker settings per repository
360 """
360 """
361 if not self.repo_settings:
361 if not self.repo_settings:
362 raise Exception('Repository is not specified')
362 raise Exception('Repository is not specified')
363 all_settings = self.repo_settings.get_all_settings(cache=cache)
363 all_settings = self.repo_settings.get_all_settings(cache=cache)
364 settings = self._make_dict_for_settings(all_settings)
364 settings = self._make_dict_for_settings(all_settings)
365 return settings
365 return settings
366
366
367 def get_settings(self, cache=False):
367 def get_settings(self, cache=False):
368 if self.inherit_global_settings:
368 if self.inherit_global_settings:
369 return self.get_global_settings(cache=cache)
369 return self.get_global_settings(cache=cache)
370 else:
370 else:
371 return self.get_repo_settings(cache=cache)
371 return self.get_repo_settings(cache=cache)
372
372
373 def delete_entries(self, uid):
373 def delete_entries(self, uid):
374 if self.repo_settings:
374 if self.repo_settings:
375 all_patterns = self.get_repo_settings()
375 all_patterns = self.get_repo_settings()
376 settings_model = self.repo_settings
376 settings_model = self.repo_settings
377 else:
377 else:
378 all_patterns = self.get_global_settings()
378 all_patterns = self.get_global_settings()
379 settings_model = self.global_settings
379 settings_model = self.global_settings
380 entries = all_patterns.get(uid)
380 entries = all_patterns.get(uid)
381
381
382 for del_key in entries:
382 for del_key in entries:
383 setting_name = self._get_keyname(del_key, uid)
383 setting_name = self._get_keyname(del_key, uid)
384 entry = settings_model.get_setting_by_name(setting_name)
384 entry = settings_model.get_setting_by_name(setting_name)
385 if entry:
385 if entry:
386 Session().delete(entry)
386 Session().delete(entry)
387
387
388 Session().commit()
388 Session().commit()
389
389
390 def create_or_update_setting(
390 def create_or_update_setting(
391 self, name, val=Optional(''), type_=Optional('unicode')):
391 self, name, val=Optional(''), type_=Optional('unicode')):
392 if self.repo_settings:
392 if self.repo_settings:
393 setting = self.repo_settings.create_or_update_setting(
393 setting = self.repo_settings.create_or_update_setting(
394 name, val, type_)
394 name, val, type_)
395 else:
395 else:
396 setting = self.global_settings.create_or_update_setting(
396 setting = self.global_settings.create_or_update_setting(
397 name, val, type_)
397 name, val, type_)
398 return setting
398 return setting
399
399
400
400
401 class VcsSettingsModel(object):
401 class VcsSettingsModel(object):
402
402
403 INHERIT_SETTINGS = 'inherit_vcs_settings'
403 INHERIT_SETTINGS = 'inherit_vcs_settings'
404 GENERAL_SETTINGS = (
404 GENERAL_SETTINGS = (
405 'use_outdated_comments', 'pr_merge_enabled',
405 'use_outdated_comments', 'pr_merge_enabled',
406 'hg_use_rebase_for_merging')
406 'hg_use_rebase_for_merging')
407 HOOKS_SETTINGS = (
407 HOOKS_SETTINGS = (
408 ('hooks', 'changegroup.repo_size'),
408 ('hooks', 'changegroup.repo_size'),
409 ('hooks', 'changegroup.push_logger'),
409 ('hooks', 'changegroup.push_logger'),
410 ('hooks', 'outgoing.pull_logger'))
410 ('hooks', 'outgoing.pull_logger'))
411 HG_SETTINGS = (
411 HG_SETTINGS = (
412 ('extensions', 'largefiles'), ('phases', 'publish'))
412 ('extensions', 'largefiles'), ('phases', 'publish'))
413 GLOBAL_HG_SETTINGS = HG_SETTINGS + (('extensions', 'hgsubversion'), )
413 GLOBAL_HG_SETTINGS = HG_SETTINGS + (('extensions', 'hgsubversion'), )
414 SVN_BRANCH_SECTION = 'vcs_svn_branch'
414 SVN_BRANCH_SECTION = 'vcs_svn_branch'
415 SVN_TAG_SECTION = 'vcs_svn_tag'
415 SVN_TAG_SECTION = 'vcs_svn_tag'
416 SSL_SETTING = ('web', 'push_ssl')
416 SSL_SETTING = ('web', 'push_ssl')
417 PATH_SETTING = ('paths', '/')
417 PATH_SETTING = ('paths', '/')
418
418
419 def __init__(self, sa=None, repo=None):
419 def __init__(self, sa=None, repo=None):
420 self.global_settings = SettingsModel(sa=sa)
420 self.global_settings = SettingsModel(sa=sa)
421 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
421 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
422 self._ui_settings = self.HG_SETTINGS + self.HOOKS_SETTINGS
422 self._ui_settings = self.HG_SETTINGS + self.HOOKS_SETTINGS
423 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
423 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
424
424
425 @property
425 @property
426 @assert_repo_settings
426 @assert_repo_settings
427 def inherit_global_settings(self):
427 def inherit_global_settings(self):
428 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
428 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
429 return setting.app_settings_value if setting else True
429 return setting.app_settings_value if setting else True
430
430
431 @inherit_global_settings.setter
431 @inherit_global_settings.setter
432 @assert_repo_settings
432 @assert_repo_settings
433 def inherit_global_settings(self, value):
433 def inherit_global_settings(self, value):
434 self.repo_settings.create_or_update_setting(
434 self.repo_settings.create_or_update_setting(
435 self.INHERIT_SETTINGS, value, type_='bool')
435 self.INHERIT_SETTINGS, value, type_='bool')
436
436
437 def get_global_svn_branch_patterns(self):
437 def get_global_svn_branch_patterns(self):
438 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
438 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
439
439
440 @assert_repo_settings
440 @assert_repo_settings
441 def get_repo_svn_branch_patterns(self):
441 def get_repo_svn_branch_patterns(self):
442 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
442 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
443
443
444 def get_global_svn_tag_patterns(self):
444 def get_global_svn_tag_patterns(self):
445 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
445 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
446
446
447 @assert_repo_settings
447 @assert_repo_settings
448 def get_repo_svn_tag_patterns(self):
448 def get_repo_svn_tag_patterns(self):
449 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
449 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
450
450
451 def get_global_settings(self):
451 def get_global_settings(self):
452 return self._collect_all_settings(global_=True)
452 return self._collect_all_settings(global_=True)
453
453
454 @assert_repo_settings
454 @assert_repo_settings
455 def get_repo_settings(self):
455 def get_repo_settings(self):
456 return self._collect_all_settings(global_=False)
456 return self._collect_all_settings(global_=False)
457
457
458 @assert_repo_settings
458 @assert_repo_settings
459 def create_or_update_repo_settings(
459 def create_or_update_repo_settings(
460 self, data, inherit_global_settings=False):
460 self, data, inherit_global_settings=False):
461 from rhodecode.model.scm import ScmModel
461 from rhodecode.model.scm import ScmModel
462
462
463 self.inherit_global_settings = inherit_global_settings
463 self.inherit_global_settings = inherit_global_settings
464
464
465 repo = self.repo_settings.get_repo()
465 repo = self.repo_settings.get_repo()
466 if not inherit_global_settings:
466 if not inherit_global_settings:
467 if repo.repo_type == 'svn':
467 if repo.repo_type == 'svn':
468 self.create_repo_svn_settings(data)
468 self.create_repo_svn_settings(data)
469 else:
469 else:
470 self.create_or_update_repo_hook_settings(data)
470 self.create_or_update_repo_hook_settings(data)
471 self.create_or_update_repo_pr_settings(data)
471 self.create_or_update_repo_pr_settings(data)
472
472
473 if repo.repo_type == 'hg':
473 if repo.repo_type == 'hg':
474 self.create_or_update_repo_hg_settings(data)
474 self.create_or_update_repo_hg_settings(data)
475
475
476 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
476 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
477
477
478 @assert_repo_settings
478 @assert_repo_settings
479 def create_or_update_repo_hook_settings(self, data):
479 def create_or_update_repo_hook_settings(self, data):
480 for section, key in self.HOOKS_SETTINGS:
480 for section, key in self.HOOKS_SETTINGS:
481 data_key = self._get_form_ui_key(section, key)
481 data_key = self._get_form_ui_key(section, key)
482 if data_key not in data:
482 if data_key not in data:
483 raise ValueError(
483 raise ValueError(
484 'The given data does not contain {} key'.format(data_key))
484 'The given data does not contain {} key'.format(data_key))
485
485
486 active = data.get(data_key)
486 active = data.get(data_key)
487 repo_setting = self.repo_settings.get_ui_by_section_and_key(
487 repo_setting = self.repo_settings.get_ui_by_section_and_key(
488 section, key)
488 section, key)
489 if not repo_setting:
489 if not repo_setting:
490 global_setting = self.global_settings.\
490 global_setting = self.global_settings.\
491 get_ui_by_section_and_key(section, key)
491 get_ui_by_section_and_key(section, key)
492 self.repo_settings.create_ui_section_value(
492 self.repo_settings.create_ui_section_value(
493 section, global_setting.ui_value, key=key, active=active)
493 section, global_setting.ui_value, key=key, active=active)
494 else:
494 else:
495 repo_setting.ui_active = active
495 repo_setting.ui_active = active
496 Session().add(repo_setting)
496 Session().add(repo_setting)
497
497
498 def update_global_hook_settings(self, data):
498 def update_global_hook_settings(self, data):
499 for section, key in self.HOOKS_SETTINGS:
499 for section, key in self.HOOKS_SETTINGS:
500 data_key = self._get_form_ui_key(section, key)
500 data_key = self._get_form_ui_key(section, key)
501 if data_key not in data:
501 if data_key not in data:
502 raise ValueError(
502 raise ValueError(
503 'The given data does not contain {} key'.format(data_key))
503 'The given data does not contain {} key'.format(data_key))
504 active = data.get(data_key)
504 active = data.get(data_key)
505 repo_setting = self.global_settings.get_ui_by_section_and_key(
505 repo_setting = self.global_settings.get_ui_by_section_and_key(
506 section, key)
506 section, key)
507 repo_setting.ui_active = active
507 repo_setting.ui_active = active
508 Session().add(repo_setting)
508 Session().add(repo_setting)
509
509
510 @assert_repo_settings
510 @assert_repo_settings
511 def create_or_update_repo_pr_settings(self, data):
511 def create_or_update_repo_pr_settings(self, data):
512 return self._create_or_update_general_settings(
512 return self._create_or_update_general_settings(
513 self.repo_settings, data)
513 self.repo_settings, data)
514
514
515 def create_or_update_global_pr_settings(self, data):
515 def create_or_update_global_pr_settings(self, data):
516 return self._create_or_update_general_settings(
516 return self._create_or_update_general_settings(
517 self.global_settings, data)
517 self.global_settings, data)
518
518
519 @assert_repo_settings
519 @assert_repo_settings
520 def create_repo_svn_settings(self, data):
520 def create_repo_svn_settings(self, data):
521 return self._create_svn_settings(self.repo_settings, data)
521 return self._create_svn_settings(self.repo_settings, data)
522
522
523 def create_global_svn_settings(self, data):
523 def create_global_svn_settings(self, data):
524 return self._create_svn_settings(self.global_settings, data)
524 return self._create_svn_settings(self.global_settings, data)
525
525
526 @assert_repo_settings
526 @assert_repo_settings
527 def create_or_update_repo_hg_settings(self, data):
527 def create_or_update_repo_hg_settings(self, data):
528 largefiles, phases = self.HG_SETTINGS
528 largefiles, phases = self.HG_SETTINGS
529 largefiles_key, phases_key = self._get_hg_settings(
529 largefiles_key, phases_key = self._get_hg_settings(
530 self.HG_SETTINGS, data)
530 self.HG_SETTINGS, data)
531 self._create_or_update_ui(
531 self._create_or_update_ui(
532 self.repo_settings, *largefiles, value='',
532 self.repo_settings, *largefiles, value='',
533 active=data[largefiles_key])
533 active=data[largefiles_key])
534 self._create_or_update_ui(
534 self._create_or_update_ui(
535 self.repo_settings, *phases, value=safe_str(data[phases_key]))
535 self.repo_settings, *phases, value=safe_str(data[phases_key]))
536
536
537 def create_or_update_global_hg_settings(self, data):
537 def create_or_update_global_hg_settings(self, data):
538 largefiles, phases, subversion = self.GLOBAL_HG_SETTINGS
538 largefiles, phases, subversion = self.GLOBAL_HG_SETTINGS
539 largefiles_key, phases_key, subversion_key = self._get_hg_settings(
539 largefiles_key, phases_key, subversion_key = self._get_hg_settings(
540 self.GLOBAL_HG_SETTINGS, data)
540 self.GLOBAL_HG_SETTINGS, data)
541 self._create_or_update_ui(
541 self._create_or_update_ui(
542 self.global_settings, *largefiles, value='',
542 self.global_settings, *largefiles, value='',
543 active=data[largefiles_key])
543 active=data[largefiles_key])
544 self._create_or_update_ui(
544 self._create_or_update_ui(
545 self.global_settings, *phases, value=safe_str(data[phases_key]))
545 self.global_settings, *phases, value=safe_str(data[phases_key]))
546 self._create_or_update_ui(
546 self._create_or_update_ui(
547 self.global_settings, *subversion, active=data[subversion_key])
547 self.global_settings, *subversion, active=data[subversion_key])
548
548
549 def update_global_ssl_setting(self, value):
549 def update_global_ssl_setting(self, value):
550 self._create_or_update_ui(
550 self._create_or_update_ui(
551 self.global_settings, *self.SSL_SETTING, value=value)
551 self.global_settings, *self.SSL_SETTING, value=value)
552
552
553 def update_global_path_setting(self, value):
553 def update_global_path_setting(self, value):
554 self._create_or_update_ui(
554 self._create_or_update_ui(
555 self.global_settings, *self.PATH_SETTING, value=value)
555 self.global_settings, *self.PATH_SETTING, value=value)
556
556
557 @assert_repo_settings
557 @assert_repo_settings
558 def delete_repo_svn_pattern(self, id_):
558 def delete_repo_svn_pattern(self, id_):
559 self.repo_settings.delete_ui(id_)
559 self.repo_settings.delete_ui(id_)
560
560
561 def delete_global_svn_pattern(self, id_):
561 def delete_global_svn_pattern(self, id_):
562 self.global_settings.delete_ui(id_)
562 self.global_settings.delete_ui(id_)
563
563
564 @assert_repo_settings
564 @assert_repo_settings
565 def get_repo_ui_settings(self, section=None, key=None):
565 def get_repo_ui_settings(self, section=None, key=None):
566 global_uis = self.global_settings.get_ui(section, key)
566 global_uis = self.global_settings.get_ui(section, key)
567 repo_uis = self.repo_settings.get_ui(section, key)
567 repo_uis = self.repo_settings.get_ui(section, key)
568 filtered_repo_uis = self._filter_ui_settings(repo_uis)
568 filtered_repo_uis = self._filter_ui_settings(repo_uis)
569 filtered_repo_uis_keys = [
569 filtered_repo_uis_keys = [
570 (s.section, s.key) for s in filtered_repo_uis]
570 (s.section, s.key) for s in filtered_repo_uis]
571
571
572 def _is_global_ui_filtered(ui):
572 def _is_global_ui_filtered(ui):
573 return (
573 return (
574 (ui.section, ui.key) in filtered_repo_uis_keys
574 (ui.section, ui.key) in filtered_repo_uis_keys
575 or ui.section in self._svn_sections)
575 or ui.section in self._svn_sections)
576
576
577 filtered_global_uis = [
577 filtered_global_uis = [
578 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
578 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
579
579
580 return filtered_global_uis + filtered_repo_uis
580 return filtered_global_uis + filtered_repo_uis
581
581
582 def get_global_ui_settings(self, section=None, key=None):
582 def get_global_ui_settings(self, section=None, key=None):
583 return self.global_settings.get_ui(section, key)
583 return self.global_settings.get_ui(section, key)
584
584
585 def get_ui_settings(self, section=None, key=None):
585 def get_ui_settings(self, section=None, key=None):
586 if not self.repo_settings or self.inherit_global_settings:
586 if not self.repo_settings or self.inherit_global_settings:
587 return self.get_global_ui_settings(section, key)
587 return self.get_global_ui_settings(section, key)
588 else:
588 else:
589 return self.get_repo_ui_settings(section, key)
589 return self.get_repo_ui_settings(section, key)
590
590
591 def get_svn_patterns(self, section=None):
591 def get_svn_patterns(self, section=None):
592 if not self.repo_settings:
592 if not self.repo_settings:
593 return self.get_global_ui_settings(section)
593 return self.get_global_ui_settings(section)
594 else:
594 else:
595 return self.get_repo_ui_settings(section)
595 return self.get_repo_ui_settings(section)
596
596
597 @assert_repo_settings
597 @assert_repo_settings
598 def get_repo_general_settings(self):
598 def get_repo_general_settings(self):
599 global_settings = self.global_settings.get_all_settings()
599 global_settings = self.global_settings.get_all_settings()
600 repo_settings = self.repo_settings.get_all_settings()
600 repo_settings = self.repo_settings.get_all_settings()
601 filtered_repo_settings = self._filter_general_settings(repo_settings)
601 filtered_repo_settings = self._filter_general_settings(repo_settings)
602 global_settings.update(filtered_repo_settings)
602 global_settings.update(filtered_repo_settings)
603 return global_settings
603 return global_settings
604
604
605 def get_global_general_settings(self):
605 def get_global_general_settings(self):
606 return self.global_settings.get_all_settings()
606 return self.global_settings.get_all_settings()
607
607
608 def get_general_settings(self):
608 def get_general_settings(self):
609 if not self.repo_settings or self.inherit_global_settings:
609 if not self.repo_settings or self.inherit_global_settings:
610 return self.get_global_general_settings()
610 return self.get_global_general_settings()
611 else:
611 else:
612 return self.get_repo_general_settings()
612 return self.get_repo_general_settings()
613
613
614 def get_repos_location(self):
614 def get_repos_location(self):
615 return self.global_settings.get_ui_by_key('/').ui_value
615 return self.global_settings.get_ui_by_key('/').ui_value
616
616
617 def _filter_ui_settings(self, settings):
617 def _filter_ui_settings(self, settings):
618 filtered_settings = [
618 filtered_settings = [
619 s for s in settings if self._should_keep_setting(s)]
619 s for s in settings if self._should_keep_setting(s)]
620 return filtered_settings
620 return filtered_settings
621
621
622 def _should_keep_setting(self, setting):
622 def _should_keep_setting(self, setting):
623 keep = (
623 keep = (
624 (setting.section, setting.key) in self._ui_settings or
624 (setting.section, setting.key) in self._ui_settings or
625 setting.section in self._svn_sections)
625 setting.section in self._svn_sections)
626 return keep
626 return keep
627
627
628 def _filter_general_settings(self, settings):
628 def _filter_general_settings(self, settings):
629 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
629 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
630 return {
630 return {
631 k: settings[k]
631 k: settings[k]
632 for k in settings if k in keys}
632 for k in settings if k in keys}
633
633
634 def _collect_all_settings(self, global_=False):
634 def _collect_all_settings(self, global_=False):
635 settings = self.global_settings if global_ else self.repo_settings
635 settings = self.global_settings if global_ else self.repo_settings
636 result = {}
636 result = {}
637
637
638 for section, key in self._ui_settings:
638 for section, key in self._ui_settings:
639 ui = settings.get_ui_by_section_and_key(section, key)
639 ui = settings.get_ui_by_section_and_key(section, key)
640 result_key = self._get_form_ui_key(section, key)
640 result_key = self._get_form_ui_key(section, key)
641 if ui:
641 if ui:
642 if section in ('hooks', 'extensions'):
642 if section in ('hooks', 'extensions'):
643 result[result_key] = ui.ui_active
643 result[result_key] = ui.ui_active
644 else:
644 else:
645 result[result_key] = ui.ui_value
645 result[result_key] = ui.ui_value
646
646
647 for name in self.GENERAL_SETTINGS:
647 for name in self.GENERAL_SETTINGS:
648 setting = settings.get_setting_by_name(name)
648 setting = settings.get_setting_by_name(name)
649 if setting:
649 if setting:
650 result_key = 'rhodecode_{}'.format(name)
650 result_key = 'rhodecode_{}'.format(name)
651 result[result_key] = setting.app_settings_value
651 result[result_key] = setting.app_settings_value
652
652
653 return result
653 return result
654
654
655 def _get_form_ui_key(self, section, key):
655 def _get_form_ui_key(self, section, key):
656 return '{section}_{key}'.format(
656 return '{section}_{key}'.format(
657 section=section, key=key.replace('.', '_'))
657 section=section, key=key.replace('.', '_'))
658
658
659 def _create_or_update_ui(
659 def _create_or_update_ui(
660 self, settings, section, key, value=None, active=None):
660 self, settings, section, key, value=None, active=None):
661 ui = settings.get_ui_by_section_and_key(section, key)
661 ui = settings.get_ui_by_section_and_key(section, key)
662 if not ui:
662 if not ui:
663 active = True if active is None else active
663 active = True if active is None else active
664 settings.create_ui_section_value(
664 settings.create_ui_section_value(
665 section, value, key=key, active=active)
665 section, value, key=key, active=active)
666 else:
666 else:
667 if active is not None:
667 if active is not None:
668 ui.ui_active = active
668 ui.ui_active = active
669 if value is not None:
669 if value is not None:
670 ui.ui_value = value
670 ui.ui_value = value
671 Session().add(ui)
671 Session().add(ui)
672
672
673 def _create_svn_settings(self, settings, data):
673 def _create_svn_settings(self, settings, data):
674 svn_settings = {
674 svn_settings = {
675 'new_svn_branch': self.SVN_BRANCH_SECTION,
675 'new_svn_branch': self.SVN_BRANCH_SECTION,
676 'new_svn_tag': self.SVN_TAG_SECTION
676 'new_svn_tag': self.SVN_TAG_SECTION
677 }
677 }
678 for key in svn_settings:
678 for key in svn_settings:
679 if data.get(key):
679 if data.get(key):
680 settings.create_ui_section_value(svn_settings[key], data[key])
680 settings.create_ui_section_value(svn_settings[key], data[key])
681
681
682 def _create_or_update_general_settings(self, settings, data):
682 def _create_or_update_general_settings(self, settings, data):
683 for name in self.GENERAL_SETTINGS:
683 for name in self.GENERAL_SETTINGS:
684 data_key = 'rhodecode_{}'.format(name)
684 data_key = 'rhodecode_{}'.format(name)
685 if data_key not in data:
685 if data_key not in data:
686 raise ValueError(
686 raise ValueError(
687 'The given data does not contain {} key'.format(data_key))
687 'The given data does not contain {} key'.format(data_key))
688 setting = settings.create_or_update_setting(
688 setting = settings.create_or_update_setting(
689 name, data[data_key], 'bool')
689 name, data[data_key], 'bool')
690 Session().add(setting)
690 Session().add(setting)
691
691
692 def _get_hg_settings(self, settings, data):
692 def _get_hg_settings(self, settings, data):
693 data_keys = [self._get_form_ui_key(*s) for s in settings]
693 data_keys = [self._get_form_ui_key(*s) for s in settings]
694 for data_key in data_keys:
694 for data_key in data_keys:
695 if data_key not in data:
695 if data_key not in data:
696 raise ValueError(
696 raise ValueError(
697 'The given data does not contain {} key'.format(data_key))
697 'The given data does not contain {} key'.format(data_key))
698 return data_keys
698 return data_keys
@@ -1,201 +1,201 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 pytest
21 import pytest
22
22
23 from rhodecode.tests import assert_session_flash
23 from rhodecode.tests import assert_session_flash
24 from rhodecode.tests.utils import AssertResponse
24 from rhodecode.tests.utils import AssertResponse
25 from rhodecode.model.db import Session
25 from rhodecode.model.db import Session
26 from rhodecode.model.settings import SettingsModel
26 from rhodecode.model.settings import SettingsModel
27
27
28
28
29 def assert_auth_settings_updated(response):
29 def assert_auth_settings_updated(response):
30 assert response.status_int == 302, 'Expected response HTTP Found 302'
30 assert response.status_int == 302, 'Expected response HTTP Found 302'
31 assert_session_flash(response, 'Auth settings updated successfully')
31 assert_session_flash(response, 'Auth settings updated successfully')
32
32
33
33
34 @pytest.mark.usefixtures("autologin_user", "app")
34 @pytest.mark.usefixtures("autologin_user", "app")
35 class TestAuthSettingsController(object):
35 class TestAuthSettingsController(object):
36
36
37 def _enable_plugins(self, plugins_list, csrf_token, override=None,
37 def _enable_plugins(self, plugins_list, csrf_token, override=None,
38 verify_response=False):
38 verify_response=False):
39 test_url = '/_admin/auth'
39 test_url = '/_admin/auth'
40 params = {
40 params = {
41 'auth_plugins': plugins_list,
41 'auth_plugins': plugins_list,
42 'csrf_token': csrf_token,
42 'csrf_token': csrf_token,
43 }
43 }
44 if override:
44 if override:
45 params.update(override)
45 params.update(override)
46 _enabled_plugins = []
46 _enabled_plugins = []
47 for plugin in plugins_list.split(','):
47 for plugin in plugins_list.split(','):
48 plugin_name = plugin.partition('#')[-1]
48 plugin_name = plugin.partition('#')[-1]
49 enabled_plugin = '%s_enabled' % plugin_name
49 enabled_plugin = '%s_enabled' % plugin_name
50 cache_ttl = '%s_cache_ttl' % plugin_name
50 cache_ttl = '%s_cache_ttl' % plugin_name
51
51
52 # default params that are needed for each plugin,
52 # default params that are needed for each plugin,
53 # `enabled` and `cache_ttl`
53 # `enabled` and `cache_ttl`
54 params.update({
54 params.update({
55 enabled_plugin: True,
55 enabled_plugin: True,
56 cache_ttl: 0
56 cache_ttl: 0
57 })
57 })
58 _enabled_plugins.append(enabled_plugin)
58 _enabled_plugins.append(enabled_plugin)
59
59
60 # we need to clean any enabled plugin before, since they require
60 # we need to clean any enabled plugin before, since they require
61 # form params to be present
61 # form params to be present
62 db_plugin = SettingsModel().get_setting_by_name('auth_plugins')
62 db_plugin = SettingsModel().get_setting_by_name('auth_plugins')
63 db_plugin.app_settings_value = \
63 db_plugin.app_settings_value = \
64 'egg:rhodecode-enterprise-ce#rhodecode'
64 'egg:rhodecode-enterprise-ce#rhodecode'
65 Session().add(db_plugin)
65 Session().add(db_plugin)
66 Session().commit()
66 Session().commit()
67 for _plugin in _enabled_plugins:
67 for _plugin in _enabled_plugins:
68 db_plugin = SettingsModel().get_setting_by_name(_plugin)
68 db_plugin = SettingsModel().get_setting_by_name(_plugin)
69 if db_plugin:
69 if db_plugin:
70 Session.delete(db_plugin)
70 Session().delete(db_plugin)
71 Session().commit()
71 Session().commit()
72
72
73 response = self.app.post(url=test_url, params=params)
73 response = self.app.post(url=test_url, params=params)
74
74
75 if verify_response:
75 if verify_response:
76 assert_auth_settings_updated(response)
76 assert_auth_settings_updated(response)
77 return params
77 return params
78
78
79 def _post_ldap_settings(self, params, override=None, force=False):
79 def _post_ldap_settings(self, params, override=None, force=False):
80
80
81 params.update({
81 params.update({
82 'filter': 'user',
82 'filter': 'user',
83 'user_member_of': '',
83 'user_member_of': '',
84 'user_search_base': '',
84 'user_search_base': '',
85 'user_search_filter': 'test_filter',
85 'user_search_filter': 'test_filter',
86
86
87 'host': 'dc.example.com',
87 'host': 'dc.example.com',
88 'port': '999',
88 'port': '999',
89 'tls_kind': 'PLAIN',
89 'tls_kind': 'PLAIN',
90 'tls_reqcert': 'NEVER',
90 'tls_reqcert': 'NEVER',
91
91
92 'dn_user': 'test_user',
92 'dn_user': 'test_user',
93 'dn_pass': 'test_pass',
93 'dn_pass': 'test_pass',
94 'base_dn': 'test_base_dn',
94 'base_dn': 'test_base_dn',
95 'search_scope': 'BASE',
95 'search_scope': 'BASE',
96 'attr_login': 'test_attr_login',
96 'attr_login': 'test_attr_login',
97 'attr_firstname': 'ima',
97 'attr_firstname': 'ima',
98 'attr_lastname': 'tester',
98 'attr_lastname': 'tester',
99 'attr_email': 'test@example.com',
99 'attr_email': 'test@example.com',
100 'cache_ttl': '0',
100 'cache_ttl': '0',
101 })
101 })
102 if force:
102 if force:
103 params = {}
103 params = {}
104 params.update(override or {})
104 params.update(override or {})
105
105
106 test_url = '/_admin/auth/ldap/'
106 test_url = '/_admin/auth/ldap/'
107
107
108 response = self.app.post(url=test_url, params=params)
108 response = self.app.post(url=test_url, params=params)
109 return response
109 return response
110
110
111 def test_index(self):
111 def test_index(self):
112 response = self.app.get('/_admin/auth')
112 response = self.app.get('/_admin/auth')
113 response.mustcontain('Authentication Plugins')
113 response.mustcontain('Authentication Plugins')
114
114
115 @pytest.mark.parametrize("disable_plugin, needs_import", [
115 @pytest.mark.parametrize("disable_plugin, needs_import", [
116 ('egg:rhodecode-enterprise-ce#headers', None),
116 ('egg:rhodecode-enterprise-ce#headers', None),
117 ('egg:rhodecode-enterprise-ce#crowd', None),
117 ('egg:rhodecode-enterprise-ce#crowd', None),
118 ('egg:rhodecode-enterprise-ce#jasig_cas', None),
118 ('egg:rhodecode-enterprise-ce#jasig_cas', None),
119 ('egg:rhodecode-enterprise-ce#ldap', None),
119 ('egg:rhodecode-enterprise-ce#ldap', None),
120 ('egg:rhodecode-enterprise-ce#pam', "pam"),
120 ('egg:rhodecode-enterprise-ce#pam', "pam"),
121 ])
121 ])
122 def test_disable_plugin(self, csrf_token, disable_plugin, needs_import):
122 def test_disable_plugin(self, csrf_token, disable_plugin, needs_import):
123 # TODO: johbo: "pam" is currently not available on darwin,
123 # TODO: johbo: "pam" is currently not available on darwin,
124 # although the docs state that it should work on darwin.
124 # although the docs state that it should work on darwin.
125 if needs_import:
125 if needs_import:
126 pytest.importorskip(needs_import)
126 pytest.importorskip(needs_import)
127
127
128 self._enable_plugins(
128 self._enable_plugins(
129 'egg:rhodecode-enterprise-ce#rhodecode,' + disable_plugin,
129 'egg:rhodecode-enterprise-ce#rhodecode,' + disable_plugin,
130 csrf_token, verify_response=True)
130 csrf_token, verify_response=True)
131
131
132 self._enable_plugins(
132 self._enable_plugins(
133 'egg:rhodecode-enterprise-ce#rhodecode', csrf_token,
133 'egg:rhodecode-enterprise-ce#rhodecode', csrf_token,
134 verify_response=True)
134 verify_response=True)
135
135
136 def test_ldap_save_settings(self, csrf_token):
136 def test_ldap_save_settings(self, csrf_token):
137 params = self._enable_plugins(
137 params = self._enable_plugins(
138 'egg:rhodecode-enterprise-ce#rhodecode,'
138 'egg:rhodecode-enterprise-ce#rhodecode,'
139 'egg:rhodecode-enterprise-ce#ldap',
139 'egg:rhodecode-enterprise-ce#ldap',
140 csrf_token)
140 csrf_token)
141 response = self._post_ldap_settings(params)
141 response = self._post_ldap_settings(params)
142 assert_auth_settings_updated(response)
142 assert_auth_settings_updated(response)
143
143
144 new_settings = SettingsModel().get_auth_settings()
144 new_settings = SettingsModel().get_auth_settings()
145 assert new_settings['auth_ldap_host'] == u'dc.example.com', \
145 assert new_settings['auth_ldap_host'] == u'dc.example.com', \
146 'fail db write compare'
146 'fail db write compare'
147
147
148 def test_ldap_error_form_wrong_port_number(self, csrf_token):
148 def test_ldap_error_form_wrong_port_number(self, csrf_token):
149 params = self._enable_plugins(
149 params = self._enable_plugins(
150 'egg:rhodecode-enterprise-ce#rhodecode,'
150 'egg:rhodecode-enterprise-ce#rhodecode,'
151 'egg:rhodecode-enterprise-ce#ldap',
151 'egg:rhodecode-enterprise-ce#ldap',
152 csrf_token)
152 csrf_token)
153 invalid_port_value = 'invalid-port-number'
153 invalid_port_value = 'invalid-port-number'
154 response = self._post_ldap_settings(params, override={
154 response = self._post_ldap_settings(params, override={
155 'port': invalid_port_value,
155 'port': invalid_port_value,
156 })
156 })
157 assertr = AssertResponse(response)
157 assertr = AssertResponse(response)
158 assertr.element_contains(
158 assertr.element_contains(
159 '.form .field #port ~ .error-message',
159 '.form .field #port ~ .error-message',
160 invalid_port_value)
160 invalid_port_value)
161
161
162 def test_ldap_error_form(self, csrf_token):
162 def test_ldap_error_form(self, csrf_token):
163 params = self._enable_plugins(
163 params = self._enable_plugins(
164 'egg:rhodecode-enterprise-ce#rhodecode,'
164 'egg:rhodecode-enterprise-ce#rhodecode,'
165 'egg:rhodecode-enterprise-ce#ldap',
165 'egg:rhodecode-enterprise-ce#ldap',
166 csrf_token)
166 csrf_token)
167 response = self._post_ldap_settings(params, override={
167 response = self._post_ldap_settings(params, override={
168 'attr_login': '',
168 'attr_login': '',
169 })
169 })
170 response.mustcontain("""<span class="error-message">The LDAP Login"""
170 response.mustcontain("""<span class="error-message">The LDAP Login"""
171 """ attribute of the CN must be specified""")
171 """ attribute of the CN must be specified""")
172
172
173 def test_post_ldap_group_settings(self, csrf_token):
173 def test_post_ldap_group_settings(self, csrf_token):
174 params = self._enable_plugins(
174 params = self._enable_plugins(
175 'egg:rhodecode-enterprise-ce#rhodecode,'
175 'egg:rhodecode-enterprise-ce#rhodecode,'
176 'egg:rhodecode-enterprise-ce#ldap',
176 'egg:rhodecode-enterprise-ce#ldap',
177 csrf_token)
177 csrf_token)
178
178
179 response = self._post_ldap_settings(params, override={
179 response = self._post_ldap_settings(params, override={
180 'host': 'dc-legacy.example.com',
180 'host': 'dc-legacy.example.com',
181 'port': '999',
181 'port': '999',
182 'tls_kind': 'PLAIN',
182 'tls_kind': 'PLAIN',
183 'tls_reqcert': 'NEVER',
183 'tls_reqcert': 'NEVER',
184 'dn_user': 'test_user',
184 'dn_user': 'test_user',
185 'dn_pass': 'test_pass',
185 'dn_pass': 'test_pass',
186 'base_dn': 'test_base_dn',
186 'base_dn': 'test_base_dn',
187 'filter': 'test_filter',
187 'filter': 'test_filter',
188 'search_scope': 'BASE',
188 'search_scope': 'BASE',
189 'attr_login': 'test_attr_login',
189 'attr_login': 'test_attr_login',
190 'attr_firstname': 'ima',
190 'attr_firstname': 'ima',
191 'attr_lastname': 'tester',
191 'attr_lastname': 'tester',
192 'attr_email': 'test@example.com',
192 'attr_email': 'test@example.com',
193 'cache_ttl': '60',
193 'cache_ttl': '60',
194 'csrf_token': csrf_token,
194 'csrf_token': csrf_token,
195 }
195 }
196 )
196 )
197 assert_auth_settings_updated(response)
197 assert_auth_settings_updated(response)
198
198
199 new_settings = SettingsModel().get_auth_settings()
199 new_settings = SettingsModel().get_auth_settings()
200 assert new_settings['auth_ldap_host'] == u'dc-legacy.example.com', \
200 assert new_settings['auth_ldap_host'] == u'dc-legacy.example.com', \
201 'fail db write compare'
201 'fail db write compare'
@@ -1,1622 +1,1622 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 collections
21 import collections
22 import datetime
22 import datetime
23 import hashlib
23 import hashlib
24 import os
24 import os
25 import re
25 import re
26 import pprint
26 import pprint
27 import shutil
27 import shutil
28 import socket
28 import socket
29 import subprocess
29 import subprocess
30 import time
30 import time
31 import uuid
31 import uuid
32
32
33 import mock
33 import mock
34 import pyramid.testing
34 import pyramid.testing
35 import pytest
35 import pytest
36 import requests
36 import requests
37 from webtest.app import TestApp
37 from webtest.app import TestApp
38
38
39 import rhodecode
39 import rhodecode
40 from rhodecode.model.changeset_status import ChangesetStatusModel
40 from rhodecode.model.changeset_status import ChangesetStatusModel
41 from rhodecode.model.comment import ChangesetCommentsModel
41 from rhodecode.model.comment import ChangesetCommentsModel
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
43 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
44 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
44 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.pull_request import PullRequestModel
46 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
51 from rhodecode.model.user_group import UserGroupModel
51 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.lib.utils import repo2db_mapper
52 from rhodecode.lib.utils import repo2db_mapper
53 from rhodecode.lib.vcs import create_vcsserver_proxy
53 from rhodecode.lib.vcs import create_vcsserver_proxy
54 from rhodecode.lib.vcs.backends import get_backend
54 from rhodecode.lib.vcs.backends import get_backend
55 from rhodecode.lib.vcs.nodes import FileNode
55 from rhodecode.lib.vcs.nodes import FileNode
56 from rhodecode.tests import (
56 from rhodecode.tests import (
57 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
57 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
58 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
58 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
59 TEST_USER_REGULAR_PASS)
59 TEST_USER_REGULAR_PASS)
60 from rhodecode.tests.fixture import Fixture
60 from rhodecode.tests.fixture import Fixture
61
61
62
62
63 def _split_comma(value):
63 def _split_comma(value):
64 return value.split(',')
64 return value.split(',')
65
65
66
66
67 def pytest_addoption(parser):
67 def pytest_addoption(parser):
68 parser.addoption(
68 parser.addoption(
69 '--keep-tmp-path', action='store_true',
69 '--keep-tmp-path', action='store_true',
70 help="Keep the test temporary directories")
70 help="Keep the test temporary directories")
71 parser.addoption(
71 parser.addoption(
72 '--backends', action='store', type=_split_comma,
72 '--backends', action='store', type=_split_comma,
73 default=['git', 'hg', 'svn'],
73 default=['git', 'hg', 'svn'],
74 help="Select which backends to test for backend specific tests.")
74 help="Select which backends to test for backend specific tests.")
75 parser.addoption(
75 parser.addoption(
76 '--dbs', action='store', type=_split_comma,
76 '--dbs', action='store', type=_split_comma,
77 default=['sqlite'],
77 default=['sqlite'],
78 help="Select which database to test for database specific tests. "
78 help="Select which database to test for database specific tests. "
79 "Possible options are sqlite,postgres,mysql")
79 "Possible options are sqlite,postgres,mysql")
80 parser.addoption(
80 parser.addoption(
81 '--appenlight', '--ae', action='store_true',
81 '--appenlight', '--ae', action='store_true',
82 help="Track statistics in appenlight.")
82 help="Track statistics in appenlight.")
83 parser.addoption(
83 parser.addoption(
84 '--appenlight-api-key', '--ae-key',
84 '--appenlight-api-key', '--ae-key',
85 help="API key for Appenlight.")
85 help="API key for Appenlight.")
86 parser.addoption(
86 parser.addoption(
87 '--appenlight-url', '--ae-url',
87 '--appenlight-url', '--ae-url',
88 default="https://ae.rhodecode.com",
88 default="https://ae.rhodecode.com",
89 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
89 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
90 parser.addoption(
90 parser.addoption(
91 '--sqlite-connection-string', action='store',
91 '--sqlite-connection-string', action='store',
92 default='', help="Connection string for the dbs tests with SQLite")
92 default='', help="Connection string for the dbs tests with SQLite")
93 parser.addoption(
93 parser.addoption(
94 '--postgres-connection-string', action='store',
94 '--postgres-connection-string', action='store',
95 default='', help="Connection string for the dbs tests with Postgres")
95 default='', help="Connection string for the dbs tests with Postgres")
96 parser.addoption(
96 parser.addoption(
97 '--mysql-connection-string', action='store',
97 '--mysql-connection-string', action='store',
98 default='', help="Connection string for the dbs tests with MySQL")
98 default='', help="Connection string for the dbs tests with MySQL")
99 parser.addoption(
99 parser.addoption(
100 '--repeat', type=int, default=100,
100 '--repeat', type=int, default=100,
101 help="Number of repetitions in performance tests.")
101 help="Number of repetitions in performance tests.")
102
102
103
103
104 def pytest_configure(config):
104 def pytest_configure(config):
105 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
105 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
106 from rhodecode.config import patches
106 from rhodecode.config import patches
107 patches.kombu_1_5_1_python_2_7_11()
107 patches.kombu_1_5_1_python_2_7_11()
108
108
109
109
110 def pytest_collection_modifyitems(session, config, items):
110 def pytest_collection_modifyitems(session, config, items):
111 # nottest marked, compare nose, used for transition from nose to pytest
111 # nottest marked, compare nose, used for transition from nose to pytest
112 remaining = [
112 remaining = [
113 i for i in items if getattr(i.obj, '__test__', True)]
113 i for i in items if getattr(i.obj, '__test__', True)]
114 items[:] = remaining
114 items[:] = remaining
115
115
116
116
117 def pytest_generate_tests(metafunc):
117 def pytest_generate_tests(metafunc):
118 # Support test generation based on --backend parameter
118 # Support test generation based on --backend parameter
119 if 'backend_alias' in metafunc.fixturenames:
119 if 'backend_alias' in metafunc.fixturenames:
120 backends = get_backends_from_metafunc(metafunc)
120 backends = get_backends_from_metafunc(metafunc)
121 scope = None
121 scope = None
122 if not backends:
122 if not backends:
123 pytest.skip("Not enabled for any of selected backends")
123 pytest.skip("Not enabled for any of selected backends")
124 metafunc.parametrize('backend_alias', backends, scope=scope)
124 metafunc.parametrize('backend_alias', backends, scope=scope)
125 elif hasattr(metafunc.function, 'backends'):
125 elif hasattr(metafunc.function, 'backends'):
126 backends = get_backends_from_metafunc(metafunc)
126 backends = get_backends_from_metafunc(metafunc)
127 if not backends:
127 if not backends:
128 pytest.skip("Not enabled for any of selected backends")
128 pytest.skip("Not enabled for any of selected backends")
129
129
130
130
131 def get_backends_from_metafunc(metafunc):
131 def get_backends_from_metafunc(metafunc):
132 requested_backends = set(metafunc.config.getoption('--backends'))
132 requested_backends = set(metafunc.config.getoption('--backends'))
133 if hasattr(metafunc.function, 'backends'):
133 if hasattr(metafunc.function, 'backends'):
134 # Supported backends by this test function, created from
134 # Supported backends by this test function, created from
135 # pytest.mark.backends
135 # pytest.mark.backends
136 backends = metafunc.function.backends.args
136 backends = metafunc.function.backends.args
137 elif hasattr(metafunc.cls, 'backend_alias'):
137 elif hasattr(metafunc.cls, 'backend_alias'):
138 # Support class attribute "backend_alias", this is mainly
138 # Support class attribute "backend_alias", this is mainly
139 # for legacy reasons for tests not yet using pytest.mark.backends
139 # for legacy reasons for tests not yet using pytest.mark.backends
140 backends = [metafunc.cls.backend_alias]
140 backends = [metafunc.cls.backend_alias]
141 else:
141 else:
142 backends = metafunc.config.getoption('--backends')
142 backends = metafunc.config.getoption('--backends')
143 return requested_backends.intersection(backends)
143 return requested_backends.intersection(backends)
144
144
145
145
146 @pytest.fixture(scope='session', autouse=True)
146 @pytest.fixture(scope='session', autouse=True)
147 def activate_example_rcextensions(request):
147 def activate_example_rcextensions(request):
148 """
148 """
149 Patch in an example rcextensions module which verifies passed in kwargs.
149 Patch in an example rcextensions module which verifies passed in kwargs.
150 """
150 """
151 from rhodecode.tests.other import example_rcextensions
151 from rhodecode.tests.other import example_rcextensions
152
152
153 old_extensions = rhodecode.EXTENSIONS
153 old_extensions = rhodecode.EXTENSIONS
154 rhodecode.EXTENSIONS = example_rcextensions
154 rhodecode.EXTENSIONS = example_rcextensions
155
155
156 @request.addfinalizer
156 @request.addfinalizer
157 def cleanup():
157 def cleanup():
158 rhodecode.EXTENSIONS = old_extensions
158 rhodecode.EXTENSIONS = old_extensions
159
159
160
160
161 @pytest.fixture
161 @pytest.fixture
162 def capture_rcextensions():
162 def capture_rcextensions():
163 """
163 """
164 Returns the recorded calls to entry points in rcextensions.
164 Returns the recorded calls to entry points in rcextensions.
165 """
165 """
166 calls = rhodecode.EXTENSIONS.calls
166 calls = rhodecode.EXTENSIONS.calls
167 calls.clear()
167 calls.clear()
168 # Note: At this moment, it is still the empty dict, but that will
168 # Note: At this moment, it is still the empty dict, but that will
169 # be filled during the test run and since it is a reference this
169 # be filled during the test run and since it is a reference this
170 # is enough to make it work.
170 # is enough to make it work.
171 return calls
171 return calls
172
172
173
173
174 @pytest.fixture(scope='session')
174 @pytest.fixture(scope='session')
175 def http_environ_session():
175 def http_environ_session():
176 """
176 """
177 Allow to use "http_environ" in session scope.
177 Allow to use "http_environ" in session scope.
178 """
178 """
179 return http_environ(
179 return http_environ(
180 http_host_stub=http_host_stub())
180 http_host_stub=http_host_stub())
181
181
182
182
183 @pytest.fixture
183 @pytest.fixture
184 def http_host_stub():
184 def http_host_stub():
185 """
185 """
186 Value of HTTP_HOST in the test run.
186 Value of HTTP_HOST in the test run.
187 """
187 """
188 return 'test.example.com:80'
188 return 'test.example.com:80'
189
189
190
190
191 @pytest.fixture
191 @pytest.fixture
192 def http_environ(http_host_stub):
192 def http_environ(http_host_stub):
193 """
193 """
194 HTTP extra environ keys.
194 HTTP extra environ keys.
195
195
196 User by the test application and as well for setting up the pylons
196 User by the test application and as well for setting up the pylons
197 environment. In the case of the fixture "app" it should be possible
197 environment. In the case of the fixture "app" it should be possible
198 to override this for a specific test case.
198 to override this for a specific test case.
199 """
199 """
200 return {
200 return {
201 'SERVER_NAME': http_host_stub.split(':')[0],
201 'SERVER_NAME': http_host_stub.split(':')[0],
202 'SERVER_PORT': http_host_stub.split(':')[1],
202 'SERVER_PORT': http_host_stub.split(':')[1],
203 'HTTP_HOST': http_host_stub,
203 'HTTP_HOST': http_host_stub,
204 }
204 }
205
205
206
206
207 @pytest.fixture(scope='function')
207 @pytest.fixture(scope='function')
208 def app(request, pylonsapp, http_environ):
208 def app(request, pylonsapp, http_environ):
209 app = TestApp(
209 app = TestApp(
210 pylonsapp,
210 pylonsapp,
211 extra_environ=http_environ)
211 extra_environ=http_environ)
212 if request.cls:
212 if request.cls:
213 request.cls.app = app
213 request.cls.app = app
214 return app
214 return app
215
215
216
216
217 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
217 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
218
218
219
219
220 def _autologin_user(app, *args):
220 def _autologin_user(app, *args):
221 session = login_user_session(app, *args)
221 session = login_user_session(app, *args)
222 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
222 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
223 return LoginData(csrf_token, session['rhodecode_user'])
223 return LoginData(csrf_token, session['rhodecode_user'])
224
224
225
225
226 @pytest.fixture
226 @pytest.fixture
227 def autologin_user(app):
227 def autologin_user(app):
228 """
228 """
229 Utility fixture which makes sure that the admin user is logged in
229 Utility fixture which makes sure that the admin user is logged in
230 """
230 """
231 return _autologin_user(app)
231 return _autologin_user(app)
232
232
233
233
234 @pytest.fixture
234 @pytest.fixture
235 def autologin_regular_user(app):
235 def autologin_regular_user(app):
236 """
236 """
237 Utility fixture which makes sure that the regular user is logged in
237 Utility fixture which makes sure that the regular user is logged in
238 """
238 """
239 return _autologin_user(
239 return _autologin_user(
240 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
240 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
241
241
242
242
243 @pytest.fixture(scope='function')
243 @pytest.fixture(scope='function')
244 def csrf_token(request, autologin_user):
244 def csrf_token(request, autologin_user):
245 return autologin_user.csrf_token
245 return autologin_user.csrf_token
246
246
247
247
248 @pytest.fixture(scope='function')
248 @pytest.fixture(scope='function')
249 def xhr_header(request):
249 def xhr_header(request):
250 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
250 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
251
251
252
252
253 @pytest.fixture
253 @pytest.fixture
254 def real_crypto_backend(monkeypatch):
254 def real_crypto_backend(monkeypatch):
255 """
255 """
256 Switch the production crypto backend on for this test.
256 Switch the production crypto backend on for this test.
257
257
258 During the test run the crypto backend is replaced with a faster
258 During the test run the crypto backend is replaced with a faster
259 implementation based on the MD5 algorithm.
259 implementation based on the MD5 algorithm.
260 """
260 """
261 monkeypatch.setattr(rhodecode, 'is_test', False)
261 monkeypatch.setattr(rhodecode, 'is_test', False)
262
262
263
263
264 @pytest.fixture(scope='class')
264 @pytest.fixture(scope='class')
265 def index_location(request, pylonsapp):
265 def index_location(request, pylonsapp):
266 index_location = pylonsapp.config['app_conf']['search.location']
266 index_location = pylonsapp.config['app_conf']['search.location']
267 if request.cls:
267 if request.cls:
268 request.cls.index_location = index_location
268 request.cls.index_location = index_location
269 return index_location
269 return index_location
270
270
271
271
272 @pytest.fixture(scope='session', autouse=True)
272 @pytest.fixture(scope='session', autouse=True)
273 def tests_tmp_path(request):
273 def tests_tmp_path(request):
274 """
274 """
275 Create temporary directory to be used during the test session.
275 Create temporary directory to be used during the test session.
276 """
276 """
277 if not os.path.exists(TESTS_TMP_PATH):
277 if not os.path.exists(TESTS_TMP_PATH):
278 os.makedirs(TESTS_TMP_PATH)
278 os.makedirs(TESTS_TMP_PATH)
279
279
280 if not request.config.getoption('--keep-tmp-path'):
280 if not request.config.getoption('--keep-tmp-path'):
281 @request.addfinalizer
281 @request.addfinalizer
282 def remove_tmp_path():
282 def remove_tmp_path():
283 shutil.rmtree(TESTS_TMP_PATH)
283 shutil.rmtree(TESTS_TMP_PATH)
284
284
285 return TESTS_TMP_PATH
285 return TESTS_TMP_PATH
286
286
287
287
288 @pytest.fixture(scope='session', autouse=True)
288 @pytest.fixture(scope='session', autouse=True)
289 def patch_pyro_request_scope_proxy_factory(request):
289 def patch_pyro_request_scope_proxy_factory(request):
290 """
290 """
291 Patch the pyro proxy factory to always use the same dummy request object
291 Patch the pyro proxy factory to always use the same dummy request object
292 when under test. This will return the same pyro proxy on every call.
292 when under test. This will return the same pyro proxy on every call.
293 """
293 """
294 dummy_request = pyramid.testing.DummyRequest()
294 dummy_request = pyramid.testing.DummyRequest()
295
295
296 def mocked_call(self, request=None):
296 def mocked_call(self, request=None):
297 return self.getProxy(request=dummy_request)
297 return self.getProxy(request=dummy_request)
298
298
299 patcher = mock.patch(
299 patcher = mock.patch(
300 'rhodecode.lib.vcs.client.RequestScopeProxyFactory.__call__',
300 'rhodecode.lib.vcs.client.RequestScopeProxyFactory.__call__',
301 new=mocked_call)
301 new=mocked_call)
302 patcher.start()
302 patcher.start()
303
303
304 @request.addfinalizer
304 @request.addfinalizer
305 def undo_patching():
305 def undo_patching():
306 patcher.stop()
306 patcher.stop()
307
307
308
308
309 @pytest.fixture
309 @pytest.fixture
310 def test_repo_group(request):
310 def test_repo_group(request):
311 """
311 """
312 Create a temporary repository group, and destroy it after
312 Create a temporary repository group, and destroy it after
313 usage automatically
313 usage automatically
314 """
314 """
315 fixture = Fixture()
315 fixture = Fixture()
316 repogroupid = 'test_repo_group_%s' % int(time.time())
316 repogroupid = 'test_repo_group_%s' % int(time.time())
317 repo_group = fixture.create_repo_group(repogroupid)
317 repo_group = fixture.create_repo_group(repogroupid)
318
318
319 def _cleanup():
319 def _cleanup():
320 fixture.destroy_repo_group(repogroupid)
320 fixture.destroy_repo_group(repogroupid)
321
321
322 request.addfinalizer(_cleanup)
322 request.addfinalizer(_cleanup)
323 return repo_group
323 return repo_group
324
324
325
325
326 @pytest.fixture
326 @pytest.fixture
327 def test_user_group(request):
327 def test_user_group(request):
328 """
328 """
329 Create a temporary user group, and destroy it after
329 Create a temporary user group, and destroy it after
330 usage automatically
330 usage automatically
331 """
331 """
332 fixture = Fixture()
332 fixture = Fixture()
333 usergroupid = 'test_user_group_%s' % int(time.time())
333 usergroupid = 'test_user_group_%s' % int(time.time())
334 user_group = fixture.create_user_group(usergroupid)
334 user_group = fixture.create_user_group(usergroupid)
335
335
336 def _cleanup():
336 def _cleanup():
337 fixture.destroy_user_group(user_group)
337 fixture.destroy_user_group(user_group)
338
338
339 request.addfinalizer(_cleanup)
339 request.addfinalizer(_cleanup)
340 return user_group
340 return user_group
341
341
342
342
343 @pytest.fixture(scope='session')
343 @pytest.fixture(scope='session')
344 def test_repo(request):
344 def test_repo(request):
345 container = TestRepoContainer()
345 container = TestRepoContainer()
346 request.addfinalizer(container._cleanup)
346 request.addfinalizer(container._cleanup)
347 return container
347 return container
348
348
349
349
350 class TestRepoContainer(object):
350 class TestRepoContainer(object):
351 """
351 """
352 Container for test repositories which are used read only.
352 Container for test repositories which are used read only.
353
353
354 Repositories will be created on demand and re-used during the lifetime
354 Repositories will be created on demand and re-used during the lifetime
355 of this object.
355 of this object.
356
356
357 Usage to get the svn test repository "minimal"::
357 Usage to get the svn test repository "minimal"::
358
358
359 test_repo = TestContainer()
359 test_repo = TestContainer()
360 repo = test_repo('minimal', 'svn')
360 repo = test_repo('minimal', 'svn')
361
361
362 """
362 """
363
363
364 dump_extractors = {
364 dump_extractors = {
365 'git': utils.extract_git_repo_from_dump,
365 'git': utils.extract_git_repo_from_dump,
366 'hg': utils.extract_hg_repo_from_dump,
366 'hg': utils.extract_hg_repo_from_dump,
367 'svn': utils.extract_svn_repo_from_dump,
367 'svn': utils.extract_svn_repo_from_dump,
368 }
368 }
369
369
370 def __init__(self):
370 def __init__(self):
371 self._cleanup_repos = []
371 self._cleanup_repos = []
372 self._fixture = Fixture()
372 self._fixture = Fixture()
373 self._repos = {}
373 self._repos = {}
374
374
375 def __call__(self, dump_name, backend_alias):
375 def __call__(self, dump_name, backend_alias):
376 key = (dump_name, backend_alias)
376 key = (dump_name, backend_alias)
377 if key not in self._repos:
377 if key not in self._repos:
378 repo = self._create_repo(dump_name, backend_alias)
378 repo = self._create_repo(dump_name, backend_alias)
379 self._repos[key] = repo.repo_id
379 self._repos[key] = repo.repo_id
380 return Repository.get(self._repos[key])
380 return Repository.get(self._repos[key])
381
381
382 def _create_repo(self, dump_name, backend_alias):
382 def _create_repo(self, dump_name, backend_alias):
383 repo_name = '%s-%s' % (backend_alias, dump_name)
383 repo_name = '%s-%s' % (backend_alias, dump_name)
384 backend_class = get_backend(backend_alias)
384 backend_class = get_backend(backend_alias)
385 dump_extractor = self.dump_extractors[backend_alias]
385 dump_extractor = self.dump_extractors[backend_alias]
386 repo_path = dump_extractor(dump_name, repo_name)
386 repo_path = dump_extractor(dump_name, repo_name)
387 vcs_repo = backend_class(repo_path)
387 vcs_repo = backend_class(repo_path)
388 repo2db_mapper({repo_name: vcs_repo})
388 repo2db_mapper({repo_name: vcs_repo})
389 repo = RepoModel().get_by_repo_name(repo_name)
389 repo = RepoModel().get_by_repo_name(repo_name)
390 self._cleanup_repos.append(repo_name)
390 self._cleanup_repos.append(repo_name)
391 return repo
391 return repo
392
392
393 def _cleanup(self):
393 def _cleanup(self):
394 for repo_name in reversed(self._cleanup_repos):
394 for repo_name in reversed(self._cleanup_repos):
395 self._fixture.destroy_repo(repo_name)
395 self._fixture.destroy_repo(repo_name)
396
396
397
397
398 @pytest.fixture
398 @pytest.fixture
399 def backend(request, backend_alias, pylonsapp, test_repo):
399 def backend(request, backend_alias, pylonsapp, test_repo):
400 """
400 """
401 Parametrized fixture which represents a single backend implementation.
401 Parametrized fixture which represents a single backend implementation.
402
402
403 It respects the option `--backends` to focus the test run on specific
403 It respects the option `--backends` to focus the test run on specific
404 backend implementations.
404 backend implementations.
405
405
406 It also supports `pytest.mark.xfail_backends` to mark tests as failing
406 It also supports `pytest.mark.xfail_backends` to mark tests as failing
407 for specific backends. This is intended as a utility for incremental
407 for specific backends. This is intended as a utility for incremental
408 development of a new backend implementation.
408 development of a new backend implementation.
409 """
409 """
410 if backend_alias not in request.config.getoption('--backends'):
410 if backend_alias not in request.config.getoption('--backends'):
411 pytest.skip("Backend %s not selected." % (backend_alias, ))
411 pytest.skip("Backend %s not selected." % (backend_alias, ))
412
412
413 utils.check_xfail_backends(request.node, backend_alias)
413 utils.check_xfail_backends(request.node, backend_alias)
414 utils.check_skip_backends(request.node, backend_alias)
414 utils.check_skip_backends(request.node, backend_alias)
415
415
416 repo_name = 'vcs_test_%s' % (backend_alias, )
416 repo_name = 'vcs_test_%s' % (backend_alias, )
417 backend = Backend(
417 backend = Backend(
418 alias=backend_alias,
418 alias=backend_alias,
419 repo_name=repo_name,
419 repo_name=repo_name,
420 test_name=request.node.name,
420 test_name=request.node.name,
421 test_repo_container=test_repo)
421 test_repo_container=test_repo)
422 request.addfinalizer(backend.cleanup)
422 request.addfinalizer(backend.cleanup)
423 return backend
423 return backend
424
424
425
425
426 @pytest.fixture
426 @pytest.fixture
427 def backend_git(request, pylonsapp, test_repo):
427 def backend_git(request, pylonsapp, test_repo):
428 return backend(request, 'git', pylonsapp, test_repo)
428 return backend(request, 'git', pylonsapp, test_repo)
429
429
430
430
431 @pytest.fixture
431 @pytest.fixture
432 def backend_hg(request, pylonsapp, test_repo):
432 def backend_hg(request, pylonsapp, test_repo):
433 return backend(request, 'hg', pylonsapp, test_repo)
433 return backend(request, 'hg', pylonsapp, test_repo)
434
434
435
435
436 @pytest.fixture
436 @pytest.fixture
437 def backend_svn(request, pylonsapp, test_repo):
437 def backend_svn(request, pylonsapp, test_repo):
438 return backend(request, 'svn', pylonsapp, test_repo)
438 return backend(request, 'svn', pylonsapp, test_repo)
439
439
440
440
441 @pytest.fixture
441 @pytest.fixture
442 def backend_random(backend_git):
442 def backend_random(backend_git):
443 """
443 """
444 Use this to express that your tests need "a backend.
444 Use this to express that your tests need "a backend.
445
445
446 A few of our tests need a backend, so that we can run the code. This
446 A few of our tests need a backend, so that we can run the code. This
447 fixture is intended to be used for such cases. It will pick one of the
447 fixture is intended to be used for such cases. It will pick one of the
448 backends and run the tests.
448 backends and run the tests.
449
449
450 The fixture `backend` would run the test multiple times for each
450 The fixture `backend` would run the test multiple times for each
451 available backend which is a pure waste of time if the test is
451 available backend which is a pure waste of time if the test is
452 independent of the backend type.
452 independent of the backend type.
453 """
453 """
454 # TODO: johbo: Change this to pick a random backend
454 # TODO: johbo: Change this to pick a random backend
455 return backend_git
455 return backend_git
456
456
457
457
458 @pytest.fixture
458 @pytest.fixture
459 def backend_stub(backend_git):
459 def backend_stub(backend_git):
460 """
460 """
461 Use this to express that your tests need a backend stub
461 Use this to express that your tests need a backend stub
462
462
463 TODO: mikhail: Implement a real stub logic instead of returning
463 TODO: mikhail: Implement a real stub logic instead of returning
464 a git backend
464 a git backend
465 """
465 """
466 return backend_git
466 return backend_git
467
467
468
468
469 @pytest.fixture
469 @pytest.fixture
470 def repo_stub(backend_stub):
470 def repo_stub(backend_stub):
471 """
471 """
472 Use this to express that your tests need a repository stub
472 Use this to express that your tests need a repository stub
473 """
473 """
474 return backend_stub.create_repo()
474 return backend_stub.create_repo()
475
475
476
476
477 class Backend(object):
477 class Backend(object):
478 """
478 """
479 Represents the test configuration for one supported backend
479 Represents the test configuration for one supported backend
480
480
481 Provides easy access to different test repositories based on
481 Provides easy access to different test repositories based on
482 `__getitem__`. Such repositories will only be created once per test
482 `__getitem__`. Such repositories will only be created once per test
483 session.
483 session.
484 """
484 """
485
485
486 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
486 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
487 _master_repo = None
487 _master_repo = None
488 _commit_ids = {}
488 _commit_ids = {}
489
489
490 def __init__(self, alias, repo_name, test_name, test_repo_container):
490 def __init__(self, alias, repo_name, test_name, test_repo_container):
491 self.alias = alias
491 self.alias = alias
492 self.repo_name = repo_name
492 self.repo_name = repo_name
493 self._cleanup_repos = []
493 self._cleanup_repos = []
494 self._test_name = test_name
494 self._test_name = test_name
495 self._test_repo_container = test_repo_container
495 self._test_repo_container = test_repo_container
496 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
496 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
497 # Fixture will survive in the end.
497 # Fixture will survive in the end.
498 self._fixture = Fixture()
498 self._fixture = Fixture()
499
499
500 def __getitem__(self, key):
500 def __getitem__(self, key):
501 return self._test_repo_container(key, self.alias)
501 return self._test_repo_container(key, self.alias)
502
502
503 @property
503 @property
504 def repo(self):
504 def repo(self):
505 """
505 """
506 Returns the "current" repository. This is the vcs_test repo or the
506 Returns the "current" repository. This is the vcs_test repo or the
507 last repo which has been created with `create_repo`.
507 last repo which has been created with `create_repo`.
508 """
508 """
509 from rhodecode.model.db import Repository
509 from rhodecode.model.db import Repository
510 return Repository.get_by_repo_name(self.repo_name)
510 return Repository.get_by_repo_name(self.repo_name)
511
511
512 @property
512 @property
513 def default_branch_name(self):
513 def default_branch_name(self):
514 VcsRepository = get_backend(self.alias)
514 VcsRepository = get_backend(self.alias)
515 return VcsRepository.DEFAULT_BRANCH_NAME
515 return VcsRepository.DEFAULT_BRANCH_NAME
516
516
517 @property
517 @property
518 def default_head_id(self):
518 def default_head_id(self):
519 """
519 """
520 Returns the default head id of the underlying backend.
520 Returns the default head id of the underlying backend.
521
521
522 This will be the default branch name in case the backend does have a
522 This will be the default branch name in case the backend does have a
523 default branch. In the other cases it will point to a valid head
523 default branch. In the other cases it will point to a valid head
524 which can serve as the base to create a new commit on top of it.
524 which can serve as the base to create a new commit on top of it.
525 """
525 """
526 vcsrepo = self.repo.scm_instance()
526 vcsrepo = self.repo.scm_instance()
527 head_id = (
527 head_id = (
528 vcsrepo.DEFAULT_BRANCH_NAME or
528 vcsrepo.DEFAULT_BRANCH_NAME or
529 vcsrepo.commit_ids[-1])
529 vcsrepo.commit_ids[-1])
530 return head_id
530 return head_id
531
531
532 @property
532 @property
533 def commit_ids(self):
533 def commit_ids(self):
534 """
534 """
535 Returns the list of commits for the last created repository
535 Returns the list of commits for the last created repository
536 """
536 """
537 return self._commit_ids
537 return self._commit_ids
538
538
539 def create_master_repo(self, commits):
539 def create_master_repo(self, commits):
540 """
540 """
541 Create a repository and remember it as a template.
541 Create a repository and remember it as a template.
542
542
543 This allows to easily create derived repositories to construct
543 This allows to easily create derived repositories to construct
544 more complex scenarios for diff, compare and pull requests.
544 more complex scenarios for diff, compare and pull requests.
545
545
546 Returns a commit map which maps from commit message to raw_id.
546 Returns a commit map which maps from commit message to raw_id.
547 """
547 """
548 self._master_repo = self.create_repo(commits=commits)
548 self._master_repo = self.create_repo(commits=commits)
549 return self._commit_ids
549 return self._commit_ids
550
550
551 def create_repo(
551 def create_repo(
552 self, commits=None, number_of_commits=0, heads=None,
552 self, commits=None, number_of_commits=0, heads=None,
553 name_suffix=u'', **kwargs):
553 name_suffix=u'', **kwargs):
554 """
554 """
555 Create a repository and record it for later cleanup.
555 Create a repository and record it for later cleanup.
556
556
557 :param commits: Optional. A sequence of dict instances.
557 :param commits: Optional. A sequence of dict instances.
558 Will add a commit per entry to the new repository.
558 Will add a commit per entry to the new repository.
559 :param number_of_commits: Optional. If set to a number, this number of
559 :param number_of_commits: Optional. If set to a number, this number of
560 commits will be added to the new repository.
560 commits will be added to the new repository.
561 :param heads: Optional. Can be set to a sequence of of commit
561 :param heads: Optional. Can be set to a sequence of of commit
562 names which shall be pulled in from the master repository.
562 names which shall be pulled in from the master repository.
563
563
564 """
564 """
565 self.repo_name = self._next_repo_name() + name_suffix
565 self.repo_name = self._next_repo_name() + name_suffix
566 repo = self._fixture.create_repo(
566 repo = self._fixture.create_repo(
567 self.repo_name, repo_type=self.alias, **kwargs)
567 self.repo_name, repo_type=self.alias, **kwargs)
568 self._cleanup_repos.append(repo.repo_name)
568 self._cleanup_repos.append(repo.repo_name)
569
569
570 commits = commits or [
570 commits = commits or [
571 {'message': 'Commit %s of %s' % (x, self.repo_name)}
571 {'message': 'Commit %s of %s' % (x, self.repo_name)}
572 for x in xrange(number_of_commits)]
572 for x in xrange(number_of_commits)]
573 self._add_commits_to_repo(repo.scm_instance(), commits)
573 self._add_commits_to_repo(repo.scm_instance(), commits)
574 if heads:
574 if heads:
575 self.pull_heads(repo, heads)
575 self.pull_heads(repo, heads)
576
576
577 return repo
577 return repo
578
578
579 def pull_heads(self, repo, heads):
579 def pull_heads(self, repo, heads):
580 """
580 """
581 Make sure that repo contains all commits mentioned in `heads`
581 Make sure that repo contains all commits mentioned in `heads`
582 """
582 """
583 vcsmaster = self._master_repo.scm_instance()
583 vcsmaster = self._master_repo.scm_instance()
584 vcsrepo = repo.scm_instance()
584 vcsrepo = repo.scm_instance()
585 vcsrepo.config.clear_section('hooks')
585 vcsrepo.config.clear_section('hooks')
586 commit_ids = [self._commit_ids[h] for h in heads]
586 commit_ids = [self._commit_ids[h] for h in heads]
587 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
587 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
588
588
589 def create_fork(self):
589 def create_fork(self):
590 repo_to_fork = self.repo_name
590 repo_to_fork = self.repo_name
591 self.repo_name = self._next_repo_name()
591 self.repo_name = self._next_repo_name()
592 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
592 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
593 self._cleanup_repos.append(self.repo_name)
593 self._cleanup_repos.append(self.repo_name)
594 return repo
594 return repo
595
595
596 def new_repo_name(self, suffix=u''):
596 def new_repo_name(self, suffix=u''):
597 self.repo_name = self._next_repo_name() + suffix
597 self.repo_name = self._next_repo_name() + suffix
598 self._cleanup_repos.append(self.repo_name)
598 self._cleanup_repos.append(self.repo_name)
599 return self.repo_name
599 return self.repo_name
600
600
601 def _next_repo_name(self):
601 def _next_repo_name(self):
602 return u"%s_%s" % (
602 return u"%s_%s" % (
603 self.invalid_repo_name.sub(u'_', self._test_name),
603 self.invalid_repo_name.sub(u'_', self._test_name),
604 len(self._cleanup_repos))
604 len(self._cleanup_repos))
605
605
606 def ensure_file(self, filename, content='Test content\n'):
606 def ensure_file(self, filename, content='Test content\n'):
607 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
607 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
608 commits = [
608 commits = [
609 {'added': [
609 {'added': [
610 FileNode(filename, content=content),
610 FileNode(filename, content=content),
611 ]},
611 ]},
612 ]
612 ]
613 self._add_commits_to_repo(self.repo.scm_instance(), commits)
613 self._add_commits_to_repo(self.repo.scm_instance(), commits)
614
614
615 def enable_downloads(self):
615 def enable_downloads(self):
616 repo = self.repo
616 repo = self.repo
617 repo.enable_downloads = True
617 repo.enable_downloads = True
618 Session().add(repo)
618 Session().add(repo)
619 Session().commit()
619 Session().commit()
620
620
621 def cleanup(self):
621 def cleanup(self):
622 for repo_name in reversed(self._cleanup_repos):
622 for repo_name in reversed(self._cleanup_repos):
623 self._fixture.destroy_repo(repo_name)
623 self._fixture.destroy_repo(repo_name)
624
624
625 def _add_commits_to_repo(self, repo, commits):
625 def _add_commits_to_repo(self, repo, commits):
626 if not commits:
626 if not commits:
627 return
627 return
628
628
629 imc = repo.in_memory_commit
629 imc = repo.in_memory_commit
630 commit = None
630 commit = None
631 self._commit_ids = {}
631 self._commit_ids = {}
632
632
633 for idx, commit in enumerate(commits):
633 for idx, commit in enumerate(commits):
634 message = unicode(commit.get('message', 'Commit %s' % idx))
634 message = unicode(commit.get('message', 'Commit %s' % idx))
635
635
636 for node in commit.get('added', []):
636 for node in commit.get('added', []):
637 imc.add(FileNode(node.path, content=node.content))
637 imc.add(FileNode(node.path, content=node.content))
638 for node in commit.get('changed', []):
638 for node in commit.get('changed', []):
639 imc.change(FileNode(node.path, content=node.content))
639 imc.change(FileNode(node.path, content=node.content))
640 for node in commit.get('removed', []):
640 for node in commit.get('removed', []):
641 imc.remove(FileNode(node.path))
641 imc.remove(FileNode(node.path))
642
642
643 parents = [
643 parents = [
644 repo.get_commit(commit_id=self._commit_ids[p])
644 repo.get_commit(commit_id=self._commit_ids[p])
645 for p in commit.get('parents', [])]
645 for p in commit.get('parents', [])]
646
646
647 operations = ('added', 'changed', 'removed')
647 operations = ('added', 'changed', 'removed')
648 if not any((commit.get(o) for o in operations)):
648 if not any((commit.get(o) for o in operations)):
649 imc.add(FileNode('file_%s' % idx, content=message))
649 imc.add(FileNode('file_%s' % idx, content=message))
650
650
651 commit = imc.commit(
651 commit = imc.commit(
652 message=message,
652 message=message,
653 author=unicode(commit.get('author', 'Automatic')),
653 author=unicode(commit.get('author', 'Automatic')),
654 date=commit.get('date'),
654 date=commit.get('date'),
655 branch=commit.get('branch'),
655 branch=commit.get('branch'),
656 parents=parents)
656 parents=parents)
657
657
658 self._commit_ids[commit.message] = commit.raw_id
658 self._commit_ids[commit.message] = commit.raw_id
659
659
660 # Creating refs for Git to allow fetching them from remote repository
660 # Creating refs for Git to allow fetching them from remote repository
661 if self.alias == 'git':
661 if self.alias == 'git':
662 refs = {}
662 refs = {}
663 for message in self._commit_ids:
663 for message in self._commit_ids:
664 # TODO: mikhail: do more special chars replacements
664 # TODO: mikhail: do more special chars replacements
665 ref_name = 'refs/test-refs/{}'.format(
665 ref_name = 'refs/test-refs/{}'.format(
666 message.replace(' ', ''))
666 message.replace(' ', ''))
667 refs[ref_name] = self._commit_ids[message]
667 refs[ref_name] = self._commit_ids[message]
668 self._create_refs(repo, refs)
668 self._create_refs(repo, refs)
669
669
670 return commit
670 return commit
671
671
672 def _create_refs(self, repo, refs):
672 def _create_refs(self, repo, refs):
673 for ref_name in refs:
673 for ref_name in refs:
674 repo.set_refs(ref_name, refs[ref_name])
674 repo.set_refs(ref_name, refs[ref_name])
675
675
676
676
677 @pytest.fixture
677 @pytest.fixture
678 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
678 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
679 """
679 """
680 Parametrized fixture which represents a single vcs backend implementation.
680 Parametrized fixture which represents a single vcs backend implementation.
681
681
682 See the fixture `backend` for more details. This one implements the same
682 See the fixture `backend` for more details. This one implements the same
683 concept, but on vcs level. So it does not provide model instances etc.
683 concept, but on vcs level. So it does not provide model instances etc.
684
684
685 Parameters are generated dynamically, see :func:`pytest_generate_tests`
685 Parameters are generated dynamically, see :func:`pytest_generate_tests`
686 for how this works.
686 for how this works.
687 """
687 """
688 if backend_alias not in request.config.getoption('--backends'):
688 if backend_alias not in request.config.getoption('--backends'):
689 pytest.skip("Backend %s not selected." % (backend_alias, ))
689 pytest.skip("Backend %s not selected." % (backend_alias, ))
690
690
691 utils.check_xfail_backends(request.node, backend_alias)
691 utils.check_xfail_backends(request.node, backend_alias)
692 utils.check_skip_backends(request.node, backend_alias)
692 utils.check_skip_backends(request.node, backend_alias)
693
693
694 repo_name = 'vcs_test_%s' % (backend_alias, )
694 repo_name = 'vcs_test_%s' % (backend_alias, )
695 repo_path = os.path.join(tests_tmp_path, repo_name)
695 repo_path = os.path.join(tests_tmp_path, repo_name)
696 backend = VcsBackend(
696 backend = VcsBackend(
697 alias=backend_alias,
697 alias=backend_alias,
698 repo_path=repo_path,
698 repo_path=repo_path,
699 test_name=request.node.name,
699 test_name=request.node.name,
700 test_repo_container=test_repo)
700 test_repo_container=test_repo)
701 request.addfinalizer(backend.cleanup)
701 request.addfinalizer(backend.cleanup)
702 return backend
702 return backend
703
703
704
704
705 @pytest.fixture
705 @pytest.fixture
706 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
706 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
707 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
707 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
708
708
709
709
710 @pytest.fixture
710 @pytest.fixture
711 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
711 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
712 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
712 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
713
713
714
714
715 @pytest.fixture
715 @pytest.fixture
716 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
716 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
717 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
717 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
718
718
719
719
720 @pytest.fixture
720 @pytest.fixture
721 def vcsbackend_random(vcsbackend_git):
721 def vcsbackend_random(vcsbackend_git):
722 """
722 """
723 Use this to express that your tests need "a vcsbackend".
723 Use this to express that your tests need "a vcsbackend".
724
724
725 The fixture `vcsbackend` would run the test multiple times for each
725 The fixture `vcsbackend` would run the test multiple times for each
726 available vcs backend which is a pure waste of time if the test is
726 available vcs backend which is a pure waste of time if the test is
727 independent of the vcs backend type.
727 independent of the vcs backend type.
728 """
728 """
729 # TODO: johbo: Change this to pick a random backend
729 # TODO: johbo: Change this to pick a random backend
730 return vcsbackend_git
730 return vcsbackend_git
731
731
732
732
733 class VcsBackend(object):
733 class VcsBackend(object):
734 """
734 """
735 Represents the test configuration for one supported vcs backend.
735 Represents the test configuration for one supported vcs backend.
736 """
736 """
737
737
738 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
738 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
739
739
740 def __init__(self, alias, repo_path, test_name, test_repo_container):
740 def __init__(self, alias, repo_path, test_name, test_repo_container):
741 self.alias = alias
741 self.alias = alias
742 self._repo_path = repo_path
742 self._repo_path = repo_path
743 self._cleanup_repos = []
743 self._cleanup_repos = []
744 self._test_name = test_name
744 self._test_name = test_name
745 self._test_repo_container = test_repo_container
745 self._test_repo_container = test_repo_container
746
746
747 def __getitem__(self, key):
747 def __getitem__(self, key):
748 return self._test_repo_container(key, self.alias).scm_instance()
748 return self._test_repo_container(key, self.alias).scm_instance()
749
749
750 @property
750 @property
751 def repo(self):
751 def repo(self):
752 """
752 """
753 Returns the "current" repository. This is the vcs_test repo of the last
753 Returns the "current" repository. This is the vcs_test repo of the last
754 repo which has been created.
754 repo which has been created.
755 """
755 """
756 Repository = get_backend(self.alias)
756 Repository = get_backend(self.alias)
757 return Repository(self._repo_path)
757 return Repository(self._repo_path)
758
758
759 @property
759 @property
760 def backend(self):
760 def backend(self):
761 """
761 """
762 Returns the backend implementation class.
762 Returns the backend implementation class.
763 """
763 """
764 return get_backend(self.alias)
764 return get_backend(self.alias)
765
765
766 def create_repo(self, number_of_commits=0, _clone_repo=None):
766 def create_repo(self, number_of_commits=0, _clone_repo=None):
767 repo_name = self._next_repo_name()
767 repo_name = self._next_repo_name()
768 self._repo_path = get_new_dir(repo_name)
768 self._repo_path = get_new_dir(repo_name)
769 Repository = get_backend(self.alias)
769 Repository = get_backend(self.alias)
770 src_url = None
770 src_url = None
771 if _clone_repo:
771 if _clone_repo:
772 src_url = _clone_repo.path
772 src_url = _clone_repo.path
773 repo = Repository(self._repo_path, create=True, src_url=src_url)
773 repo = Repository(self._repo_path, create=True, src_url=src_url)
774 self._cleanup_repos.append(repo)
774 self._cleanup_repos.append(repo)
775 for idx in xrange(number_of_commits):
775 for idx in xrange(number_of_commits):
776 self.ensure_file(filename='file_%s' % idx, content=repo.name)
776 self.ensure_file(filename='file_%s' % idx, content=repo.name)
777 return repo
777 return repo
778
778
779 def clone_repo(self, repo):
779 def clone_repo(self, repo):
780 return self.create_repo(_clone_repo=repo)
780 return self.create_repo(_clone_repo=repo)
781
781
782 def cleanup(self):
782 def cleanup(self):
783 for repo in self._cleanup_repos:
783 for repo in self._cleanup_repos:
784 shutil.rmtree(repo.path)
784 shutil.rmtree(repo.path)
785
785
786 def new_repo_path(self):
786 def new_repo_path(self):
787 repo_name = self._next_repo_name()
787 repo_name = self._next_repo_name()
788 self._repo_path = get_new_dir(repo_name)
788 self._repo_path = get_new_dir(repo_name)
789 return self._repo_path
789 return self._repo_path
790
790
791 def _next_repo_name(self):
791 def _next_repo_name(self):
792 return "%s_%s" % (
792 return "%s_%s" % (
793 self.invalid_repo_name.sub('_', self._test_name),
793 self.invalid_repo_name.sub('_', self._test_name),
794 len(self._cleanup_repos))
794 len(self._cleanup_repos))
795
795
796 def add_file(self, repo, filename, content='Test content\n'):
796 def add_file(self, repo, filename, content='Test content\n'):
797 imc = repo.in_memory_commit
797 imc = repo.in_memory_commit
798 imc.add(FileNode(filename, content=content))
798 imc.add(FileNode(filename, content=content))
799 imc.commit(
799 imc.commit(
800 message=u'Automatic commit from vcsbackend fixture',
800 message=u'Automatic commit from vcsbackend fixture',
801 author=u'Automatic')
801 author=u'Automatic')
802
802
803 def ensure_file(self, filename, content='Test content\n'):
803 def ensure_file(self, filename, content='Test content\n'):
804 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
804 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
805 self.add_file(self.repo, filename, content)
805 self.add_file(self.repo, filename, content)
806
806
807
807
808 @pytest.fixture
808 @pytest.fixture
809 def reposerver(request):
809 def reposerver(request):
810 """
810 """
811 Allows to serve a backend repository
811 Allows to serve a backend repository
812 """
812 """
813
813
814 repo_server = RepoServer()
814 repo_server = RepoServer()
815 request.addfinalizer(repo_server.cleanup)
815 request.addfinalizer(repo_server.cleanup)
816 return repo_server
816 return repo_server
817
817
818
818
819 class RepoServer(object):
819 class RepoServer(object):
820 """
820 """
821 Utility to serve a local repository for the duration of a test case.
821 Utility to serve a local repository for the duration of a test case.
822
822
823 Supports only Subversion so far.
823 Supports only Subversion so far.
824 """
824 """
825
825
826 url = None
826 url = None
827
827
828 def __init__(self):
828 def __init__(self):
829 self._cleanup_servers = []
829 self._cleanup_servers = []
830
830
831 def serve(self, vcsrepo):
831 def serve(self, vcsrepo):
832 if vcsrepo.alias != 'svn':
832 if vcsrepo.alias != 'svn':
833 raise TypeError("Backend %s not supported" % vcsrepo.alias)
833 raise TypeError("Backend %s not supported" % vcsrepo.alias)
834
834
835 proc = subprocess.Popen(
835 proc = subprocess.Popen(
836 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
836 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
837 '--root', vcsrepo.path])
837 '--root', vcsrepo.path])
838 self._cleanup_servers.append(proc)
838 self._cleanup_servers.append(proc)
839 self.url = 'svn://localhost'
839 self.url = 'svn://localhost'
840
840
841 def cleanup(self):
841 def cleanup(self):
842 for proc in self._cleanup_servers:
842 for proc in self._cleanup_servers:
843 proc.terminate()
843 proc.terminate()
844
844
845
845
846 @pytest.fixture
846 @pytest.fixture
847 def pr_util(backend, request):
847 def pr_util(backend, request):
848 """
848 """
849 Utility for tests of models and for functional tests around pull requests.
849 Utility for tests of models and for functional tests around pull requests.
850
850
851 It gives an instance of :class:`PRTestUtility` which provides various
851 It gives an instance of :class:`PRTestUtility` which provides various
852 utility methods around one pull request.
852 utility methods around one pull request.
853
853
854 This fixture uses `backend` and inherits its parameterization.
854 This fixture uses `backend` and inherits its parameterization.
855 """
855 """
856
856
857 util = PRTestUtility(backend)
857 util = PRTestUtility(backend)
858
858
859 @request.addfinalizer
859 @request.addfinalizer
860 def cleanup():
860 def cleanup():
861 util.cleanup()
861 util.cleanup()
862
862
863 return util
863 return util
864
864
865
865
866 class PRTestUtility(object):
866 class PRTestUtility(object):
867
867
868 pull_request = None
868 pull_request = None
869 pull_request_id = None
869 pull_request_id = None
870 mergeable_patcher = None
870 mergeable_patcher = None
871 mergeable_mock = None
871 mergeable_mock = None
872 notification_patcher = None
872 notification_patcher = None
873
873
874 def __init__(self, backend):
874 def __init__(self, backend):
875 self.backend = backend
875 self.backend = backend
876
876
877 def create_pull_request(
877 def create_pull_request(
878 self, commits=None, target_head=None, source_head=None,
878 self, commits=None, target_head=None, source_head=None,
879 revisions=None, approved=False, author=None, mergeable=False,
879 revisions=None, approved=False, author=None, mergeable=False,
880 enable_notifications=True, name_suffix=u'', reviewers=None,
880 enable_notifications=True, name_suffix=u'', reviewers=None,
881 title=u"Test", description=u"Description"):
881 title=u"Test", description=u"Description"):
882 self.set_mergeable(mergeable)
882 self.set_mergeable(mergeable)
883 if not enable_notifications:
883 if not enable_notifications:
884 # mock notification side effect
884 # mock notification side effect
885 self.notification_patcher = mock.patch(
885 self.notification_patcher = mock.patch(
886 'rhodecode.model.notification.NotificationModel.create')
886 'rhodecode.model.notification.NotificationModel.create')
887 self.notification_patcher.start()
887 self.notification_patcher.start()
888
888
889 if not self.pull_request:
889 if not self.pull_request:
890 if not commits:
890 if not commits:
891 commits = [
891 commits = [
892 {'message': 'c1'},
892 {'message': 'c1'},
893 {'message': 'c2'},
893 {'message': 'c2'},
894 {'message': 'c3'},
894 {'message': 'c3'},
895 ]
895 ]
896 target_head = 'c1'
896 target_head = 'c1'
897 source_head = 'c2'
897 source_head = 'c2'
898 revisions = ['c2']
898 revisions = ['c2']
899
899
900 self.commit_ids = self.backend.create_master_repo(commits)
900 self.commit_ids = self.backend.create_master_repo(commits)
901 self.target_repository = self.backend.create_repo(
901 self.target_repository = self.backend.create_repo(
902 heads=[target_head], name_suffix=name_suffix)
902 heads=[target_head], name_suffix=name_suffix)
903 self.source_repository = self.backend.create_repo(
903 self.source_repository = self.backend.create_repo(
904 heads=[source_head], name_suffix=name_suffix)
904 heads=[source_head], name_suffix=name_suffix)
905 self.author = author or UserModel().get_by_username(
905 self.author = author or UserModel().get_by_username(
906 TEST_USER_ADMIN_LOGIN)
906 TEST_USER_ADMIN_LOGIN)
907
907
908 model = PullRequestModel()
908 model = PullRequestModel()
909 self.create_parameters = {
909 self.create_parameters = {
910 'created_by': self.author,
910 'created_by': self.author,
911 'source_repo': self.source_repository.repo_name,
911 'source_repo': self.source_repository.repo_name,
912 'source_ref': self._default_branch_reference(source_head),
912 'source_ref': self._default_branch_reference(source_head),
913 'target_repo': self.target_repository.repo_name,
913 'target_repo': self.target_repository.repo_name,
914 'target_ref': self._default_branch_reference(target_head),
914 'target_ref': self._default_branch_reference(target_head),
915 'revisions': [self.commit_ids[r] for r in revisions],
915 'revisions': [self.commit_ids[r] for r in revisions],
916 'reviewers': reviewers or self._get_reviewers(),
916 'reviewers': reviewers or self._get_reviewers(),
917 'title': title,
917 'title': title,
918 'description': description,
918 'description': description,
919 }
919 }
920 self.pull_request = model.create(**self.create_parameters)
920 self.pull_request = model.create(**self.create_parameters)
921 assert model.get_versions(self.pull_request) == []
921 assert model.get_versions(self.pull_request) == []
922
922
923 self.pull_request_id = self.pull_request.pull_request_id
923 self.pull_request_id = self.pull_request.pull_request_id
924
924
925 if approved:
925 if approved:
926 self.approve()
926 self.approve()
927
927
928 Session().add(self.pull_request)
928 Session().add(self.pull_request)
929 Session().commit()
929 Session().commit()
930
930
931 return self.pull_request
931 return self.pull_request
932
932
933 def approve(self):
933 def approve(self):
934 self.create_status_votes(
934 self.create_status_votes(
935 ChangesetStatus.STATUS_APPROVED,
935 ChangesetStatus.STATUS_APPROVED,
936 *self.pull_request.reviewers)
936 *self.pull_request.reviewers)
937
937
938 def close(self):
938 def close(self):
939 PullRequestModel().close_pull_request(self.pull_request, self.author)
939 PullRequestModel().close_pull_request(self.pull_request, self.author)
940
940
941 def _default_branch_reference(self, commit_message):
941 def _default_branch_reference(self, commit_message):
942 reference = '%s:%s:%s' % (
942 reference = '%s:%s:%s' % (
943 'branch',
943 'branch',
944 self.backend.default_branch_name,
944 self.backend.default_branch_name,
945 self.commit_ids[commit_message])
945 self.commit_ids[commit_message])
946 return reference
946 return reference
947
947
948 def _get_reviewers(self):
948 def _get_reviewers(self):
949 model = UserModel()
949 model = UserModel()
950 return [
950 return [
951 model.get_by_username(TEST_USER_REGULAR_LOGIN),
951 model.get_by_username(TEST_USER_REGULAR_LOGIN),
952 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
952 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
953 ]
953 ]
954
954
955 def update_source_repository(self, head=None):
955 def update_source_repository(self, head=None):
956 heads = [head or 'c3']
956 heads = [head or 'c3']
957 self.backend.pull_heads(self.source_repository, heads=heads)
957 self.backend.pull_heads(self.source_repository, heads=heads)
958
958
959 def add_one_commit(self, head=None):
959 def add_one_commit(self, head=None):
960 self.update_source_repository(head=head)
960 self.update_source_repository(head=head)
961 old_commit_ids = set(self.pull_request.revisions)
961 old_commit_ids = set(self.pull_request.revisions)
962 PullRequestModel().update_commits(self.pull_request)
962 PullRequestModel().update_commits(self.pull_request)
963 commit_ids = set(self.pull_request.revisions)
963 commit_ids = set(self.pull_request.revisions)
964 new_commit_ids = commit_ids - old_commit_ids
964 new_commit_ids = commit_ids - old_commit_ids
965 assert len(new_commit_ids) == 1
965 assert len(new_commit_ids) == 1
966 return new_commit_ids.pop()
966 return new_commit_ids.pop()
967
967
968 def remove_one_commit(self):
968 def remove_one_commit(self):
969 assert len(self.pull_request.revisions) == 2
969 assert len(self.pull_request.revisions) == 2
970 source_vcs = self.source_repository.scm_instance()
970 source_vcs = self.source_repository.scm_instance()
971 removed_commit_id = source_vcs.commit_ids[-1]
971 removed_commit_id = source_vcs.commit_ids[-1]
972
972
973 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
973 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
974 # remove the if once that's sorted out.
974 # remove the if once that's sorted out.
975 if self.backend.alias == "git":
975 if self.backend.alias == "git":
976 kwargs = {'branch_name': self.backend.default_branch_name}
976 kwargs = {'branch_name': self.backend.default_branch_name}
977 else:
977 else:
978 kwargs = {}
978 kwargs = {}
979 source_vcs.strip(removed_commit_id, **kwargs)
979 source_vcs.strip(removed_commit_id, **kwargs)
980
980
981 PullRequestModel().update_commits(self.pull_request)
981 PullRequestModel().update_commits(self.pull_request)
982 assert len(self.pull_request.revisions) == 1
982 assert len(self.pull_request.revisions) == 1
983 return removed_commit_id
983 return removed_commit_id
984
984
985 def create_comment(self, linked_to=None):
985 def create_comment(self, linked_to=None):
986 comment = ChangesetCommentsModel().create(
986 comment = ChangesetCommentsModel().create(
987 text=u"Test comment",
987 text=u"Test comment",
988 repo=self.target_repository.repo_name,
988 repo=self.target_repository.repo_name,
989 user=self.author,
989 user=self.author,
990 pull_request=self.pull_request)
990 pull_request=self.pull_request)
991 assert comment.pull_request_version_id is None
991 assert comment.pull_request_version_id is None
992
992
993 if linked_to:
993 if linked_to:
994 PullRequestModel()._link_comments_to_version(linked_to)
994 PullRequestModel()._link_comments_to_version(linked_to)
995
995
996 return comment
996 return comment
997
997
998 def create_inline_comment(
998 def create_inline_comment(
999 self, linked_to=None, line_no=u'n1', file_path='file_1'):
999 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1000 comment = ChangesetCommentsModel().create(
1000 comment = ChangesetCommentsModel().create(
1001 text=u"Test comment",
1001 text=u"Test comment",
1002 repo=self.target_repository.repo_name,
1002 repo=self.target_repository.repo_name,
1003 user=self.author,
1003 user=self.author,
1004 line_no=line_no,
1004 line_no=line_no,
1005 f_path=file_path,
1005 f_path=file_path,
1006 pull_request=self.pull_request)
1006 pull_request=self.pull_request)
1007 assert comment.pull_request_version_id is None
1007 assert comment.pull_request_version_id is None
1008
1008
1009 if linked_to:
1009 if linked_to:
1010 PullRequestModel()._link_comments_to_version(linked_to)
1010 PullRequestModel()._link_comments_to_version(linked_to)
1011
1011
1012 return comment
1012 return comment
1013
1013
1014 def create_version_of_pull_request(self):
1014 def create_version_of_pull_request(self):
1015 pull_request = self.create_pull_request()
1015 pull_request = self.create_pull_request()
1016 version = PullRequestModel()._create_version_from_snapshot(
1016 version = PullRequestModel()._create_version_from_snapshot(
1017 pull_request)
1017 pull_request)
1018 return version
1018 return version
1019
1019
1020 def create_status_votes(self, status, *reviewers):
1020 def create_status_votes(self, status, *reviewers):
1021 for reviewer in reviewers:
1021 for reviewer in reviewers:
1022 ChangesetStatusModel().set_status(
1022 ChangesetStatusModel().set_status(
1023 repo=self.pull_request.target_repo,
1023 repo=self.pull_request.target_repo,
1024 status=status,
1024 status=status,
1025 user=reviewer.user_id,
1025 user=reviewer.user_id,
1026 pull_request=self.pull_request)
1026 pull_request=self.pull_request)
1027
1027
1028 def set_mergeable(self, value):
1028 def set_mergeable(self, value):
1029 if not self.mergeable_patcher:
1029 if not self.mergeable_patcher:
1030 self.mergeable_patcher = mock.patch.object(
1030 self.mergeable_patcher = mock.patch.object(
1031 VcsSettingsModel, 'get_general_settings')
1031 VcsSettingsModel, 'get_general_settings')
1032 self.mergeable_mock = self.mergeable_patcher.start()
1032 self.mergeable_mock = self.mergeable_patcher.start()
1033 self.mergeable_mock.return_value = {
1033 self.mergeable_mock.return_value = {
1034 'rhodecode_pr_merge_enabled': value}
1034 'rhodecode_pr_merge_enabled': value}
1035
1035
1036 def cleanup(self):
1036 def cleanup(self):
1037 # In case the source repository is already cleaned up, the pull
1037 # In case the source repository is already cleaned up, the pull
1038 # request will already be deleted.
1038 # request will already be deleted.
1039 pull_request = PullRequest().get(self.pull_request_id)
1039 pull_request = PullRequest().get(self.pull_request_id)
1040 if pull_request:
1040 if pull_request:
1041 PullRequestModel().delete(pull_request)
1041 PullRequestModel().delete(pull_request)
1042 Session().commit()
1042 Session().commit()
1043
1043
1044 if self.notification_patcher:
1044 if self.notification_patcher:
1045 self.notification_patcher.stop()
1045 self.notification_patcher.stop()
1046
1046
1047 if self.mergeable_patcher:
1047 if self.mergeable_patcher:
1048 self.mergeable_patcher.stop()
1048 self.mergeable_patcher.stop()
1049
1049
1050
1050
1051 @pytest.fixture
1051 @pytest.fixture
1052 def user_admin(pylonsapp):
1052 def user_admin(pylonsapp):
1053 """
1053 """
1054 Provides the default admin test user as an instance of `db.User`.
1054 Provides the default admin test user as an instance of `db.User`.
1055 """
1055 """
1056 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1056 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1057 return user
1057 return user
1058
1058
1059
1059
1060 @pytest.fixture
1060 @pytest.fixture
1061 def user_regular(pylonsapp):
1061 def user_regular(pylonsapp):
1062 """
1062 """
1063 Provides the default regular test user as an instance of `db.User`.
1063 Provides the default regular test user as an instance of `db.User`.
1064 """
1064 """
1065 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1065 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1066 return user
1066 return user
1067
1067
1068
1068
1069 @pytest.fixture
1069 @pytest.fixture
1070 def user_util(request, pylonsapp):
1070 def user_util(request, pylonsapp):
1071 """
1071 """
1072 Provides a wired instance of `UserUtility` with integrated cleanup.
1072 Provides a wired instance of `UserUtility` with integrated cleanup.
1073 """
1073 """
1074 utility = UserUtility(test_name=request.node.name)
1074 utility = UserUtility(test_name=request.node.name)
1075 request.addfinalizer(utility.cleanup)
1075 request.addfinalizer(utility.cleanup)
1076 return utility
1076 return utility
1077
1077
1078
1078
1079 # TODO: johbo: Split this up into utilities per domain or something similar
1079 # TODO: johbo: Split this up into utilities per domain or something similar
1080 class UserUtility(object):
1080 class UserUtility(object):
1081
1081
1082 def __init__(self, test_name="test"):
1082 def __init__(self, test_name="test"):
1083 self._test_name = test_name
1083 self._test_name = test_name
1084 self.fixture = Fixture()
1084 self.fixture = Fixture()
1085 self.repo_group_ids = []
1085 self.repo_group_ids = []
1086 self.user_ids = []
1086 self.user_ids = []
1087 self.user_group_ids = []
1087 self.user_group_ids = []
1088 self.user_repo_permission_ids = []
1088 self.user_repo_permission_ids = []
1089 self.user_group_repo_permission_ids = []
1089 self.user_group_repo_permission_ids = []
1090 self.user_repo_group_permission_ids = []
1090 self.user_repo_group_permission_ids = []
1091 self.user_group_repo_group_permission_ids = []
1091 self.user_group_repo_group_permission_ids = []
1092 self.user_user_group_permission_ids = []
1092 self.user_user_group_permission_ids = []
1093 self.user_group_user_group_permission_ids = []
1093 self.user_group_user_group_permission_ids = []
1094 self.user_permissions = []
1094 self.user_permissions = []
1095
1095
1096 def create_repo_group(
1096 def create_repo_group(
1097 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1097 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1098 group_name = "{prefix}_repogroup_{count}".format(
1098 group_name = "{prefix}_repogroup_{count}".format(
1099 prefix=self._test_name,
1099 prefix=self._test_name,
1100 count=len(self.repo_group_ids))
1100 count=len(self.repo_group_ids))
1101 repo_group = self.fixture.create_repo_group(
1101 repo_group = self.fixture.create_repo_group(
1102 group_name, cur_user=owner)
1102 group_name, cur_user=owner)
1103 if auto_cleanup:
1103 if auto_cleanup:
1104 self.repo_group_ids.append(repo_group.group_id)
1104 self.repo_group_ids.append(repo_group.group_id)
1105 return repo_group
1105 return repo_group
1106
1106
1107 def create_user(self, auto_cleanup=True, **kwargs):
1107 def create_user(self, auto_cleanup=True, **kwargs):
1108 user_name = "{prefix}_user_{count}".format(
1108 user_name = "{prefix}_user_{count}".format(
1109 prefix=self._test_name,
1109 prefix=self._test_name,
1110 count=len(self.user_ids))
1110 count=len(self.user_ids))
1111 user = self.fixture.create_user(user_name, **kwargs)
1111 user = self.fixture.create_user(user_name, **kwargs)
1112 if auto_cleanup:
1112 if auto_cleanup:
1113 self.user_ids.append(user.user_id)
1113 self.user_ids.append(user.user_id)
1114 return user
1114 return user
1115
1115
1116 def create_user_with_group(self):
1116 def create_user_with_group(self):
1117 user = self.create_user()
1117 user = self.create_user()
1118 user_group = self.create_user_group(members=[user])
1118 user_group = self.create_user_group(members=[user])
1119 return user, user_group
1119 return user, user_group
1120
1120
1121 def create_user_group(self, members=None, auto_cleanup=True, **kwargs):
1121 def create_user_group(self, members=None, auto_cleanup=True, **kwargs):
1122 group_name = "{prefix}_usergroup_{count}".format(
1122 group_name = "{prefix}_usergroup_{count}".format(
1123 prefix=self._test_name,
1123 prefix=self._test_name,
1124 count=len(self.user_group_ids))
1124 count=len(self.user_group_ids))
1125 user_group = self.fixture.create_user_group(group_name, **kwargs)
1125 user_group = self.fixture.create_user_group(group_name, **kwargs)
1126 if auto_cleanup:
1126 if auto_cleanup:
1127 self.user_group_ids.append(user_group.users_group_id)
1127 self.user_group_ids.append(user_group.users_group_id)
1128 if members:
1128 if members:
1129 for user in members:
1129 for user in members:
1130 UserGroupModel().add_user_to_group(user_group, user)
1130 UserGroupModel().add_user_to_group(user_group, user)
1131 return user_group
1131 return user_group
1132
1132
1133 def grant_user_permission(self, user_name, permission_name):
1133 def grant_user_permission(self, user_name, permission_name):
1134 self._inherit_default_user_permissions(user_name, False)
1134 self._inherit_default_user_permissions(user_name, False)
1135 self.user_permissions.append((user_name, permission_name))
1135 self.user_permissions.append((user_name, permission_name))
1136
1136
1137 def grant_user_permission_to_repo_group(
1137 def grant_user_permission_to_repo_group(
1138 self, repo_group, user, permission_name):
1138 self, repo_group, user, permission_name):
1139 permission = RepoGroupModel().grant_user_permission(
1139 permission = RepoGroupModel().grant_user_permission(
1140 repo_group, user, permission_name)
1140 repo_group, user, permission_name)
1141 self.user_repo_group_permission_ids.append(
1141 self.user_repo_group_permission_ids.append(
1142 (repo_group.group_id, user.user_id))
1142 (repo_group.group_id, user.user_id))
1143 return permission
1143 return permission
1144
1144
1145 def grant_user_group_permission_to_repo_group(
1145 def grant_user_group_permission_to_repo_group(
1146 self, repo_group, user_group, permission_name):
1146 self, repo_group, user_group, permission_name):
1147 permission = RepoGroupModel().grant_user_group_permission(
1147 permission = RepoGroupModel().grant_user_group_permission(
1148 repo_group, user_group, permission_name)
1148 repo_group, user_group, permission_name)
1149 self.user_group_repo_group_permission_ids.append(
1149 self.user_group_repo_group_permission_ids.append(
1150 (repo_group.group_id, user_group.users_group_id))
1150 (repo_group.group_id, user_group.users_group_id))
1151 return permission
1151 return permission
1152
1152
1153 def grant_user_permission_to_repo(
1153 def grant_user_permission_to_repo(
1154 self, repo, user, permission_name):
1154 self, repo, user, permission_name):
1155 permission = RepoModel().grant_user_permission(
1155 permission = RepoModel().grant_user_permission(
1156 repo, user, permission_name)
1156 repo, user, permission_name)
1157 self.user_repo_permission_ids.append(
1157 self.user_repo_permission_ids.append(
1158 (repo.repo_id, user.user_id))
1158 (repo.repo_id, user.user_id))
1159 return permission
1159 return permission
1160
1160
1161 def grant_user_group_permission_to_repo(
1161 def grant_user_group_permission_to_repo(
1162 self, repo, user_group, permission_name):
1162 self, repo, user_group, permission_name):
1163 permission = RepoModel().grant_user_group_permission(
1163 permission = RepoModel().grant_user_group_permission(
1164 repo, user_group, permission_name)
1164 repo, user_group, permission_name)
1165 self.user_group_repo_permission_ids.append(
1165 self.user_group_repo_permission_ids.append(
1166 (repo.repo_id, user_group.users_group_id))
1166 (repo.repo_id, user_group.users_group_id))
1167 return permission
1167 return permission
1168
1168
1169 def grant_user_permission_to_user_group(
1169 def grant_user_permission_to_user_group(
1170 self, target_user_group, user, permission_name):
1170 self, target_user_group, user, permission_name):
1171 permission = UserGroupModel().grant_user_permission(
1171 permission = UserGroupModel().grant_user_permission(
1172 target_user_group, user, permission_name)
1172 target_user_group, user, permission_name)
1173 self.user_user_group_permission_ids.append(
1173 self.user_user_group_permission_ids.append(
1174 (target_user_group.users_group_id, user.user_id))
1174 (target_user_group.users_group_id, user.user_id))
1175 return permission
1175 return permission
1176
1176
1177 def grant_user_group_permission_to_user_group(
1177 def grant_user_group_permission_to_user_group(
1178 self, target_user_group, user_group, permission_name):
1178 self, target_user_group, user_group, permission_name):
1179 permission = UserGroupModel().grant_user_group_permission(
1179 permission = UserGroupModel().grant_user_group_permission(
1180 target_user_group, user_group, permission_name)
1180 target_user_group, user_group, permission_name)
1181 self.user_group_user_group_permission_ids.append(
1181 self.user_group_user_group_permission_ids.append(
1182 (target_user_group.users_group_id, user_group.users_group_id))
1182 (target_user_group.users_group_id, user_group.users_group_id))
1183 return permission
1183 return permission
1184
1184
1185 def revoke_user_permission(self, user_name, permission_name):
1185 def revoke_user_permission(self, user_name, permission_name):
1186 self._inherit_default_user_permissions(user_name, True)
1186 self._inherit_default_user_permissions(user_name, True)
1187 UserModel().revoke_perm(user_name, permission_name)
1187 UserModel().revoke_perm(user_name, permission_name)
1188
1188
1189 def _inherit_default_user_permissions(self, user_name, value):
1189 def _inherit_default_user_permissions(self, user_name, value):
1190 user = UserModel().get_by_username(user_name)
1190 user = UserModel().get_by_username(user_name)
1191 user.inherit_default_permissions = value
1191 user.inherit_default_permissions = value
1192 Session.add(user)
1192 Session().add(user)
1193 Session.commit()
1193 Session().commit()
1194
1194
1195 def cleanup(self):
1195 def cleanup(self):
1196 self._cleanup_permissions()
1196 self._cleanup_permissions()
1197 self._cleanup_repo_groups()
1197 self._cleanup_repo_groups()
1198 self._cleanup_user_groups()
1198 self._cleanup_user_groups()
1199 self._cleanup_users()
1199 self._cleanup_users()
1200
1200
1201 def _cleanup_permissions(self):
1201 def _cleanup_permissions(self):
1202 if self.user_permissions:
1202 if self.user_permissions:
1203 for user_name, permission_name in self.user_permissions:
1203 for user_name, permission_name in self.user_permissions:
1204 self.revoke_user_permission(user_name, permission_name)
1204 self.revoke_user_permission(user_name, permission_name)
1205
1205
1206 for permission in self.user_repo_permission_ids:
1206 for permission in self.user_repo_permission_ids:
1207 RepoModel().revoke_user_permission(*permission)
1207 RepoModel().revoke_user_permission(*permission)
1208
1208
1209 for permission in self.user_group_repo_permission_ids:
1209 for permission in self.user_group_repo_permission_ids:
1210 RepoModel().revoke_user_group_permission(*permission)
1210 RepoModel().revoke_user_group_permission(*permission)
1211
1211
1212 for permission in self.user_repo_group_permission_ids:
1212 for permission in self.user_repo_group_permission_ids:
1213 RepoGroupModel().revoke_user_permission(*permission)
1213 RepoGroupModel().revoke_user_permission(*permission)
1214
1214
1215 for permission in self.user_group_repo_group_permission_ids:
1215 for permission in self.user_group_repo_group_permission_ids:
1216 RepoGroupModel().revoke_user_group_permission(*permission)
1216 RepoGroupModel().revoke_user_group_permission(*permission)
1217
1217
1218 for permission in self.user_user_group_permission_ids:
1218 for permission in self.user_user_group_permission_ids:
1219 UserGroupModel().revoke_user_permission(*permission)
1219 UserGroupModel().revoke_user_permission(*permission)
1220
1220
1221 for permission in self.user_group_user_group_permission_ids:
1221 for permission in self.user_group_user_group_permission_ids:
1222 UserGroupModel().revoke_user_group_permission(*permission)
1222 UserGroupModel().revoke_user_group_permission(*permission)
1223
1223
1224 def _cleanup_repo_groups(self):
1224 def _cleanup_repo_groups(self):
1225 def _repo_group_compare(first_group_id, second_group_id):
1225 def _repo_group_compare(first_group_id, second_group_id):
1226 """
1226 """
1227 Gives higher priority to the groups with the most complex paths
1227 Gives higher priority to the groups with the most complex paths
1228 """
1228 """
1229 first_group = RepoGroup.get(first_group_id)
1229 first_group = RepoGroup.get(first_group_id)
1230 second_group = RepoGroup.get(second_group_id)
1230 second_group = RepoGroup.get(second_group_id)
1231 first_group_parts = (
1231 first_group_parts = (
1232 len(first_group.group_name.split('/')) if first_group else 0)
1232 len(first_group.group_name.split('/')) if first_group else 0)
1233 second_group_parts = (
1233 second_group_parts = (
1234 len(second_group.group_name.split('/')) if second_group else 0)
1234 len(second_group.group_name.split('/')) if second_group else 0)
1235 return cmp(second_group_parts, first_group_parts)
1235 return cmp(second_group_parts, first_group_parts)
1236
1236
1237 sorted_repo_group_ids = sorted(
1237 sorted_repo_group_ids = sorted(
1238 self.repo_group_ids, cmp=_repo_group_compare)
1238 self.repo_group_ids, cmp=_repo_group_compare)
1239 for repo_group_id in sorted_repo_group_ids:
1239 for repo_group_id in sorted_repo_group_ids:
1240 self.fixture.destroy_repo_group(repo_group_id)
1240 self.fixture.destroy_repo_group(repo_group_id)
1241
1241
1242 def _cleanup_user_groups(self):
1242 def _cleanup_user_groups(self):
1243 def _user_group_compare(first_group_id, second_group_id):
1243 def _user_group_compare(first_group_id, second_group_id):
1244 """
1244 """
1245 Gives higher priority to the groups with the most complex paths
1245 Gives higher priority to the groups with the most complex paths
1246 """
1246 """
1247 first_group = UserGroup.get(first_group_id)
1247 first_group = UserGroup.get(first_group_id)
1248 second_group = UserGroup.get(second_group_id)
1248 second_group = UserGroup.get(second_group_id)
1249 first_group_parts = (
1249 first_group_parts = (
1250 len(first_group.users_group_name.split('/'))
1250 len(first_group.users_group_name.split('/'))
1251 if first_group else 0)
1251 if first_group else 0)
1252 second_group_parts = (
1252 second_group_parts = (
1253 len(second_group.users_group_name.split('/'))
1253 len(second_group.users_group_name.split('/'))
1254 if second_group else 0)
1254 if second_group else 0)
1255 return cmp(second_group_parts, first_group_parts)
1255 return cmp(second_group_parts, first_group_parts)
1256
1256
1257 sorted_user_group_ids = sorted(
1257 sorted_user_group_ids = sorted(
1258 self.user_group_ids, cmp=_user_group_compare)
1258 self.user_group_ids, cmp=_user_group_compare)
1259 for user_group_id in sorted_user_group_ids:
1259 for user_group_id in sorted_user_group_ids:
1260 self.fixture.destroy_user_group(user_group_id)
1260 self.fixture.destroy_user_group(user_group_id)
1261
1261
1262 def _cleanup_users(self):
1262 def _cleanup_users(self):
1263 for user_id in self.user_ids:
1263 for user_id in self.user_ids:
1264 self.fixture.destroy_user(user_id)
1264 self.fixture.destroy_user(user_id)
1265
1265
1266
1266
1267 # TODO: Think about moving this into a pytest-pyro package and make it a
1267 # TODO: Think about moving this into a pytest-pyro package and make it a
1268 # pytest plugin
1268 # pytest plugin
1269 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1269 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1270 def pytest_runtest_makereport(item, call):
1270 def pytest_runtest_makereport(item, call):
1271 """
1271 """
1272 Adding the remote traceback if the exception has this information.
1272 Adding the remote traceback if the exception has this information.
1273
1273
1274 Pyro4 attaches this information as the attribute `_pyroTraceback`
1274 Pyro4 attaches this information as the attribute `_pyroTraceback`
1275 to the exception instance.
1275 to the exception instance.
1276 """
1276 """
1277 outcome = yield
1277 outcome = yield
1278 report = outcome.get_result()
1278 report = outcome.get_result()
1279 if call.excinfo:
1279 if call.excinfo:
1280 _add_pyro_remote_traceback(report, call.excinfo.value)
1280 _add_pyro_remote_traceback(report, call.excinfo.value)
1281
1281
1282
1282
1283 def _add_pyro_remote_traceback(report, exc):
1283 def _add_pyro_remote_traceback(report, exc):
1284 pyro_traceback = getattr(exc, '_pyroTraceback', None)
1284 pyro_traceback = getattr(exc, '_pyroTraceback', None)
1285
1285
1286 if pyro_traceback:
1286 if pyro_traceback:
1287 traceback = ''.join(pyro_traceback)
1287 traceback = ''.join(pyro_traceback)
1288 section = 'Pyro4 remote traceback ' + report.when
1288 section = 'Pyro4 remote traceback ' + report.when
1289 report.sections.append((section, traceback))
1289 report.sections.append((section, traceback))
1290
1290
1291
1291
1292 @pytest.fixture(scope='session')
1292 @pytest.fixture(scope='session')
1293 def testrun():
1293 def testrun():
1294 return {
1294 return {
1295 'uuid': uuid.uuid4(),
1295 'uuid': uuid.uuid4(),
1296 'start': datetime.datetime.utcnow().isoformat(),
1296 'start': datetime.datetime.utcnow().isoformat(),
1297 'timestamp': int(time.time()),
1297 'timestamp': int(time.time()),
1298 }
1298 }
1299
1299
1300
1300
1301 @pytest.fixture(autouse=True)
1301 @pytest.fixture(autouse=True)
1302 def collect_appenlight_stats(request, testrun):
1302 def collect_appenlight_stats(request, testrun):
1303 """
1303 """
1304 This fixture reports memory consumtion of single tests.
1304 This fixture reports memory consumtion of single tests.
1305
1305
1306 It gathers data based on `psutil` and sends them to Appenlight. The option
1306 It gathers data based on `psutil` and sends them to Appenlight. The option
1307 ``--ae`` has te be used to enable this fixture and the API key for your
1307 ``--ae`` has te be used to enable this fixture and the API key for your
1308 application has to be provided in ``--ae-key``.
1308 application has to be provided in ``--ae-key``.
1309 """
1309 """
1310 try:
1310 try:
1311 # cygwin cannot have yet psutil support.
1311 # cygwin cannot have yet psutil support.
1312 import psutil
1312 import psutil
1313 except ImportError:
1313 except ImportError:
1314 return
1314 return
1315
1315
1316 if not request.config.getoption('--appenlight'):
1316 if not request.config.getoption('--appenlight'):
1317 return
1317 return
1318 else:
1318 else:
1319 # Only request the pylonsapp fixture if appenlight tracking is
1319 # Only request the pylonsapp fixture if appenlight tracking is
1320 # enabled. This will speed up a test run of unit tests by 2 to 3
1320 # enabled. This will speed up a test run of unit tests by 2 to 3
1321 # seconds if appenlight is not enabled.
1321 # seconds if appenlight is not enabled.
1322 pylonsapp = request.getfuncargvalue("pylonsapp")
1322 pylonsapp = request.getfuncargvalue("pylonsapp")
1323 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1323 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1324 client = AppenlightClient(
1324 client = AppenlightClient(
1325 url=url,
1325 url=url,
1326 api_key=request.config.getoption('--appenlight-api-key'),
1326 api_key=request.config.getoption('--appenlight-api-key'),
1327 namespace=request.node.nodeid,
1327 namespace=request.node.nodeid,
1328 request=str(testrun['uuid']),
1328 request=str(testrun['uuid']),
1329 testrun=testrun)
1329 testrun=testrun)
1330
1330
1331 client.collect({
1331 client.collect({
1332 'message': "Starting",
1332 'message': "Starting",
1333 })
1333 })
1334
1334
1335 server_and_port = pylonsapp.config['vcs.server']
1335 server_and_port = pylonsapp.config['vcs.server']
1336 server = create_vcsserver_proxy(server_and_port)
1336 server = create_vcsserver_proxy(server_and_port)
1337 with server:
1337 with server:
1338 vcs_pid = server.get_pid()
1338 vcs_pid = server.get_pid()
1339 server.run_gc()
1339 server.run_gc()
1340 vcs_process = psutil.Process(vcs_pid)
1340 vcs_process = psutil.Process(vcs_pid)
1341 mem = vcs_process.memory_info()
1341 mem = vcs_process.memory_info()
1342 client.tag_before('vcsserver.rss', mem.rss)
1342 client.tag_before('vcsserver.rss', mem.rss)
1343 client.tag_before('vcsserver.vms', mem.vms)
1343 client.tag_before('vcsserver.vms', mem.vms)
1344
1344
1345 test_process = psutil.Process()
1345 test_process = psutil.Process()
1346 mem = test_process.memory_info()
1346 mem = test_process.memory_info()
1347 client.tag_before('test.rss', mem.rss)
1347 client.tag_before('test.rss', mem.rss)
1348 client.tag_before('test.vms', mem.vms)
1348 client.tag_before('test.vms', mem.vms)
1349
1349
1350 client.tag_before('time', time.time())
1350 client.tag_before('time', time.time())
1351
1351
1352 @request.addfinalizer
1352 @request.addfinalizer
1353 def send_stats():
1353 def send_stats():
1354 client.tag_after('time', time.time())
1354 client.tag_after('time', time.time())
1355 with server:
1355 with server:
1356 gc_stats = server.run_gc()
1356 gc_stats = server.run_gc()
1357 for tag, value in gc_stats.items():
1357 for tag, value in gc_stats.items():
1358 client.tag_after(tag, value)
1358 client.tag_after(tag, value)
1359 mem = vcs_process.memory_info()
1359 mem = vcs_process.memory_info()
1360 client.tag_after('vcsserver.rss', mem.rss)
1360 client.tag_after('vcsserver.rss', mem.rss)
1361 client.tag_after('vcsserver.vms', mem.vms)
1361 client.tag_after('vcsserver.vms', mem.vms)
1362
1362
1363 mem = test_process.memory_info()
1363 mem = test_process.memory_info()
1364 client.tag_after('test.rss', mem.rss)
1364 client.tag_after('test.rss', mem.rss)
1365 client.tag_after('test.vms', mem.vms)
1365 client.tag_after('test.vms', mem.vms)
1366
1366
1367 client.collect({
1367 client.collect({
1368 'message': "Finished",
1368 'message': "Finished",
1369 })
1369 })
1370 client.send_stats()
1370 client.send_stats()
1371
1371
1372 return client
1372 return client
1373
1373
1374
1374
1375 class AppenlightClient():
1375 class AppenlightClient():
1376
1376
1377 url_template = '{url}?protocol_version=0.5'
1377 url_template = '{url}?protocol_version=0.5'
1378
1378
1379 def __init__(
1379 def __init__(
1380 self, url, api_key, add_server=True, add_timestamp=True,
1380 self, url, api_key, add_server=True, add_timestamp=True,
1381 namespace=None, request=None, testrun=None):
1381 namespace=None, request=None, testrun=None):
1382 self.url = self.url_template.format(url=url)
1382 self.url = self.url_template.format(url=url)
1383 self.api_key = api_key
1383 self.api_key = api_key
1384 self.add_server = add_server
1384 self.add_server = add_server
1385 self.add_timestamp = add_timestamp
1385 self.add_timestamp = add_timestamp
1386 self.namespace = namespace
1386 self.namespace = namespace
1387 self.request = request
1387 self.request = request
1388 self.server = socket.getfqdn(socket.gethostname())
1388 self.server = socket.getfqdn(socket.gethostname())
1389 self.tags_before = {}
1389 self.tags_before = {}
1390 self.tags_after = {}
1390 self.tags_after = {}
1391 self.stats = []
1391 self.stats = []
1392 self.testrun = testrun or {}
1392 self.testrun = testrun or {}
1393
1393
1394 def tag_before(self, tag, value):
1394 def tag_before(self, tag, value):
1395 self.tags_before[tag] = value
1395 self.tags_before[tag] = value
1396
1396
1397 def tag_after(self, tag, value):
1397 def tag_after(self, tag, value):
1398 self.tags_after[tag] = value
1398 self.tags_after[tag] = value
1399
1399
1400 def collect(self, data):
1400 def collect(self, data):
1401 if self.add_server:
1401 if self.add_server:
1402 data.setdefault('server', self.server)
1402 data.setdefault('server', self.server)
1403 if self.add_timestamp:
1403 if self.add_timestamp:
1404 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1404 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1405 if self.namespace:
1405 if self.namespace:
1406 data.setdefault('namespace', self.namespace)
1406 data.setdefault('namespace', self.namespace)
1407 if self.request:
1407 if self.request:
1408 data.setdefault('request', self.request)
1408 data.setdefault('request', self.request)
1409 self.stats.append(data)
1409 self.stats.append(data)
1410
1410
1411 def send_stats(self):
1411 def send_stats(self):
1412 tags = [
1412 tags = [
1413 ('testrun', self.request),
1413 ('testrun', self.request),
1414 ('testrun.start', self.testrun['start']),
1414 ('testrun.start', self.testrun['start']),
1415 ('testrun.timestamp', self.testrun['timestamp']),
1415 ('testrun.timestamp', self.testrun['timestamp']),
1416 ('test', self.namespace),
1416 ('test', self.namespace),
1417 ]
1417 ]
1418 for key, value in self.tags_before.items():
1418 for key, value in self.tags_before.items():
1419 tags.append((key + '.before', value))
1419 tags.append((key + '.before', value))
1420 try:
1420 try:
1421 delta = self.tags_after[key] - value
1421 delta = self.tags_after[key] - value
1422 tags.append((key + '.delta', delta))
1422 tags.append((key + '.delta', delta))
1423 except Exception:
1423 except Exception:
1424 pass
1424 pass
1425 for key, value in self.tags_after.items():
1425 for key, value in self.tags_after.items():
1426 tags.append((key + '.after', value))
1426 tags.append((key + '.after', value))
1427 self.collect({
1427 self.collect({
1428 'message': "Collected tags",
1428 'message': "Collected tags",
1429 'tags': tags,
1429 'tags': tags,
1430 })
1430 })
1431
1431
1432 response = requests.post(
1432 response = requests.post(
1433 self.url,
1433 self.url,
1434 headers={
1434 headers={
1435 'X-appenlight-api-key': self.api_key},
1435 'X-appenlight-api-key': self.api_key},
1436 json=self.stats,
1436 json=self.stats,
1437 )
1437 )
1438
1438
1439 if not response.status_code == 200:
1439 if not response.status_code == 200:
1440 pprint.pprint(self.stats)
1440 pprint.pprint(self.stats)
1441 print response.headers
1441 print response.headers
1442 print response.text
1442 print response.text
1443 raise Exception('Sending to appenlight failed')
1443 raise Exception('Sending to appenlight failed')
1444
1444
1445
1445
1446 @pytest.fixture
1446 @pytest.fixture
1447 def gist_util(request, pylonsapp):
1447 def gist_util(request, pylonsapp):
1448 """
1448 """
1449 Provides a wired instance of `GistUtility` with integrated cleanup.
1449 Provides a wired instance of `GistUtility` with integrated cleanup.
1450 """
1450 """
1451 utility = GistUtility()
1451 utility = GistUtility()
1452 request.addfinalizer(utility.cleanup)
1452 request.addfinalizer(utility.cleanup)
1453 return utility
1453 return utility
1454
1454
1455
1455
1456 class GistUtility(object):
1456 class GistUtility(object):
1457 def __init__(self):
1457 def __init__(self):
1458 self.fixture = Fixture()
1458 self.fixture = Fixture()
1459 self.gist_ids = []
1459 self.gist_ids = []
1460
1460
1461 def create_gist(self, **kwargs):
1461 def create_gist(self, **kwargs):
1462 gist = self.fixture.create_gist(**kwargs)
1462 gist = self.fixture.create_gist(**kwargs)
1463 self.gist_ids.append(gist.gist_id)
1463 self.gist_ids.append(gist.gist_id)
1464 return gist
1464 return gist
1465
1465
1466 def cleanup(self):
1466 def cleanup(self):
1467 for id_ in self.gist_ids:
1467 for id_ in self.gist_ids:
1468 self.fixture.destroy_gists(str(id_))
1468 self.fixture.destroy_gists(str(id_))
1469
1469
1470
1470
1471 @pytest.fixture
1471 @pytest.fixture
1472 def enabled_backends(request):
1472 def enabled_backends(request):
1473 backends = request.config.option.backends
1473 backends = request.config.option.backends
1474 return backends[:]
1474 return backends[:]
1475
1475
1476
1476
1477 @pytest.fixture
1477 @pytest.fixture
1478 def settings_util(request):
1478 def settings_util(request):
1479 """
1479 """
1480 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1480 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1481 """
1481 """
1482 utility = SettingsUtility()
1482 utility = SettingsUtility()
1483 request.addfinalizer(utility.cleanup)
1483 request.addfinalizer(utility.cleanup)
1484 return utility
1484 return utility
1485
1485
1486
1486
1487 class SettingsUtility(object):
1487 class SettingsUtility(object):
1488 def __init__(self):
1488 def __init__(self):
1489 self.rhodecode_ui_ids = []
1489 self.rhodecode_ui_ids = []
1490 self.rhodecode_setting_ids = []
1490 self.rhodecode_setting_ids = []
1491 self.repo_rhodecode_ui_ids = []
1491 self.repo_rhodecode_ui_ids = []
1492 self.repo_rhodecode_setting_ids = []
1492 self.repo_rhodecode_setting_ids = []
1493
1493
1494 def create_repo_rhodecode_ui(
1494 def create_repo_rhodecode_ui(
1495 self, repo, section, value, key=None, active=True, cleanup=True):
1495 self, repo, section, value, key=None, active=True, cleanup=True):
1496 key = key or hashlib.sha1(
1496 key = key or hashlib.sha1(
1497 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1497 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1498
1498
1499 setting = RepoRhodeCodeUi()
1499 setting = RepoRhodeCodeUi()
1500 setting.repository_id = repo.repo_id
1500 setting.repository_id = repo.repo_id
1501 setting.ui_section = section
1501 setting.ui_section = section
1502 setting.ui_value = value
1502 setting.ui_value = value
1503 setting.ui_key = key
1503 setting.ui_key = key
1504 setting.ui_active = active
1504 setting.ui_active = active
1505 Session().add(setting)
1505 Session().add(setting)
1506 Session().commit()
1506 Session().commit()
1507
1507
1508 if cleanup:
1508 if cleanup:
1509 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1509 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1510 return setting
1510 return setting
1511
1511
1512 def create_rhodecode_ui(
1512 def create_rhodecode_ui(
1513 self, section, value, key=None, active=True, cleanup=True):
1513 self, section, value, key=None, active=True, cleanup=True):
1514 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1514 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1515
1515
1516 setting = RhodeCodeUi()
1516 setting = RhodeCodeUi()
1517 setting.ui_section = section
1517 setting.ui_section = section
1518 setting.ui_value = value
1518 setting.ui_value = value
1519 setting.ui_key = key
1519 setting.ui_key = key
1520 setting.ui_active = active
1520 setting.ui_active = active
1521 Session().add(setting)
1521 Session().add(setting)
1522 Session().commit()
1522 Session().commit()
1523
1523
1524 if cleanup:
1524 if cleanup:
1525 self.rhodecode_ui_ids.append(setting.ui_id)
1525 self.rhodecode_ui_ids.append(setting.ui_id)
1526 return setting
1526 return setting
1527
1527
1528 def create_repo_rhodecode_setting(
1528 def create_repo_rhodecode_setting(
1529 self, repo, name, value, type_, cleanup=True):
1529 self, repo, name, value, type_, cleanup=True):
1530 setting = RepoRhodeCodeSetting(
1530 setting = RepoRhodeCodeSetting(
1531 repo.repo_id, key=name, val=value, type=type_)
1531 repo.repo_id, key=name, val=value, type=type_)
1532 Session().add(setting)
1532 Session().add(setting)
1533 Session().commit()
1533 Session().commit()
1534
1534
1535 if cleanup:
1535 if cleanup:
1536 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1536 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1537 return setting
1537 return setting
1538
1538
1539 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1539 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1540 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1540 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1541 Session().add(setting)
1541 Session().add(setting)
1542 Session().commit()
1542 Session().commit()
1543
1543
1544 if cleanup:
1544 if cleanup:
1545 self.rhodecode_setting_ids.append(setting.app_settings_id)
1545 self.rhodecode_setting_ids.append(setting.app_settings_id)
1546
1546
1547 return setting
1547 return setting
1548
1548
1549 def cleanup(self):
1549 def cleanup(self):
1550 for id_ in self.rhodecode_ui_ids:
1550 for id_ in self.rhodecode_ui_ids:
1551 setting = RhodeCodeUi.get(id_)
1551 setting = RhodeCodeUi.get(id_)
1552 Session().delete(setting)
1552 Session().delete(setting)
1553
1553
1554 for id_ in self.rhodecode_setting_ids:
1554 for id_ in self.rhodecode_setting_ids:
1555 setting = RhodeCodeSetting.get(id_)
1555 setting = RhodeCodeSetting.get(id_)
1556 Session().delete(setting)
1556 Session().delete(setting)
1557
1557
1558 for id_ in self.repo_rhodecode_ui_ids:
1558 for id_ in self.repo_rhodecode_ui_ids:
1559 setting = RepoRhodeCodeUi.get(id_)
1559 setting = RepoRhodeCodeUi.get(id_)
1560 Session().delete(setting)
1560 Session().delete(setting)
1561
1561
1562 for id_ in self.repo_rhodecode_setting_ids:
1562 for id_ in self.repo_rhodecode_setting_ids:
1563 setting = RepoRhodeCodeSetting.get(id_)
1563 setting = RepoRhodeCodeSetting.get(id_)
1564 Session().delete(setting)
1564 Session().delete(setting)
1565
1565
1566 Session().commit()
1566 Session().commit()
1567
1567
1568
1568
1569 @pytest.fixture
1569 @pytest.fixture
1570 def no_notifications(request):
1570 def no_notifications(request):
1571 notification_patcher = mock.patch(
1571 notification_patcher = mock.patch(
1572 'rhodecode.model.notification.NotificationModel.create')
1572 'rhodecode.model.notification.NotificationModel.create')
1573 notification_patcher.start()
1573 notification_patcher.start()
1574 request.addfinalizer(notification_patcher.stop)
1574 request.addfinalizer(notification_patcher.stop)
1575
1575
1576
1576
1577 @pytest.fixture
1577 @pytest.fixture
1578 def silence_action_logger(request):
1578 def silence_action_logger(request):
1579 notification_patcher = mock.patch(
1579 notification_patcher = mock.patch(
1580 'rhodecode.lib.utils.action_logger')
1580 'rhodecode.lib.utils.action_logger')
1581 notification_patcher.start()
1581 notification_patcher.start()
1582 request.addfinalizer(notification_patcher.stop)
1582 request.addfinalizer(notification_patcher.stop)
1583
1583
1584
1584
1585 @pytest.fixture(scope='session')
1585 @pytest.fixture(scope='session')
1586 def repeat(request):
1586 def repeat(request):
1587 """
1587 """
1588 The number of repetitions is based on this fixture.
1588 The number of repetitions is based on this fixture.
1589
1589
1590 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1590 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1591 tests are not too slow in our default test suite.
1591 tests are not too slow in our default test suite.
1592 """
1592 """
1593 return request.config.getoption('--repeat')
1593 return request.config.getoption('--repeat')
1594
1594
1595
1595
1596 @pytest.fixture
1596 @pytest.fixture
1597 def rhodecode_fixtures():
1597 def rhodecode_fixtures():
1598 return Fixture()
1598 return Fixture()
1599
1599
1600
1600
1601 @pytest.fixture
1601 @pytest.fixture
1602 def request_stub():
1602 def request_stub():
1603 """
1603 """
1604 Stub request object.
1604 Stub request object.
1605 """
1605 """
1606 request = pyramid.testing.DummyRequest()
1606 request = pyramid.testing.DummyRequest()
1607 request.scheme = 'https'
1607 request.scheme = 'https'
1608 return request
1608 return request
1609
1609
1610
1610
1611 @pytest.fixture
1611 @pytest.fixture
1612 def config_stub(request, request_stub):
1612 def config_stub(request, request_stub):
1613 """
1613 """
1614 Set up pyramid.testing and return the Configurator.
1614 Set up pyramid.testing and return the Configurator.
1615 """
1615 """
1616 config = pyramid.testing.setUp(request=request_stub)
1616 config = pyramid.testing.setUp(request=request_stub)
1617
1617
1618 @request.addfinalizer
1618 @request.addfinalizer
1619 def cleanup():
1619 def cleanup():
1620 pyramid.testing.tearDown()
1620 pyramid.testing.tearDown()
1621
1621
1622 return config
1622 return config
General Comments 0
You need to be logged in to leave comments. Login now