##// END OF EJS Templates
release: Merge default into stable for release preparation
marcink -
r655:3c0cb104 merge stable
parent child Browse files
Show More
@@ -0,0 +1,6 b''
1 {% extends "!footer.html" %}
2
3 {% block extrafooter %}
4 <br/>
5 Documentation defects and suggestions can be submitted <a href="https://issues.rhodecode.com/projects/documentation">here</a>
6 {% endblock %}
@@ -0,0 +1,77 b''
1 .. _deprecated-methods-ref:
2
3 deprecated methods
4 =================
5
6 changeset_comment
7 -----------------
8
9 .. py:function:: changeset_comment(apiuser, repoid, revision, message, userid=<Optional:<OptionalAttr:apiuser>>, status=<Optional:None>)
10
11 .. deprecated:: 3.4.0
12
13 Please use method `comment_commit` instead.
14
15
16 Set a changeset comment, and optionally change the status of the
17 changeset.
18
19 This command can only be run using an |authtoken| with admin
20 permissions on the |repo|.
21
22 :param apiuser: This is filled automatically from the |authtoken|.
23 :type apiuser: AuthUser
24 :param repoid: Set the repository name or repository ID.
25 :type repoid: str or int
26 :param revision: Specify the revision for which to set a comment.
27 :type revision: str
28 :param message: The comment text.
29 :type message: str
30 :param userid: Set the user name of the comment creator.
31 :type userid: Optional(str or int)
32 :param status: Set the comment status. The following are valid options:
33 * not_reviewed
34 * approved
35 * rejected
36 * under_review
37 :type status: str
38
39 Example error output:
40
41 .. code-block:: json
42
43 {
44 "id" : <id_given_in_input>,
45 "result" : {
46 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
47 "status_change": null or <status>,
48 "success": true
49 },
50 "error" : null
51 }
52
53
54 get_locks
55 ---------
56
57 .. py:function:: get_locks(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
58
59 .. deprecated:: 4.0.0
60
61 Please use method `get_user_locks` instead.
62
63 None
64
65
66 show_ip
67 -------
68
69 .. py:function:: show_ip(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
70
71 .. deprecated:: 4.0.0
72
73 Please use method `get_ip` instead.
74
75 None
76
77
@@ -0,0 +1,121 b''
1 .. _gist-methods-ref:
2
3 gist methods
4 =================
5
6 create_gist
7 -----------
8
9 .. py:function:: create_gist(apiuser, files, gistid=<Optional:None>, owner=<Optional:<OptionalAttr:apiuser>>, gist_type=<Optional:u'public'>, lifetime=<Optional:-1>, acl_level=<Optional:u'acl_public'>, description=<Optional:''>)
10
11 Creates a new Gist.
12
13 :param apiuser: This is filled automatically from the |authtoken|.
14 :type apiuser: AuthUser
15 :param files: files to be added to the gist. The data structure has
16 to match the following example::
17
18 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
19
20 :type files: dict
21 :param gistid: Set a custom id for the gist
22 :type gistid: Optional(str)
23 :param owner: Set the gist owner, defaults to api method caller
24 :type owner: Optional(str or int)
25 :param gist_type: type of gist ``public`` or ``private``
26 :type gist_type: Optional(str)
27 :param lifetime: time in minutes of gist lifetime
28 :type lifetime: Optional(int)
29 :param acl_level: acl level for this gist, can be
30 ``acl_public`` or ``acl_private`` If the value is set to
31 ``acl_private`` only logged in users are able to access this gist.
32 If not set it defaults to ``acl_public``.
33 :type acl_level: Optional(str)
34 :param description: gist description
35 :type description: Optional(str)
36
37 Example output:
38
39 .. code-block:: bash
40
41 id : <id_given_in_input>
42 result : {
43 "msg": "created new gist",
44 "gist": {}
45 }
46 error : null
47
48 Example error output:
49
50 .. code-block:: bash
51
52 id : <id_given_in_input>
53 result : null
54 error : {
55 "failed to create gist"
56 }
57
58
59 delete_gist
60 -----------
61
62 .. py:function:: delete_gist(apiuser, gistid)
63
64 Deletes existing gist
65
66 :param apiuser: filled automatically from apikey
67 :type apiuser: AuthUser
68 :param gistid: id of gist to delete
69 :type gistid: str
70
71 Example output:
72
73 .. code-block:: bash
74
75 id : <id_given_in_input>
76 result : {
77 "deleted gist ID: <gist_id>",
78 "gist": null
79 }
80 error : null
81
82 Example error output:
83
84 .. code-block:: bash
85
86 id : <id_given_in_input>
87 result : null
88 error : {
89 "failed to delete gist ID:<gist_id>"
90 }
91
92
93 get_gist
94 --------
95
96 .. py:function:: get_gist(apiuser, gistid, content=<Optional:False>)
97
98 Get the specified gist, based on the gist ID.
99
100 :param apiuser: This is filled automatically from the |authtoken|.
101 :type apiuser: AuthUser
102 :param gistid: Set the id of the private or public gist
103 :type gistid: str
104 :param content: Return the gist content. Default is false.
105 :type content: Optional(bool)
106
107
108 get_gists
109 ---------
110
111 .. py:function:: get_gists(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
112
113 Get all gists for given user. If userid is empty returned gists
114 are for user who called the api
115
116 :param apiuser: This is filled automatically from the |authtoken|.
117 :type apiuser: AuthUser
118 :param userid: user to get gists for
119 :type userid: Optional(str or int)
120
121
@@ -0,0 +1,71 b''
1 .. _license-methods-ref:
2
3 license methods
4 =================
5
6 get_license_info (EE only)
7 ----------------
8
9 .. py:function:: get_license_info(apiuser)
10
11 Returns the |RCE| license information.
12
13 :param apiuser: This is filled automatically from the |authtoken|.
14 :type apiuser: AuthUser
15
16 Example output:
17
18 .. code-block:: bash
19
20 id : <id_given_in_input>
21 result : {
22 'rhodecode_version': <rhodecode version>,
23 'token': <license token>,
24 'issued_to': <license owner>,
25 'issued_on': <license issue date>,
26 'expires_on': <license expiration date>,
27 'type': <license type>,
28 'users_limit': <license users limit>,
29 'key': <license key>
30 }
31 error : null
32
33
34 set_license_key (EE only)
35 ---------------
36
37 .. py:function:: set_license_key(apiuser, key)
38
39 Sets the |RCE| license key.
40
41 :param apiuser: This is filled automatically from the |authtoken|.
42 :type apiuser: AuthUser
43 :param key: This is the license key to be set.
44 :type key: str
45
46 Example output:
47
48 .. code-block:: bash
49
50 id : <id_given_in_input>
51 result: {
52 "msg" : "updated license information",
53 "key": <key>
54 }
55 error: null
56
57 Example error output:
58
59 .. code-block:: bash
60
61 id : <id_given_in_input>
62 result : null
63 error : {
64 "license key is not valid"
65 or
66 "trial licenses cannot be uploaded"
67 or
68 "error occurred while updating license"
69 }
70
71
@@ -0,0 +1,344 b''
1 .. _pull-request-methods-ref:
2
3 pull_request methods
4 =================
5
6 close_pull_request
7 ------------------
8
9 .. py:function:: close_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
10
11 Close the pull request specified by `pullrequestid`.
12
13 :param apiuser: This is filled automatically from the |authtoken|.
14 :type apiuser: AuthUser
15 :param repoid: Repository name or repository ID to which the pull
16 request belongs.
17 :type repoid: str or int
18 :param pullrequestid: ID of the pull request to be closed.
19 :type pullrequestid: int
20 :param userid: Close the pull request as this user.
21 :type userid: Optional(str or int)
22
23 Example output:
24
25 .. code-block:: bash
26
27 "id": <id_given_in_input>,
28 "result":
29 {
30 "pull_request_id": "<int>",
31 "closed": "<bool>"
32 },
33 "error": null
34
35
36 comment_pull_request
37 --------------------
38
39 .. py:function:: comment_pull_request(apiuser, repoid, pullrequestid, message=<Optional:None>, status=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
40
41 Comment on the pull request specified with the `pullrequestid`,
42 in the |repo| specified by the `repoid`, and optionally change the
43 review status.
44
45 :param apiuser: This is filled automatically from the |authtoken|.
46 :type apiuser: AuthUser
47 :param repoid: The repository name or repository ID.
48 :type repoid: str or int
49 :param pullrequestid: The pull request ID.
50 :type pullrequestid: int
51 :param message: The text content of the comment.
52 :type message: str
53 :param status: (**Optional**) Set the approval status of the pull
54 request. Valid options are:
55 * not_reviewed
56 * approved
57 * rejected
58 * under_review
59 :type status: str
60 :param userid: Comment on the pull request as this user
61 :type userid: Optional(str or int)
62
63 Example output:
64
65 .. code-block:: bash
66
67 id : <id_given_in_input>
68 result :
69 {
70 "pull_request_id": "<Integer>",
71 "comment_id": "<Integer>"
72 }
73 error : null
74
75
76 create_pull_request
77 -------------------
78
79 .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, title, description=<Optional:''>, reviewers=<Optional:None>)
80
81 Creates a new pull request.
82
83 Accepts refs in the following formats:
84
85 * branch:<branch_name>:<sha>
86 * branch:<branch_name>
87 * bookmark:<bookmark_name>:<sha> (Mercurial only)
88 * bookmark:<bookmark_name> (Mercurial only)
89
90 :param apiuser: This is filled automatically from the |authtoken|.
91 :type apiuser: AuthUser
92 :param source_repo: Set the source repository name.
93 :type source_repo: str
94 :param target_repo: Set the target repository name.
95 :type target_repo: str
96 :param source_ref: Set the source ref name.
97 :type source_ref: str
98 :param target_ref: Set the target ref name.
99 :type target_ref: str
100 :param title: Set the pull request title.
101 :type title: str
102 :param description: Set the pull request description.
103 :type description: Optional(str)
104 :param reviewers: Set the new pull request reviewers list.
105 :type reviewers: Optional(list)
106
107
108 get_pull_request
109 ----------------
110
111 .. py:function:: get_pull_request(apiuser, repoid, pullrequestid)
112
113 Get a pull request based on the given ID.
114
115 :param apiuser: This is filled automatically from the |authtoken|.
116 :type apiuser: AuthUser
117 :param repoid: Repository name or repository ID from where the pull
118 request was opened.
119 :type repoid: str or int
120 :param pullrequestid: ID of the requested pull request.
121 :type pullrequestid: int
122
123 Example output:
124
125 .. code-block:: bash
126
127 "id": <id_given_in_input>,
128 "result":
129 {
130 "pull_request_id": "<pull_request_id>",
131 "url": "<url>",
132 "title": "<title>",
133 "description": "<description>",
134 "status" : "<status>",
135 "created_on": "<date_time_created>",
136 "updated_on": "<date_time_updated>",
137 "commit_ids": [
138 ...
139 "<commit_id>",
140 "<commit_id>",
141 ...
142 ],
143 "review_status": "<review_status>",
144 "mergeable": {
145 "status": "<bool>",
146 "message": "<message>",
147 },
148 "source": {
149 "clone_url": "<clone_url>",
150 "repository": "<repository_name>",
151 "reference":
152 {
153 "name": "<name>",
154 "type": "<type>",
155 "commit_id": "<commit_id>",
156 }
157 },
158 "target": {
159 "clone_url": "<clone_url>",
160 "repository": "<repository_name>",
161 "reference":
162 {
163 "name": "<name>",
164 "type": "<type>",
165 "commit_id": "<commit_id>",
166 }
167 },
168 "author": <user_obj>,
169 "reviewers": [
170 ...
171 {
172 "user": "<user_obj>",
173 "review_status": "<review_status>",
174 }
175 ...
176 ]
177 },
178 "error": null
179
180
181 get_pull_requests
182 -----------------
183
184 .. py:function:: get_pull_requests(apiuser, repoid, status=<Optional:'new'>)
185
186 Get all pull requests from the repository specified in `repoid`.
187
188 :param apiuser: This is filled automatically from the |authtoken|.
189 :type apiuser: AuthUser
190 :param repoid: Repository name or repository ID.
191 :type repoid: str or int
192 :param status: Only return pull requests with the specified status.
193 Valid options are.
194 * ``new`` (default)
195 * ``open``
196 * ``closed``
197 :type status: str
198
199 Example output:
200
201 .. code-block:: bash
202
203 "id": <id_given_in_input>,
204 "result":
205 [
206 ...
207 {
208 "pull_request_id": "<pull_request_id>",
209 "url": "<url>",
210 "title" : "<title>",
211 "description": "<description>",
212 "status": "<status>",
213 "created_on": "<date_time_created>",
214 "updated_on": "<date_time_updated>",
215 "commit_ids": [
216 ...
217 "<commit_id>",
218 "<commit_id>",
219 ...
220 ],
221 "review_status": "<review_status>",
222 "mergeable": {
223 "status": "<bool>",
224 "message: "<message>",
225 },
226 "source": {
227 "clone_url": "<clone_url>",
228 "reference":
229 {
230 "name": "<name>",
231 "type": "<type>",
232 "commit_id": "<commit_id>",
233 }
234 },
235 "target": {
236 "clone_url": "<clone_url>",
237 "reference":
238 {
239 "name": "<name>",
240 "type": "<type>",
241 "commit_id": "<commit_id>",
242 }
243 },
244 "author": <user_obj>,
245 "reviewers": [
246 ...
247 {
248 "user": "<user_obj>",
249 "review_status": "<review_status>",
250 }
251 ...
252 ]
253 }
254 ...
255 ],
256 "error": null
257
258
259 merge_pull_request
260 ------------------
261
262 .. py:function:: merge_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
263
264 Merge the pull request specified by `pullrequestid` into its target
265 repository.
266
267 :param apiuser: This is filled automatically from the |authtoken|.
268 :type apiuser: AuthUser
269 :param repoid: The Repository name or repository ID of the
270 target repository to which the |pr| is to be merged.
271 :type repoid: str or int
272 :param pullrequestid: ID of the pull request which shall be merged.
273 :type pullrequestid: int
274 :param userid: Merge the pull request as this user.
275 :type userid: Optional(str or int)
276
277 Example output:
278
279 .. code-block:: bash
280
281 "id": <id_given_in_input>,
282 "result":
283 {
284 "executed": "<bool>",
285 "failure_reason": "<int>",
286 "merge_commit_id": "<merge_commit_id>",
287 "possible": "<bool>"
288 },
289 "error": null
290
291
292 update_pull_request
293 -------------------
294
295 .. py:function:: update_pull_request(apiuser, repoid, pullrequestid, title=<Optional:''>, description=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>, close_pull_request=<Optional:None>)
296
297 Updates a pull request.
298
299 :param apiuser: This is filled automatically from the |authtoken|.
300 :type apiuser: AuthUser
301 :param repoid: The repository name or repository ID.
302 :type repoid: str or int
303 :param pullrequestid: The pull request ID.
304 :type pullrequestid: int
305 :param title: Set the pull request title.
306 :type title: str
307 :param description: Update pull request description.
308 :type description: Optional(str)
309 :param reviewers: Update pull request reviewers list with new value.
310 :type reviewers: Optional(list)
311 :param update_commits: Trigger update of commits for this pull request
312 :type: update_commits: Optional(bool)
313 :param close_pull_request: Close this pull request with rejected state
314 :type: close_pull_request: Optional(bool)
315
316 Example output:
317
318 .. code-block:: bash
319
320 id : <id_given_in_input>
321 result :
322 {
323 "msg": "Updated pull request `63`",
324 "pull_request": <pull_request_object>,
325 "updated_reviewers": {
326 "added": [
327 "username"
328 ],
329 "removed": []
330 },
331 "updated_commits": {
332 "added": [
333 "<sha1_hash>"
334 ],
335 "common": [
336 "<sha1_hash>",
337 "<sha1_hash>",
338 ],
339 "removed": []
340 }
341 }
342 error : null
343
344
@@ -0,0 +1,350 b''
1 .. _repo-group-methods-ref:
2
3 repo_group methods
4 =================
5
6 create_repo_group
7 -----------------
8
9 .. py:function:: create_repo_group(apiuser, group_name, description=<Optional:''>, owner=<Optional:<OptionalAttr:apiuser>>, copy_permissions=<Optional:False>)
10
11 Creates a repository group.
12
13 * If the repository group name contains "/", all the required repository
14 groups will be created.
15
16 For example "foo/bar/baz" will create |repo| groups "foo" and "bar"
17 (with "foo" as parent). It will also create the "baz" repository
18 with "bar" as |repo| group.
19
20 This command can only be run using an |authtoken| with admin
21 permissions.
22
23 :param apiuser: This is filled automatically from the |authtoken|.
24 :type apiuser: AuthUser
25 :param group_name: Set the repository group name.
26 :type group_name: str
27 :param description: Set the |repo| group description.
28 :type description: str
29 :param owner: Set the |repo| group owner.
30 :type owner: str
31 :param copy_permissions:
32 :type copy_permissions:
33
34 Example output:
35
36 .. code-block:: bash
37
38 id : <id_given_in_input>
39 result : {
40 "msg": "Created new repo group `<repo_group_name>`"
41 "repo_group": <repogroup_object>
42 }
43 error : null
44
45
46 Example error output:
47
48 .. code-block:: bash
49
50 id : <id_given_in_input>
51 result : null
52 error : {
53 failed to create repo group `<repogroupid>`
54 }
55
56
57 delete_repo_group
58 -----------------
59
60 .. py:function:: delete_repo_group(apiuser, repogroupid)
61
62 Deletes a |repo| group.
63
64 :param apiuser: This is filled automatically from the |authtoken|.
65 :type apiuser: AuthUser
66 :param repogroupid: Set the name or ID of repository group to be
67 deleted.
68 :type repogroupid: str or int
69
70 Example output:
71
72 .. code-block:: bash
73
74 id : <id_given_in_input>
75 result : {
76 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
77 'repo_group': null
78 }
79 error : null
80
81 Example error output:
82
83 .. code-block:: bash
84
85 id : <id_given_in_input>
86 result : null
87 error : {
88 "failed to delete repo group ID:<repogroupid> <repogroupname>"
89 }
90
91
92 get_repo_group
93 --------------
94
95 .. py:function:: get_repo_group(apiuser, repogroupid)
96
97 Return the specified |repo| group, along with permissions,
98 and repositories inside the group
99
100 :param apiuser: This is filled automatically from the |authtoken|.
101 :type apiuser: AuthUser
102 :param repogroupid: Specify the name of ID of the repository group.
103 :type repogroupid: str or int
104
105
106 Example output:
107
108 .. code-block:: bash
109
110 {
111 "error": null,
112 "id": repo-group-id,
113 "result": {
114 "group_description": "repo group description",
115 "group_id": 14,
116 "group_name": "group name",
117 "members": [
118 {
119 "name": "super-admin-username",
120 "origin": "super-admin",
121 "permission": "group.admin",
122 "type": "user"
123 },
124 {
125 "name": "owner-name",
126 "origin": "owner",
127 "permission": "group.admin",
128 "type": "user"
129 },
130 {
131 "name": "user-group-name",
132 "origin": "permission",
133 "permission": "group.write",
134 "type": "user_group"
135 }
136 ],
137 "owner": "owner-name",
138 "parent_group": null,
139 "repositories": [ repo-list ]
140 }
141 }
142
143
144 get_repo_groups
145 ---------------
146
147 .. py:function:: get_repo_groups(apiuser)
148
149 Returns all repository groups.
150
151 :param apiuser: This is filled automatically from the |authtoken|.
152 :type apiuser: AuthUser
153
154
155 grant_user_group_permission_to_repo_group
156 -----------------------------------------
157
158 .. py:function:: grant_user_group_permission_to_repo_group(apiuser, repogroupid, usergroupid, perm, apply_to_children=<Optional:'none'>)
159
160 Grant permission for a user group on given repository group, or update
161 existing permissions if found.
162
163 This command can only be run using an |authtoken| with admin
164 permissions on the |repo| group.
165
166 :param apiuser: This is filled automatically from the |authtoken|.
167 :type apiuser: AuthUser
168 :param repogroupid: Set the name or id of repository group
169 :type repogroupid: str or int
170 :param usergroupid: id of usergroup
171 :type usergroupid: str or int
172 :param perm: (group.(none|read|write|admin))
173 :type perm: str
174 :param apply_to_children: 'none', 'repos', 'groups', 'all'
175 :type apply_to_children: str
176
177 Example output:
178
179 .. code-block:: bash
180
181 id : <id_given_in_input>
182 result : {
183 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
184 "success": true
185
186 }
187 error : null
188
189 Example error output:
190
191 .. code-block:: bash
192
193 id : <id_given_in_input>
194 result : null
195 error : {
196 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
197 }
198
199
200 grant_user_permission_to_repo_group
201 -----------------------------------
202
203 .. py:function:: grant_user_permission_to_repo_group(apiuser, repogroupid, userid, perm, apply_to_children=<Optional:'none'>)
204
205 Grant permission for a user on the given repository group, or update
206 existing permissions if found.
207
208 This command can only be run using an |authtoken| with admin
209 permissions.
210
211 :param apiuser: This is filled automatically from the |authtoken|.
212 :type apiuser: AuthUser
213 :param repogroupid: Set the name or ID of repository group.
214 :type repogroupid: str or int
215 :param userid: Set the user name.
216 :type userid: str
217 :param perm: (group.(none|read|write|admin))
218 :type perm: str
219 :param apply_to_children: 'none', 'repos', 'groups', 'all'
220 :type apply_to_children: str
221
222 Example output:
223
224 .. code-block:: bash
225
226 id : <id_given_in_input>
227 result: {
228 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
229 "success": true
230 }
231 error: null
232
233 Example error output:
234
235 .. code-block:: bash
236
237 id : <id_given_in_input>
238 result : null
239 error : {
240 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
241 }
242
243
244 revoke_user_group_permission_from_repo_group
245 --------------------------------------------
246
247 .. py:function:: revoke_user_group_permission_from_repo_group(apiuser, repogroupid, usergroupid, apply_to_children=<Optional:'none'>)
248
249 Revoke permission for user group on given repository.
250
251 This command can only be run using an |authtoken| with admin
252 permissions on the |repo| group.
253
254 :param apiuser: This is filled automatically from the |authtoken|.
255 :type apiuser: AuthUser
256 :param repogroupid: name or id of repository group
257 :type repogroupid: str or int
258 :param usergroupid:
259 :param apply_to_children: 'none', 'repos', 'groups', 'all'
260 :type apply_to_children: str
261
262 Example output:
263
264 .. code-block:: bash
265
266 id : <id_given_in_input>
267 result: {
268 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
269 "success": true
270 }
271 error: null
272
273 Example error output:
274
275 .. code-block:: bash
276
277 id : <id_given_in_input>
278 result : null
279 error : {
280 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
281 }
282
283
284 revoke_user_permission_from_repo_group
285 --------------------------------------
286
287 .. py:function:: revoke_user_permission_from_repo_group(apiuser, repogroupid, userid, apply_to_children=<Optional:'none'>)
288
289 Revoke permission for a user in a given repository group.
290
291 This command can only be run using an |authtoken| with admin
292 permissions on the |repo| group.
293
294 :param apiuser: This is filled automatically from the |authtoken|.
295 :type apiuser: AuthUser
296 :param repogroupid: Set the name or ID of the repository group.
297 :type repogroupid: str or int
298 :param userid: Set the user name to revoke.
299 :type userid: str
300 :param apply_to_children: 'none', 'repos', 'groups', 'all'
301 :type apply_to_children: str
302
303 Example output:
304
305 .. code-block:: bash
306
307 id : <id_given_in_input>
308 result: {
309 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
310 "success": true
311 }
312 error: null
313
314 Example error output:
315
316 .. code-block:: bash
317
318 id : <id_given_in_input>
319 result : null
320 error : {
321 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
322 }
323
324
325 update_repo_group
326 -----------------
327
328 .. py:function:: update_repo_group(apiuser, repogroupid, group_name=<Optional:''>, description=<Optional:''>, owner=<Optional:<OptionalAttr:apiuser>>, parent=<Optional:None>, enable_locking=<Optional:False>)
329
330 Updates repository group with the details given.
331
332 This command can only be run using an |authtoken| with admin
333 permissions.
334
335 :param apiuser: This is filled automatically from the |authtoken|.
336 :type apiuser: AuthUser
337 :param repogroupid: Set the ID of repository group.
338 :type repogroupid: str or int
339 :param group_name: Set the name of the |repo| group.
340 :type group_name: str
341 :param description: Set a description for the group.
342 :type description: str
343 :param owner: Set the |repo| group owner.
344 :type owner: str
345 :param parent: Set the |repo| group parent.
346 :type parent: str or int
347 :param enable_locking: Enable |repo| locking. The default is false.
348 :type enable_locking: bool
349
350
This diff has been collapsed as it changes many lines, (967 lines changed) Show them Hide them
@@ -0,0 +1,967 b''
1 .. _repo-methods-ref:
2
3 repo methods
4 =================
5
6 add_field_to_repo
7 -----------------
8
9 .. py:function:: add_field_to_repo(apiuser, repoid, key, label=<Optional:''>, description=<Optional:''>)
10
11 Adds an extra field to a repository.
12
13 This command can only be run using an |authtoken| with at least
14 write permissions to the |repo|.
15
16 :param apiuser: This is filled automatically from the |authtoken|.
17 :type apiuser: AuthUser
18 :param repoid: Set the repository name or repository id.
19 :type repoid: str or int
20 :param key: Create a unique field key for this repository.
21 :type key: str
22 :param label:
23 :type label: Optional(str)
24 :param description:
25 :type description: Optional(str)
26
27
28 comment_commit
29 --------------
30
31 .. py:function:: comment_commit(apiuser, repoid, commit_id, message, userid=<Optional:<OptionalAttr:apiuser>>, status=<Optional:None>)
32
33 Set a commit comment, and optionally change the status of the commit.
34
35 :param apiuser: This is filled automatically from the |authtoken|.
36 :type apiuser: AuthUser
37 :param repoid: Set the repository name or repository ID.
38 :type repoid: str or int
39 :param commit_id: Specify the commit_id for which to set a comment.
40 :type commit_id: str
41 :param message: The comment text.
42 :type message: str
43 :param userid: Set the user name of the comment creator.
44 :type userid: Optional(str or int)
45 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
46 'under_review'
47 :type status: str
48
49 Example error output:
50
51 .. code-block:: json
52
53 {
54 "id" : <id_given_in_input>,
55 "result" : {
56 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
57 "status_change": null or <status>,
58 "success": true
59 },
60 "error" : null
61 }
62
63
64 create_repo
65 -----------
66
67 .. py:function:: create_repo(apiuser, repo_name, repo_type, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, copy_permissions=<Optional:False>)
68
69 Creates a repository.
70
71 * If the repository name contains "/", all the required repository
72 groups will be created.
73
74 For example "foo/bar/baz" will create |repo| groups "foo" and "bar"
75 (with "foo" as parent). It will also create the "baz" repository
76 with "bar" as |repo| group.
77
78 This command can only be run using an |authtoken| with at least
79 write permissions to the |repo|.
80
81 :param apiuser: This is filled automatically from the |authtoken|.
82 :type apiuser: AuthUser
83 :param repo_name: Set the repository name.
84 :type repo_name: str
85 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
86 :type repo_type: str
87 :param owner: user_id or username
88 :type owner: Optional(str)
89 :param description: Set the repository description.
90 :type description: Optional(str)
91 :param private:
92 :type private: bool
93 :param clone_uri:
94 :type clone_uri: str
95 :param landing_rev: <rev_type>:<rev>
96 :type landing_rev: str
97 :param enable_locking:
98 :type enable_locking: bool
99 :param enable_downloads:
100 :type enable_downloads: bool
101 :param enable_statistics:
102 :type enable_statistics: bool
103 :param copy_permissions: Copy permission from group in which the
104 repository is being created.
105 :type copy_permissions: bool
106
107
108 Example output:
109
110 .. code-block:: bash
111
112 id : <id_given_in_input>
113 result: {
114 "msg": "Created new repository `<reponame>`",
115 "success": true,
116 "task": "<celery task id or None if done sync>"
117 }
118 error: null
119
120
121 Example error output:
122
123 .. code-block:: bash
124
125 id : <id_given_in_input>
126 result : null
127 error : {
128 'failed to create repository `<repo_name>`
129 }
130
131
132 delete_repo
133 -----------
134
135 .. py:function:: delete_repo(apiuser, repoid, forks=<Optional:''>)
136
137 Deletes a repository.
138
139 * When the `forks` parameter is set it's possible to detach or delete
140 forks of deleted repository.
141
142 This command can only be run using an |authtoken| with admin
143 permissions on the |repo|.
144
145 :param apiuser: This is filled automatically from the |authtoken|.
146 :type apiuser: AuthUser
147 :param repoid: Set the repository name or repository ID.
148 :type repoid: str or int
149 :param forks: Set to `detach` or `delete` forks from the |repo|.
150 :type forks: Optional(str)
151
152 Example error output:
153
154 .. code-block:: bash
155
156 id : <id_given_in_input>
157 result: {
158 "msg": "Deleted repository `<reponame>`",
159 "success": true
160 }
161 error: null
162
163
164 fork_repo
165 ---------
166
167 .. py:function:: fork_repo(apiuser, repoid, fork_name, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, copy_permissions=<Optional:False>, private=<Optional:False>, landing_rev=<Optional:'rev:tip'>)
168
169 Creates a fork of the specified |repo|.
170
171 * If using |RCE| with Celery this will immediately return a success
172 message, even though the fork will be created asynchronously.
173
174 This command can only be run using an |authtoken| with fork
175 permissions on the |repo|.
176
177 :param apiuser: This is filled automatically from the |authtoken|.
178 :type apiuser: AuthUser
179 :param repoid: Set repository name or repository ID.
180 :type repoid: str or int
181 :param fork_name: Set the fork name.
182 :type fork_name: str
183 :param owner: Set the fork owner.
184 :type owner: str
185 :param description: Set the fork descripton.
186 :type description: str
187 :param copy_permissions: Copy permissions from parent |repo|. The
188 default is False.
189 :type copy_permissions: bool
190 :param private: Make the fork private. The default is False.
191 :type private: bool
192 :param landing_rev: Set the landing revision. The default is tip.
193
194 Example output:
195
196 .. code-block:: bash
197
198 id : <id_for_response>
199 api_key : "<api_key>"
200 args: {
201 "repoid" : "<reponame or repo_id>",
202 "fork_name": "<forkname>",
203 "owner": "<username or user_id = Optional(=apiuser)>",
204 "description": "<description>",
205 "copy_permissions": "<bool>",
206 "private": "<bool>",
207 "landing_rev": "<landing_rev>"
208 }
209
210 Example error output:
211
212 .. code-block:: bash
213
214 id : <id_given_in_input>
215 result: {
216 "msg": "Created fork of `<reponame>` as `<forkname>`",
217 "success": true,
218 "task": "<celery task id or None if done sync>"
219 }
220 error: null
221
222
223 get_repo
224 --------
225
226 .. py:function:: get_repo(apiuser, repoid, cache=<Optional:True>)
227
228 Gets an existing repository by its name or repository_id.
229
230 The members section so the output returns users groups or users
231 associated with that repository.
232
233 This command can only be run using an |authtoken| with admin rights,
234 or users with at least read rights to the |repo|.
235
236 :param apiuser: This is filled automatically from the |authtoken|.
237 :type apiuser: AuthUser
238 :param repoid: The repository name or repository id.
239 :type repoid: str or int
240 :param cache: use the cached value for last changeset
241 :type: cache: Optional(bool)
242
243 Example output:
244
245 .. code-block:: bash
246
247 {
248 "error": null,
249 "id": <repo_id>,
250 "result": {
251 "clone_uri": null,
252 "created_on": "timestamp",
253 "description": "repo description",
254 "enable_downloads": false,
255 "enable_locking": false,
256 "enable_statistics": false,
257 "followers": [
258 {
259 "active": true,
260 "admin": false,
261 "api_key": "****************************************",
262 "api_keys": [
263 "****************************************"
264 ],
265 "email": "user@example.com",
266 "emails": [
267 "user@example.com"
268 ],
269 "extern_name": "rhodecode",
270 "extern_type": "rhodecode",
271 "firstname": "username",
272 "ip_addresses": [],
273 "language": null,
274 "last_login": "2015-09-16T17:16:35.854",
275 "lastname": "surname",
276 "user_id": <user_id>,
277 "username": "name"
278 }
279 ],
280 "fork_of": "parent-repo",
281 "landing_rev": [
282 "rev",
283 "tip"
284 ],
285 "last_changeset": {
286 "author": "User <user@example.com>",
287 "branch": "default",
288 "date": "timestamp",
289 "message": "last commit message",
290 "parents": [
291 {
292 "raw_id": "commit-id"
293 }
294 ],
295 "raw_id": "commit-id",
296 "revision": <revision number>,
297 "short_id": "short id"
298 },
299 "lock_reason": null,
300 "locked_by": null,
301 "locked_date": null,
302 "members": [
303 {
304 "name": "super-admin-name",
305 "origin": "super-admin",
306 "permission": "repository.admin",
307 "type": "user"
308 },
309 {
310 "name": "owner-name",
311 "origin": "owner",
312 "permission": "repository.admin",
313 "type": "user"
314 },
315 {
316 "name": "user-group-name",
317 "origin": "permission",
318 "permission": "repository.write",
319 "type": "user_group"
320 }
321 ],
322 "owner": "owner-name",
323 "permissions": [
324 {
325 "name": "super-admin-name",
326 "origin": "super-admin",
327 "permission": "repository.admin",
328 "type": "user"
329 },
330 {
331 "name": "owner-name",
332 "origin": "owner",
333 "permission": "repository.admin",
334 "type": "user"
335 },
336 {
337 "name": "user-group-name",
338 "origin": "permission",
339 "permission": "repository.write",
340 "type": "user_group"
341 }
342 ],
343 "private": true,
344 "repo_id": 676,
345 "repo_name": "user-group/repo-name",
346 "repo_type": "hg"
347 }
348 }
349
350
351 get_repo_changeset
352 ------------------
353
354 .. py:function:: get_repo_changeset(apiuser, repoid, revision, details=<Optional:'basic'>)
355
356 Returns information about a changeset.
357
358 Additionally parameters define the amount of details returned by
359 this function.
360
361 This command can only be run using an |authtoken| with admin rights,
362 or users with at least read rights to the |repo|.
363
364 :param apiuser: This is filled automatically from the |authtoken|.
365 :type apiuser: AuthUser
366 :param repoid: The repository name or repository id
367 :type repoid: str or int
368 :param revision: revision for which listing should be done
369 :type revision: str
370 :param details: details can be 'basic|extended|full' full gives diff
371 info details like the diff itself, and number of changed files etc.
372 :type details: Optional(str)
373
374
375 get_repo_changesets
376 -------------------
377
378 .. py:function:: get_repo_changesets(apiuser, repoid, start_rev, limit, details=<Optional:'basic'>)
379
380 Returns a set of commits limited by the number starting
381 from the `start_rev` option.
382
383 Additional parameters define the amount of details returned by this
384 function.
385
386 This command can only be run using an |authtoken| with admin rights,
387 or users with at least read rights to |repos|.
388
389 :param apiuser: This is filled automatically from the |authtoken|.
390 :type apiuser: AuthUser
391 :param repoid: The repository name or repository ID.
392 :type repoid: str or int
393 :param start_rev: The starting revision from where to get changesets.
394 :type start_rev: str
395 :param limit: Limit the number of commits to this amount
396 :type limit: str or int
397 :param details: Set the level of detail returned. Valid option are:
398 ``basic``, ``extended`` and ``full``.
399 :type details: Optional(str)
400
401 .. note::
402
403 Setting the parameter `details` to the value ``full`` is extensive
404 and returns details like the diff itself, and the number
405 of changed files.
406
407
408 get_repo_nodes
409 --------------
410
411 .. py:function:: get_repo_nodes(apiuser, repoid, revision, root_path, ret_type=<Optional:'all'>, details=<Optional:'basic'>, max_file_bytes=<Optional:None>)
412
413 Returns a list of nodes and children in a flat list for a given
414 path at given revision.
415
416 It's possible to specify ret_type to show only `files` or `dirs`.
417
418 This command can only be run using an |authtoken| with admin rights,
419 or users with at least read rights to |repos|.
420
421 :param apiuser: This is filled automatically from the |authtoken|.
422 :type apiuser: AuthUser
423 :param repoid: The repository name or repository ID.
424 :type repoid: str or int
425 :param revision: The revision for which listing should be done.
426 :type revision: str
427 :param root_path: The path from which to start displaying.
428 :type root_path: str
429 :param ret_type: Set the return type. Valid options are
430 ``all`` (default), ``files`` and ``dirs``.
431 :type ret_type: Optional(str)
432 :param details: Returns extended information about nodes, such as
433 md5, binary, and or content. The valid options are ``basic`` and
434 ``full``.
435 :type details: Optional(str)
436 :param max_file_bytes: Only return file content under this file size bytes
437 :type details: Optional(int)
438
439 Example output:
440
441 .. code-block:: bash
442
443 id : <id_given_in_input>
444 result: [
445 {
446 "name" : "<name>"
447 "type" : "<type>",
448 "binary": "<true|false>" (only in extended mode)
449 "md5" : "<md5 of file content>" (only in extended mode)
450 },
451 ...
452 ]
453 error: null
454
455
456 get_repo_refs
457 -------------
458
459 .. py:function:: get_repo_refs(apiuser, repoid)
460
461 Returns a dictionary of current references. It returns
462 bookmarks, branches, closed_branches, and tags for given repository
463
464 It's possible to specify ret_type to show only `files` or `dirs`.
465
466 This command can only be run using an |authtoken| with admin rights,
467 or users with at least read rights to |repos|.
468
469 :param apiuser: This is filled automatically from the |authtoken|.
470 :type apiuser: AuthUser
471 :param repoid: The repository name or repository ID.
472 :type repoid: str or int
473
474 Example output:
475
476 .. code-block:: bash
477
478 id : <id_given_in_input>
479 result: [
480 TODO...
481 ]
482 error: null
483
484
485 get_repo_settings
486 -----------------
487
488 .. py:function:: get_repo_settings(apiuser, repoid, key=<Optional:None>)
489
490 Returns all settings for a repository. If key is given it only returns the
491 setting identified by the key or null.
492
493 :param apiuser: This is filled automatically from the |authtoken|.
494 :type apiuser: AuthUser
495 :param repoid: The repository name or repository id.
496 :type repoid: str or int
497 :param key: Key of the setting to return.
498 :type: key: Optional(str)
499
500 Example output:
501
502 .. code-block:: bash
503
504 {
505 "error": null,
506 "id": 237,
507 "result": {
508 "extensions_largefiles": true,
509 "hooks_changegroup_push_logger": true,
510 "hooks_changegroup_repo_size": false,
511 "hooks_outgoing_pull_logger": true,
512 "phases_publish": "True",
513 "rhodecode_hg_use_rebase_for_merging": true,
514 "rhodecode_pr_merge_enabled": true,
515 "rhodecode_use_outdated_comments": true
516 }
517 }
518
519
520 get_repos
521 ---------
522
523 .. py:function:: get_repos(apiuser)
524
525 Lists all existing repositories.
526
527 This command can only be run using an |authtoken| with admin rights,
528 or users with at least read rights to |repos|.
529
530 :param apiuser: This is filled automatically from the |authtoken|.
531 :type apiuser: AuthUser
532
533 Example output:
534
535 .. code-block:: bash
536
537 id : <id_given_in_input>
538 result: [
539 {
540 "repo_id" : "<repo_id>",
541 "repo_name" : "<reponame>"
542 "repo_type" : "<repo_type>",
543 "clone_uri" : "<clone_uri>",
544 "private": : "<bool>",
545 "created_on" : "<datetimecreated>",
546 "description" : "<description>",
547 "landing_rev": "<landing_rev>",
548 "owner": "<repo_owner>",
549 "fork_of": "<name_of_fork_parent>",
550 "enable_downloads": "<bool>",
551 "enable_locking": "<bool>",
552 "enable_statistics": "<bool>",
553 },
554 ...
555 ]
556 error: null
557
558
559 grant_user_group_permission
560 ---------------------------
561
562 .. py:function:: grant_user_group_permission(apiuser, repoid, usergroupid, perm)
563
564 Grant permission for a user group on the specified repository,
565 or update existing permissions.
566
567 This command can only be run using an |authtoken| with admin
568 permissions on the |repo|.
569
570 :param apiuser: This is filled automatically from the |authtoken|.
571 :type apiuser: AuthUser
572 :param repoid: Set the repository name or repository ID.
573 :type repoid: str or int
574 :param usergroupid: Specify the ID of the user group.
575 :type usergroupid: str or int
576 :param perm: Set the user group permissions using the following
577 format: (repository.(none|read|write|admin))
578 :type perm: str
579
580 Example output:
581
582 .. code-block:: bash
583
584 id : <id_given_in_input>
585 result : {
586 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
587 "success": true
588
589 }
590 error : null
591
592 Example error output:
593
594 .. code-block:: bash
595
596 id : <id_given_in_input>
597 result : null
598 error : {
599 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
600 }
601
602
603 grant_user_permission
604 ---------------------
605
606 .. py:function:: grant_user_permission(apiuser, repoid, userid, perm)
607
608 Grant permissions for the specified user on the given repository,
609 or update existing permissions if found.
610
611 This command can only be run using an |authtoken| with admin
612 permissions on the |repo|.
613
614 :param apiuser: This is filled automatically from the |authtoken|.
615 :type apiuser: AuthUser
616 :param repoid: Set the repository name or repository ID.
617 :type repoid: str or int
618 :param userid: Set the user name.
619 :type userid: str
620 :param perm: Set the user permissions, using the following format
621 ``(repository.(none|read|write|admin))``
622 :type perm: str
623
624 Example output:
625
626 .. code-block:: bash
627
628 id : <id_given_in_input>
629 result: {
630 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
631 "success": true
632 }
633 error: null
634
635
636 invalidate_cache
637 ----------------
638
639 .. py:function:: invalidate_cache(apiuser, repoid, delete_keys=<Optional:False>)
640
641 Invalidates the cache for the specified repository.
642
643 This command can only be run using an |authtoken| with admin rights to
644 the specified repository.
645
646 This command takes the following options:
647
648 :param apiuser: This is filled automatically from |authtoken|.
649 :type apiuser: AuthUser
650 :param repoid: Sets the repository name or repository ID.
651 :type repoid: str or int
652 :param delete_keys: This deletes the invalidated keys instead of
653 just flagging them.
654 :type delete_keys: Optional(``True`` | ``False``)
655
656 Example output:
657
658 .. code-block:: bash
659
660 id : <id_given_in_input>
661 result : {
662 'msg': Cache for repository `<repository name>` was invalidated,
663 'repository': <repository name>
664 }
665 error : null
666
667 Example error output:
668
669 .. code-block:: bash
670
671 id : <id_given_in_input>
672 result : null
673 error : {
674 'Error occurred during cache invalidation action'
675 }
676
677
678 lock
679 ----
680
681 .. py:function:: lock(apiuser, repoid, locked=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
682
683 Sets the lock state of the specified |repo| by the given user.
684 From more information, see :ref:`repo-locking`.
685
686 * If the ``userid`` option is not set, the repository is locked to the
687 user who called the method.
688 * If the ``locked`` parameter is not set, the current lock state of the
689 repository is displayed.
690
691 This command can only be run using an |authtoken| with admin rights to
692 the specified repository.
693
694 This command takes the following options:
695
696 :param apiuser: This is filled automatically from the |authtoken|.
697 :type apiuser: AuthUser
698 :param repoid: Sets the repository name or repository ID.
699 :type repoid: str or int
700 :param locked: Sets the lock state.
701 :type locked: Optional(``True`` | ``False``)
702 :param userid: Set the repository lock to this user.
703 :type userid: Optional(str or int)
704
705 Example error output:
706
707 .. code-block:: bash
708
709 id : <id_given_in_input>
710 result : {
711 'repo': '<reponame>',
712 'locked': <bool: lock state>,
713 'locked_since': <int: lock timestamp>,
714 'locked_by': <username of person who made the lock>,
715 'lock_reason': <str: reason for locking>,
716 'lock_state_changed': <bool: True if lock state has been changed in this request>,
717 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
718 or
719 'msg': 'Repo `<repository name>` not locked.'
720 or
721 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
722 }
723 error : null
724
725 Example error output:
726
727 .. code-block:: bash
728
729 id : <id_given_in_input>
730 result : null
731 error : {
732 'Error occurred locking repository `<reponame>`
733 }
734
735
736 pull
737 ----
738
739 .. py:function:: pull(apiuser, repoid)
740
741 Triggers a pull on the given repository from a remote location. You
742 can use this to keep remote repositories up-to-date.
743
744 This command can only be run using an |authtoken| with admin
745 rights to the specified repository. For more information,
746 see :ref:`config-token-ref`.
747
748 This command takes the following options:
749
750 :param apiuser: This is filled automatically from the |authtoken|.
751 :type apiuser: AuthUser
752 :param repoid: The repository name or repository ID.
753 :type repoid: str or int
754
755 Example output:
756
757 .. code-block:: bash
758
759 id : <id_given_in_input>
760 result : {
761 "msg": "Pulled from `<repository name>`"
762 "repository": "<repository name>"
763 }
764 error : null
765
766 Example error output:
767
768 .. code-block:: bash
769
770 id : <id_given_in_input>
771 result : null
772 error : {
773 "Unable to pull changes from `<reponame>`"
774 }
775
776
777 remove_field_from_repo
778 ----------------------
779
780 .. py:function:: remove_field_from_repo(apiuser, repoid, key)
781
782 Removes an extra field from a repository.
783
784 This command can only be run using an |authtoken| with at least
785 write permissions to the |repo|.
786
787 :param apiuser: This is filled automatically from the |authtoken|.
788 :type apiuser: AuthUser
789 :param repoid: Set the repository name or repository ID.
790 :type repoid: str or int
791 :param key: Set the unique field key for this repository.
792 :type key: str
793
794
795 revoke_user_group_permission
796 ----------------------------
797
798 .. py:function:: revoke_user_group_permission(apiuser, repoid, usergroupid)
799
800 Revoke the permissions of a user group on a given repository.
801
802 This command can only be run using an |authtoken| with admin
803 permissions on the |repo|.
804
805 :param apiuser: This is filled automatically from the |authtoken|.
806 :type apiuser: AuthUser
807 :param repoid: Set the repository name or repository ID.
808 :type repoid: str or int
809 :param usergroupid: Specify the user group ID.
810 :type usergroupid: str or int
811
812 Example output:
813
814 .. code-block:: bash
815
816 id : <id_given_in_input>
817 result: {
818 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
819 "success": true
820 }
821 error: null
822
823
824 revoke_user_permission
825 ----------------------
826
827 .. py:function:: revoke_user_permission(apiuser, repoid, userid)
828
829 Revoke permission for a user on the specified repository.
830
831 This command can only be run using an |authtoken| with admin
832 permissions on the |repo|.
833
834 :param apiuser: This is filled automatically from the |authtoken|.
835 :type apiuser: AuthUser
836 :param repoid: Set the repository name or repository ID.
837 :type repoid: str or int
838 :param userid: Set the user name of revoked user.
839 :type userid: str or int
840
841 Example error output:
842
843 .. code-block:: bash
844
845 id : <id_given_in_input>
846 result: {
847 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
848 "success": true
849 }
850 error: null
851
852
853 set_repo_settings
854 -----------------
855
856 .. py:function:: set_repo_settings(apiuser, repoid, settings)
857
858 Update repository settings. Returns true on success.
859
860 :param apiuser: This is filled automatically from the |authtoken|.
861 :type apiuser: AuthUser
862 :param repoid: The repository name or repository id.
863 :type repoid: str or int
864 :param settings: The new settings for the repository.
865 :type: settings: dict
866
867 Example output:
868
869 .. code-block:: bash
870
871 {
872 "error": null,
873 "id": 237,
874 "result": true
875 }
876
877
878 strip
879 -----
880
881 .. py:function:: strip(apiuser, repoid, revision, branch)
882
883 Strips the given revision from the specified repository.
884
885 * This will remove the revision and all of its decendants.
886
887 This command can only be run using an |authtoken| with admin rights to
888 the specified repository.
889
890 This command takes the following options:
891
892 :param apiuser: This is filled automatically from the |authtoken|.
893 :type apiuser: AuthUser
894 :param repoid: The repository name or repository ID.
895 :type repoid: str or int
896 :param revision: The revision you wish to strip.
897 :type revision: str
898 :param branch: The branch from which to strip the revision.
899 :type branch: str
900
901 Example output:
902
903 .. code-block:: bash
904
905 id : <id_given_in_input>
906 result : {
907 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
908 "repository": "<repository name>"
909 }
910 error : null
911
912 Example error output:
913
914 .. code-block:: bash
915
916 id : <id_given_in_input>
917 result : null
918 error : {
919 "Unable to strip commit <commit_hash> from repo `<repository name>`"
920 }
921
922
923 update_repo
924 -----------
925
926 .. py:function:: update_repo(apiuser, repoid, name=<Optional:None>, owner=<Optional:<OptionalAttr:apiuser>>, group=<Optional:None>, fork_of=<Optional:None>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, fields=<Optional:''>)
927
928 Updates a repository with the given information.
929
930 This command can only be run using an |authtoken| with at least
931 write permissions to the |repo|.
932
933 :param apiuser: This is filled automatically from the |authtoken|.
934 :type apiuser: AuthUser
935 :param repoid: repository name or repository ID.
936 :type repoid: str or int
937 :param name: Update the |repo| name.
938 :type name: str
939 :param owner: Set the |repo| owner.
940 :type owner: str
941 :param group: Set the |repo| group the |repo| belongs to.
942 :type group: str
943 :param fork_of: Set the master |repo| name.
944 :type fork_of: str
945 :param description: Update the |repo| description.
946 :type description: str
947 :param private: Set the |repo| as private. (True | False)
948 :type private: bool
949 :param clone_uri: Update the |repo| clone URI.
950 :type clone_uri: str
951 :param landing_rev: Set the |repo| landing revision. Default is
952 ``tip``.
953 :type landing_rev: str
954 :param enable_statistics: Enable statistics on the |repo|,
955 (True | False).
956 :type enable_statistics: bool
957 :param enable_locking: Enable |repo| locking.
958 :type enable_locking: bool
959 :param enable_downloads: Enable downloads from the |repo|,
960 (True | False).
961 :type enable_downloads: bool
962 :param fields: Add extra fields to the |repo|. Use the following
963 example format: ``field_key=field_val,field_key2=fieldval2``.
964 Escape ', ' with \,
965 :type fields: str
966
967
@@ -0,0 +1,115 b''
1 .. _server-methods-ref:
2
3 server methods
4 =================
5
6 get_ip
7 ------
8
9 .. py:function:: get_ip(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
10
11 Displays the IP Address as seen from the |RCE| server.
12
13 * This command displays the IP Address, as well as all the defined IP
14 addresses for the specified user. If the ``userid`` is not set, the
15 data returned is for the user calling the method.
16
17 This command can only be run using an |authtoken| with admin rights to
18 the specified repository.
19
20 This command takes the following options:
21
22 :param apiuser: This is filled automatically from |authtoken|.
23 :type apiuser: AuthUser
24 :param userid: Sets the userid for which associated IP Address data
25 is returned.
26 :type userid: Optional(str or int)
27
28 Example output:
29
30 .. code-block:: bash
31
32 id : <id_given_in_input>
33 result : {
34 "server_ip_addr": "<ip_from_clien>",
35 "user_ips": [
36 {
37 "ip_addr": "<ip_with_mask>",
38 "ip_range": ["<start_ip>", "<end_ip>"],
39 },
40 ...
41 ]
42 }
43
44
45 get_server_info
46 ---------------
47
48 .. py:function:: get_server_info(apiuser)
49
50 Returns the |RCE| server information.
51
52 This includes the running version of |RCE| and all installed
53 packages. This command takes the following options:
54
55 :param apiuser: This is filled automatically from the |authtoken|.
56 :type apiuser: AuthUser
57
58 Example output:
59
60 .. code-block:: bash
61
62 id : <id_given_in_input>
63 result : {
64 'modules': [<module name>,...]
65 'py_version': <python version>,
66 'platform': <platform type>,
67 'rhodecode_version': <rhodecode version>
68 }
69 error : null
70
71
72 rescan_repos
73 ------------
74
75 .. py:function:: rescan_repos(apiuser, remove_obsolete=<Optional:False>)
76
77 Triggers a rescan of the specified repositories.
78
79 * If the ``remove_obsolete`` option is set, it also deletes repositories
80 that are found in the database but not on the file system, so called
81 "clean zombies".
82
83 This command can only be run using an |authtoken| with admin rights to
84 the specified repository.
85
86 This command takes the following options:
87
88 :param apiuser: This is filled automatically from the |authtoken|.
89 :type apiuser: AuthUser
90 :param remove_obsolete: Deletes repositories from the database that
91 are not found on the filesystem.
92 :type remove_obsolete: Optional(``True`` | ``False``)
93
94 Example output:
95
96 .. code-block:: bash
97
98 id : <id_given_in_input>
99 result : {
100 'added': [<added repository name>,...]
101 'removed': [<removed repository name>,...]
102 }
103 error : null
104
105 Example error output:
106
107 .. code-block:: bash
108
109 id : <id_given_in_input>
110 result : null
111 error : {
112 'Error occurred during rescan repositories action'
113 }
114
115
@@ -0,0 +1,406 b''
1 .. _user-group-methods-ref:
2
3 user_group methods
4 =================
5
6 add_user_to_user_group
7 ----------------------
8
9 .. py:function:: add_user_to_user_group(apiuser, usergroupid, userid)
10
11 Adds a user to a `user group`. If the user already exists in the group
12 this command will return false.
13
14 This command can only be run using an |authtoken| with admin rights to
15 the specified user group.
16
17 This command takes the following options:
18
19 :param apiuser: This is filled automatically from the |authtoken|.
20 :type apiuser: AuthUser
21 :param usergroupid: Set the name of the `user group` to which a
22 user will be added.
23 :type usergroupid: int
24 :param userid: Set the `user_id` of the user to add to the group.
25 :type userid: int
26
27 Example output:
28
29 .. code-block:: bash
30
31 id : <id_given_in_input>
32 result : {
33 "success": True|False # depends on if member is in group
34 "msg": "added member `<username>` to user group `<groupname>` |
35 User is already in that group"
36
37 }
38 error : null
39
40 Example error output:
41
42 .. code-block:: bash
43
44 id : <id_given_in_input>
45 result : null
46 error : {
47 "failed to add member to user group `<user_group_name>`"
48 }
49
50
51 create_user_group
52 -----------------
53
54 .. py:function:: create_user_group(apiuser, group_name, description=<Optional:''>, owner=<Optional:<OptionalAttr:apiuser>>, active=<Optional:True>)
55
56 Creates a new user group.
57
58 This command can only be run using an |authtoken| with admin rights to
59 the specified repository.
60
61 This command takes the following options:
62
63 :param apiuser: This is filled automatically from the |authtoken|.
64 :type apiuser: AuthUser
65 :param group_name: Set the name of the new user group.
66 :type group_name: str
67 :param description: Give a description of the new user group.
68 :type description: str
69 :param owner: Set the owner of the new user group.
70 If not set, the owner is the |authtoken| user.
71 :type owner: Optional(str or int)
72 :param active: Set this group as active.
73 :type active: Optional(``True`` | ``False``)
74
75 Example output:
76
77 .. code-block:: bash
78
79 id : <id_given_in_input>
80 result: {
81 "msg": "created new user group `<groupname>`",
82 "user_group": <user_group_object>
83 }
84 error: null
85
86 Example error output:
87
88 .. code-block:: bash
89
90 id : <id_given_in_input>
91 result : null
92 error : {
93 "user group `<group name>` already exist"
94 or
95 "failed to create group `<group name>`"
96 }
97
98
99 delete_user_group
100 -----------------
101
102 .. py:function:: delete_user_group(apiuser, usergroupid)
103
104 Deletes the specified `user group`.
105
106 This command can only be run using an |authtoken| with admin rights to
107 the specified repository.
108
109 This command takes the following options:
110
111 :param apiuser: filled automatically from apikey
112 :type apiuser: AuthUser
113 :param usergroupid:
114 :type usergroupid: int
115
116 Example output:
117
118 .. code-block:: bash
119
120 id : <id_given_in_input>
121 result : {
122 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
123 }
124 error : null
125
126 Example error output:
127
128 .. code-block:: bash
129
130 id : <id_given_in_input>
131 result : null
132 error : {
133 "failed to delete user group ID:<user_group_id> <user_group_name>"
134 or
135 "RepoGroup assigned to <repo_groups_list>"
136 }
137
138
139 get_user_group
140 --------------
141
142 .. py:function:: get_user_group(apiuser, usergroupid)
143
144 Returns the data of an existing user group.
145
146 This command can only be run using an |authtoken| with admin rights to
147 the specified repository.
148
149 :param apiuser: This is filled automatically from the |authtoken|.
150 :type apiuser: AuthUser
151 :param usergroupid: Set the user group from which to return data.
152 :type usergroupid: str or int
153
154 Example error output:
155
156 .. code-block:: bash
157
158 {
159 "error": null,
160 "id": <id>,
161 "result": {
162 "active": true,
163 "group_description": "group description",
164 "group_name": "group name",
165 "members": [
166 {
167 "name": "owner-name",
168 "origin": "owner",
169 "permission": "usergroup.admin",
170 "type": "user"
171 },
172 {
173 {
174 "name": "user name",
175 "origin": "permission",
176 "permission": "usergroup.admin",
177 "type": "user"
178 },
179 {
180 "name": "user group name",
181 "origin": "permission",
182 "permission": "usergroup.write",
183 "type": "user_group"
184 }
185 ],
186 "owner": "owner name",
187 "users": [],
188 "users_group_id": 2
189 }
190 }
191
192
193 get_user_groups
194 ---------------
195
196 .. py:function:: get_user_groups(apiuser)
197
198 Lists all the existing user groups within RhodeCode.
199
200 This command can only be run using an |authtoken| with admin rights to
201 the specified repository.
202
203 This command takes the following options:
204
205 :param apiuser: This is filled automatically from the |authtoken|.
206 :type apiuser: AuthUser
207
208 Example error output:
209
210 .. code-block:: bash
211
212 id : <id_given_in_input>
213 result : [<user_group_obj>,...]
214 error : null
215
216
217 grant_user_group_permission_to_user_group
218 -----------------------------------------
219
220 .. py:function:: grant_user_group_permission_to_user_group(apiuser, usergroupid, sourceusergroupid, perm)
221
222 Give one user group permissions to another user group.
223
224 :param apiuser: This is filled automatically from the |authtoken|.
225 :type apiuser: AuthUser
226 :param usergroupid: Set the user group on which to edit permissions.
227 :type usergroupid: str or int
228 :param sourceusergroupid: Set the source user group to which
229 access/permissions will be granted.
230 :type sourceusergroupid: str or int
231 :param perm: (usergroup.(none|read|write|admin))
232 :type perm: str
233
234 Example output:
235
236 .. code-block:: bash
237
238 id : <id_given_in_input>
239 result : {
240 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
241 "success": true
242 }
243 error : null
244
245
246 grant_user_permission_to_user_group
247 -----------------------------------
248
249 .. py:function:: grant_user_permission_to_user_group(apiuser, usergroupid, userid, perm)
250
251 Set permissions for a user in a user group.
252
253 :param apiuser: This is filled automatically from the |authtoken|.
254 :type apiuser: AuthUser
255 :param usergroupid: Set the user group to edit permissions on.
256 :type usergroupid: str or int
257 :param userid: Set the user from whom you wish to set permissions.
258 :type userid: str
259 :param perm: (usergroup.(none|read|write|admin))
260 :type perm: str
261
262 Example output:
263
264 .. code-block:: bash
265
266 id : <id_given_in_input>
267 result : {
268 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
269 "success": true
270 }
271 error : null
272
273
274 remove_user_from_user_group
275 ---------------------------
276
277 .. py:function:: remove_user_from_user_group(apiuser, usergroupid, userid)
278
279 Removes a user from a user group.
280
281 * If the specified user is not in the group, this command will return
282 `false`.
283
284 This command can only be run using an |authtoken| with admin rights to
285 the specified user group.
286
287 :param apiuser: This is filled automatically from the |authtoken|.
288 :type apiuser: AuthUser
289 :param usergroupid: Sets the user group name.
290 :type usergroupid: str or int
291 :param userid: The user you wish to remove from |RCE|.
292 :type userid: str or int
293
294 Example output:
295
296 .. code-block:: bash
297
298 id : <id_given_in_input>
299 result: {
300 "success": True|False, # depends on if member is in group
301 "msg": "removed member <username> from user group <groupname> |
302 User wasn't in group"
303 }
304 error: null
305
306
307 revoke_user_group_permission_from_user_group
308 --------------------------------------------
309
310 .. py:function:: revoke_user_group_permission_from_user_group(apiuser, usergroupid, sourceusergroupid)
311
312 Revoke the permissions that one user group has to another.
313
314 :param apiuser: This is filled automatically from the |authtoken|.
315 :type apiuser: AuthUser
316 :param usergroupid: Set the user group on which to edit permissions.
317 :type usergroupid: str or int
318 :param sourceusergroupid: Set the user group from which permissions
319 are revoked.
320 :type sourceusergroupid: str or int
321
322 Example output:
323
324 .. code-block:: bash
325
326 id : <id_given_in_input>
327 result : {
328 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
329 "success": true
330 }
331 error : null
332
333
334 revoke_user_permission_from_user_group
335 --------------------------------------
336
337 .. py:function:: revoke_user_permission_from_user_group(apiuser, usergroupid, userid)
338
339 Revoke a users permissions in a user group.
340
341 :param apiuser: This is filled automatically from the |authtoken|.
342 :type apiuser: AuthUser
343 :param usergroupid: Set the user group from which to revoke the user
344 permissions.
345 :type: usergroupid: str or int
346 :param userid: Set the userid of the user whose permissions will be
347 revoked.
348 :type userid: str
349
350 Example output:
351
352 .. code-block:: bash
353
354 id : <id_given_in_input>
355 result : {
356 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
357 "success": true
358 }
359 error : null
360
361
362 update_user_group
363 -----------------
364
365 .. py:function:: update_user_group(apiuser, usergroupid, group_name=<Optional:''>, description=<Optional:''>, owner=<Optional:None>, active=<Optional:True>)
366
367 Updates the specified `user group` with the details provided.
368
369 This command can only be run using an |authtoken| with admin rights to
370 the specified repository.
371
372 :param apiuser: This is filled automatically from the |authtoken|.
373 :type apiuser: AuthUser
374 :param usergroupid: Set the id of the `user group` to update.
375 :type usergroupid: str or int
376 :param group_name: Set the new name the `user group`
377 :type group_name: str
378 :param description: Give a description for the `user group`
379 :type description: str
380 :param owner: Set the owner of the `user group`.
381 :type owner: Optional(str or int)
382 :param active: Set the group as active.
383 :type active: Optional(``True`` | ``False``)
384
385 Example output:
386
387 .. code-block:: bash
388
389 id : <id_given_in_input>
390 result : {
391 "msg": 'updated user group ID:<user group id> <user group name>',
392 "user_group": <user_group_object>
393 }
394 error : null
395
396 Example error output:
397
398 .. code-block:: bash
399
400 id : <id_given_in_input>
401 result : null
402 error : {
403 "failed to update user group `<user group name>`"
404 }
405
406
@@ -0,0 +1,295 b''
1 .. _user-methods-ref:
2
3 user methods
4 =================
5
6 create_user
7 -----------
8
9 .. py:function:: create_user(apiuser, username, email, password=<Optional:''>, firstname=<Optional:''>, lastname=<Optional:''>, active=<Optional:True>, admin=<Optional:False>, extern_name=<Optional:'rhodecode'>, extern_type=<Optional:'rhodecode'>, force_password_change=<Optional:False>)
10
11 Creates a new user and returns the new user object.
12
13 This command can only be run using an |authtoken| with admin rights to
14 the specified repository.
15
16 This command takes the following options:
17
18 :param apiuser: This is filled automatically from the |authtoken|.
19 :type apiuser: AuthUser
20 :param username: Set the new username.
21 :type username: str or int
22 :param email: Set the user email address.
23 :type email: str
24 :param password: Set the new user password.
25 :type password: Optional(str)
26 :param firstname: Set the new user firstname.
27 :type firstname: Optional(str)
28 :param lastname: Set the new user surname.
29 :type lastname: Optional(str)
30 :param active: Set the user as active.
31 :type active: Optional(``True`` | ``False``)
32 :param admin: Give the new user admin rights.
33 :type admin: Optional(``True`` | ``False``)
34 :param extern_name: Set the authentication plugin name.
35 Using LDAP this is filled with LDAP UID.
36 :type extern_name: Optional(str)
37 :param extern_type: Set the new user authentication plugin.
38 :type extern_type: Optional(str)
39 :param force_password_change: Force the new user to change password
40 on next login.
41 :type force_password_change: Optional(``True`` | ``False``)
42
43 Example output:
44
45 .. code-block:: bash
46
47 id : <id_given_in_input>
48 result: {
49 "msg" : "created new user `<username>`",
50 "user": <user_obj>
51 }
52 error: null
53
54 Example error output:
55
56 .. code-block:: bash
57
58 id : <id_given_in_input>
59 result : null
60 error : {
61 "user `<username>` already exist"
62 or
63 "email `<email>` already exist"
64 or
65 "failed to create user `<username>`"
66 }
67
68
69 delete_user
70 -----------
71
72 .. py:function:: delete_user(apiuser, userid)
73
74 Deletes the specified user from the |RCE| user database.
75
76 This command can only be run using an |authtoken| with admin rights to
77 the specified repository.
78
79 .. important::
80
81 Ensure all open pull requests and open code review
82 requests to this user are close.
83
84 Also ensure all repositories, or repository groups owned by this
85 user are reassigned before deletion.
86
87 This command takes the following options:
88
89 :param apiuser: This is filled automatically from the |authtoken|.
90 :type apiuser: AuthUser
91 :param userid: Set the user to delete.
92 :type userid: str or int
93
94 Example output:
95
96 .. code-block:: bash
97
98 id : <id_given_in_input>
99 result: {
100 "msg" : "deleted user ID:<userid> <username>",
101 "user": null
102 }
103 error: null
104
105 Example error output:
106
107 .. code-block:: bash
108
109 id : <id_given_in_input>
110 result : null
111 error : {
112 "failed to delete user ID:<userid> <username>"
113 }
114
115
116 get_user
117 --------
118
119 .. py:function:: get_user(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
120
121 Returns the information associated with a username or userid.
122
123 * If the ``userid`` is not set, this command returns the information
124 for the ``userid`` calling the method.
125
126 .. note::
127
128 Normal users may only run this command against their ``userid``. For
129 full privileges you must run this command using an |authtoken| with
130 admin rights.
131
132 :param apiuser: This is filled automatically from the |authtoken|.
133 :type apiuser: AuthUser
134 :param userid: Sets the userid for which data will be returned.
135 :type userid: Optional(str or int)
136
137 Example output:
138
139 .. code-block:: bash
140
141 {
142 "error": null,
143 "id": <id>,
144 "result": {
145 "active": true,
146 "admin": false,
147 "api_key": "api-key",
148 "api_keys": [ list of keys ],
149 "email": "user@example.com",
150 "emails": [
151 "user@example.com"
152 ],
153 "extern_name": "rhodecode",
154 "extern_type": "rhodecode",
155 "firstname": "username",
156 "ip_addresses": [],
157 "language": null,
158 "last_login": "Timestamp",
159 "lastname": "surnae",
160 "permissions": {
161 "global": [
162 "hg.inherit_default_perms.true",
163 "usergroup.read",
164 "hg.repogroup.create.false",
165 "hg.create.none",
166 "hg.extern_activate.manual",
167 "hg.create.write_on_repogroup.false",
168 "hg.usergroup.create.false",
169 "group.none",
170 "repository.none",
171 "hg.register.none",
172 "hg.fork.repository"
173 ],
174 "repositories": { "username/example": "repository.write"},
175 "repositories_groups": { "user-group/repo": "group.none" },
176 "user_groups": { "user_group_name": "usergroup.read" }
177 },
178 "user_id": 32,
179 "username": "username"
180 }
181 }
182
183
184 get_user_locks
185 --------------
186
187 .. py:function:: get_user_locks(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
188
189 Displays all repositories locked by the specified user.
190
191 * If this command is run by a non-admin user, it returns
192 a list of |repos| locked by that user.
193
194 This command takes the following options:
195
196 :param apiuser: This is filled automatically from the |authtoken|.
197 :type apiuser: AuthUser
198 :param userid: Sets the userid whose list of locked |repos| will be
199 displayed.
200 :type userid: Optional(str or int)
201
202 Example output:
203
204 .. code-block:: bash
205
206 id : <id_given_in_input>
207 result : {
208 [repo_object, repo_object,...]
209 }
210 error : null
211
212
213 get_users
214 ---------
215
216 .. py:function:: get_users(apiuser)
217
218 Lists all users in the |RCE| user database.
219
220 This command can only be run using an |authtoken| with admin rights to
221 the specified repository.
222
223 This command takes the following options:
224
225 :param apiuser: This is filled automatically from the |authtoken|.
226 :type apiuser: AuthUser
227
228 Example output:
229
230 .. code-block:: bash
231
232 id : <id_given_in_input>
233 result: [<user_object>, ...]
234 error: null
235
236
237 update_user
238 -----------
239
240 .. py:function:: update_user(apiuser, userid, username=<Optional:None>, email=<Optional:None>, password=<Optional:None>, firstname=<Optional:None>, lastname=<Optional:None>, active=<Optional:None>, admin=<Optional:None>, extern_type=<Optional:None>, extern_name=<Optional:None>)
241
242 Updates the details for the specified user, if that user exists.
243
244 This command can only be run using an |authtoken| with admin rights to
245 the specified repository.
246
247 This command takes the following options:
248
249 :param apiuser: This is filled automatically from |authtoken|.
250 :type apiuser: AuthUser
251 :param userid: Set the ``userid`` to update.
252 :type userid: str or int
253 :param username: Set the new username.
254 :type username: str or int
255 :param email: Set the new email.
256 :type email: str
257 :param password: Set the new password.
258 :type password: Optional(str)
259 :param firstname: Set the new first name.
260 :type firstname: Optional(str)
261 :param lastname: Set the new surname.
262 :type lastname: Optional(str)
263 :param active: Set the new user as active.
264 :type active: Optional(``True`` | ``False``)
265 :param admin: Give the user admin rights.
266 :type admin: Optional(``True`` | ``False``)
267 :param extern_name: Set the authentication plugin user name.
268 Using LDAP this is filled with LDAP UID.
269 :type extern_name: Optional(str)
270 :param extern_type: Set the authentication plugin type.
271 :type extern_type: Optional(str)
272
273
274 Example output:
275
276 .. code-block:: bash
277
278 id : <id_given_in_input>
279 result: {
280 "msg" : "updated user ID:<userid> <username>",
281 "user": <user_object>,
282 }
283 error: null
284
285 Example error output:
286
287 .. code-block:: bash
288
289 id : <id_given_in_input>
290 result : null
291 error : {
292 "failed to update user `<username>`"
293 }
294
295
@@ -0,0 +1,25 b''
1 .. _extensions-hooks-ref:
2
3 Extensions & Hooks
4 ==================
5
6 The extensions & hooks section references three concepts regularly,
7 so to clarify what is meant each time, read the following definitions:
8
9 * **Plugin**: A Plugin is software that adds a specific feature to
10 an existing software application.
11 * **Extension**: An extension extends the capabilities of,
12 or the data available to, an existing software application.
13 * **Hook**: A hook intercepts function calls, messages, or events passed
14 between software components and can be used to trigger plugins, or their
15 extensions.
16
17 .. toctree::
18
19 rcx
20 install-ext
21 config-ext
22 extensions
23 hooks
24 full-blown-example
25 int-slack
@@ -0,0 +1,23 b''
1 .. _integrations-email:
2
3 Email integration
4 =================
5
6 The email integration allows you to send the summary of repo pushes to a
7 list of email recipients in the format:
8
9 An example::
10
11 User: johndoe
12 Branches: default
13 Repository: http://rhodecode.company.com/repo
14 Commit: 8eab60a44a612e331edfcd59b8d96b2f6a935cd9
15 URL: http://rhodecode.company.com/repo/changeset/8eab60a44a612e331edfcd59b8d96b2f6a935cd9
16 Author: John Doe
17 Date: 2016-03-01 11:20:44
18 Commit Message:
19
20 fixed bug with thing
21
22
23 To create one, create a ``email`` integration in `creating-integrations`.
@@ -0,0 +1,14 b''
1 .. _integrations-hipchat:
2
3 Hipchat integration
4 ===================
5
6 In order to set a Hipchat integration up, it is necessary to obtain the Hipchat
7 service url by:
8
9 1. Log into Hipchat (https://your-hipchat.hipchat.com/)
10 2. Go to *Integrations* -> *Build your own*
11 3. Select a room to post notifications to and save it
12
13 Hipchat will create a URL for you to use in your integration as outlined in
14 :ref:`creating-integrations`. No newline at end of file
@@ -0,0 +1,27 b''
1 .. _integrations-jira:
2
3 JIRA integration
4 ================
5
6 .. important::
7
8 JIRA integration is only available in |RCEE|.
9
10
11 .. important::
12
13 In order to make issue numbers clickable in commit messages, see the
14 :ref:`rhodecode-issue-trackers-ref` section. The JIRA integration
15 only deals with altering JIRA issues.
16
17
18 The JIRA integration allows you to reference and change issue statuses in
19 JIRA directly from commit messages using commit message patterns such as
20 ``fixes #JIRA-235`` in order to change the status of issue JIRA-235 to
21 eg. "Resolved".
22
23 In order to apply a status to a JIRA issue, it is necessary to find the
24 transition status id in the *Workflow* section of JIRA.
25
26 Once you have the transition status id, you can create a JIRA integration
27 as outlined in :ref:`creating-integrations`.
@@ -0,0 +1,28 b''
1 .. _integrations-redmine:
2
3 Redmine integration
4 ===================
5
6 .. important::
7
8 Redmine integration is only available in |RCEE|.
9
10
11 .. important::
12
13 In order to make issue numbers clickable in commit messages, see the section
14 :ref:`rhodecode-issue-trackers-ref`. Redmine integration is specifically for
15 altering Redmine issues.
16
17
18 Redmine integration allows you to reference and change issue statuses in
19 Redmine directly from commit messages, using commit message patterns such as
20 ``fixes #235`` in order to change the status of issue 235 to eg. "Resolved".
21
22 To set a Redmine integration up, it is first necessary to obtain a Redmine API
23 key. This can be found under *My Account* in the Redmine application.
24 You may have to enable API Access in Redmine settings if it is not already
25 available.
26
27 Once you have the API key, create a Redmine integration as outlined in
28 :ref:`creating-integrations`.
@@ -0,0 +1,21 b''
1 .. _integrations-slack:
2
3 Slack integration
4 =================
5
6 To set a Slack integration up, it is first necessary to set up a Slack webhook
7 API endpoint for your Slack channel. This can be done at:
8
9 https://my.slack.com/services/new/incoming-webhook/
10
11 Select the channel you would like to use, and Slack will provide you with the
12 webhook URL for configuration.
13
14 You can now create a Slack integration as outlined in
15 :ref:`creating-integrations`.
16
17 .. note::
18 Some settings in the RhodeCode admin are identical to the options within the
19 Slack integration. For example, if notifications are to be sent in a private
20 chat, leave the "Channel" field blank. Likewise, the Emoji option within
21 RhodeCode can override the one set in the Slack admin. No newline at end of file
@@ -0,0 +1,12 b''
1 .. _integrations-webhook:
2
3 Webhook integration
4 ===================
5
6 The Webhook integration allows you to POST events such as repository pushes
7 or pull requests to a custom http endpoint as a json dict with details of the
8 event.
9
10 To create a webhook integration, select "webhook" in the integration settings
11 and use the url and key from your custom webhook. See
12 :ref:`creating-integrations` for additional instructions. No newline at end of file
@@ -0,0 +1,121 b''
1 |RCE| 4.3.0 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2016-08-12
8
9
10 General
11 ^^^^^^^
12
13 - Subversion: detect requests also based on magic path.
14 This adds subversion 1.9 support for SVN backend.
15 - Summary/changelog: unified how data is displayed for those pages.
16 * use consistent order of columns
17 * fix the link to commit status
18 * fix order of displaying comments
19 - Live-chat: refactor live chat system for code review based on
20 latest channelstream changes.
21 - SVN: Add template to generate the apache mod_dav_svn config for all
22 repository groups. Repository groups can now be automatically mapped to be
23 supported by SVN backend. Set `svn.proxy.generate_config = true` and similar
24 options found inside .ini config.
25 - Readme/markup: improved order of generating readme files. Fixes #4050
26 * we now use order based on default system renderer
27 * having multiple readme files will pick correct one as set renderer
28 - Api: add a max_file_bytes parameter to get_nodes so that large files
29 can be skipped.
30 - Auth-ldap: added flag to set debug mode for LDAP connections.
31 - Labs: moved rebase-merge option from labs settings into VCS settings.
32 - System: send platform type and version to upgrade endpoint when checking
33 for new versions.
34 - Packaging: update rhodecode-tools from 0.8.3 to 0.10.0
35 - Packaging: update codemirror from 5.4.0 to 5.11.0
36 - Packaging: updated pygments to 2.1.3
37 - Packaging: bumped supervisor to 3.3.0
38 - Packaging: bumped psycopg2 to 2.6.1
39 - Packaging: bumped mercurial to 3.8.4
40
41
42 New Features
43 ^^^^^^^^^^^^
44
45 - Integrations: created new event based integration framework.
46 Allows to configure global, or per repo: Slack, Hipchat, Webhooks, Email
47 integrations. This also deprecated usage of rcextensions for those.
48 - Integrations (EE only): added smart commits for Jira and Redmine with
49 ability to map keywords into issue tracker actions.
50 `Fixes #123 -> resolves issues`, `Closes #123 -> closes issue` etc.
51 - Markdown: added improved support for Github flavored markdown.
52 - Labs: enable labs setting by default. Labs are new experimental features in
53 RhodeCode that can be used to test new upcomming features.
54 - Api: Add api methods to get/set repository settings, implements #4021.
55 - Gravatars: commit emails are now displayed based on the actual email
56 used inside commit rather then the main one of associated account
57 inside RhodeCode, #4037.
58 - Emails: All emails got new styling. They look now consistent
59 to UI of application. We also added bunch of usefull information into
60 email body, #4087.
61 - Pull requests: add show/hide comment functionality inside diffs, #4106.
62 - Notifications: added real-time notifications with via channelstream
63 about new comments when reviewing the code. Never miss someone replies
64 onto comments you submitted while doing a code-review.
65
66
67 Security
68 ^^^^^^^^
69
70 - Api: make `comment_commits` api call have consistent permissions
71 with web interface.
72 - Files: fixes display of "Add File" button missing or present despite
73 permissions, because of cached version of the page was rendered, fixes #4083.
74 - Login/Registration: fixed flash message problem on login/registration
75 pages, fixes #4043.
76 - Auth-token: allow other authentication types to use auth-token.
77 Accounts associated with other types like LDAP, or PAM can
78 now use auth-tokens to authenticate via RhodeCode.
79
80
81 Performance
82 ^^^^^^^^^^^
83
84 - Core: made all RhodeCode components gevent compatible. RhodeCode can now make
85 use of async workers. You can handle dozens of concurrent operations using a
86 single worker. This works only with new HTTP backend.
87 - Core: added new very efficient HTTP backend can be used to replace pyro4.
88 - Core: Set 'gzip_responses' to false by default. We no longer require any
89 gzip computations on backed, thus speeding up large file transfers.
90 - UI: optimized events system for JavaScript to boost performance on
91 large html pages.
92 - VCS: moved VCSMiddleware up to pyramid layer as wrapper around pylons app.
93 Skips few calls, and allows slightly faster clones/pulls and pushes.
94
95
96 Fixes
97 ^^^^^
98
99 - VCS: add vcsserver cache invalidation to mercurial backend.
100 Fixes multi-process problems after Mercurial 3.8.X release with server
101 side merges.
102 - VCS: clear caches on remap-and-rescan option.
103 - VCS: improved logic of updating commit caches in cases of rebases.
104 - Caches: Add an argument to make the cache context thread scoped. Brings
105 support to gevent compatible handling.
106 - Diff2way: fixed unicode problem on non-ascii files.
107 - Full text search: whoosh schema uses now bigger ints, fixes #4035
108 - File-browser: optimized cached tree calculation, reduced load times by
109 50% on complex file trees.
110 - Styling: #4086 fixing bug where long commit messages did not wrap in file view.
111 - SVN: Ignore the content length header from response, fixes #4112.
112 Fixes the "svn: E120106: ra_serf: The server sent a truncated HTTP response body."
113 - Auth: Fix password_changed function, fixes #4043.
114 - UI/tables: better message when tables are empty #685 #1832.
115 - UX: put gravatar and username together in user list #3203.
116 - Gists: use colander schema to validate input data.
117 * brings consistent validation acros API and web
118 * use nicer and stricter schemas to validate data
119 * fixes #4118
120 - Appenlight: error reporting can now also report VCSMiddleware errors.
121 - Users: hash email key for User.get_by_email() fixes #4132
@@ -0,0 +1,13 b''
1 # Copyright 2009 Facebook
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
13 # under the License.
@@ -0,0 +1,79 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22
23 from pyramid.settings import asbool
24
25 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.lib.ext_json import json
27
28
29 def url_gen(request):
30 urls = {
31 'connect': request.route_url('channelstream_connect'),
32 'subscribe': request.route_url('channelstream_subscribe')
33 }
34 return json.dumps(urls)
35
36
37 PLUGIN_DEFINITION = {
38 'name': 'channelstream',
39 'config': {
40 'javascript': [],
41 'css': [],
42 'template_hooks': {
43 'plugin_init_template': 'rhodecode:templates/channelstream/plugin_init.html'
44 },
45 'url_gen': url_gen,
46 'static': None,
47 'enabled': False,
48 'server': '',
49 'secret': ''
50 }
51 }
52
53
54 def includeme(config):
55 settings = config.registry.settings
56 PLUGIN_DEFINITION['config']['enabled'] = asbool(
57 settings.get('channelstream.enabled'))
58 PLUGIN_DEFINITION['config']['server'] = settings.get(
59 'channelstream.server', '')
60 PLUGIN_DEFINITION['config']['secret'] = settings.get(
61 'channelstream.secret', '')
62 PLUGIN_DEFINITION['config']['history.location'] = settings.get(
63 'channelstream.history.location', '')
64 config.register_rhodecode_plugin(
65 PLUGIN_DEFINITION['name'],
66 PLUGIN_DEFINITION['config']
67 )
68 # create plugin history location
69 history_dir = PLUGIN_DEFINITION['config']['history.location']
70 if history_dir and not os.path.exists(history_dir):
71 os.makedirs(history_dir, 0750)
72
73 config.add_route(
74 name='channelstream_connect',
75 pattern=ADMIN_PREFIX + '/channelstream/connect')
76 config.add_route(
77 name='channelstream_subscribe',
78 pattern=ADMIN_PREFIX + '/channelstream/subscribe')
79 config.scan('rhodecode.channelstream')
@@ -0,0 +1,177 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Channel Stream controller for rhodecode
23
24 :created_on: Oct 10, 2015
25 :author: marcinl
26 :copyright: (c) 2013-2015 RhodeCode GmbH.
27 :license: Commercial License, see LICENSE for more details.
28 """
29
30 import logging
31 import uuid
32
33 from pylons import tmpl_context as c
34 from pyramid.settings import asbool
35 from pyramid.view import view_config
36 from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPBadGateway
37
38 from rhodecode.lib.channelstream import (
39 channelstream_request,
40 ChannelstreamConnectionException,
41 ChannelstreamPermissionException,
42 check_channel_permissions,
43 get_connection_validators,
44 get_user_data,
45 parse_channels_info,
46 update_history_from_logs,
47 STATE_PUBLIC_KEYS)
48 from rhodecode.lib.auth import NotAnonymous
49 from rhodecode.lib.utils2 import str2bool
50
51 log = logging.getLogger(__name__)
52
53
54 class ChannelstreamView(object):
55 def __init__(self, context, request):
56 self.context = context
57 self.request = request
58
59 # Some of the decorators rely on this attribute to be present
60 # on the class of the decorated method.
61 self._rhodecode_user = request.user
62 registry = request.registry
63 self.channelstream_config = registry.rhodecode_plugins['channelstream']
64 if not self.channelstream_config.get('enabled'):
65 log.exception('Channelstream plugin is disabled')
66 raise HTTPBadRequest()
67
68 @NotAnonymous()
69 @view_config(route_name='channelstream_connect', renderer='json')
70 def connect(self):
71 """ handle authorization of users trying to connect """
72 try:
73 json_body = self.request.json_body
74 except Exception:
75 log.exception('Failed to decode json from request')
76 raise HTTPBadRequest()
77 try:
78 channels = check_channel_permissions(
79 json_body.get('channels'),
80 get_connection_validators(self.request.registry))
81 except ChannelstreamPermissionException:
82 log.error('Incorrect permissions for requested channels')
83 raise HTTPForbidden()
84
85 user = c.rhodecode_user
86 if user.user_id:
87 user_data = get_user_data(user.user_id)
88 else:
89 user_data = {
90 'id': None,
91 'username': None,
92 'first_name': None,
93 'last_name': None,
94 'icon_link': None,
95 'display_name': None,
96 'display_link': None,
97 }
98 payload = {
99 'username': user.username,
100 'user_state': user_data,
101 'conn_id': str(uuid.uuid4()),
102 'channels': channels,
103 'channel_configs': {},
104 'state_public_keys': STATE_PUBLIC_KEYS,
105 'info': {
106 'exclude_channels': ['broadcast']
107 }
108 }
109 filtered_channels = [channel for channel in channels
110 if channel != 'broadcast']
111 for channel in filtered_channels:
112 payload['channel_configs'][channel] = {
113 'notify_presence': True,
114 'history_size': 100,
115 'store_history': True,
116 'broadcast_presence_with_user_lists': True
117 }
118 # connect user to server
119 try:
120 connect_result = channelstream_request(self.channelstream_config,
121 payload, '/connect')
122 except ChannelstreamConnectionException:
123 log.exception('Channelstream service is down')
124 return HTTPBadGateway()
125
126 connect_result['channels'] = channels
127 connect_result['channels_info'] = parse_channels_info(
128 connect_result['channels_info'],
129 include_channel_info=filtered_channels)
130 update_history_from_logs(self.channelstream_config,
131 filtered_channels, connect_result)
132 return connect_result
133
134 @NotAnonymous()
135 @view_config(route_name='channelstream_subscribe', renderer='json')
136 def subscribe(self):
137 """ can be used to subscribe specific connection to other channels """
138 try:
139 json_body = self.request.json_body
140 except Exception:
141 log.exception('Failed to decode json from request')
142 raise HTTPBadRequest()
143 try:
144 channels = check_channel_permissions(
145 json_body.get('channels'),
146 get_connection_validators(self.request.registry))
147 except ChannelstreamPermissionException:
148 log.error('Incorrect permissions for requested channels')
149 raise HTTPForbidden()
150 payload = {'conn_id': json_body.get('conn_id', ''),
151 'channels': channels,
152 'channel_configs': {},
153 'info': {
154 'exclude_channels': ['broadcast']}
155 }
156 filtered_channels = [chan for chan in channels if chan != 'broadcast']
157 for channel in filtered_channels:
158 payload['channel_configs'][channel] = {
159 'notify_presence': True,
160 'history_size': 100,
161 'store_history': True,
162 'broadcast_presence_with_user_lists': True
163 }
164 try:
165 connect_result = channelstream_request(
166 self.channelstream_config, payload, '/subscribe')
167 except ChannelstreamConnectionException:
168 log.exception('Channelstream service is down')
169 return HTTPBadGateway()
170 # include_channel_info will limit history only to new channel
171 # to not overwrite histories on other channels in client
172 connect_result['channels_info'] = parse_channels_info(
173 connect_result['channels_info'],
174 include_channel_info=filtered_channels)
175 update_history_from_logs(self.channelstream_config,
176 filtered_channels, connect_result)
177 return connect_result
@@ -0,0 +1,78 b''
1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import logging
20 from pyramid.threadlocal import get_current_registry
21
22 log = logging.getLogger(__name__)
23
24
25 def trigger(event, registry=None):
26 """
27 Helper method to send an event. This wraps the pyramid logic to send an
28 event.
29 """
30 # For the first step we are using pyramids thread locals here. If the
31 # event mechanism works out as a good solution we should think about
32 # passing the registry as an argument to get rid of it.
33 registry = registry or get_current_registry()
34 registry.notify(event)
35 log.debug('event %s triggered', event)
36
37 # Until we can work around the problem that VCS operations do not have a
38 # pyramid context to work with, we send the events to integrations directly
39
40 # Later it will be possible to use regular pyramid subscribers ie:
41 # config.add_subscriber(integrations_event_handler, RhodecodeEvent)
42 from rhodecode.integrations import integrations_event_handler
43 if isinstance(event, RhodecodeEvent):
44 integrations_event_handler(event)
45
46
47 from rhodecode.events.base import RhodecodeEvent
48
49 from rhodecode.events.user import ( # noqa
50 UserPreCreate,
51 UserPreUpdate,
52 UserRegistered
53 )
54
55 from rhodecode.events.repo import ( # noqa
56 RepoEvent,
57 RepoPreCreateEvent, RepoCreateEvent,
58 RepoPreDeleteEvent, RepoDeleteEvent,
59 RepoPrePushEvent, RepoPushEvent,
60 RepoPrePullEvent, RepoPullEvent,
61 )
62
63 from rhodecode.events.repo_group import ( # noqa
64 RepoGroupEvent,
65 RepoGroupCreateEvent,
66 RepoGroupUpdateEvent,
67 RepoGroupDeleteEvent,
68 )
69
70 from rhodecode.events.pullrequest import ( # noqa
71 PullRequestEvent,
72 PullRequestCreateEvent,
73 PullRequestUpdateEvent,
74 PullRequestCommentEvent,
75 PullRequestReviewEvent,
76 PullRequestMergeEvent,
77 PullRequestCloseEvent,
78 )
@@ -0,0 +1,69 b''
1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 from datetime import datetime
20 from pyramid.threadlocal import get_current_request
21 from rhodecode.lib.utils2 import AttributeDict
22
23
24 # this is a user object to be used for events caused by the system (eg. shell)
25 SYSTEM_USER = AttributeDict(dict(
26 username='__SYSTEM__'
27 ))
28
29
30 class RhodecodeEvent(object):
31 """
32 Base event class for all Rhodecode events
33 """
34 name = "RhodeCodeEvent"
35
36 def __init__(self):
37 self.request = get_current_request()
38 self.utc_timestamp = datetime.utcnow()
39
40 @property
41 def actor(self):
42 if self.request:
43 return self.request.user.get_instance()
44 return SYSTEM_USER
45
46 @property
47 def actor_ip(self):
48 if self.request:
49 return self.request.user.ip_addr
50 return '<no ip available>'
51
52 @property
53 def server_url(self):
54 if self.request:
55 from rhodecode.lib import helpers as h
56 return h.url('home', qualified=True)
57 return '<no server_url available>'
58
59 def as_dict(self):
60 data = {
61 'name': self.name,
62 'utc_timestamp': self.utc_timestamp,
63 'actor_ip': self.actor_ip,
64 'actor': {
65 'username': self.actor.username
66 },
67 'server_url': self.server_url
68 }
69 return data
@@ -0,0 +1,131 b''
1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19
20 from rhodecode.translation import lazy_ugettext
21 from rhodecode.events.repo import (
22 RepoEvent, _commits_as_dict, _issues_as_dict)
23
24
25 class PullRequestEvent(RepoEvent):
26 """
27 Base class for pull request events.
28
29 :param pullrequest: a :class:`PullRequest` instance
30 """
31
32 def __init__(self, pullrequest):
33 super(PullRequestEvent, self).__init__(pullrequest.target_repo)
34 self.pullrequest = pullrequest
35
36 def as_dict(self):
37 from rhodecode.model.pull_request import PullRequestModel
38 data = super(PullRequestEvent, self).as_dict()
39
40 commits = _commits_as_dict(
41 commit_ids=self.pullrequest.revisions,
42 repos=[self.pullrequest.source_repo]
43 )
44 issues = _issues_as_dict(commits)
45
46 data.update({
47 'pullrequest': {
48 'title': self.pullrequest.title,
49 'issues': issues,
50 'pull_request_id': self.pullrequest.pull_request_id,
51 'url': PullRequestModel().get_url(self.pullrequest),
52 'status': self.pullrequest.calculated_review_status(),
53 'commits': commits,
54 }
55 })
56 return data
57
58
59 class PullRequestCreateEvent(PullRequestEvent):
60 """
61 An instance of this class is emitted as an :term:`event` after a pull
62 request is created.
63 """
64 name = 'pullrequest-create'
65 display_name = lazy_ugettext('pullrequest created')
66
67
68 class PullRequestCloseEvent(PullRequestEvent):
69 """
70 An instance of this class is emitted as an :term:`event` after a pull
71 request is closed.
72 """
73 name = 'pullrequest-close'
74 display_name = lazy_ugettext('pullrequest closed')
75
76
77 class PullRequestUpdateEvent(PullRequestEvent):
78 """
79 An instance of this class is emitted as an :term:`event` after a pull
80 request's commits have been updated.
81 """
82 name = 'pullrequest-update'
83 display_name = lazy_ugettext('pullrequest commits updated')
84
85
86 class PullRequestReviewEvent(PullRequestEvent):
87 """
88 An instance of this class is emitted as an :term:`event` after a pull
89 request review has changed.
90 """
91 name = 'pullrequest-review'
92 display_name = lazy_ugettext('pullrequest review changed')
93
94
95 class PullRequestMergeEvent(PullRequestEvent):
96 """
97 An instance of this class is emitted as an :term:`event` after a pull
98 request is merged.
99 """
100 name = 'pullrequest-merge'
101 display_name = lazy_ugettext('pullrequest merged')
102
103
104 class PullRequestCommentEvent(PullRequestEvent):
105 """
106 An instance of this class is emitted as an :term:`event` after a pull
107 request comment is created.
108 """
109 name = 'pullrequest-comment'
110 display_name = lazy_ugettext('pullrequest commented')
111
112 def __init__(self, pullrequest, comment):
113 super(PullRequestCommentEvent, self).__init__(pullrequest)
114 self.comment = comment
115
116 def as_dict(self):
117 from rhodecode.model.comment import ChangesetCommentsModel
118 data = super(PullRequestCommentEvent, self).as_dict()
119
120 status = None
121 if self.comment.status_change:
122 status = self.comment.status_change[0].status
123
124 data.update({
125 'comment': {
126 'status': status,
127 'text': self.comment.text,
128 'url': ChangesetCommentsModel().get_url(self.comment)
129 }
130 })
131 return data
@@ -0,0 +1,257 b''
1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import logging
20
21 from rhodecode.translation import lazy_ugettext
22 from rhodecode.model.db import User, Repository, Session
23 from rhodecode.events.base import RhodecodeEvent
24 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
25
26 log = logging.getLogger(__name__)
27
28 def _commits_as_dict(commit_ids, repos):
29 """
30 Helper function to serialize commit_ids
31
32 :param commit_ids: commits to get
33 :param repos: list of repos to check
34 """
35 from rhodecode.lib.utils2 import extract_mentioned_users
36 from rhodecode.model.db import Repository
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.helpers import process_patterns
39 from rhodecode.lib.helpers import urlify_commit_message
40
41 if not repos:
42 raise Exception('no repo defined')
43
44 if not isinstance(repos, (tuple, list)):
45 repos = [repos]
46
47 if not commit_ids:
48 return []
49
50 needed_commits = set(commit_ids)
51
52 commits = []
53 reviewers = []
54 for repo in repos:
55 if not needed_commits:
56 return commits # return early if we have the commits we need
57
58 vcs_repo = repo.scm_instance(cache=False)
59 try:
60 for commit_id in list(needed_commits):
61 try:
62 cs = vcs_repo.get_changeset(commit_id)
63 except CommitDoesNotExistError:
64 continue # maybe its in next repo
65
66 cs_data = cs.__json__()
67 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
68 cs_data['reviewers'] = reviewers
69 cs_data['url'] = h.url('changeset_home',
70 repo_name=repo.repo_name,
71 revision=cs_data['raw_id'],
72 qualified=True
73 )
74 urlified_message, issues_data = process_patterns(
75 cs_data['message'], repo.repo_name)
76 cs_data['issues'] = issues_data
77 cs_data['message_html'] = urlify_commit_message(cs_data['message'],
78 repo.repo_name)
79 commits.append(cs_data)
80
81 needed_commits.discard(commit_id)
82
83 except Exception as e:
84 log.exception(e)
85 # we don't send any commits when crash happens, only full list matters
86 # we short circuit then.
87 return []
88
89 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
90 if missing_commits:
91 log.error('missing commits: %s' % ', '.join(missing_commits))
92
93 return commits
94
95
96 def _issues_as_dict(commits):
97 """ Helper function to serialize issues from commits """
98 issues = {}
99 for commit in commits:
100 for issue in commit['issues']:
101 issues[issue['id']] = issue
102 return issues
103
104 class RepoEvent(RhodecodeEvent):
105 """
106 Base class for events acting on a repository.
107
108 :param repo: a :class:`Repository` instance
109 """
110
111 def __init__(self, repo):
112 super(RepoEvent, self).__init__()
113 self.repo = repo
114
115 def as_dict(self):
116 from rhodecode.model.repo import RepoModel
117 data = super(RepoEvent, self).as_dict()
118 data.update({
119 'repo': {
120 'repo_id': self.repo.repo_id,
121 'repo_name': self.repo.repo_name,
122 'repo_type': self.repo.repo_type,
123 'url': RepoModel().get_url(self.repo)
124 }
125 })
126 return data
127
128
129 class RepoPreCreateEvent(RepoEvent):
130 """
131 An instance of this class is emitted as an :term:`event` before a repo is
132 created.
133 """
134 name = 'repo-pre-create'
135 display_name = lazy_ugettext('repository pre create')
136
137
138 class RepoCreateEvent(RepoEvent):
139 """
140 An instance of this class is emitted as an :term:`event` whenever a repo is
141 created.
142 """
143 name = 'repo-create'
144 display_name = lazy_ugettext('repository created')
145
146
147 class RepoPreDeleteEvent(RepoEvent):
148 """
149 An instance of this class is emitted as an :term:`event` whenever a repo is
150 created.
151 """
152 name = 'repo-pre-delete'
153 display_name = lazy_ugettext('repository pre delete')
154
155
156 class RepoDeleteEvent(RepoEvent):
157 """
158 An instance of this class is emitted as an :term:`event` whenever a repo is
159 created.
160 """
161 name = 'repo-delete'
162 display_name = lazy_ugettext('repository deleted')
163
164
165 class RepoVCSEvent(RepoEvent):
166 """
167 Base class for events triggered by the VCS
168 """
169 def __init__(self, repo_name, extras):
170 self.repo = Repository.get_by_repo_name(repo_name)
171 if not self.repo:
172 raise Exception('repo by this name %s does not exist' % repo_name)
173 self.extras = extras
174 super(RepoVCSEvent, self).__init__(self.repo)
175
176 @property
177 def actor(self):
178 if self.extras.get('username'):
179 return User.get_by_username(self.extras['username'])
180
181 @property
182 def actor_ip(self):
183 if self.extras.get('ip'):
184 return self.extras['ip']
185
186 @property
187 def server_url(self):
188 if self.extras.get('server_url'):
189 return self.extras['server_url']
190
191
192 class RepoPrePullEvent(RepoVCSEvent):
193 """
194 An instance of this class is emitted as an :term:`event` before commits
195 are pulled from a repo.
196 """
197 name = 'repo-pre-pull'
198 display_name = lazy_ugettext('repository pre pull')
199
200
201 class RepoPullEvent(RepoVCSEvent):
202 """
203 An instance of this class is emitted as an :term:`event` after commits
204 are pulled from a repo.
205 """
206 name = 'repo-pull'
207 display_name = lazy_ugettext('repository pull')
208
209
210 class RepoPrePushEvent(RepoVCSEvent):
211 """
212 An instance of this class is emitted as an :term:`event` before commits
213 are pushed to a repo.
214 """
215 name = 'repo-pre-push'
216 display_name = lazy_ugettext('repository pre push')
217
218
219 class RepoPushEvent(RepoVCSEvent):
220 """
221 An instance of this class is emitted as an :term:`event` after commits
222 are pushed to a repo.
223
224 :param extras: (optional) dict of data from proxied VCS actions
225 """
226 name = 'repo-push'
227 display_name = lazy_ugettext('repository push')
228
229 def __init__(self, repo_name, pushed_commit_ids, extras):
230 super(RepoPushEvent, self).__init__(repo_name, extras)
231 self.pushed_commit_ids = pushed_commit_ids
232
233 def as_dict(self):
234 data = super(RepoPushEvent, self).as_dict()
235 branch_url = repo_url = data['repo']['url']
236
237 commits = _commits_as_dict(
238 commit_ids=self.pushed_commit_ids, repos=[self.repo])
239 issues = _issues_as_dict(commits)
240
241 branches = set(
242 commit['branch'] for commit in commits if commit['branch'])
243 branches = [
244 {
245 'name': branch,
246 'url': '{}/changelog?branch={}'.format(
247 data['repo']['url'], branch)
248 }
249 for branch in branches
250 ]
251
252 data['push'] = {
253 'commits': commits,
254 'issues': issues,
255 'branches': branches,
256 }
257 return data
@@ -0,0 +1,80 b''
1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import logging
20
21 from rhodecode.translation import lazy_ugettext
22 from rhodecode.events.base import RhodecodeEvent
23
24
25 log = logging.getLogger(__name__)
26
27
28 class RepoGroupEvent(RhodecodeEvent):
29 """
30 Base class for events acting on a repository group.
31
32 :param repo: a :class:`RepositoryGroup` instance
33 """
34
35 def __init__(self, repo_group):
36 super(RepoGroupEvent, self).__init__()
37 self.repo_group = repo_group
38
39 def as_dict(self):
40 data = super(RepoGroupEvent, self).as_dict()
41 data.update({
42 'repo_group': {
43 'group_id': self.repo_group.group_id,
44 'group_name': self.repo_group.group_name,
45 'group_parent_id': self.repo_group.group_parent_id,
46 'group_description': self.repo_group.group_description,
47 'user_id': self.repo_group.user_id,
48 'created_by': self.repo_group.user.username,
49 'created_on': self.repo_group.created_on,
50 'enable_locking': self.repo_group.enable_locking,
51 }
52 })
53 return data
54
55
56 class RepoGroupCreateEvent(RepoGroupEvent):
57 """
58 An instance of this class is emitted as an :term:`event` whenever a
59 repository group is created.
60 """
61 name = 'repo-group-create'
62 display_name = lazy_ugettext('repository group created')
63
64
65 class RepoGroupDeleteEvent(RepoGroupEvent):
66 """
67 An instance of this class is emitted as an :term:`event` whenever a
68 repository group is deleted.
69 """
70 name = 'repo-group-delete'
71 display_name = lazy_ugettext('repository group deleted')
72
73
74 class RepoGroupUpdateEvent(RepoGroupEvent):
75 """
76 An instance of this class is emitted as an :term:`event` whenever a
77 repository group is updated.
78 """
79 name = 'repo-group-update'
80 display_name = lazy_ugettext('repository group update')
@@ -0,0 +1,62 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from rhodecode.integrations.registry import IntegrationTypeRegistry
24 from rhodecode.integrations.types import webhook, slack, hipchat, email
25
26 log = logging.getLogger(__name__)
27
28
29 # TODO: dan: This is currently global until we figure out what to do about
30 # VCS's not having a pyramid context - move it to pyramid app configuration
31 # includeme level later to allow per instance integration setup
32 integration_type_registry = IntegrationTypeRegistry()
33
34 integration_type_registry.register_integration_type(
35 webhook.WebhookIntegrationType)
36 integration_type_registry.register_integration_type(
37 slack.SlackIntegrationType)
38 integration_type_registry.register_integration_type(
39 hipchat.HipchatIntegrationType)
40 integration_type_registry.register_integration_type(
41 email.EmailIntegrationType)
42
43
44 def integrations_event_handler(event):
45 """
46 Takes an event and passes it to all enabled integrations
47 """
48 from rhodecode.model.integration import IntegrationModel
49
50 integration_model = IntegrationModel()
51 integrations = integration_model.get_for_event(event)
52 for integration in integrations:
53 try:
54 integration_model.send_event(integration, event)
55 except Exception:
56 log.exception(
57 'failure occured when sending event %s to integration %s' % (
58 event, integration))
59
60
61 def includeme(config):
62 config.include('rhodecode.integrations.routes')
@@ -0,0 +1,37 b''
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2012-2016 RhodeCode GmbH
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
20 import logging
21
22 log = logging.getLogger(__name__)
23
24
25 class IntegrationTypeRegistry(dict):
26 """
27 Registry Class to hold IntegrationTypes
28 """
29 def register_integration_type(self, IntegrationType):
30 key = IntegrationType.key
31 if key in self:
32 log.warning(
33 'Overriding existing integration type %s (%s) with %s' % (
34 self[key], key, IntegrationType))
35
36 self[key] = IntegrationType
37
@@ -0,0 +1,133 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from rhodecode.model.db import Repository, Integration
24 from rhodecode.config.routing import (
25 ADMIN_PREFIX, add_route_requirements, URL_NAME_REQUIREMENTS)
26 from rhodecode.integrations import integration_type_registry
27
28 log = logging.getLogger(__name__)
29
30
31 def includeme(config):
32 config.add_route('global_integrations_home',
33 ADMIN_PREFIX + '/integrations')
34 config.add_route('global_integrations_list',
35 ADMIN_PREFIX + '/integrations/{integration}')
36 for route_name in ['global_integrations_home', 'global_integrations_list']:
37 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
38 attr='index',
39 renderer='rhodecode:templates/admin/integrations/list.html',
40 request_method='GET',
41 route_name=route_name)
42
43 config.add_route('global_integrations_create',
44 ADMIN_PREFIX + '/integrations/{integration}/new',
45 custom_predicates=(valid_integration,))
46 config.add_route('global_integrations_edit',
47 ADMIN_PREFIX + '/integrations/{integration}/{integration_id}',
48 custom_predicates=(valid_integration,))
49 for route_name in ['global_integrations_create', 'global_integrations_edit']:
50 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
51 attr='settings_get',
52 renderer='rhodecode:templates/admin/integrations/edit.html',
53 request_method='GET',
54 route_name=route_name)
55 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
56 attr='settings_post',
57 renderer='rhodecode:templates/admin/integrations/edit.html',
58 request_method='POST',
59 route_name=route_name)
60
61 config.add_route('repo_integrations_home',
62 add_route_requirements(
63 '{repo_name}/settings/integrations',
64 URL_NAME_REQUIREMENTS
65 ),
66 custom_predicates=(valid_repo,))
67 config.add_route('repo_integrations_list',
68 add_route_requirements(
69 '{repo_name}/settings/integrations/{integration}',
70 URL_NAME_REQUIREMENTS
71 ),
72 custom_predicates=(valid_repo, valid_integration))
73 for route_name in ['repo_integrations_home', 'repo_integrations_list']:
74 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
75 attr='index',
76 request_method='GET',
77 route_name=route_name)
78
79 config.add_route('repo_integrations_create',
80 add_route_requirements(
81 '{repo_name}/settings/integrations/{integration}/new',
82 URL_NAME_REQUIREMENTS
83 ),
84 custom_predicates=(valid_repo, valid_integration))
85 config.add_route('repo_integrations_edit',
86 add_route_requirements(
87 '{repo_name}/settings/integrations/{integration}/{integration_id}',
88 URL_NAME_REQUIREMENTS
89 ),
90 custom_predicates=(valid_repo, valid_integration))
91 for route_name in ['repo_integrations_edit', 'repo_integrations_create']:
92 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
93 attr='settings_get',
94 renderer='rhodecode:templates/admin/integrations/edit.html',
95 request_method='GET',
96 route_name=route_name)
97 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
98 attr='settings_post',
99 renderer='rhodecode:templates/admin/integrations/edit.html',
100 request_method='POST',
101 route_name=route_name)
102
103
104 def valid_repo(info, request):
105 repo = Repository.get_by_repo_name(info['match']['repo_name'])
106 if repo:
107 return True
108
109
110 def valid_integration(info, request):
111 integration_type = info['match']['integration']
112 integration_id = info['match'].get('integration_id')
113 repo_name = info['match'].get('repo_name')
114
115 if integration_type not in integration_type_registry:
116 return False
117
118 repo = None
119 if repo_name:
120 repo = Repository.get_by_repo_name(info['match']['repo_name'])
121 if not repo:
122 return False
123
124 if integration_id:
125 integration = Integration.get(integration_id)
126 if not integration:
127 return False
128 if integration.integration_type != integration_type:
129 return False
130 if repo and repo.repo_id != integration.repo_id:
131 return False
132
133 return True
@@ -0,0 +1,45 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import colander
22
23 from rhodecode.translation import lazy_ugettext
24
25
26 class IntegrationSettingsSchemaBase(colander.MappingSchema):
27 """
28 This base schema is intended for use in integrations.
29 It adds a few default settings (e.g., "enabled"), so that integration
30 authors don't have to maintain a bunch of boilerplate.
31 """
32 enabled = colander.SchemaNode(
33 colander.Bool(),
34 default=True,
35 description=lazy_ugettext('Enable or disable this integration.'),
36 missing=False,
37 title=lazy_ugettext('Enabled'),
38 )
39
40 name = colander.SchemaNode(
41 colander.String(),
42 description=lazy_ugettext('Short name for this integration.'),
43 missing=colander.required,
44 title=lazy_ugettext('Integration name'),
45 )
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,42 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
22
23
24 class IntegrationTypeBase(object):
25 """ Base class for IntegrationType plugins """
26
27 def __init__(self, settings):
28 """
29 :param settings: dict of settings to be used for the integration
30 """
31 self.settings = settings
32
33
34 def settings_schema(self):
35 """
36 A colander schema of settings for the integration type
37
38 Subclasses can return their own schema but should always
39 inherit from IntegrationSettingsSchemaBase
40 """
41 return IntegrationSettingsSchemaBase()
42
@@ -0,0 +1,222 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from __future__ import unicode_literals
22 import deform
23 import logging
24 import colander
25
26 from mako.template import Template
27
28 from rhodecode import events
29 from rhodecode.translation import _, lazy_ugettext
30 from rhodecode.lib.celerylib import run_task
31 from rhodecode.lib.celerylib import tasks
32 from rhodecode.integrations.types.base import IntegrationTypeBase
33 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
34
35
36 log = logging.getLogger(__name__)
37
38 repo_push_template_plaintext = Template('''
39 Commits:
40
41 % for commit in data['push']['commits']:
42 ${commit['url']} by ${commit['author']} at ${commit['date']}
43 ${commit['message']}
44 ----
45
46 % endfor
47 ''')
48
49 ## TODO (marcink): think about putting this into a file, or use base.mako email template
50
51 repo_push_template_html = Template('''
52 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
53 <html xmlns="http://www.w3.org/1999/xhtml">
54 <head>
55 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
56 <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
57 <title>${subject}</title>
58 <style type="text/css">
59 /* Based on The MailChimp Reset INLINE: Yes. */
60 #outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
61 body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
62 /* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
63 .ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
64 .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
65 /* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
66 #backgroundTable {margin:0; padding:0; line-height: 100% !important;}
67 /* End reset */
68
69 /* defaults for images*/
70 img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
71 a img {border:none;}
72 .image_fix {display:block;}
73
74 body {line-height:1.2em;}
75 p {margin: 0 0 20px;}
76 h1, h2, h3, h4, h5, h6 {color:#323232!important;}
77 a {color:#427cc9;text-decoration:none;outline:none;cursor:pointer;}
78 a:focus {outline:none;}
79 a:hover {color: #305b91;}
80 h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color:#427cc9!important;text-decoration:none!important;}
81 h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {color: #305b91!important;}
82 h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {color: #305b91!important;}
83 table {font-size:13px;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;}
84 table td {padding:.65em 1em .65em 0;border-collapse:collapse;vertical-align:top;text-align:left;}
85 input {display:inline;border-radius:2px;border-style:solid;border: 1px solid #dbd9da;padding:.5em;}
86 input:focus {outline: 1px solid #979797}
87 @media only screen and (-webkit-min-device-pixel-ratio: 2) {
88 /* Put your iPhone 4g styles in here */
89 }
90
91 /* Android targeting */
92 @media only screen and (-webkit-device-pixel-ratio:.75){
93 /* Put CSS for low density (ldpi) Android layouts in here */
94 }
95 @media only screen and (-webkit-device-pixel-ratio:1){
96 /* Put CSS for medium density (mdpi) Android layouts in here */
97 }
98 @media only screen and (-webkit-device-pixel-ratio:1.5){
99 /* Put CSS for high density (hdpi) Android layouts in here */
100 }
101 /* end Android targeting */
102
103 </style>
104
105 <!-- Targeting Windows Mobile -->
106 <!--[if IEMobile 7]>
107 <style type="text/css">
108
109 </style>
110 <![endif]-->
111
112 <!--[if gte mso 9]>
113 <style>
114 /* Target Outlook 2007 and 2010 */
115 </style>
116 <![endif]-->
117 </head>
118 <body>
119 <!-- Wrapper/Container Table: Use a wrapper table to control the width and the background color consistently of your email. Use this approach instead of setting attributes on the body tag. -->
120 <table cellpadding="0" cellspacing="0" border="0" id="backgroundTable" align="left" style="margin:1%;width:97%;padding:0;font-family:sans-serif;font-weight:100;border:1px solid #dbd9da">
121 <tr>
122 <td valign="top" style="padding:0;">
123 <table cellpadding="0" cellspacing="0" border="0" align="left" width="100%">
124 <tr><td style="width:100%;padding:7px;background-color:#202020" valign="top">
125 <a style="color:#eeeeee;text-decoration:none;" href="${instance_url}">
126 ${'RhodeCode'}
127 </a>
128 </td></tr>
129 <tr>
130 <td style="padding:15px;" valign="top">
131 % for commit in data['push']['commits']:
132 <a href="${commit['url']}">${commit['short_id']}</a> by ${commit['author']} at ${commit['date']} <br/>
133 ${commit['message_html']} <br/>
134 <br/>
135 % endfor
136 </td>
137 </tr>
138 </table>
139 </td>
140 </tr>
141 </table>
142 <!-- End of wrapper table -->
143 <p><a style="margin-top:15px;margin-left:1%;font-family:sans-serif;font-weight:100;font-size:11px;color:#666666;text-decoration:none;" href="${instance_url}">
144 ${'This is a notification from RhodeCode. %(instance_url)s' % {'instance_url': instance_url}}
145 </a></p>
146 </body>
147 </html>
148 ''')
149
150
151 class EmailSettingsSchema(IntegrationSettingsSchemaBase):
152 @colander.instantiate(validator=colander.Length(min=1))
153 class recipients(colander.SequenceSchema):
154 title = lazy_ugettext('Recipients')
155 description = lazy_ugettext('Email addresses to send push events to')
156 widget = deform.widget.SequenceWidget(min_len=1)
157
158 recipient = colander.SchemaNode(
159 colander.String(),
160 title=lazy_ugettext('Email address'),
161 description=lazy_ugettext('Email address'),
162 default='',
163 validator=colander.Email(),
164 widget=deform.widget.TextInputWidget(
165 placeholder='user@domain.com',
166 ),
167 )
168
169
170 class EmailIntegrationType(IntegrationTypeBase):
171 key = 'email'
172 display_name = lazy_ugettext('Email')
173 SettingsSchema = EmailSettingsSchema
174
175 def settings_schema(self):
176 schema = EmailSettingsSchema()
177 return schema
178
179 def send_event(self, event):
180 data = event.as_dict()
181 log.debug('got event: %r', event)
182
183 if isinstance(event, events.RepoPushEvent):
184 repo_push_handler(data, self.settings)
185 else:
186 log.debug('ignoring event: %r', event)
187
188
189 def repo_push_handler(data, settings):
190 commit_num = len(data['push']['commits'])
191 server_url = data['server_url']
192
193 if commit_num == 0:
194 subject = '[{repo_name}] {author} pushed {commit_num} commit on branches: {branches}'.format(
195 author=data['actor']['username'],
196 repo_name=data['repo']['repo_name'],
197 commit_num=commit_num,
198 branches=', '.join(
199 branch['name'] for branch in data['push']['branches'])
200 )
201 else:
202 subject = '[{repo_name}] {author} pushed {commit_num} commits on branches: {branches}'.format(
203 author=data['actor']['username'],
204 repo_name=data['repo']['repo_name'],
205 commit_num=commit_num,
206 branches=', '.join(
207 branch['name'] for branch in data['push']['branches']))
208
209 email_body_plaintext = repo_push_template_plaintext.render(
210 data=data,
211 subject=subject,
212 instance_url=server_url)
213
214 email_body_html = repo_push_template_html.render(
215 data=data,
216 subject=subject,
217 instance_url=server_url)
218
219 for email_address in settings['recipients']:
220 run_task(
221 tasks.send_email, email_address, subject,
222 email_body_plaintext, email_body_html)
@@ -0,0 +1,242 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from __future__ import unicode_literals
22 import deform
23 import re
24 import logging
25 import requests
26 import colander
27 import textwrap
28 from celery.task import task
29 from mako.template import Template
30
31 from rhodecode import events
32 from rhodecode.translation import lazy_ugettext
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.celerylib import run_task
35 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.integrations.types.base import IntegrationTypeBase
37 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
38
39 log = logging.getLogger(__name__)
40
41
42 class HipchatSettingsSchema(IntegrationSettingsSchemaBase):
43 color_choices = [
44 ('yellow', lazy_ugettext('Yellow')),
45 ('red', lazy_ugettext('Red')),
46 ('green', lazy_ugettext('Green')),
47 ('purple', lazy_ugettext('Purple')),
48 ('gray', lazy_ugettext('Gray')),
49 ]
50
51 server_url = colander.SchemaNode(
52 colander.String(),
53 title=lazy_ugettext('Hipchat server URL'),
54 description=lazy_ugettext('Hipchat integration url.'),
55 default='',
56 preparer=strip_whitespace,
57 validator=colander.url,
58 widget=deform.widget.TextInputWidget(
59 placeholder='https://?.hipchat.com/v2/room/?/notification?auth_token=?',
60 ),
61 )
62 notify = colander.SchemaNode(
63 colander.Bool(),
64 title=lazy_ugettext('Notify'),
65 description=lazy_ugettext('Make a notification to the users in room.'),
66 missing=False,
67 default=False,
68 )
69 color = colander.SchemaNode(
70 colander.String(),
71 title=lazy_ugettext('Color'),
72 description=lazy_ugettext('Background color of message.'),
73 missing='',
74 validator=colander.OneOf([x[0] for x in color_choices]),
75 widget=deform.widget.Select2Widget(
76 values=color_choices,
77 ),
78 )
79
80
81 repo_push_template = Template('''
82 <b>${data['actor']['username']}</b> pushed to
83 %if data['push']['branches']:
84 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'}
85 ${', '.join('<a href="%s">%s</a>' % (branch['url'], branch['name']) for branch in data['push']['branches'])}
86 %else:
87 unknown branch
88 %endif
89 in <a href="${data['repo']['url']}">${data['repo']['repo_name']}</a>
90 <br>
91 <ul>
92 %for commit in data['push']['commits']:
93 <li>
94 <a href="${commit['url']}">${commit['short_id']}</a> - ${commit['message_html']}
95 </li>
96 %endfor
97 </ul>
98 ''')
99
100
101
102 class HipchatIntegrationType(IntegrationTypeBase):
103 key = 'hipchat'
104 display_name = lazy_ugettext('Hipchat')
105 valid_events = [
106 events.PullRequestCloseEvent,
107 events.PullRequestMergeEvent,
108 events.PullRequestUpdateEvent,
109 events.PullRequestCommentEvent,
110 events.PullRequestReviewEvent,
111 events.PullRequestCreateEvent,
112 events.RepoPushEvent,
113 events.RepoCreateEvent,
114 ]
115
116 def send_event(self, event):
117 if event.__class__ not in self.valid_events:
118 log.debug('event not valid: %r' % event)
119 return
120
121 if event.name not in self.settings['events']:
122 log.debug('event ignored: %r' % event)
123 return
124
125 data = event.as_dict()
126
127 text = '<b>%s<b> caused a <b>%s</b> event' % (
128 data['actor']['username'], event.name)
129
130 log.debug('handling hipchat event for %s' % event.name)
131
132 if isinstance(event, events.PullRequestCommentEvent):
133 text = self.format_pull_request_comment_event(event, data)
134 elif isinstance(event, events.PullRequestReviewEvent):
135 text = self.format_pull_request_review_event(event, data)
136 elif isinstance(event, events.PullRequestEvent):
137 text = self.format_pull_request_event(event, data)
138 elif isinstance(event, events.RepoPushEvent):
139 text = self.format_repo_push_event(data)
140 elif isinstance(event, events.RepoCreateEvent):
141 text = self.format_repo_create_event(data)
142 else:
143 log.error('unhandled event type: %r' % event)
144
145 run_task(post_text_to_hipchat, self.settings, text)
146
147 def settings_schema(self):
148 schema = HipchatSettingsSchema()
149 schema.add(colander.SchemaNode(
150 colander.Set(),
151 widget=deform.widget.CheckboxChoiceWidget(
152 values=sorted(
153 [(e.name, e.display_name) for e in self.valid_events]
154 )
155 ),
156 description="Events activated for this integration",
157 name='events'
158 ))
159
160 return schema
161
162 def format_pull_request_comment_event(self, event, data):
163 comment_text = data['comment']['text']
164 if len(comment_text) > 200:
165 comment_text = '{comment_text}<a href="{comment_url}">...<a/>'.format(
166 comment_text=comment_text[:200],
167 comment_url=data['comment']['url'],
168 )
169
170 comment_status = ''
171 if data['comment']['status']:
172 comment_status = '[{}]: '.format(data['comment']['status'])
173
174 return (textwrap.dedent(
175 '''
176 {user} commented on pull request <a href="{pr_url}">{number}</a> - {pr_title}:
177 >>> {comment_status}{comment_text}
178 ''').format(
179 comment_status=comment_status,
180 user=data['actor']['username'],
181 number=data['pullrequest']['pull_request_id'],
182 pr_url=data['pullrequest']['url'],
183 pr_status=data['pullrequest']['status'],
184 pr_title=data['pullrequest']['title'],
185 comment_text=comment_text
186 )
187 )
188
189 def format_pull_request_review_event(self, event, data):
190 return (textwrap.dedent(
191 '''
192 Status changed to {pr_status} for pull request <a href="{pr_url}">#{number}</a> - {pr_title}
193 ''').format(
194 user=data['actor']['username'],
195 number=data['pullrequest']['pull_request_id'],
196 pr_url=data['pullrequest']['url'],
197 pr_status=data['pullrequest']['status'],
198 pr_title=data['pullrequest']['title'],
199 )
200 )
201
202 def format_pull_request_event(self, event, data):
203 action = {
204 events.PullRequestCloseEvent: 'closed',
205 events.PullRequestMergeEvent: 'merged',
206 events.PullRequestUpdateEvent: 'updated',
207 events.PullRequestCreateEvent: 'created',
208 }.get(event.__class__, str(event.__class__))
209
210 return ('Pull request <a href="{url}">#{number}</a> - {title} '
211 '{action} by {user}').format(
212 user=data['actor']['username'],
213 number=data['pullrequest']['pull_request_id'],
214 url=data['pullrequest']['url'],
215 title=data['pullrequest']['title'],
216 action=action
217 )
218
219 def format_repo_push_event(self, data):
220 result = repo_push_template.render(
221 data=data,
222 )
223 return result
224
225 def format_repo_create_event(self, data):
226 return '<a href="{}">{}</a> ({}) repository created by <b>{}</b>'.format(
227 data['repo']['url'],
228 data['repo']['repo_name'],
229 data['repo']['repo_type'],
230 data['actor']['username'],
231 )
232
233
234 @task(ignore_result=True)
235 def post_text_to_hipchat(settings, text):
236 log.debug('sending %s to hipchat %s' % (text, settings['server_url']))
237 resp = requests.post(settings['server_url'], json={
238 "message": text,
239 "color": settings.get('color', 'yellow'),
240 "notify": settings.get('notify', False),
241 })
242 resp.raise_for_status() # raise exception on a failed request
@@ -0,0 +1,253 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from __future__ import unicode_literals
22 import deform
23 import re
24 import logging
25 import requests
26 import colander
27 import textwrap
28 from celery.task import task
29 from mako.template import Template
30
31 from rhodecode import events
32 from rhodecode.translation import lazy_ugettext
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.celerylib import run_task
35 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.integrations.types.base import IntegrationTypeBase
37 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
38
39 log = logging.getLogger(__name__)
40
41
42 class SlackSettingsSchema(IntegrationSettingsSchemaBase):
43 service = colander.SchemaNode(
44 colander.String(),
45 title=lazy_ugettext('Slack service URL'),
46 description=h.literal(lazy_ugettext(
47 'This can be setup at the '
48 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
49 'slack app manager</a>')),
50 default='',
51 preparer=strip_whitespace,
52 validator=colander.url,
53 widget=deform.widget.TextInputWidget(
54 placeholder='https://hooks.slack.com/services/...',
55 ),
56 )
57 username = colander.SchemaNode(
58 colander.String(),
59 title=lazy_ugettext('Username'),
60 description=lazy_ugettext('Username to show notifications coming from.'),
61 missing='Rhodecode',
62 preparer=strip_whitespace,
63 widget=deform.widget.TextInputWidget(
64 placeholder='Rhodecode'
65 ),
66 )
67 channel = colander.SchemaNode(
68 colander.String(),
69 title=lazy_ugettext('Channel'),
70 description=lazy_ugettext('Channel to send notifications to.'),
71 missing='',
72 preparer=strip_whitespace,
73 widget=deform.widget.TextInputWidget(
74 placeholder='#general'
75 ),
76 )
77 icon_emoji = colander.SchemaNode(
78 colander.String(),
79 title=lazy_ugettext('Emoji'),
80 description=lazy_ugettext('Emoji to use eg. :studio_microphone:'),
81 missing='',
82 preparer=strip_whitespace,
83 widget=deform.widget.TextInputWidget(
84 placeholder=':studio_microphone:'
85 ),
86 )
87
88
89 repo_push_template = Template(r'''
90 *${data['actor']['username']}* pushed to \
91 %if data['push']['branches']:
92 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'} \
93 ${', '.join('<%s|%s>' % (branch['url'], branch['name']) for branch in data['push']['branches'])} \
94 %else:
95 unknown branch \
96 %endif
97 in <${data['repo']['url']}|${data['repo']['repo_name']}>
98 >>>
99 %for commit in data['push']['commits']:
100 <${commit['url']}|${commit['short_id']}> - ${commit['message_html']|html_to_slack_links}
101 %endfor
102 ''')
103
104
105 class SlackIntegrationType(IntegrationTypeBase):
106 key = 'slack'
107 display_name = lazy_ugettext('Slack')
108 SettingsSchema = SlackSettingsSchema
109 valid_events = [
110 events.PullRequestCloseEvent,
111 events.PullRequestMergeEvent,
112 events.PullRequestUpdateEvent,
113 events.PullRequestCommentEvent,
114 events.PullRequestReviewEvent,
115 events.PullRequestCreateEvent,
116 events.RepoPushEvent,
117 events.RepoCreateEvent,
118 ]
119
120 def send_event(self, event):
121 if event.__class__ not in self.valid_events:
122 log.debug('event not valid: %r' % event)
123 return
124
125 if event.name not in self.settings['events']:
126 log.debug('event ignored: %r' % event)
127 return
128
129 data = event.as_dict()
130
131 text = '*%s* caused a *%s* event' % (
132 data['actor']['username'], event.name)
133
134 log.debug('handling slack event for %s' % event.name)
135
136 if isinstance(event, events.PullRequestCommentEvent):
137 text = self.format_pull_request_comment_event(event, data)
138 elif isinstance(event, events.PullRequestReviewEvent):
139 text = self.format_pull_request_review_event(event, data)
140 elif isinstance(event, events.PullRequestEvent):
141 text = self.format_pull_request_event(event, data)
142 elif isinstance(event, events.RepoPushEvent):
143 text = self.format_repo_push_event(data)
144 elif isinstance(event, events.RepoCreateEvent):
145 text = self.format_repo_create_event(data)
146 else:
147 log.error('unhandled event type: %r' % event)
148
149 run_task(post_text_to_slack, self.settings, text)
150
151 def settings_schema(self):
152 schema = SlackSettingsSchema()
153 schema.add(colander.SchemaNode(
154 colander.Set(),
155 widget=deform.widget.CheckboxChoiceWidget(
156 values=sorted(
157 [(e.name, e.display_name) for e in self.valid_events]
158 )
159 ),
160 description="Events activated for this integration",
161 name='events'
162 ))
163
164 return schema
165
166 def format_pull_request_comment_event(self, event, data):
167 comment_text = data['comment']['text']
168 if len(comment_text) > 200:
169 comment_text = '<{comment_url}|{comment_text}...>'.format(
170 comment_text=comment_text[:200],
171 comment_url=data['comment']['url'],
172 )
173
174 comment_status = ''
175 if data['comment']['status']:
176 comment_status = '[{}]: '.format(data['comment']['status'])
177
178 return (textwrap.dedent(
179 '''
180 {user} commented on pull request <{pr_url}|#{number}> - {pr_title}:
181 >>> {comment_status}{comment_text}
182 ''').format(
183 comment_status=comment_status,
184 user=data['actor']['username'],
185 number=data['pullrequest']['pull_request_id'],
186 pr_url=data['pullrequest']['url'],
187 pr_status=data['pullrequest']['status'],
188 pr_title=data['pullrequest']['title'],
189 comment_text=comment_text
190 )
191 )
192
193 def format_pull_request_review_event(self, event, data):
194 return (textwrap.dedent(
195 '''
196 Status changed to {pr_status} for pull request <{pr_url}|#{number}> - {pr_title}
197 ''').format(
198 user=data['actor']['username'],
199 number=data['pullrequest']['pull_request_id'],
200 pr_url=data['pullrequest']['url'],
201 pr_status=data['pullrequest']['status'],
202 pr_title=data['pullrequest']['title'],
203 )
204 )
205
206 def format_pull_request_event(self, event, data):
207 action = {
208 events.PullRequestCloseEvent: 'closed',
209 events.PullRequestMergeEvent: 'merged',
210 events.PullRequestUpdateEvent: 'updated',
211 events.PullRequestCreateEvent: 'created',
212 }.get(event.__class__, str(event.__class__))
213
214 return ('Pull request <{url}|#{number}> - {title} '
215 '{action} by {user}').format(
216 user=data['actor']['username'],
217 number=data['pullrequest']['pull_request_id'],
218 url=data['pullrequest']['url'],
219 title=data['pullrequest']['title'],
220 action=action
221 )
222
223 def format_repo_push_event(self, data):
224 result = repo_push_template.render(
225 data=data,
226 html_to_slack_links=html_to_slack_links,
227 )
228 return result
229
230 def format_repo_create_event(self, data):
231 return '<{}|{}> ({}) repository created by *{}*'.format(
232 data['repo']['url'],
233 data['repo']['repo_name'],
234 data['repo']['repo_type'],
235 data['actor']['username'],
236 )
237
238
239 def html_to_slack_links(message):
240 return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
241 r'<\1|\2>', message)
242
243
244 @task(ignore_result=True)
245 def post_text_to_slack(settings, text):
246 log.debug('sending %s to slack %s' % (text, settings['service']))
247 resp = requests.post(settings['service'], json={
248 "channel": settings.get('channel', ''),
249 "username": settings.get('username', 'Rhodecode'),
250 "text": text,
251 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:')
252 })
253 resp.raise_for_status() # raise exception on a failed request
@@ -0,0 +1,111 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from __future__ import unicode_literals
22
23 import deform
24 import logging
25 import requests
26 import colander
27 from celery.task import task
28 from mako.template import Template
29
30 from rhodecode import events
31 from rhodecode.translation import lazy_ugettext
32 from rhodecode.integrations.types.base import IntegrationTypeBase
33 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
34
35 log = logging.getLogger(__name__)
36
37
38 class WebhookSettingsSchema(IntegrationSettingsSchemaBase):
39 url = colander.SchemaNode(
40 colander.String(),
41 title=lazy_ugettext('Webhook URL'),
42 description=lazy_ugettext('URL of the webhook to receive POST event.'),
43 default='',
44 validator=colander.url,
45 widget=deform.widget.TextInputWidget(
46 placeholder='https://www.example.com/webhook'
47 ),
48 )
49 secret_token = colander.SchemaNode(
50 colander.String(),
51 title=lazy_ugettext('Secret Token'),
52 description=lazy_ugettext('String used to validate received payloads.'),
53 default='',
54 widget=deform.widget.TextInputWidget(
55 placeholder='secret_token'
56 ),
57 )
58
59
60 class WebhookIntegrationType(IntegrationTypeBase):
61 key = 'webhook'
62 display_name = lazy_ugettext('Webhook')
63 valid_events = [
64 events.PullRequestCloseEvent,
65 events.PullRequestMergeEvent,
66 events.PullRequestUpdateEvent,
67 events.PullRequestCommentEvent,
68 events.PullRequestReviewEvent,
69 events.PullRequestCreateEvent,
70 events.RepoPushEvent,
71 events.RepoCreateEvent,
72 ]
73
74 def settings_schema(self):
75 schema = WebhookSettingsSchema()
76 schema.add(colander.SchemaNode(
77 colander.Set(),
78 widget=deform.widget.CheckboxChoiceWidget(
79 values=sorted(
80 [(e.name, e.display_name) for e in self.valid_events]
81 )
82 ),
83 description="Events activated for this integration",
84 name='events'
85 ))
86 return schema
87
88 def send_event(self, event):
89 log.debug('handling event %s with webhook integration %s',
90 event.name, self)
91
92 if event.__class__ not in self.valid_events:
93 log.debug('event not valid: %r' % event)
94 return
95
96 if event.name not in self.settings['events']:
97 log.debug('event ignored: %r' % event)
98 return
99
100 data = event.as_dict()
101 post_to_webhook(data, self.settings)
102
103
104 @task(ignore_result=True)
105 def post_to_webhook(data, settings):
106 log.debug('sending event:%s to webhook %s', data['name'], settings['url'])
107 resp = requests.post(settings['url'], json={
108 'token': settings['secret_token'],
109 'event': data
110 })
111 resp.raise_for_status() # raise exception on a failed request
@@ -0,0 +1,272 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import colander
22 import logging
23 import pylons
24 import deform
25
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
27 from pyramid.renderers import render
28 from pyramid.response import Response
29
30 from rhodecode.lib import auth
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
32 from rhodecode.model.db import Repository, Session, Integration
33 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.integration import IntegrationModel
35 from rhodecode.admin.navigation import navigation_list
36 from rhodecode.translation import _
37 from rhodecode.integrations import integration_type_registry
38
39 log = logging.getLogger(__name__)
40
41
42 class IntegrationSettingsViewBase(object):
43 """ Base Integration settings view used by both repo / global settings """
44
45 def __init__(self, context, request):
46 self.context = context
47 self.request = request
48 self._load_general_context()
49
50 if not self.perm_check(request.user):
51 raise HTTPForbidden()
52
53 def _load_general_context(self):
54 """
55 This avoids boilerplate for repo/global+list/edit+views/templates
56 by doing all possible contexts at the same time however it should
57 be split up into separate functions once more "contexts" exist
58 """
59
60 self.IntegrationType = None
61 self.repo = None
62 self.integration = None
63 self.integrations = {}
64
65 request = self.request
66
67 if 'repo_name' in request.matchdict: # we're in a repo context
68 repo_name = request.matchdict['repo_name']
69 self.repo = Repository.get_by_repo_name(repo_name)
70
71 if 'integration' in request.matchdict: # we're in integration context
72 integration_type = request.matchdict['integration']
73 self.IntegrationType = integration_type_registry[integration_type]
74
75 if 'integration_id' in request.matchdict: # single integration context
76 integration_id = request.matchdict['integration_id']
77 self.integration = Integration.get(integration_id)
78 else: # list integrations context
79 for integration in IntegrationModel().get_integrations(self.repo):
80 self.integrations.setdefault(integration.integration_type, []
81 ).append(integration)
82
83 self.settings = self.integration and self.integration.settings or {}
84
85 def _template_c_context(self):
86 # TODO: dan: this is a stopgap in order to inherit from current pylons
87 # based admin/repo settings templates - this should be removed entirely
88 # after port to pyramid
89
90 c = pylons.tmpl_context
91 c.active = 'integrations'
92 c.rhodecode_user = self.request.user
93 c.repo = self.repo
94 c.repo_name = self.repo and self.repo.repo_name or None
95 if self.repo:
96 c.repo_info = self.repo
97 c.rhodecode_db_repo = self.repo
98 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
99 else:
100 c.navlist = navigation_list(self.request)
101
102 return c
103
104 def _form_schema(self):
105 if self.integration:
106 settings = self.integration.settings
107 else:
108 settings = {}
109 return self.IntegrationType(settings=settings).settings_schema()
110
111 def settings_get(self, defaults=None, errors=None, form=None):
112 """
113 View that displays the plugin settings as a form.
114 """
115 defaults = defaults or {}
116 errors = errors or {}
117
118 if self.integration:
119 defaults = self.integration.settings or {}
120 defaults['name'] = self.integration.name
121 defaults['enabled'] = self.integration.enabled
122 else:
123 if self.repo:
124 scope = self.repo.repo_name
125 else:
126 scope = _('Global')
127
128 defaults['name'] = '{} {} integration'.format(scope,
129 self.IntegrationType.display_name)
130 defaults['enabled'] = True
131
132 schema = self._form_schema().bind(request=self.request)
133
134 if self.integration:
135 buttons = ('submit', 'delete')
136 else:
137 buttons = ('submit',)
138
139 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
140
141 for node in schema:
142 setting = self.settings.get(node.name)
143 if setting is not None:
144 defaults.setdefault(node.name, setting)
145 else:
146 if node.default:
147 defaults.setdefault(node.name, node.default)
148
149 template_context = {
150 'form': form,
151 'defaults': defaults,
152 'errors': errors,
153 'schema': schema,
154 'current_IntegrationType': self.IntegrationType,
155 'integration': self.integration,
156 'settings': self.settings,
157 'resource': self.context,
158 'c': self._template_c_context(),
159 }
160
161 return template_context
162
163 @auth.CSRFRequired()
164 def settings_post(self):
165 """
166 View that validates and stores the plugin settings.
167 """
168 if self.request.params.get('delete'):
169 Session().delete(self.integration)
170 Session().commit()
171 self.request.session.flash(
172 _('Integration {integration_name} deleted successfully.').format(
173 integration_name=self.integration.name),
174 queue='success')
175 if self.repo:
176 redirect_to = self.request.route_url(
177 'repo_integrations_home', repo_name=self.repo.repo_name)
178 else:
179 redirect_to = self.request.route_url('global_integrations_home')
180 raise HTTPFound(redirect_to)
181
182 schema = self._form_schema().bind(request=self.request)
183
184 form = deform.Form(schema, buttons=('submit', 'delete'))
185
186 params = {}
187 for node in schema.children:
188 if type(node.typ) in (colander.Set, colander.List):
189 val = self.request.params.getall(node.name)
190 else:
191 val = self.request.params.get(node.name)
192 if val:
193 params[node.name] = val
194
195 controls = self.request.POST.items()
196 try:
197 valid_data = form.validate(controls)
198 except deform.ValidationFailure as e:
199 self.request.session.flash(
200 _('Errors exist when saving integration settings. '
201 'Please check the form inputs.'),
202 queue='error')
203 return self.settings_get(errors={}, defaults=params, form=e)
204
205 if not self.integration:
206 self.integration = Integration()
207 self.integration.integration_type = self.IntegrationType.key
208 if self.repo:
209 self.integration.repo = self.repo
210 Session().add(self.integration)
211
212 self.integration.enabled = valid_data.pop('enabled', False)
213 self.integration.name = valid_data.pop('name')
214 self.integration.settings = valid_data
215
216 Session().commit()
217
218 # Display success message and redirect.
219 self.request.session.flash(
220 _('Integration {integration_name} updated successfully.').format(
221 integration_name=self.IntegrationType.display_name),
222 queue='success')
223
224 if self.repo:
225 redirect_to = self.request.route_url(
226 'repo_integrations_edit', repo_name=self.repo.repo_name,
227 integration=self.integration.integration_type,
228 integration_id=self.integration.integration_id)
229 else:
230 redirect_to = self.request.route_url(
231 'global_integrations_edit',
232 integration=self.integration.integration_type,
233 integration_id=self.integration.integration_id)
234
235 return HTTPFound(redirect_to)
236
237 def index(self):
238 current_integrations = self.integrations
239 if self.IntegrationType:
240 current_integrations = {
241 self.IntegrationType.key: self.integrations.get(
242 self.IntegrationType.key, [])
243 }
244
245 template_context = {
246 'current_IntegrationType': self.IntegrationType,
247 'current_integrations': current_integrations,
248 'available_integrations': integration_type_registry,
249 'c': self._template_c_context()
250 }
251
252 if self.repo:
253 html = render('rhodecode:templates/admin/integrations/list.html',
254 template_context,
255 request=self.request)
256 else:
257 html = render('rhodecode:templates/admin/integrations/list.html',
258 template_context,
259 request=self.request)
260
261 return Response(html)
262
263
264 class GlobalIntegrationsView(IntegrationSettingsViewBase):
265 def perm_check(self, user):
266 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
267
268
269 class RepoIntegrationsView(IntegrationSettingsViewBase):
270 def perm_check(self, user):
271 return auth.HasRepoPermissionAll('repository.admin'
272 )(repo_name=self.repo.repo_name, user=user)
@@ -0,0 +1,219 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import os
23
24 import itsdangerous
25 import requests
26
27 from dogpile.core import ReadWriteMutex
28
29 import rhodecode.lib.helpers as h
30
31 from rhodecode.lib.auth import HasRepoPermissionAny
32 from rhodecode.lib.ext_json import json
33 from rhodecode.model.db import User
34
35 log = logging.getLogger(__name__)
36
37 LOCK = ReadWriteMutex()
38
39 STATE_PUBLIC_KEYS = ['id', 'username', 'first_name', 'last_name',
40 'icon_link', 'display_name', 'display_link']
41
42
43 class ChannelstreamException(Exception):
44 pass
45
46
47 class ChannelstreamConnectionException(Exception):
48 pass
49
50
51 class ChannelstreamPermissionException(Exception):
52 pass
53
54
55 def channelstream_request(config, payload, endpoint, raise_exc=True):
56 signer = itsdangerous.TimestampSigner(config['secret'])
57 sig_for_server = signer.sign(endpoint)
58 secret_headers = {'x-channelstream-secret': sig_for_server,
59 'x-channelstream-endpoint': endpoint,
60 'Content-Type': 'application/json'}
61 req_url = 'http://{}{}'.format(config['server'], endpoint)
62 response = None
63 try:
64 response = requests.post(req_url, data=json.dumps(payload),
65 headers=secret_headers).json()
66 except requests.ConnectionError:
67 log.exception('ConnectionError happened')
68 if raise_exc:
69 raise ChannelstreamConnectionException()
70 except Exception:
71 log.exception('Exception related to channelstream happened')
72 if raise_exc:
73 raise ChannelstreamConnectionException()
74 return response
75
76
77 def get_user_data(user_id):
78 user = User.get(user_id)
79 return {
80 'id': user.user_id,
81 'username': user.username,
82 'first_name': user.name,
83 'last_name': user.lastname,
84 'icon_link': h.gravatar_url(user.email, 14),
85 'display_name': h.person(user, 'username_or_name_or_email'),
86 'display_link': h.link_to_user(user),
87 }
88
89
90 def broadcast_validator(channel_name):
91 """ checks if user can access the broadcast channel """
92 if channel_name == 'broadcast':
93 return True
94
95
96 def repo_validator(channel_name):
97 """ checks if user can access the broadcast channel """
98 channel_prefix = '/repo$'
99 if channel_name.startswith(channel_prefix):
100 elements = channel_name[len(channel_prefix):].split('$')
101 repo_name = elements[0]
102 can_access = HasRepoPermissionAny(
103 'repository.read',
104 'repository.write',
105 'repository.admin')(repo_name)
106 log.debug('permission check for {} channel '
107 'resulted in {}'.format(repo_name, can_access))
108 if can_access:
109 return True
110 return False
111
112
113 def check_channel_permissions(channels, plugin_validators, should_raise=True):
114 valid_channels = []
115
116 validators = [broadcast_validator, repo_validator]
117 if plugin_validators:
118 validators.extend(plugin_validators)
119 for channel_name in channels:
120 is_valid = False
121 for validator in validators:
122 if validator(channel_name):
123 is_valid = True
124 break
125 if is_valid:
126 valid_channels.append(channel_name)
127 else:
128 if should_raise:
129 raise ChannelstreamPermissionException()
130 return valid_channels
131
132
133 def get_channels_info(self, channels):
134 payload = {'channels': channels}
135 # gather persistence info
136 return channelstream_request(self._config(), payload, '/info')
137
138
139 def parse_channels_info(info_result, include_channel_info=None):
140 """
141 Returns data that contains only secure information that can be
142 presented to clients
143 """
144 include_channel_info = include_channel_info or []
145
146 user_state_dict = {}
147 for userinfo in info_result['users']:
148 user_state_dict[userinfo['user']] = {
149 k: v for k, v in userinfo['state'].items()
150 if k in STATE_PUBLIC_KEYS
151 }
152
153 channels_info = {}
154
155 for c_name, c_info in info_result['channels'].items():
156 if c_name not in include_channel_info:
157 continue
158 connected_list = []
159 for userinfo in c_info['users']:
160 connected_list.append({
161 'user': userinfo['user'],
162 'state': user_state_dict[userinfo['user']]
163 })
164 channels_info[c_name] = {'users': connected_list,
165 'history': c_info['history']}
166
167 return channels_info
168
169
170 def log_filepath(history_location, channel_name):
171 filename = '{}.log'.format(channel_name.encode('hex'))
172 filepath = os.path.join(history_location, filename)
173 return filepath
174
175
176 def read_history(history_location, channel_name):
177 filepath = log_filepath(history_location, channel_name)
178 if not os.path.exists(filepath):
179 return []
180 history_lines_limit = -100
181 history = []
182 with open(filepath, 'rb') as f:
183 for line in f.readlines()[history_lines_limit:]:
184 try:
185 history.append(json.loads(line))
186 except Exception:
187 log.exception('Failed to load history')
188 return history
189
190
191 def update_history_from_logs(config, channels, payload):
192 history_location = config.get('history.location')
193 for channel in channels:
194 history = read_history(history_location, channel)
195 payload['channels_info'][channel]['history'] = history
196
197
198 def write_history(config, message):
199 """ writes a messge to a base64encoded filename """
200 history_location = config.get('history.location')
201 if not os.path.exists(history_location):
202 return
203 try:
204 LOCK.acquire_write_lock()
205 filepath = log_filepath(history_location, message['channel'])
206 with open(filepath, 'ab') as f:
207 json.dump(message, f)
208 f.write('\n')
209 finally:
210 LOCK.release_write_lock()
211
212
213 def get_connection_validators(registry):
214 validators = []
215 for k, config in registry.rhodecode_plugins.iteritems():
216 validator = config.get('channelstream', {}).get('connect_validator')
217 if validator:
218 validators.append(validator)
219 return validators
This diff has been collapsed as it changes many lines, (3516 lines changed) Show them Hide them
@@ -0,0 +1,3516 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Database Models for RhodeCode Enterprise
23 """
24
25 import os
26 import sys
27 import time
28 import hashlib
29 import logging
30 import datetime
31 import warnings
32 import ipaddress
33 import functools
34 import traceback
35 import collections
36
37
38 from sqlalchemy import *
39 from sqlalchemy.exc import IntegrityError
40 from sqlalchemy.ext.declarative import declared_attr
41 from sqlalchemy.ext.hybrid import hybrid_property
42 from sqlalchemy.orm import (
43 relationship, joinedload, class_mapper, validates, aliased)
44 from sqlalchemy.sql.expression import true
45 from beaker.cache import cache_region, region_invalidate
46 from webob.exc import HTTPNotFound
47 from zope.cachedescriptors.property import Lazy as LazyProperty
48
49 from pylons import url
50 from pylons.i18n.translation import lazy_ugettext as _
51
52 from rhodecode.lib.vcs import get_backend
53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 from rhodecode.lib.vcs.exceptions import VCSError
55 from rhodecode.lib.vcs.backends.base import (
56 EmptyCommit, Reference, MergeFailureReason)
57 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 from rhodecode.lib.ext_json import json
61 from rhodecode.lib.caching_query import FromCache
62 from rhodecode.lib.encrypt import AESCipher
63
64 from rhodecode.model.meta import Base, Session
65
66 URL_SEP = '/'
67 log = logging.getLogger(__name__)
68
69 # =============================================================================
70 # BASE CLASSES
71 # =============================================================================
72
73 # this is propagated from .ini file rhodecode.encrypted_values.secret or
74 # beaker.session.secret if first is not set.
75 # and initialized at environment.py
76 ENCRYPTION_KEY = None
77
78 # used to sort permissions by types, '#' used here is not allowed to be in
79 # usernames, and it's very early in sorted string.printable table.
80 PERMISSION_TYPE_SORT = {
81 'admin': '####',
82 'write': '###',
83 'read': '##',
84 'none': '#',
85 }
86
87
88 def display_sort(obj):
89 """
90 Sort function used to sort permissions in .permissions() function of
91 Repository, RepoGroup, UserGroup. Also it put the default user in front
92 of all other resources
93 """
94
95 if obj.username == User.DEFAULT_USER:
96 return '#####'
97 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
98 return prefix + obj.username
99
100
101 def _hash_key(k):
102 return md5_safe(k)
103
104
105 class EncryptedTextValue(TypeDecorator):
106 """
107 Special column for encrypted long text data, use like::
108
109 value = Column("encrypted_value", EncryptedValue(), nullable=False)
110
111 This column is intelligent so if value is in unencrypted form it return
112 unencrypted form, but on save it always encrypts
113 """
114 impl = Text
115
116 def process_bind_param(self, value, dialect):
117 if not value:
118 return value
119 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
120 # protect against double encrypting if someone manually starts
121 # doing
122 raise ValueError('value needs to be in unencrypted format, ie. '
123 'not starting with enc$aes')
124 return 'enc$aes_hmac$%s' % AESCipher(
125 ENCRYPTION_KEY, hmac=True).encrypt(value)
126
127 def process_result_value(self, value, dialect):
128 import rhodecode
129
130 if not value:
131 return value
132
133 parts = value.split('$', 3)
134 if not len(parts) == 3:
135 # probably not encrypted values
136 return value
137 else:
138 if parts[0] != 'enc':
139 # parts ok but without our header ?
140 return value
141 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
142 'rhodecode.encrypted_values.strict') or True)
143 # at that stage we know it's our encryption
144 if parts[1] == 'aes':
145 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
146 elif parts[1] == 'aes_hmac':
147 decrypted_data = AESCipher(
148 ENCRYPTION_KEY, hmac=True,
149 strict_verification=enc_strict_mode).decrypt(parts[2])
150 else:
151 raise ValueError(
152 'Encryption type part is wrong, must be `aes` '
153 'or `aes_hmac`, got `%s` instead' % (parts[1]))
154 return decrypted_data
155
156
157 class BaseModel(object):
158 """
159 Base Model for all classes
160 """
161
162 @classmethod
163 def _get_keys(cls):
164 """return column names for this model """
165 return class_mapper(cls).c.keys()
166
167 def get_dict(self):
168 """
169 return dict with keys and values corresponding
170 to this model data """
171
172 d = {}
173 for k in self._get_keys():
174 d[k] = getattr(self, k)
175
176 # also use __json__() if present to get additional fields
177 _json_attr = getattr(self, '__json__', None)
178 if _json_attr:
179 # update with attributes from __json__
180 if callable(_json_attr):
181 _json_attr = _json_attr()
182 for k, val in _json_attr.iteritems():
183 d[k] = val
184 return d
185
186 def get_appstruct(self):
187 """return list with keys and values tuples corresponding
188 to this model data """
189
190 l = []
191 for k in self._get_keys():
192 l.append((k, getattr(self, k),))
193 return l
194
195 def populate_obj(self, populate_dict):
196 """populate model with data from given populate_dict"""
197
198 for k in self._get_keys():
199 if k in populate_dict:
200 setattr(self, k, populate_dict[k])
201
202 @classmethod
203 def query(cls):
204 return Session().query(cls)
205
206 @classmethod
207 def get(cls, id_):
208 if id_:
209 return cls.query().get(id_)
210
211 @classmethod
212 def get_or_404(cls, id_):
213 try:
214 id_ = int(id_)
215 except (TypeError, ValueError):
216 raise HTTPNotFound
217
218 res = cls.query().get(id_)
219 if not res:
220 raise HTTPNotFound
221 return res
222
223 @classmethod
224 def getAll(cls):
225 # deprecated and left for backward compatibility
226 return cls.get_all()
227
228 @classmethod
229 def get_all(cls):
230 return cls.query().all()
231
232 @classmethod
233 def delete(cls, id_):
234 obj = cls.query().get(id_)
235 Session().delete(obj)
236
237 @classmethod
238 def identity_cache(cls, session, attr_name, value):
239 exist_in_session = []
240 for (item_cls, pkey), instance in session.identity_map.items():
241 if cls == item_cls and getattr(instance, attr_name) == value:
242 exist_in_session.append(instance)
243 if exist_in_session:
244 if len(exist_in_session) == 1:
245 return exist_in_session[0]
246 log.exception(
247 'multiple objects with attr %s and '
248 'value %s found with same name: %r',
249 attr_name, value, exist_in_session)
250
251 def __repr__(self):
252 if hasattr(self, '__unicode__'):
253 # python repr needs to return str
254 try:
255 return safe_str(self.__unicode__())
256 except UnicodeDecodeError:
257 pass
258 return '<DB:%s>' % (self.__class__.__name__)
259
260
261 class RhodeCodeSetting(Base, BaseModel):
262 __tablename__ = 'rhodecode_settings'
263 __table_args__ = (
264 UniqueConstraint('app_settings_name'),
265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
267 )
268
269 SETTINGS_TYPES = {
270 'str': safe_str,
271 'int': safe_int,
272 'unicode': safe_unicode,
273 'bool': str2bool,
274 'list': functools.partial(aslist, sep=',')
275 }
276 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
277 GLOBAL_CONF_KEY = 'app_settings'
278
279 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
281 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
282 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
283
284 def __init__(self, key='', val='', type='unicode'):
285 self.app_settings_name = key
286 self.app_settings_type = type
287 self.app_settings_value = val
288
289 @validates('_app_settings_value')
290 def validate_settings_value(self, key, val):
291 assert type(val) == unicode
292 return val
293
294 @hybrid_property
295 def app_settings_value(self):
296 v = self._app_settings_value
297 _type = self.app_settings_type
298 if _type:
299 _type = self.app_settings_type.split('.')[0]
300 # decode the encrypted value
301 if 'encrypted' in self.app_settings_type:
302 cipher = EncryptedTextValue()
303 v = safe_unicode(cipher.process_result_value(v, None))
304
305 converter = self.SETTINGS_TYPES.get(_type) or \
306 self.SETTINGS_TYPES['unicode']
307 return converter(v)
308
309 @app_settings_value.setter
310 def app_settings_value(self, val):
311 """
312 Setter that will always make sure we use unicode in app_settings_value
313
314 :param val:
315 """
316 val = safe_unicode(val)
317 # encode the encrypted value
318 if 'encrypted' in self.app_settings_type:
319 cipher = EncryptedTextValue()
320 val = safe_unicode(cipher.process_bind_param(val, None))
321 self._app_settings_value = val
322
323 @hybrid_property
324 def app_settings_type(self):
325 return self._app_settings_type
326
327 @app_settings_type.setter
328 def app_settings_type(self, val):
329 if val.split('.')[0] not in self.SETTINGS_TYPES:
330 raise Exception('type must be one of %s got %s'
331 % (self.SETTINGS_TYPES.keys(), val))
332 self._app_settings_type = val
333
334 def __unicode__(self):
335 return u"<%s('%s:%s[%s]')>" % (
336 self.__class__.__name__,
337 self.app_settings_name, self.app_settings_value,
338 self.app_settings_type
339 )
340
341
342 class RhodeCodeUi(Base, BaseModel):
343 __tablename__ = 'rhodecode_ui'
344 __table_args__ = (
345 UniqueConstraint('ui_key'),
346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
348 )
349
350 HOOK_REPO_SIZE = 'changegroup.repo_size'
351 # HG
352 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
353 HOOK_PULL = 'outgoing.pull_logger'
354 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
355 HOOK_PUSH = 'changegroup.push_logger'
356
357 # TODO: johbo: Unify way how hooks are configured for git and hg,
358 # git part is currently hardcoded.
359
360 # SVN PATTERNS
361 SVN_BRANCH_ID = 'vcs_svn_branch'
362 SVN_TAG_ID = 'vcs_svn_tag'
363
364 ui_id = Column(
365 "ui_id", Integer(), nullable=False, unique=True, default=None,
366 primary_key=True)
367 ui_section = Column(
368 "ui_section", String(255), nullable=True, unique=None, default=None)
369 ui_key = Column(
370 "ui_key", String(255), nullable=True, unique=None, default=None)
371 ui_value = Column(
372 "ui_value", String(255), nullable=True, unique=None, default=None)
373 ui_active = Column(
374 "ui_active", Boolean(), nullable=True, unique=None, default=True)
375
376 def __repr__(self):
377 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
378 self.ui_key, self.ui_value)
379
380
381 class RepoRhodeCodeSetting(Base, BaseModel):
382 __tablename__ = 'repo_rhodecode_settings'
383 __table_args__ = (
384 UniqueConstraint(
385 'app_settings_name', 'repository_id',
386 name='uq_repo_rhodecode_setting_name_repo_id'),
387 {'extend_existing': True, 'mysql_engine': 'InnoDB',
388 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
389 )
390
391 repository_id = Column(
392 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
393 nullable=False)
394 app_settings_id = Column(
395 "app_settings_id", Integer(), nullable=False, unique=True,
396 default=None, primary_key=True)
397 app_settings_name = Column(
398 "app_settings_name", String(255), nullable=True, unique=None,
399 default=None)
400 _app_settings_value = Column(
401 "app_settings_value", String(4096), nullable=True, unique=None,
402 default=None)
403 _app_settings_type = Column(
404 "app_settings_type", String(255), nullable=True, unique=None,
405 default=None)
406
407 repository = relationship('Repository')
408
409 def __init__(self, repository_id, key='', val='', type='unicode'):
410 self.repository_id = repository_id
411 self.app_settings_name = key
412 self.app_settings_type = type
413 self.app_settings_value = val
414
415 @validates('_app_settings_value')
416 def validate_settings_value(self, key, val):
417 assert type(val) == unicode
418 return val
419
420 @hybrid_property
421 def app_settings_value(self):
422 v = self._app_settings_value
423 type_ = self.app_settings_type
424 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
425 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
426 return converter(v)
427
428 @app_settings_value.setter
429 def app_settings_value(self, val):
430 """
431 Setter that will always make sure we use unicode in app_settings_value
432
433 :param val:
434 """
435 self._app_settings_value = safe_unicode(val)
436
437 @hybrid_property
438 def app_settings_type(self):
439 return self._app_settings_type
440
441 @app_settings_type.setter
442 def app_settings_type(self, val):
443 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
444 if val not in SETTINGS_TYPES:
445 raise Exception('type must be one of %s got %s'
446 % (SETTINGS_TYPES.keys(), val))
447 self._app_settings_type = val
448
449 def __unicode__(self):
450 return u"<%s('%s:%s:%s[%s]')>" % (
451 self.__class__.__name__, self.repository.repo_name,
452 self.app_settings_name, self.app_settings_value,
453 self.app_settings_type
454 )
455
456
457 class RepoRhodeCodeUi(Base, BaseModel):
458 __tablename__ = 'repo_rhodecode_ui'
459 __table_args__ = (
460 UniqueConstraint(
461 'repository_id', 'ui_section', 'ui_key',
462 name='uq_repo_rhodecode_ui_repository_id_section_key'),
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
464 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
465 )
466
467 repository_id = Column(
468 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
469 nullable=False)
470 ui_id = Column(
471 "ui_id", Integer(), nullable=False, unique=True, default=None,
472 primary_key=True)
473 ui_section = Column(
474 "ui_section", String(255), nullable=True, unique=None, default=None)
475 ui_key = Column(
476 "ui_key", String(255), nullable=True, unique=None, default=None)
477 ui_value = Column(
478 "ui_value", String(255), nullable=True, unique=None, default=None)
479 ui_active = Column(
480 "ui_active", Boolean(), nullable=True, unique=None, default=True)
481
482 repository = relationship('Repository')
483
484 def __repr__(self):
485 return '<%s[%s:%s]%s=>%s]>' % (
486 self.__class__.__name__, self.repository.repo_name,
487 self.ui_section, self.ui_key, self.ui_value)
488
489
490 class User(Base, BaseModel):
491 __tablename__ = 'users'
492 __table_args__ = (
493 UniqueConstraint('username'), UniqueConstraint('email'),
494 Index('u_username_idx', 'username'),
495 Index('u_email_idx', 'email'),
496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
497 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
498 )
499 DEFAULT_USER = 'default'
500 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
501 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
502
503 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
504 username = Column("username", String(255), nullable=True, unique=None, default=None)
505 password = Column("password", String(255), nullable=True, unique=None, default=None)
506 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
507 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
508 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
509 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
510 _email = Column("email", String(255), nullable=True, unique=None, default=None)
511 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
512 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
513 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
514 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
515 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
517 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
518
519 user_log = relationship('UserLog')
520 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
521
522 repositories = relationship('Repository')
523 repository_groups = relationship('RepoGroup')
524 user_groups = relationship('UserGroup')
525
526 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
527 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
528
529 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
530 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
531 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
532
533 group_member = relationship('UserGroupMember', cascade='all')
534
535 notifications = relationship('UserNotification', cascade='all')
536 # notifications assigned to this user
537 user_created_notifications = relationship('Notification', cascade='all')
538 # comments created by this user
539 user_comments = relationship('ChangesetComment', cascade='all')
540 # user profile extra info
541 user_emails = relationship('UserEmailMap', cascade='all')
542 user_ip_map = relationship('UserIpMap', cascade='all')
543 user_auth_tokens = relationship('UserApiKeys', cascade='all')
544 # gists
545 user_gists = relationship('Gist', cascade='all')
546 # user pull requests
547 user_pull_requests = relationship('PullRequest', cascade='all')
548 # external identities
549 extenal_identities = relationship(
550 'ExternalIdentity',
551 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
552 cascade='all')
553
554 def __unicode__(self):
555 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
556 self.user_id, self.username)
557
558 @hybrid_property
559 def email(self):
560 return self._email
561
562 @email.setter
563 def email(self, val):
564 self._email = val.lower() if val else None
565
566 @property
567 def firstname(self):
568 # alias for future
569 return self.name
570
571 @property
572 def emails(self):
573 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
574 return [self.email] + [x.email for x in other]
575
576 @property
577 def auth_tokens(self):
578 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
579
580 @property
581 def extra_auth_tokens(self):
582 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
583
584 @property
585 def feed_token(self):
586 feed_tokens = UserApiKeys.query()\
587 .filter(UserApiKeys.user == self)\
588 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
589 .all()
590 if feed_tokens:
591 return feed_tokens[0].api_key
592 else:
593 # use the main token so we don't end up with nothing...
594 return self.api_key
595
596 @classmethod
597 def extra_valid_auth_tokens(cls, user, role=None):
598 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
599 .filter(or_(UserApiKeys.expires == -1,
600 UserApiKeys.expires >= time.time()))
601 if role:
602 tokens = tokens.filter(or_(UserApiKeys.role == role,
603 UserApiKeys.role == UserApiKeys.ROLE_ALL))
604 return tokens.all()
605
606 @property
607 def ip_addresses(self):
608 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
609 return [x.ip_addr for x in ret]
610
611 @property
612 def username_and_name(self):
613 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
614
615 @property
616 def username_or_name_or_email(self):
617 full_name = self.full_name if self.full_name is not ' ' else None
618 return self.username or full_name or self.email
619
620 @property
621 def full_name(self):
622 return '%s %s' % (self.firstname, self.lastname)
623
624 @property
625 def full_name_or_username(self):
626 return ('%s %s' % (self.firstname, self.lastname)
627 if (self.firstname and self.lastname) else self.username)
628
629 @property
630 def full_contact(self):
631 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
632
633 @property
634 def short_contact(self):
635 return '%s %s' % (self.firstname, self.lastname)
636
637 @property
638 def is_admin(self):
639 return self.admin
640
641 @property
642 def AuthUser(self):
643 """
644 Returns instance of AuthUser for this user
645 """
646 from rhodecode.lib.auth import AuthUser
647 return AuthUser(user_id=self.user_id, api_key=self.api_key,
648 username=self.username)
649
650 @hybrid_property
651 def user_data(self):
652 if not self._user_data:
653 return {}
654
655 try:
656 return json.loads(self._user_data)
657 except TypeError:
658 return {}
659
660 @user_data.setter
661 def user_data(self, val):
662 if not isinstance(val, dict):
663 raise Exception('user_data must be dict, got %s' % type(val))
664 try:
665 self._user_data = json.dumps(val)
666 except Exception:
667 log.error(traceback.format_exc())
668
669 @classmethod
670 def get_by_username(cls, username, case_insensitive=False,
671 cache=False, identity_cache=False):
672 session = Session()
673
674 if case_insensitive:
675 q = cls.query().filter(
676 func.lower(cls.username) == func.lower(username))
677 else:
678 q = cls.query().filter(cls.username == username)
679
680 if cache:
681 if identity_cache:
682 val = cls.identity_cache(session, 'username', username)
683 if val:
684 return val
685 else:
686 q = q.options(
687 FromCache("sql_cache_short",
688 "get_user_by_name_%s" % _hash_key(username)))
689
690 return q.scalar()
691
692 @classmethod
693 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
694 q = cls.query().filter(cls.api_key == auth_token)
695
696 if cache:
697 q = q.options(FromCache("sql_cache_short",
698 "get_auth_token_%s" % auth_token))
699 res = q.scalar()
700
701 if fallback and not res:
702 #fallback to additional keys
703 _res = UserApiKeys.query()\
704 .filter(UserApiKeys.api_key == auth_token)\
705 .filter(or_(UserApiKeys.expires == -1,
706 UserApiKeys.expires >= time.time()))\
707 .first()
708 if _res:
709 res = _res.user
710 return res
711
712 @classmethod
713 def get_by_email(cls, email, case_insensitive=False, cache=False):
714
715 if case_insensitive:
716 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
717
718 else:
719 q = cls.query().filter(cls.email == email)
720
721 if cache:
722 q = q.options(FromCache("sql_cache_short",
723 "get_email_key_%s" % email))
724
725 ret = q.scalar()
726 if ret is None:
727 q = UserEmailMap.query()
728 # try fetching in alternate email map
729 if case_insensitive:
730 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
731 else:
732 q = q.filter(UserEmailMap.email == email)
733 q = q.options(joinedload(UserEmailMap.user))
734 if cache:
735 q = q.options(FromCache("sql_cache_short",
736 "get_email_map_key_%s" % email))
737 ret = getattr(q.scalar(), 'user', None)
738
739 return ret
740
741 @classmethod
742 def get_from_cs_author(cls, author):
743 """
744 Tries to get User objects out of commit author string
745
746 :param author:
747 """
748 from rhodecode.lib.helpers import email, author_name
749 # Valid email in the attribute passed, see if they're in the system
750 _email = email(author)
751 if _email:
752 user = cls.get_by_email(_email, case_insensitive=True)
753 if user:
754 return user
755 # Maybe we can match by username?
756 _author = author_name(author)
757 user = cls.get_by_username(_author, case_insensitive=True)
758 if user:
759 return user
760
761 def update_userdata(self, **kwargs):
762 usr = self
763 old = usr.user_data
764 old.update(**kwargs)
765 usr.user_data = old
766 Session().add(usr)
767 log.debug('updated userdata with ', kwargs)
768
769 def update_lastlogin(self):
770 """Update user lastlogin"""
771 self.last_login = datetime.datetime.now()
772 Session().add(self)
773 log.debug('updated user %s lastlogin', self.username)
774
775 def update_lastactivity(self):
776 """Update user lastactivity"""
777 usr = self
778 old = usr.user_data
779 old.update({'last_activity': time.time()})
780 usr.user_data = old
781 Session().add(usr)
782 log.debug('updated user %s lastactivity', usr.username)
783
784 def update_password(self, new_password, change_api_key=False):
785 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
786
787 self.password = get_crypt_password(new_password)
788 if change_api_key:
789 self.api_key = generate_auth_token(self.username)
790 Session().add(self)
791
792 @classmethod
793 def get_first_super_admin(cls):
794 user = User.query().filter(User.admin == true()).first()
795 if user is None:
796 raise Exception('FATAL: Missing administrative account!')
797 return user
798
799 @classmethod
800 def get_all_super_admins(cls):
801 """
802 Returns all admin accounts sorted by username
803 """
804 return User.query().filter(User.admin == true())\
805 .order_by(User.username.asc()).all()
806
807 @classmethod
808 def get_default_user(cls, cache=False):
809 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
810 if user is None:
811 raise Exception('FATAL: Missing default account!')
812 return user
813
814 def _get_default_perms(self, user, suffix=''):
815 from rhodecode.model.permission import PermissionModel
816 return PermissionModel().get_default_perms(user.user_perms, suffix)
817
818 def get_default_perms(self, suffix=''):
819 return self._get_default_perms(self, suffix)
820
821 def get_api_data(self, include_secrets=False, details='full'):
822 """
823 Common function for generating user related data for API
824
825 :param include_secrets: By default secrets in the API data will be replaced
826 by a placeholder value to prevent exposing this data by accident. In case
827 this data shall be exposed, set this flag to ``True``.
828
829 :param details: details can be 'basic|full' basic gives only a subset of
830 the available user information that includes user_id, name and emails.
831 """
832 user = self
833 user_data = self.user_data
834 data = {
835 'user_id': user.user_id,
836 'username': user.username,
837 'firstname': user.name,
838 'lastname': user.lastname,
839 'email': user.email,
840 'emails': user.emails,
841 }
842 if details == 'basic':
843 return data
844
845 api_key_length = 40
846 api_key_replacement = '*' * api_key_length
847
848 extras = {
849 'api_key': api_key_replacement,
850 'api_keys': [api_key_replacement],
851 'active': user.active,
852 'admin': user.admin,
853 'extern_type': user.extern_type,
854 'extern_name': user.extern_name,
855 'last_login': user.last_login,
856 'ip_addresses': user.ip_addresses,
857 'language': user_data.get('language')
858 }
859 data.update(extras)
860
861 if include_secrets:
862 data['api_key'] = user.api_key
863 data['api_keys'] = user.auth_tokens
864 return data
865
866 def __json__(self):
867 data = {
868 'full_name': self.full_name,
869 'full_name_or_username': self.full_name_or_username,
870 'short_contact': self.short_contact,
871 'full_contact': self.full_contact,
872 }
873 data.update(self.get_api_data())
874 return data
875
876
877 class UserApiKeys(Base, BaseModel):
878 __tablename__ = 'user_api_keys'
879 __table_args__ = (
880 Index('uak_api_key_idx', 'api_key'),
881 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
882 UniqueConstraint('api_key'),
883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
884 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
885 )
886 __mapper_args__ = {}
887
888 # ApiKey role
889 ROLE_ALL = 'token_role_all'
890 ROLE_HTTP = 'token_role_http'
891 ROLE_VCS = 'token_role_vcs'
892 ROLE_API = 'token_role_api'
893 ROLE_FEED = 'token_role_feed'
894 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
895
896 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
897 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
898 api_key = Column("api_key", String(255), nullable=False, unique=True)
899 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
900 expires = Column('expires', Float(53), nullable=False)
901 role = Column('role', String(255), nullable=True)
902 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
903
904 user = relationship('User', lazy='joined')
905
906 @classmethod
907 def _get_role_name(cls, role):
908 return {
909 cls.ROLE_ALL: _('all'),
910 cls.ROLE_HTTP: _('http/web interface'),
911 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
912 cls.ROLE_API: _('api calls'),
913 cls.ROLE_FEED: _('feed access'),
914 }.get(role, role)
915
916 @property
917 def expired(self):
918 if self.expires == -1:
919 return False
920 return time.time() > self.expires
921
922 @property
923 def role_humanized(self):
924 return self._get_role_name(self.role)
925
926
927 class UserEmailMap(Base, BaseModel):
928 __tablename__ = 'user_email_map'
929 __table_args__ = (
930 Index('uem_email_idx', 'email'),
931 UniqueConstraint('email'),
932 {'extend_existing': True, 'mysql_engine': 'InnoDB',
933 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
934 )
935 __mapper_args__ = {}
936
937 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
939 _email = Column("email", String(255), nullable=True, unique=False, default=None)
940 user = relationship('User', lazy='joined')
941
942 @validates('_email')
943 def validate_email(self, key, email):
944 # check if this email is not main one
945 main_email = Session().query(User).filter(User.email == email).scalar()
946 if main_email is not None:
947 raise AttributeError('email %s is present is user table' % email)
948 return email
949
950 @hybrid_property
951 def email(self):
952 return self._email
953
954 @email.setter
955 def email(self, val):
956 self._email = val.lower() if val else None
957
958
959 class UserIpMap(Base, BaseModel):
960 __tablename__ = 'user_ip_map'
961 __table_args__ = (
962 UniqueConstraint('user_id', 'ip_addr'),
963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
964 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
965 )
966 __mapper_args__ = {}
967
968 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
970 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
971 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
972 description = Column("description", String(10000), nullable=True, unique=None, default=None)
973 user = relationship('User', lazy='joined')
974
975 @classmethod
976 def _get_ip_range(cls, ip_addr):
977 net = ipaddress.ip_network(ip_addr, strict=False)
978 return [str(net.network_address), str(net.broadcast_address)]
979
980 def __json__(self):
981 return {
982 'ip_addr': self.ip_addr,
983 'ip_range': self._get_ip_range(self.ip_addr),
984 }
985
986 def __unicode__(self):
987 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
988 self.user_id, self.ip_addr)
989
990 class UserLog(Base, BaseModel):
991 __tablename__ = 'user_logs'
992 __table_args__ = (
993 {'extend_existing': True, 'mysql_engine': 'InnoDB',
994 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
995 )
996 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
997 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
998 username = Column("username", String(255), nullable=True, unique=None, default=None)
999 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1000 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1001 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1002 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1003 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1004
1005 def __unicode__(self):
1006 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1007 self.repository_name,
1008 self.action)
1009
1010 @property
1011 def action_as_day(self):
1012 return datetime.date(*self.action_date.timetuple()[:3])
1013
1014 user = relationship('User')
1015 repository = relationship('Repository', cascade='')
1016
1017
1018 class UserGroup(Base, BaseModel):
1019 __tablename__ = 'users_groups'
1020 __table_args__ = (
1021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1023 )
1024
1025 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1026 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1027 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1028 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1029 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1030 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1031 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1032 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1033
1034 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1035 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1036 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1037 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1038 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1039 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1040
1041 user = relationship('User')
1042
1043 @hybrid_property
1044 def group_data(self):
1045 if not self._group_data:
1046 return {}
1047
1048 try:
1049 return json.loads(self._group_data)
1050 except TypeError:
1051 return {}
1052
1053 @group_data.setter
1054 def group_data(self, val):
1055 try:
1056 self._group_data = json.dumps(val)
1057 except Exception:
1058 log.error(traceback.format_exc())
1059
1060 def __unicode__(self):
1061 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1062 self.users_group_id,
1063 self.users_group_name)
1064
1065 @classmethod
1066 def get_by_group_name(cls, group_name, cache=False,
1067 case_insensitive=False):
1068 if case_insensitive:
1069 q = cls.query().filter(func.lower(cls.users_group_name) ==
1070 func.lower(group_name))
1071
1072 else:
1073 q = cls.query().filter(cls.users_group_name == group_name)
1074 if cache:
1075 q = q.options(FromCache(
1076 "sql_cache_short",
1077 "get_group_%s" % _hash_key(group_name)))
1078 return q.scalar()
1079
1080 @classmethod
1081 def get(cls, user_group_id, cache=False):
1082 user_group = cls.query()
1083 if cache:
1084 user_group = user_group.options(FromCache("sql_cache_short",
1085 "get_users_group_%s" % user_group_id))
1086 return user_group.get(user_group_id)
1087
1088 def permissions(self, with_admins=True, with_owner=True):
1089 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1090 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1091 joinedload(UserUserGroupToPerm.user),
1092 joinedload(UserUserGroupToPerm.permission),)
1093
1094 # get owners and admins and permissions. We do a trick of re-writing
1095 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1096 # has a global reference and changing one object propagates to all
1097 # others. This means if admin is also an owner admin_row that change
1098 # would propagate to both objects
1099 perm_rows = []
1100 for _usr in q.all():
1101 usr = AttributeDict(_usr.user.get_dict())
1102 usr.permission = _usr.permission.permission_name
1103 perm_rows.append(usr)
1104
1105 # filter the perm rows by 'default' first and then sort them by
1106 # admin,write,read,none permissions sorted again alphabetically in
1107 # each group
1108 perm_rows = sorted(perm_rows, key=display_sort)
1109
1110 _admin_perm = 'usergroup.admin'
1111 owner_row = []
1112 if with_owner:
1113 usr = AttributeDict(self.user.get_dict())
1114 usr.owner_row = True
1115 usr.permission = _admin_perm
1116 owner_row.append(usr)
1117
1118 super_admin_rows = []
1119 if with_admins:
1120 for usr in User.get_all_super_admins():
1121 # if this admin is also owner, don't double the record
1122 if usr.user_id == owner_row[0].user_id:
1123 owner_row[0].admin_row = True
1124 else:
1125 usr = AttributeDict(usr.get_dict())
1126 usr.admin_row = True
1127 usr.permission = _admin_perm
1128 super_admin_rows.append(usr)
1129
1130 return super_admin_rows + owner_row + perm_rows
1131
1132 def permission_user_groups(self):
1133 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1134 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1135 joinedload(UserGroupUserGroupToPerm.target_user_group),
1136 joinedload(UserGroupUserGroupToPerm.permission),)
1137
1138 perm_rows = []
1139 for _user_group in q.all():
1140 usr = AttributeDict(_user_group.user_group.get_dict())
1141 usr.permission = _user_group.permission.permission_name
1142 perm_rows.append(usr)
1143
1144 return perm_rows
1145
1146 def _get_default_perms(self, user_group, suffix=''):
1147 from rhodecode.model.permission import PermissionModel
1148 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1149
1150 def get_default_perms(self, suffix=''):
1151 return self._get_default_perms(self, suffix)
1152
1153 def get_api_data(self, with_group_members=True, include_secrets=False):
1154 """
1155 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1156 basically forwarded.
1157
1158 """
1159 user_group = self
1160
1161 data = {
1162 'users_group_id': user_group.users_group_id,
1163 'group_name': user_group.users_group_name,
1164 'group_description': user_group.user_group_description,
1165 'active': user_group.users_group_active,
1166 'owner': user_group.user.username,
1167 }
1168 if with_group_members:
1169 users = []
1170 for user in user_group.members:
1171 user = user.user
1172 users.append(user.get_api_data(include_secrets=include_secrets))
1173 data['users'] = users
1174
1175 return data
1176
1177
1178 class UserGroupMember(Base, BaseModel):
1179 __tablename__ = 'users_groups_members'
1180 __table_args__ = (
1181 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1182 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1183 )
1184
1185 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1186 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1187 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1188
1189 user = relationship('User', lazy='joined')
1190 users_group = relationship('UserGroup')
1191
1192 def __init__(self, gr_id='', u_id=''):
1193 self.users_group_id = gr_id
1194 self.user_id = u_id
1195
1196
1197 class RepositoryField(Base, BaseModel):
1198 __tablename__ = 'repositories_fields'
1199 __table_args__ = (
1200 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1202 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1203 )
1204 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1205
1206 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1207 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1208 field_key = Column("field_key", String(250))
1209 field_label = Column("field_label", String(1024), nullable=False)
1210 field_value = Column("field_value", String(10000), nullable=False)
1211 field_desc = Column("field_desc", String(1024), nullable=False)
1212 field_type = Column("field_type", String(255), nullable=False, unique=None)
1213 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1214
1215 repository = relationship('Repository')
1216
1217 @property
1218 def field_key_prefixed(self):
1219 return 'ex_%s' % self.field_key
1220
1221 @classmethod
1222 def un_prefix_key(cls, key):
1223 if key.startswith(cls.PREFIX):
1224 return key[len(cls.PREFIX):]
1225 return key
1226
1227 @classmethod
1228 def get_by_key_name(cls, key, repo):
1229 row = cls.query()\
1230 .filter(cls.repository == repo)\
1231 .filter(cls.field_key == key).scalar()
1232 return row
1233
1234
1235 class Repository(Base, BaseModel):
1236 __tablename__ = 'repositories'
1237 __table_args__ = (
1238 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1241 )
1242 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1243 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1244
1245 STATE_CREATED = 'repo_state_created'
1246 STATE_PENDING = 'repo_state_pending'
1247 STATE_ERROR = 'repo_state_error'
1248
1249 LOCK_AUTOMATIC = 'lock_auto'
1250 LOCK_API = 'lock_api'
1251 LOCK_WEB = 'lock_web'
1252 LOCK_PULL = 'lock_pull'
1253
1254 NAME_SEP = URL_SEP
1255
1256 repo_id = Column(
1257 "repo_id", Integer(), nullable=False, unique=True, default=None,
1258 primary_key=True)
1259 _repo_name = Column(
1260 "repo_name", Text(), nullable=False, default=None)
1261 _repo_name_hash = Column(
1262 "repo_name_hash", String(255), nullable=False, unique=True)
1263 repo_state = Column("repo_state", String(255), nullable=True)
1264
1265 clone_uri = Column(
1266 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1267 default=None)
1268 repo_type = Column(
1269 "repo_type", String(255), nullable=False, unique=False, default=None)
1270 user_id = Column(
1271 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1272 unique=False, default=None)
1273 private = Column(
1274 "private", Boolean(), nullable=True, unique=None, default=None)
1275 enable_statistics = Column(
1276 "statistics", Boolean(), nullable=True, unique=None, default=True)
1277 enable_downloads = Column(
1278 "downloads", Boolean(), nullable=True, unique=None, default=True)
1279 description = Column(
1280 "description", String(10000), nullable=True, unique=None, default=None)
1281 created_on = Column(
1282 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1283 default=datetime.datetime.now)
1284 updated_on = Column(
1285 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1286 default=datetime.datetime.now)
1287 _landing_revision = Column(
1288 "landing_revision", String(255), nullable=False, unique=False,
1289 default=None)
1290 enable_locking = Column(
1291 "enable_locking", Boolean(), nullable=False, unique=None,
1292 default=False)
1293 _locked = Column(
1294 "locked", String(255), nullable=True, unique=False, default=None)
1295 _changeset_cache = Column(
1296 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1297
1298 fork_id = Column(
1299 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1300 nullable=True, unique=False, default=None)
1301 group_id = Column(
1302 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1303 unique=False, default=None)
1304
1305 user = relationship('User', lazy='joined')
1306 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1307 group = relationship('RepoGroup', lazy='joined')
1308 repo_to_perm = relationship(
1309 'UserRepoToPerm', cascade='all',
1310 order_by='UserRepoToPerm.repo_to_perm_id')
1311 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1312 stats = relationship('Statistics', cascade='all', uselist=False)
1313
1314 followers = relationship(
1315 'UserFollowing',
1316 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1317 cascade='all')
1318 extra_fields = relationship(
1319 'RepositoryField', cascade="all, delete, delete-orphan")
1320 logs = relationship('UserLog')
1321 comments = relationship(
1322 'ChangesetComment', cascade="all, delete, delete-orphan")
1323 pull_requests_source = relationship(
1324 'PullRequest',
1325 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1326 cascade="all, delete, delete-orphan")
1327 pull_requests_target = relationship(
1328 'PullRequest',
1329 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1330 cascade="all, delete, delete-orphan")
1331 ui = relationship('RepoRhodeCodeUi', cascade="all")
1332 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1333
1334 def __unicode__(self):
1335 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1336 safe_unicode(self.repo_name))
1337
1338 @hybrid_property
1339 def landing_rev(self):
1340 # always should return [rev_type, rev]
1341 if self._landing_revision:
1342 _rev_info = self._landing_revision.split(':')
1343 if len(_rev_info) < 2:
1344 _rev_info.insert(0, 'rev')
1345 return [_rev_info[0], _rev_info[1]]
1346 return [None, None]
1347
1348 @landing_rev.setter
1349 def landing_rev(self, val):
1350 if ':' not in val:
1351 raise ValueError('value must be delimited with `:` and consist '
1352 'of <rev_type>:<rev>, got %s instead' % val)
1353 self._landing_revision = val
1354
1355 @hybrid_property
1356 def locked(self):
1357 if self._locked:
1358 user_id, timelocked, reason = self._locked.split(':')
1359 lock_values = int(user_id), timelocked, reason
1360 else:
1361 lock_values = [None, None, None]
1362 return lock_values
1363
1364 @locked.setter
1365 def locked(self, val):
1366 if val and isinstance(val, (list, tuple)):
1367 self._locked = ':'.join(map(str, val))
1368 else:
1369 self._locked = None
1370
1371 @hybrid_property
1372 def changeset_cache(self):
1373 from rhodecode.lib.vcs.backends.base import EmptyCommit
1374 dummy = EmptyCommit().__json__()
1375 if not self._changeset_cache:
1376 return dummy
1377 try:
1378 return json.loads(self._changeset_cache)
1379 except TypeError:
1380 return dummy
1381 except Exception:
1382 log.error(traceback.format_exc())
1383 return dummy
1384
1385 @changeset_cache.setter
1386 def changeset_cache(self, val):
1387 try:
1388 self._changeset_cache = json.dumps(val)
1389 except Exception:
1390 log.error(traceback.format_exc())
1391
1392 @hybrid_property
1393 def repo_name(self):
1394 return self._repo_name
1395
1396 @repo_name.setter
1397 def repo_name(self, value):
1398 self._repo_name = value
1399 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1400
1401 @classmethod
1402 def normalize_repo_name(cls, repo_name):
1403 """
1404 Normalizes os specific repo_name to the format internally stored inside
1405 database using URL_SEP
1406
1407 :param cls:
1408 :param repo_name:
1409 """
1410 return cls.NAME_SEP.join(repo_name.split(os.sep))
1411
1412 @classmethod
1413 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1414 session = Session()
1415 q = session.query(cls).filter(cls.repo_name == repo_name)
1416
1417 if cache:
1418 if identity_cache:
1419 val = cls.identity_cache(session, 'repo_name', repo_name)
1420 if val:
1421 return val
1422 else:
1423 q = q.options(
1424 FromCache("sql_cache_short",
1425 "get_repo_by_name_%s" % _hash_key(repo_name)))
1426
1427 return q.scalar()
1428
1429 @classmethod
1430 def get_by_full_path(cls, repo_full_path):
1431 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1432 repo_name = cls.normalize_repo_name(repo_name)
1433 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1434
1435 @classmethod
1436 def get_repo_forks(cls, repo_id):
1437 return cls.query().filter(Repository.fork_id == repo_id)
1438
1439 @classmethod
1440 def base_path(cls):
1441 """
1442 Returns base path when all repos are stored
1443
1444 :param cls:
1445 """
1446 q = Session().query(RhodeCodeUi)\
1447 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1448 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1449 return q.one().ui_value
1450
1451 @classmethod
1452 def is_valid(cls, repo_name):
1453 """
1454 returns True if given repo name is a valid filesystem repository
1455
1456 :param cls:
1457 :param repo_name:
1458 """
1459 from rhodecode.lib.utils import is_valid_repo
1460
1461 return is_valid_repo(repo_name, cls.base_path())
1462
1463 @classmethod
1464 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1465 case_insensitive=True):
1466 q = Repository.query()
1467
1468 if not isinstance(user_id, Optional):
1469 q = q.filter(Repository.user_id == user_id)
1470
1471 if not isinstance(group_id, Optional):
1472 q = q.filter(Repository.group_id == group_id)
1473
1474 if case_insensitive:
1475 q = q.order_by(func.lower(Repository.repo_name))
1476 else:
1477 q = q.order_by(Repository.repo_name)
1478 return q.all()
1479
1480 @property
1481 def forks(self):
1482 """
1483 Return forks of this repo
1484 """
1485 return Repository.get_repo_forks(self.repo_id)
1486
1487 @property
1488 def parent(self):
1489 """
1490 Returns fork parent
1491 """
1492 return self.fork
1493
1494 @property
1495 def just_name(self):
1496 return self.repo_name.split(self.NAME_SEP)[-1]
1497
1498 @property
1499 def groups_with_parents(self):
1500 groups = []
1501 if self.group is None:
1502 return groups
1503
1504 cur_gr = self.group
1505 groups.insert(0, cur_gr)
1506 while 1:
1507 gr = getattr(cur_gr, 'parent_group', None)
1508 cur_gr = cur_gr.parent_group
1509 if gr is None:
1510 break
1511 groups.insert(0, gr)
1512
1513 return groups
1514
1515 @property
1516 def groups_and_repo(self):
1517 return self.groups_with_parents, self
1518
1519 @LazyProperty
1520 def repo_path(self):
1521 """
1522 Returns base full path for that repository means where it actually
1523 exists on a filesystem
1524 """
1525 q = Session().query(RhodeCodeUi).filter(
1526 RhodeCodeUi.ui_key == self.NAME_SEP)
1527 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1528 return q.one().ui_value
1529
1530 @property
1531 def repo_full_path(self):
1532 p = [self.repo_path]
1533 # we need to split the name by / since this is how we store the
1534 # names in the database, but that eventually needs to be converted
1535 # into a valid system path
1536 p += self.repo_name.split(self.NAME_SEP)
1537 return os.path.join(*map(safe_unicode, p))
1538
1539 @property
1540 def cache_keys(self):
1541 """
1542 Returns associated cache keys for that repo
1543 """
1544 return CacheKey.query()\
1545 .filter(CacheKey.cache_args == self.repo_name)\
1546 .order_by(CacheKey.cache_key)\
1547 .all()
1548
1549 def get_new_name(self, repo_name):
1550 """
1551 returns new full repository name based on assigned group and new new
1552
1553 :param group_name:
1554 """
1555 path_prefix = self.group.full_path_splitted if self.group else []
1556 return self.NAME_SEP.join(path_prefix + [repo_name])
1557
1558 @property
1559 def _config(self):
1560 """
1561 Returns db based config object.
1562 """
1563 from rhodecode.lib.utils import make_db_config
1564 return make_db_config(clear_session=False, repo=self)
1565
1566 def permissions(self, with_admins=True, with_owner=True):
1567 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1568 q = q.options(joinedload(UserRepoToPerm.repository),
1569 joinedload(UserRepoToPerm.user),
1570 joinedload(UserRepoToPerm.permission),)
1571
1572 # get owners and admins and permissions. We do a trick of re-writing
1573 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1574 # has a global reference and changing one object propagates to all
1575 # others. This means if admin is also an owner admin_row that change
1576 # would propagate to both objects
1577 perm_rows = []
1578 for _usr in q.all():
1579 usr = AttributeDict(_usr.user.get_dict())
1580 usr.permission = _usr.permission.permission_name
1581 perm_rows.append(usr)
1582
1583 # filter the perm rows by 'default' first and then sort them by
1584 # admin,write,read,none permissions sorted again alphabetically in
1585 # each group
1586 perm_rows = sorted(perm_rows, key=display_sort)
1587
1588 _admin_perm = 'repository.admin'
1589 owner_row = []
1590 if with_owner:
1591 usr = AttributeDict(self.user.get_dict())
1592 usr.owner_row = True
1593 usr.permission = _admin_perm
1594 owner_row.append(usr)
1595
1596 super_admin_rows = []
1597 if with_admins:
1598 for usr in User.get_all_super_admins():
1599 # if this admin is also owner, don't double the record
1600 if usr.user_id == owner_row[0].user_id:
1601 owner_row[0].admin_row = True
1602 else:
1603 usr = AttributeDict(usr.get_dict())
1604 usr.admin_row = True
1605 usr.permission = _admin_perm
1606 super_admin_rows.append(usr)
1607
1608 return super_admin_rows + owner_row + perm_rows
1609
1610 def permission_user_groups(self):
1611 q = UserGroupRepoToPerm.query().filter(
1612 UserGroupRepoToPerm.repository == self)
1613 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1614 joinedload(UserGroupRepoToPerm.users_group),
1615 joinedload(UserGroupRepoToPerm.permission),)
1616
1617 perm_rows = []
1618 for _user_group in q.all():
1619 usr = AttributeDict(_user_group.users_group.get_dict())
1620 usr.permission = _user_group.permission.permission_name
1621 perm_rows.append(usr)
1622
1623 return perm_rows
1624
1625 def get_api_data(self, include_secrets=False):
1626 """
1627 Common function for generating repo api data
1628
1629 :param include_secrets: See :meth:`User.get_api_data`.
1630
1631 """
1632 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1633 # move this methods on models level.
1634 from rhodecode.model.settings import SettingsModel
1635
1636 repo = self
1637 _user_id, _time, _reason = self.locked
1638
1639 data = {
1640 'repo_id': repo.repo_id,
1641 'repo_name': repo.repo_name,
1642 'repo_type': repo.repo_type,
1643 'clone_uri': repo.clone_uri or '',
1644 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1645 'private': repo.private,
1646 'created_on': repo.created_on,
1647 'description': repo.description,
1648 'landing_rev': repo.landing_rev,
1649 'owner': repo.user.username,
1650 'fork_of': repo.fork.repo_name if repo.fork else None,
1651 'enable_statistics': repo.enable_statistics,
1652 'enable_locking': repo.enable_locking,
1653 'enable_downloads': repo.enable_downloads,
1654 'last_changeset': repo.changeset_cache,
1655 'locked_by': User.get(_user_id).get_api_data(
1656 include_secrets=include_secrets) if _user_id else None,
1657 'locked_date': time_to_datetime(_time) if _time else None,
1658 'lock_reason': _reason if _reason else None,
1659 }
1660
1661 # TODO: mikhail: should be per-repo settings here
1662 rc_config = SettingsModel().get_all_settings()
1663 repository_fields = str2bool(
1664 rc_config.get('rhodecode_repository_fields'))
1665 if repository_fields:
1666 for f in self.extra_fields:
1667 data[f.field_key_prefixed] = f.field_value
1668
1669 return data
1670
1671 @classmethod
1672 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1673 if not lock_time:
1674 lock_time = time.time()
1675 if not lock_reason:
1676 lock_reason = cls.LOCK_AUTOMATIC
1677 repo.locked = [user_id, lock_time, lock_reason]
1678 Session().add(repo)
1679 Session().commit()
1680
1681 @classmethod
1682 def unlock(cls, repo):
1683 repo.locked = None
1684 Session().add(repo)
1685 Session().commit()
1686
1687 @classmethod
1688 def getlock(cls, repo):
1689 return repo.locked
1690
1691 def is_user_lock(self, user_id):
1692 if self.lock[0]:
1693 lock_user_id = safe_int(self.lock[0])
1694 user_id = safe_int(user_id)
1695 # both are ints, and they are equal
1696 return all([lock_user_id, user_id]) and lock_user_id == user_id
1697
1698 return False
1699
1700 def get_locking_state(self, action, user_id, only_when_enabled=True):
1701 """
1702 Checks locking on this repository, if locking is enabled and lock is
1703 present returns a tuple of make_lock, locked, locked_by.
1704 make_lock can have 3 states None (do nothing) True, make lock
1705 False release lock, This value is later propagated to hooks, which
1706 do the locking. Think about this as signals passed to hooks what to do.
1707
1708 """
1709 # TODO: johbo: This is part of the business logic and should be moved
1710 # into the RepositoryModel.
1711
1712 if action not in ('push', 'pull'):
1713 raise ValueError("Invalid action value: %s" % repr(action))
1714
1715 # defines if locked error should be thrown to user
1716 currently_locked = False
1717 # defines if new lock should be made, tri-state
1718 make_lock = None
1719 repo = self
1720 user = User.get(user_id)
1721
1722 lock_info = repo.locked
1723
1724 if repo and (repo.enable_locking or not only_when_enabled):
1725 if action == 'push':
1726 # check if it's already locked !, if it is compare users
1727 locked_by_user_id = lock_info[0]
1728 if user.user_id == locked_by_user_id:
1729 log.debug(
1730 'Got `push` action from user %s, now unlocking', user)
1731 # unlock if we have push from user who locked
1732 make_lock = False
1733 else:
1734 # we're not the same user who locked, ban with
1735 # code defined in settings (default is 423 HTTP Locked) !
1736 log.debug('Repo %s is currently locked by %s', repo, user)
1737 currently_locked = True
1738 elif action == 'pull':
1739 # [0] user [1] date
1740 if lock_info[0] and lock_info[1]:
1741 log.debug('Repo %s is currently locked by %s', repo, user)
1742 currently_locked = True
1743 else:
1744 log.debug('Setting lock on repo %s by %s', repo, user)
1745 make_lock = True
1746
1747 else:
1748 log.debug('Repository %s do not have locking enabled', repo)
1749
1750 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1751 make_lock, currently_locked, lock_info)
1752
1753 from rhodecode.lib.auth import HasRepoPermissionAny
1754 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1755 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1756 # if we don't have at least write permission we cannot make a lock
1757 log.debug('lock state reset back to FALSE due to lack '
1758 'of at least read permission')
1759 make_lock = False
1760
1761 return make_lock, currently_locked, lock_info
1762
1763 @property
1764 def last_db_change(self):
1765 return self.updated_on
1766
1767 @property
1768 def clone_uri_hidden(self):
1769 clone_uri = self.clone_uri
1770 if clone_uri:
1771 import urlobject
1772 url_obj = urlobject.URLObject(clone_uri)
1773 if url_obj.password:
1774 clone_uri = url_obj.with_password('*****')
1775 return clone_uri
1776
1777 def clone_url(self, **override):
1778 qualified_home_url = url('home', qualified=True)
1779
1780 uri_tmpl = None
1781 if 'with_id' in override:
1782 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1783 del override['with_id']
1784
1785 if 'uri_tmpl' in override:
1786 uri_tmpl = override['uri_tmpl']
1787 del override['uri_tmpl']
1788
1789 # we didn't override our tmpl from **overrides
1790 if not uri_tmpl:
1791 uri_tmpl = self.DEFAULT_CLONE_URI
1792 try:
1793 from pylons import tmpl_context as c
1794 uri_tmpl = c.clone_uri_tmpl
1795 except Exception:
1796 # in any case if we call this outside of request context,
1797 # ie, not having tmpl_context set up
1798 pass
1799
1800 return get_clone_url(uri_tmpl=uri_tmpl,
1801 qualifed_home_url=qualified_home_url,
1802 repo_name=self.repo_name,
1803 repo_id=self.repo_id, **override)
1804
1805 def set_state(self, state):
1806 self.repo_state = state
1807 Session().add(self)
1808 #==========================================================================
1809 # SCM PROPERTIES
1810 #==========================================================================
1811
1812 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1813 return get_commit_safe(
1814 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1815
1816 def get_changeset(self, rev=None, pre_load=None):
1817 warnings.warn("Use get_commit", DeprecationWarning)
1818 commit_id = None
1819 commit_idx = None
1820 if isinstance(rev, basestring):
1821 commit_id = rev
1822 else:
1823 commit_idx = rev
1824 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1825 pre_load=pre_load)
1826
1827 def get_landing_commit(self):
1828 """
1829 Returns landing commit, or if that doesn't exist returns the tip
1830 """
1831 _rev_type, _rev = self.landing_rev
1832 commit = self.get_commit(_rev)
1833 if isinstance(commit, EmptyCommit):
1834 return self.get_commit()
1835 return commit
1836
1837 def update_commit_cache(self, cs_cache=None, config=None):
1838 """
1839 Update cache of last changeset for repository, keys should be::
1840
1841 short_id
1842 raw_id
1843 revision
1844 parents
1845 message
1846 date
1847 author
1848
1849 :param cs_cache:
1850 """
1851 from rhodecode.lib.vcs.backends.base import BaseChangeset
1852 if cs_cache is None:
1853 # use no-cache version here
1854 scm_repo = self.scm_instance(cache=False, config=config)
1855 if scm_repo:
1856 cs_cache = scm_repo.get_commit(
1857 pre_load=["author", "date", "message", "parents"])
1858 else:
1859 cs_cache = EmptyCommit()
1860
1861 if isinstance(cs_cache, BaseChangeset):
1862 cs_cache = cs_cache.__json__()
1863
1864 def is_outdated(new_cs_cache):
1865 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1866 new_cs_cache['revision'] != self.changeset_cache['revision']):
1867 return True
1868 return False
1869
1870 # check if we have maybe already latest cached revision
1871 if is_outdated(cs_cache) or not self.changeset_cache:
1872 _default = datetime.datetime.fromtimestamp(0)
1873 last_change = cs_cache.get('date') or _default
1874 log.debug('updated repo %s with new cs cache %s',
1875 self.repo_name, cs_cache)
1876 self.updated_on = last_change
1877 self.changeset_cache = cs_cache
1878 Session().add(self)
1879 Session().commit()
1880 else:
1881 log.debug('Skipping update_commit_cache for repo:`%s` '
1882 'commit already with latest changes', self.repo_name)
1883
1884 @property
1885 def tip(self):
1886 return self.get_commit('tip')
1887
1888 @property
1889 def author(self):
1890 return self.tip.author
1891
1892 @property
1893 def last_change(self):
1894 return self.scm_instance().last_change
1895
1896 def get_comments(self, revisions=None):
1897 """
1898 Returns comments for this repository grouped by revisions
1899
1900 :param revisions: filter query by revisions only
1901 """
1902 cmts = ChangesetComment.query()\
1903 .filter(ChangesetComment.repo == self)
1904 if revisions:
1905 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1906 grouped = collections.defaultdict(list)
1907 for cmt in cmts.all():
1908 grouped[cmt.revision].append(cmt)
1909 return grouped
1910
1911 def statuses(self, revisions=None):
1912 """
1913 Returns statuses for this repository
1914
1915 :param revisions: list of revisions to get statuses for
1916 """
1917 statuses = ChangesetStatus.query()\
1918 .filter(ChangesetStatus.repo == self)\
1919 .filter(ChangesetStatus.version == 0)
1920
1921 if revisions:
1922 # Try doing the filtering in chunks to avoid hitting limits
1923 size = 500
1924 status_results = []
1925 for chunk in xrange(0, len(revisions), size):
1926 status_results += statuses.filter(
1927 ChangesetStatus.revision.in_(
1928 revisions[chunk: chunk+size])
1929 ).all()
1930 else:
1931 status_results = statuses.all()
1932
1933 grouped = {}
1934
1935 # maybe we have open new pullrequest without a status?
1936 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1937 status_lbl = ChangesetStatus.get_status_lbl(stat)
1938 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1939 for rev in pr.revisions:
1940 pr_id = pr.pull_request_id
1941 pr_repo = pr.target_repo.repo_name
1942 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1943
1944 for stat in status_results:
1945 pr_id = pr_repo = None
1946 if stat.pull_request:
1947 pr_id = stat.pull_request.pull_request_id
1948 pr_repo = stat.pull_request.target_repo.repo_name
1949 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1950 pr_id, pr_repo]
1951 return grouped
1952
1953 # ==========================================================================
1954 # SCM CACHE INSTANCE
1955 # ==========================================================================
1956
1957 def scm_instance(self, **kwargs):
1958 import rhodecode
1959
1960 # Passing a config will not hit the cache currently only used
1961 # for repo2dbmapper
1962 config = kwargs.pop('config', None)
1963 cache = kwargs.pop('cache', None)
1964 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1965 # if cache is NOT defined use default global, else we have a full
1966 # control over cache behaviour
1967 if cache is None and full_cache and not config:
1968 return self._get_instance_cached()
1969 return self._get_instance(cache=bool(cache), config=config)
1970
1971 def _get_instance_cached(self):
1972 @cache_region('long_term')
1973 def _get_repo(cache_key):
1974 return self._get_instance()
1975
1976 invalidator_context = CacheKey.repo_context_cache(
1977 _get_repo, self.repo_name, None)
1978
1979 with invalidator_context as context:
1980 context.invalidate()
1981 repo = context.compute()
1982
1983 return repo
1984
1985 def _get_instance(self, cache=True, config=None):
1986 repo_full_path = self.repo_full_path
1987 try:
1988 vcs_alias = get_scm(repo_full_path)[0]
1989 log.debug(
1990 'Creating instance of %s repository from %s',
1991 vcs_alias, repo_full_path)
1992 backend = get_backend(vcs_alias)
1993 except VCSError:
1994 log.exception(
1995 'Perhaps this repository is in db and not in '
1996 'filesystem run rescan repositories with '
1997 '"destroy old data" option from admin panel')
1998 return
1999
2000 config = config or self._config
2001 custom_wire = {
2002 'cache': cache # controls the vcs.remote cache
2003 }
2004 repo = backend(
2005 safe_str(repo_full_path), config=config, create=False,
2006 with_wire=custom_wire)
2007
2008 return repo
2009
2010 def __json__(self):
2011 return {'landing_rev': self.landing_rev}
2012
2013 def get_dict(self):
2014
2015 # Since we transformed `repo_name` to a hybrid property, we need to
2016 # keep compatibility with the code which uses `repo_name` field.
2017
2018 result = super(Repository, self).get_dict()
2019 result['repo_name'] = result.pop('_repo_name', None)
2020 return result
2021
2022
2023 class RepoGroup(Base, BaseModel):
2024 __tablename__ = 'groups'
2025 __table_args__ = (
2026 UniqueConstraint('group_name', 'group_parent_id'),
2027 CheckConstraint('group_id != group_parent_id'),
2028 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2029 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2030 )
2031 __mapper_args__ = {'order_by': 'group_name'}
2032
2033 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2034
2035 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2036 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2037 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2038 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2039 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2040 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2041 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2042
2043 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2044 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2045 parent_group = relationship('RepoGroup', remote_side=group_id)
2046 user = relationship('User')
2047
2048 def __init__(self, group_name='', parent_group=None):
2049 self.group_name = group_name
2050 self.parent_group = parent_group
2051
2052 def __unicode__(self):
2053 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2054 self.group_name)
2055
2056 @classmethod
2057 def _generate_choice(cls, repo_group):
2058 from webhelpers.html import literal as _literal
2059 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2060 return repo_group.group_id, _name(repo_group.full_path_splitted)
2061
2062 @classmethod
2063 def groups_choices(cls, groups=None, show_empty_group=True):
2064 if not groups:
2065 groups = cls.query().all()
2066
2067 repo_groups = []
2068 if show_empty_group:
2069 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2070
2071 repo_groups.extend([cls._generate_choice(x) for x in groups])
2072
2073 repo_groups = sorted(
2074 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2075 return repo_groups
2076
2077 @classmethod
2078 def url_sep(cls):
2079 return URL_SEP
2080
2081 @classmethod
2082 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2083 if case_insensitive:
2084 gr = cls.query().filter(func.lower(cls.group_name)
2085 == func.lower(group_name))
2086 else:
2087 gr = cls.query().filter(cls.group_name == group_name)
2088 if cache:
2089 gr = gr.options(FromCache(
2090 "sql_cache_short",
2091 "get_group_%s" % _hash_key(group_name)))
2092 return gr.scalar()
2093
2094 @classmethod
2095 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2096 case_insensitive=True):
2097 q = RepoGroup.query()
2098
2099 if not isinstance(user_id, Optional):
2100 q = q.filter(RepoGroup.user_id == user_id)
2101
2102 if not isinstance(group_id, Optional):
2103 q = q.filter(RepoGroup.group_parent_id == group_id)
2104
2105 if case_insensitive:
2106 q = q.order_by(func.lower(RepoGroup.group_name))
2107 else:
2108 q = q.order_by(RepoGroup.group_name)
2109 return q.all()
2110
2111 @property
2112 def parents(self):
2113 parents_recursion_limit = 10
2114 groups = []
2115 if self.parent_group is None:
2116 return groups
2117 cur_gr = self.parent_group
2118 groups.insert(0, cur_gr)
2119 cnt = 0
2120 while 1:
2121 cnt += 1
2122 gr = getattr(cur_gr, 'parent_group', None)
2123 cur_gr = cur_gr.parent_group
2124 if gr is None:
2125 break
2126 if cnt == parents_recursion_limit:
2127 # this will prevent accidental infinit loops
2128 log.error(('more than %s parents found for group %s, stopping '
2129 'recursive parent fetching' % (parents_recursion_limit, self)))
2130 break
2131
2132 groups.insert(0, gr)
2133 return groups
2134
2135 @property
2136 def children(self):
2137 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2138
2139 @property
2140 def name(self):
2141 return self.group_name.split(RepoGroup.url_sep())[-1]
2142
2143 @property
2144 def full_path(self):
2145 return self.group_name
2146
2147 @property
2148 def full_path_splitted(self):
2149 return self.group_name.split(RepoGroup.url_sep())
2150
2151 @property
2152 def repositories(self):
2153 return Repository.query()\
2154 .filter(Repository.group == self)\
2155 .order_by(Repository.repo_name)
2156
2157 @property
2158 def repositories_recursive_count(self):
2159 cnt = self.repositories.count()
2160
2161 def children_count(group):
2162 cnt = 0
2163 for child in group.children:
2164 cnt += child.repositories.count()
2165 cnt += children_count(child)
2166 return cnt
2167
2168 return cnt + children_count(self)
2169
2170 def _recursive_objects(self, include_repos=True):
2171 all_ = []
2172
2173 def _get_members(root_gr):
2174 if include_repos:
2175 for r in root_gr.repositories:
2176 all_.append(r)
2177 childs = root_gr.children.all()
2178 if childs:
2179 for gr in childs:
2180 all_.append(gr)
2181 _get_members(gr)
2182
2183 _get_members(self)
2184 return [self] + all_
2185
2186 def recursive_groups_and_repos(self):
2187 """
2188 Recursive return all groups, with repositories in those groups
2189 """
2190 return self._recursive_objects()
2191
2192 def recursive_groups(self):
2193 """
2194 Returns all children groups for this group including children of children
2195 """
2196 return self._recursive_objects(include_repos=False)
2197
2198 def get_new_name(self, group_name):
2199 """
2200 returns new full group name based on parent and new name
2201
2202 :param group_name:
2203 """
2204 path_prefix = (self.parent_group.full_path_splitted if
2205 self.parent_group else [])
2206 return RepoGroup.url_sep().join(path_prefix + [group_name])
2207
2208 def permissions(self, with_admins=True, with_owner=True):
2209 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2210 q = q.options(joinedload(UserRepoGroupToPerm.group),
2211 joinedload(UserRepoGroupToPerm.user),
2212 joinedload(UserRepoGroupToPerm.permission),)
2213
2214 # get owners and admins and permissions. We do a trick of re-writing
2215 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2216 # has a global reference and changing one object propagates to all
2217 # others. This means if admin is also an owner admin_row that change
2218 # would propagate to both objects
2219 perm_rows = []
2220 for _usr in q.all():
2221 usr = AttributeDict(_usr.user.get_dict())
2222 usr.permission = _usr.permission.permission_name
2223 perm_rows.append(usr)
2224
2225 # filter the perm rows by 'default' first and then sort them by
2226 # admin,write,read,none permissions sorted again alphabetically in
2227 # each group
2228 perm_rows = sorted(perm_rows, key=display_sort)
2229
2230 _admin_perm = 'group.admin'
2231 owner_row = []
2232 if with_owner:
2233 usr = AttributeDict(self.user.get_dict())
2234 usr.owner_row = True
2235 usr.permission = _admin_perm
2236 owner_row.append(usr)
2237
2238 super_admin_rows = []
2239 if with_admins:
2240 for usr in User.get_all_super_admins():
2241 # if this admin is also owner, don't double the record
2242 if usr.user_id == owner_row[0].user_id:
2243 owner_row[0].admin_row = True
2244 else:
2245 usr = AttributeDict(usr.get_dict())
2246 usr.admin_row = True
2247 usr.permission = _admin_perm
2248 super_admin_rows.append(usr)
2249
2250 return super_admin_rows + owner_row + perm_rows
2251
2252 def permission_user_groups(self):
2253 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2254 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2255 joinedload(UserGroupRepoGroupToPerm.users_group),
2256 joinedload(UserGroupRepoGroupToPerm.permission),)
2257
2258 perm_rows = []
2259 for _user_group in q.all():
2260 usr = AttributeDict(_user_group.users_group.get_dict())
2261 usr.permission = _user_group.permission.permission_name
2262 perm_rows.append(usr)
2263
2264 return perm_rows
2265
2266 def get_api_data(self):
2267 """
2268 Common function for generating api data
2269
2270 """
2271 group = self
2272 data = {
2273 'group_id': group.group_id,
2274 'group_name': group.group_name,
2275 'group_description': group.group_description,
2276 'parent_group': group.parent_group.group_name if group.parent_group else None,
2277 'repositories': [x.repo_name for x in group.repositories],
2278 'owner': group.user.username,
2279 }
2280 return data
2281
2282
2283 class Permission(Base, BaseModel):
2284 __tablename__ = 'permissions'
2285 __table_args__ = (
2286 Index('p_perm_name_idx', 'permission_name'),
2287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2288 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2289 )
2290 PERMS = [
2291 ('hg.admin', _('RhodeCode Super Administrator')),
2292
2293 ('repository.none', _('Repository no access')),
2294 ('repository.read', _('Repository read access')),
2295 ('repository.write', _('Repository write access')),
2296 ('repository.admin', _('Repository admin access')),
2297
2298 ('group.none', _('Repository group no access')),
2299 ('group.read', _('Repository group read access')),
2300 ('group.write', _('Repository group write access')),
2301 ('group.admin', _('Repository group admin access')),
2302
2303 ('usergroup.none', _('User group no access')),
2304 ('usergroup.read', _('User group read access')),
2305 ('usergroup.write', _('User group write access')),
2306 ('usergroup.admin', _('User group admin access')),
2307
2308 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2309 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2310
2311 ('hg.usergroup.create.false', _('User Group creation disabled')),
2312 ('hg.usergroup.create.true', _('User Group creation enabled')),
2313
2314 ('hg.create.none', _('Repository creation disabled')),
2315 ('hg.create.repository', _('Repository creation enabled')),
2316 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2317 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2318
2319 ('hg.fork.none', _('Repository forking disabled')),
2320 ('hg.fork.repository', _('Repository forking enabled')),
2321
2322 ('hg.register.none', _('Registration disabled')),
2323 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2324 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2325
2326 ('hg.extern_activate.manual', _('Manual activation of external account')),
2327 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2328
2329 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2330 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2331 ]
2332
2333 # definition of system default permissions for DEFAULT user
2334 DEFAULT_USER_PERMISSIONS = [
2335 'repository.read',
2336 'group.read',
2337 'usergroup.read',
2338 'hg.create.repository',
2339 'hg.repogroup.create.false',
2340 'hg.usergroup.create.false',
2341 'hg.create.write_on_repogroup.true',
2342 'hg.fork.repository',
2343 'hg.register.manual_activate',
2344 'hg.extern_activate.auto',
2345 'hg.inherit_default_perms.true',
2346 ]
2347
2348 # defines which permissions are more important higher the more important
2349 # Weight defines which permissions are more important.
2350 # The higher number the more important.
2351 PERM_WEIGHTS = {
2352 'repository.none': 0,
2353 'repository.read': 1,
2354 'repository.write': 3,
2355 'repository.admin': 4,
2356
2357 'group.none': 0,
2358 'group.read': 1,
2359 'group.write': 3,
2360 'group.admin': 4,
2361
2362 'usergroup.none': 0,
2363 'usergroup.read': 1,
2364 'usergroup.write': 3,
2365 'usergroup.admin': 4,
2366
2367 'hg.repogroup.create.false': 0,
2368 'hg.repogroup.create.true': 1,
2369
2370 'hg.usergroup.create.false': 0,
2371 'hg.usergroup.create.true': 1,
2372
2373 'hg.fork.none': 0,
2374 'hg.fork.repository': 1,
2375 'hg.create.none': 0,
2376 'hg.create.repository': 1
2377 }
2378
2379 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2380 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2381 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2382
2383 def __unicode__(self):
2384 return u"<%s('%s:%s')>" % (
2385 self.__class__.__name__, self.permission_id, self.permission_name
2386 )
2387
2388 @classmethod
2389 def get_by_key(cls, key):
2390 return cls.query().filter(cls.permission_name == key).scalar()
2391
2392 @classmethod
2393 def get_default_repo_perms(cls, user_id, repo_id=None):
2394 q = Session().query(UserRepoToPerm, Repository, Permission)\
2395 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2396 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2397 .filter(UserRepoToPerm.user_id == user_id)
2398 if repo_id:
2399 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2400 return q.all()
2401
2402 @classmethod
2403 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2404 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2405 .join(
2406 Permission,
2407 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2408 .join(
2409 Repository,
2410 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2411 .join(
2412 UserGroup,
2413 UserGroupRepoToPerm.users_group_id ==
2414 UserGroup.users_group_id)\
2415 .join(
2416 UserGroupMember,
2417 UserGroupRepoToPerm.users_group_id ==
2418 UserGroupMember.users_group_id)\
2419 .filter(
2420 UserGroupMember.user_id == user_id,
2421 UserGroup.users_group_active == true())
2422 if repo_id:
2423 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2424 return q.all()
2425
2426 @classmethod
2427 def get_default_group_perms(cls, user_id, repo_group_id=None):
2428 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2429 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2430 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2431 .filter(UserRepoGroupToPerm.user_id == user_id)
2432 if repo_group_id:
2433 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2434 return q.all()
2435
2436 @classmethod
2437 def get_default_group_perms_from_user_group(
2438 cls, user_id, repo_group_id=None):
2439 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2440 .join(
2441 Permission,
2442 UserGroupRepoGroupToPerm.permission_id ==
2443 Permission.permission_id)\
2444 .join(
2445 RepoGroup,
2446 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2447 .join(
2448 UserGroup,
2449 UserGroupRepoGroupToPerm.users_group_id ==
2450 UserGroup.users_group_id)\
2451 .join(
2452 UserGroupMember,
2453 UserGroupRepoGroupToPerm.users_group_id ==
2454 UserGroupMember.users_group_id)\
2455 .filter(
2456 UserGroupMember.user_id == user_id,
2457 UserGroup.users_group_active == true())
2458 if repo_group_id:
2459 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2460 return q.all()
2461
2462 @classmethod
2463 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2464 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2465 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2466 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2467 .filter(UserUserGroupToPerm.user_id == user_id)
2468 if user_group_id:
2469 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2470 return q.all()
2471
2472 @classmethod
2473 def get_default_user_group_perms_from_user_group(
2474 cls, user_id, user_group_id=None):
2475 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2476 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2477 .join(
2478 Permission,
2479 UserGroupUserGroupToPerm.permission_id ==
2480 Permission.permission_id)\
2481 .join(
2482 TargetUserGroup,
2483 UserGroupUserGroupToPerm.target_user_group_id ==
2484 TargetUserGroup.users_group_id)\
2485 .join(
2486 UserGroup,
2487 UserGroupUserGroupToPerm.user_group_id ==
2488 UserGroup.users_group_id)\
2489 .join(
2490 UserGroupMember,
2491 UserGroupUserGroupToPerm.user_group_id ==
2492 UserGroupMember.users_group_id)\
2493 .filter(
2494 UserGroupMember.user_id == user_id,
2495 UserGroup.users_group_active == true())
2496 if user_group_id:
2497 q = q.filter(
2498 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2499
2500 return q.all()
2501
2502
2503 class UserRepoToPerm(Base, BaseModel):
2504 __tablename__ = 'repo_to_perm'
2505 __table_args__ = (
2506 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2508 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2509 )
2510 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2511 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2512 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2513 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2514
2515 user = relationship('User')
2516 repository = relationship('Repository')
2517 permission = relationship('Permission')
2518
2519 @classmethod
2520 def create(cls, user, repository, permission):
2521 n = cls()
2522 n.user = user
2523 n.repository = repository
2524 n.permission = permission
2525 Session().add(n)
2526 return n
2527
2528 def __unicode__(self):
2529 return u'<%s => %s >' % (self.user, self.repository)
2530
2531
2532 class UserUserGroupToPerm(Base, BaseModel):
2533 __tablename__ = 'user_user_group_to_perm'
2534 __table_args__ = (
2535 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2537 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2538 )
2539 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2540 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2541 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2542 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2543
2544 user = relationship('User')
2545 user_group = relationship('UserGroup')
2546 permission = relationship('Permission')
2547
2548 @classmethod
2549 def create(cls, user, user_group, permission):
2550 n = cls()
2551 n.user = user
2552 n.user_group = user_group
2553 n.permission = permission
2554 Session().add(n)
2555 return n
2556
2557 def __unicode__(self):
2558 return u'<%s => %s >' % (self.user, self.user_group)
2559
2560
2561 class UserToPerm(Base, BaseModel):
2562 __tablename__ = 'user_to_perm'
2563 __table_args__ = (
2564 UniqueConstraint('user_id', 'permission_id'),
2565 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2566 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2567 )
2568 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2569 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2570 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2571
2572 user = relationship('User')
2573 permission = relationship('Permission', lazy='joined')
2574
2575 def __unicode__(self):
2576 return u'<%s => %s >' % (self.user, self.permission)
2577
2578
2579 class UserGroupRepoToPerm(Base, BaseModel):
2580 __tablename__ = 'users_group_repo_to_perm'
2581 __table_args__ = (
2582 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2584 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2585 )
2586 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2587 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2588 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2589 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2590
2591 users_group = relationship('UserGroup')
2592 permission = relationship('Permission')
2593 repository = relationship('Repository')
2594
2595 @classmethod
2596 def create(cls, users_group, repository, permission):
2597 n = cls()
2598 n.users_group = users_group
2599 n.repository = repository
2600 n.permission = permission
2601 Session().add(n)
2602 return n
2603
2604 def __unicode__(self):
2605 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2606
2607
2608 class UserGroupUserGroupToPerm(Base, BaseModel):
2609 __tablename__ = 'user_group_user_group_to_perm'
2610 __table_args__ = (
2611 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2612 CheckConstraint('target_user_group_id != user_group_id'),
2613 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2614 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2615 )
2616 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2617 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2618 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2619 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2620
2621 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2622 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2623 permission = relationship('Permission')
2624
2625 @classmethod
2626 def create(cls, target_user_group, user_group, permission):
2627 n = cls()
2628 n.target_user_group = target_user_group
2629 n.user_group = user_group
2630 n.permission = permission
2631 Session().add(n)
2632 return n
2633
2634 def __unicode__(self):
2635 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2636
2637
2638 class UserGroupToPerm(Base, BaseModel):
2639 __tablename__ = 'users_group_to_perm'
2640 __table_args__ = (
2641 UniqueConstraint('users_group_id', 'permission_id',),
2642 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2643 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2644 )
2645 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2646 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2647 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2648
2649 users_group = relationship('UserGroup')
2650 permission = relationship('Permission')
2651
2652
2653 class UserRepoGroupToPerm(Base, BaseModel):
2654 __tablename__ = 'user_repo_group_to_perm'
2655 __table_args__ = (
2656 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2657 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2658 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2659 )
2660
2661 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2662 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2663 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2664 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2665
2666 user = relationship('User')
2667 group = relationship('RepoGroup')
2668 permission = relationship('Permission')
2669
2670 @classmethod
2671 def create(cls, user, repository_group, permission):
2672 n = cls()
2673 n.user = user
2674 n.group = repository_group
2675 n.permission = permission
2676 Session().add(n)
2677 return n
2678
2679
2680 class UserGroupRepoGroupToPerm(Base, BaseModel):
2681 __tablename__ = 'users_group_repo_group_to_perm'
2682 __table_args__ = (
2683 UniqueConstraint('users_group_id', 'group_id'),
2684 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2685 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2686 )
2687
2688 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2689 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2690 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2691 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2692
2693 users_group = relationship('UserGroup')
2694 permission = relationship('Permission')
2695 group = relationship('RepoGroup')
2696
2697 @classmethod
2698 def create(cls, user_group, repository_group, permission):
2699 n = cls()
2700 n.users_group = user_group
2701 n.group = repository_group
2702 n.permission = permission
2703 Session().add(n)
2704 return n
2705
2706 def __unicode__(self):
2707 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2708
2709
2710 class Statistics(Base, BaseModel):
2711 __tablename__ = 'statistics'
2712 __table_args__ = (
2713 UniqueConstraint('repository_id'),
2714 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2715 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2716 )
2717 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2718 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2719 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2720 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2721 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2722 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2723
2724 repository = relationship('Repository', single_parent=True)
2725
2726
2727 class UserFollowing(Base, BaseModel):
2728 __tablename__ = 'user_followings'
2729 __table_args__ = (
2730 UniqueConstraint('user_id', 'follows_repository_id'),
2731 UniqueConstraint('user_id', 'follows_user_id'),
2732 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2733 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2734 )
2735
2736 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2737 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2738 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2739 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2740 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2741
2742 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2743
2744 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2745 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2746
2747 @classmethod
2748 def get_repo_followers(cls, repo_id):
2749 return cls.query().filter(cls.follows_repo_id == repo_id)
2750
2751
2752 class CacheKey(Base, BaseModel):
2753 __tablename__ = 'cache_invalidation'
2754 __table_args__ = (
2755 UniqueConstraint('cache_key'),
2756 Index('key_idx', 'cache_key'),
2757 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2758 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2759 )
2760 CACHE_TYPE_ATOM = 'ATOM'
2761 CACHE_TYPE_RSS = 'RSS'
2762 CACHE_TYPE_README = 'README'
2763
2764 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2765 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2766 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2767 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2768
2769 def __init__(self, cache_key, cache_args=''):
2770 self.cache_key = cache_key
2771 self.cache_args = cache_args
2772 self.cache_active = False
2773
2774 def __unicode__(self):
2775 return u"<%s('%s:%s[%s]')>" % (
2776 self.__class__.__name__,
2777 self.cache_id, self.cache_key, self.cache_active)
2778
2779 def _cache_key_partition(self):
2780 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2781 return prefix, repo_name, suffix
2782
2783 def get_prefix(self):
2784 """
2785 Try to extract prefix from existing cache key. The key could consist
2786 of prefix, repo_name, suffix
2787 """
2788 # this returns prefix, repo_name, suffix
2789 return self._cache_key_partition()[0]
2790
2791 def get_suffix(self):
2792 """
2793 get suffix that might have been used in _get_cache_key to
2794 generate self.cache_key. Only used for informational purposes
2795 in repo_edit.html.
2796 """
2797 # prefix, repo_name, suffix
2798 return self._cache_key_partition()[2]
2799
2800 @classmethod
2801 def delete_all_cache(cls):
2802 """
2803 Delete all cache keys from database.
2804 Should only be run when all instances are down and all entries
2805 thus stale.
2806 """
2807 cls.query().delete()
2808 Session().commit()
2809
2810 @classmethod
2811 def get_cache_key(cls, repo_name, cache_type):
2812 """
2813
2814 Generate a cache key for this process of RhodeCode instance.
2815 Prefix most likely will be process id or maybe explicitly set
2816 instance_id from .ini file.
2817 """
2818 import rhodecode
2819 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2820
2821 repo_as_unicode = safe_unicode(repo_name)
2822 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2823 if cache_type else repo_as_unicode
2824
2825 return u'{}{}'.format(prefix, key)
2826
2827 @classmethod
2828 def set_invalidate(cls, repo_name, delete=False):
2829 """
2830 Mark all caches of a repo as invalid in the database.
2831 """
2832
2833 try:
2834 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2835 if delete:
2836 log.debug('cache objects deleted for repo %s',
2837 safe_str(repo_name))
2838 qry.delete()
2839 else:
2840 log.debug('cache objects marked as invalid for repo %s',
2841 safe_str(repo_name))
2842 qry.update({"cache_active": False})
2843
2844 Session().commit()
2845 except Exception:
2846 log.exception(
2847 'Cache key invalidation failed for repository %s',
2848 safe_str(repo_name))
2849 Session().rollback()
2850
2851 @classmethod
2852 def get_active_cache(cls, cache_key):
2853 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2854 if inv_obj:
2855 return inv_obj
2856 return None
2857
2858 @classmethod
2859 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2860 """
2861 @cache_region('long_term')
2862 def _heavy_calculation(cache_key):
2863 return 'result'
2864
2865 cache_context = CacheKey.repo_context_cache(
2866 _heavy_calculation, repo_name, cache_type)
2867
2868 with cache_context as context:
2869 context.invalidate()
2870 computed = context.compute()
2871
2872 assert computed == 'result'
2873 """
2874 from rhodecode.lib import caches
2875 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2876
2877
2878 class ChangesetComment(Base, BaseModel):
2879 __tablename__ = 'changeset_comments'
2880 __table_args__ = (
2881 Index('cc_revision_idx', 'revision'),
2882 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2883 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2884 )
2885
2886 COMMENT_OUTDATED = u'comment_outdated'
2887
2888 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2889 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2890 revision = Column('revision', String(40), nullable=True)
2891 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2892 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2893 line_no = Column('line_no', Unicode(10), nullable=True)
2894 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2895 f_path = Column('f_path', Unicode(1000), nullable=True)
2896 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2897 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2898 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2899 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2900 renderer = Column('renderer', Unicode(64), nullable=True)
2901 display_state = Column('display_state', Unicode(128), nullable=True)
2902
2903 author = relationship('User', lazy='joined')
2904 repo = relationship('Repository')
2905 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2906 pull_request = relationship('PullRequest', lazy='joined')
2907 pull_request_version = relationship('PullRequestVersion')
2908
2909 @classmethod
2910 def get_users(cls, revision=None, pull_request_id=None):
2911 """
2912 Returns user associated with this ChangesetComment. ie those
2913 who actually commented
2914
2915 :param cls:
2916 :param revision:
2917 """
2918 q = Session().query(User)\
2919 .join(ChangesetComment.author)
2920 if revision:
2921 q = q.filter(cls.revision == revision)
2922 elif pull_request_id:
2923 q = q.filter(cls.pull_request_id == pull_request_id)
2924 return q.all()
2925
2926 def render(self, mentions=False):
2927 from rhodecode.lib import helpers as h
2928 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2929
2930 def __repr__(self):
2931 if self.comment_id:
2932 return '<DB:ChangesetComment #%s>' % self.comment_id
2933 else:
2934 return '<DB:ChangesetComment at %#x>' % id(self)
2935
2936
2937 class ChangesetStatus(Base, BaseModel):
2938 __tablename__ = 'changeset_statuses'
2939 __table_args__ = (
2940 Index('cs_revision_idx', 'revision'),
2941 Index('cs_version_idx', 'version'),
2942 UniqueConstraint('repo_id', 'revision', 'version'),
2943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2944 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2945 )
2946 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2947 STATUS_APPROVED = 'approved'
2948 STATUS_REJECTED = 'rejected'
2949 STATUS_UNDER_REVIEW = 'under_review'
2950
2951 STATUSES = [
2952 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2953 (STATUS_APPROVED, _("Approved")),
2954 (STATUS_REJECTED, _("Rejected")),
2955 (STATUS_UNDER_REVIEW, _("Under Review")),
2956 ]
2957
2958 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2959 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2960 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2961 revision = Column('revision', String(40), nullable=False)
2962 status = Column('status', String(128), nullable=False, default=DEFAULT)
2963 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2964 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2965 version = Column('version', Integer(), nullable=False, default=0)
2966 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2967
2968 author = relationship('User', lazy='joined')
2969 repo = relationship('Repository')
2970 comment = relationship('ChangesetComment', lazy='joined')
2971 pull_request = relationship('PullRequest', lazy='joined')
2972
2973 def __unicode__(self):
2974 return u"<%s('%s[%s]:%s')>" % (
2975 self.__class__.__name__,
2976 self.status, self.version, self.author
2977 )
2978
2979 @classmethod
2980 def get_status_lbl(cls, value):
2981 return dict(cls.STATUSES).get(value)
2982
2983 @property
2984 def status_lbl(self):
2985 return ChangesetStatus.get_status_lbl(self.status)
2986
2987
2988 class _PullRequestBase(BaseModel):
2989 """
2990 Common attributes of pull request and version entries.
2991 """
2992
2993 # .status values
2994 STATUS_NEW = u'new'
2995 STATUS_OPEN = u'open'
2996 STATUS_CLOSED = u'closed'
2997
2998 title = Column('title', Unicode(255), nullable=True)
2999 description = Column(
3000 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3001 nullable=True)
3002 # new/open/closed status of pull request (not approve/reject/etc)
3003 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3004 created_on = Column(
3005 'created_on', DateTime(timezone=False), nullable=False,
3006 default=datetime.datetime.now)
3007 updated_on = Column(
3008 'updated_on', DateTime(timezone=False), nullable=False,
3009 default=datetime.datetime.now)
3010
3011 @declared_attr
3012 def user_id(cls):
3013 return Column(
3014 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3015 unique=None)
3016
3017 # 500 revisions max
3018 _revisions = Column(
3019 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3020
3021 @declared_attr
3022 def source_repo_id(cls):
3023 # TODO: dan: rename column to source_repo_id
3024 return Column(
3025 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3026 nullable=False)
3027
3028 source_ref = Column('org_ref', Unicode(255), nullable=False)
3029
3030 @declared_attr
3031 def target_repo_id(cls):
3032 # TODO: dan: rename column to target_repo_id
3033 return Column(
3034 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3035 nullable=False)
3036
3037 target_ref = Column('other_ref', Unicode(255), nullable=False)
3038
3039 # TODO: dan: rename column to last_merge_source_rev
3040 _last_merge_source_rev = Column(
3041 'last_merge_org_rev', String(40), nullable=True)
3042 # TODO: dan: rename column to last_merge_target_rev
3043 _last_merge_target_rev = Column(
3044 'last_merge_other_rev', String(40), nullable=True)
3045 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3046 merge_rev = Column('merge_rev', String(40), nullable=True)
3047
3048 @hybrid_property
3049 def revisions(self):
3050 return self._revisions.split(':') if self._revisions else []
3051
3052 @revisions.setter
3053 def revisions(self, val):
3054 self._revisions = ':'.join(val)
3055
3056 @declared_attr
3057 def author(cls):
3058 return relationship('User', lazy='joined')
3059
3060 @declared_attr
3061 def source_repo(cls):
3062 return relationship(
3063 'Repository',
3064 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3065
3066 @property
3067 def source_ref_parts(self):
3068 refs = self.source_ref.split(':')
3069 return Reference(refs[0], refs[1], refs[2])
3070
3071 @declared_attr
3072 def target_repo(cls):
3073 return relationship(
3074 'Repository',
3075 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3076
3077 @property
3078 def target_ref_parts(self):
3079 refs = self.target_ref.split(':')
3080 return Reference(refs[0], refs[1], refs[2])
3081
3082
3083 class PullRequest(Base, _PullRequestBase):
3084 __tablename__ = 'pull_requests'
3085 __table_args__ = (
3086 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3087 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3088 )
3089
3090 pull_request_id = Column(
3091 'pull_request_id', Integer(), nullable=False, primary_key=True)
3092
3093 def __repr__(self):
3094 if self.pull_request_id:
3095 return '<DB:PullRequest #%s>' % self.pull_request_id
3096 else:
3097 return '<DB:PullRequest at %#x>' % id(self)
3098
3099 reviewers = relationship('PullRequestReviewers',
3100 cascade="all, delete, delete-orphan")
3101 statuses = relationship('ChangesetStatus')
3102 comments = relationship('ChangesetComment',
3103 cascade="all, delete, delete-orphan")
3104 versions = relationship('PullRequestVersion',
3105 cascade="all, delete, delete-orphan")
3106
3107 def is_closed(self):
3108 return self.status == self.STATUS_CLOSED
3109
3110 def get_api_data(self):
3111 from rhodecode.model.pull_request import PullRequestModel
3112 pull_request = self
3113 merge_status = PullRequestModel().merge_status(pull_request)
3114 data = {
3115 'pull_request_id': pull_request.pull_request_id,
3116 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3117 pull_request_id=self.pull_request_id,
3118 qualified=True),
3119 'title': pull_request.title,
3120 'description': pull_request.description,
3121 'status': pull_request.status,
3122 'created_on': pull_request.created_on,
3123 'updated_on': pull_request.updated_on,
3124 'commit_ids': pull_request.revisions,
3125 'review_status': pull_request.calculated_review_status(),
3126 'mergeable': {
3127 'status': merge_status[0],
3128 'message': unicode(merge_status[1]),
3129 },
3130 'source': {
3131 'clone_url': pull_request.source_repo.clone_url(),
3132 'repository': pull_request.source_repo.repo_name,
3133 'reference': {
3134 'name': pull_request.source_ref_parts.name,
3135 'type': pull_request.source_ref_parts.type,
3136 'commit_id': pull_request.source_ref_parts.commit_id,
3137 },
3138 },
3139 'target': {
3140 'clone_url': pull_request.target_repo.clone_url(),
3141 'repository': pull_request.target_repo.repo_name,
3142 'reference': {
3143 'name': pull_request.target_ref_parts.name,
3144 'type': pull_request.target_ref_parts.type,
3145 'commit_id': pull_request.target_ref_parts.commit_id,
3146 },
3147 },
3148 'author': pull_request.author.get_api_data(include_secrets=False,
3149 details='basic'),
3150 'reviewers': [
3151 {
3152 'user': reviewer.get_api_data(include_secrets=False,
3153 details='basic'),
3154 'review_status': st[0][1].status if st else 'not_reviewed',
3155 }
3156 for reviewer, st in pull_request.reviewers_statuses()
3157 ]
3158 }
3159
3160 return data
3161
3162 def __json__(self):
3163 return {
3164 'revisions': self.revisions,
3165 }
3166
3167 def calculated_review_status(self):
3168 # TODO: anderson: 13.05.15 Used only on templates/my_account_pullrequests.html
3169 # because it's tricky on how to use ChangesetStatusModel from there
3170 warnings.warn("Use calculated_review_status from ChangesetStatusModel", DeprecationWarning)
3171 from rhodecode.model.changeset_status import ChangesetStatusModel
3172 return ChangesetStatusModel().calculated_review_status(self)
3173
3174 def reviewers_statuses(self):
3175 warnings.warn("Use reviewers_statuses from ChangesetStatusModel", DeprecationWarning)
3176 from rhodecode.model.changeset_status import ChangesetStatusModel
3177 return ChangesetStatusModel().reviewers_statuses(self)
3178
3179
3180 class PullRequestVersion(Base, _PullRequestBase):
3181 __tablename__ = 'pull_request_versions'
3182 __table_args__ = (
3183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3184 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3185 )
3186
3187 pull_request_version_id = Column(
3188 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3189 pull_request_id = Column(
3190 'pull_request_id', Integer(),
3191 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3192 pull_request = relationship('PullRequest')
3193
3194 def __repr__(self):
3195 if self.pull_request_version_id:
3196 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3197 else:
3198 return '<DB:PullRequestVersion at %#x>' % id(self)
3199
3200
3201 class PullRequestReviewers(Base, BaseModel):
3202 __tablename__ = 'pull_request_reviewers'
3203 __table_args__ = (
3204 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3205 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3206 )
3207
3208 def __init__(self, user=None, pull_request=None):
3209 self.user = user
3210 self.pull_request = pull_request
3211
3212 pull_requests_reviewers_id = Column(
3213 'pull_requests_reviewers_id', Integer(), nullable=False,
3214 primary_key=True)
3215 pull_request_id = Column(
3216 "pull_request_id", Integer(),
3217 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3218 user_id = Column(
3219 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3220
3221 user = relationship('User')
3222 pull_request = relationship('PullRequest')
3223
3224
3225 class Notification(Base, BaseModel):
3226 __tablename__ = 'notifications'
3227 __table_args__ = (
3228 Index('notification_type_idx', 'type'),
3229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3230 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3231 )
3232
3233 TYPE_CHANGESET_COMMENT = u'cs_comment'
3234 TYPE_MESSAGE = u'message'
3235 TYPE_MENTION = u'mention'
3236 TYPE_REGISTRATION = u'registration'
3237 TYPE_PULL_REQUEST = u'pull_request'
3238 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3239
3240 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3241 subject = Column('subject', Unicode(512), nullable=True)
3242 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3243 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3244 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3245 type_ = Column('type', Unicode(255))
3246
3247 created_by_user = relationship('User')
3248 notifications_to_users = relationship('UserNotification', lazy='joined',
3249 cascade="all, delete, delete-orphan")
3250
3251 @property
3252 def recipients(self):
3253 return [x.user for x in UserNotification.query()\
3254 .filter(UserNotification.notification == self)\
3255 .order_by(UserNotification.user_id.asc()).all()]
3256
3257 @classmethod
3258 def create(cls, created_by, subject, body, recipients, type_=None):
3259 if type_ is None:
3260 type_ = Notification.TYPE_MESSAGE
3261
3262 notification = cls()
3263 notification.created_by_user = created_by
3264 notification.subject = subject
3265 notification.body = body
3266 notification.type_ = type_
3267 notification.created_on = datetime.datetime.now()
3268
3269 for u in recipients:
3270 assoc = UserNotification()
3271 assoc.notification = notification
3272
3273 # if created_by is inside recipients mark his notification
3274 # as read
3275 if u.user_id == created_by.user_id:
3276 assoc.read = True
3277
3278 u.notifications.append(assoc)
3279 Session().add(notification)
3280
3281 return notification
3282
3283 @property
3284 def description(self):
3285 from rhodecode.model.notification import NotificationModel
3286 return NotificationModel().make_description(self)
3287
3288
3289 class UserNotification(Base, BaseModel):
3290 __tablename__ = 'user_to_notification'
3291 __table_args__ = (
3292 UniqueConstraint('user_id', 'notification_id'),
3293 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3294 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3295 )
3296 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3297 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3298 read = Column('read', Boolean, default=False)
3299 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3300
3301 user = relationship('User', lazy="joined")
3302 notification = relationship('Notification', lazy="joined",
3303 order_by=lambda: Notification.created_on.desc(),)
3304
3305 def mark_as_read(self):
3306 self.read = True
3307 Session().add(self)
3308
3309
3310 class Gist(Base, BaseModel):
3311 __tablename__ = 'gists'
3312 __table_args__ = (
3313 Index('g_gist_access_id_idx', 'gist_access_id'),
3314 Index('g_created_on_idx', 'created_on'),
3315 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3316 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3317 )
3318 GIST_PUBLIC = u'public'
3319 GIST_PRIVATE = u'private'
3320 DEFAULT_FILENAME = u'gistfile1.txt'
3321
3322 ACL_LEVEL_PUBLIC = u'acl_public'
3323 ACL_LEVEL_PRIVATE = u'acl_private'
3324
3325 gist_id = Column('gist_id', Integer(), primary_key=True)
3326 gist_access_id = Column('gist_access_id', Unicode(250))
3327 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3328 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3329 gist_expires = Column('gist_expires', Float(53), nullable=False)
3330 gist_type = Column('gist_type', Unicode(128), nullable=False)
3331 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3332 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3333 acl_level = Column('acl_level', Unicode(128), nullable=True)
3334
3335 owner = relationship('User')
3336
3337 def __repr__(self):
3338 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3339
3340 @classmethod
3341 def get_or_404(cls, id_):
3342 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3343 if not res:
3344 raise HTTPNotFound
3345 return res
3346
3347 @classmethod
3348 def get_by_access_id(cls, gist_access_id):
3349 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3350
3351 def gist_url(self):
3352 import rhodecode
3353 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3354 if alias_url:
3355 return alias_url.replace('{gistid}', self.gist_access_id)
3356
3357 return url('gist', gist_id=self.gist_access_id, qualified=True)
3358
3359 @classmethod
3360 def base_path(cls):
3361 """
3362 Returns base path when all gists are stored
3363
3364 :param cls:
3365 """
3366 from rhodecode.model.gist import GIST_STORE_LOC
3367 q = Session().query(RhodeCodeUi)\
3368 .filter(RhodeCodeUi.ui_key == URL_SEP)
3369 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3370 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3371
3372 def get_api_data(self):
3373 """
3374 Common function for generating gist related data for API
3375 """
3376 gist = self
3377 data = {
3378 'gist_id': gist.gist_id,
3379 'type': gist.gist_type,
3380 'access_id': gist.gist_access_id,
3381 'description': gist.gist_description,
3382 'url': gist.gist_url(),
3383 'expires': gist.gist_expires,
3384 'created_on': gist.created_on,
3385 'modified_at': gist.modified_at,
3386 'content': None,
3387 'acl_level': gist.acl_level,
3388 }
3389 return data
3390
3391 def __json__(self):
3392 data = dict(
3393 )
3394 data.update(self.get_api_data())
3395 return data
3396 # SCM functions
3397
3398 def scm_instance(self, **kwargs):
3399 from rhodecode.lib.vcs import get_repo
3400 base_path = self.base_path()
3401 return get_repo(os.path.join(*map(safe_str,
3402 [base_path, self.gist_access_id])))
3403
3404
3405 class DbMigrateVersion(Base, BaseModel):
3406 __tablename__ = 'db_migrate_version'
3407 __table_args__ = (
3408 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3409 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3410 )
3411 repository_id = Column('repository_id', String(250), primary_key=True)
3412 repository_path = Column('repository_path', Text)
3413 version = Column('version', Integer)
3414
3415
3416 class ExternalIdentity(Base, BaseModel):
3417 __tablename__ = 'external_identities'
3418 __table_args__ = (
3419 Index('local_user_id_idx', 'local_user_id'),
3420 Index('external_id_idx', 'external_id'),
3421 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3422 'mysql_charset': 'utf8'})
3423
3424 external_id = Column('external_id', Unicode(255), default=u'',
3425 primary_key=True)
3426 external_username = Column('external_username', Unicode(1024), default=u'')
3427 local_user_id = Column('local_user_id', Integer(),
3428 ForeignKey('users.user_id'), primary_key=True)
3429 provider_name = Column('provider_name', Unicode(255), default=u'',
3430 primary_key=True)
3431 access_token = Column('access_token', String(1024), default=u'')
3432 alt_token = Column('alt_token', String(1024), default=u'')
3433 token_secret = Column('token_secret', String(1024), default=u'')
3434
3435 @classmethod
3436 def by_external_id_and_provider(cls, external_id, provider_name,
3437 local_user_id=None):
3438 """
3439 Returns ExternalIdentity instance based on search params
3440
3441 :param external_id:
3442 :param provider_name:
3443 :return: ExternalIdentity
3444 """
3445 query = cls.query()
3446 query = query.filter(cls.external_id == external_id)
3447 query = query.filter(cls.provider_name == provider_name)
3448 if local_user_id:
3449 query = query.filter(cls.local_user_id == local_user_id)
3450 return query.first()
3451
3452 @classmethod
3453 def user_by_external_id_and_provider(cls, external_id, provider_name):
3454 """
3455 Returns User instance based on search params
3456
3457 :param external_id:
3458 :param provider_name:
3459 :return: User
3460 """
3461 query = User.query()
3462 query = query.filter(cls.external_id == external_id)
3463 query = query.filter(cls.provider_name == provider_name)
3464 query = query.filter(User.user_id == cls.local_user_id)
3465 return query.first()
3466
3467 @classmethod
3468 def by_local_user_id(cls, local_user_id):
3469 """
3470 Returns all tokens for user
3471
3472 :param local_user_id:
3473 :return: ExternalIdentity
3474 """
3475 query = cls.query()
3476 query = query.filter(cls.local_user_id == local_user_id)
3477 return query
3478
3479
3480 class Integration(Base, BaseModel):
3481 __tablename__ = 'integrations'
3482 __table_args__ = (
3483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3484 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3485 )
3486
3487 integration_id = Column('integration_id', Integer(), primary_key=True)
3488 integration_type = Column('integration_type', String(255))
3489 enabled = Column("enabled", Boolean(), nullable=False)
3490 name = Column('name', String(255), nullable=False)
3491 settings_json = Column('settings_json',
3492 UnicodeText().with_variant(UnicodeText(16384), 'mysql'))
3493 repo_id = Column(
3494 "repo_id", Integer(), ForeignKey('repositories.repo_id'),
3495 nullable=True, unique=None, default=None)
3496 repo = relationship('Repository', lazy='joined')
3497
3498 @hybrid_property
3499 def settings(self):
3500 data = json.loads(self.settings_json or '{}')
3501 return data
3502
3503 @settings.setter
3504 def settings(self, dct):
3505 self.settings_json = json.dumps(dct, indent=2)
3506
3507 def __repr__(self):
3508 if self.repo:
3509 scope = 'repo=%r' % self.repo
3510 else:
3511 scope = 'global'
3512
3513 return '<Integration(%r, %r)>' % (self.integration_type, scope)
3514
3515 def settings_as_dict(self):
3516 return json.loads(self.settings_json)
@@ -0,0 +1,27 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4 import sqlalchemy as sa
5
6 from alembic.migration import MigrationContext
7 from alembic.operations import Operations
8
9 from rhodecode.lib.dbmigrate.versions import _reset_base
10
11 log = logging.getLogger(__name__)
12
13
14 def upgrade(migrate_engine):
15 """
16 Upgrade operations go here.
17 Don't create your own engine; bind migrate_engine to your metadata
18 """
19 _reset_base(migrate_engine)
20 from rhodecode.lib.dbmigrate.schema import db_4_3_0_0
21
22 integrations_table = db_4_3_0_0.Integration.__table__
23 integrations_table.create()
24
25
26 def downgrade(migrate_engine):
27 pass
@@ -0,0 +1,265 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import collections
22
23 import sqlalchemy
24 from sqlalchemy import UnicodeText
25 from sqlalchemy.ext.mutable import Mutable
26
27 from rhodecode.lib.ext_json import json
28
29
30 class JsonRaw(unicode):
31 """
32 Allows interacting with a JSON types field using a raw string.
33
34 For example::
35 db_instance = JsonTable()
36 db_instance.enabled = True
37 db_instance.json_data = JsonRaw('{"a": 4}')
38
39 This will bypass serialization/checks, and allow storing
40 raw values
41 """
42 pass
43
44
45 # Set this to the standard dict if Order is not required
46 DictClass = collections.OrderedDict
47
48
49 class JSONEncodedObj(sqlalchemy.types.TypeDecorator):
50 """
51 Represents an immutable structure as a json-encoded string.
52
53 If default is, for example, a dict, then a NULL value in the
54 database will be exposed as an empty dict.
55 """
56
57 impl = UnicodeText
58 safe = True
59
60 def __init__(self, *args, **kwargs):
61 self.default = kwargs.pop('default', None)
62 self.safe = kwargs.pop('safe_json', self.safe)
63 self.dialect_map = kwargs.pop('dialect_map', {})
64 super(JSONEncodedObj, self).__init__(*args, **kwargs)
65
66 def load_dialect_impl(self, dialect):
67 if dialect.name in self.dialect_map:
68 return dialect.type_descriptor(self.dialect_map[dialect.name])
69 return dialect.type_descriptor(self.impl)
70
71 def process_bind_param(self, value, dialect):
72 if isinstance(value, JsonRaw):
73 value = value
74 elif value is not None:
75 value = json.dumps(value)
76 return value
77
78 def process_result_value(self, value, dialect):
79 if self.default is not None and (not value or value == '""'):
80 return self.default()
81
82 if value is not None:
83 try:
84 value = json.loads(value, object_pairs_hook=DictClass)
85 except Exception as e:
86 if self.safe:
87 return self.default()
88 else:
89 raise
90 return value
91
92
93 class MutationObj(Mutable):
94 @classmethod
95 def coerce(cls, key, value):
96 if isinstance(value, dict) and not isinstance(value, MutationDict):
97 return MutationDict.coerce(key, value)
98 if isinstance(value, list) and not isinstance(value, MutationList):
99 return MutationList.coerce(key, value)
100 return value
101
102 @classmethod
103 def _listen_on_attribute(cls, attribute, coerce, parent_cls):
104 key = attribute.key
105 if parent_cls is not attribute.class_:
106 return
107
108 # rely on "propagate" here
109 parent_cls = attribute.class_
110
111 def load(state, *args):
112 val = state.dict.get(key, None)
113 if coerce:
114 val = cls.coerce(key, val)
115 state.dict[key] = val
116 if isinstance(val, cls):
117 val._parents[state.obj()] = key
118
119 def set(target, value, oldvalue, initiator):
120 if not isinstance(value, cls):
121 value = cls.coerce(key, value)
122 if isinstance(value, cls):
123 value._parents[target.obj()] = key
124 if isinstance(oldvalue, cls):
125 oldvalue._parents.pop(target.obj(), None)
126 return value
127
128 def pickle(state, state_dict):
129 val = state.dict.get(key, None)
130 if isinstance(val, cls):
131 if 'ext.mutable.values' not in state_dict:
132 state_dict['ext.mutable.values'] = []
133 state_dict['ext.mutable.values'].append(val)
134
135 def unpickle(state, state_dict):
136 if 'ext.mutable.values' in state_dict:
137 for val in state_dict['ext.mutable.values']:
138 val._parents[state.obj()] = key
139
140 sqlalchemy.event.listen(parent_cls, 'load', load, raw=True,
141 propagate=True)
142 sqlalchemy.event.listen(parent_cls, 'refresh', load, raw=True,
143 propagate=True)
144 sqlalchemy.event.listen(parent_cls, 'pickle', pickle, raw=True,
145 propagate=True)
146 sqlalchemy.event.listen(attribute, 'set', set, raw=True, retval=True,
147 propagate=True)
148 sqlalchemy.event.listen(parent_cls, 'unpickle', unpickle, raw=True,
149 propagate=True)
150
151
152 class MutationDict(MutationObj, DictClass):
153 @classmethod
154 def coerce(cls, key, value):
155 """Convert plain dictionary to MutationDict"""
156 self = MutationDict(
157 (k, MutationObj.coerce(key, v)) for (k, v) in value.items())
158 self._key = key
159 return self
160
161 def __setitem__(self, key, value):
162 # Due to the way OrderedDict works, this is called during __init__.
163 # At this time we don't have a key set, but what is more, the value
164 # being set has already been coerced. So special case this and skip.
165 if hasattr(self, '_key'):
166 value = MutationObj.coerce(self._key, value)
167 DictClass.__setitem__(self, key, value)
168 self.changed()
169
170 def __delitem__(self, key):
171 DictClass.__delitem__(self, key)
172 self.changed()
173
174 def __setstate__(self, state):
175 self.__dict__ = state
176
177 def __reduce_ex__(self, proto):
178 # support pickling of MutationDicts
179 d = dict(self)
180 return (self.__class__, (d, ))
181
182
183 class MutationList(MutationObj, list):
184 @classmethod
185 def coerce(cls, key, value):
186 """Convert plain list to MutationList"""
187 self = MutationList((MutationObj.coerce(key, v) for v in value))
188 self._key = key
189 return self
190
191 def __setitem__(self, idx, value):
192 list.__setitem__(self, idx, MutationObj.coerce(self._key, value))
193 self.changed()
194
195 def __setslice__(self, start, stop, values):
196 list.__setslice__(self, start, stop,
197 (MutationObj.coerce(self._key, v) for v in values))
198 self.changed()
199
200 def __delitem__(self, idx):
201 list.__delitem__(self, idx)
202 self.changed()
203
204 def __delslice__(self, start, stop):
205 list.__delslice__(self, start, stop)
206 self.changed()
207
208 def append(self, value):
209 list.append(self, MutationObj.coerce(self._key, value))
210 self.changed()
211
212 def insert(self, idx, value):
213 list.insert(self, idx, MutationObj.coerce(self._key, value))
214 self.changed()
215
216 def extend(self, values):
217 list.extend(self, (MutationObj.coerce(self._key, v) for v in values))
218 self.changed()
219
220 def pop(self, *args, **kw):
221 value = list.pop(self, *args, **kw)
222 self.changed()
223 return value
224
225 def remove(self, value):
226 list.remove(self, value)
227 self.changed()
228
229
230 def JsonType(impl=None, **kwargs):
231 """
232 Helper for using a mutation obj, it allows to use .with_variant easily.
233 example::
234
235 settings = Column('settings_json',
236 MutationObj.as_mutable(
237 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
238 """
239
240 if impl == 'list':
241 return JSONEncodedObj(default=list, **kwargs)
242 elif impl == 'dict':
243 return JSONEncodedObj(default=DictClass, **kwargs)
244 else:
245 return JSONEncodedObj(**kwargs)
246
247
248 JSON = MutationObj.as_mutable(JsonType())
249 """
250 A type to encode/decode JSON on the fly
251
252 sqltype is the string type for the underlying DB column::
253
254 Column(JSON) (defaults to UnicodeText)
255 """
256
257 JSONDict = MutationObj.as_mutable(JsonType('dict'))
258 """
259 A type to encode/decode JSON dictionaries on the fly
260 """
261
262 JSONList = MutationObj.as_mutable(JsonType('list'))
263 """
264 A type to encode/decode JSON lists` on the fly
265 """
@@ -0,0 +1,245 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 This serves as a drop in replacement for pycurl. It implements the pycurl Curl
23 class in a way that is compatible with gevent.
24 """
25
26
27 import logging
28 import gevent
29 import pycurl
30
31 # Import everything from pycurl.
32 # This allows us to use this module as a drop in replacement of pycurl.
33 from pycurl import * # noqa
34
35 from gevent import core
36 from gevent.hub import Waiter
37
38
39 log = logging.getLogger(__name__)
40
41
42 class GeventCurlMulti(object):
43 """
44 Wrapper around pycurl.CurlMulti that integrates it into gevent's event
45 loop.
46
47 Parts of this class are a modified version of code copied from the Tornado
48 Web Server project which is licensed under the Apache License, Version 2.0
49 (the "License"). To be more specific the code originates from this file:
50 https://github.com/tornadoweb/tornado/blob/stable/tornado/curl_httpclient.py
51
52 This is the original license header of the origin:
53
54 Copyright 2009 Facebook
55
56 Licensed under the Apache License, Version 2.0 (the "License"); you may
57 not use this file except in compliance with the License. You may obtain
58 a copy of the License at
59
60 http://www.apache.org/licenses/LICENSE-2.0
61
62 Unless required by applicable law or agreed to in writing, software
63 distributed under the License is distributed on an "AS IS" BASIS,
64 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
65 implied. See the License for the specific language governing
66 permissions and limitations under the License.
67 """
68
69 def __init__(self, loop=None):
70 self._watchers = {}
71 self._timeout = None
72 self.loop = loop or gevent.get_hub().loop
73
74 # Setup curl's multi instance.
75 self._curl_multi = pycurl.CurlMulti()
76 self.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)
77 self.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)
78
79 def __getattr__(self, item):
80 """
81 The pycurl.CurlMulti class is final and we cannot subclass it.
82 Therefore we are wrapping it and forward everything to it here.
83 """
84 return getattr(self._curl_multi, item)
85
86 def add_handle(self, curl):
87 """
88 Add handle variant that also takes care about the initial invocation of
89 socket action method. This is done by setting an immediate timeout.
90 """
91 result = self._curl_multi.add_handle(curl)
92 self._set_timeout(0)
93 return result
94
95 def _handle_socket(self, event, fd, multi, data):
96 """
97 Called by libcurl when it wants to change the file descriptors it cares
98 about.
99 """
100 event_map = {
101 pycurl.POLL_NONE: core.NONE,
102 pycurl.POLL_IN: core.READ,
103 pycurl.POLL_OUT: core.WRITE,
104 pycurl.POLL_INOUT: core.READ | core.WRITE
105 }
106
107 if event == pycurl.POLL_REMOVE:
108 watcher = self._watchers.pop(fd, None)
109 if watcher is not None:
110 watcher.stop()
111 else:
112 gloop_event = event_map[event]
113 watcher = self._watchers.get(fd)
114 if watcher is None:
115 watcher = self.loop.io(fd, gloop_event)
116 watcher.start(self._handle_events, fd, pass_events=True)
117 self._watchers[fd] = watcher
118 else:
119 if watcher.events != gloop_event:
120 watcher.stop()
121 watcher.events = gloop_event
122 watcher.start(self._handle_events, fd, pass_events=True)
123
124 def _set_timeout(self, msecs):
125 """
126 Called by libcurl to schedule a timeout.
127 """
128 if self._timeout is not None:
129 self._timeout.stop()
130 self._timeout = self.loop.timer(msecs/1000.0)
131 self._timeout.start(self._handle_timeout)
132
133 def _handle_events(self, events, fd):
134 action = 0
135 if events & core.READ:
136 action |= pycurl.CSELECT_IN
137 if events & core.WRITE:
138 action |= pycurl.CSELECT_OUT
139 while True:
140 try:
141 ret, num_handles = self._curl_multi.socket_action(fd, action)
142 except pycurl.error, e:
143 ret = e.args[0]
144 if ret != pycurl.E_CALL_MULTI_PERFORM:
145 break
146 self._finish_pending_requests()
147
148 def _handle_timeout(self):
149 """
150 Called by IOLoop when the requested timeout has passed.
151 """
152 if self._timeout is not None:
153 self._timeout.stop()
154 self._timeout = None
155 while True:
156 try:
157 ret, num_handles = self._curl_multi.socket_action(
158 pycurl.SOCKET_TIMEOUT, 0)
159 except pycurl.error, e:
160 ret = e.args[0]
161 if ret != pycurl.E_CALL_MULTI_PERFORM:
162 break
163 self._finish_pending_requests()
164
165 # In theory, we shouldn't have to do this because curl will call
166 # _set_timeout whenever the timeout changes. However, sometimes after
167 # _handle_timeout we will need to reschedule immediately even though
168 # nothing has changed from curl's perspective. This is because when
169 # socket_action is called with SOCKET_TIMEOUT, libcurl decides
170 # internally which timeouts need to be processed by using a monotonic
171 # clock (where available) while tornado uses python's time.time() to
172 # decide when timeouts have occurred. When those clocks disagree on
173 # elapsed time (as they will whenever there is an NTP adjustment),
174 # tornado might call _handle_timeout before libcurl is ready. After
175 # each timeout, resync the scheduled timeout with libcurl's current
176 # state.
177 new_timeout = self._curl_multi.timeout()
178 if new_timeout >= 0:
179 self._set_timeout(new_timeout)
180
181 def _finish_pending_requests(self):
182 """
183 Process any requests that were completed by the last call to
184 multi.socket_action.
185 """
186 while True:
187 num_q, ok_list, err_list = self._curl_multi.info_read()
188 for curl in ok_list:
189 curl.waiter.switch()
190 for curl, errnum, errmsg in err_list:
191 curl.waiter.throw(Exception('%s %s' % (errnum, errmsg)))
192 if num_q == 0:
193 break
194
195
196 class GeventCurl(object):
197 """
198 Gevent compatible implementation of the pycurl.Curl class. Essentially a
199 wrapper around pycurl.Curl with a customized perform method. It uses the
200 GeventCurlMulti class to implement a blocking API to libcurl's "easy"
201 interface.
202 """
203
204 # Reference to the GeventCurlMulti instance.
205 _multi_instance = None
206
207 def __init__(self):
208 self._curl = pycurl.Curl()
209
210 def __getattr__(self, item):
211 """
212 The pycurl.Curl class is final and we cannot subclass it. Therefore we
213 are wrapping it and forward everything to it here.
214 """
215 return getattr(self._curl, item)
216
217 @property
218 def _multi(self):
219 """
220 Lazy property that returns the GeventCurlMulti instance. The value is
221 cached as a class attribute. Therefore only one instance per process
222 exists.
223 """
224 if GeventCurl._multi_instance is None:
225 GeventCurl._multi_instance = GeventCurlMulti()
226 return GeventCurl._multi_instance
227
228 def perform(self):
229 """
230 This perform method is compatible with gevent because it uses gevent
231 synchronization mechanisms to wait for the request to finish.
232 """
233 waiter = self._curl.waiter = Waiter()
234 try:
235 self._multi.add_handle(self._curl)
236 response = waiter.get()
237 finally:
238 self._multi.remove_handle(self._curl)
239 del self._curl.waiter
240
241 return response
242
243 # Curl is originally imported from pycurl. At this point we override it with
244 # our custom implementation.
245 Curl = GeventCurl
@@ -0,0 +1,132 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 """
23 Model for integrations
24 """
25
26
27 import logging
28 import traceback
29
30 from pylons import tmpl_context as c
31 from pylons.i18n.translation import _, ungettext
32 from sqlalchemy import or_
33 from sqlalchemy.sql.expression import false, true
34 from mako import exceptions
35
36 import rhodecode
37 from rhodecode import events
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.utils import PartialRenderer
41 from rhodecode.model import BaseModel
42 from rhodecode.model.db import Integration, User
43 from rhodecode.model.meta import Session
44 from rhodecode.integrations import integration_type_registry
45 from rhodecode.integrations.types.base import IntegrationTypeBase
46
47 log = logging.getLogger(__name__)
48
49
50 class IntegrationModel(BaseModel):
51
52 cls = Integration
53
54 def __get_integration(self, integration):
55 if isinstance(integration, Integration):
56 return integration
57 elif isinstance(integration, (int, long)):
58 return self.sa.query(Integration).get(integration)
59 else:
60 if integration:
61 raise Exception('integration must be int, long or Instance'
62 ' of Integration got %s' % type(integration))
63
64 def create(self, IntegrationType, enabled, name, settings, repo=None):
65 """ Create an IntegrationType integration """
66 integration = Integration()
67 integration.integration_type = IntegrationType.key
68 integration.settings = {}
69 integration.repo = repo
70 integration.enabled = enabled
71 integration.name = name
72
73 self.sa.add(integration)
74 self.sa.commit()
75 return integration
76
77 def delete(self, integration):
78 try:
79 integration = self.__get_integration(integration)
80 if integration:
81 self.sa.delete(integration)
82 return True
83 except Exception:
84 log.error(traceback.format_exc())
85 raise
86 return False
87
88 def get_integration_handler(self, integration):
89 TypeClass = integration_type_registry.get(integration.integration_type)
90 if not TypeClass:
91 log.error('No class could be found for integration type: {}'.format(
92 integration.integration_type))
93 return None
94
95 return TypeClass(integration.settings)
96
97 def send_event(self, integration, event):
98 """ Send an event to an integration """
99 handler = self.get_integration_handler(integration)
100 if handler:
101 handler.send_event(event)
102
103 def get_integrations(self, repo=None):
104 if repo:
105 return self.sa.query(Integration).filter(
106 Integration.repo_id==repo.repo_id).all()
107
108 # global integrations
109 return self.sa.query(Integration).filter(
110 Integration.repo_id==None).all()
111
112 def get_for_event(self, event, cache=False):
113 """
114 Get integrations that match an event
115 """
116 query = self.sa.query(Integration).filter(Integration.enabled==True)
117
118 if isinstance(event, events.RepoEvent): # global + repo integrations
119 query = query.filter(
120 or_(Integration.repo_id==None,
121 Integration.repo_id==event.repo.repo_id))
122 if cache:
123 query = query.options(FromCache(
124 "sql_cache_short",
125 "get_enabled_repo_integrations_%i" % event.repo.repo_id))
126 else: # only global integrations
127 query = query.filter(Integration.repo_id==None)
128 if cache:
129 query = query.options(FromCache(
130 "sql_cache_short", "get_enabled_global_integrations"))
131
132 return query.all()
@@ -0,0 +1,24 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import colander
22
23 from colander import Invalid # noqa, don't remove this
24
@@ -0,0 +1,89 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import unicodedata
22
23
24
25 def strip_preparer(value):
26 """
27 strips given values using .strip() function
28 """
29
30 if value:
31 value = value.strip()
32 return value
33
34
35 def slugify_preparer(value):
36 """
37 Slugify given value to a safe representation for url/id
38 """
39 from rhodecode.lib.utils import repo_name_slug
40 if value:
41 value = repo_name_slug(value.lower())
42 return value
43
44
45 def non_ascii_strip_preparer(value):
46 """
47 trie to replace non-ascii letters to their ascii representation
48 eg::
49
50 `żołw` converts into `zolw`
51 """
52 if value:
53 value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
54 return value
55
56
57 def unique_list_preparer(value):
58 """
59 Converts an list to a list with only unique values
60 """
61
62 def make_unique(value):
63 seen = []
64 return [c for c in value if
65 not (c in seen or seen.append(c))]
66
67 if isinstance(value, list):
68 ret_val = make_unique(value)
69 elif isinstance(value, set):
70 ret_val = list(value)
71 elif isinstance(value, tuple):
72 ret_val = make_unique(value)
73 elif value is None:
74 ret_val = []
75 else:
76 ret_val = [value]
77
78 return ret_val
79
80
81 def unique_list_from_str_preparer(value):
82 """
83 Converts an list to a list with only unique values
84 """
85 from rhodecode.lib.utils2 import aslist
86
87 if isinstance(value, basestring):
88 value = aslist(value, ',')
89 return unique_list_preparer(value) No newline at end of file
@@ -0,0 +1,25 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 Colander Schema nodes
23 http://docs.pylonsproject.org/projects/colander/en/latest/basics.html#schema-node-objects
24 """
25
@@ -0,0 +1,185 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22
23 import colander
24
25 from rhodecode.translation import _
26 from rhodecode.model.validation_schema import validators, preparers
27
28
29 def nodes_to_sequence(nodes, colander_node=None):
30 """
31 Converts old style dict nodes to new list of dicts
32
33 :param nodes: dict with key beeing name of the file
34
35 """
36 if not isinstance(nodes, dict):
37 msg = 'Nodes needs to be a dict, got {}'.format(type(nodes))
38 raise colander.Invalid(colander_node, msg)
39 out = []
40
41 for key, val in nodes.items():
42 val = (isinstance(val, dict) and val) or {}
43 out.append(dict(
44 filename=key,
45 content=val.get('content'),
46 mimetype=val.get('mimetype')
47 ))
48
49 out = Nodes().deserialize(out)
50 return out
51
52
53 def sequence_to_nodes(nodes, colander_node=None):
54 if not isinstance(nodes, list):
55 msg = 'Nodes needs to be a list, got {}'.format(type(nodes))
56 raise colander.Invalid(colander_node, msg)
57 nodes = Nodes().deserialize(nodes)
58
59 out = {}
60 try:
61 for file_data in nodes:
62 file_data_skip = file_data.copy()
63 # if we got filename_org we use it as a key so we keep old
64 # name as input and rename is-reflected inside the values as
65 # filename and filename_org differences.
66 filename_org = file_data.get('filename_org')
67 filename = filename_org or file_data['filename']
68 out[filename] = {}
69 out[filename].update(file_data_skip)
70
71 except Exception as e:
72 msg = 'Invalid data format org_exc:`{}`'.format(repr(e))
73 raise colander.Invalid(colander_node, msg)
74 return out
75
76
77 @colander.deferred
78 def deferred_lifetime_validator(node, kw):
79 options = kw.get('lifetime_options', [])
80 return colander.All(
81 colander.Range(min=-1, max=60 * 24 * 30 * 12),
82 colander.OneOf([x for x in options]))
83
84
85 def unique_gist_validator(node, value):
86 from rhodecode.model.db import Gist
87 existing = Gist.get_by_access_id(value)
88 if existing:
89 msg = _(u'Gist with name {} already exists').format(value)
90 raise colander.Invalid(node, msg)
91
92
93 def filename_validator(node, value):
94 if value != os.path.basename(value):
95 msg = _(u'Filename {} cannot be inside a directory').format(value)
96 raise colander.Invalid(node, msg)
97
98
99 class NodeSchema(colander.MappingSchema):
100 # if we perform rename this will be org filename
101 filename_org = colander.SchemaNode(
102 colander.String(),
103 preparer=[preparers.strip_preparer,
104 preparers.non_ascii_strip_preparer],
105 validator=filename_validator,
106 missing=None)
107
108 filename = colander.SchemaNode(
109 colander.String(),
110 preparer=[preparers.strip_preparer,
111 preparers.non_ascii_strip_preparer],
112 validator=filename_validator)
113
114 content = colander.SchemaNode(
115 colander.String())
116 mimetype = colander.SchemaNode(
117 colander.String(),
118 missing=None)
119
120
121 class Nodes(colander.SequenceSchema):
122 filenames = NodeSchema()
123
124 def validator(self, node, cstruct):
125 if not isinstance(cstruct, list):
126 return
127
128 found_filenames = []
129 for data in cstruct:
130 filename = data['filename']
131 if filename in found_filenames:
132 msg = _('Duplicated value for filename found: `{}`').format(
133 filename)
134 raise colander.Invalid(node, msg)
135 found_filenames.append(filename)
136
137
138 class GistSchema(colander.MappingSchema):
139 """
140 schema = GistSchema()
141 schema.bind(
142 lifetime_options = [1,2,3]
143 )
144 out = schema.deserialize(dict(
145 nodes=[
146 {'filename': 'x', 'content': 'xxx', },
147 {'filename': 'docs/Z', 'content': 'xxx', 'mimetype': 'x'},
148 ]
149 ))
150 """
151
152 from rhodecode.model.db import Gist
153
154 gistid = colander.SchemaNode(
155 colander.String(),
156 missing=None,
157 preparer=[preparers.strip_preparer,
158 preparers.non_ascii_strip_preparer,
159 preparers.slugify_preparer],
160 validator=colander.All(
161 colander.Length(min=3),
162 unique_gist_validator
163 ))
164
165 description = colander.SchemaNode(
166 colander.String(),
167 missing=u'')
168
169 lifetime = colander.SchemaNode(
170 colander.Integer(),
171 validator=deferred_lifetime_validator)
172
173 gist_acl_level = colander.SchemaNode(
174 colander.String(),
175 validator=colander.OneOf([Gist.ACL_LEVEL_PUBLIC,
176 Gist.ACL_LEVEL_PRIVATE]))
177
178 gist_type = colander.SchemaNode(
179 colander.String(),
180 missing=Gist.ACL_LEVEL_PUBLIC,
181 validator=colander.OneOf([Gist.GIST_PRIVATE, Gist.GIST_PUBLIC]))
182
183 nodes = Nodes()
184
185
@@ -0,0 +1,29 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import colander
23
24
25 from rhodecode.model.validation_schema import validators, preparers, types
26
27
28 class RepoGroupSchema(colander.Schema):
29 group_name = colander.SchemaNode(types.GroupNameType())
@@ -0,0 +1,27 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import colander
22
23 from rhodecode.model.validation_schema import validators, preparers, types
24
25
26 class RepoSchema(colander.Schema):
27 repo_name = colander.SchemaNode(types.GroupNameType())
@@ -0,0 +1,44 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import colander
23
24
25 class SearchParamsSchema(colander.MappingSchema):
26 search_query = colander.SchemaNode(
27 colander.String(),
28 missing='')
29 search_type = colander.SchemaNode(
30 colander.String(),
31 missing='content',
32 validator=colander.OneOf(['content', 'path', 'commit', 'repository']))
33 search_sort = colander.SchemaNode(
34 colander.String(),
35 missing='newfirst',
36 validator=colander.OneOf(
37 ['oldfirst', 'newfirst']))
38 page_limit = colander.SchemaNode(
39 colander.Integer(),
40 missing=10,
41 validator=colander.Range(1, 500))
42 requested_page = colander.SchemaNode(
43 colander.Integer(),
44 missing=1)
@@ -0,0 +1,34 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import colander
22
23
24 class GroupNameType(colander.String):
25 SEPARATOR = '/'
26
27 def deserialize(self, node, cstruct):
28 result = super(GroupNameType, self).deserialize(node, cstruct)
29 return self._replace_extra_slashes(result)
30
31 def _replace_extra_slashes(self, path):
32 path = path.split(self.SEPARATOR)
33 path = [item for item in path if item]
34 return self.SEPARATOR.join(path)
@@ -0,0 +1,19 b''
1 import os
2
3 import ipaddress
4 import colander
5
6 from rhodecode.translation import _
7
8
9 def ip_addr_validator(node, value):
10 try:
11 # this raises an ValueError if address is not IpV4 or IpV6
12 ipaddress.ip_network(value, strict=False)
13 except ValueError:
14 msg = _(u'Please enter a valid IPv4 or IpV6 address')
15 raise colander.Invalid(node, msg)
16
17
18
19
@@ -0,0 +1,44 b''
1 <!DOCTYPE html>
2 <html xmlns="http://www.w3.org/1999/xhtml">
3 <head>
4 <title>Error - 502 Bad Gateway</title>
5 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
6 <meta name="robots" content="index, nofollow"/>
7
8 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
9
10 <style>body { background:#eeeeee; }</style>
11
12 </head>
13
14 <body>
15
16 <div class="wrapper error_page">
17 <div class="main-content">
18 <h1>
19 502 Bad Gateway | <span class="error_message">Backend server is unreachable</span>
20 </h1>
21 <div class="inner-column">
22 <h4>Possible Cause</h4>
23 <ul>
24 <li>The server is beeing restarted.</li>
25 <li>The server is overloaded.</li>
26 <li>The link may be incorrect.</li>
27 </ul>
28 </div>
29 <div class="inner-column">
30 <h4>Support</h4>
31 <p>For support, go to <a href="https://rhodecode.com/help/" target="_blank">Support</a>.
32 It may be useful to include your log file; see the log file locations <a href="https://rhodecode.com/r1/enterprise/docs/admin-system-overview/">here</a>.
33 </p>
34 </div>
35 <div class="inner-column">
36 <h4>Documentation</h4>
37 <p>For more information, see <a href="https://rhodecode.com/r1/enterprise/docs/">docs.rhodecode.com</a>.</p>
38 </div>
39 </div>
40 </div>
41
42 </body>
43
44 </html>
@@ -0,0 +1,91 b''
1 .deform {
2
3 * {
4 box-sizing: border-box;
5 }
6
7 .required:after {
8 color: #e32;
9 content: '*';
10 display:inline;
11 }
12
13 .control-label {
14 width: 200px;
15 float: left;
16 }
17 .control-inputs {
18 width: 400px;
19 float: left;
20 }
21 .form-group .radio, .form-group .checkbox {
22 position: relative;
23 display: block;
24 /* margin-bottom: 10px; */
25 }
26
27 .form-group {
28 clear: left;
29 }
30
31 .form-control {
32 width: 100%;
33 }
34
35 .error-block {
36 color: red;
37 }
38
39 .deform-seq-container .control-inputs {
40 width: 100%;
41 }
42
43 .deform-seq-container .deform-seq-item-handle {
44 width: 8.3%;
45 float: left;
46 }
47
48 .deform-seq-container .deform-seq-item-group {
49 width: 91.6%;
50 float: left;
51 }
52
53 .form-control {
54 input {
55 height: 40px;
56 }
57 input[type=checkbox], input[type=radio] {
58 height: auto;
59 }
60 select {
61 height: 40px;
62 }
63 }
64
65 .form-control.select2-container { height: 40px; }
66
67 .deform-two-field-sequence .deform-seq-container .deform-seq-item label {
68 display: none;
69 }
70 .deform-two-field-sequence .deform-seq-container .deform-seq-item:first-child label {
71 display: block;
72 }
73 .deform-two-field-sequence .deform-seq-container .deform-seq-item .panel-heading {
74 display: none;
75 }
76 .deform-two-field-sequence .deform-seq-container .deform-seq-item.form-group {
77 background: red;
78 }
79 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group .form-group {
80 width: 45%; padding: 0 2px; float: left; clear: none;
81 }
82 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel {
83 padding: 0;
84 margin: 5px 0;
85 border: none;
86 }
87 .deform-two-field-sequence .deform-seq-container .deform-seq-item .deform-seq-item-group > .panel > .panel-body {
88 padding: 0;
89 }
90
91 }
@@ -0,0 +1,268 b''
1 // Mix-ins
2 .borderRadius(@radius) {
3 -moz-border-radius: @radius;
4 -webkit-border-radius: @radius;
5 border-radius: @radius;
6 }
7
8 .boxShadow(@boxShadow) {
9 -moz-box-shadow: @boxShadow;
10 -webkit-box-shadow: @boxShadow;
11 box-shadow: @boxShadow;
12 }
13
14 .opacity(@opacity) {
15 @opacityPercent: @opacity * 100;
16 opacity: @opacity;
17 -ms-filter: ~"progid:DXImageTransform.Microsoft.Alpha(Opacity=@{opacityPercent})";
18 filter: ~"alpha(opacity=@{opacityPercent})";
19 }
20
21 .wordWrap(@wordWrap: break-word) {
22 -ms-word-wrap: @wordWrap;
23 word-wrap: @wordWrap;
24 }
25
26 // Variables
27 @black: #000000;
28 @grey: #999999;
29 @light-grey: #CCCCCC;
30 @white: #FFFFFF;
31 @near-black: #030303;
32 @green: #51A351;
33 @red: #BD362F;
34 @blue: #2F96B4;
35 @orange: #F89406;
36 @default-container-opacity: .8;
37
38 // Styles
39 .toast-title {
40 font-weight: bold;
41 }
42
43 .toast-message {
44 .wordWrap();
45
46 a,
47 label {
48 color: @near-black;
49 }
50
51 a:hover {
52 color: @light-grey;
53 text-decoration: none;
54 }
55 }
56
57 .toast-close-button {
58 position: relative;
59 right: -0.3em;
60 top: -0.3em;
61 float: right;
62 font-size: 20px;
63 font-weight: bold;
64 color: @black;
65 -webkit-text-shadow: 0 1px 0 rgba(255,255,255,1);
66 text-shadow: 0 1px 0 rgba(255,255,255,1);
67 .opacity(0.8);
68
69 &:hover,
70 &:focus {
71 color: @black;
72 text-decoration: none;
73 cursor: pointer;
74 .opacity(0.4);
75 }
76 }
77
78 /*Additional properties for button version
79 iOS requires the button element instead of an anchor tag.
80 If you want the anchor version, it requires `href="#"`.*/
81 button.toast-close-button {
82 padding: 0;
83 cursor: pointer;
84 background: transparent;
85 border: 0;
86 -webkit-appearance: none;
87 }
88
89 //#endregion
90
91 .toast-top-center {
92 top: 0;
93 right: 0;
94 width: 100%;
95 }
96
97 .toast-bottom-center {
98 bottom: 0;
99 right: 0;
100 width: 100%;
101 }
102
103 .toast-top-full-width {
104 top: 0;
105 right: 0;
106 width: 100%;
107 }
108
109 .toast-bottom-full-width {
110 bottom: 0;
111 right: 0;
112 width: 100%;
113 }
114
115 .toast-top-left {
116 top: 12px;
117 left: 12px;
118 }
119
120 .toast-top-right {
121 top: 12px;
122 right: 12px;
123 }
124
125 .toast-bottom-right {
126 right: 12px;
127 bottom: 12px;
128 }
129
130 .toast-bottom-left {
131 bottom: 12px;
132 left: 12px;
133 }
134
135 #toast-container {
136 position: fixed;
137 z-index: 999999;
138 // The container should not be clickable.
139 pointer-events: none;
140 * {
141 -moz-box-sizing: border-box;
142 -webkit-box-sizing: border-box;
143 box-sizing: border-box;
144 }
145
146 > div {
147 position: relative;
148 // The toast itself should be clickable.
149 pointer-events: auto;
150 overflow: hidden;
151 margin: 0 0 6px;
152 padding: 15px;
153 width: 300px;
154 .borderRadius(1px 1px 1px 1px);
155 background-position: 15px center;
156 background-repeat: no-repeat;
157 color: @near-black;
158 .opacity(@default-container-opacity);
159 }
160
161 > :hover {
162 .opacity(1);
163 cursor: pointer;
164 }
165
166 > .toast-info {
167 //background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
168 }
169
170 > .toast-error {
171 //background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
172 }
173
174 > .toast-success {
175 //background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
176 }
177
178 > .toast-warning {
179 //background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
180 }
181
182 /*overrides*/
183 &.toast-top-center > div,
184 &.toast-bottom-center > div {
185 width: 400px;
186 margin-left: auto;
187 margin-right: auto;
188 }
189
190 &.toast-top-full-width > div,
191 &.toast-bottom-full-width > div {
192 width: 96%;
193 margin-left: auto;
194 margin-right: auto;
195 }
196 }
197
198 .toast {
199 border-color: @near-black;
200 border-style: solid;
201 border-width: 2px 2px 2px 25px;
202 background-color: @white;
203 }
204
205 .toast-success {
206 border-color: @green;
207 }
208
209 .toast-error {
210 border-color: @red;
211 }
212
213 .toast-info {
214 border-color: @blue;
215 }
216
217 .toast-warning {
218 border-color: @orange;
219 }
220
221 .toast-progress {
222 position: absolute;
223 left: 0;
224 bottom: 0;
225 height: 4px;
226 background-color: @black;
227 .opacity(0.4);
228 }
229
230 /*Responsive Design*/
231
232 @media all and (max-width: 240px) {
233 #toast-container {
234
235 > div {
236 padding: 8px;
237 width: 11em;
238 }
239
240 & .toast-close-button {
241 right: -0.2em;
242 top: -0.2em;
243 }
244 }
245 }
246
247 @media all and (min-width: 241px) and (max-width: 480px) {
248 #toast-container {
249 > div {
250 padding: 8px;
251 width: 18em;
252 }
253
254 & .toast-close-button {
255 right: -0.2em;
256 top: -0.2em;
257 }
258 }
259 }
260
261 @media all and (min-width: 481px) and (max-width: 768px) {
262 #toast-container {
263 > div {
264 padding: 15px;
265 width: 25em;
266 }
267 }
268 }
@@ -0,0 +1,68 b''
1 // AUTO GENERATED FILE FOR Babel JS-GETTEXT EXTRACTORS, DO NOT CHANGE
2 _gettext('Add another comment');
3 _gettext('Comment text will be set automatically based on currently selected status ({0}) ...');
4 _gettext('Follow');
5 _gettext('Loading ...');
6 _gettext('Loading failed');
7 _gettext('Loading more results...');
8 _gettext('No bookmarks available yet.');
9 _gettext('No branches available yet.');
10 _gettext('No gists available yet.');
11 _gettext('No matches found');
12 _gettext('No matching files');
13 _gettext('No pull requests available yet.');
14 _gettext('No repositories available yet.');
15 _gettext('No repository groups available yet.');
16 _gettext('No results');
17 _gettext('No tags available yet.');
18 _gettext('No user groups available yet.');
19 _gettext('No users available yet.');
20 _gettext('One result is available, press enter to select it.');
21 _gettext('Open new pull request');
22 _gettext('Open new pull request for selected commit');
23 _gettext('Please delete {0} character');
24 _gettext('Please delete {0} characters');
25 _gettext('Please enter {0} or more character');
26 _gettext('Please enter {0} or more characters');
27 _gettext('Searching...');
28 _gettext('Selection link');
29 _gettext('Set status to Approved');
30 _gettext('Set status to Rejected');
31 _gettext('Show more');
32 _gettext('Show selected commit __S');
33 _gettext('Show selected commits __S ... __E');
34 _gettext('Start following this repository');
35 _gettext('Status Review');
36 _gettext('Stop following this repository');
37 _gettext('Submitting...');
38 _gettext('Unfollow');
39 _gettext('Updating...');
40 _gettext('You can only select {0} item');
41 _gettext('You can only select {0} items');
42 _gettext('disabled');
43 _gettext('enabled');
44 _gettext('file');
45 _gettext('files');
46 _gettext('in {0}');
47 _gettext('in {0} and {1}');
48 _gettext('in {0}, {1}');
49 _gettext('just now');
50 _gettext('specify commit');
51 _gettext('truncated result');
52 _gettext('truncated results');
53 _gettext('{0} active out of {1} users');
54 _gettext('{0} ago');
55 _gettext('{0} and {1}');
56 _gettext('{0} and {1} ago');
57 _gettext('{0} day');
58 _gettext('{0} days');
59 _gettext('{0} hour');
60 _gettext('{0} hours');
61 _gettext('{0} min');
62 _gettext('{0} month');
63 _gettext('{0} months');
64 _gettext('{0} results are available, use up and down arrow keys to navigate.');
65 _gettext('{0} sec');
66 _gettext('{0} year');
67 _gettext('{0} years');
68 _gettext('{0}, {1} ago');
@@ -0,0 +1,194 b''
1 /*
2 * Register a top-level callback to the deform.load() function
3 * this will be called when the DOM has finished loading. No need
4 * to include the call at the end of the page.
5 */
6
7 $(document).ready(function(){
8 deform.load();
9 });
10
11
12 var deform_loaded = false;
13
14 var deform = {
15 callbacks: [],
16
17 addCallback: function (oid, callback) {
18 deform.callbacks.push([oid, callback]);
19 },
20
21 clearCallbacks: function () {
22 deform.callbacks = [];
23 },
24
25 load: function() {
26 $(function() {
27 if (!deform_loaded) {
28 deform.processCallbacks();
29 deform.focusFirstInput();
30 deform_loaded = true;
31 }});
32 },
33
34
35 processCallbacks: function () {
36 $(deform.callbacks).each(function(num, item) {
37 var oid = item[0];
38 var callback = item[1];
39 callback(oid);
40 }
41 );
42 deform.clearCallbacks();
43 },
44
45 addSequenceItem: function (protonode, before) {
46 // - Clone the prototype node and add it before the "before" node.
47 // Also ensure any callbacks are run for the widget.
48
49 // In order to avoid breaking accessibility:
50 //
51 // - Find each tag within the prototype node with an id
52 // that has the string ``deformField(\d+)`` within it, and modify
53 // its id to have a random component.
54 // - For each label referencing an change id, change the label's
55 // for attribute to the new id.
56
57 var fieldmatch = /deformField(\d+)/;
58 var namematch = /(.+)?-[#]{3}/;
59 var code = protonode.attr('prototype');
60 var html = decodeURIComponent(code);
61 var $htmlnode = $(html);
62 var $idnodes = $htmlnode.find('[id]');
63 var $namednodes = $htmlnode.find('[name]');
64 var genid = deform.randomString(6);
65 var idmap = {};
66
67 // replace ids containing ``deformField`` and associated label for=
68 // items which point at them
69
70 $idnodes.each(function(idx, node) {
71 var $node = $(node);
72 var oldid = $node.attr('id');
73 var newid = oldid.replace(fieldmatch, "deformField$1-" + genid);
74 $node.attr('id', newid);
75 idmap[oldid] = newid;
76 var labelselector = 'label[for=' + oldid + ']';
77 var $fornodes = $htmlnode.find(labelselector);
78 $fornodes.attr('for', newid);
79 });
80
81 // replace names a containing ```deformField`` like we do for ids
82
83 $namednodes.each(function(idx, node) {
84 var $node = $(node);
85 var oldname = $node.attr('name');
86 var newname = oldname.replace(fieldmatch, "deformField$1-" + genid);
87 $node.attr('name', newname);
88 });
89
90 $htmlnode.insertBefore(before);
91
92 $(deform.callbacks).each(function(num, item) {
93 var oid = item[0];
94 var callback = item[1];
95 var newid = idmap[oid];
96 if (newid) {
97 callback(newid);
98 }
99 });
100
101 deform.clearCallbacks();
102 var old_len = parseInt(before.attr('now_len')||'0', 10);
103 before.attr('now_len', old_len + 1);
104 // we added something to the dom, trigger a change event
105 var e = jQuery.Event("change");
106 $('#deform').trigger(e);
107 },
108
109 appendSequenceItem: function(node) {
110 var $oid_node = $(node).closest('.deform-seq');
111 var $proto_node = $oid_node.find('.deform-proto').first();
112 var $before_node = $oid_node.find('.deform-insert-before').last();
113 var min_len = parseInt($before_node.attr('min_len')||'0', 10);
114 var max_len = parseInt($before_node.attr('max_len')||'9999', 10);
115 var now_len = parseInt($before_node.attr('now_len')||'0', 10);
116 var orderable = parseInt($before_node.attr('orderable')||'0', 10);
117
118 if (now_len < max_len) {
119 deform.addSequenceItem($proto_node, $before_node);
120 deform.processSequenceButtons($oid_node, min_len, max_len,
121 now_len + 1, orderable);
122 }
123 return false;
124 },
125
126 removeSequenceItem: function(clicked) {
127 var $item_node = $(clicked).closest('.deform-seq-item');
128 var $oid_node = $item_node.closest('.deform-seq');
129 var $before_node = $oid_node.find('.deform-insert-before').last();
130 var min_len = parseInt($before_node.attr('min_len')||'0', 10);
131 var max_len = parseInt($before_node.attr('max_len')||'9999', 10);
132 var now_len = parseInt($before_node.attr('now_len')||'0', 10);
133 var orderable = parseInt($before_node.attr('orderable')||'0', 10);
134 if (now_len > min_len) {
135 $before_node.attr('now_len', now_len - 1);
136 $item_node.remove();
137 deform.processSequenceButtons($oid_node, min_len, max_len,
138 now_len-1, orderable);
139 }
140 // we removed something from the dom, trigger a change event
141 var e = jQuery.Event("change");
142 $('#deform').trigger(e);
143 return false;
144 },
145
146 processSequenceButtons: function(oid_node, min_len, max_len, now_len,
147 orderable) {
148 orderable = !!orderable; // convert to bool
149 var has_multiple = now_len > 1;
150 var $ul = oid_node.find('.deform-seq-container').not(oid_node.find('.deform-seq-container .deform-seq-container'));
151 var $lis = $ul.find('.deform-seq-item').not($ul.find('.deform-seq-container .deform-seq-item'));
152 var show_closebutton = now_len > min_len;
153 var show_addbutton = now_len < max_len;
154 $lis.find('.deform-close-button').not($lis.find('.deform-seq-container .deform-close-button')).toggle(show_closebutton);
155 oid_node.find('.deform-seq-add').not(oid_node.find('.deform-seq-container .deform-seq-add')).toggle(show_addbutton);
156 $lis.find('.deform-order-button').not($lis.find('.deform-seq-container .deform-order-button')).toggle(orderable && has_multiple);
157 },
158
159 focusFirstInput: function (el) {
160 el = el || document.body;
161 var input = $(el).find(':input')
162 .filter('[id ^= deformField]')
163 .filter('[type != hidden]')
164 .first();
165 if (input) {
166 var raw = input.get(0);
167 if (raw) {
168 if (raw.type === 'text' || raw.type === 'file' ||
169 raw.type == 'password' || raw.type == 'text' ||
170 raw.type == 'textarea') {
171 if (!input.hasClass("hasDatepicker")) {
172 input.focus();
173 }
174 }
175 }
176 }
177 },
178
179 randomString: function (length) {
180 var chr='0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz';
181 chr = chr.split('');
182
183 if (! length) {
184 length = Math.floor(Math.random() * chr.length);
185 }
186
187 var str = '';
188 for (var i = 0; i < length; i++) {
189 str += chr[Math.floor(Math.random() * chr.length)];
190 }
191 return str;
192 }
193
194 };
@@ -0,0 +1,435 b''
1 /*
2 * Toastr
3 * Copyright 2012-2015
4 * Authors: John Papa, Hans Fjällemark, and Tim Ferrell.
5 * All Rights Reserved.
6 * Use, reproduction, distribution, and modification of this code is subject to the terms and
7 * conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
8 *
9 * ARIA Support: Greta Krafsig
10 *
11 * Project: https://github.com/CodeSeven/toastr
12 */
13 /* global define */
14 (function (define) {
15 define(['jquery'], function ($) {
16 return (function () {
17 var $container;
18 var listener;
19 var toastId = 0;
20 var toastType = {
21 error: 'error',
22 info: 'info',
23 success: 'success',
24 warning: 'warning'
25 };
26
27 var toastr = {
28 clear: clear,
29 remove: remove,
30 error: error,
31 getContainer: getContainer,
32 info: info,
33 options: {},
34 subscribe: subscribe,
35 success: success,
36 version: '2.1.2',
37 warning: warning
38 };
39
40 var previousToast;
41
42 return toastr;
43
44 ////////////////
45
46 function error(message, title, optionsOverride) {
47 return notify({
48 type: toastType.error,
49 iconClass: getOptions().iconClasses.error,
50 message: message,
51 optionsOverride: optionsOverride,
52 title: title
53 });
54 }
55
56 function getContainer(options, create) {
57 if (!options) { options = getOptions(); }
58 $container = $('#' + options.containerId);
59 if ($container.length) {
60 return $container;
61 }
62 if (create) {
63 $container = createContainer(options);
64 }
65 return $container;
66 }
67
68 function info(message, title, optionsOverride) {
69 return notify({
70 type: toastType.info,
71 iconClass: getOptions().iconClasses.info,
72 message: message,
73 optionsOverride: optionsOverride,
74 title: title
75 });
76 }
77
78 function subscribe(callback) {
79 listener = callback;
80 }
81
82 function success(message, title, optionsOverride) {
83 return notify({
84 type: toastType.success,
85 iconClass: getOptions().iconClasses.success,
86 message: message,
87 optionsOverride: optionsOverride,
88 title: title
89 });
90 }
91
92 function warning(message, title, optionsOverride) {
93 return notify({
94 type: toastType.warning,
95 iconClass: getOptions().iconClasses.warning,
96 message: message,
97 optionsOverride: optionsOverride,
98 title: title
99 });
100 }
101
102 function clear($toastElement, clearOptions) {
103 var options = getOptions();
104 if (!$container) { getContainer(options); }
105 if (!clearToast($toastElement, options, clearOptions)) {
106 clearContainer(options);
107 }
108 }
109
110 function remove($toastElement) {
111 var options = getOptions();
112 if (!$container) { getContainer(options); }
113 if ($toastElement && $(':focus', $toastElement).length === 0) {
114 removeToast($toastElement);
115 return;
116 }
117 if ($container.children().length) {
118 $container.remove();
119 }
120 }
121
122 // internal functions
123
124 function clearContainer (options) {
125 var toastsToClear = $container.children();
126 for (var i = toastsToClear.length - 1; i >= 0; i--) {
127 clearToast($(toastsToClear[i]), options);
128 }
129 }
130
131 function clearToast ($toastElement, options, clearOptions) {
132 var force = clearOptions && clearOptions.force ? clearOptions.force : false;
133 if ($toastElement && (force || $(':focus', $toastElement).length === 0)) {
134 $toastElement[options.hideMethod]({
135 duration: options.hideDuration,
136 easing: options.hideEasing,
137 complete: function () { removeToast($toastElement); }
138 });
139 return true;
140 }
141 return false;
142 }
143
144 function createContainer(options) {
145 $container = $('<div/>')
146 .attr('id', options.containerId)
147 .addClass(options.positionClass)
148 .attr('aria-live', 'polite')
149 .attr('role', 'alert');
150
151 $container.appendTo($(options.target));
152 return $container;
153 }
154
155 function getDefaults() {
156 return {
157 tapToDismiss: true,
158 toastClass: 'toast',
159 containerId: 'toast-container',
160 debug: false,
161
162 showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
163 showDuration: 300,
164 showEasing: 'swing', //swing and linear are built into jQuery
165 onShown: undefined,
166 hideMethod: 'fadeOut',
167 hideDuration: 1000,
168 hideEasing: 'swing',
169 onHidden: undefined,
170 closeMethod: false,
171 closeDuration: false,
172 closeEasing: false,
173
174 extendedTimeOut: 1000,
175 iconClasses: {
176 error: 'toast-error',
177 info: 'toast-info',
178 success: 'toast-success',
179 warning: 'toast-warning'
180 },
181 iconClass: 'toast-info',
182 positionClass: 'toast-top-right',
183 timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky
184 titleClass: 'toast-title',
185 messageClass: 'toast-message',
186 escapeHtml: false,
187 target: 'body',
188 closeHtml: '<button type="button">&times;</button>',
189 newestOnTop: true,
190 preventDuplicates: false,
191 progressBar: false
192 };
193 }
194
195 function publish(args) {
196 if (!listener) { return; }
197 listener(args);
198 }
199
200 function notify(map) {
201 var options = getOptions();
202 var iconClass = map.iconClass || options.iconClass;
203
204 if (typeof (map.optionsOverride) !== 'undefined') {
205 options = $.extend(options, map.optionsOverride);
206 iconClass = map.optionsOverride.iconClass || iconClass;
207 }
208
209 if (shouldExit(options, map)) { return; }
210
211 toastId++;
212
213 $container = getContainer(options, true);
214
215 var intervalId = null;
216 var $toastElement = $('<div/>');
217 var $titleElement = $('<div/>');
218 var $messageElement = $('<div/>');
219 var $progressElement = $('<div/>');
220 var $closeElement = $(options.closeHtml);
221 var progressBar = {
222 intervalId: null,
223 hideEta: null,
224 maxHideTime: null
225 };
226 var response = {
227 toastId: toastId,
228 state: 'visible',
229 startTime: new Date(),
230 options: options,
231 map: map
232 };
233
234 personalizeToast();
235
236 displayToast();
237
238 handleEvents();
239
240 publish(response);
241
242 if (options.debug && console) {
243 console.log(response);
244 }
245
246 return $toastElement;
247
248 function escapeHtml(source) {
249 if (source == null)
250 source = "";
251
252 return new String(source)
253 .replace(/&/g, '&amp;')
254 .replace(/"/g, '&quot;')
255 .replace(/'/g, '&#39;')
256 .replace(/</g, '&lt;')
257 .replace(/>/g, '&gt;');
258 }
259
260 function personalizeToast() {
261 setIcon();
262 setTitle();
263 setMessage();
264 setCloseButton();
265 setProgressBar();
266 setSequence();
267 }
268
269 function handleEvents() {
270 $toastElement.hover(stickAround, delayedHideToast);
271 if (!options.onclick && options.tapToDismiss) {
272 $toastElement.click(hideToast);
273 }
274
275 if (options.closeButton && $closeElement) {
276 $closeElement.click(function (event) {
277 if (event.stopPropagation) {
278 event.stopPropagation();
279 } else if (event.cancelBubble !== undefined && event.cancelBubble !== true) {
280 event.cancelBubble = true;
281 }
282 hideToast(true);
283 });
284 }
285
286 if (options.onclick) {
287 $toastElement.click(function (event) {
288 options.onclick(event);
289 hideToast();
290 });
291 }
292 }
293
294 function displayToast() {
295 $toastElement.hide();
296
297 $toastElement[options.showMethod](
298 {duration: options.showDuration, easing: options.showEasing, complete: options.onShown}
299 );
300
301 if (options.timeOut > 0) {
302 intervalId = setTimeout(hideToast, options.timeOut);
303 progressBar.maxHideTime = parseFloat(options.timeOut);
304 progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
305 if (options.progressBar) {
306 progressBar.intervalId = setInterval(updateProgress, 10);
307 }
308 }
309 }
310
311 function setIcon() {
312 if (map.iconClass) {
313 $toastElement.addClass(options.toastClass).addClass(iconClass);
314 }
315 }
316
317 function setSequence() {
318 if (options.newestOnTop) {
319 $container.prepend($toastElement);
320 } else {
321 $container.append($toastElement);
322 }
323 }
324
325 function setTitle() {
326 if (map.title) {
327 $titleElement.append(!options.escapeHtml ? map.title : escapeHtml(map.title)).addClass(options.titleClass);
328 $toastElement.append($titleElement);
329 }
330 }
331
332 function setMessage() {
333 if (map.message) {
334 $messageElement.append(!options.escapeHtml ? map.message : escapeHtml(map.message)).addClass(options.messageClass);
335 $toastElement.append($messageElement);
336 }
337 }
338
339 function setCloseButton() {
340 if (options.closeButton) {
341 $closeElement.addClass('toast-close-button').attr('role', 'button');
342 $toastElement.prepend($closeElement);
343 }
344 }
345
346 function setProgressBar() {
347 if (options.progressBar) {
348 $progressElement.addClass('toast-progress');
349 $toastElement.prepend($progressElement);
350 }
351 }
352
353 function shouldExit(options, map) {
354 if (options.preventDuplicates) {
355 if (map.message === previousToast) {
356 return true;
357 } else {
358 previousToast = map.message;
359 }
360 }
361 return false;
362 }
363
364 function hideToast(override) {
365 var method = override && options.closeMethod !== false ? options.closeMethod : options.hideMethod;
366 var duration = override && options.closeDuration !== false ?
367 options.closeDuration : options.hideDuration;
368 var easing = override && options.closeEasing !== false ? options.closeEasing : options.hideEasing;
369 if ($(':focus', $toastElement).length && !override) {
370 return;
371 }
372 clearTimeout(progressBar.intervalId);
373 return $toastElement[method]({
374 duration: duration,
375 easing: easing,
376 complete: function () {
377 removeToast($toastElement);
378 if (options.onHidden && response.state !== 'hidden') {
379 options.onHidden();
380 }
381 response.state = 'hidden';
382 response.endTime = new Date();
383 publish(response);
384 }
385 });
386 }
387
388 function delayedHideToast() {
389 if (options.timeOut > 0 || options.extendedTimeOut > 0) {
390 intervalId = setTimeout(hideToast, options.extendedTimeOut);
391 progressBar.maxHideTime = parseFloat(options.extendedTimeOut);
392 progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
393 }
394 }
395
396 function stickAround() {
397 clearTimeout(intervalId);
398 progressBar.hideEta = 0;
399 $toastElement.stop(true, true)[options.showMethod](
400 {duration: options.showDuration, easing: options.showEasing}
401 );
402 }
403
404 function updateProgress() {
405 var percentage = ((progressBar.hideEta - (new Date().getTime())) / progressBar.maxHideTime) * 100;
406 $progressElement.width(percentage + '%');
407 }
408 }
409
410 function getOptions() {
411 return $.extend({}, getDefaults(), toastr.options);
412 }
413
414 function removeToast($toastElement) {
415 if (!$container) { $container = getContainer(); }
416 if ($toastElement.is(':visible')) {
417 return;
418 }
419 $toastElement.remove();
420 $toastElement = null;
421 if ($container.children().length === 0) {
422 $container.remove();
423 previousToast = undefined;
424 }
425 }
426
427 })();
428 });
429 }(typeof define === 'function' && define.amd ? define : function (deps, factory) {
430 if (typeof module !== 'undefined' && module.exports) { //Node
431 module.exports = factory(require('jquery'));
432 } else {
433 window.toastr = factory(window.jQuery);
434 }
435 }));
@@ -0,0 +1,219 b''
1 "use strict";
2 /** leak object to top level scope **/
3 var ccLog = undefined;
4 // global code-mirror logger;, to enable run
5 // Logger.get('ConnectionController').setLevel(Logger.DEBUG)
6 ccLog = Logger.get('ConnectionController');
7 ccLog.setLevel(Logger.OFF);
8
9 var ConnectionController;
10 var connCtrlr;
11 var registerViewChannels;
12
13 (function () {
14 ConnectionController = function (webappUrl, serverUrl, urls) {
15 var self = this;
16
17 var channels = ['broadcast'];
18 this.state = {
19 open: false,
20 webappUrl: webappUrl,
21 serverUrl: serverUrl,
22 connId: null,
23 socket: null,
24 channels: channels,
25 heartbeat: null,
26 channelsInfo: {},
27 urls: urls
28 };
29 this.channelNameParsers = [];
30
31 this.addChannelNameParser = function (fn) {
32 if (this.channelNameParsers.indexOf(fn) === -1) {
33 this.channelNameParsers.push(fn);
34 }
35 };
36
37 this.listen = function () {
38 if (window.WebSocket) {
39 ccLog.debug('attempting to create socket');
40 var socket_url = self.state.serverUrl + "/ws?conn_id=" + self.state.connId;
41 var socket_conf = {
42 url: socket_url,
43 handleAs: 'json',
44 headers: {
45 "Accept": "application/json",
46 "Content-Type": "application/json"
47 }
48 };
49 self.state.socket = new WebSocket(socket_conf.url);
50
51 self.state.socket.onopen = function (event) {
52 ccLog.debug('open event', event);
53 if (self.state.heartbeat === null) {
54 self.state.heartbeat = setInterval(function () {
55 if (self.state.socket.readyState === WebSocket.OPEN) {
56 self.state.socket.send('heartbeat');
57 }
58 }, 10000)
59 }
60 };
61 self.state.socket.onmessage = function (event) {
62 var data = $.parseJSON(event.data);
63 for (var i = 0; i < data.length; i++) {
64 if (data[i].message.topic) {
65 ccLog.debug('publishing',
66 data[i].message.topic, data[i]);
67 $.Topic(data[i].message.topic).publish(data[i])
68 }
69 else {
70 ccLog.warn('unhandled message', data);
71 }
72 }
73 };
74 self.state.socket.onclose = function (event) {
75 ccLog.debug('closed event', event);
76 setTimeout(function () {
77 self.connect(true);
78 }, 5000);
79 };
80
81 self.state.socket.onerror = function (event) {
82 ccLog.debug('error event', event);
83 };
84 }
85 else {
86 ccLog.debug('attempting to create long polling connection');
87 var poolUrl = self.state.serverUrl + "/listen?conn_id=" + self.state.connId;
88 self.state.socket = $.ajax({
89 url: poolUrl
90 }).done(function (data) {
91 ccLog.debug('data', data);
92 var data = $.parseJSON(data);
93 for (var i = 0; i < data.length; i++) {
94 if (data[i].message.topic) {
95 ccLog.info('publishing',
96 data[i].message.topic, data[i]);
97 $.Topic(data[i].message.topic).publish(data[i])
98 }
99 else {
100 ccLog.warn('unhandled message', data);
101 }
102 }
103 self.listen();
104 }).fail(function () {
105 ccLog.debug('longpoll error');
106 setTimeout(function () {
107 self.connect(true);
108 }, 5000);
109 });
110 }
111
112 };
113
114 this.connect = function (create_new_socket) {
115 var connReq = {'channels': self.state.channels};
116 ccLog.debug('try obtaining connection info', connReq);
117 $.ajax({
118 url: self.state.urls.connect,
119 type: "POST",
120 contentType: "application/json",
121 data: JSON.stringify(connReq),
122 dataType: "json"
123 }).done(function (data) {
124 ccLog.debug('Got connection:', data.conn_id);
125 self.state.channels = data.channels;
126 self.state.channelsInfo = data.channels_info;
127 self.state.connId = data.conn_id;
128 if (create_new_socket) {
129 self.listen();
130 }
131 self.update();
132 }).fail(function () {
133 setTimeout(function () {
134 self.connect(create_new_socket);
135 }, 5000);
136 });
137 self.update();
138 };
139
140 this.subscribeToChannels = function (channels) {
141 var new_channels = [];
142 for (var i = 0; i < channels.length; i++) {
143 var channel = channels[i];
144 if (self.state.channels.indexOf(channel)) {
145 self.state.channels.push(channel);
146 new_channels.push(channel)
147 }
148 }
149 /**
150 * only execute the request if socket is present because subscribe
151 * can actually add channels before initial app connection
152 **/
153 if (new_channels && self.state.socket !== null) {
154 var connReq = {
155 'channels': self.state.channels,
156 'conn_id': self.state.connId
157 };
158 $.ajax({
159 url: self.state.urls.subscribe,
160 type: "POST",
161 contentType: "application/json",
162 data: JSON.stringify(connReq),
163 dataType: "json"
164 }).done(function (data) {
165 self.state.channels = data.channels;
166 self.state.channelsInfo = data.channels_info;
167 self.update();
168 });
169 }
170 self.update();
171 };
172
173 this.update = function () {
174 for (var key in this.state.channelsInfo) {
175 if (this.state.channelsInfo.hasOwnProperty(key)) {
176 // update channels with latest info
177 $.Topic('/connection_controller/channel_update').publish(
178 {channel: key, state: this.state.channelsInfo[key]});
179 }
180 }
181 /**
182 * checks current channel list in state and if channel is not present
183 * converts them into executable "commands" and pushes them on topics
184 */
185 for (var i = 0; i < this.state.channels.length; i++) {
186 var channel = this.state.channels[i];
187 for (var j = 0; j < this.channelNameParsers.length; j++) {
188 this.channelNameParsers[j](channel);
189 }
190 }
191 };
192
193 this.run = function () {
194 this.connect(true);
195 };
196
197 $.Topic('/connection_controller/subscribe').subscribe(
198 self.subscribeToChannels);
199 };
200
201 $.Topic('/plugins/__REGISTER__').subscribe(function (data) {
202 // enable chat controller
203 if (window.CHANNELSTREAM_SETTINGS && window.CHANNELSTREAM_SETTINGS.enabled) {
204 $(document).ready(function () {
205 connCtrlr.run();
206 });
207 }
208 });
209
210 registerViewChannels = function (){
211 // subscribe to PR repo channel for PR's'
212 if (templateContext.pull_request_data.pull_request_id) {
213 var channelName = '/repo$' + templateContext.repo_name + '$/pr/' +
214 String(templateContext.pull_request_data.pull_request_id);
215 connCtrlr.state.channels.push(channelName);
216 }
217 }
218
219 })();
@@ -0,0 +1,60 b''
1 "use strict";
2
3 toastr.options = {
4 "closeButton": true,
5 "debug": false,
6 "newestOnTop": false,
7 "progressBar": false,
8 "positionClass": "toast-top-center",
9 "preventDuplicates": false,
10 "onclick": null,
11 "showDuration": "300",
12 "hideDuration": "300",
13 "timeOut": "0",
14 "extendedTimeOut": "0",
15 "showEasing": "swing",
16 "hideEasing": "linear",
17 "showMethod": "fadeIn",
18 "hideMethod": "fadeOut"
19 };
20
21 function notifySystem(data) {
22 var notification = new Notification(data.message.level + ': ' + data.message.message);
23 };
24
25 function notifyToaster(data){
26 toastr[data.message.level](data.message.message);
27 }
28
29 function handleNotifications(data) {
30
31 if (!templateContext.rhodecode_user.notification_status && !data.testMessage) {
32 // do not act if notifications are disabled
33 return
34 }
35 // use only js notifications for now
36 var onlyJS = true;
37 if (!("Notification" in window) || onlyJS) {
38 // use legacy notificartion
39 notifyToaster(data);
40 }
41 else {
42 // Let's check whether notification permissions have already been granted
43 if (Notification.permission === "granted") {
44 notifySystem(data);
45 }
46 // Otherwise, we need to ask the user for permission
47 else if (Notification.permission !== 'denied') {
48 Notification.requestPermission(function (permission) {
49 if (permission === "granted") {
50 notifySystem(data);
51 }
52 });
53 }
54 else{
55 notifyToaster(data);
56 }
57 }
58 };
59
60 $.Topic('/notifications').subscribe(handleNotifications);
@@ -0,0 +1,59 b''
1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 // #
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
6 // #
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19
20 var topics = {};
21 jQuery.Topic = function (id) {
22 var callbacks, method,
23 topic = id && topics[id];
24
25 if (!topic) {
26 callbacks = jQuery.Callbacks();
27 topic = {
28 unhandledData: [],
29 publish: callbacks.fire,
30 prepare: function(){
31 for(var i=0; i< arguments.length; i++){
32 this.unhandledData.push(arguments[i]);
33 }
34 },
35 prepareOrPublish: function(){
36 if (callbacks.has() === true){
37 this.publish.apply(this, arguments);
38 }
39 else{
40 this.prepare.apply(this, arguments);
41 }
42 },
43 processPrepared: function(){
44 var data = this.unhandledData;
45 this.unhandledData = [];
46 for(var i=0; i< data.length; i++){
47 this.publish(data[i]);
48 }
49 },
50 subscribe: callbacks.add,
51 unsubscribe: callbacks.remove,
52 callbacks: callbacks
53 };
54 if (id) {
55 topics[id] = topic;
56 }
57 }
58 return topic;
59 };
@@ -0,0 +1,4 b''
1 /plugins/__REGISTER__ - launched after the onDomReady() code from rhodecode.js is executed
2 /ui/plugins/code/anchor_focus - launched when rc starts to scroll on load to anchor on PR/Codeview
3 /ui/plugins/code/comment_form_built - launched when injectInlineForm() is executed and the form object is created
4 /notifications - shows new event notifications No newline at end of file
@@ -0,0 +1,75 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import os
23
24 from rhodecode import events
25 from rhodecode.lib.utils2 import str2bool
26
27 from .subscribers import generate_config_subscriber
28 from . import config_keys
29
30
31 log = logging.getLogger(__name__)
32
33
34 def includeme(config):
35 settings = config.registry.settings
36 _sanitize_settings_and_apply_defaults(settings)
37
38 if settings[config_keys.generate_config]:
39 config.add_subscriber(
40 generate_config_subscriber, events.RepoGroupEvent)
41
42
43 def _sanitize_settings_and_apply_defaults(settings):
44 """
45 Set defaults, convert to python types and validate settings.
46 """
47 # Convert bool settings from string to bool.
48 settings[config_keys.generate_config] = str2bool(
49 settings.get(config_keys.generate_config, 'false'))
50 settings[config_keys.list_parent_path] = str2bool(
51 settings.get(config_keys.list_parent_path, 'true'))
52
53 # Set defaults if key not present.
54 settings.setdefault(config_keys.config_file_path, None)
55 settings.setdefault(config_keys.location_root, '/')
56 settings.setdefault(config_keys.parent_path_root, None)
57
58 # Append path separator to paths.
59 settings[config_keys.location_root] = _append_path_sep(
60 settings[config_keys.location_root])
61 settings[config_keys.parent_path_root] = _append_path_sep(
62 settings[config_keys.parent_path_root])
63
64 # Validate settings.
65 if settings[config_keys.generate_config]:
66 assert settings[config_keys.config_file_path] is not None
67
68
69 def _append_path_sep(path):
70 """
71 Append the path separator if missing.
72 """
73 if isinstance(path, basestring) and not path.endswith(os.path.sep):
74 path += os.path.sep
75 return path
@@ -0,0 +1,28 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 # Definition of setting keys used to configure this module. Defined here to
23 # avoid repetition of keys throughout the module.
24 config_file_path = 'svn.proxy.config_file_path'
25 generate_config = 'svn.proxy.generate_config'
26 list_parent_path = 'svn.proxy.list_parent_path'
27 location_root = 'svn.proxy.location_root'
28 parent_path_root = 'svn.proxy.parent_path_root'
@@ -0,0 +1,31 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 from .utils import generate_mod_dav_svn_config
23
24
25 def generate_config_subscriber(event):
26 """
27 Subscriber to the `rhodcode.events.RepoGroupEvent`. This triggers the
28 automatic generation of mod_dav_svn config file on repository group
29 changes.
30 """
31 generate_mod_dav_svn_config(event.request.registry.settings)
@@ -0,0 +1,65 b''
1 # Auto generated configuration for use with the Apache mod_dav_svn module.
2 #
3 # WARNING: Make sure your Apache instance which runs the mod_dav_svn module is
4 # only accessible by RhodeCode. Otherwise everyone is able to browse
5 # the repositories or run subversion operations (checkout/commit/etc.).
6 #
7 # The mod_dav_svn module does not support subversion repositories which are
8 # organized in subfolders. To support the repository groups of RhodeCode it is
9 # required to provide a <Location> block for each group pointing to the
10 # repository group sub folder.
11 #
12 # To ease the configuration RhodeCode auto generates this file whenever a
13 # repository group is created/changed/deleted. Auto generation can be configured
14 # in the ini file.
15 #
16 # To include this configuration into your apache config you can use the
17 # `Include` directive. See the following example snippet of a virtual host how
18 # to include this configuration file.
19 #
20 # <VirtualHost *:8080>
21 # ServerAdmin webmaster@localhost
22 # DocumentRoot /var/www/html
23 # ErrorLog ${'${APACHE_LOG_DIR}'}/error.log
24 # CustomLog ${'${APACHE_LOG_DIR}'}/access.log combined
25 # Include /path/to/generated/mod_dav_svn.conf
26 # </VirtualHost>
27
28
29 <Location ${location_root}>
30 # The mod_dav_svn module takes the username from the apache request object.
31 # Without authorization this will be empty and no username is logged for the
32 # transactions. This will result in "(no author)" for each revision. The
33 # following directives implement a fake authentication that allows every
34 # username/password combination.
35 AuthType Basic
36 AuthName ${rhodecode_realm}
37 AuthBasicProvider anon
38 Anonymous *
39 Require valid-user
40
41 DAV svn
42 SVNParentPath ${parent_path_root}
43 SVNListParentPath ${'On' if svn_list_parent_path else 'Off'}
44
45 Allow from all
46 Order allow,deny
47 </Location>
48
49 % for location, parent_path in repo_group_paths:
50
51 <Location ${location}>
52 AuthType Basic
53 AuthName ${rhodecode_realm}
54 AuthBasicProvider anon
55 Anonymous *
56 Require valid-user
57
58 DAV svn
59 SVNParentPath ${parent_path}
60 SVNListParentPath ${'On' if svn_list_parent_path else 'Off'}
61
62 Allow from all
63 Order allow,deny
64 </Location>
65 % endfor
@@ -0,0 +1,146 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import mock
23 import re
24 import shutil
25 import tempfile
26
27 from pyramid import testing
28
29 from rhodecode.svn_support import config_keys, utils
30
31
32 class TestModDavSvnConfig(object):
33 @classmethod
34 def setup_class(cls):
35 # Make mako renderer available in tests.
36 config = testing.setUp()
37 config.include('pyramid_mako')
38
39 # Temporary directory holding the generated config files.
40 cls.tempdir = tempfile.mkdtemp(suffix='pytest-mod-dav-svn')
41
42 cls.location_root = '/location/root'
43 cls.parent_path_root = '/parent/path/root'
44
45 @classmethod
46 def teardown_class(cls):
47 testing.tearDown()
48 shutil.rmtree(cls.tempdir, ignore_errors=True)
49
50 @classmethod
51 def get_settings(cls):
52 config_file_path = tempfile.mkstemp(
53 suffix='mod-dav-svn.conf', dir=cls.tempdir)[1]
54 return {
55 config_keys.config_file_path: config_file_path,
56 config_keys.location_root: cls.location_root,
57 config_keys.parent_path_root: cls.parent_path_root,
58 config_keys.list_parent_path: True,
59 }
60
61 @classmethod
62 def get_repo_groups(cls, count=1):
63 repo_groups = []
64 for num in range(0, count):
65 full_path = '/path/to/RepoGroup{}'.format(num)
66 repo_group_mock = mock.MagicMock()
67 repo_group_mock.full_path = full_path
68 repo_group_mock.full_path_splitted = full_path.split('/')
69 repo_groups.append(repo_group_mock)
70 return repo_groups
71
72 def assert_root_location_directive(self, config):
73 pattern = '<Location {location}>'.format(location=self.location_root)
74 assert len(re.findall(pattern, config)) == 1
75
76 def assert_group_location_directive(self, config, group_path):
77 pattern = '<Location {location}{group_path}>'.format(
78 location=self.location_root, group_path=group_path)
79 assert len(re.findall(pattern, config)) == 1
80
81 @mock.patch('rhodecode.svn_support.utils.RepoGroup')
82 def test_generate_mod_dav_svn_config(self, RepoGroupMock):
83 num_groups = 3
84 RepoGroupMock.get_all_repo_groups.return_value = self.get_repo_groups(
85 count=num_groups)
86
87 # Execute the method under test.
88 settings = self.get_settings()
89 utils.generate_mod_dav_svn_config(settings)
90
91 # Read generated file.
92 with open(settings[config_keys.config_file_path], 'r') as file_:
93 content = file_.read()
94
95 # Assert that one location directive exists for each repository group.
96 for group in self.get_repo_groups(count=num_groups):
97 self.assert_group_location_directive(content, group.full_path)
98
99 # Assert that the root location directive exists.
100 self.assert_root_location_directive(content)
101
102 @mock.patch('rhodecode.svn_support.utils.RepoGroup')
103 def test_list_parent_path_on(self, RepoGroupMock):
104 RepoGroupMock.get_all_repo_groups.return_value = self.get_repo_groups()
105
106 # Execute the method under test.
107 settings = self.get_settings()
108 settings[config_keys.list_parent_path] = True
109 utils.generate_mod_dav_svn_config(settings)
110
111 # Read generated file.
112 with open(settings[config_keys.config_file_path], 'r') as file_:
113 content = file_.read()
114
115 # Make assertions.
116 assert not re.search('SVNListParentPath\s+Off', content)
117 assert re.search('SVNListParentPath\s+On', content)
118
119 @mock.patch('rhodecode.svn_support.utils.RepoGroup')
120 def test_list_parent_path_off(self, RepoGroupMock):
121 RepoGroupMock.get_all_repo_groups.return_value = self.get_repo_groups()
122
123 # Execute the method under test.
124 settings = self.get_settings()
125 settings[config_keys.list_parent_path] = False
126 utils.generate_mod_dav_svn_config(settings)
127
128 # Read generated file.
129 with open(settings[config_keys.config_file_path], 'r') as file_:
130 content = file_.read()
131
132 # Make assertions.
133 assert re.search('SVNListParentPath\s+Off', content)
134 assert not re.search('SVNListParentPath\s+On', content)
135
136 @mock.patch('rhodecode.svn_support.utils.log')
137 def test_write_does_not_raise_on_error(self, LogMock):
138 """
139 Writing the configuration to file should never raise exceptions.
140 If e.g. path points to a place without write permissions.
141 """
142 utils._write_mod_dav_svn_config(
143 'content', '/dev/null/not/existing/path')
144
145 # Assert that we log the exception.
146 assert LogMock.exception.called
@@ -0,0 +1,83 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import os
23
24 from pyramid.renderers import render
25
26 from rhodecode.lib.utils import get_rhodecode_realm
27 from rhodecode.model.db import RepoGroup
28 from . import config_keys
29
30
31 log = logging.getLogger(__name__)
32
33
34 def generate_mod_dav_svn_config(settings):
35 """
36 Generate the configuration file for use with subversion's mod_dav_svn
37 module. The configuration has to contain a <Location> block for each
38 available repository group because the mod_dav_svn module does not support
39 repositories organized in sub folders.
40 """
41 config = _render_mod_dav_svn_config(
42 settings[config_keys.parent_path_root],
43 settings[config_keys.list_parent_path],
44 settings[config_keys.location_root],
45 RepoGroup.get_all_repo_groups())
46 _write_mod_dav_svn_config(config, settings[config_keys.config_file_path])
47
48
49 def _render_mod_dav_svn_config(
50 parent_path_root, list_parent_path, location_root, repo_groups):
51 """
52 Render mod_dav_svn configuration to string.
53 """
54 repo_group_paths = []
55 for repo_group in repo_groups:
56 group_path = repo_group.full_path_splitted
57 location = os.path.join(location_root, *group_path)
58 parent_path = os.path.join(parent_path_root, *group_path)
59 repo_group_paths.append((location, parent_path))
60
61 context = {
62 'location_root': location_root,
63 'parent_path_root': parent_path_root,
64 'repo_group_paths': repo_group_paths,
65 'svn_list_parent_path': list_parent_path,
66 'rhodecode_realm': get_rhodecode_realm(),
67 }
68
69 # Render the configuration template to string.
70 template = 'rhodecode:svn_support/templates/mod-dav-svn.conf.mako'
71 return render(template, context)
72
73
74 def _write_mod_dav_svn_config(config, filepath):
75 """
76 Write mod_dav_svn config to file. Log on exceptions but do not raise.
77 """
78 try:
79 with open(filepath, 'w') as file_:
80 file_.write(config)
81 except Exception:
82 log.exception(
83 'Can not write mod_dav_svn configuration to "%s"', filepath)
@@ -0,0 +1,40 b''
1 ## -*- coding: utf-8 -*-
2 <%!
3 def inherit(context):
4 if context['c'].repo:
5 return "/admin/repos/repo_edit.html"
6 else:
7 return "/admin/settings/settings.html"
8 %>
9 <%inherit file="${inherit(context)}" />
10
11 <%def name="title()">
12 ${_('Integrations Settings')}
13 %if c.rhodecode_name:
14 &middot; ${h.branding(c.rhodecode_name)}
15 %endif
16 </%def>
17
18 <%def name="breadcrumbs_links()">
19 ${h.link_to(_('Admin'),h.url('admin_home'))}
20 &raquo;
21 ${_('Integrations')}
22 </%def>
23
24 <%def name="menu_bar_nav()">
25 %if c.repo:
26 ${self.menu_items(active='repositories')}
27 %else:
28 ${self.menu_items(active='admin')}
29 %endif
30 </%def>
31
32 <%def name="menu_bar_subnav()">
33 %if c.repo:
34 ${self.repo_menu(active='options')}
35 %endif
36 </%def>
37
38 <%def name="main_content()">
39 ${next.body()}
40 </%def>
@@ -0,0 +1,43 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.html"/>
3
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
6 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
7 &raquo;
8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 &raquo;
10 ${h.link_to(current_IntegrationType.display_name,
11 request.route_url(route_name='repo_integrations_list',
12 repo_name=c.repo.repo_name,
13 integration=current_IntegrationType.key))}
14 %else:
15 ${h.link_to(_('Admin'),h.url('admin_home'))}
16 &raquo;
17 ${h.link_to(_('Settings'),h.url('admin_settings'))}
18 &raquo;
19 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
20 &raquo;
21 ${h.link_to(current_IntegrationType.display_name,
22 request.route_url(route_name='global_integrations_list',
23 integration=current_IntegrationType.key))}
24 %endif
25 %if integration:
26 &raquo;
27 ${integration.name}
28 %endif
29 </%def>
30 <div class="panel panel-default">
31 <div class="panel-heading">
32 <h2 class="panel-title">
33 %if integration:
34 ${current_IntegrationType.display_name} - ${integration.name}
35 %else:
36 ${_('Create New %(integration_type)s Integration') % {'integration_type': current_IntegrationType.display_name}}
37 %endif
38 </h2>
39 </div>
40 <div class="panel-body">
41 ${form.render() | n}
42 </div>
43 </div>
@@ -0,0 +1,147 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.html"/>
3
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
6 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
7 %else:
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Settings'),h.url('admin_settings'))}
11 %endif
12 %if current_IntegrationType:
13 &raquo;
14 %if c.repo:
15 ${h.link_to(_('Integrations'),
16 request.route_url(route_name='repo_integrations_home',
17 repo_name=c.repo.repo_name))}
18 %else:
19 ${h.link_to(_('Integrations'),
20 request.route_url(route_name='global_integrations_home'))}
21 %endif
22 &raquo;
23 ${current_IntegrationType.display_name}
24 %else:
25 &raquo;
26 ${_('Integrations')}
27 %endif
28 </%def>
29 <div class="panel panel-default">
30 <div class="panel-heading">
31 <h3 class="panel-title">${_('Create New Integration')}</h3>
32 </div>
33 <div class="panel-body">
34 %if not available_integrations:
35 ${_('No integrations available.')}
36 %else:
37 %for integration in available_integrations:
38 <%
39 if c.repo:
40 create_url = request.route_url('repo_integrations_create',
41 repo_name=c.repo.repo_name,
42 integration=integration)
43 else:
44 create_url = request.route_url('global_integrations_create',
45 integration=integration)
46 %>
47 <a href="${create_url}" class="btn">
48 ${integration}
49 </a>
50 %endfor
51 %endif
52 </div>
53 </div>
54 <div class="panel panel-default">
55 <div class="panel-heading">
56 <h3 class="panel-title">${_('Current Integrations')}</h3>
57 </div>
58 <div class="panel-body">
59 <table class="rctable issuetracker">
60 <thead>
61 <tr>
62 <th>${_('Enabled')}</th>
63 <th>${_('Description')}</th>
64 <th>${_('Type')}</th>
65 <th>${_('Actions')}</th>
66 <th></th>
67 </tr>
68 </thead>
69 <tbody>
70
71 %for integration_type, integrations in sorted(current_integrations.items()):
72 %for integration in sorted(integrations, key=lambda x: x.name):
73 <tr id="integration_${integration.integration_id}">
74 <td class="td-enabled">
75 %if integration.enabled:
76 <div class="flag_status approved pull-left"></div>
77 %else:
78 <div class="flag_status rejected pull-left"></div>
79 %endif
80 </td>
81 <td class="td-description">
82 ${integration.name}
83 </td>
84 <td class="td-regex">
85 ${integration.integration_type}
86 </td>
87 <td class="td-action">
88 %if integration_type not in available_integrations:
89 ${_('unknown integration')}
90 %else:
91 <%
92 if c.repo:
93 edit_url = request.route_url('repo_integrations_edit',
94 repo_name=c.repo.repo_name,
95 integration=integration.integration_type,
96 integration_id=integration.integration_id)
97 else:
98 edit_url = request.route_url('global_integrations_edit',
99 integration=integration.integration_type,
100 integration_id=integration.integration_id)
101 %>
102 <div class="grid_edit">
103 <a href="${edit_url}">${_('Edit')}</a>
104 </div>
105 <div class="grid_delete">
106 <a href="${edit_url}"
107 class="btn btn-link btn-danger delete_integration_entry"
108 data-desc="${integration.name}"
109 data-uid="${integration.integration_id}">
110 ${_('Delete')}
111 </a>
112 </div>
113 %endif
114 </td>
115 </tr>
116 %endfor
117 %endfor
118 <tr id="last-row"></tr>
119 </tbody>
120 </table>
121 </div>
122 </div>
123 <script type="text/javascript">
124 var delete_integration = function(entry) {
125 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
126 var request = $.ajax({
127 type: "POST",
128 url: $(entry).attr('href'),
129 data: {
130 'delete': 'delete',
131 'csrf_token': CSRF_TOKEN
132 },
133 success: function(){
134 location.reload();
135 },
136 error: function(data, textStatus, errorThrown){
137 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
138 }
139 });
140 };
141 }
142
143 $('.delete_integration_entry').on('click', function(e){
144 e.preventDefault();
145 delete_integration(this);
146 });
147 </script> No newline at end of file
@@ -0,0 +1,14 b''
1 <%
2 from pyramid.renderers import render as pyramid_render
3 from pyramid.threadlocal import get_current_registry, get_current_request
4 pyramid_registry = get_current_registry()
5 %>
6 % for plugin, config in getattr(pyramid_registry, 'rhodecode_plugins', {}).items():
7 % if config['template_hooks'].get('plugin_init_template'):
8 ${pyramid_render(config['template_hooks'].get('plugin_init_template'),
9 {'config':config}, request=get_current_request(), package='rc_ae')|n}
10 % endif
11 % endfor
12 <script>
13 $.Topic('/plugins/__REGISTER__').prepareOrPublish({});
14 </script>
@@ -0,0 +1,24 b''
1 <script>
2 var CHANNELSTREAM_URLS = ${config['url_gen'](request)|n};
3 %if request.registry.rhodecode_plugins['channelstream']['enabled'] and c.rhodecode_user.username != h.DEFAULT_USER:
4 var CHANNELSTREAM_SETTINGS = {
5 'enabled': true,
6 'ws_location': '${request.registry.settings.get('channelstream.ws_url')}',
7 'webapp_location': '${h.url('/', qualified=True)[:-1]}'
8 };
9 %else:
10 var CHANNELSTREAM_SETTINGS = {
11 'enabled':false,
12 'ws_location': '',
13 'webapp_location': ''};
14 %endif
15
16 if (CHANNELSTREAM_SETTINGS.enabled) {
17 connCtrlr = new ConnectionController(
18 CHANNELSTREAM_SETTINGS.webapp_location,
19 CHANNELSTREAM_SETTINGS.ws_location,
20 CHANNELSTREAM_URLS
21 );
22 registerViewChannels();
23 }
24 </script>
@@ -0,0 +1,78 b''
1 <div id="file-tree-wrapper" class="browser-body ${'full-load' if c.full_load else ''}">
2 <table class="code-browser rctable">
3 <thead>
4 <tr>
5 <th>${_('Name')}</th>
6 <th>${_('Size')}</th>
7 <th>${_('Modified')}</th>
8 <th>${_('Last Commit')}</th>
9 <th>${_('Author')}</th>
10 </tr>
11 </thead>
12
13 <tbody id="tbody">
14 %if c.file.parent:
15 <tr class="parity0">
16 <td class="td-componentname">
17 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.file.parent.path)}" class="pjax-link">
18 <i class="icon-folder"></i>..
19 </a>
20 </td>
21 <td></td>
22 <td></td>
23 <td></td>
24 <td></td>
25 </tr>
26 %endif
27 %for cnt,node in enumerate(c.file):
28 <tr class="parity${cnt%2}">
29 <td class="td-componentname">
30 %if node.is_submodule():
31 <span class="submodule-dir">
32 ${h.link_to_if(
33 node.url.startswith('http://') or node.url.startswith('https://'),
34 node.name, node.url)}
35 </span>
36 %else:
37 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=h.safe_unicode(node.path))}" class="pjax-link">
38 <i class="${'icon-file browser-file' if node.is_file() else 'icon-folder browser-dir'}"></i>${node.name}
39 </a>
40 %endif
41 </td>
42 %if node.is_file():
43 <td class="td-size" data-attr-name="size">
44 % if c.full_load:
45 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
46 % else:
47 ${_('Loading ...')}
48 % endif
49 </td>
50 <td class="td-time" data-attr-name="modified_at">
51 % if c.full_load:
52 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
53 % endif
54 </td>
55 <td class="td-hash" data-attr-name="commit_id">
56 % if c.full_load:
57 <div class="tooltip" title="${node.last_commit.message}">
58 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.revision}:${node.last_commit.short_id}</pre>
59 </div>
60 % endif
61 </td>
62 <td class="td-user" data-attr-name="author">
63 % if c.full_load:
64 <span data-author="${node.last_commit.author}" title="${node.last_commit.author}">${h.gravatar_with_user(node.last_commit.author)|n}</span>
65 % endif
66 </td>
67 %else:
68 <td></td>
69 <td></td>
70 <td></td>
71 <td></td>
72 %endif
73 </tr>
74 %endfor
75 </tbody>
76 <tbody id="tbody_filtered"></tbody>
77 </table>
78 </div>
@@ -0,0 +1,20 b''
1 <div class="checkbox">
2 <input tal:define="name name|field.name;
3 true_val true_val|field.widget.true_val;
4 css_class css_class|field.widget.css_class;
5 style style|field.widget.style;
6 oid oid|field.oid"
7 type="checkbox"
8 name="${name}" value="${true_val}"
9 id="${oid}"
10 tal:attributes="checked cstruct == true_val;
11 class css_class;
12 style style;" />
13
14 <label for="${field.oid}">
15 <span tal:condition="hasattr(field, 'schema') and hasattr(field.schema, 'label')"
16 tal:replace="field.schema.label" class="checkbox-label" >
17 </span>
18
19 </label>
20 </div> No newline at end of file
@@ -0,0 +1,25 b''
1 <div tal:define="css_class css_class|field.widget.css_class;
2 style style|field.widget.style;
3 oid oid|field.oid;
4 inline getattr(field.widget, 'inline', False)"
5 tal:omit-tag="not inline">
6 ${field.start_sequence()}
7 <div tal:repeat="choice values | field.widget.values"
8 tal:omit-tag="inline"
9 class="checkbox">
10 <div tal:define="(value, title) choice">
11 <input tal:attributes="checked value in cstruct;
12 class css_class;
13 style style"
14 type="checkbox"
15 name="checkbox"
16 value="${value}"
17 id="${oid}-${repeat.choice.index}"/>
18 <label for="${oid}-${repeat.choice.index}"
19 tal:attributes="class inline and 'checkbox-inline'">
20 ${title}
21 </label>
22 </div>
23 </div>
24 ${field.end_sequence()}
25 </div> No newline at end of file
@@ -0,0 +1,100 b''
1 <form
2 tal:define="style style|field.widget.style;
3 css_class css_class|string:${field.widget.css_class or field.css_class or ''};
4 item_template item_template|field.widget.item_template;
5 autocomplete autocomplete|field.autocomplete;
6 title title|field.title;
7 errormsg errormsg|field.errormsg;
8 description description|field.description;
9 buttons buttons|field.buttons;
10 use_ajax use_ajax|field.use_ajax;
11 ajax_options ajax_options|field.ajax_options;
12 formid formid|field.formid;
13 action action|field.action or None;
14 method method|field.method;"
15 tal:attributes="autocomplete autocomplete;
16 style style;
17 class css_class;
18 action action;"
19 id="${formid}"
20 method="${method}"
21 enctype="multipart/form-data"
22 accept-charset="utf-8"
23 i18n:domain="deform"
24 >
25
26 <fieldset class="deform-form-fieldset">
27
28 <legend tal:condition="title">${title}</legend>
29
30 <input type="hidden" name="${h.csrf_token_key}" value="${h.get_csrf_token()}" />
31 <input type="hidden" name="_charset_" />
32 <input type="hidden" name="__formid__" value="${formid}"/>
33
34 <!--
35 <div class="alert alert-danger" tal:condition="field.error">
36 <div class="error-msg-lbl" i18n:translate=""
37 >There was a problem with your submission</div>
38 <div class="error-msg-detail" i18n:translate=""
39 >Errors have been highlighted below</div>
40 <p class="error-msg">${field.errormsg}</p>
41 </div>
42 -->
43
44 <p class="section first" tal:condition="description">
45 ${description}
46 </p>
47
48 <div tal:repeat="child field"
49 tal:replace="structure child.render_template(item_template)"/>
50
51 <div class="form-group">
52 <tal:loop tal:repeat="button buttons">
53 <button
54 tal:define="btn_disposition repeat.button.start and 'btn-primary' or (button.name == 'delete' and 'btn-danger' or 'btn-default');
55 btn_icon button.icon|None"
56 tal:attributes="disabled button.disabled if button.disabled else None"
57 id="${formid+button.name}"
58 name="${button.name}"
59 type="${button.type}"
60 class="btn ${button.css_class or btn_disposition}"
61 value="${button.value}">
62 <i tal:condition="btn_icon" class="${btn_icon}"> </i>
63 ${button.title}
64 </button>
65 </tal:loop>
66 </div>
67
68 </fieldset>
69
70 <script type="text/javascript" tal:condition="use_ajax">
71 deform.addCallback(
72 '${formid}',
73 function(oid) {
74 var target = '#' + oid;
75 var options = {
76 target: target,
77 replaceTarget: true,
78 success: function() {
79 deform.processCallbacks();
80 deform.focusFirstInput(target);
81 },
82 beforeSerialize: function() {
83 // See http://bit.ly/1agBs9Z (hack to fix tinymce-related ajax bug)
84 if ('tinymce' in window) {
85 $(tinymce.get()).each(
86 function(i, el) {
87 var content = el.getContent();
88 var editor_input = document.getElementById(el.id);
89 editor_input.value = content;
90 });
91 }
92 }
93 };
94 var extra_options = ${ajax_options} || {};
95 $('#' + oid).ajaxForm($.extend(options, extra_options));
96 }
97 );
98 </script>
99
100 </form> No newline at end of file
@@ -0,0 +1,33 b''
1 <tal:def tal:define="title title|field.title;
2 description description|field.description;
3 errormsg errormsg|field.errormsg;
4 item_template item_template|field.widget.item_template"
5 i18n:domain="deform">
6
7 <div class="panel panel-default">
8 <div class="panel-heading">${title}</div>
9 <div class="panel-body">
10
11 <div tal:condition="errormsg"
12 class="clearfix alert alert-danger">
13 <p i18n:translate="">
14 There was a problem with this section
15 </p>
16 <p>${errormsg}</p>
17 </div>
18
19 <div tal:condition="description">
20 ${description}
21 </div>
22
23 ${field.start_mapping()}
24 <div tal:repeat="child field.children"
25 tal:replace="structure child.render_template(item_template)" >
26 </div>
27 ${field.end_mapping()}
28
29 <div style="clear: both"></div>
30 </div>
31 </div>
32
33 </tal:def> No newline at end of file
@@ -0,0 +1,47 b''
1 <div tal:define="error_class error_class|field.widget.error_class;
2 description description|field.description;
3 title title|field.title;
4 oid oid|field.oid;
5 hidden hidden|field.widget.hidden;
6 category category|field.widget.category;
7 structural hidden or category == 'structural';
8 required required|field.required;"
9 class="form-group ${field.error and 'has-error' or ''} ${field.widget.item_css_class or ''}"
10 id="item-${oid}"
11 tal:omit-tag="structural"
12 i18n:domain="deform">
13
14 <label for="${oid}"
15 class="control-label ${required and 'required' or ''}"
16 tal:condition="not structural"
17 id="req-${oid}"
18 >
19 ${title}
20 </label>
21 <div class="control-inputs">
22 <div tal:define="input_prepend field.widget.input_prepend | None;
23 input_append field.widget.input_append | None"
24 tal:omit-tag="not (input_prepend or input_append)"
25 class="input-group">
26 <span class="input-group-addon"
27 tal:condition="input_prepend">${input_prepend}</span
28 ><span tal:replace="structure field.serialize(cstruct).strip()"
29 /><span class="input-group-addon"
30 tal:condition="input_append">${input_append}</span>
31 </div>
32 <p class="help-block error-block"
33 tal:define="errstr 'error-%s' % field.oid"
34 tal:repeat="msg field.error.messages()"
35 i18n:translate=""
36 tal:attributes="id repeat.msg.index==0 and errstr or
37 ('%s-%s' % (errstr, repeat.msg.index))"
38 tal:condition="field.error and not field.widget.hidden and not field.typ.__class__.__name__=='Mapping'">
39 ${msg}
40 </p>
41
42 <p tal:condition="field.description and not field.widget.hidden"
43 class="help-block" >
44 ${field.description}
45 </p>
46 </div>
47 </div> No newline at end of file
@@ -0,0 +1,24 b''
1 <div tal:define="
2 item_tmpl item_template|field.widget.readonly_item_template;
3 oid oid|field.oid;
4 name name|field.name;
5 title title|field.title;"
6 class="deform-seq"
7 id="${oid}">
8
9 <div class="panel panel-default">
10 <div class="panel-heading">${title}</div>
11 <div class="panel-body">
12
13 <div class="deform-seq-container">
14 <div tal:define="subfields [ x[1] for x in subfields ]"
15 tal:repeat="subfield subfields"
16 tal:replace="structure subfield.render_template(item_tmpl,
17 parent=field)" />
18 </div>
19
20 <div style="clear: both"></div>
21 </div>
22
23 </div>
24 </div> No newline at end of file
@@ -0,0 +1,11 b''
1 <div tal:omit-tag="field.widget.hidden"
2 tal:define="
3 hidden hidden|field.widget.hidden;
4 description description|field.description;"
5 title="${description}"
6 class="form-group row deform-seq-item ${field.error and error_class or ''} ${field.widget.item_css_class or ''}"
7 i18n:domain="deform">
8 <div class="deform-seq-item-group">
9 <span tal:replace="structure field.serialize(cstruct, readonly=True)"/>
10 </div>
11 </div>
@@ -0,0 +1,55 b''
1 <div tal:define="
2 name name|field.name;
3 style field.widget.style;
4 oid oid|field.oid;
5 css_class css_class|field.widget.css_class;
6 optgroup_class optgroup_class|field.widget.optgroup_class;
7 multiple multiple|field.widget.multiple;"
8 tal:omit-tag="">
9 <input type="hidden" name="__start__" value="${name}:sequence"
10 tal:condition="multiple" />
11
12 <select tal:attributes="
13 name name;
14 id oid;
15 class string: form-control ${css_class or ''};
16 data-placeholder field.widget.placeholder|None;
17 multiple multiple;
18 style style;">
19 <tal:loop tal:repeat="item values">
20 <optgroup tal:condition="isinstance(item, optgroup_class)"
21 tal:attributes="label item.label">
22 <option tal:repeat="(value, description) item.options"
23 tal:attributes="
24 selected (multiple and value in list(map(unicode, cstruct)) or value == list(map(unicode, cstruct))) and 'selected';
25 class css_class;
26 label field.widget.long_label_generator and description;
27 value value"
28 tal:content="field.widget.long_label_generator and field.widget.long_label_generator(item.label, description) or description"/>
29 </optgroup>
30 <option tal:condition="not isinstance(item, optgroup_class)"
31 tal:attributes="
32 selected (multiple and item[0] in list(map(unicode, cstruct)) or item[0] == unicode(cstruct)) and 'selected';
33 class css_class;
34 value item[0]">${item[1]}</option>
35 </tal:loop>
36 </select>
37
38 <script type="text/javascript">
39 deform.addCallback(
40 '${field.oid}',
41 function(oid) {
42 $('#' + oid).select2({
43 containerCssClass: 'form-control drop-menu',
44 dropdownCssClass: 'drop-menu-dropdown',
45 dropdownAutoWidth: true,
46 placeholder: "${str(field.widget.placeholder).replace('"','\\"')|""}",
47 allowClear: true
48 });
49 }
50 );
51 </script>
52
53 <input type="hidden" name="__end__" value="${name}:sequence"
54 tal:condition="multiple" />
55 </div> No newline at end of file
@@ -0,0 +1,105 b''
1 <div tal:define="item_tmpl item_template|field.widget.item_template;
2 oid oid|field.oid;
3 name name|field.name;
4 min_len min_len|field.widget.min_len;
5 min_len min_len or 0;
6 max_len max_len|field.widget.max_len;
7 max_len max_len or 100000;
8 now_len len(subfields);
9 orderable orderable|field.widget.orderable;
10 orderable orderable and 1 or 0;
11 prototype field.widget.prototype(field);
12 title title|field.title;"
13 class="deform-seq"
14 id="${oid}">
15
16 <style>
17 body.dragging, body.dragging * {
18 cursor: move !important;
19 }
20
21 .dragged {
22 position: absolute;
23 opacity: 0.5;
24 z-index: 2000;
25 }
26 </style>
27
28 <!-- sequence -->
29 <input type="hidden" name="__start__"
30 value="${field.name}:sequence"
31 class="deform-proto"
32 tal:attributes="prototype prototype"/>
33
34 <div class="panel panel-default">
35 <div class="panel-heading">${title}</div>
36 <div class="panel-body">
37
38 <div class="deform-seq-container"
39 id="${oid}-orderable">
40 <div tal:define="subfields [ x[1] for x in subfields ]"
41 tal:repeat="subfield subfields"
42 tal:replace="structure subfield.render_template(item_tmpl,
43 parent=field)" />
44 <span class="deform-insert-before"
45 tal:attributes="
46 min_len min_len;
47 max_len max_len;
48 now_len now_len;
49 orderable orderable;"></span>
50 </div>
51
52 <div style="clear: both"></div>
53 </div>
54
55 <div class="panel-footer">
56 <a href="#"
57 class="btn deform-seq-add"
58 id="${field.oid}-seqAdd"
59 onclick="javascript: return deform.appendSequenceItem(this);">
60 <small id="${field.oid}-addtext">${add_subitem_text}</small>
61 </a>
62
63 <script type="text/javascript">
64 deform.addCallback(
65 '${field.oid}',
66 function(oid) {
67 oid_node = $('#'+ oid);
68 deform.processSequenceButtons(oid_node, ${min_len},
69 ${max_len}, ${now_len},
70 ${orderable});
71 }
72 )
73 <tal:block condition="orderable">
74 $( "#${oid}-orderable" ).sortable({
75 handle: ".deform-order-button, .panel-heading",
76 containerSelector: "#${oid}-orderable",
77 itemSelector: ".deform-seq-item",
78 placeholder: '<span class="glyphicon glyphicon-arrow-right placeholder"></span>',
79 onDragStart: function ($item, container, _super) {
80 var offset = $item.offset(),
81 pointer = container.rootGroup.pointer
82
83 adjustment = {
84 left: pointer.left - offset.left,
85 top: pointer.top - offset.top
86 }
87
88 _super($item, container)
89 },
90 onDrag: function ($item, position) {
91 $item.css({
92 left: position.left - adjustment.left,
93 top: position.top - adjustment.top
94 })
95 }
96 });
97 </tal:block>
98 </script>
99
100 <input type="hidden" name="__end__" value="${field.name}:sequence"/>
101 <!-- /sequence -->
102 </div>
103
104 </div>
105 </div> No newline at end of file
@@ -0,0 +1,35 b''
1 <div tal:omit-tag="field.widget.hidden"
2 tal:define="hidden hidden|field.widget.hidden;
3 error_class error_class|field.widget.error_class;
4 description description|field.description;
5 title title|field.title;
6 oid oid|field.oid"
7 class="form-group row deform-seq-item ${field.error and error_class or ''} ${field.widget.item_css_class or ''}"
8 i18n:domain="deform">
9 <div class="deform-seq-item-group">
10 <span tal:replace="structure field.serialize(cstruct)"/>
11 <tal:errors condition="field.error and not hidden"
12 define="errstr 'error-%s' % oid"
13 repeat="msg field.error.messages()">
14 <p tal:condition="msg"
15 id="${errstr if repeat.msg.index==0 else '%s-%s' % (errstr, repeat.msg.index)}"
16 class="${error_class} help-block error-block"
17 i18n:translate="">${msg}</p>
18 </tal:errors>
19 </div>
20 <div class="deform-seq-item-handle" style="padding:0">
21 <!-- sequence_item -->
22 <span class="deform-order-button close glyphicon glyphicon-resize-vertical"
23 id="${oid}-order"
24 tal:condition="not hidden"
25 title="Reorder (via drag and drop)"
26 i18n:attributes="title"></span>
27 <a class="deform-close-button close"
28 id="${oid}-close"
29 tal:condition="not field.widget.hidden"
30 title="Remove"
31 i18n:attributes="title"
32 onclick="javascript:deform.removeSequenceItem(this);">&times;</a>
33 </div>
34 <!-- /sequence_item -->
35 </div>
@@ -0,0 +1,23 b''
1 <span tal:define="name name|field.name;
2 css_class css_class|field.widget.css_class;
3 oid oid|field.oid;
4 mask mask|field.widget.mask;
5 placeholder placeholder|field.widget.placeholder|field.placeholder|'';
6 mask_placeholder mask_placeholder|field.widget.mask_placeholder;
7 style style|field.widget.style;
8 "
9 tal:omit-tag="">
10 <input type="text" name="${name}" value="${cstruct}"
11 tal:attributes="class string: form-control ${css_class or ''};
12 style style"
13 placeholder="${placeholder}"
14 id="${oid}"/>
15 <script tal:condition="mask" type="text/javascript">
16 deform.addCallback(
17 '${oid}',
18 function (oid) {
19 $("#" + oid).mask("${mask}",
20 {placeholder:"${mask_placeholder}"});
21 });
22 </script>
23 </span> No newline at end of file
@@ -0,0 +1,202 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import pytest
23
24 from rhodecode.config.middleware import (
25 _sanitize_vcs_settings, _bool_setting, _string_setting, _list_setting,
26 _int_setting)
27
28
29 class TestHelperFunctions(object):
30 @pytest.mark.parametrize('raw, expected', [
31 ('true', True), (u'true', True),
32 ('yes', True), (u'yes', True),
33 ('on', True), (u'on', True),
34 ('false', False), (u'false', False),
35 ('no', False), (u'no', False),
36 ('off', False), (u'off', False),
37 ('invalid-bool-value', False),
38 ('invalid-∫øø@-√å@¨€', False),
39 (u'invalid-∫øø@-√å@¨€', False),
40 ])
41 def test_bool_setting_helper(self, raw, expected):
42 key = 'dummy-key'
43 settings = {key: raw}
44 _bool_setting(settings, key, None)
45 assert settings[key] is expected
46
47 @pytest.mark.parametrize('raw, expected', [
48 ('', ''),
49 ('test-string', 'test-string'),
50 ('CaSe-TeSt', 'case-test'),
51 ('test-string-烩€', 'test-string-烩€'),
52 (u'test-string-烩€', u'test-string-烩€'),
53 ])
54 def test_string_setting_helper(self, raw, expected):
55 key = 'dummy-key'
56 settings = {key: raw}
57 _string_setting(settings, key, None)
58 assert settings[key] == expected
59
60 @pytest.mark.parametrize('raw, expected', [
61 ('', []),
62 ('test', ['test']),
63 ('CaSe-TeSt', ['CaSe-TeSt']),
64 ('test-string-烩€', ['test-string-烩€']),
65 (u'test-string-烩€', [u'test-string-烩€']),
66 ('hg git svn', ['hg', 'git', 'svn']),
67 ('hg,git,svn', ['hg', 'git', 'svn']),
68 ('hg, git, svn', ['hg', 'git', 'svn']),
69 ('hg\ngit\nsvn', ['hg', 'git', 'svn']),
70 (' hg\n git\n svn ', ['hg', 'git', 'svn']),
71 (', hg , git , svn , ', ['', 'hg', 'git', 'svn', '']),
72 ('cheese,free node,other', ['cheese', 'free node', 'other']),
73 ])
74 def test_list_setting_helper(self, raw, expected):
75 key = 'dummy-key'
76 settings = {key: raw}
77 _list_setting(settings, key, None)
78 assert settings[key] == expected
79
80 @pytest.mark.parametrize('raw, expected', [
81 ('0', 0),
82 ('-0', 0),
83 ('12345', 12345),
84 ('-12345', -12345),
85 (u'-12345', -12345),
86 ])
87 def test_int_setting_helper(self, raw, expected):
88 key = 'dummy-key'
89 settings = {key: raw}
90 _int_setting(settings, key, None)
91 assert settings[key] == expected
92
93 @pytest.mark.parametrize('raw', [
94 ('0xff'),
95 (''),
96 ('invalid-int'),
97 ('invalid-⁄~†'),
98 (u'invalid-⁄~†'),
99 ])
100 def test_int_setting_helper_invalid_input(self, raw):
101 key = 'dummy-key'
102 settings = {key: raw}
103 with pytest.raises(Exception):
104 _int_setting(settings, key, None)
105
106
107 class TestSanitizeVcsSettings(object):
108 _bool_settings = [
109 ('vcs.hooks.direct_calls', False),
110 ('vcs.server.enable', True),
111 ('vcs.start_server', False),
112 ('startup.import_repos', False),
113 ]
114
115 _string_settings = [
116 ('vcs.svn.compatible_version', ''),
117 ('git_rev_filter', '--all'),
118 ('vcs.hooks.protocol', 'pyro4'),
119 ('vcs.server', ''),
120 ('vcs.server.log_level', 'debug'),
121 ('vcs.server.protocol', 'pyro4'),
122 ]
123
124 _list_settings = [
125 ('vcs.backends', 'hg git'),
126 ]
127
128 @pytest.mark.parametrize('key, default', _list_settings)
129 def test_list_setting_spacesep_list(self, key, default):
130 test_list = ['test', 'list', 'values', 'for', key]
131 input_value = ' '.join(test_list)
132 settings = {key: input_value}
133 _sanitize_vcs_settings(settings)
134 assert settings[key] == test_list
135
136 @pytest.mark.parametrize('key, default', _list_settings)
137 def test_list_setting_newlinesep_list(self, key, default):
138 test_list = ['test', 'list', 'values', 'for', key]
139 input_value = '\n'.join(test_list)
140 settings = {key: input_value}
141 _sanitize_vcs_settings(settings)
142 assert settings[key] == test_list
143
144 @pytest.mark.parametrize('key, default', _list_settings)
145 def test_list_setting_commasep_list(self, key, default):
146 test_list = ['test', 'list', 'values', 'for', key]
147 input_value = ','.join(test_list)
148 settings = {key: input_value}
149 _sanitize_vcs_settings(settings)
150 assert settings[key] == test_list
151
152 @pytest.mark.parametrize('key, default', _list_settings)
153 def test_list_setting_comma_and_space_sep_list(self, key, default):
154 test_list = ['test', 'list', 'values', 'for', key]
155 input_value = ', '.join(test_list)
156 settings = {key: input_value}
157 _sanitize_vcs_settings(settings)
158 assert settings[key] == test_list
159
160 @pytest.mark.parametrize('key, default', _string_settings)
161 def test_string_setting_string(self, key, default):
162 test_value = 'test-string-for-{}'.format(key)
163 settings = {key: test_value}
164 _sanitize_vcs_settings(settings)
165 assert settings[key] == test_value
166
167 @pytest.mark.parametrize('key, default', _string_settings)
168 def test_string_setting_default(self, key, default):
169 settings = {}
170 _sanitize_vcs_settings(settings)
171 assert settings[key] == default
172
173 @pytest.mark.parametrize('key, default', _string_settings)
174 def test_string_setting_lowercase(self, key, default):
175 test_value = 'Test-String-For-{}'.format(key)
176 settings = {key: test_value}
177 _sanitize_vcs_settings(settings)
178 assert settings[key] == test_value.lower()
179
180 @pytest.mark.parametrize('key, default', _bool_settings)
181 def test_bool_setting_true(self, key, default):
182 settings = {key: 'true'}
183 _sanitize_vcs_settings(settings)
184 assert settings[key] is True
185
186 @pytest.mark.parametrize('key, default', _bool_settings)
187 def test_bool_setting_false(self, key, default):
188 settings = {key: 'false'}
189 _sanitize_vcs_settings(settings)
190 assert settings[key] is False
191
192 @pytest.mark.parametrize('key, default', _bool_settings)
193 def test_bool_setting_invalid_string(self, key, default):
194 settings = {key: 'no-bool-val-string'}
195 _sanitize_vcs_settings(settings)
196 assert settings[key] is False
197
198 @pytest.mark.parametrize('key, default', _bool_settings)
199 def test_bool_setting_default(self, key, default):
200 settings = {}
201 _sanitize_vcs_settings(settings)
202 assert settings[key] is default
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,41 b''
1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 #
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import mock
20 import decorator
21
22
23 class EventCatcher(object):
24 """ Testing context manager to check if events are fired """
25
26 def __init__(self):
27 self.events = [] # the actual events captured
28 self.events_types = [] # the types of events captured
29
30 def __enter__(self):
31 self.event_trigger_patch = mock.patch('rhodecode.events.trigger')
32 self.mocked_event_trigger = self.event_trigger_patch.start()
33 return self
34
35 def __exit__(self, type_, value, traceback):
36 self.event_trigger_patch.stop()
37
38 for call in self.mocked_event_trigger.call_args_list:
39 event = call[0][0]
40 self.events.append(event)
41 self.events_types.append(type(event))
@@ -0,0 +1,93 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.tests.events.conftest import EventCatcher
24
25 from rhodecode.model.comment import ChangesetCommentsModel
26 from rhodecode.model.pull_request import PullRequestModel
27 from rhodecode.events import (
28 PullRequestCreateEvent,
29 PullRequestUpdateEvent,
30 PullRequestCommentEvent,
31 PullRequestReviewEvent,
32 PullRequestMergeEvent,
33 PullRequestCloseEvent,
34 )
35
36 # TODO: dan: make the serialization tests complete json comparisons
37 @pytest.mark.backends("git", "hg")
38 @pytest.mark.parametrize('EventClass', [
39 PullRequestCreateEvent,
40 PullRequestUpdateEvent,
41 PullRequestReviewEvent,
42 PullRequestMergeEvent,
43 PullRequestCloseEvent,
44 ])
45 def test_pullrequest_events_serialized(pr_util, EventClass):
46 pr = pr_util.create_pull_request()
47 event = EventClass(pr)
48 data = event.as_dict()
49 assert data['name'] == EventClass.name
50 assert data['repo']['repo_name'] == pr.target_repo.repo_name
51 assert data['pullrequest']['pull_request_id'] == pr.pull_request_id
52 assert data['pullrequest']['url']
53
54 @pytest.mark.backends("git", "hg")
55 def test_create_pull_request_events(pr_util):
56 with EventCatcher() as event_catcher:
57 pr_util.create_pull_request()
58
59 assert PullRequestCreateEvent in event_catcher.events_types
60
61 @pytest.mark.backends("git", "hg")
62 def test_pullrequest_comment_events_serialized(pr_util):
63 pr = pr_util.create_pull_request()
64 comment = ChangesetCommentsModel().get_comments(
65 pr.target_repo.repo_id, pull_request=pr)[0]
66 event = PullRequestCommentEvent(pr, comment)
67 data = event.as_dict()
68 assert data['name'] == PullRequestCommentEvent.name
69 assert data['repo']['repo_name'] == pr.target_repo.repo_name
70 assert data['pullrequest']['pull_request_id'] == pr.pull_request_id
71 assert data['pullrequest']['url']
72 assert data['comment']['text'] == comment.text
73
74
75 @pytest.mark.backends("git", "hg")
76 def test_close_pull_request_events(pr_util, user_admin):
77 pr = pr_util.create_pull_request()
78
79 with EventCatcher() as event_catcher:
80 PullRequestModel().close_pull_request(pr, user_admin)
81
82 assert PullRequestCloseEvent in event_catcher.events_types
83
84
85 @pytest.mark.backends("git", "hg")
86 def test_close_pull_request_with_comment_events(pr_util, user_admin):
87 pr = pr_util.create_pull_request()
88
89 with EventCatcher() as event_catcher:
90 PullRequestModel().close_pull_request_with_comment(
91 pr, user_admin, pr.target_repo)
92
93 assert PullRequestCloseEvent in event_catcher.events_types
@@ -0,0 +1,116 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.tests.events.conftest import EventCatcher
24
25 from rhodecode.lib import hooks_base, utils2
26 from rhodecode.model.repo import RepoModel
27 from rhodecode.events.repo import (
28 RepoPrePullEvent, RepoPullEvent,
29 RepoPrePushEvent, RepoPushEvent,
30 RepoPreCreateEvent, RepoCreateEvent,
31 RepoPreDeleteEvent, RepoDeleteEvent,
32 )
33
34
35 @pytest.fixture
36 def scm_extras(user_regular, repo_stub):
37 extras = utils2.AttributeDict({
38 'ip': '127.0.0.1',
39 'username': user_regular.username,
40 'action': '',
41 'repository': repo_stub.repo_name,
42 'scm': repo_stub.scm_instance().alias,
43 'config': '',
44 'server_url': 'http://example.com',
45 'make_lock': None,
46 'locked_by': [None],
47 'commit_ids': ['a' * 40] * 3,
48 })
49 return extras
50
51
52 # TODO: dan: make the serialization tests complete json comparisons
53 @pytest.mark.parametrize('EventClass', [
54 RepoPreCreateEvent, RepoCreateEvent,
55 RepoPreDeleteEvent, RepoDeleteEvent,
56 ])
57 def test_repo_events_serialized(repo_stub, EventClass):
58 event = EventClass(repo_stub)
59 data = event.as_dict()
60 assert data['name'] == EventClass.name
61 assert data['repo']['repo_name'] == repo_stub.repo_name
62 assert data['repo']['url']
63
64
65 @pytest.mark.parametrize('EventClass', [
66 RepoPrePullEvent, RepoPullEvent, RepoPrePushEvent
67 ])
68 def test_vcs_repo_events_serialize(repo_stub, scm_extras, EventClass):
69 event = EventClass(repo_name=repo_stub.repo_name, extras=scm_extras)
70 data = event.as_dict()
71 assert data['name'] == EventClass.name
72 assert data['repo']['repo_name'] == repo_stub.repo_name
73 assert data['repo']['url']
74
75
76
77 @pytest.mark.parametrize('EventClass', [RepoPushEvent])
78 def test_vcs_repo_push_event_serialize(repo_stub, scm_extras, EventClass):
79 event = EventClass(repo_name=repo_stub.repo_name,
80 pushed_commit_ids=scm_extras['commit_ids'],
81 extras=scm_extras)
82 data = event.as_dict()
83 assert data['name'] == EventClass.name
84 assert data['repo']['repo_name'] == repo_stub.repo_name
85 assert data['repo']['url']
86
87
88 def test_create_delete_repo_fires_events(backend):
89 with EventCatcher() as event_catcher:
90 repo = backend.create_repo()
91 assert event_catcher.events_types == [RepoPreCreateEvent, RepoCreateEvent]
92
93 with EventCatcher() as event_catcher:
94 RepoModel().delete(repo)
95 assert event_catcher.events_types == [RepoPreDeleteEvent, RepoDeleteEvent]
96
97
98 def test_pull_fires_events(scm_extras):
99 with EventCatcher() as event_catcher:
100 hooks_base.pre_push(scm_extras)
101 assert event_catcher.events_types == [RepoPrePushEvent]
102
103 with EventCatcher() as event_catcher:
104 hooks_base.post_push(scm_extras)
105 assert event_catcher.events_types == [RepoPushEvent]
106
107
108 def test_push_fires_events(scm_extras):
109 with EventCatcher() as event_catcher:
110 hooks_base.pre_pull(scm_extras)
111 assert event_catcher.events_types == [RepoPrePullEvent]
112
113 with EventCatcher() as event_catcher:
114 hooks_base.post_pull(scm_extras)
115 assert event_catcher.events_types == [RepoPullEvent]
116
@@ -0,0 +1,78 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22 import requests
23 from mock import Mock, patch
24
25 from rhodecode import events
26 from rhodecode.model.db import Session, Integration
27 from rhodecode.model.integration import IntegrationModel
28 from rhodecode.integrations.types.base import IntegrationTypeBase
29
30
31 class TestIntegrationType(IntegrationTypeBase):
32 """ Test integration type class """
33
34 key = 'test-integration'
35 display_name = 'Test integration type'
36
37 def __init__(self, settings):
38 super(IntegrationTypeBase, self).__init__(settings)
39 self.sent_events = [] # for testing
40
41 def send_event(self, event):
42 self.sent_events.append(event)
43
44
45 @pytest.fixture
46 def repo_integration_stub(request, repo_stub):
47 settings = {'test_key': 'test_value'}
48 integration = IntegrationModel().create(
49 TestIntegrationType, settings=settings, repo=repo_stub, enabled=True,
50 name='test repo integration')
51
52 @request.addfinalizer
53 def cleanup():
54 IntegrationModel().delete(integration)
55
56 return integration
57
58
59 @pytest.fixture
60 def global_integration_stub(request):
61 settings = {'test_key': 'test_value'}
62 integration = IntegrationModel().create(
63 TestIntegrationType, settings=settings, enabled=True,
64 name='test global integration')
65
66 @request.addfinalizer
67 def cleanup():
68 IntegrationModel().delete(integration)
69
70 return integration
71
72
73 def test_delete_repo_with_integration_deletes_integration(repo_integration_stub):
74 Session().delete(repo_integration_stub.repo)
75 Session().commit()
76 Session().expire_all()
77 assert Integration.get(repo_integration_stub.integration_id) is None
78
@@ -0,0 +1,95 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22 import requests
23 from mock import Mock, patch
24
25 from rhodecode import events
26 from rhodecode.model.db import Session, Integration
27 from rhodecode.integrations.types.slack import SlackIntegrationType
28
29 @pytest.fixture
30 def repo_push_event(backend, user_regular):
31 commits = [
32 {'message': 'ancestor commit fixes #15'},
33 {'message': 'quick fixes'},
34 {'message': 'change that fixes #41, #2'},
35 {'message': 'this is because 5b23c3532 broke stuff'},
36 {'message': 'last commit'},
37 ]
38 commit_ids = backend.create_master_repo(commits).values()
39 repo = backend.create_repo()
40 scm_extras = {
41 'ip': '127.0.0.1',
42 'username': user_regular.username,
43 'action': '',
44 'repository': repo.repo_name,
45 'scm': repo.scm_instance().alias,
46 'config': '',
47 'server_url': 'http://example.com',
48 'make_lock': None,
49 'locked_by': [None],
50 'commit_ids': commit_ids,
51 }
52
53 return events.RepoPushEvent(repo_name=repo.repo_name,
54 pushed_commit_ids=commit_ids,
55 extras=scm_extras)
56
57
58 @pytest.fixture
59 def slack_settings():
60 return {
61 "service": "mock://slackintegration",
62 "events": [
63 "pullrequest-create",
64 "repo-push",
65 ],
66 "channel": "#testing",
67 "icon_emoji": ":recycle:",
68 "username": "rhodecode-test"
69 }
70
71
72 @pytest.fixture
73 def slack_integration(request, app, slack_settings):
74 integration = Integration()
75 integration.name = 'test slack integration'
76 integration.enabled = True
77 integration.integration_type = SlackIntegrationType.key
78 integration.settings = slack_settings
79 Session().add(integration)
80 Session().commit()
81 request.addfinalizer(lambda: Session().delete(integration))
82 return integration
83
84
85 def test_slack_push(slack_integration, repo_push_event):
86 with patch('rhodecode.integrations.types.slack.post_text_to_slack') as call:
87 events.trigger(repo_push_event)
88 assert 'pushed to' in call.call_args[0][1]
89
90 slack_integration.settings['events'] = []
91 Session().commit()
92
93 with patch('rhodecode.integrations.types.slack.post_text_to_slack') as call:
94 events.trigger(repo_push_event)
95 assert not call.call_args
@@ -0,0 +1,45 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pickle
22 import pytest
23
24 from rhodecode.lib.jsonalchemy import MutationDict, MutationList
25
26
27 def test_mutation_dict_is_picklable():
28 mutation_dict = MutationDict({'key1': 'value1', 'key2': 'value2'})
29 dumped = pickle.dumps(mutation_dict)
30 loaded = pickle.loads(dumped)
31 assert loaded == mutation_dict
32
33 def test_mutation_list_is_picklable():
34 mutation_list = MutationList(['a', 'b', 'c'])
35 dumped = pickle.dumps(mutation_list)
36 loaded = pickle.loads(dumped)
37 assert loaded == mutation_list
38
39 def test_mutation_dict_with_lists_is_picklable():
40 mutation_dict = MutationDict({
41 'key': MutationList(['values', MutationDict({'key': 'value'})])
42 })
43 dumped = pickle.dumps(mutation_dict)
44 loaded = pickle.loads(dumped)
45 assert loaded == mutation_dict
1 NO CONTENT: new file 100644
@@ -0,0 +1,100 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import colander
22 import pytest
23
24 from rhodecode.model import validation_schema
25 from rhodecode.model.validation_schema.schemas import gist_schema
26
27
28 class TestGistSchema(object):
29
30 def test_deserialize_bad_data(self):
31 schema = gist_schema.GistSchema().bind(
32 lifetime_options=[1, 2, 3]
33 )
34 with pytest.raises(validation_schema.Invalid) as exc_info:
35 schema.deserialize('err')
36 err = exc_info.value.asdict()
37 assert err[''] == '"err" is not a mapping type: ' \
38 'Does not implement dict-like functionality.'
39
40 def test_deserialize_bad_lifetime_options(self):
41 schema = gist_schema.GistSchema().bind(
42 lifetime_options=[1, 2, 3]
43 )
44 with pytest.raises(validation_schema.Invalid) as exc_info:
45 schema.deserialize(dict(
46 lifetime=10
47 ))
48 err = exc_info.value.asdict()
49 assert err['lifetime'] == '"10" is not one of 1, 2, 3'
50
51 with pytest.raises(validation_schema.Invalid) as exc_info:
52 schema.deserialize(dict(
53 lifetime='x'
54 ))
55 err = exc_info.value.asdict()
56 assert err['lifetime'] == '"x" is not a number'
57
58 def test_serialize_data_correctly(self):
59 schema = gist_schema.GistSchema().bind(
60 lifetime_options=[1, 2, 3]
61 )
62 nodes = [{
63 'filename': 'foobar',
64 'filename_org': 'foobar',
65 'content': 'content',
66 'mimetype': 'xx'
67 }]
68 schema_data = schema.deserialize(dict(
69 lifetime=2,
70 gist_type='public',
71 gist_acl_level='acl_public',
72 nodes=nodes,
73 ))
74
75 assert schema_data['nodes'] == nodes
76
77 def test_serialize_data_correctly_with_conversion(self):
78 schema = gist_schema.GistSchema().bind(
79 lifetime_options=[1, 2, 3],
80 convert_nodes=True
81 )
82 nodes = [{
83 'filename': 'foobar',
84 'filename_org': None,
85 'content': 'content',
86 'mimetype': 'xx'
87 }]
88 schema_data = schema.deserialize(dict(
89 lifetime=2,
90 gist_type='public',
91 gist_acl_level='acl_public',
92 nodes=nodes,
93 ))
94
95 assert schema_data['nodes'] == nodes
96
97 seq_nodes = gist_schema.sequence_to_nodes(nodes)
98 assert isinstance(seq_nodes, dict)
99 seq_nodes = gist_schema.nodes_to_sequence(seq_nodes)
100 assert nodes == seq_nodes
@@ -0,0 +1,189 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from mock import call, patch
24
25 from rhodecode.lib.vcs.backends.base import Reference
26
27
28 class TestMercurialRemoteRepoInvalidation(object):
29 """
30 If the VCSServer is running with multiple processes or/and instances.
31 Operations on repositories are potentially handled by different processes
32 in a random fashion. The mercurial repository objects used in the VCSServer
33 are caching the commits of the repo. Therefore we have to invalidate the
34 VCSServer caching of these objects after a writing operation.
35 """
36
37 # Default reference used as a dummy during tests.
38 default_ref = Reference('branch', 'default', None)
39
40 # Methods of vcsserver.hg.HgRemote that are "writing" operations.
41 writing_methods = [
42 'bookmark',
43 'commit',
44 'merge',
45 'pull',
46 'pull_cmd',
47 'rebase',
48 'strip',
49 'tag',
50 ]
51
52 @pytest.mark.parametrize('method_name, method_args', [
53 ('_local_merge', [default_ref, None, None, None, default_ref]),
54 ('_local_pull', ['', default_ref]),
55 ('bookmark', [None]),
56 ('pull', ['', default_ref]),
57 ('remove_tag', ['mytag', None]),
58 ('strip', [None]),
59 ('tag', ['newtag', None]),
60 ])
61 def test_method_invokes_invalidate_on_remote_repo(
62 self, method_name, method_args, backend_hg):
63 """
64 Check that the listed methods are invalidating the VCSServer cache
65 after invoking a writing method of their remote repository object.
66 """
67 tags = {'mytag': 'mytag-id'}
68
69 def add_tag(name, raw_id, *args, **kwds):
70 tags[name] = raw_id
71
72 repo = backend_hg.repo.scm_instance()
73 with patch.object(repo, '_remote') as remote:
74 remote.lookup.return_value = ('commit-id', 'commit-idx')
75 remote.tags.return_value = tags
76 remote._get_tags.return_value = tags
77 remote.tag.side_effect = add_tag
78
79 # Invoke method.
80 method = getattr(repo, method_name)
81 method(*method_args)
82
83 # Assert that every "writing" method is followed by an invocation
84 # of the cache invalidation method.
85 for counter, method_call in enumerate(remote.method_calls):
86 call_name = method_call[0]
87 if call_name in self.writing_methods:
88 next_call = remote.method_calls[counter + 1]
89 assert next_call == call.invalidate_vcs_cache()
90
91 def _prepare_shadow_repo(self, pull_request):
92 """
93 Helper that creates a shadow repo that can be used to reproduce the
94 CommitDoesNotExistError when pulling in from target and source
95 references.
96 """
97 from rhodecode.model.pull_request import PullRequestModel
98
99 target_vcs = pull_request.target_repo.scm_instance()
100 target_ref = pull_request.target_ref_parts
101 source_ref = pull_request.source_ref_parts
102
103 # Create shadow repository.
104 pr = PullRequestModel()
105 workspace_id = pr._workspace_id(pull_request)
106 shadow_repository_path = target_vcs._maybe_prepare_merge_workspace(
107 workspace_id, target_ref)
108 shadow_repo = target_vcs._get_shadow_instance(shadow_repository_path)
109
110 # This will populate the cache of the mercurial repository object
111 # inside of the VCSServer.
112 shadow_repo.get_commit()
113
114 return shadow_repo, source_ref, target_ref
115
116 @pytest.mark.backends('hg')
117 def test_commit_does_not_exist_error_happens(self, pr_util, pylonsapp):
118 """
119 This test is somewhat special. It does not really test the system
120 instead it is more or less a precondition for the
121 "test_commit_does_not_exist_error_does_not_happen". It deactivates the
122 cache invalidation and asserts that the error occurs.
123 """
124 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
125
126 if pylonsapp.config['vcs.server.protocol'] == 'pyro4':
127 pytest.skip('Test is intended for the HTTP protocol only.')
128
129 pull_request = pr_util.create_pull_request()
130 target_vcs = pull_request.target_repo.scm_instance()
131 source_vcs = pull_request.source_repo.scm_instance()
132 shadow_repo, source_ref, target_ref = self._prepare_shadow_repo(
133 pull_request)
134
135 # Pull from target and source references but without invalidation of
136 # RemoteRepo objects and without VCSServer caching of mercurial
137 # repository objects.
138 with patch.object(shadow_repo._remote, 'invalidate_vcs_cache'):
139 # NOTE: Do not use patch.dict() to disable the cache because it
140 # restores the WHOLE dict and not only the patched keys.
141 shadow_repo._remote._wire['cache'] = False
142 shadow_repo._local_pull(target_vcs.path, target_ref)
143 shadow_repo._local_pull(source_vcs.path, source_ref)
144 shadow_repo._remote._wire.pop('cache')
145
146 # Try to lookup the target_ref in shadow repo. This should work because
147 # the shadow repo is a clone of the target and always contains all off
148 # it's commits in the initial cache.
149 shadow_repo.get_commit(target_ref.commit_id)
150
151 # If we try to lookup the source_ref it should fail because the shadow
152 # repo commit cache doesn't get invalidated. (Due to patched
153 # invalidation and caching above).
154 with pytest.raises(CommitDoesNotExistError):
155 shadow_repo.get_commit(source_ref.commit_id)
156
157 @pytest.mark.backends('hg')
158 def test_commit_does_not_exist_error_does_not_happen(
159 self, pr_util, pylonsapp):
160 """
161 This test simulates a pull request merge in which the pull operations
162 are handled by a different VCSServer process than all other operations.
163 Without correct cache invalidation this leads to an error when
164 retrieving the pulled commits afterwards.
165 """
166 if pylonsapp.config['vcs.server.protocol'] == 'pyro4':
167 pytest.skip('Test is intended for the HTTP protocol only.')
168
169 pull_request = pr_util.create_pull_request()
170 target_vcs = pull_request.target_repo.scm_instance()
171 source_vcs = pull_request.source_repo.scm_instance()
172 shadow_repo, source_ref, target_ref = self._prepare_shadow_repo(
173 pull_request)
174
175 # Pull from target and source references without without VCSServer
176 # caching of mercurial repository objects but with active invalidation
177 # of RemoteRepo objects.
178 # NOTE: Do not use patch.dict() to disable the cache because it
179 # restores the WHOLE dict and not only the patched keys.
180 shadow_repo._remote._wire['cache'] = False
181 shadow_repo._local_pull(target_vcs.path, target_ref)
182 shadow_repo._local_pull(source_vcs.path, source_ref)
183 shadow_repo._remote._wire.pop('cache')
184
185 # Try to lookup the target and source references in shadow repo. This
186 # should work because the RemoteRepo object gets invalidated during the
187 # above pull operations.
188 shadow_repo.get_commit(target_ref.commit_id)
189 shadow_repo.get_commit(source_ref.commit_id)
@@ -1,5 +1,5 b''
1 1 [bumpversion]
2 current_version = 4.2.1
2 current_version = 4.3.0
3 3 message = release: Bump version {current_version} to {new_version}
4 4
5 5 [bumpversion:file:rhodecode/VERSION]
@@ -25,6 +25,7 b' syntax: regexp'
25 25 ^build/
26 26 ^coverage\.xml$
27 27 ^data$
28 ^\.eggs/
28 29 ^configs/data$
29 30 ^dev.ini$
30 31 ^acceptance_tests/dev.*\.ini$
@@ -4,26 +4,21 b' done = false'
4 4 [task:bump_version]
5 5 done = true
6 6
7 [task:rc_tools_pinned]
8 done = true
9
10 7 [task:fixes_on_stable]
11 done = true
12 8
13 9 [task:pip2nix_generated]
14 done = true
15 10
16 11 [task:changelog_updated]
17 done = true
18 12
19 13 [task:generate_api_docs]
20 done = true
14
15 [task:updated_translation]
21 16
22 17 [release]
23 state = prepared
24 version = 4.2.1
18 state = in_progress
19 version = 4.3.0
25 20
26 [task:updated_translation]
21 [task:rc_tools_pinned]
27 22
28 23 [task:generate_js_routes]
29 24
@@ -20,6 +20,7 b' module.exports = function(grunt) {'
20 20 '<%= dirs.js.src %>/moment.js',
21 21 '<%= dirs.js.src %>/appenlight-client-0.4.1.min.js',
22 22 '<%= dirs.js.src %>/i18n_utils.js',
23 '<%= dirs.js.src %>/deform.js',
23 24
24 25 // Plugins
25 26 '<%= dirs.js.src %>/plugins/jquery.pjax.js',
@@ -31,6 +32,7 b' module.exports = function(grunt) {'
31 32 '<%= dirs.js.src %>/plugins/jquery.mark.js',
32 33 '<%= dirs.js.src %>/plugins/jquery.timeago.js',
33 34 '<%= dirs.js.src %>/plugins/jquery.timeago-extension.js',
35 '<%= dirs.js.src %>/plugins/toastr.js',
34 36
35 37 // Select2
36 38 '<%= dirs.js.src %>/select2/select2.js',
@@ -56,12 +58,14 b' module.exports = function(grunt) {'
56 58 '<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js',
57 59 '<%= dirs.js.src %>/rhodecode/utils/ie.js',
58 60 '<%= dirs.js.src %>/rhodecode/utils/os.js',
61 '<%= dirs.js.src %>/rhodecode/utils/topics.js',
59 62
60 63 // Rhodecode widgets
61 64 '<%= dirs.js.src %>/rhodecode/widgets/multiselect.js',
62 65
63 66 // Rhodecode components
64 67 '<%= dirs.js.src %>/rhodecode/init.js',
68 '<%= dirs.js.src %>/rhodecode/connection_controller.js',
65 69 '<%= dirs.js.src %>/rhodecode/codemirror.js',
66 70 '<%= dirs.js.src %>/rhodecode/comments.js',
67 71 '<%= dirs.js.src %>/rhodecode/constants.js',
@@ -76,6 +80,7 b' module.exports = function(grunt) {'
76 80 '<%= dirs.js.src %>/rhodecode/select2_widgets.js',
77 81 '<%= dirs.js.src %>/rhodecode/tooltips.js',
78 82 '<%= dirs.js.src %>/rhodecode/users.js',
83 '<%= dirs.js.src %>/rhodecode/utils/notifications.js',
79 84 '<%= dirs.js.src %>/rhodecode/appenlight.js',
80 85
81 86 // Rhodecode main module
@@ -10,8 +10,10 b' permission notice:'
10 10 file:
11 11 Copyright (c) 2008-2011 - msgpack-python
12 12 file:licenses/msgpack_license.txt
13 Copyright (c) 2009 - tornado
14 file:licenses/tornado_license.txt
13 15
14 Both licensed under the Apache License, Version 2.0 (the "License");
16 All licensed under the Apache License, Version 2.0 (the "License");
15 17 you may not use this file except in compliance with the License.
16 18 You may obtain a copy of the License at
17 19
@@ -2,20 +2,20 b''
2 2 include test.ini
3 3 include MANIFEST.in
4 4 include README.rst
5 include CHANGES.rst
6 include LICENSE.txt
7
5 8 include rhodecode/VERSION
6 9
7 10 # docs
8 11 recursive-include docs *
9 12
10 # init.d
11 recursive-include init.d *
13 # all config files
14 recursive-include configs *
12 15
13 16 # translations
14 17 recursive-include rhodecode/i18n *
15 18
16 # bin stuff
17 recursive-include rhodecode/bin *
18
19 19 # hook templates
20 20 recursive-include rhodecode/config/hook_templates *
21 21
@@ -20,7 +20,7 b' ci-docs: docs;'
20 20 clean: test-clean
21 21 find . -type f \( -iname '*.c' -o -iname '*.pyc' -o -iname '*.so' \) -exec rm '{}' ';'
22 22
23 test: test-clean test-lint test-only
23 test: test-clean test-only
24 24
25 25 test-clean:
26 26 rm -rf coverage.xml htmlcov junit.xml pylint.log result
@@ -1,23 +1,36 b''
1 ################################################################################
1
2
2 3 ################################################################################
3 # RhodeCode Enterprise - configuration file #
4 # Built-in functions and variables #
4 ## RHODECODE ENTERPRISE CONFIGURATION ##
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 # #
7 6 ################################################################################
8 7
9 8 [DEFAULT]
10 9 debug = true
10
11 11 ################################################################################
12 ## EMAIL CONFIGURATION ##
12 13 ## Uncomment and replace with the email address which should receive ##
13 14 ## any error reports after an application crash ##
14 15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 16 ################################################################################
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
20
21 ## email FROM address all mails will be sent
18 22 #app_email_from = rhodecode-noreply@localhost
23
24 ## Uncomment and replace with the address which should receive any error report
25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 #email_to = admin@localhost
27
28 ## in case of Application errors, sent an error email form
29 #error_email_from = rhodecode_error@localhost
30
31 ## additional error message to be send in case of server crash
19 32 #error_message =
20 #email_prefix = [RhodeCode]
33
21 34
22 35 #smtp_server = mail.server.com
23 36 #smtp_username =
@@ -37,6 +50,7 b' port = 5000'
37 50 ## WAITRESS WSGI SERVER ##
38 51 ## Recommended for Development ##
39 52 ##################################
53
40 54 use = egg:waitress#main
41 55 ## number of worker threads
42 56 threads = 5
@@ -51,6 +65,7 b' asyncore_use_poll = true'
51 65 ## GUNICORN WSGI SERVER ##
52 66 ##########################
53 67 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
68
54 69 #use = egg:gunicorn#main
55 70 ## Sets the number of process workers. You must set `instance_id = *`
56 71 ## when this option is set to more than one worker, recommended
@@ -77,15 +92,18 b' asyncore_use_poll = true'
77 92
78 93
79 94 ## prefix middleware for RhodeCode, disables force_https flag.
95 ## recommended when using proxy setup.
80 96 ## allows to set RhodeCode under a prefix in server.
81 97 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 #[filter:proxy-prefix]
83 #use = egg:PasteDeploy#prefix
84 #prefix = /<your-prefix>
98 ## optionally set prefix like: `prefix = /<your-prefix>`
99 [filter:proxy-prefix]
100 use = egg:PasteDeploy#prefix
101 prefix = /
85 102
86 103 [app:main]
87 104 use = egg:rhodecode-enterprise-ce
88 ## enable proxy prefix middleware, defined below
105
106 ## enable proxy prefix middleware, defined above
89 107 #filter-with = proxy-prefix
90 108
91 109 # During development the we want to have the debug toolbar enabled
@@ -123,12 +141,10 b' rhodecode.api.url = /_admin/api'
123 141 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
124 142 #rhodecode.encrypted_values.strict = false
125 143
126 full_stack = true
144 ## return gzipped responses from Rhodecode (static files/application)
145 gzip_responses = false
127 146
128 ## Serve static files via RhodeCode, disable to serve them via HTTP server
129 static_files = true
130
131 # autogenerate javascript routes file on startup
147 ## autogenerate javascript routes file on startup
132 148 generate_js_files = false
133 149
134 150 ## Optional Languages
@@ -317,8 +333,8 b' beaker.cache.sql_cache_short.type = memo'
317 333 beaker.cache.sql_cache_short.expire = 10
318 334 beaker.cache.sql_cache_short.key_length = 256
319 335
320 # default is memory cache, configure only if required
321 # using multi-node or multi-worker setup
336 ## default is memory cache, configure only if required
337 ## using multi-node or multi-worker setup
322 338 #beaker.cache.auth_plugins.type = ext:database
323 339 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
324 340 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
@@ -331,8 +347,8 b' beaker.cache.repo_cache_long.type = memo'
331 347 beaker.cache.repo_cache_long.max_items = 4096
332 348 beaker.cache.repo_cache_long.expire = 2592000
333 349
334 # default is memorylru_base cache, configure only if required
335 # using multi-node or multi-worker setup
350 ## default is memorylru_base cache, configure only if required
351 ## using multi-node or multi-worker setup
336 352 #beaker.cache.repo_cache_long.type = ext:memcached
337 353 #beaker.cache.repo_cache_long.url = localhost:11211
338 354 #beaker.cache.repo_cache_long.expire = 1209600
@@ -347,7 +363,7 b' beaker.cache.repo_cache_long.expire = 25'
347 363 beaker.session.type = file
348 364 beaker.session.data_dir = %(here)s/data/sessions/data
349 365
350 ## db based session, fast, and allows easy management over logged in users ##
366 ## db based session, fast, and allows easy management over logged in users
351 367 #beaker.session.type = ext:database
352 368 #beaker.session.table_name = db_session
353 369 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
@@ -368,6 +384,7 b' beaker.session.lock_dir = %(here)s/data/'
368 384 ## accessed for given amount of time in seconds
369 385 beaker.session.timeout = 2592000
370 386 beaker.session.httponly = true
387 ## Path to use for the cookie.
371 388 #beaker.session.cookie_path = /<your-prefix>
372 389
373 390 ## uncomment for https secure cookie
@@ -391,6 +408,23 b' beaker.session.auto = false'
391 408 search.module = rhodecode.lib.index.whoosh
392 409 search.location = %(here)s/data/index
393 410
411 ########################################
412 ### CHANNELSTREAM CONFIG ####
413 ########################################
414 ## channelstream enables persistent connections and live notification
415 ## in the system. It's also used by the chat system
416
417 channelstream.enabled = true
418 ## location of channelstream server on the backend
419 channelstream.server = 127.0.0.1:9800
420 ## location of the channelstream server from outside world
421 ## most likely this would be an http server special backend URL, that handles
422 ## websocket connections see nginx example for config
423 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
424 channelstream.secret = secret
425 channelstream.history.location = %(here)s/channelstream_history
426
427
394 428 ###################################
395 429 ## APPENLIGHT CONFIG ##
396 430 ###################################
@@ -466,9 +500,10 b' debug_style = true'
466 500 #########################################################
467 501 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
468 502 #########################################################
469 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
503 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
470 504 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
471 505 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
506 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
472 507
473 508 # see sqlalchemy docs for other advanced settings
474 509
@@ -498,28 +533,53 b' vcs.server = localhost:9900'
498 533 ## Available protocols are:
499 534 ## `pyro4` - using pyro4 server
500 535 ## `http` - using http-rpc backend
501 #vcs.server.protocol = http
536 vcs.server.protocol = http
502 537
503 538 ## Push/Pull operations protocol, available options are:
504 539 ## `pyro4` - using pyro4 server
505 540 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
506 541 ## `vcsserver.scm_app` - internal app (EE only)
507 #vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
542 vcs.scm_app_implementation = rhodecode.lib.middleware.utils.scm_app_http
508 543
509 544 ## Push/Pull operations hooks protocol, available options are:
510 545 ## `pyro4` - using pyro4 server
511 546 ## `http` - using http-rpc backend
512 #vcs.hooks.protocol = http
547 vcs.hooks.protocol = http
513 548
514 549 vcs.server.log_level = debug
515 550 ## Start VCSServer with this instance as a subprocess, usefull for development
516 551 vcs.start_server = true
552
553 ## List of enabled VCS backends, available options are:
554 ## `hg` - mercurial
555 ## `git` - git
556 ## `svn` - subversion
517 557 vcs.backends = hg, git, svn
558
518 559 vcs.connection_timeout = 3600
519 560 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
520 561 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
521 562 #vcs.svn.compatible_version = pre-1.8-compatible
522 563
564
565 ############################################################
566 ### Subversion proxy support (mod_dav_svn) ###
567 ### Maps RhodeCode repo groups into SVN paths for Apache ###
568 ############################################################
569 ## Enable or disable the config file generation.
570 svn.proxy.generate_config = false
571 ## Generate config file with `SVNListParentPath` set to `On`.
572 svn.proxy.list_parent_path = true
573 ## Set location and file name of generated config file.
574 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
575 ## File system path to the directory containing the repositories served by
576 ## RhodeCode.
577 svn.proxy.parent_path_root = /path/to/repo_store
578 ## Used as a prefix to the <Location> block in the generated config file. In
579 ## most cases it should be set to `/`.
580 svn.proxy.location_root = /
581
582
523 583 ################################
524 584 ### LOGGING CONFIGURATION ####
525 585 ################################
@@ -1,23 +1,36 b''
1 ################################################################################
1
2
2 3 ################################################################################
3 # RhodeCode Enterprise - configuration file #
4 # Built-in functions and variables #
4 ## RHODECODE ENTERPRISE CONFIGURATION ##
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 # #
7 6 ################################################################################
8 7
9 8 [DEFAULT]
10 9 debug = true
10
11 11 ################################################################################
12 ## EMAIL CONFIGURATION ##
12 13 ## Uncomment and replace with the email address which should receive ##
13 14 ## any error reports after an application crash ##
14 15 ## Additionally these settings will be used by the RhodeCode mailing system ##
15 16 ################################################################################
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
20
21 ## email FROM address all mails will be sent
18 22 #app_email_from = rhodecode-noreply@localhost
23
24 ## Uncomment and replace with the address which should receive any error report
25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 #email_to = admin@localhost
27
28 ## in case of Application errors, sent an error email form
29 #error_email_from = rhodecode_error@localhost
30
31 ## additional error message to be send in case of server crash
19 32 #error_message =
20 #email_prefix = [RhodeCode]
33
21 34
22 35 #smtp_server = mail.server.com
23 36 #smtp_username =
@@ -37,6 +50,7 b' port = 5000'
37 50 ## WAITRESS WSGI SERVER ##
38 51 ## Recommended for Development ##
39 52 ##################################
53
40 54 #use = egg:waitress#main
41 55 ## number of worker threads
42 56 #threads = 5
@@ -51,6 +65,7 b' port = 5000'
51 65 ## GUNICORN WSGI SERVER ##
52 66 ##########################
53 67 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
68
54 69 use = egg:gunicorn#main
55 70 ## Sets the number of process workers. You must set `instance_id = *`
56 71 ## when this option is set to more than one worker, recommended
@@ -77,15 +92,18 b' timeout = 21600'
77 92
78 93
79 94 ## prefix middleware for RhodeCode, disables force_https flag.
95 ## recommended when using proxy setup.
80 96 ## allows to set RhodeCode under a prefix in server.
81 97 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
82 #[filter:proxy-prefix]
83 #use = egg:PasteDeploy#prefix
84 #prefix = /<your-prefix>
98 ## optionally set prefix like: `prefix = /<your-prefix>`
99 [filter:proxy-prefix]
100 use = egg:PasteDeploy#prefix
101 prefix = /
85 102
86 103 [app:main]
87 104 use = egg:rhodecode-enterprise-ce
88 ## enable proxy prefix middleware, defined below
105
106 ## enable proxy prefix middleware, defined above
89 107 #filter-with = proxy-prefix
90 108
91 109 ## encryption key used to encrypt social plugin tokens,
@@ -97,12 +115,10 b' use = egg:rhodecode-enterprise-ce'
97 115 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
98 116 #rhodecode.encrypted_values.strict = false
99 117
100 full_stack = true
118 ## return gzipped responses from Rhodecode (static files/application)
119 gzip_responses = false
101 120
102 ## Serve static files via RhodeCode, disable to serve them via HTTP server
103 static_files = true
104
105 # autogenerate javascript routes file on startup
121 ## autogenerate javascript routes file on startup
106 122 generate_js_files = false
107 123
108 124 ## Optional Languages
@@ -291,8 +307,8 b' beaker.cache.sql_cache_short.type = memo'
291 307 beaker.cache.sql_cache_short.expire = 10
292 308 beaker.cache.sql_cache_short.key_length = 256
293 309
294 # default is memory cache, configure only if required
295 # using multi-node or multi-worker setup
310 ## default is memory cache, configure only if required
311 ## using multi-node or multi-worker setup
296 312 #beaker.cache.auth_plugins.type = ext:database
297 313 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
298 314 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
@@ -305,8 +321,8 b' beaker.cache.repo_cache_long.type = memo'
305 321 beaker.cache.repo_cache_long.max_items = 4096
306 322 beaker.cache.repo_cache_long.expire = 2592000
307 323
308 # default is memorylru_base cache, configure only if required
309 # using multi-node or multi-worker setup
324 ## default is memorylru_base cache, configure only if required
325 ## using multi-node or multi-worker setup
310 326 #beaker.cache.repo_cache_long.type = ext:memcached
311 327 #beaker.cache.repo_cache_long.url = localhost:11211
312 328 #beaker.cache.repo_cache_long.expire = 1209600
@@ -321,7 +337,7 b' beaker.cache.repo_cache_long.expire = 25'
321 337 beaker.session.type = file
322 338 beaker.session.data_dir = %(here)s/data/sessions/data
323 339
324 ## db based session, fast, and allows easy management over logged in users ##
340 ## db based session, fast, and allows easy management over logged in users
325 341 #beaker.session.type = ext:database
326 342 #beaker.session.table_name = db_session
327 343 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
@@ -342,6 +358,7 b' beaker.session.lock_dir = %(here)s/data/'
342 358 ## accessed for given amount of time in seconds
343 359 beaker.session.timeout = 2592000
344 360 beaker.session.httponly = true
361 ## Path to use for the cookie.
345 362 #beaker.session.cookie_path = /<your-prefix>
346 363
347 364 ## uncomment for https secure cookie
@@ -365,6 +382,23 b' beaker.session.auto = false'
365 382 search.module = rhodecode.lib.index.whoosh
366 383 search.location = %(here)s/data/index
367 384
385 ########################################
386 ### CHANNELSTREAM CONFIG ####
387 ########################################
388 ## channelstream enables persistent connections and live notification
389 ## in the system. It's also used by the chat system
390
391 channelstream.enabled = true
392 ## location of channelstream server on the backend
393 channelstream.server = 127.0.0.1:9800
394 ## location of the channelstream server from outside world
395 ## most likely this would be an http server special backend URL, that handles
396 ## websocket connections see nginx example for config
397 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
398 channelstream.secret = secret
399 channelstream.history.location = %(here)s/channelstream_history
400
401
368 402 ###################################
369 403 ## APPENLIGHT CONFIG ##
370 404 ###################################
@@ -436,8 +470,9 b' set debug = false'
436 470 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
437 471 #########################################################
438 472 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db?timeout=30
473 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
474 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
439 475 sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode
440 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode
441 476
442 477 # see sqlalchemy docs for other advanced settings
443 478
@@ -483,12 +518,37 b' vcs.server = localhost:9900'
483 518 vcs.server.log_level = info
484 519 ## Start VCSServer with this instance as a subprocess, usefull for development
485 520 vcs.start_server = false
521
522 ## List of enabled VCS backends, available options are:
523 ## `hg` - mercurial
524 ## `git` - git
525 ## `svn` - subversion
486 526 vcs.backends = hg, git, svn
527
487 528 vcs.connection_timeout = 3600
488 529 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
489 530 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
490 531 #vcs.svn.compatible_version = pre-1.8-compatible
491 532
533
534 ############################################################
535 ### Subversion proxy support (mod_dav_svn) ###
536 ### Maps RhodeCode repo groups into SVN paths for Apache ###
537 ############################################################
538 ## Enable or disable the config file generation.
539 svn.proxy.generate_config = false
540 ## Generate config file with `SVNListParentPath` set to `On`.
541 svn.proxy.list_parent_path = true
542 ## Set location and file name of generated config file.
543 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
544 ## File system path to the directory containing the repositories served by
545 ## RhodeCode.
546 svn.proxy.parent_path_root = /path/to/repo_store
547 ## Used as a prefix to the <Location> block in the generated config file. In
548 ## most cases it should be set to `/`.
549 svn.proxy.location_root = /
550
551
492 552 ################################
493 553 ### LOGGING CONFIGURATION ####
494 554 ################################
@@ -123,8 +123,9 b' let'
123 123 # pkgs/default.nix?
124 124 passthru = {
125 125 inherit
126 pythonLocalOverrides
127 myPythonPackagesUnfix;
126 linkNodeModules
127 myPythonPackagesUnfix
128 pythonLocalOverrides;
128 129 pythonPackages = self;
129 130 };
130 131
@@ -165,6 +166,7 b' let'
165 166 ln -s ${self.supervisor}/bin/supervisor* $out/bin/
166 167 ln -s ${self.gunicorn}/bin/gunicorn $out/bin/
167 168 ln -s ${self.PasteScript}/bin/paster $out/bin/
169 ln -s ${self.channelstream}/bin/channelstream $out/bin/
168 170 ln -s ${self.pyramid}/bin/* $out/bin/ #*/
169 171
170 172 # rhodecode-tools
@@ -9,9 +9,9 b' Here is a sample configuration file for '
9 9 ServerName hg.myserver.com
10 10 ServerAlias hg.myserver.com
11 11
12 ## uncomment root directive if you want to serve static files by
13 ## Apache requires static_files = false in .ini file
14 #DocumentRoot /path/to/rhodecode/installation/public
12 ## uncomment to serve static files by Apache
13 ## ProxyPass /_static/rhodecode !
14 ## Alias /_static/rhodecode /path/to/.rccontrol/enterprise-1/static
15 15
16 16 <Proxy *>
17 17 Order allow,deny
@@ -16,17 +16,18 b' Use the following example to configure A'
16 16 In addition to the regular Apache setup you will need to add the following
17 17 lines into the ``rhodecode.ini`` file.
18 18
19 * Above ``[app:main]`` section of the ``rhodecode.ini`` file add the
20 following section if it doesn't exist yet.
21
22 .. code-block:: ini
23
24 [filter:proxy-prefix]
25 use = egg:PasteDeploy#prefix
26 prefix = /<someprefix> # Change <someprefix> into your chosen prefix
27
19 28 * In the the ``[app:main]`` section of your ``rhodecode.ini`` file add the
20 29 following line.
21 30
22 31 .. code-block:: ini
23 32
24 33 filter-with = proxy-prefix
25
26 * At the end of the ``rhodecode.ini`` file add the following section.
27
28 .. code-block:: ini
29
30 [filter:proxy-prefix]
31 use = egg:PasteDeploy#prefix
32 prefix = /<someprefix> # Change <someprefix> into your chosen prefix
@@ -121,7 +121,7 b' then work on restoring any specific setu'
121 121 :ref:`indexing-ref` section for details.
122 122 * To reconfigure any extensions, copy the backed up extensions into the
123 123 :file:`/home/{user}/.rccontrol/{instance-id}/rcextensions` and also specify
124 any custom hooks if necessary. See the :ref:`integrations-ref` section for
124 any custom hooks if necessary. See the :ref:`extensions-hooks-ref` section for
125 125 details.
126 126
127 127 .. _Schrödinger's Backup: http://novabackup.novastor.com/blog/schrodingers-backup-good-bad-backup/
@@ -5,6 +5,11 b' Use the following example to configure N'
5 5
6 6 .. code-block:: nginx
7 7
8 log_format log_custom '$remote_addr - $remote_user [$time_local] '
9 '"$request" $status $body_bytes_sent '
10 '"$http_referer" "$http_user_agent" '
11 '$request_time $upstream_response_time $pipe';
12
8 13 upstream rc {
9 14
10 15 server 127.0.0.1:10002;
@@ -14,12 +19,12 b' Use the following example to configure N'
14 19 # server 127.0.0.1:10004;
15 20 }
16 21
17 ## gist alias
22 ## gist alias server, for serving nicer GIST urls
18 23
19 24 server {
20 25 listen 443;
21 26 server_name gist.myserver.com;
22 access_log /var/log/nginx/gist.access.log;
27 access_log /var/log/nginx/gist.access.log log_custom;
23 28 error_log /var/log/nginx/gist.error.log;
24 29
25 30 ssl on;
@@ -28,23 +33,36 b' Use the following example to configure N'
28 33
29 34 ssl_session_timeout 5m;
30 35
31 ssl_protocols SSLv3 TLSv1;
32 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
36 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
33 37 ssl_prefer_server_ciphers on;
38 ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
39
34 40 add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";
35 41
36 42 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
37 ssl_dhparam /etc/nginx/ssl/dhparam.pem;
43 #ssl_dhparam /etc/nginx/ssl/dhparam.pem;
38 44
39 45 rewrite ^/(.+)$ https://rhodecode.myserver.com/_admin/gists/$1;
40 46 rewrite (.*) https://rhodecode.myserver.com/_admin/gists;
41 47 }
42 48
49 ## HTTP to HTTPS rewrite
43 50 server {
44 listen 443;
51 listen 80;
45 52 server_name rhodecode.myserver.com;
46 access_log /var/log/nginx/rhodecode.access.log;
47 error_log /var/log/nginx/rhodecode.error.log;
53
54 if ($http_host = rhodecode.myserver.com) {
55 rewrite (.*) https://rhodecode.myserver.com$1 permanent;
56 }
57 }
58
59 ## MAIN SSL enabled server
60 server {
61 listen 443 ssl;
62 server_name rhodecode.myserver.com;
63
64 access_log /var/log/nginx/rhodecode.access.log log_custom;
65 error_log /var/log/nginx/rhodecode.error.log;
48 66
49 67 ssl on;
50 68 ssl_certificate rhodecode.myserver.com.crt;
@@ -52,21 +70,51 b' Use the following example to configure N'
52 70
53 71 ssl_session_timeout 5m;
54 72
55 ssl_protocols SSLv3 TLSv1;
56 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
73 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
57 74 ssl_prefer_server_ciphers on;
75 ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
76
77 # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
78 #ssl_dhparam /etc/nginx/ssl/dhparam.pem;
79
80 include /etc/nginx/proxy.conf;
81
82 ## serve static files by nginx, recommended
83 # location /_static/rhodecode {
84 # alias /path/to/.rccontrol/enterprise-1/static;
85 # }
58 86
59 ## uncomment root directive if you want to serve static files by nginx
60 ## requires static_files = false in .ini file
61 # root /path/to/rhodecode/installation/public;
87 ## channel stream live components
88 location /_channelstream {
89 rewrite /_channelstream/(.*) /$1 break;
90 proxy_pass http://127.0.0.1:9800;
62 91
63 include /etc/nginx/proxy.conf;
92 proxy_connect_timeout 10;
93 proxy_send_timeout 10m;
94 proxy_read_timeout 10m;
95 tcp_nodelay off;
96 proxy_set_header Host $host;
97 proxy_set_header X-Real-IP $remote_addr;
98 proxy_set_header X-Url-Scheme $scheme;
99 proxy_set_header X-Forwarded-Proto $scheme;
100 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
101 gzip off;
102 proxy_http_version 1.1;
103 proxy_set_header Upgrade $http_upgrade;
104 proxy_set_header Connection "upgrade";
105 }
64 106
65 107 location / {
66 108 try_files $uri @rhode;
67 109 }
68 110
69 location @rhode {
70 proxy_pass http://rc;
71 }
72 }
111 location @rhode {
112 proxy_pass http://rc;
113 }
114
115 ## custom 502 error page
116 error_page 502 /502.html;
117 location = /502.html {
118 root /path/to/.rccontrol/enterprise-1/static;
119 }
120 } No newline at end of file
@@ -15,7 +15,16 b' Use the following example to configure N'
15 15 }
16 16
17 17 In addition to the Nginx configuration you will need to add the following
18 lines into the ``rhodecode.ini`` file.
18 lines (if they not exist) into the ``rhodecode.ini`` file.
19
20 * Above ``[app:main]`` section of the ``rhodecode.ini`` file add the
21 following section if it doesn't exist yet.
22
23 .. code-block:: ini
24
25 [filter:proxy-prefix]
26 use = egg:PasteDeploy#prefix
27 prefix = /<someprefix> # Change <someprefix> into your chosen prefix
19 28
20 29 * In the the ``[app:main]`` section of your ``rhodecode.ini`` file add the
21 30 following line.
@@ -24,10 +33,4 b' lines into the ``rhodecode.ini`` file.'
24 33
25 34 filter-with = proxy-prefix
26 35
27 * At the end of the ``rhodecode.ini`` file add the following section.
28 36
29 .. code-block:: ini
30
31 [filter:proxy-prefix]
32 use = egg:PasteDeploy#prefix
33 prefix = /<someprefix> # Change <someprefix> into your chosen prefix
@@ -32,7 +32,7 b' Example Usage'
32 32 -------------
33 33
34 34 To use the extra fields in an extension, see the example below. For more
35 information and examples, see the :ref:`integrations-ref` section.
35 information and examples, see the :ref:`extensions-hooks-ref` section.
36 36
37 37 .. code-block:: python
38 38
@@ -30,7 +30,7 b' account permissions.'
30 30 # Use this example to change user permissions
31 31 In [1]: adminuser = User.get_by_username('username')
32 32 In [2]: adminuser.admin = True
33 In [3]: Session.add(adminuser);Session().commit()
33 In [3]: Session().add(adminuser);Session().commit()
34 34 In [4]: exit()
35 35
36 36 Set to read global ``.hgrc`` file
@@ -76,7 +76,7 b' following examples. For more |svn| infor'
76 76 .. code-block:: bash
77 77
78 78 # To clone a repository
79 svn clone http://my-svn-server.example.com/my-svn-repo
79 svn checkout http://my-svn-server.example.com/my-svn-repo
80 80
81 81 # svn commit
82 82 svn commit
This diff has been collapsed as it changes many lines, (2637 lines changed) Show them Hide them
@@ -194,2631 +194,14 b' are not required in args.'
194 194 ApiController.
195 195
196 196 .. --- API DEFS MARKER ---
197
198 pull
199 ----
200
201 .. py:function:: pull(apiuser, repoid)
202
203 Triggers a pull on the given repository from a remote location. You
204 can use this to keep remote repositories up-to-date.
205
206 This command can only be run using an |authtoken| with admin
207 rights to the specified repository. For more information,
208 see :ref:`config-token-ref`.
209
210 This command takes the following options:
211
212 :param apiuser: This is filled automatically from the |authtoken|.
213 :type apiuser: AuthUser
214 :param repoid: The repository name or repository ID.
215 :type repoid: str or int
216
217 Example output:
218
219 .. code-block:: bash
220
221 id : <id_given_in_input>
222 result : {
223 "msg": "Pulled from `<repository name>`"
224 "repository": "<repository name>"
225 }
226 error : null
227
228 Example error output:
229
230 .. code-block:: bash
231
232 id : <id_given_in_input>
233 result : null
234 error : {
235 "Unable to pull changes from `<reponame>`"
236 }
237
238
239 strip
240 -----
241
242 .. py:function:: strip(apiuser, repoid, revision, branch)
243
244 Strips the given revision from the specified repository.
245
246 * This will remove the revision and all of its decendants.
247
248 This command can only be run using an |authtoken| with admin rights to
249 the specified repository.
250
251 This command takes the following options:
252
253 :param apiuser: This is filled automatically from the |authtoken|.
254 :type apiuser: AuthUser
255 :param repoid: The repository name or repository ID.
256 :type repoid: str or int
257 :param revision: The revision you wish to strip.
258 :type revision: str
259 :param branch: The branch from which to strip the revision.
260 :type branch: str
261
262 Example output:
263
264 .. code-block:: bash
265
266 id : <id_given_in_input>
267 result : {
268 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
269 "repository": "<repository name>"
270 }
271 error : null
272
273 Example error output:
274
275 .. code-block:: bash
276
277 id : <id_given_in_input>
278 result : null
279 error : {
280 "Unable to strip commit <commit_hash> from repo `<repository name>`"
281 }
282
283
284 rescan_repos
285 ------------
286
287 .. py:function:: rescan_repos(apiuser, remove_obsolete=<Optional:False>)
288
289 Triggers a rescan of the specified repositories.
290
291 * If the ``remove_obsolete`` option is set, it also deletes repositories
292 that are found in the database but not on the file system, so called
293 "clean zombies".
294
295 This command can only be run using an |authtoken| with admin rights to
296 the specified repository.
297
298 This command takes the following options:
299
300 :param apiuser: This is filled automatically from the |authtoken|.
301 :type apiuser: AuthUser
302 :param remove_obsolete: Deletes repositories from the database that
303 are not found on the filesystem.
304 :type remove_obsolete: Optional(``True`` | ``False``)
305
306 Example output:
307
308 .. code-block:: bash
309
310 id : <id_given_in_input>
311 result : {
312 'added': [<added repository name>,...]
313 'removed': [<removed repository name>,...]
314 }
315 error : null
316
317 Example error output:
318
319 .. code-block:: bash
320
321 id : <id_given_in_input>
322 result : null
323 error : {
324 'Error occurred during rescan repositories action'
325 }
326
327
328 invalidate_cache
329 ----------------
330
331 .. py:function:: invalidate_cache(apiuser, repoid, delete_keys=<Optional:False>)
332
333 Invalidates the cache for the specified repository.
334
335 This command can only be run using an |authtoken| with admin rights to
336 the specified repository.
337
338 This command takes the following options:
339
340 :param apiuser: This is filled automatically from |authtoken|.
341 :type apiuser: AuthUser
342 :param repoid: Sets the repository name or repository ID.
343 :type repoid: str or int
344 :param delete_keys: This deletes the invalidated keys instead of
345 just flagging them.
346 :type delete_keys: Optional(``True`` | ``False``)
347
348 Example output:
349
350 .. code-block:: bash
351
352 id : <id_given_in_input>
353 result : {
354 'msg': Cache for repository `<repository name>` was invalidated,
355 'repository': <repository name>
356 }
357 error : null
358
359 Example error output:
360
361 .. code-block:: bash
362
363 id : <id_given_in_input>
364 result : null
365 error : {
366 'Error occurred during cache invalidation action'
367 }
368
369
370 lock
371 ----
372
373 .. py:function:: lock(apiuser, repoid, locked=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
374
375 Sets the lock state of the specified |repo| by the given user.
376 From more information, see :ref:`repo-locking`.
377
378 * If the ``userid`` option is not set, the repository is locked to the
379 user who called the method.
380 * If the ``locked`` parameter is not set, the current lock state of the
381 repository is displayed.
382
383 This command can only be run using an |authtoken| with admin rights to
384 the specified repository.
385
386 This command takes the following options:
387
388 :param apiuser: This is filled automatically from the |authtoken|.
389 :type apiuser: AuthUser
390 :param repoid: Sets the repository name or repository ID.
391 :type repoid: str or int
392 :param locked: Sets the lock state.
393 :type locked: Optional(``True`` | ``False``)
394 :param userid: Set the repository lock to this user.
395 :type userid: Optional(str or int)
396
397 Example error output:
398
399 .. code-block:: bash
400
401 id : <id_given_in_input>
402 result : {
403 'repo': '<reponame>',
404 'locked': <bool: lock state>,
405 'locked_since': <int: lock timestamp>,
406 'locked_by': <username of person who made the lock>,
407 'lock_reason': <str: reason for locking>,
408 'lock_state_changed': <bool: True if lock state has been changed in this request>,
409 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
410 or
411 'msg': 'Repo `<repository name>` not locked.'
412 or
413 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
414 }
415 error : null
416
417 Example error output:
418
419 .. code-block:: bash
420
421 id : <id_given_in_input>
422 result : null
423 error : {
424 'Error occurred locking repository `<reponame>`
425 }
426
427
428 get_locks
429 ---------
430
431 .. py:function:: get_locks(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
432
433 Displays all repositories locked by the specified user.
434
435 * If this command is run by a non-admin user, it returns
436 a list of |repos| locked by that user.
437
438 This command takes the following options:
439
440 :param apiuser: This is filled automatically from the |authtoken|.
441 :type apiuser: AuthUser
442 :param userid: Sets the userid whose list of locked |repos| will be
443 displayed.
444 :type userid: Optional(str or int)
445
446 Example output:
447
448 .. code-block:: bash
449
450 id : <id_given_in_input>
451 result : {
452 [repo_object, repo_object,...]
453 }
454 error : null
455
456
457 get_ip
458 ------
459
460 .. py:function:: get_ip(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
461
462 Displays the IP Address as seen from the |RCE| server.
463
464 * This command displays the IP Address, as well as all the defined IP
465 addresses for the specified user. If the ``userid`` is not set, the
466 data returned is for the user calling the method.
467
468 This command can only be run using an |authtoken| with admin rights to
469 the specified repository.
470
471 This command takes the following options:
472
473 :param apiuser: This is filled automatically from |authtoken|.
474 :type apiuser: AuthUser
475 :param userid: Sets the userid for which associated IP Address data
476 is returned.
477 :type userid: Optional(str or int)
478
479 Example output:
480
481 .. code-block:: bash
482
483 id : <id_given_in_input>
484 result : {
485 "server_ip_addr": "<ip_from_clien>",
486 "user_ips": [
487 {
488 "ip_addr": "<ip_with_mask>",
489 "ip_range": ["<start_ip>", "<end_ip>"],
490 },
491 ...
492 ]
493 }
494
495
496 show_ip
497 -------
498
499 .. py:function:: show_ip(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
500
501 Displays the IP Address as seen from the |RCE| server.
502
503 * This command displays the IP Address, as well as all the defined IP
504 addresses for the specified user. If the ``userid`` is not set, the
505 data returned is for the user calling the method.
506
507 This command can only be run using an |authtoken| with admin rights to
508 the specified repository.
509
510 This command takes the following options:
511
512 :param apiuser: This is filled automatically from |authtoken|.
513 :type apiuser: AuthUser
514 :param userid: Sets the userid for which associated IP Address data
515 is returned.
516 :type userid: Optional(str or int)
517
518 Example output:
519
520 .. code-block:: bash
521
522 id : <id_given_in_input>
523 result : {
524 "server_ip_addr": "<ip_from_clien>",
525 "user_ips": [
526 {
527 "ip_addr": "<ip_with_mask>",
528 "ip_range": ["<start_ip>", "<end_ip>"],
529 },
530 ...
531 ]
532 }
533
534
535 get_license_info
536 ----------------
537
538 .. py:function:: get_license_info(apiuser)
539
540 Returns the |RCE| license information.
541
542 :param apiuser: This is filled automatically from the |authtoken|.
543 :type apiuser: AuthUser
544
545 Example output:
546
547 .. code-block:: bash
548
549 id : <id_given_in_input>
550 result : {
551 'rhodecode_version': <rhodecode version>,
552 'token': <license token>,
553 'issued_to': <license owner>,
554 'issued_on': <license issue date>,
555 'expires_on': <license expiration date>,
556 'type': <license type>,
557 'users_limit': <license users limit>,
558 'key': <license key>
559 }
560 error : null
561
562
563 set_license_key
564 ---------------
565
566 .. py:function:: set_license_key(apiuser, key)
567
568 Sets the |RCE| license key.
569
570 :param apiuser: This is filled automatically from the |authtoken|.
571 :type apiuser: AuthUser
572 :param key: This is the license key to be set.
573 :type key: str
574
575 Example output:
576
577 .. code-block:: bash
578
579 id : <id_given_in_input>
580 result: {
581 "msg" : "updated license information",
582 "key": <key>
583 }
584 error: null
585
586 Example error output:
587
588 .. code-block:: bash
589
590 id : <id_given_in_input>
591 result : null
592 error : {
593 "license key is not valid"
594 or
595 "trial licenses cannot be uploaded"
596 or
597 "error occurred while updating license"
598 }
599
600
601 get_server_info
602 ---------------
603
604 .. py:function:: get_server_info(apiuser)
605
606 Returns the |RCE| server information.
607
608 This includes the running version of |RCE| and all installed
609 packages. This command takes the following options:
610
611 :param apiuser: This is filled automatically from the |authtoken|.
612 :type apiuser: AuthUser
613
614 Example output:
615
616 .. code-block:: bash
617
618 id : <id_given_in_input>
619 result : {
620 'modules': [<module name>,...]
621 'py_version': <python version>,
622 'platform': <platform type>,
623 'rhodecode_version': <rhodecode version>
624 }
625 error : null
626
627
628 get_user
629 --------
630
631 .. py:function:: get_user(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
632
633 Returns the information associated with a username or userid.
634
635 * If the ``userid`` is not set, this command returns the information
636 for the ``userid`` calling the method.
637
638 .. note::
639
640 Normal users may only run this command against their ``userid``. For
641 full privileges you must run this command using an |authtoken| with
642 admin rights.
643
644 This command takes the following options:
645
646 :param apiuser: This is filled automatically from the |authtoken|.
647 :type apiuser: AuthUser
648 :param userid: Sets the userid for which data will be returned.
649 :type userid: Optional(str or int)
650
651 Example output:
652
653 .. code-block:: bash
654
655 {
656 "error": null,
657 "id": <id>,
658 "result": {
659 "active": true,
660 "admin": false,
661 "api_key": "api-key",
662 "api_keys": [ list of keys ],
663 "email": "user@example.com",
664 "emails": [
665 "user@example.com"
666 ],
667 "extern_name": "rhodecode",
668 "extern_type": "rhodecode",
669 "firstname": "username",
670 "ip_addresses": [],
671 "language": null,
672 "last_login": "Timestamp",
673 "lastname": "surnae",
674 "permissions": {
675 "global": [
676 "hg.inherit_default_perms.true",
677 "usergroup.read",
678 "hg.repogroup.create.false",
679 "hg.create.none",
680 "hg.extern_activate.manual",
681 "hg.create.write_on_repogroup.false",
682 "hg.usergroup.create.false",
683 "group.none",
684 "repository.none",
685 "hg.register.none",
686 "hg.fork.repository"
687 ],
688 "repositories": { "username/example": "repository.write"},
689 "repositories_groups": { "user-group/repo": "group.none" },
690 "user_groups": { "user_group_name": "usergroup.read" }
691 },
692 "user_id": 32,
693 "username": "username"
694 }
695 }
696
697
698 get_users
699 ---------
700
701 .. py:function:: get_users(apiuser)
702
703 Lists all users in the |RCE| user database.
704
705 This command can only be run using an |authtoken| with admin rights to
706 the specified repository.
707
708 This command takes the following options:
709
710 :param apiuser: This is filled automatically from the |authtoken|.
711 :type apiuser: AuthUser
712
713 Example output:
714
715 .. code-block:: bash
716
717 id : <id_given_in_input>
718 result: [<user_object>, ...]
719 error: null
720
721
722 create_user
723 -----------
724
725 .. py:function:: create_user(apiuser, username, email, password=<Optional:''>, firstname=<Optional:''>, lastname=<Optional:''>, active=<Optional:True>, admin=<Optional:False>, extern_name=<Optional:'rhodecode'>, extern_type=<Optional:'rhodecode'>, force_password_change=<Optional:False>)
726
727 Creates a new user and returns the new user object.
728
729 This command can only be run using an |authtoken| with admin rights to
730 the specified repository.
731
732 This command takes the following options:
733
734 :param apiuser: This is filled automatically from the |authtoken|.
735 :type apiuser: AuthUser
736 :param username: Set the new username.
737 :type username: str or int
738 :param email: Set the user email address.
739 :type email: str
740 :param password: Set the new user password.
741 :type password: Optional(str)
742 :param firstname: Set the new user firstname.
743 :type firstname: Optional(str)
744 :param lastname: Set the new user surname.
745 :type lastname: Optional(str)
746 :param active: Set the user as active.
747 :type active: Optional(``True`` | ``False``)
748 :param admin: Give the new user admin rights.
749 :type admin: Optional(``True`` | ``False``)
750 :param extern_name: Set the authentication plugin name.
751 Using LDAP this is filled with LDAP UID.
752 :type extern_name: Optional(str)
753 :param extern_type: Set the new user authentication plugin.
754 :type extern_type: Optional(str)
755 :param force_password_change: Force the new user to change password
756 on next login.
757 :type force_password_change: Optional(``True`` | ``False``)
758
759 Example output:
760
761 .. code-block:: bash
762
763 id : <id_given_in_input>
764 result: {
765 "msg" : "created new user `<username>`",
766 "user": <user_obj>
767 }
768 error: null
769
770 Example error output:
771
772 .. code-block:: bash
773
774 id : <id_given_in_input>
775 result : null
776 error : {
777 "user `<username>` already exist"
778 or
779 "email `<email>` already exist"
780 or
781 "failed to create user `<username>`"
782 }
783
784
785 update_user
786 -----------
787
788 .. py:function:: update_user(apiuser, userid, username=<Optional:None>, email=<Optional:None>, password=<Optional:None>, firstname=<Optional:None>, lastname=<Optional:None>, active=<Optional:None>, admin=<Optional:None>, extern_type=<Optional:None>, extern_name=<Optional:None>)
789
790 Updates the details for the specified user, if that user exists.
791
792 This command can only be run using an |authtoken| with admin rights to
793 the specified repository.
794
795 This command takes the following options:
796
797 :param apiuser: This is filled automatically from |authtoken|.
798 :type apiuser: AuthUser
799 :param userid: Set the ``userid`` to update.
800 :type userid: str or int
801 :param username: Set the new username.
802 :type username: str or int
803 :param email: Set the new email.
804 :type email: str
805 :param password: Set the new password.
806 :type password: Optional(str)
807 :param firstname: Set the new first name.
808 :type firstname: Optional(str)
809 :param lastname: Set the new surname.
810 :type lastname: Optional(str)
811 :param active: Set the new user as active.
812 :type active: Optional(``True`` | ``False``)
813 :param admin: Give the user admin rights.
814 :type admin: Optional(``True`` | ``False``)
815 :param extern_name: Set the authentication plugin user name.
816 Using LDAP this is filled with LDAP UID.
817 :type extern_name: Optional(str)
818 :param extern_type: Set the authentication plugin type.
819 :type extern_type: Optional(str)
820
821
822 Example output:
823
824 .. code-block:: bash
825
826 id : <id_given_in_input>
827 result: {
828 "msg" : "updated user ID:<userid> <username>",
829 "user": <user_object>,
830 }
831 error: null
832
833 Example error output:
834
835 .. code-block:: bash
836
837 id : <id_given_in_input>
838 result : null
839 error : {
840 "failed to update user `<username>`"
841 }
842
843
844 delete_user
845 -----------
846
847 .. py:function:: delete_user(apiuser, userid)
848
849 Deletes the specified user from the |RCE| user database.
850
851 This command can only be run using an |authtoken| with admin rights to
852 the specified repository.
853
854 .. important::
855
856 Ensure all open pull requests and open code review
857 requests to this user are close.
858
859 Also ensure all repositories, or repository groups owned by this
860 user are reassigned before deletion.
861
862 This command takes the following options:
863
864 :param apiuser: This is filled automatically from the |authtoken|.
865 :type apiuser: AuthUser
866 :param userid: Set the user to delete.
867 :type userid: str or int
868
869 Example output:
870
871 .. code-block:: bash
872
873 id : <id_given_in_input>
874 result: {
875 "msg" : "deleted user ID:<userid> <username>",
876 "user": null
877 }
878 error: null
879
880 Example error output:
881
882 .. code-block:: bash
883
884 id : <id_given_in_input>
885 result : null
886 error : {
887 "failed to delete user ID:<userid> <username>"
888 }
889
890
891 get_user_group
892 --------------
893
894 .. py:function:: get_user_group(apiuser, usergroupid)
895
896 Returns the data of an existing user group.
897
898 This command can only be run using an |authtoken| with admin rights to
899 the specified repository.
900
901 :param apiuser: This is filled automatically from the |authtoken|.
902 :type apiuser: AuthUser
903 :param usergroupid: Set the user group from which to return data.
904 :type usergroupid: str or int
905
906 Example error output:
907
908 .. code-block:: bash
909
910 {
911 "error": null,
912 "id": <id>,
913 "result": {
914 "active": true,
915 "group_description": "group description",
916 "group_name": "group name",
917 "members": [
918 {
919 "name": "owner-name",
920 "origin": "owner",
921 "permission": "usergroup.admin",
922 "type": "user"
923 },
924 {
925 {
926 "name": "user name",
927 "origin": "permission",
928 "permission": "usergroup.admin",
929 "type": "user"
930 },
931 {
932 "name": "user group name",
933 "origin": "permission",
934 "permission": "usergroup.write",
935 "type": "user_group"
936 }
937 ],
938 "owner": "owner name",
939 "users": [],
940 "users_group_id": 2
941 }
942 }
943
944
945 get_user_groups
946 ---------------
947
948 .. py:function:: get_user_groups(apiuser)
949
950 Lists all the existing user groups within RhodeCode.
951
952 This command can only be run using an |authtoken| with admin rights to
953 the specified repository.
954
955 This command takes the following options:
956
957 :param apiuser: This is filled automatically from the |authtoken|.
958 :type apiuser: AuthUser
959
960 Example error output:
961
962 .. code-block:: bash
963
964 id : <id_given_in_input>
965 result : [<user_group_obj>,...]
966 error : null
967
968
969 create_user_group
970 -----------------
971
972 .. py:function:: create_user_group(apiuser, group_name, description=<Optional:''>, owner=<Optional:<OptionalAttr:apiuser>>, active=<Optional:True>)
973
974 Creates a new user group.
975
976 This command can only be run using an |authtoken| with admin rights to
977 the specified repository.
978
979 This command takes the following options:
980
981 :param apiuser: This is filled automatically from the |authtoken|.
982 :type apiuser: AuthUser
983 :param group_name: Set the name of the new user group.
984 :type group_name: str
985 :param description: Give a description of the new user group.
986 :type description: str
987 :param owner: Set the owner of the new user group.
988 If not set, the owner is the |authtoken| user.
989 :type owner: Optional(str or int)
990 :param active: Set this group as active.
991 :type active: Optional(``True`` | ``False``)
992
993 Example output:
994
995 .. code-block:: bash
996
997 id : <id_given_in_input>
998 result: {
999 "msg": "created new user group `<groupname>`",
1000 "user_group": <user_group_object>
1001 }
1002 error: null
1003
1004 Example error output:
1005
1006 .. code-block:: bash
1007
1008 id : <id_given_in_input>
1009 result : null
1010 error : {
1011 "user group `<group name>` already exist"
1012 or
1013 "failed to create group `<group name>`"
1014 }
1015
1016
1017 update_user_group
1018 -----------------
1019
1020 .. py:function:: update_user_group(apiuser, usergroupid, group_name=<Optional:''>, description=<Optional:''>, owner=<Optional:None>, active=<Optional:True>)
1021
1022 Updates the specified `user group` with the details provided.
1023
1024 This command can only be run using an |authtoken| with admin rights to
1025 the specified repository.
1026
1027 :param apiuser: This is filled automatically from the |authtoken|.
1028 :type apiuser: AuthUser
1029 :param usergroupid: Set the id of the `user group` to update.
1030 :type usergroupid: str or int
1031 :param group_name: Set the new name the `user group`
1032 :type group_name: str
1033 :param description: Give a description for the `user group`
1034 :type description: str
1035 :param owner: Set the owner of the `user group`.
1036 :type owner: Optional(str or int)
1037 :param active: Set the group as active.
1038 :type active: Optional(``True`` | ``False``)
1039
1040 Example output:
1041
1042 .. code-block:: bash
1043
1044 id : <id_given_in_input>
1045 result : {
1046 "msg": 'updated user group ID:<user group id> <user group name>',
1047 "user_group": <user_group_object>
1048 }
1049 error : null
1050
1051 Example error output:
1052
1053 .. code-block:: bash
1054
1055 id : <id_given_in_input>
1056 result : null
1057 error : {
1058 "failed to update user group `<user group name>`"
1059 }
1060
1061
1062 delete_user_group
1063 -----------------
1064
1065 .. py:function:: delete_user_group(apiuser, usergroupid)
1066
1067 Deletes the specified `user group`.
1068
1069 This command can only be run using an |authtoken| with admin rights to
1070 the specified repository.
1071
1072 This command takes the following options:
1073
1074 :param apiuser: filled automatically from apikey
1075 :type apiuser: AuthUser
1076 :param usergroupid:
1077 :type usergroupid: int
1078
1079 Example output:
1080
1081 .. code-block:: bash
1082
1083 id : <id_given_in_input>
1084 result : {
1085 "msg": "deleted user group ID:<user_group_id> <user_group_name>"
1086 }
1087 error : null
1088
1089 Example error output:
1090
1091 .. code-block:: bash
1092
1093 id : <id_given_in_input>
1094 result : null
1095 error : {
1096 "failed to delete user group ID:<user_group_id> <user_group_name>"
1097 or
1098 "RepoGroup assigned to <repo_groups_list>"
1099 }
1100
1101
1102 add_user_to_user_group
1103 ----------------------
1104
1105 .. py:function:: add_user_to_user_group(apiuser, usergroupid, userid)
1106
1107 Adds a user to a `user group`. If the user already exists in the group
1108 this command will return false.
1109
1110 This command can only be run using an |authtoken| with admin rights to
1111 the specified user group.
1112
1113 This command takes the following options:
1114
1115 :param apiuser: This is filled automatically from the |authtoken|.
1116 :type apiuser: AuthUser
1117 :param usergroupid: Set the name of the `user group` to which a
1118 user will be added.
1119 :type usergroupid: int
1120 :param userid: Set the `user_id` of the user to add to the group.
1121 :type userid: int
1122
1123 Example output:
1124
1125 .. code-block:: bash
1126
1127 id : <id_given_in_input>
1128 result : {
1129 "success": True|False # depends on if member is in group
1130 "msg": "added member `<username>` to user group `<groupname>` |
1131 User is already in that group"
1132
1133 }
1134 error : null
1135
1136 Example error output:
1137
1138 .. code-block:: bash
1139
1140 id : <id_given_in_input>
1141 result : null
1142 error : {
1143 "failed to add member to user group `<user_group_name>`"
1144 }
1145
1146
1147 remove_user_from_user_group
1148 ---------------------------
1149
1150 .. py:function:: remove_user_from_user_group(apiuser, usergroupid, userid)
1151
1152 Removes a user from a user group.
1153
1154 * If the specified user is not in the group, this command will return
1155 `false`.
1156
1157 This command can only be run using an |authtoken| with admin rights to
1158 the specified user group.
1159
1160 :param apiuser: This is filled automatically from the |authtoken|.
1161 :type apiuser: AuthUser
1162 :param usergroupid: Sets the user group name.
1163 :type usergroupid: str or int
1164 :param userid: The user you wish to remove from |RCE|.
1165 :type userid: str or int
1166
1167 Example output:
1168
1169 .. code-block:: bash
1170
1171 id : <id_given_in_input>
1172 result: {
1173 "success": True|False, # depends on if member is in group
1174 "msg": "removed member <username> from user group <groupname> |
1175 User wasn't in group"
1176 }
1177 error: null
1178
1179
1180 grant_user_permission_to_user_group
1181 -----------------------------------
1182
1183 .. py:function:: grant_user_permission_to_user_group(apiuser, usergroupid, userid, perm)
1184
1185 Set permissions for a user in a user group.
1186
1187 :param apiuser: This is filled automatically from the |authtoken|.
1188 :type apiuser: AuthUser
1189 :param usergroupid: Set the user group to edit permissions on.
1190 :type usergroupid: str or int
1191 :param userid: Set the user from whom you wish to set permissions.
1192 :type userid: str
1193 :param perm: (usergroup.(none|read|write|admin))
1194 :type perm: str
1195
1196 Example output:
1197
1198 .. code-block:: bash
1199
1200 id : <id_given_in_input>
1201 result : {
1202 "msg": "Granted perm: `<perm_name>` for user: `<username>` in user group: `<user_group_name>`",
1203 "success": true
1204 }
1205 error : null
1206
1207
1208 revoke_user_permission_from_user_group
1209 --------------------------------------
1210
1211 .. py:function:: revoke_user_permission_from_user_group(apiuser, usergroupid, userid)
1212
1213 Revoke a users permissions in a user group.
1214
1215 :param apiuser: This is filled automatically from the |authtoken|.
1216 :type apiuser: AuthUser
1217 :param usergroupid: Set the user group from which to revoke the user
1218 permissions.
1219 :type: usergroupid: str or int
1220 :param userid: Set the userid of the user whose permissions will be
1221 revoked.
1222 :type userid: str
1223
1224 Example output:
1225
1226 .. code-block:: bash
1227
1228 id : <id_given_in_input>
1229 result : {
1230 "msg": "Revoked perm for user: `<username>` in user group: `<user_group_name>`",
1231 "success": true
1232 }
1233 error : null
1234
1235
1236 grant_user_group_permission_to_user_group
1237 -----------------------------------------
1238
1239 .. py:function:: grant_user_group_permission_to_user_group(apiuser, usergroupid, sourceusergroupid, perm)
1240
1241 Give one user group permissions to another user group.
1242
1243 :param apiuser: This is filled automatically from the |authtoken|.
1244 :type apiuser: AuthUser
1245 :param usergroupid: Set the user group on which to edit permissions.
1246 :type usergroupid: str or int
1247 :param sourceusergroupid: Set the source user group to which
1248 access/permissions will be granted.
1249 :type sourceusergroupid: str or int
1250 :param perm: (usergroup.(none|read|write|admin))
1251 :type perm: str
1252
1253 Example output:
1254
1255 .. code-block:: bash
1256
1257 id : <id_given_in_input>
1258 result : {
1259 "msg": "Granted perm: `<perm_name>` for user group: `<source_user_group_name>` in user group: `<user_group_name>`",
1260 "success": true
1261 }
1262 error : null
1263
1264
1265 revoke_user_group_permission_from_user_group
1266 --------------------------------------------
1267
1268 .. py:function:: revoke_user_group_permission_from_user_group(apiuser, usergroupid, sourceusergroupid)
1269
1270 Revoke the permissions that one user group has to another.
1271
1272 :param apiuser: This is filled automatically from the |authtoken|.
1273 :type apiuser: AuthUser
1274 :param usergroupid: Set the user group on which to edit permissions.
1275 :type usergroupid: str or int
1276 :param sourceusergroupid: Set the user group from which permissions
1277 are revoked.
1278 :type sourceusergroupid: str or int
1279
1280 Example output:
1281
1282 .. code-block:: bash
1283
1284 id : <id_given_in_input>
1285 result : {
1286 "msg": "Revoked perm for user group: `<user_group_name>` in user group: `<target_user_group_name>`",
1287 "success": true
1288 }
1289 error : null
1290
1291
1292 get_pull_request
1293 ----------------
1294
1295 .. py:function:: get_pull_request(apiuser, repoid, pullrequestid)
1296
1297 Get a pull request based on the given ID.
1298
1299 :param apiuser: This is filled automatically from the |authtoken|.
1300 :type apiuser: AuthUser
1301 :param repoid: Repository name or repository ID from where the pull
1302 request was opened.
1303 :type repoid: str or int
1304 :param pullrequestid: ID of the requested pull request.
1305 :type pullrequestid: int
1306
1307 Example output:
1308
1309 .. code-block:: bash
1310
1311 "id": <id_given_in_input>,
1312 "result":
1313 {
1314 "pull_request_id": "<pull_request_id>",
1315 "url": "<url>",
1316 "title": "<title>",
1317 "description": "<description>",
1318 "status" : "<status>",
1319 "created_on": "<date_time_created>",
1320 "updated_on": "<date_time_updated>",
1321 "commit_ids": [
1322 ...
1323 "<commit_id>",
1324 "<commit_id>",
1325 ...
1326 ],
1327 "review_status": "<review_status>",
1328 "mergeable": {
1329 "status": "<bool>",
1330 "message": "<message>",
1331 },
1332 "source": {
1333 "clone_url": "<clone_url>",
1334 "repository": "<repository_name>",
1335 "reference":
1336 {
1337 "name": "<name>",
1338 "type": "<type>",
1339 "commit_id": "<commit_id>",
1340 }
1341 },
1342 "target": {
1343 "clone_url": "<clone_url>",
1344 "repository": "<repository_name>",
1345 "reference":
1346 {
1347 "name": "<name>",
1348 "type": "<type>",
1349 "commit_id": "<commit_id>",
1350 }
1351 },
1352 "author": <user_obj>,
1353 "reviewers": [
1354 ...
1355 {
1356 "user": "<user_obj>",
1357 "review_status": "<review_status>",
1358 }
1359 ...
1360 ]
1361 },
1362 "error": null
1363
1364
1365 get_pull_requests
1366 -----------------
1367
1368 .. py:function:: get_pull_requests(apiuser, repoid, status=<Optional:'new'>)
1369
1370 Get all pull requests from the repository specified in `repoid`.
1371
1372 :param apiuser: This is filled automatically from the |authtoken|.
1373 :type apiuser: AuthUser
1374 :param repoid: Repository name or repository ID.
1375 :type repoid: str or int
1376 :param status: Only return pull requests with the specified status.
1377 Valid options are.
1378 * ``new`` (default)
1379 * ``open``
1380 * ``closed``
1381 :type status: str
1382
1383 Example output:
1384
1385 .. code-block:: bash
1386
1387 "id": <id_given_in_input>,
1388 "result":
1389 [
1390 ...
1391 {
1392 "pull_request_id": "<pull_request_id>",
1393 "url": "<url>",
1394 "title" : "<title>",
1395 "description": "<description>",
1396 "status": "<status>",
1397 "created_on": "<date_time_created>",
1398 "updated_on": "<date_time_updated>",
1399 "commit_ids": [
1400 ...
1401 "<commit_id>",
1402 "<commit_id>",
1403 ...
1404 ],
1405 "review_status": "<review_status>",
1406 "mergeable": {
1407 "status": "<bool>",
1408 "message: "<message>",
1409 },
1410 "source": {
1411 "clone_url": "<clone_url>",
1412 "reference":
1413 {
1414 "name": "<name>",
1415 "type": "<type>",
1416 "commit_id": "<commit_id>",
1417 }
1418 },
1419 "target": {
1420 "clone_url": "<clone_url>",
1421 "reference":
1422 {
1423 "name": "<name>",
1424 "type": "<type>",
1425 "commit_id": "<commit_id>",
1426 }
1427 },
1428 "author": <user_obj>,
1429 "reviewers": [
1430 ...
1431 {
1432 "user": "<user_obj>",
1433 "review_status": "<review_status>",
1434 }
1435 ...
1436 ]
1437 }
1438 ...
1439 ],
1440 "error": null
1441
1442
1443 merge_pull_request
1444 ------------------
1445
1446 .. py:function:: merge_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
1447
1448 Merge the pull request specified by `pullrequestid` into its target
1449 repository.
1450
1451 :param apiuser: This is filled automatically from the |authtoken|.
1452 :type apiuser: AuthUser
1453 :param repoid: The Repository name or repository ID of the
1454 target repository to which the |pr| is to be merged.
1455 :type repoid: str or int
1456 :param pullrequestid: ID of the pull request which shall be merged.
1457 :type pullrequestid: int
1458 :param userid: Merge the pull request as this user.
1459 :type userid: Optional(str or int)
1460
1461 Example output:
1462
1463 .. code-block:: bash
1464
1465 "id": <id_given_in_input>,
1466 "result":
1467 {
1468 "executed": "<bool>",
1469 "failure_reason": "<int>",
1470 "merge_commit_id": "<merge_commit_id>",
1471 "possible": "<bool>"
1472 },
1473 "error": null
1474
1475
1476 close_pull_request
1477 ------------------
1478
1479 .. py:function:: close_pull_request(apiuser, repoid, pullrequestid, userid=<Optional:<OptionalAttr:apiuser>>)
1480
1481 Close the pull request specified by `pullrequestid`.
1482
1483 :param apiuser: This is filled automatically from the |authtoken|.
1484 :type apiuser: AuthUser
1485 :param repoid: Repository name or repository ID to which the pull
1486 request belongs.
1487 :type repoid: str or int
1488 :param pullrequestid: ID of the pull request to be closed.
1489 :type pullrequestid: int
1490 :param userid: Close the pull request as this user.
1491 :type userid: Optional(str or int)
1492
1493 Example output:
1494
1495 .. code-block:: bash
1496
1497 "id": <id_given_in_input>,
1498 "result":
1499 {
1500 "pull_request_id": "<int>",
1501 "closed": "<bool>"
1502 },
1503 "error": null
1504
1505
1506 comment_pull_request
1507 --------------------
1508
1509 .. py:function:: comment_pull_request(apiuser, repoid, pullrequestid, message=<Optional:None>, status=<Optional:None>, userid=<Optional:<OptionalAttr:apiuser>>)
197 .. toctree::
1510 198
1511 Comment on the pull request specified with the `pullrequestid`,
1512 in the |repo| specified by the `repoid`, and optionally change the
1513 review status.
1514
1515 :param apiuser: This is filled automatically from the |authtoken|.
1516 :type apiuser: AuthUser
1517 :param repoid: The repository name or repository ID.
1518 :type repoid: str or int
1519 :param pullrequestid: The pull request ID.
1520 :type pullrequestid: int
1521 :param message: The text content of the comment.
1522 :type message: str
1523 :param status: (**Optional**) Set the approval status of the pull
1524 request. Valid options are:
1525 * not_reviewed
1526 * approved
1527 * rejected
1528 * under_review
1529 :type status: str
1530 :param userid: Comment on the pull request as this user
1531 :type userid: Optional(str or int)
1532
1533 Example output:
1534
1535 .. code-block:: bash
1536
1537 id : <id_given_in_input>
1538 result :
1539 {
1540 "pull_request_id": "<Integer>",
1541 "comment_id": "<Integer>"
1542 }
1543 error : null
1544
1545
1546 create_pull_request
1547 -------------------
1548
1549 .. py:function:: create_pull_request(apiuser, source_repo, target_repo, source_ref, target_ref, title, description=<Optional:''>, reviewers=<Optional:None>)
1550
1551 Creates a new pull request.
1552
1553 Accepts refs in the following formats:
1554
1555 * branch:<branch_name>:<sha>
1556 * branch:<branch_name>
1557 * bookmark:<bookmark_name>:<sha> (Mercurial only)
1558 * bookmark:<bookmark_name> (Mercurial only)
1559
1560 :param apiuser: This is filled automatically from the |authtoken|.
1561 :type apiuser: AuthUser
1562 :param source_repo: Set the source repository name.
1563 :type source_repo: str
1564 :param target_repo: Set the target repository name.
1565 :type target_repo: str
1566 :param source_ref: Set the source ref name.
1567 :type source_ref: str
1568 :param target_ref: Set the target ref name.
1569 :type target_ref: str
1570 :param title: Set the pull request title.
1571 :type title: str
1572 :param description: Set the pull request description.
1573 :type description: Optional(str)
1574 :param reviewers: Set the new pull request reviewers list.
1575 :type reviewers: Optional(list)
1576
1577
1578 update_pull_request
1579 -------------------
1580
1581 .. py:function:: update_pull_request(apiuser, repoid, pullrequestid, title=<Optional:''>, description=<Optional:''>, reviewers=<Optional:None>, update_commits=<Optional:None>, close_pull_request=<Optional:None>)
1582
1583 Updates a pull request.
1584
1585 :param apiuser: This is filled automatically from the |authtoken|.
1586 :type apiuser: AuthUser
1587 :param repoid: The repository name or repository ID.
1588 :type repoid: str or int
1589 :param pullrequestid: The pull request ID.
1590 :type pullrequestid: int
1591 :param title: Set the pull request title.
1592 :type title: str
1593 :param description: Update pull request description.
1594 :type description: Optional(str)
1595 :param reviewers: Update pull request reviewers list with new value.
1596 :type reviewers: Optional(list)
1597 :param update_commits: Trigger update of commits for this pull request
1598 :type: update_commits: Optional(bool)
1599 :param close_pull_request: Close this pull request with rejected state
1600 :type: close_pull_request: Optional(bool)
1601
1602 Example output:
1603
1604 .. code-block:: bash
1605
1606 id : <id_given_in_input>
1607 result :
1608 {
1609 "msg": "Updated pull request `63`",
1610 "pull_request": <pull_request_object>,
1611 "updated_reviewers": {
1612 "added": [
1613 "username"
1614 ],
1615 "removed": []
1616 },
1617 "updated_commits": {
1618 "added": [
1619 "<sha1_hash>"
1620 ],
1621 "common": [
1622 "<sha1_hash>",
1623 "<sha1_hash>",
1624 ],
1625 "removed": []
1626 }
1627 }
1628 error : null
1629
1630
1631 get_repo
1632 --------
1633
1634 .. py:function:: get_repo(apiuser, repoid, cache=<Optional:True>)
1635
1636 Gets an existing repository by its name or repository_id.
1637
1638 The members section so the output returns users groups or users
1639 associated with that repository.
1640
1641 This command can only be run using an |authtoken| with admin rights,
1642 or users with at least read rights to the |repo|.
1643
1644 :param apiuser: This is filled automatically from the |authtoken|.
1645 :type apiuser: AuthUser
1646 :param repoid: The repository name or repository id.
1647 :type repoid: str or int
1648 :param cache: use the cached value for last changeset
1649 :type: cache: Optional(bool)
1650
1651 Example output:
1652
1653 .. code-block:: bash
1654
1655 {
1656 "error": null,
1657 "id": <repo_id>,
1658 "result": {
1659 "clone_uri": null,
1660 "created_on": "timestamp",
1661 "description": "repo description",
1662 "enable_downloads": false,
1663 "enable_locking": false,
1664 "enable_statistics": false,
1665 "followers": [
1666 {
1667 "active": true,
1668 "admin": false,
1669 "api_key": "****************************************",
1670 "api_keys": [
1671 "****************************************"
1672 ],
1673 "email": "user@example.com",
1674 "emails": [
1675 "user@example.com"
1676 ],
1677 "extern_name": "rhodecode",
1678 "extern_type": "rhodecode",
1679 "firstname": "username",
1680 "ip_addresses": [],
1681 "language": null,
1682 "last_login": "2015-09-16T17:16:35.854",
1683 "lastname": "surname",
1684 "user_id": <user_id>,
1685 "username": "name"
1686 }
1687 ],
1688 "fork_of": "parent-repo",
1689 "landing_rev": [
1690 "rev",
1691 "tip"
1692 ],
1693 "last_changeset": {
1694 "author": "User <user@example.com>",
1695 "branch": "default",
1696 "date": "timestamp",
1697 "message": "last commit message",
1698 "parents": [
1699 {
1700 "raw_id": "commit-id"
1701 }
1702 ],
1703 "raw_id": "commit-id",
1704 "revision": <revision number>,
1705 "short_id": "short id"
1706 },
1707 "lock_reason": null,
1708 "locked_by": null,
1709 "locked_date": null,
1710 "members": [
1711 {
1712 "name": "super-admin-name",
1713 "origin": "super-admin",
1714 "permission": "repository.admin",
1715 "type": "user"
1716 },
1717 {
1718 "name": "owner-name",
1719 "origin": "owner",
1720 "permission": "repository.admin",
1721 "type": "user"
1722 },
1723 {
1724 "name": "user-group-name",
1725 "origin": "permission",
1726 "permission": "repository.write",
1727 "type": "user_group"
1728 }
1729 ],
1730 "owner": "owner-name",
1731 "permissions": [
1732 {
1733 "name": "super-admin-name",
1734 "origin": "super-admin",
1735 "permission": "repository.admin",
1736 "type": "user"
1737 },
1738 {
1739 "name": "owner-name",
1740 "origin": "owner",
1741 "permission": "repository.admin",
1742 "type": "user"
1743 },
1744 {
1745 "name": "user-group-name",
1746 "origin": "permission",
1747 "permission": "repository.write",
1748 "type": "user_group"
1749 }
1750 ],
1751 "private": true,
1752 "repo_id": 676,
1753 "repo_name": "user-group/repo-name",
1754 "repo_type": "hg"
1755 }
1756 }
1757
1758
1759 get_repos
1760 ---------
1761
1762 .. py:function:: get_repos(apiuser)
1763
1764 Lists all existing repositories.
1765
1766 This command can only be run using an |authtoken| with admin rights,
1767 or users with at least read rights to |repos|.
1768
1769 :param apiuser: This is filled automatically from the |authtoken|.
1770 :type apiuser: AuthUser
1771
1772 Example output:
1773
1774 .. code-block:: bash
1775
1776 id : <id_given_in_input>
1777 result: [
1778 {
1779 "repo_id" : "<repo_id>",
1780 "repo_name" : "<reponame>"
1781 "repo_type" : "<repo_type>",
1782 "clone_uri" : "<clone_uri>",
1783 "private": : "<bool>",
1784 "created_on" : "<datetimecreated>",
1785 "description" : "<description>",
1786 "landing_rev": "<landing_rev>",
1787 "owner": "<repo_owner>",
1788 "fork_of": "<name_of_fork_parent>",
1789 "enable_downloads": "<bool>",
1790 "enable_locking": "<bool>",
1791 "enable_statistics": "<bool>",
1792 },
1793 ...
1794 ]
1795 error: null
1796
1797
1798 get_repo_changeset
1799 ------------------
1800
1801 .. py:function:: get_repo_changeset(apiuser, repoid, revision, details=<Optional:'basic'>)
1802
1803 Returns information about a changeset.
1804
1805 Additionally parameters define the amount of details returned by
1806 this function.
1807
1808 This command can only be run using an |authtoken| with admin rights,
1809 or users with at least read rights to the |repo|.
1810
1811 :param apiuser: This is filled automatically from the |authtoken|.
1812 :type apiuser: AuthUser
1813 :param repoid: The repository name or repository id
1814 :type repoid: str or int
1815 :param revision: revision for which listing should be done
1816 :type revision: str
1817 :param details: details can be 'basic|extended|full' full gives diff
1818 info details like the diff itself, and number of changed files etc.
1819 :type details: Optional(str)
1820
1821
1822 get_repo_changesets
1823 -------------------
1824
1825 .. py:function:: get_repo_changesets(apiuser, repoid, start_rev, limit, details=<Optional:'basic'>)
1826
1827 Returns a set of changesets limited by the number of commits starting
1828 from the `start_rev` option.
1829
1830 Additional parameters define the amount of details returned by this
1831 function.
1832
1833 This command can only be run using an |authtoken| with admin rights,
1834 or users with at least read rights to |repos|.
1835
1836 :param apiuser: This is filled automatically from the |authtoken|.
1837 :type apiuser: AuthUser
1838 :param repoid: The repository name or repository ID.
1839 :type repoid: str or int
1840 :param start_rev: The starting revision from where to get changesets.
1841 :type start_rev: str
1842 :param limit: Limit the number of changesets to this amount
1843 :type limit: str or int
1844 :param details: Set the level of detail returned. Valid option are:
1845 ``basic``, ``extended`` and ``full``.
1846 :type details: Optional(str)
1847
1848 .. note::
1849
1850 Setting the parameter `details` to the value ``full`` is extensive
1851 and returns details like the diff itself, and the number
1852 of changed files.
1853
1854
1855 get_repo_nodes
1856 --------------
1857
1858 .. py:function:: get_repo_nodes(apiuser, repoid, revision, root_path, ret_type=<Optional:'all'>, details=<Optional:'basic'>)
1859
1860 Returns a list of nodes and children in a flat list for a given
1861 path at given revision.
1862
1863 It's possible to specify ret_type to show only `files` or `dirs`.
1864
1865 This command can only be run using an |authtoken| with admin rights,
1866 or users with at least read rights to |repos|.
1867
1868 :param apiuser: This is filled automatically from the |authtoken|.
1869 :type apiuser: AuthUser
1870 :param repoid: The repository name or repository ID.
1871 :type repoid: str or int
1872 :param revision: The revision for which listing should be done.
1873 :type revision: str
1874 :param root_path: The path from which to start displaying.
1875 :type root_path: str
1876 :param ret_type: Set the return type. Valid options are
1877 ``all`` (default), ``files`` and ``dirs``.
1878 :type ret_type: Optional(str)
1879 :param details: Returns extended information about nodes, such as
1880 md5, binary, and or content. The valid options are ``basic`` and
1881 ``full``.
1882 :type details: Optional(str)
1883
1884 Example output:
1885
1886 .. code-block:: bash
1887
1888 id : <id_given_in_input>
1889 result: [
1890 {
1891 "name" : "<name>"
1892 "type" : "<type>",
1893 "binary": "<true|false>" (only in extended mode)
1894 "md5" : "<md5 of file content>" (only in extended mode)
1895 },
1896 ...
1897 ]
1898 error: null
1899
1900
1901 create_repo
1902 -----------
1903
1904 .. py:function:: create_repo(apiuser, repo_name, repo_type, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, copy_permissions=<Optional:False>)
1905
1906 Creates a repository.
1907
1908 * If the repository name contains "/", all the required repository
1909 groups will be created.
1910
1911 For example "foo/bar/baz" will create |repo| groups "foo" and "bar"
1912 (with "foo" as parent). It will also create the "baz" repository
1913 with "bar" as |repo| group.
1914
1915 This command can only be run using an |authtoken| with at least
1916 write permissions to the |repo|.
1917
1918 :param apiuser: This is filled automatically from the |authtoken|.
1919 :type apiuser: AuthUser
1920 :param repo_name: Set the repository name.
1921 :type repo_name: str
1922 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
1923 :type repo_type: str
1924 :param owner: user_id or username
1925 :type owner: Optional(str)
1926 :param description: Set the repository description.
1927 :type description: Optional(str)
1928 :param private:
1929 :type private: bool
1930 :param clone_uri:
1931 :type clone_uri: str
1932 :param landing_rev: <rev_type>:<rev>
1933 :type landing_rev: str
1934 :param enable_locking:
1935 :type enable_locking: bool
1936 :param enable_downloads:
1937 :type enable_downloads: bool
1938 :param enable_statistics:
1939 :type enable_statistics: bool
1940 :param copy_permissions: Copy permission from group in which the
1941 repository is being created.
1942 :type copy_permissions: bool
1943
1944
1945 Example output:
1946
1947 .. code-block:: bash
1948
1949 id : <id_given_in_input>
1950 result: {
1951 "msg": "Created new repository `<reponame>`",
1952 "success": true,
1953 "task": "<celery task id or None if done sync>"
1954 }
1955 error: null
1956
1957
1958 Example error output:
1959
1960 .. code-block:: bash
1961
1962 id : <id_given_in_input>
1963 result : null
1964 error : {
1965 'failed to create repository `<repo_name>`
1966 }
1967
1968
1969 add_field_to_repo
1970 -----------------
1971
1972 .. py:function:: add_field_to_repo(apiuser, repoid, key, label=<Optional:''>, description=<Optional:''>)
1973
1974 Adds an extra field to a repository.
1975
1976 This command can only be run using an |authtoken| with at least
1977 write permissions to the |repo|.
1978
1979 :param apiuser: This is filled automatically from the |authtoken|.
1980 :type apiuser: AuthUser
1981 :param repoid: Set the repository name or repository id.
1982 :type repoid: str or int
1983 :param key: Create a unique field key for this repository.
1984 :type key: str
1985 :param label:
1986 :type label: Optional(str)
1987 :param description:
1988 :type description: Optional(str)
1989
1990
1991 remove_field_from_repo
1992 ----------------------
1993
1994 .. py:function:: remove_field_from_repo(apiuser, repoid, key)
1995
1996 Removes an extra field from a repository.
1997
1998 This command can only be run using an |authtoken| with at least
1999 write permissions to the |repo|.
2000
2001 :param apiuser: This is filled automatically from the |authtoken|.
2002 :type apiuser: AuthUser
2003 :param repoid: Set the repository name or repository ID.
2004 :type repoid: str or int
2005 :param key: Set the unique field key for this repository.
2006 :type key: str
2007
2008
2009 update_repo
2010 -----------
2011
2012 .. py:function:: update_repo(apiuser, repoid, name=<Optional:None>, owner=<Optional:<OptionalAttr:apiuser>>, group=<Optional:None>, fork_of=<Optional:None>, description=<Optional:''>, private=<Optional:False>, clone_uri=<Optional:None>, landing_rev=<Optional:'rev:tip'>, enable_statistics=<Optional:False>, enable_locking=<Optional:False>, enable_downloads=<Optional:False>, fields=<Optional:''>)
2013
2014 Updates a repository with the given information.
2015
2016 This command can only be run using an |authtoken| with at least
2017 write permissions to the |repo|.
2018
2019 :param apiuser: This is filled automatically from the |authtoken|.
2020 :type apiuser: AuthUser
2021 :param repoid: repository name or repository ID.
2022 :type repoid: str or int
2023 :param name: Update the |repo| name.
2024 :type name: str
2025 :param owner: Set the |repo| owner.
2026 :type owner: str
2027 :param group: Set the |repo| group the |repo| belongs to.
2028 :type group: str
2029 :param fork_of: Set the master |repo| name.
2030 :type fork_of: str
2031 :param description: Update the |repo| description.
2032 :type description: str
2033 :param private: Set the |repo| as private. (True | False)
2034 :type private: bool
2035 :param clone_uri: Update the |repo| clone URI.
2036 :type clone_uri: str
2037 :param landing_rev: Set the |repo| landing revision. Default is
2038 ``tip``.
2039 :type landing_rev: str
2040 :param enable_statistics: Enable statistics on the |repo|,
2041 (True | False).
2042 :type enable_statistics: bool
2043 :param enable_locking: Enable |repo| locking.
2044 :type enable_locking: bool
2045 :param enable_downloads: Enable downloads from the |repo|,
2046 (True | False).
2047 :type enable_downloads: bool
2048 :param fields: Add extra fields to the |repo|. Use the following
2049 example format: ``field_key=field_val,field_key2=fieldval2``.
2050 Escape ', ' with \,
2051 :type fields: str
2052
2053
2054 fork_repo
2055 ---------
2056
2057 .. py:function:: fork_repo(apiuser, repoid, fork_name, owner=<Optional:<OptionalAttr:apiuser>>, description=<Optional:''>, copy_permissions=<Optional:False>, private=<Optional:False>, landing_rev=<Optional:'rev:tip'>)
2058
2059 Creates a fork of the specified |repo|.
2060
2061 * If using |RCE| with Celery this will immediately return a success
2062 message, even though the fork will be created asynchronously.
2063
2064 This command can only be run using an |authtoken| with fork
2065 permissions on the |repo|.
2066
2067 :param apiuser: This is filled automatically from the |authtoken|.
2068 :type apiuser: AuthUser
2069 :param repoid: Set repository name or repository ID.
2070 :type repoid: str or int
2071 :param fork_name: Set the fork name.
2072 :type fork_name: str
2073 :param owner: Set the fork owner.
2074 :type owner: str
2075 :param description: Set the fork descripton.
2076 :type description: str
2077 :param copy_permissions: Copy permissions from parent |repo|. The
2078 default is False.
2079 :type copy_permissions: bool
2080 :param private: Make the fork private. The default is False.
2081 :type private: bool
2082 :param landing_rev: Set the landing revision. The default is tip.
2083
2084 Example output:
2085
2086 .. code-block:: bash
2087
2088 id : <id_for_response>
2089 api_key : "<api_key>"
2090 args: {
2091 "repoid" : "<reponame or repo_id>",
2092 "fork_name": "<forkname>",
2093 "owner": "<username or user_id = Optional(=apiuser)>",
2094 "description": "<description>",
2095 "copy_permissions": "<bool>",
2096 "private": "<bool>",
2097 "landing_rev": "<landing_rev>"
2098 }
2099
2100 Example error output:
2101
2102 .. code-block:: bash
2103
2104 id : <id_given_in_input>
2105 result: {
2106 "msg": "Created fork of `<reponame>` as `<forkname>`",
2107 "success": true,
2108 "task": "<celery task id or None if done sync>"
2109 }
2110 error: null
2111
2112
2113 delete_repo
2114 -----------
2115
2116 .. py:function:: delete_repo(apiuser, repoid, forks=<Optional:''>)
2117
2118 Deletes a repository.
2119
2120 * When the `forks` parameter is set it's possible to detach or delete
2121 forks of deleted repository.
2122
2123 This command can only be run using an |authtoken| with admin
2124 permissions on the |repo|.
2125
2126 :param apiuser: This is filled automatically from the |authtoken|.
2127 :type apiuser: AuthUser
2128 :param repoid: Set the repository name or repository ID.
2129 :type repoid: str or int
2130 :param forks: Set to `detach` or `delete` forks from the |repo|.
2131 :type forks: Optional(str)
2132
2133 Example error output:
2134
2135 .. code-block:: bash
2136
2137 id : <id_given_in_input>
2138 result: {
2139 "msg": "Deleted repository `<reponame>`",
2140 "success": true
2141 }
2142 error: null
2143
2144
2145 comment_commit
2146 --------------
2147
2148 .. py:function:: comment_commit(apiuser, repoid, commit_id, message, userid=<Optional:<OptionalAttr:apiuser>>, status=<Optional:None>)
2149
2150 Set a commit comment, and optionally change the status of the commit.
2151 This command can be executed only using api_key belonging to user
2152 with admin rights, or repository administrator.
2153
2154 :param apiuser: This is filled automatically from the |authtoken|.
2155 :type apiuser: AuthUser
2156 :param repoid: Set the repository name or repository ID.
2157 :type repoid: str or int
2158 :param commit_id: Specify the commit_id for which to set a comment.
2159 :type commit_id: str
2160 :param message: The comment text.
2161 :type message: str
2162 :param userid: Set the user name of the comment creator.
2163 :type userid: Optional(str or int)
2164 :param status: status, one of 'not_reviewed', 'approved', 'rejected',
2165 'under_review'
2166 :type status: str
2167
2168 Example error output:
2169
2170 .. code-block:: json
2171
2172 {
2173 "id" : <id_given_in_input>,
2174 "result" : {
2175 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
2176 "status_change": null or <status>,
2177 "success": true
2178 },
2179 "error" : null
2180 }
2181
2182
2183 changeset_comment
2184 -----------------
2185
2186 .. py:function:: changeset_comment(apiuser, repoid, revision, message, userid=<Optional:<OptionalAttr:apiuser>>, status=<Optional:None>)
2187
2188 .. deprecated:: 3.4.0
2189
2190 Please use method `comment_commit` instead.
2191
2192
2193 Set a changeset comment, and optionally change the status of the
2194 changeset.
2195
2196 This command can only be run using an |authtoken| with admin
2197 permissions on the |repo|.
2198
2199 :param apiuser: This is filled automatically from the |authtoken|.
2200 :type apiuser: AuthUser
2201 :param repoid: Set the repository name or repository ID.
2202 :type repoid: str or int
2203 :param revision: Specify the revision for which to set a comment.
2204 :type revision: str
2205 :param message: The comment text.
2206 :type message: str
2207 :param userid: Set the user name of the comment creator.
2208 :type userid: Optional(str or int)
2209 :param status: Set the comment status. The following are valid options:
2210 * not_reviewed
2211 * approved
2212 * rejected
2213 * under_review
2214 :type status: str
2215
2216 Example error output:
2217
2218 .. code-block:: json
2219
2220 {
2221 "id" : <id_given_in_input>,
2222 "result" : {
2223 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
2224 "status_change": null or <status>,
2225 "success": true
2226 },
2227 "error" : null
2228 }
2229
2230
2231 grant_user_permission
2232 ---------------------
2233
2234 .. py:function:: grant_user_permission(apiuser, repoid, userid, perm)
2235
2236 Grant permissions for the specified user on the given repository,
2237 or update existing permissions if found.
2238
2239 This command can only be run using an |authtoken| with admin
2240 permissions on the |repo|.
2241
2242 :param apiuser: This is filled automatically from the |authtoken|.
2243 :type apiuser: AuthUser
2244 :param repoid: Set the repository name or repository ID.
2245 :type repoid: str or int
2246 :param userid: Set the user name.
2247 :type userid: str
2248 :param perm: Set the user permissions, using the following format
2249 ``(repository.(none|read|write|admin))``
2250 :type perm: str
2251
2252 Example output:
2253
2254 .. code-block:: bash
2255
2256 id : <id_given_in_input>
2257 result: {
2258 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
2259 "success": true
2260 }
2261 error: null
2262
2263
2264 revoke_user_permission
2265 ----------------------
2266
2267 .. py:function:: revoke_user_permission(apiuser, repoid, userid)
2268
2269 Revoke permission for a user on the specified repository.
2270
2271 This command can only be run using an |authtoken| with admin
2272 permissions on the |repo|.
2273
2274 :param apiuser: This is filled automatically from the |authtoken|.
2275 :type apiuser: AuthUser
2276 :param repoid: Set the repository name or repository ID.
2277 :type repoid: str or int
2278 :param userid: Set the user name of revoked user.
2279 :type userid: str or int
2280
2281 Example error output:
2282
2283 .. code-block:: bash
2284
2285 id : <id_given_in_input>
2286 result: {
2287 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
2288 "success": true
2289 }
2290 error: null
2291
2292
2293 grant_user_group_permission
2294 ---------------------------
2295
2296 .. py:function:: grant_user_group_permission(apiuser, repoid, usergroupid, perm)
2297
2298 Grant permission for a user group on the specified repository,
2299 or update existing permissions.
2300
2301 This command can only be run using an |authtoken| with admin
2302 permissions on the |repo|.
2303
2304 :param apiuser: This is filled automatically from the |authtoken|.
2305 :type apiuser: AuthUser
2306 :param repoid: Set the repository name or repository ID.
2307 :type repoid: str or int
2308 :param usergroupid: Specify the ID of the user group.
2309 :type usergroupid: str or int
2310 :param perm: Set the user group permissions using the following
2311 format: (repository.(none|read|write|admin))
2312 :type perm: str
2313
2314 Example output:
2315
2316 .. code-block:: bash
2317
2318 id : <id_given_in_input>
2319 result : {
2320 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
2321 "success": true
2322
2323 }
2324 error : null
2325
2326 Example error output:
2327
2328 .. code-block:: bash
2329
2330 id : <id_given_in_input>
2331 result : null
2332 error : {
2333 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
2334 }
2335
2336
2337 revoke_user_group_permission
2338 ----------------------------
2339
2340 .. py:function:: revoke_user_group_permission(apiuser, repoid, usergroupid)
2341
2342 Revoke the permissions of a user group on a given repository.
2343
2344 This command can only be run using an |authtoken| with admin
2345 permissions on the |repo|.
2346
2347 :param apiuser: This is filled automatically from the |authtoken|.
2348 :type apiuser: AuthUser
2349 :param repoid: Set the repository name or repository ID.
2350 :type repoid: str or int
2351 :param usergroupid: Specify the user group ID.
2352 :type usergroupid: str or int
2353
2354 Example output:
2355
2356 .. code-block:: bash
2357
2358 id : <id_given_in_input>
2359 result: {
2360 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
2361 "success": true
2362 }
2363 error: null
2364
2365
2366 get_repo_group
2367 --------------
2368
2369 .. py:function:: get_repo_group(apiuser, repogroupid)
2370
2371 Return the specified |repo| group, along with permissions,
2372 and repositories inside the group
2373
2374 :param apiuser: This is filled automatically from the |authtoken|.
2375 :type apiuser: AuthUser
2376 :param repogroupid: Specify the name of ID of the repository group.
2377 :type repogroupid: str or int
2378
2379
2380 Example output:
2381
2382 .. code-block:: bash
2383
2384 {
2385 "error": null,
2386 "id": repo-group-id,
2387 "result": {
2388 "group_description": "repo group description",
2389 "group_id": 14,
2390 "group_name": "group name",
2391 "members": [
2392 {
2393 "name": "super-admin-username",
2394 "origin": "super-admin",
2395 "permission": "group.admin",
2396 "type": "user"
2397 },
2398 {
2399 "name": "owner-name",
2400 "origin": "owner",
2401 "permission": "group.admin",
2402 "type": "user"
2403 },
2404 {
2405 "name": "user-group-name",
2406 "origin": "permission",
2407 "permission": "group.write",
2408 "type": "user_group"
2409 }
2410 ],
2411 "owner": "owner-name",
2412 "parent_group": null,
2413 "repositories": [ repo-list ]
2414 }
2415 }
2416
2417
2418 get_repo_groups
2419 ---------------
2420
2421 .. py:function:: get_repo_groups(apiuser)
2422
2423 Returns all repository groups.
2424
2425 :param apiuser: This is filled automatically from the |authtoken|.
2426 :type apiuser: AuthUser
2427
2428
2429 create_repo_group
2430 -----------------
2431
2432 .. py:function:: create_repo_group(apiuser, group_name, description=<Optional:''>, owner=<Optional:<OptionalAttr:apiuser>>, copy_permissions=<Optional:False>)
2433
2434 Creates a repository group.
2435
2436 * If the repository group name contains "/", all the required repository
2437 groups will be created.
2438
2439 For example "foo/bar/baz" will create |repo| groups "foo" and "bar"
2440 (with "foo" as parent). It will also create the "baz" repository
2441 with "bar" as |repo| group.
2442
2443 This command can only be run using an |authtoken| with admin
2444 permissions.
2445
2446 :param apiuser: This is filled automatically from the |authtoken|.
2447 :type apiuser: AuthUser
2448 :param group_name: Set the repository group name.
2449 :type group_name: str
2450 :param description: Set the |repo| group description.
2451 :type description: str
2452 :param owner: Set the |repo| group owner.
2453 :type owner: str
2454 :param copy_permissions:
2455 :type copy_permissions:
2456
2457 Example output:
2458
2459 .. code-block:: bash
2460
2461 id : <id_given_in_input>
2462 result : {
2463 "msg": "Created new repo group `<repo_group_name>`"
2464 "repo_group": <repogroup_object>
2465 }
2466 error : null
2467
2468
2469 Example error output:
2470
2471 .. code-block:: bash
2472
2473 id : <id_given_in_input>
2474 result : null
2475 error : {
2476 failed to create repo group `<repogroupid>`
2477 }
2478
2479
2480 update_repo_group
2481 -----------------
2482
2483 .. py:function:: update_repo_group(apiuser, repogroupid, group_name=<Optional:''>, description=<Optional:''>, owner=<Optional:<OptionalAttr:apiuser>>, parent=<Optional:None>, enable_locking=<Optional:False>)
2484
2485 Updates repository group with the details given.
2486
2487 This command can only be run using an |authtoken| with admin
2488 permissions.
2489
2490 :param apiuser: This is filled automatically from the |authtoken|.
2491 :type apiuser: AuthUser
2492 :param repogroupid: Set the ID of repository group.
2493 :type repogroupid: str or int
2494 :param group_name: Set the name of the |repo| group.
2495 :type group_name: str
2496 :param description: Set a description for the group.
2497 :type description: str
2498 :param owner: Set the |repo| group owner.
2499 :type owner: str
2500 :param parent: Set the |repo| group parent.
2501 :type parent: str or int
2502 :param enable_locking: Enable |repo| locking. The default is false.
2503 :type enable_locking: bool
2504
2505
2506 delete_repo_group
2507 -----------------
2508
2509 .. py:function:: delete_repo_group(apiuser, repogroupid)
2510
2511 Deletes a |repo| group.
2512
2513 :param apiuser: This is filled automatically from the |authtoken|.
2514 :type apiuser: AuthUser
2515 :param repogroupid: Set the name or ID of repository group to be
2516 deleted.
2517 :type repogroupid: str or int
2518
2519 Example output:
2520
2521 .. code-block:: bash
2522
2523 id : <id_given_in_input>
2524 result : {
2525 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>
2526 'repo_group': null
2527 }
2528 error : null
2529
2530 Example error output:
2531
2532 .. code-block:: bash
2533
2534 id : <id_given_in_input>
2535 result : null
2536 error : {
2537 "failed to delete repo group ID:<repogroupid> <repogroupname>"
2538 }
2539
2540
2541 grant_user_permission_to_repo_group
2542 -----------------------------------
2543
2544 .. py:function:: grant_user_permission_to_repo_group(apiuser, repogroupid, userid, perm, apply_to_children=<Optional:'none'>)
2545
2546 Grant permission for a user on the given repository group, or update
2547 existing permissions if found.
2548
2549 This command can only be run using an |authtoken| with admin
2550 permissions.
2551
2552 :param apiuser: This is filled automatically from the |authtoken|.
2553 :type apiuser: AuthUser
2554 :param repogroupid: Set the name or ID of repository group.
2555 :type repogroupid: str or int
2556 :param userid: Set the user name.
2557 :type userid: str
2558 :param perm: (group.(none|read|write|admin))
2559 :type perm: str
2560 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2561 :type apply_to_children: str
2562
2563 Example output:
2564
2565 .. code-block:: bash
2566
2567 id : <id_given_in_input>
2568 result: {
2569 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2570 "success": true
2571 }
2572 error: null
2573
2574 Example error output:
2575
2576 .. code-block:: bash
2577
2578 id : <id_given_in_input>
2579 result : null
2580 error : {
2581 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2582 }
2583
2584
2585 revoke_user_permission_from_repo_group
2586 --------------------------------------
2587
2588 .. py:function:: revoke_user_permission_from_repo_group(apiuser, repogroupid, userid, apply_to_children=<Optional:'none'>)
2589
2590 Revoke permission for a user in a given repository group.
2591
2592 This command can only be run using an |authtoken| with admin
2593 permissions on the |repo| group.
2594
2595 :param apiuser: This is filled automatically from the |authtoken|.
2596 :type apiuser: AuthUser
2597 :param repogroupid: Set the name or ID of the repository group.
2598 :type repogroupid: str or int
2599 :param userid: Set the user name to revoke.
2600 :type userid: str
2601 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2602 :type apply_to_children: str
2603
2604 Example output:
2605
2606 .. code-block:: bash
2607
2608 id : <id_given_in_input>
2609 result: {
2610 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
2611 "success": true
2612 }
2613 error: null
2614
2615 Example error output:
2616
2617 .. code-block:: bash
2618
2619 id : <id_given_in_input>
2620 result : null
2621 error : {
2622 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
2623 }
2624
2625
2626 grant_user_group_permission_to_repo_group
2627 -----------------------------------------
2628
2629 .. py:function:: grant_user_group_permission_to_repo_group(apiuser, repogroupid, usergroupid, perm, apply_to_children=<Optional:'none'>)
2630
2631 Grant permission for a user group on given repository group, or update
2632 existing permissions if found.
2633
2634 This command can only be run using an |authtoken| with admin
2635 permissions on the |repo| group.
2636
2637 :param apiuser: This is filled automatically from the |authtoken|.
2638 :type apiuser: AuthUser
2639 :param repogroupid: Set the name or id of repository group
2640 :type repogroupid: str or int
2641 :param usergroupid: id of usergroup
2642 :type usergroupid: str or int
2643 :param perm: (group.(none|read|write|admin))
2644 :type perm: str
2645 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2646 :type apply_to_children: str
2647
2648 Example output:
2649
2650 .. code-block:: bash
2651
2652 id : <id_given_in_input>
2653 result : {
2654 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2655 "success": true
2656
2657 }
2658 error : null
2659
2660 Example error output:
2661
2662 .. code-block:: bash
2663
2664 id : <id_given_in_input>
2665 result : null
2666 error : {
2667 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2668 }
2669
2670
2671 revoke_user_group_permission_from_repo_group
2672 --------------------------------------------
2673
2674 .. py:function:: revoke_user_group_permission_from_repo_group(apiuser, repogroupid, usergroupid, apply_to_children=<Optional:'none'>)
2675
2676 Revoke permission for user group on given repository.
2677
2678 This command can only be run using an |authtoken| with admin
2679 permissions on the |repo| group.
2680
2681 :param apiuser: This is filled automatically from the |authtoken|.
2682 :type apiuser: AuthUser
2683 :param repogroupid: name or id of repository group
2684 :type repogroupid: str or int
2685 :param usergroupid:
2686 :param apply_to_children: 'none', 'repos', 'groups', 'all'
2687 :type apply_to_children: str
2688
2689 Example output:
2690
2691 .. code-block:: bash
2692
2693 id : <id_given_in_input>
2694 result: {
2695 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
2696 "success": true
2697 }
2698 error: null
2699
2700 Example error output:
2701
2702 .. code-block:: bash
2703
2704 id : <id_given_in_input>
2705 result : null
2706 error : {
2707 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
2708 }
2709
2710
2711 get_gist
2712 --------
2713
2714 .. py:function:: get_gist(apiuser, gistid, content=<Optional:False>)
2715
2716 Get the specified gist, based on the gist ID.
2717
2718 :param apiuser: This is filled automatically from the |authtoken|.
2719 :type apiuser: AuthUser
2720 :param gistid: Set the id of the private or public gist
2721 :type gistid: str
2722 :param content: Return the gist content. Default is false.
2723 :type content: Optional(bool)
2724
2725
2726 get_gists
2727 ---------
2728
2729 .. py:function:: get_gists(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
2730
2731 Get all gists for given user. If userid is empty returned gists
2732 are for user who called the api
2733
2734 :param apiuser: This is filled automatically from the |authtoken|.
2735 :type apiuser: AuthUser
2736 :param userid: user to get gists for
2737 :type userid: Optional(str or int)
2738
2739
2740 create_gist
2741 -----------
2742
2743 .. py:function:: create_gist(apiuser, files, owner=<Optional:<OptionalAttr:apiuser>>, gist_type=<Optional:u'public'>, lifetime=<Optional:-1>, acl_level=<Optional:u'acl_public'>, description=<Optional:''>)
2744
2745 Creates a new Gist.
2746
2747 :param apiuser: This is filled automatically from the |authtoken|.
2748 :type apiuser: AuthUser
2749 :param files: files to be added to the gist. The data structure has
2750 to match the following example::
2751
2752 {'filename': {'content':'...', 'lexer': null},
2753 'filename2': {'content':'...', 'lexer': null}}
2754
2755 :type files: dict
2756 :param owner: Set the gist owner, defaults to api method caller
2757 :type owner: Optional(str or int)
2758 :param gist_type: type of gist ``public`` or ``private``
2759 :type gist_type: Optional(str)
2760 :param lifetime: time in minutes of gist lifetime
2761 :type lifetime: Optional(int)
2762 :param acl_level: acl level for this gist, can be
2763 ``acl_public`` or ``acl_private`` If the value is set to
2764 ``acl_private`` only logged in users are able to access this gist.
2765 If not set it defaults to ``acl_public``.
2766 :type acl_level: Optional(str)
2767 :param description: gist description
2768 :type description: Optional(str)
2769
2770 Example output:
2771
2772 .. code-block:: bash
2773
2774 id : <id_given_in_input>
2775 result : {
2776 "msg": "created new gist",
2777 "gist": {}
2778 }
2779 error : null
2780
2781 Example error output:
2782
2783 .. code-block:: bash
2784
2785 id : <id_given_in_input>
2786 result : null
2787 error : {
2788 "failed to create gist"
2789 }
2790
2791
2792 delete_gist
2793 -----------
2794
2795 .. py:function:: delete_gist(apiuser, gistid)
2796
2797 Deletes existing gist
2798
2799 :param apiuser: filled automatically from apikey
2800 :type apiuser: AuthUser
2801 :param gistid: id of gist to delete
2802 :type gistid: str
2803
2804 Example output:
2805
2806 .. code-block:: bash
2807
2808 id : <id_given_in_input>
2809 result : {
2810 "deleted gist ID: <gist_id>",
2811 "gist": null
2812 }
2813 error : null
2814
2815 Example error output:
2816
2817 .. code-block:: bash
2818
2819 id : <id_given_in_input>
2820 result : null
2821 error : {
2822 "failed to delete gist ID:<gist_id>"
2823 }
2824
199 methods/license-methods
200 methods/deprecated-methods
201 methods/gist-methods
202 methods/pull-request-methods
203 methods/repo-methods
204 methods/repo-group-methods
205 methods/server-methods
206 methods/user-methods
207 methods/user-group-methods
@@ -14,7 +14,7 b' code review matters, see these posts on '
14 14
15 15 You can also use the |RCE| API set up continuous integration servers to leave
16 16 comments from a test suite. See the :ref:`api` and
17 :ref:`integrations-ref` sections for examples on how to set this up.
17 :ref:`extensions-hooks-ref` sections for examples on how to set this up.
18 18
19 19 .. toctree::
20 20
@@ -6,6 +6,8 b" rst_epilog = '''"
6 6 .. |AE| replace:: Appenlight
7 7 .. |authtoken| replace:: Authentication Token
8 8 .. |authtokens| replace:: **Auth Tokens**
9 .. |RCCEshort| replace:: Community
10 .. |RCEEshort| replace:: Enterprise
9 11 .. |git| replace:: Git
10 12 .. |hg| replace:: Mercurial
11 13 .. |svn| replace:: Subversion
@@ -78,7 +78,7 b' following command from inside the cloned'
78 78
79 79 On the first run, this will take a while to download and optionally compile
80 80 a few things. The following runs will be faster. The development shell works
81 fine on MacOS and Linux platforms.
81 fine on both MacOS and Linux platforms.
82 82
83 83
84 84
@@ -91,9 +91,9 b' use the following steps:'
91 91 1. Create a copy of `~/rhodecode-enterprise-ce/configs/development.ini`
92 92 2. Adjust the configuration settings to your needs
93 93
94 .. note::
94 .. note::
95 95
96 It is recommended to use the name `dev.ini`.
96 It is recommended to use the name `dev.ini`.
97 97
98 98
99 99 Setup the Development Database
@@ -108,32 +108,37 b' time operation::'
108 108 --repos=~/my_dev_repos
109 109
110 110
111 Compile CSS and JavaScript
112 ^^^^^^^^^^^^^^^^^^^^^^^^^^
113
114 To use the application's frontend, you will need to compile the CSS and
115 JavaScript with Grunt. This is easily done from within the nix-shell using the
116 following command::
117
118 make web-build
119
120 You will need to recompile following any changes made to the CSS or JavaScript
121 files.
122
123
111 124 Start the Development Server
112 125 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
113 126
114 When starting the development server, you should start the vcsserver as a
115 separate process. To do this, use one of the following examples:
127 From the rhodecode-vcsserver directory, start the development server in another
128 nix-shell, using the following command::
116 129
117 1. Set the `start.vcs_server` flag in the ``dev.ini`` file to true. For example:
130 pserve configs/development.ini http_port=9900
118 131
119 .. code-block:: python
132 In the adjacent nix-shell which you created for your development server, you may
133 now start CE with the following command::
120 134
121 ### VCS CONFIG ###
122 ##################
123 vcs.start_server = true
124 vcs.server = localhost:9900
125 vcs.server.log_level = debug
126 135
127 Then start the server using the following command: ``rcserver dev.ini``
136 rcserver dev.ini
128 137
129 2. Start the development server using the following example::
130
131 rcserver --with-vcsserver dev.ini
138 .. note::
132 139
133 3. Start the development server in a different terminal using the following
134 example::
135
136 vcsserver
140 To automatically refresh - and recompile the frontend assets - when changes
141 are made in the source code, you can use the option `--reload`.
137 142
138 143
139 144 Run the Environment Tests
@@ -141,4 +146,12 b' Run the Environment Tests'
141 146
142 147 Please make sure that the tests are passing to verify that your environment is
143 148 set up correctly. RhodeCode uses py.test to run tests.
144 Please simply run ``make test`` to run the basic test suite.
149 While your instance is running, start a new nix-shell and simply run
150 ``make test`` to run the basic test suite.
151
152
153 Need Help?
154 ^^^^^^^^^^
155
156 Join us on Slack via https://rhodecode.com/join or post questions in our
157 Community Portal at https://community.rhodecode.com
@@ -86,14 +86,11 b' Sign the Contributor License Agreement'
86 86 ======================================
87 87
88 88 If your contribution is approved, you will need to virtually sign the license
89 agreement in order for it to be merged into the project's codebase. You can read
90 it on our website here: https://rhodecode.com/static/pdf/RhodeCode-CLA.pdf
89 agreement in order for it to be merged into the project's codebase.
91 90
92 To sign, go to code.rhodecode.com
93 and clone the CLA repository. Add your name and make a pull request to add it to
94 the contributor agreement; this serves as your virtual signature. Once your
95 signature is merged, add a link to the relevant commit to your contribution
96 pull request.
91 You can read it on our website at https://rhodecode.com/rhodecode-cla
92
93 To sign electronically, go to https://rhodecode.com/sign-cla
97 94
98 95
99 96
@@ -28,10 +28,11 b' let'
28 28 };
29 29
30 30 Pygments = buildPythonPackage rec {
31 name = "Pygments-2.0.2";
31 name = "Pygments-2.1.3";
32 doCheck = false;
32 33 src = fetchurl {
33 url = "https://pypi.python.org/packages/source/P/Pygments/${name}.tar.gz";
34 md5 = "238587a1370d62405edabd0794b3ec4a";
34 url = "https://pypi.python.org/packages/b8/67/ab177979be1c81bc99c8d0592ef22d547e70bb4c6815c383286ed5dec504/Pygments-2.1.3.tar.gz";
35 md5 = "ed3fba2467c8afcda4d317e4ef2c6150";
35 36 };
36 37 };
37 38
@@ -78,11 +79,19 b' let'
78 79 ];
79 80 };
80 81
81 Sphinx = buildPythonPackage (rec {
82 name = "Sphinx-1.3.1";
82 imagesize = buildPythonPackage rec {
83 name = "imagesize-0.7.1";
83 84 src = fetchurl {
84 url = "http://pypi.python.org/packages/source/S/Sphinx/${name}.tar.gz";
85 md5 = "8786a194acf9673464c5455b11fd4332";
85 url = "https://pypi.python.org/packages/53/72/6c6f1e787d9cab2cc733cf042f125abec07209a58308831c9f292504e826/${name}.tar.gz";
86 md5 = "976148283286a6ba5f69b0f81aef8052";
87 };
88 };
89
90 Sphinx = buildPythonPackage (rec {
91 name = "Sphinx-1.4.4";
92 src = fetchurl {
93 url = "https://pypi.python.org/packages/20/a2/72f44c84f6c4115e3fef58d36d657ec311d80196eab9fd5ec7bcde76143b/${name}.tar.gz";
94 md5 = "64ce2ec08d37ed56313a98232cbe2aee";
86 95 };
87 96 propagatedBuildInputs = [
88 97 docutils
@@ -93,6 +102,7 b' let'
93 102 snowballstemmer
94 103 pytz
95 104 babel
105 imagesize
96 106
97 107 # TODO: johbo: Had to include it here so that can be imported
98 108 sphinx_rtd_theme
1 NO CONTENT: file renamed from docs/integrations/config-ext.rst to docs/extensions/config-ext.rst
1 NO CONTENT: file renamed from docs/integrations/example-ext.py to docs/extensions/example-ext.py
1 NO CONTENT: file renamed from docs/integrations/extensions.rst to docs/extensions/extensions.rst
1 NO CONTENT: file renamed from docs/integrations/full-blown-example.rst to docs/extensions/full-blown-example.rst
1 NO CONTENT: file renamed from docs/integrations/hooks.rst to docs/extensions/hooks.rst
1 NO CONTENT: file renamed from docs/integrations/install-ext.rst to docs/extensions/install-ext.rst
1 NO CONTENT: file renamed from docs/integrations/int-slack.rst to docs/extensions/int-slack.rst
1 NO CONTENT: file renamed from docs/integrations/rcx.rst to docs/extensions/rcx.rst
@@ -58,6 +58,7 b' and commit files and |repos| while manag'
58 58 collaboration/review-notifications
59 59 collaboration/pull-requests
60 60 code-review/code-review
61 integrations/integrations
61 62
62 63 .. toctree::
63 64 :maxdepth: 1
@@ -65,7 +66,7 b' and commit files and |repos| while manag'
65 66
66 67 api/api
67 68 tools/rhodecode-tools
68 integrations/integrations
69 extensions/extensions-hooks
69 70 contributing/contributing
70 71
71 72 .. toctree::
@@ -1,7 +1,7 b''
1 1 .. _quick-start:
2 2
3 Quick Start Guide
4 =================
3 Quick Start Installation Guide
4 ==============================
5 5
6 6 .. important::
7 7
@@ -1,25 +1,52 b''
1 .. _integrations-ref:
1 .. _integrations:
2
3 Integrations
4 ------------
2 5
3 Integrations and Extensions
4 ===========================
6 Rhodecode supports integrations with external services for various events,
7 such as commit pushes and pull requests. Multiple integrations of the same type
8 can be added at the same time; this is useful for posting different events to
9 different Slack channels, for example.
5 10
6 The integrations section references three concepts regularly,
7 so to clarify what is meant each time, read the following definitions:
11 Supported integrations
12 ^^^^^^^^^^^^^^^^^^^^^^
8 13
9 * **Plugin**: A Plugin is software that adds a specific feature to
10 an existing software application.
11 * **Extension**: An extension extends the capabilities of,
12 or the data available to, an existing software application.
13 * **Hook**: A hook intercepts function calls, messages, or events passed
14 between software components and can be used to trigger plugins, or their
15 extensions.
14 ============================ ============ =====================================
15 Type/Name |RC| Edition Description
16 ============================ ============ =====================================
17 :ref:`integrations-slack` |RCCEshort| https://slack.com/
18 :ref:`integrations-hipchat` |RCCEshort| https://www.hipchat.com/
19 :ref:`integrations-webhook` |RCCEshort| POST events as `json` to a custom url
20 :ref:`integrations-email` |RCEEshort| Send repo push commits by email
21 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference redmine issues
22 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
23 ============================ ============ =====================================
24
25 .. _creating-integrations:
26
27 Creating an Integration
28 ^^^^^^^^^^^^^^^^^^^^^^^
29
30 Integrations can be added globally via the admin UI:
31
32 :menuselection:`Admin --> Integrations`
33
34 or per repository in each repository's settings:
35
36 :menuselection:`Admin --> Repositories --> Edit --> Integrations`
37
38 To create an integration, select the type from the list in the *Create New
39 Integration* section.
40
41 The *Current Integrations* section shows existing integrations that have been
42 created along with their type (eg. Slack) and enabled status.
43
44 See pages specific to each type of integration for more instructions:
16 45
17 46 .. toctree::
18 47
19 rcx
20 install-ext
21 config-ext
22 extensions
23 hooks
24 full-blown-example
25 int-slack
48 slack
49 hipchat
50 redmine
51 jira
52 webhook
@@ -5,12 +5,12 b' Issue Tracker Integration'
5 5
6 6 You can set an issue tracker connection in two ways with |RCE|.
7 7
8 * At instance level you can set a default issue tracker.
9 * At |repo| level you can configure an integration with a different issue
8 * At the instance level, you can set a default issue tracker.
9 * At the |repo| level, you can configure an integration with a different issue
10 10 tracker.
11 11
12 To integrate |RCM| with an issue tracker you need to define a regular
13 expression that will fetch the issue ID stored in commit messages and replace
12 To integrate |RCM| with an issue tracker, you need to define a regular
13 expression that will fetch the issue ID stored in commit messages, and replace
14 14 it with a URL. This enables |RCE| to generate a link matching each issue to the
15 15 target |repo|.
16 16
@@ -33,9 +33,8 b' 3. Select **Add** so save the rule to yo'
33 33 Repository Issue Tracker Configuration
34 34 --------------------------------------
35 35
36 You can configure specific |repos| to use a different issue tracker if
37 you need to connect to a non-default one. See the instructions in
38 :ref:`repo-it`
36 You can configure specific |repos| to use a different issue tracker than the
37 default one. See the instructions in :ref:`repo-it`
39 38
40 39 .. _issue-tr-eg-ref:
41 40
@@ -9,6 +9,6 b' Release Date'
9 9 Fixes
10 10 ^^^^^
11 11
12 - ui: fixed empty labels caused by missing translation of JS components
13 - login: fixed bad routing URL in comments when user is not logged in.
14 - celery: make sure to run tasks in sync mode if connection to celery is lost.
12 - UI: fixed empty labels caused by missing translation of JS components.
13 - Login: fixed bad routing URL in comments when user is not logged in.
14 - Celery: make sure to run tasks in sync mode if connection to celery is lost.
@@ -9,6 +9,7 b' Release Notes'
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.3.0.rst
12 13 release-notes-4.2.1.rst
13 14 release-notes-4.2.0.rst
14 15 release-notes-4.1.2.rst
@@ -301,7 +301,7 b' used to send signals to build-bots such '
301 301
302 302 \ - -plugins
303 303 Add plugins to your |RCE| installation. See the
304 :ref:`integrations-ref` section for more details.
304 :ref:`extensions-hooks-ref` section for more details.
305 305
306 306 \ - -version
307 307 Display your |RCT| version.
@@ -1,12 +1,12 b''
1 1 diff --git a/requirements.txt b/requirements.txt
2 2 --- a/requirements.txt
3 3 +++ b/requirements.txt
4 @@ -3,7 +3,7 @@future==0.14.3
4 @@ -3,7 +3,7 @@ future==0.14.3
5 5 six==1.9.0
6 6 mako==1.0.1
7 7 markupsafe==0.23
8 8 -requests==2.5.1
9 9 +requests
10 #responses
11 10 whoosh==2.7.0
12 elasticsearch==2.3.0 No newline at end of file
11 elasticsearch==2.3.0
12 elasticsearch-dsl==2.0.0 No newline at end of file
@@ -95,6 +95,14 b' self: super: {'
95 95 };
96 96 });
97 97
98 py-gfm = super.py-gfm.override {
99 src = pkgs.fetchgit {
100 url = "https://code.rhodecode.com/upstream/py-gfm";
101 rev = "0d66a19bc16e3d49de273c0f797d4e4781e8c0f2";
102 sha256 = "0ryp74jyihd3ckszq31bml5jr3bciimhfp7va7kw6ld92930ksv3";
103 };
104 };
105
98 106 pycurl = super.pycurl.override (attrs: {
99 107 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
100 108 pkgs.curl
@@ -38,6 +38,19 b''
38 38 license = [ pkgs.lib.licenses.mit ];
39 39 };
40 40 };
41 Chameleon = super.buildPythonPackage {
42 name = "Chameleon-2.24";
43 buildInputs = with self; [];
44 doCheck = false;
45 propagatedBuildInputs = with self; [];
46 src = fetchurl {
47 url = "https://pypi.python.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
48 md5 = "1b01f1f6533a8a11d0d2f2366dec5342";
49 };
50 meta = {
51 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
52 };
53 };
41 54 Fabric = super.buildPythonPackage {
42 55 name = "Fabric-1.10.0";
43 56 buildInputs = with self; [];
@@ -169,13 +182,13 b''
169 182 };
170 183 };
171 184 Pygments = super.buildPythonPackage {
172 name = "Pygments-2.0.2";
185 name = "Pygments-2.1.3";
173 186 buildInputs = with self; [];
174 187 doCheck = false;
175 188 propagatedBuildInputs = with self; [];
176 189 src = fetchurl {
177 url = "https://pypi.python.org/packages/f4/c6/bdbc5a8a112256b2b6136af304dbae93d8b1ef8738ff2d12a51018800e46/Pygments-2.0.2.tar.gz";
178 md5 = "238587a1370d62405edabd0794b3ec4a";
190 url = "https://pypi.python.org/packages/b8/67/ab177979be1c81bc99c8d0592ef22d547e70bb4c6815c383286ed5dec504/Pygments-2.1.3.tar.gz";
191 md5 = "ed3fba2467c8afcda4d317e4ef2c6150";
179 192 };
180 193 meta = {
181 194 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -467,6 +480,19 b''
467 480 license = [ pkgs.lib.licenses.bsdOriginal ];
468 481 };
469 482 };
483 channelstream = super.buildPythonPackage {
484 name = "channelstream-0.5.2";
485 buildInputs = with self; [];
486 doCheck = false;
487 propagatedBuildInputs = with self; [gevent ws4py pyramid pyramid-jinja2 itsdangerous requests six];
488 src = fetchurl {
489 url = "https://pypi.python.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
490 md5 = "1c5eb2a8a405be6f1073da94da6d81d3";
491 };
492 meta = {
493 license = [ pkgs.lib.licenses.bsdOriginal ];
494 };
495 };
470 496 click = super.buildPythonPackage {
471 497 name = "click-5.1";
472 498 buildInputs = with self; [];
@@ -558,6 +584,19 b''
558 584 license = [ pkgs.lib.licenses.bsdOriginal ];
559 585 };
560 586 };
587 deform = super.buildPythonPackage {
588 name = "deform-2.0a2";
589 buildInputs = with self; [];
590 doCheck = false;
591 propagatedBuildInputs = with self; [Chameleon colander peppercorn translationstring zope.deprecation];
592 src = fetchurl {
593 url = "https://pypi.python.org/packages/8d/b3/aab57e81da974a806dc9c5fa024a6404720f890a6dcf2e80885e3cb4609a/deform-2.0a2.tar.gz";
594 md5 = "7a90d41f7fbc18002ce74f39bd90a5e4";
595 };
596 meta = {
597 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
598 };
599 };
561 600 docutils = super.buildPythonPackage {
562 601 name = "docutils-0.12";
563 602 buildInputs = with self; [];
@@ -572,13 +611,13 b''
572 611 };
573 612 };
574 613 dogpile.cache = super.buildPythonPackage {
575 name = "dogpile.cache-0.5.7";
614 name = "dogpile.cache-0.6.1";
576 615 buildInputs = with self; [];
577 616 doCheck = false;
578 propagatedBuildInputs = with self; [dogpile.core];
617 propagatedBuildInputs = with self; [];
579 618 src = fetchurl {
580 url = "https://pypi.python.org/packages/07/74/2a83bedf758156d9c95d112691bbad870d3b77ccbcfb781b4ef836ea7d96/dogpile.cache-0.5.7.tar.gz";
581 md5 = "3e58ce41af574aab41d78e9c4190f194";
619 url = "https://pypi.python.org/packages/f6/a0/6f2142c58c6588d17c734265b103ae1cd0741e1681dd9483a63f22033375/dogpile.cache-0.6.1.tar.gz";
620 md5 = "35d7fb30f22bbd0685763d894dd079a9";
582 621 };
583 622 meta = {
584 623 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -688,6 +727,19 b''
688 727 license = [ pkgs.lib.licenses.bsdOriginal ];
689 728 };
690 729 };
730 gevent = super.buildPythonPackage {
731 name = "gevent-1.1.1";
732 buildInputs = with self; [];
733 doCheck = false;
734 propagatedBuildInputs = with self; [greenlet];
735 src = fetchurl {
736 url = "https://pypi.python.org/packages/12/dc/0b2e57823225de86f6e111a65d212c9e3b64847dddaa19691a6cb94b0b2e/gevent-1.1.1.tar.gz";
737 md5 = "1532f5396ab4d07a231f1935483be7c3";
738 };
739 meta = {
740 license = [ pkgs.lib.licenses.mit ];
741 };
742 };
691 743 gnureadline = super.buildPythonPackage {
692 744 name = "gnureadline-6.3.3";
693 745 buildInputs = with self; [];
@@ -702,7 +754,7 b''
702 754 };
703 755 };
704 756 gprof2dot = super.buildPythonPackage {
705 name = "gprof2dot-2015.12.1";
757 name = "gprof2dot-2015.12.01";
706 758 buildInputs = with self; [];
707 759 doCheck = false;
708 760 propagatedBuildInputs = with self; [];
@@ -714,6 +766,19 b''
714 766 license = [ { fullName = "LGPL"; } ];
715 767 };
716 768 };
769 greenlet = super.buildPythonPackage {
770 name = "greenlet-0.4.9";
771 buildInputs = with self; [];
772 doCheck = false;
773 propagatedBuildInputs = with self; [];
774 src = fetchurl {
775 url = "https://pypi.python.org/packages/4e/3d/9d421539b74e33608b245092870156b2e171fb49f2b51390aa4641eecb4a/greenlet-0.4.9.zip";
776 md5 = "c6659cdb2a5e591723e629d2eef22e82";
777 };
778 meta = {
779 license = [ pkgs.lib.licenses.mit ];
780 };
781 };
717 782 gunicorn = super.buildPythonPackage {
718 783 name = "gunicorn-19.6.0";
719 784 buildInputs = with self; [];
@@ -948,6 +1013,19 b''
948 1013 license = [ { fullName = "Expat license"; } pkgs.lib.licenses.mit ];
949 1014 };
950 1015 };
1016 peppercorn = super.buildPythonPackage {
1017 name = "peppercorn-0.5";
1018 buildInputs = with self; [];
1019 doCheck = false;
1020 propagatedBuildInputs = with self; [];
1021 src = fetchurl {
1022 url = "https://pypi.python.org/packages/45/ec/a62ec317d1324a01567c5221b420742f094f05ee48097e5157d32be3755c/peppercorn-0.5.tar.gz";
1023 md5 = "f08efbca5790019ab45d76b7244abd40";
1024 };
1025 meta = {
1026 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1027 };
1028 };
951 1029 psutil = super.buildPythonPackage {
952 1030 name = "psutil-2.2.1";
953 1031 buildInputs = with self; [];
@@ -962,13 +1040,13 b''
962 1040 };
963 1041 };
964 1042 psycopg2 = super.buildPythonPackage {
965 name = "psycopg2-2.6";
1043 name = "psycopg2-2.6.1";
966 1044 buildInputs = with self; [];
967 1045 doCheck = false;
968 1046 propagatedBuildInputs = with self; [];
969 1047 src = fetchurl {
970 url = "https://pypi.python.org/packages/dd/c7/9016ff8ff69da269b1848276eebfb264af5badf6b38caad805426771f04d/psycopg2-2.6.tar.gz";
971 md5 = "fbbb039a8765d561a1c04969bbae7c74";
1048 url = "https://pypi.python.org/packages/86/fd/cc8315be63a41fe000cce20482a917e874cdc1151e62cb0141f5e55f711e/psycopg2-2.6.1.tar.gz";
1049 md5 = "842b44f8c95517ed5b792081a2370da1";
972 1050 };
973 1051 meta = {
974 1052 license = [ pkgs.lib.licenses.zpt21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
@@ -1000,6 +1078,19 b''
1000 1078 license = [ pkgs.lib.licenses.bsdOriginal ];
1001 1079 };
1002 1080 };
1081 py-gfm = super.buildPythonPackage {
1082 name = "py-gfm-0.1.3";
1083 buildInputs = with self; [];
1084 doCheck = false;
1085 propagatedBuildInputs = with self; [setuptools Markdown];
1086 src = fetchurl {
1087 url = "https://pypi.python.org/packages/12/e4/6b3d8678da04f97d7490d8264d8de51c2dc9fb91209ccee9c515c95e14c5/py-gfm-0.1.3.tar.gz";
1088 md5 = "e588d9e69640a241b97e2c59c22527a6";
1089 };
1090 meta = {
1091 license = [ pkgs.lib.licenses.bsdOriginal ];
1092 };
1093 };
1003 1094 pycrypto = super.buildPythonPackage {
1004 1095 name = "pycrypto-2.6.1";
1005 1096 buildInputs = with self; [];
@@ -1339,23 +1430,23 b''
1339 1430 };
1340 1431 };
1341 1432 rhodecode-enterprise-ce = super.buildPythonPackage {
1342 name = "rhodecode-enterprise-ce-4.2.1";
1433 name = "rhodecode-enterprise-ce-4.3.0";
1343 1434 buildInputs = with self; [WebTest configobj cssselect flake8 lxml mock pytest pytest-cov pytest-runner];
1344 1435 doCheck = true;
1345 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery colander decorator docutils gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors psutil py-bcrypt];
1436 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
1346 1437 src = ./.;
1347 1438 meta = {
1348 1439 license = [ { fullName = "AGPLv3, and Commercial License"; } ];
1349 1440 };
1350 1441 };
1351 1442 rhodecode-tools = super.buildPythonPackage {
1352 name = "rhodecode-tools-0.8.3";
1443 name = "rhodecode-tools-0.10.0";
1353 1444 buildInputs = with self; [];
1354 1445 doCheck = false;
1355 1446 propagatedBuildInputs = with self; [click future six Mako MarkupSafe requests Whoosh elasticsearch elasticsearch-dsl];
1356 1447 src = fetchurl {
1357 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.8.3.zip";
1358 md5 = "9acdfd71b8ddf4056057065f37ab9ccb";
1448 url = "https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.10.0.zip";
1449 md5 = "4762391473ded761bead3aa58c748044";
1359 1450 };
1360 1451 meta = {
1361 1452 license = [ { fullName = "AGPLv3 and Proprietary"; } ];
@@ -1453,13 +1544,13 b''
1453 1544 };
1454 1545 };
1455 1546 supervisor = super.buildPythonPackage {
1456 name = "supervisor-3.1.3";
1547 name = "supervisor-3.3.0";
1457 1548 buildInputs = with self; [];
1458 1549 doCheck = false;
1459 1550 propagatedBuildInputs = with self; [meld3];
1460 1551 src = fetchurl {
1461 url = "https://pypi.python.org/packages/a6/41/65ad5bd66230b173eb4d0b8810230f3a9c59ef52ae066e540b6b99895db7/supervisor-3.1.3.tar.gz";
1462 md5 = "aad263c4fbc070de63dd354864d5e552";
1552 url = "https://pypi.python.org/packages/44/80/d28047d120bfcc8158b4e41127706731ee6a3419c661e0a858fb0e7c4b2d/supervisor-3.3.0.tar.gz";
1553 md5 = "46bac00378d1eddb616752b990c67416";
1463 1554 };
1464 1555 meta = {
1465 1556 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
@@ -1556,6 +1647,19 b''
1556 1647 license = [ pkgs.lib.licenses.zpt21 ];
1557 1648 };
1558 1649 };
1650 ws4py = super.buildPythonPackage {
1651 name = "ws4py-0.3.5";
1652 buildInputs = with self; [];
1653 doCheck = false;
1654 propagatedBuildInputs = with self; [];
1655 src = fetchurl {
1656 url = "https://pypi.python.org/packages/b6/4f/34af703be86939629479e74d6e650e39f3bd73b3b09212c34e5125764cbc/ws4py-0.3.5.zip";
1657 md5 = "a261b75c20b980e55ce7451a3576a867";
1658 };
1659 meta = {
1660 license = [ pkgs.lib.licenses.bsdOriginal ];
1661 };
1662 };
1559 1663 wsgiref = super.buildPythonPackage {
1560 1664 name = "wsgiref-0.1.2";
1561 1665 buildInputs = with self; [];
@@ -11,7 +11,7 b' MySQL-python==1.2.5'
11 11 Paste==2.0.2
12 12 PasteDeploy==1.5.2
13 13 PasteScript==1.7.5
14 Pygments==2.0.2
14 Pygments==2.1.3
15 15
16 16 # TODO: This version is not available on PyPI
17 17 # Pylons==1.0.2.dev20160108
@@ -24,10 +24,6 b' Pyro4==4.41'
24 24 # TODO: This should probably not be in here
25 25 # -e hg+https://johbo@code.rhodecode.com/johbo/rhodecode-fork@3a454bd1f17c0b2b2a951cf2b111e0320d7942a9#egg=RhodeCodeEnterprise-dev
26 26
27 # TODO: This is not really a dependency, we should add it only
28 # into the development environment, since there it is useful.
29 # RhodeCodeVCSServer==3.9.0
30
31 27 Routes==1.13
32 28 SQLAlchemy==0.9.9
33 29 Sphinx==1.2.2
@@ -53,6 +49,7 b' backport-ipaddress==0.1'
53 49 bottle==0.12.8
54 50 bumpversion==0.5.3
55 51 celery==2.2.10
52 channelstream==0.5.2
56 53 click==5.1
57 54 colander==1.2
58 55 configobj==5.0.6
@@ -60,15 +57,18 b' cov-core==1.15.0'
60 57 coverage==3.7.1
61 58 cssselect==0.9.1
62 59 decorator==3.4.2
60 deform==2.0a2
63 61 docutils==0.12
64 dogpile.cache==0.5.7
62 dogpile.cache==0.6.1
65 63 dogpile.core==0.4.1
66 64 dulwich==0.12.0
67 65 ecdsa==0.11
68 66 flake8==2.4.1
69 67 future==0.14.3
70 68 futures==3.0.2
69 gevent==1.1.1
71 70 gprof2dot==2015.12.1
71 greenlet==0.4.9
72 72 gunicorn==19.6.0
73 73
74 74 # TODO: Needs subvertpy and blows up without Subversion headers,
@@ -94,9 +94,10 b' packaging==15.2'
94 94 paramiko==1.15.1
95 95 pep8==1.5.7
96 96 psutil==2.2.1
97 psycopg2==2.6
97 psycopg2==2.6.1
98 98 py==1.4.29
99 99 py-bcrypt==0.4
100 py-gfm==0.1.3
100 101 pycrypto==2.6.1
101 102 pycurl==7.19.5
102 103 pyflakes==0.8.1
@@ -123,7 +124,7 b' pyzmq==14.6.0'
123 124 # TODO: This is not available in public
124 125 # rc-testdata==0.2.0
125 126
126 https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.8.3.zip#md5=9acdfd71b8ddf4056057065f37ab9ccb
127 https://code.rhodecode.com/rhodecode-tools-ce/archive/v0.10.0.zip#md5=4762391473ded761bead3aa58c748044
127 128
128 129
129 130 recaptcha-client==1.0.6
@@ -136,7 +137,7 b' setuptools-scm==1.11.0'
136 137 simplejson==3.7.2
137 138 six==1.9.0
138 139 subprocess32==3.2.6
139 supervisor==3.1.3
140 supervisor==3.3.0
140 141 transifex-client==0.10
141 142 translationstring==1.3
142 143 trollius==1.0.4
@@ -1,1 +1,1 b''
1 4.2.1 No newline at end of file
1 4.3.0 No newline at end of file
@@ -43,11 +43,15 b' CELERY_EAGER = False'
43 43 # link to config for pylons
44 44 CONFIG = {}
45 45
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
49
46 50 # Linked module for extensions
47 51 EXTENSIONS = {}
48 52
49 53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
50 __dbversion__ = 54 # defines current db version for migrations
54 __dbversion__ = 55 # defines current db version for migrations
51 55 __platform__ = platform.system()
52 56 __license__ = 'AGPLv3, and Commercial License'
53 57 __author__ = 'RhodeCode GmbH'
@@ -80,6 +80,8 b' class NavigationRegistry(object):'
80 80 NavEntry('email', _('Email'), 'admin_settings_email'),
81 81 NavEntry('hooks', _('Hooks'), 'admin_settings_hooks'),
82 82 NavEntry('search', _('Full Text Search'), 'admin_settings_search'),
83 NavEntry('integrations', _('Integrations'),
84 'global_integrations_home', pyramid=True),
83 85 NavEntry('system', _('System Info'), 'admin_settings_system'),
84 86 NavEntry('open_source', _('Open Source Licenses'),
85 87 'admin_settings_open_source', pyramid=True),
@@ -25,12 +25,15 b' import types'
25 25
26 26 import decorator
27 27 import venusian
28 from collections import OrderedDict
29
28 30 from pyramid.exceptions import ConfigurationError
29 31 from pyramid.renderers import render
30 32 from pyramid.response import Response
31 33 from pyramid.httpexceptions import HTTPNotFound
32 34
33 from rhodecode.api.exc import JSONRPCBaseError, JSONRPCError, JSONRPCForbidden
35 from rhodecode.api.exc import (
36 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
34 37 from rhodecode.lib.auth import AuthUser
35 38 from rhodecode.lib.base import get_ip_addr
36 39 from rhodecode.lib.ext_json import json
@@ -127,6 +130,11 b' def exception_view(exc, request):'
127 130 if isinstance(exc, JSONRPCError):
128 131 fault_message = exc.message
129 132 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
133 elif isinstance(exc, JSONRPCValidationError):
134 colander_exc = exc.colander_exception
135 #TODO: think maybe of nicer way to serialize errors ?
136 fault_message = colander_exc.asdict()
137 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
130 138 elif isinstance(exc, JSONRPCForbidden):
131 139 fault_message = 'Access was denied to this resource.'
132 140 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
@@ -474,7 +482,7 b' def includeme(config):'
474 482 plugin_module, config.registry.settings)
475 483
476 484 if not hasattr(config.registry, 'jsonrpc_methods'):
477 config.registry.jsonrpc_methods = {}
485 config.registry.jsonrpc_methods = OrderedDict()
478 486
479 487 # match filter by given method only
480 488 config.add_view_predicate(
@@ -27,5 +27,13 b' class JSONRPCError(JSONRPCBaseError):'
27 27 pass
28 28
29 29
30 class JSONRPCValidationError(JSONRPCBaseError):
31
32 def __init__(self, *args, **kwargs):
33 self.colander_exception = kwargs.pop('colander_exc')
34 super(JSONRPCValidationError, self).__init__(*args, **kwargs)
35
36
30 37 class JSONRPCForbidden(JSONRPCBaseError):
31 38 pass
39
@@ -43,7 +43,7 b' class TestApiCreateGist(object):'
43 43 description='foobar-gist',
44 44 gist_type=gist_type,
45 45 acl_level=gist_acl_level,
46 files={'foobar': {'content': 'foo'}})
46 files={'foobar_ąć': {'content': 'foo'}})
47 47 response = api_call(self.app, params)
48 48 response_json = response.json
49 49 gist = response_json['result']['gist']
@@ -68,6 +68,32 b' class TestApiCreateGist(object):'
68 68 finally:
69 69 Fixture().destroy_gists()
70 70
71 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
72 ({'gist_type': '"ups" is not one of private, public'},
73 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
74
75 ({'lifetime': '-120 is less than minimum value -1'},
76 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
77
78 ({'0.content': 'Required'},
79 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
80 ])
81 def test_api_try_create_gist(
82 self, expected, lifetime, gist_type, gist_acl_level, files):
83 id_, params = build_data(
84 self.apikey_regular, 'create_gist',
85 lifetime=lifetime,
86 description='foobar-gist',
87 gist_type=gist_type,
88 acl_level=gist_acl_level,
89 files=files)
90 response = api_call(self.app, params)
91
92 try:
93 assert_error(id_, expected, given=response.body)
94 finally:
95 Fixture().destroy_gists()
96
71 97 @mock.patch.object(GistModel, 'create', crash)
72 98 def test_api_create_gist_exception_occurred(self):
73 99 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
@@ -21,7 +21,7 b''
21 21
22 22 import pytest
23 23
24 from rhodecode.api.views import depracated_api
24 from rhodecode.api.views import deprecated_api
25 25 from rhodecode.lib.ext_json import json
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call)
@@ -30,7 +30,7 b' from rhodecode.api.tests.utils import ('
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestCommitComment(object):
32 32 def test_deprecated_message_in_docstring(self):
33 docstring = depracated_api.changeset_comment.__doc__
33 docstring = deprecated_api.changeset_comment.__doc__
34 34 assert '.. deprecated:: 3.4.0' in docstring
35 35 assert 'Please use method `comment_commit` instead.' in docstring
36 36
@@ -73,6 +73,29 b' class TestGetRepoNodes(object):'
73 73 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
74 74 assert_error(id_, expected, given=response.body)
75 75
76 def test_api_get_repo_nodes_max_file_bytes(self, backend):
77 commit_id = 'tip'
78 path = '/'
79 max_file_bytes = 500
80
81 id_, params = build_data(
82 self.apikey, 'get_repo_nodes',
83 repoid=backend.repo_name, revision=commit_id, details='full',
84 root_path=path)
85 response = api_call(self.app, params)
86 assert any(file['content'] and len(file['content']) > max_file_bytes
87 for file in response.json['result'])
88
89 id_, params = build_data(
90 self.apikey, 'get_repo_nodes',
91 repoid=backend.repo_name, revision=commit_id,
92 root_path=path, details='full',
93 max_file_bytes=max_file_bytes)
94 response = api_call(self.app, params)
95 assert all(
96 file['content'] is None if file['size'] > max_file_bytes else True
97 for file in response.json['result'])
98
76 99 def test_api_get_repo_nodes_bad_ret_type(self, backend):
77 100 commit_id = 'tip'
78 101 path = '/'
@@ -47,9 +47,14 b' class TestApiUpdateRepo(object):'
47 47 ({'enable_statistics': True}, SAME_AS_UPDATES),
48 48 ({'enable_locking': True}, SAME_AS_UPDATES),
49 49 ({'enable_downloads': True}, SAME_AS_UPDATES),
50 ({'name': 'new_repo_name'}, {'repo_name': 'new_repo_name'}),
51 ({'group': 'test_group_for_update'},
52 {'repo_name': 'test_group_for_update/%s' % UPDATE_REPO_NAME}),
50 ({'name': 'new_repo_name'}, {
51 'repo_name': 'new_repo_name',
52 'url': 'http://test.example.com:80/new_repo_name',
53 }),
54 ({'group': 'test_group_for_update'}, {
55 'repo_name': 'test_group_for_update/%s' % UPDATE_REPO_NAME,
56 'url': 'http://test.example.com:80/test_group_for_update/%s' % UPDATE_REPO_NAME
57 }),
53 58 ])
54 59 def test_api_update_repo(self, updates, expected, backend):
55 60 repo_name = UPDATE_REPO_NAME
@@ -34,8 +34,6 b' from rhodecode.lib.vcs.exceptions import'
34 34 log = logging.getLogger(__name__)
35 35
36 36
37
38
39 37 class OAttr(object):
40 38 """
41 39 Special Option that defines other attribute, and can default to them
1 NO CONTENT: file renamed from rhodecode/api/views/depracated_api.py to rhodecode/api/views/deprecated_api.py
@@ -23,6 +23,7 b' import logging'
23 23 import time
24 24
25 25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.exc import JSONRPCValidationError
26 27 from rhodecode.api.utils import (
27 28 Optional, OAttr, get_gist_or_error, get_user_or_error,
28 29 has_superadmin_permission)
@@ -96,7 +97,8 b' def get_gists(request, apiuser, userid=O'
96 97
97 98 @jsonrpc_method()
98 99 def create_gist(
99 request, apiuser, files, owner=Optional(OAttr('apiuser')),
100 request, apiuser, files, gistid=Optional(None),
101 owner=Optional(OAttr('apiuser')),
100 102 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
101 103 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
102 104 description=Optional('')):
@@ -108,10 +110,11 b' def create_gist('
108 110 :param files: files to be added to the gist. The data structure has
109 111 to match the following example::
110 112
111 {'filename': {'content':'...', 'lexer': null},
112 'filename2': {'content':'...', 'lexer': null}}
113 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
113 114
114 115 :type files: dict
116 :param gistid: Set a custom id for the gist
117 :type gistid: Optional(str)
115 118 :param owner: Set the gist owner, defaults to api method caller
116 119 :type owner: Optional(str or int)
117 120 :param gist_type: type of gist ``public`` or ``private``
@@ -148,23 +151,49 b' def create_gist('
148 151 }
149 152
150 153 """
154 from rhodecode.model import validation_schema
155 from rhodecode.model.validation_schema.schemas import gist_schema
156
157 if isinstance(owner, Optional):
158 owner = apiuser.user_id
159
160 owner = get_user_or_error(owner)
161
162 lifetime = Optional.extract(lifetime)
163 schema = gist_schema.GistSchema().bind(
164 # bind the given values if it's allowed, however the deferred
165 # validator will still validate it according to other rules
166 lifetime_options=[lifetime])
151 167
152 168 try:
153 if isinstance(owner, Optional):
154 owner = apiuser.user_id
169 nodes = gist_schema.nodes_to_sequence(
170 files, colander_node=schema.get('nodes'))
171
172 schema_data = schema.deserialize(dict(
173 gistid=Optional.extract(gistid),
174 description=Optional.extract(description),
175 gist_type=Optional.extract(gist_type),
176 lifetime=lifetime,
177 gist_acl_level=Optional.extract(acl_level),
178 nodes=nodes
179 ))
155 180
156 owner = get_user_or_error(owner)
157 description = Optional.extract(description)
158 gist_type = Optional.extract(gist_type)
159 lifetime = Optional.extract(lifetime)
160 acl_level = Optional.extract(acl_level)
181 # convert to safer format with just KEYs so we sure no duplicates
182 schema_data['nodes'] = gist_schema.sequence_to_nodes(
183 schema_data['nodes'], colander_node=schema.get('nodes'))
184
185 except validation_schema.Invalid as err:
186 raise JSONRPCValidationError(colander_exc=err)
161 187
162 gist = GistModel().create(description=description,
163 owner=owner,
164 gist_mapping=files,
165 gist_type=gist_type,
166 lifetime=lifetime,
167 gist_acl_level=acl_level)
188 try:
189 gist = GistModel().create(
190 owner=owner,
191 gist_id=schema_data['gistid'],
192 description=schema_data['description'],
193 gist_mapping=schema_data['nodes'],
194 gist_type=schema_data['gist_type'],
195 lifetime=schema_data['lifetime'],
196 gist_acl_level=schema_data['gist_acl_level'])
168 197 Session().commit()
169 198 return {
170 199 'msg': 'created new gist',
@@ -265,7 +265,7 b' def merge_pull_request(request, apiuser,'
265 265 PullRequestModel().close_pull_request(
266 266 pull_request.pull_request_id, apiuser)
267 267
268 Session.commit()
268 Session().commit()
269 269 return data
270 270
271 271
@@ -319,7 +319,7 b' def close_pull_request(request, apiuser,'
319 319
320 320 PullRequestModel().close_pull_request(
321 321 pull_request.pull_request_id, apiuser)
322 Session.commit()
322 Session().commit()
323 323 data = {
324 324 'pull_request_id': pull_request.pull_request_id,
325 325 'closed': True,
@@ -408,6 +408,8 b' def comment_pull_request(request, apiuse'
408 408 line_no=None,
409 409 status_change=(ChangesetStatus.get_status_lbl(status)
410 410 if status and allowed_to_change_status else None),
411 status_change_type=(status
412 if status and allowed_to_change_status else None),
411 413 closing_pr=False,
412 414 renderer=renderer
413 415 )
@@ -43,8 +43,8 b' from rhodecode.model.db import ('
43 43 from rhodecode.model.repo import RepoModel
44 44 from rhodecode.model.repo_group import RepoGroupModel
45 45 from rhodecode.model.scm import ScmModel, RepoList
46 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.validation_schema import RepoSchema
46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
47 from rhodecode.model.validation_schema.schemas import repo_schema
48 48
49 49 log = logging.getLogger(__name__)
50 50
@@ -400,7 +400,8 b' def get_repo_changesets(request, apiuser'
400 400
401 401 @jsonrpc_method()
402 402 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
403 ret_type=Optional('all'), details=Optional('basic')):
403 ret_type=Optional('all'), details=Optional('basic'),
404 max_file_bytes=Optional(None)):
404 405 """
405 406 Returns a list of nodes and children in a flat list for a given
406 407 path at given revision.
@@ -425,6 +426,8 b' def get_repo_nodes(request, apiuser, rep'
425 426 md5, binary, and or content. The valid options are ``basic`` and
426 427 ``full``.
427 428 :type details: Optional(str)
429 :param max_file_bytes: Only return file content under this file size bytes
430 :type details: Optional(int)
428 431
429 432 Example output:
430 433
@@ -472,7 +475,8 b' def get_repo_nodes(request, apiuser, rep'
472 475
473 476 _d, _f = ScmModel().get_nodes(
474 477 repo, revision, root_path, flat=False,
475 extended_info=extended_info, content=content)
478 extended_info=extended_info, content=content,
479 max_file_bytes=max_file_bytes)
476 480 _map = {
477 481 'all': _d + _f,
478 482 'files': _f,
@@ -606,7 +610,7 b' def create_repo(request, apiuser, repo_n'
606 610 }
607 611
608 612 """
609 schema = RepoSchema()
613 schema = repo_schema.RepoSchema()
610 614 try:
611 615 data = schema.deserialize({
612 616 'repo_name': repo_name
@@ -1310,8 +1314,6 b' def comment_commit('
1310 1314 userid=Optional(OAttr('apiuser')), status=Optional(None)):
1311 1315 """
1312 1316 Set a commit comment, and optionally change the status of the commit.
1313 This command can be executed only using api_key belonging to user
1314 with admin rights, or repository administrator.
1315 1317
1316 1318 :param apiuser: This is filled automatically from the |authtoken|.
1317 1319 :type apiuser: AuthUser
@@ -1344,7 +1346,7 b' def comment_commit('
1344 1346 """
1345 1347 repo = get_repo_or_error(repoid)
1346 1348 if not has_superadmin_permission(apiuser):
1347 _perms = ('repository.admin',)
1349 _perms = ('repository.read', 'repository.write', 'repository.admin')
1348 1350 has_repo_permissions(apiuser, repoid, repo, _perms)
1349 1351
1350 1352 if isinstance(userid, Optional):
@@ -1361,9 +1363,11 b' def comment_commit('
1361 1363 try:
1362 1364 rc_config = SettingsModel().get_all_settings()
1363 1365 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1364
1366 status_change_label = ChangesetStatus.get_status_lbl(status)
1365 1367 comm = ChangesetCommentsModel().create(
1366 message, repo, user, revision=commit_id, status_change=status,
1368 message, repo, user, revision=commit_id,
1369 status_change=status_change_label,
1370 status_change_type=status,
1367 1371 renderer=renderer)
1368 1372 if status:
1369 1373 # also do a status change
@@ -1775,3 +1779,110 b' def strip(request, apiuser, repoid, revi'
1775 1779 'Unable to strip commit %s from repo `%s`' % (
1776 1780 revision, repo.repo_name)
1777 1781 )
1782
1783
1784 @jsonrpc_method()
1785 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1786 """
1787 Returns all settings for a repository. If key is given it only returns the
1788 setting identified by the key or null.
1789
1790 :param apiuser: This is filled automatically from the |authtoken|.
1791 :type apiuser: AuthUser
1792 :param repoid: The repository name or repository id.
1793 :type repoid: str or int
1794 :param key: Key of the setting to return.
1795 :type: key: Optional(str)
1796
1797 Example output:
1798
1799 .. code-block:: bash
1800
1801 {
1802 "error": null,
1803 "id": 237,
1804 "result": {
1805 "extensions_largefiles": true,
1806 "hooks_changegroup_push_logger": true,
1807 "hooks_changegroup_repo_size": false,
1808 "hooks_outgoing_pull_logger": true,
1809 "phases_publish": "True",
1810 "rhodecode_hg_use_rebase_for_merging": true,
1811 "rhodecode_pr_merge_enabled": true,
1812 "rhodecode_use_outdated_comments": true
1813 }
1814 }
1815 """
1816
1817 # Restrict access to this api method to admins only.
1818 if not has_superadmin_permission(apiuser):
1819 raise JSONRPCForbidden()
1820
1821 try:
1822 repo = get_repo_or_error(repoid)
1823 settings_model = VcsSettingsModel(repo=repo)
1824 settings = settings_model.get_global_settings()
1825 settings.update(settings_model.get_repo_settings())
1826
1827 # If only a single setting is requested fetch it from all settings.
1828 key = Optional.extract(key)
1829 if key is not None:
1830 settings = settings.get(key, None)
1831 except Exception:
1832 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1833 log.exception(msg)
1834 raise JSONRPCError(msg)
1835
1836 return settings
1837
1838
1839 @jsonrpc_method()
1840 def set_repo_settings(request, apiuser, repoid, settings):
1841 """
1842 Update repository settings. Returns true on success.
1843
1844 :param apiuser: This is filled automatically from the |authtoken|.
1845 :type apiuser: AuthUser
1846 :param repoid: The repository name or repository id.
1847 :type repoid: str or int
1848 :param settings: The new settings for the repository.
1849 :type: settings: dict
1850
1851 Example output:
1852
1853 .. code-block:: bash
1854
1855 {
1856 "error": null,
1857 "id": 237,
1858 "result": true
1859 }
1860 """
1861 # Restrict access to this api method to admins only.
1862 if not has_superadmin_permission(apiuser):
1863 raise JSONRPCForbidden()
1864
1865 if type(settings) is not dict:
1866 raise JSONRPCError('Settings have to be a JSON Object.')
1867
1868 try:
1869 settings_model = VcsSettingsModel(repo=repoid)
1870
1871 # Merge global, repo and incoming settings.
1872 new_settings = settings_model.get_global_settings()
1873 new_settings.update(settings_model.get_repo_settings())
1874 new_settings.update(settings)
1875
1876 # Update the settings.
1877 inherit_global_settings = new_settings.get(
1878 'inherit_global_settings', False)
1879 settings_model.create_or_update_repo_settings(
1880 new_settings, inherit_global_settings=inherit_global_settings)
1881 Session().commit()
1882 except Exception:
1883 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1884 log.exception(msg)
1885 raise JSONRPCError(msg)
1886
1887 # Indicate success.
1888 return True
@@ -34,7 +34,7 b' from rhodecode.lib.auth import ('
34 34 from rhodecode.model.db import Session, RepoGroup
35 35 from rhodecode.model.repo_group import RepoGroupModel
36 36 from rhodecode.model.scm import RepoGroupList
37 from rhodecode.model.validation_schema import RepoGroupSchema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
38 38
39 39
40 40 log = logging.getLogger(__name__)
@@ -193,7 +193,7 b' def create_repo_group(request, apiuser, '
193 193
194 194 """
195 195
196 schema = RepoGroupSchema()
196 schema = repo_group_schema.RepoGroupSchema()
197 197 try:
198 198 data = schema.deserialize({
199 199 'group_name': group_name
@@ -490,20 +490,26 b' def loadplugin(plugin_id):'
490 490 or None on failure.
491 491 """
492 492 # TODO: Disusing pyramids thread locals to retrieve the registry.
493 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
493 authn_registry = get_authn_registry()
494 494 plugin = authn_registry.get_plugin(plugin_id)
495 495 if plugin is None:
496 496 log.error('Authentication plugin not found: "%s"', plugin_id)
497 497 return plugin
498 498
499 499
500 def get_authn_registry(registry=None):
501 registry = registry or get_current_registry()
502 authn_registry = registry.getUtility(IAuthnPluginRegistry)
503 return authn_registry
504
505
500 506 def get_auth_cache_manager(custom_ttl=None):
501 507 return caches.get_cache_manager(
502 508 'auth_plugins', 'rhodecode.authentication', custom_ttl)
503 509
504 510
505 511 def authenticate(username, password, environ=None, auth_type=None,
506 skip_missing=False):
512 skip_missing=False, registry=None):
507 513 """
508 514 Authentication function used for access control,
509 515 It tries to authenticate based on enabled authentication modules.
@@ -520,7 +526,7 b' def authenticate(username, password, env'
520 526 % auth_type)
521 527 headers_only = environ and not (username and password)
522 528
523 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
529 authn_registry = get_authn_registry(registry)
524 530 for plugin in authn_registry.get_plugins_for_authentication():
525 531 plugin.set_auth_type(auth_type)
526 532 user = plugin.get_user(username)
@@ -559,16 +565,16 b' def authenticate(username, password, env'
559 565 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
560 566 # plugin cache set inside is more important than the settings value
561 567 _cache_ttl = plugin.AUTH_CACHE_TTL
562 elif plugin_settings.get('auth_cache_ttl'):
563 _cache_ttl = safe_int(plugin_settings.get('auth_cache_ttl'), 0)
568 elif plugin_settings.get('cache_ttl'):
569 _cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
564 570
565 571 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
566 572
567 573 # get instance of cache manager configured for a namespace
568 574 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
569 575
570 log.debug('Cache for plugin `%s` active: %s', plugin.get_id(),
571 plugin_cache_active)
576 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
577 plugin.get_id(), plugin_cache_active, _cache_ttl)
572 578
573 579 # for environ based password can be empty, but then the validation is
574 580 # on the server that fills in the env data needed for authentication
@@ -581,8 +587,7 b' def authenticate(username, password, env'
581 587 # to RhodeCode database. If this function returns data
582 588 # then auth is correct.
583 589 start = time.time()
584 log.debug('Running plugin `%s` _authenticate method',
585 plugin.get_id())
590 log.debug('Running plugin `%s` _authenticate method', plugin.get_id())
586 591
587 592 def auth_func():
588 593 """
@@ -141,9 +141,9 b' class LdapSettingsSchema(AuthnPluginSett'
141 141 colander.String(),
142 142 default='',
143 143 description=_('LDAP Attribute to map to user name'),
144 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
145 144 preparer=strip_whitespace,
146 145 title=_('Login Attribute'),
146 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
147 147 widget='string')
148 148 attr_firstname = colander.SchemaNode(
149 149 colander.String(),
@@ -186,6 +186,7 b' class AuthLdap(object):'
186 186 if ldap == Missing:
187 187 raise LdapImportError("Missing or incompatible ldap library")
188 188
189 self.debug = False
189 190 self.ldap_version = ldap_version
190 191 self.ldap_server_type = 'ldap'
191 192
@@ -213,6 +214,8 b' class AuthLdap(object):'
213 214 self.LDAP_FILTER = safe_str(ldap_filter)
214 215
215 216 def _get_ldap_server(self):
217 if self.debug:
218 ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
216 219 if hasattr(ldap, 'OPT_X_TLS_CACERTDIR'):
217 220 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR,
218 221 '/etc/openldap/cacerts')
@@ -137,5 +137,7 b' class RhodeCodeAuthPlugin(RhodeCodeAuthP'
137 137 "authenticating on this plugin", userobj)
138 138 return None
139 139 else:
140 log.warning('user %s tried auth but is disabled', userobj)
140 log.warning(
141 'user `%s` failed to authenticate via %s, reason: account not '
142 'active.', username, self.name)
141 143 return None
@@ -83,13 +83,17 b' class RhodeCodeAuthPlugin(RhodeCodeAuthP'
83 83 allowed_auth_plugins=None, allowed_auth_sources=None):
84 84 """
85 85 Custom method for this auth that doesn't accept empty users. And also
86 allows rhodecode and authtoken extern_type to auth with this. But only
87 via vcs mode
86 allows users from all other active plugins to use it and also
87 authenticate against it. But only via vcs mode
88 88 """
89 # only this and rhodecode plugins can use this type
90 from rhodecode.authentication.plugins import auth_rhodecode
91 allowed_auth_plugins = [
92 self.name, auth_rhodecode.RhodeCodeAuthPlugin.name]
89 from rhodecode.authentication.base import get_authn_registry
90 authn_registry = get_authn_registry()
91
92 active_plugins = set(
93 [x.name for x in authn_registry.get_plugins_for_authentication()])
94 active_plugins.discard(self.name)
95
96 allowed_auth_plugins = [self.name] + list(active_plugins)
93 97 # only for vcs operations
94 98 allowed_auth_sources = [VCS_TYPE]
95 99
@@ -26,8 +26,8 b' from pyramid.httpexceptions import HTTPF'
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 from rhodecode.authentication.base import get_auth_cache_manager
30 from rhodecode.authentication.interface import IAuthnPluginRegistry
29 from rhodecode.authentication.base import (
30 get_auth_cache_manager, get_authn_registry)
31 31 from rhodecode.lib import auth
32 32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 33 from rhodecode.model.forms import AuthSettingsForm
@@ -96,7 +96,7 b' class AuthnPluginViewBase(object):'
96 96 # Store validated data.
97 97 for name, value in valid_data.items():
98 98 self.plugin.create_or_update_setting(name, value)
99 Session.commit()
99 Session().commit()
100 100
101 101 # Display success message and redirect.
102 102 self.request.session.flash(
@@ -125,7 +125,7 b' class AuthSettingsView(object):'
125 125 @HasPermissionAllDecorator('hg.admin')
126 126 def index(self, defaults=None, errors=None, prefix_error=False):
127 127 defaults = defaults or {}
128 authn_registry = self.request.registry.getUtility(IAuthnPluginRegistry)
128 authn_registry = get_authn_registry(self.request.registry)
129 129 enabled_plugins = SettingsModel().get_auth_plugins()
130 130
131 131 # Create template context and render it.
@@ -30,36 +30,6 b' from rhodecode.lib.utils2 import __get_l'
30 30 # extensions will index it's content
31 31 LANGUAGES_EXTENSIONS_MAP = __get_lem()
32 32
33 # list of readme files to search in file tree and display in summary
34 # attached weights defines the search order lower is first
35 ALL_READMES = [
36 ('readme', 0), ('README', 0), ('Readme', 0),
37 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
38 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
39 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
40 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
41 ]
42
43 # extension together with weights to search lower is first
44 RST_EXTS = [
45 ('', 0), ('.rst', 1), ('.rest', 1),
46 ('.RST', 2), ('.REST', 2)
47 ]
48
49 MARKDOWN_EXTS = [
50 ('.md', 1), ('.MD', 1),
51 ('.mkdn', 2), ('.MKDN', 2),
52 ('.mdown', 3), ('.MDOWN', 3),
53 ('.markdown', 4), ('.MARKDOWN', 4)
54 ]
55
56 PLAIN_EXTS = [
57 ('.text', 2), ('.TEXT', 2),
58 ('.txt', 3), ('.TXT', 3)
59 ]
60
61 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
62
63 33 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
64 34
65 35 DATE_FORMAT = "%Y-%m-%d"
@@ -34,11 +34,16 b' from pylons.configuration import PylonsC'
34 34 from pylons.error import handle_mako_error
35 35 from pyramid.settings import asbool
36 36
37 # don't remove this import it does magic for celery
38 from rhodecode.lib import celerypylons # noqa
37 # ------------------------------------------------------------------------------
38 # CELERY magic until refactor - issue #4163 - import order matters here:
39 from rhodecode.lib import celerypylons # this must be first, celerypylons
40 # sets config settings upon import
39 41
40 import rhodecode.lib.app_globals as app_globals
42 import rhodecode.integrations # any modules using celery task
43 # decorators should be added afterwards:
44 # ------------------------------------------------------------------------------
41 45
46 from rhodecode.lib import app_globals
42 47 from rhodecode.config import utils
43 48 from rhodecode.config.routing import make_map
44 49 from rhodecode.config.jsroutes import generate_jsroutes_content
@@ -112,25 +117,12 b' def load_environment(global_conf, app_co'
112 117 # sets the c attribute access when don't existing attribute are accessed
113 118 config['pylons.strict_tmpl_context'] = True
114 119
115 # Limit backends to "vcs.backends" from configuration
116 backends = config['vcs.backends'] = aslist(
117 config.get('vcs.backends', 'hg,git'), sep=',')
118 for alias in rhodecode.BACKENDS.keys():
119 if alias not in backends:
120 del rhodecode.BACKENDS[alias]
121 log.info("Enabled backends: %s", backends)
122
123 # initialize vcs client and optionally run the server if enabled
124 vcs_server_uri = config.get('vcs.server', '')
125 vcs_server_enabled = str2bool(config.get('vcs.server.enable', 'true'))
126 start_server = (
127 str2bool(config.get('vcs.start_server', 'false')) and
128 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
129 if vcs_server_enabled and start_server:
130 log.info("Starting vcsserver")
131 start_vcs_server(server_and_port=vcs_server_uri,
132 protocol=utils.get_vcs_server_protocol(config),
133 log_level=config['vcs.server.log_level'])
120 # configure channelstream
121 config['channelstream_config'] = {
122 'enabled': asbool(config.get('channelstream.enabled', False)),
123 'server': config.get('channelstream.server'),
124 'secret': config.get('channelstream.secret')
125 }
134 126
135 127 set_available_permissions(config)
136 128 db_cfg = make_db_config(clear_session=True)
@@ -138,9 +130,6 b' def load_environment(global_conf, app_co'
138 130 repos_path = list(db_cfg.items('paths'))[0][1]
139 131 config['base_path'] = repos_path
140 132
141 config['vcs.hooks.direct_calls'] = _use_direct_hook_calls(config)
142 config['vcs.hooks.protocol'] = _get_vcs_hooks_protocol(config)
143
144 133 # store db config also in main global CONFIG
145 134 set_rhodecode_config(config)
146 135
@@ -153,34 +142,17 b' def load_environment(global_conf, app_co'
153 142 # store config reference into our module to skip import magic of pylons
154 143 rhodecode.CONFIG.update(config)
155 144
156 utils.configure_pyro4(config)
157 utils.configure_vcs(config)
158 if vcs_server_enabled:
159 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(config))
160
161 import_on_startup = str2bool(config.get('startup.import_repos', False))
162 if vcs_server_enabled and import_on_startup:
163 repo2db_mapper(ScmModel().repo_scan(repos_path), remove_obsolete=False)
164 145 return config
165 146
166 147
167 def _use_direct_hook_calls(config):
168 default_direct_hook_calls = 'false'
169 direct_hook_calls = str2bool(
170 config.get('vcs.hooks.direct_calls', default_direct_hook_calls))
171 return direct_hook_calls
172
173
174 def _get_vcs_hooks_protocol(config):
175 protocol = config.get('vcs.hooks.protocol', 'pyro4').lower()
176 return protocol
177
178
179 148 def load_pyramid_environment(global_config, settings):
180 149 # Some parts of the code expect a merge of global and app settings.
181 150 settings_merged = global_config.copy()
182 151 settings_merged.update(settings)
183 152
153 # Store the settings to make them available to other modules.
154 rhodecode.PYRAMID_SETTINGS = settings_merged
155
184 156 # If this is a test run we prepare the test environment like
185 157 # creating a test database, test search index and test repositories.
186 158 # This has to be done before the database connection is initialized.
@@ -190,3 +162,27 b' def load_pyramid_environment(global_conf'
190 162
191 163 # Initialize the database connection.
192 164 utils.initialize_database(settings_merged)
165
166 # Limit backends to `vcs.backends` from configuration
167 for alias in rhodecode.BACKENDS.keys():
168 if alias not in settings['vcs.backends']:
169 del rhodecode.BACKENDS[alias]
170 log.info('Enabled VCS backends: %s', rhodecode.BACKENDS.keys())
171
172 # initialize vcs client and optionally run the server if enabled
173 vcs_server_uri = settings['vcs.server']
174 vcs_server_enabled = settings['vcs.server.enable']
175 start_server = (
176 settings['vcs.start_server'] and
177 not int(os.environ.get('RC_VCSSERVER_TEST_DISABLE', '0')))
178
179 if vcs_server_enabled and start_server:
180 log.info("Starting vcsserver")
181 start_vcs_server(server_and_port=vcs_server_uri,
182 protocol=utils.get_vcs_server_protocol(settings),
183 log_level=settings['vcs.server.log_level'])
184
185 utils.configure_pyro4(settings)
186 utils.configure_vcs(settings)
187 if vcs_server_enabled:
188 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
@@ -22,23 +22,25 b''
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 from collections import OrderedDict
25 26
26 27 from paste.registry import RegistryManager
27 28 from paste.gzipper import make_gzip_middleware
28 29 from pylons.wsgiapp import PylonsApp
29 30 from pyramid.authorization import ACLAuthorizationPolicy
30 31 from pyramid.config import Configurator
31 from pyramid.static import static_view
32 32 from pyramid.settings import asbool, aslist
33 33 from pyramid.wsgi import wsgiapp
34 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError
34 from pyramid.httpexceptions import HTTPError, HTTPInternalServerError, HTTPFound
35 from pyramid.events import ApplicationCreated
35 36 import pyramid.httpexceptions as httpexceptions
36 from pyramid.renderers import render_to_response, render
37 from pyramid.renderers import render_to_response
37 38 from routes.middleware import RoutesMiddleware
38 39 import routes.util
39 40
40 41 import rhodecode
41 42 from rhodecode.config import patches
43 from rhodecode.config.routing import STATIC_FILE_PREFIX
42 44 from rhodecode.config.environment import (
43 45 load_environment, load_pyramid_environment)
44 46 from rhodecode.lib.middleware import csrf
@@ -47,24 +49,45 b' from rhodecode.lib.middleware.disable_vc'
47 49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
48 50 from rhodecode.lib.middleware.vcs import VCSMiddleware
49 51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
53 from rhodecode.subscribers import scan_repositories_if_enabled
50 54
51 55
52 56 log = logging.getLogger(__name__)
53 57
54 58
55 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
59 # this is used to avoid avoid the route lookup overhead in routesmiddleware
60 # for certain routes which won't go to pylons to - eg. static files, debugger
61 # it is only needed for the pylons migration and can be removed once complete
62 class SkippableRoutesMiddleware(RoutesMiddleware):
63 """ Routes middleware that allows you to skip prefixes """
64
65 def __init__(self, *args, **kw):
66 self.skip_prefixes = kw.pop('skip_prefixes', [])
67 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
68
69 def __call__(self, environ, start_response):
70 for prefix in self.skip_prefixes:
71 if environ['PATH_INFO'].startswith(prefix):
72 # added to avoid the case when a missing /_static route falls
73 # through to pylons and causes an exception as pylons is
74 # expecting wsgiorg.routingargs to be set in the environ
75 # by RoutesMiddleware.
76 if 'wsgiorg.routing_args' not in environ:
77 environ['wsgiorg.routing_args'] = (None, {})
78 return self.app(environ, start_response)
79
80 return super(SkippableRoutesMiddleware, self).__call__(
81 environ, start_response)
82
83
84 def make_app(global_conf, static_files=True, **app_conf):
56 85 """Create a Pylons WSGI application and return it
57 86
58 87 ``global_conf``
59 88 The inherited configuration for this application. Normally from
60 89 the [DEFAULT] section of the Paste ini file.
61 90
62 ``full_stack``
63 Whether or not this application provides a full WSGI stack (by
64 default, meaning it handles its own exceptions and errors).
65 Disable full_stack when this application is "managed" by
66 another WSGI middleware.
67
68 91 ``app_conf``
69 92 The application's local configuration. Normally specified in
70 93 the [app:<name>] section of the Paste ini file (where <name>
@@ -89,16 +112,6 b' def make_app(global_conf, full_stack=Tru'
89 112 app = csrf.OriginChecker(app, expected_origin,
90 113 skip_urls=[routes.util.url_for('api')])
91 114
92
93 if asbool(full_stack):
94
95 # Appenlight monitoring and error handler
96 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
97
98 # we want our low level middleware to get to the request ASAP. We don't
99 # need any pylons stack middleware in them
100 app = VCSMiddleware(app, config, appenlight_client)
101
102 115 # Establish the Registry for this application
103 116 app = RegistryManager(app)
104 117
@@ -140,13 +153,78 b' def make_pyramid_app(global_config, **se'
140 153
141 154 load_pyramid_environment(global_config, settings)
142 155
156 includeme_first(config)
143 157 includeme(config)
144 includeme_last(config)
145 158 pyramid_app = config.make_wsgi_app()
146 159 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
160 pyramid_app.config = config
147 161 return pyramid_app
148 162
149 163
164 def make_not_found_view(config):
165 """
166 This creates the view which should be registered as not-found-view to
167 pyramid. Basically it contains of the old pylons app, converted to a view.
168 Additionally it is wrapped by some other middlewares.
169 """
170 settings = config.registry.settings
171 vcs_server_enabled = settings['vcs.server.enable']
172
173 # Make pylons app from unprepared settings.
174 pylons_app = make_app(
175 config.registry._pylons_compat_global_config,
176 **config.registry._pylons_compat_settings)
177 config.registry._pylons_compat_config = pylons_app.config
178
179 # Appenlight monitoring.
180 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
181 pylons_app, settings)
182
183 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find
184 # a view to handle the request. Therefore we wrap it around the pylons app.
185 if vcs_server_enabled:
186 pylons_app = VCSMiddleware(
187 pylons_app, settings, appenlight_client, registry=config.registry)
188
189 pylons_app_as_view = wsgiapp(pylons_app)
190
191 # Protect from VCS Server error related pages when server is not available
192 if not vcs_server_enabled:
193 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
194
195 def pylons_app_with_error_handler(context, request):
196 """
197 Handle exceptions from rc pylons app:
198
199 - old webob type exceptions get converted to pyramid exceptions
200 - pyramid exceptions are passed to the error handler view
201 """
202 def is_vcs_response(response):
203 return 'X-RhodeCode-Backend' in response.headers
204
205 def is_http_error(response):
206 # webob type error responses
207 return (400 <= response.status_int <= 599)
208
209 def is_error_handling_needed(response):
210 return is_http_error(response) and not is_vcs_response(response)
211
212 try:
213 response = pylons_app_as_view(context, request)
214 if is_error_handling_needed(response):
215 response = webob_to_pyramid_http_response(response)
216 return error_handler(response, request)
217 except HTTPError as e: # pyramid type exceptions
218 return error_handler(e, request)
219 except Exception:
220 if settings.get('debugtoolbar.enabled', False):
221 raise
222 return error_handler(HTTPInternalServerError(), request)
223 return response
224
225 return pylons_app_with_error_handler
226
227
150 228 def add_pylons_compat_data(registry, global_config, settings):
151 229 """
152 230 Attach data to the registry to support the Pylons integration.
@@ -205,20 +283,32 b' def error_handler(exception, request):'
205 283 def includeme(config):
206 284 settings = config.registry.settings
207 285
286 # plugin information
287 config.registry.rhodecode_plugins = OrderedDict()
288
289 config.add_directive(
290 'register_rhodecode_plugin', register_rhodecode_plugin)
291
208 292 if asbool(settings.get('appenlight', 'false')):
209 293 config.include('appenlight_client.ext.pyramid_tween')
210 294
211 295 # Includes which are required. The application would fail without them.
212 296 config.include('pyramid_mako')
213 297 config.include('pyramid_beaker')
298 config.include('rhodecode.channelstream')
214 299 config.include('rhodecode.admin')
215 300 config.include('rhodecode.authentication')
301 config.include('rhodecode.integrations')
216 302 config.include('rhodecode.login')
217 303 config.include('rhodecode.tweens')
218 304 config.include('rhodecode.api')
305 config.include('rhodecode.svn_support')
219 306 config.add_route(
220 307 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
221 308
309 # Add subscribers.
310 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
311
222 312 # Set the authorization policy.
223 313 authz_policy = ACLAuthorizationPolicy()
224 314 config.set_authorization_policy(authz_policy)
@@ -226,86 +316,37 b' def includeme(config):'
226 316 # Set the default renderer for HTML templates to mako.
227 317 config.add_mako_renderer('.html')
228 318
229 # plugin information
230 config.registry.rhodecode_plugins = {}
231
232 config.add_directive(
233 'register_rhodecode_plugin', register_rhodecode_plugin)
234 319 # include RhodeCode plugins
235 320 includes = aslist(settings.get('rhodecode.includes', []))
236 321 for inc in includes:
237 322 config.include(inc)
238 323
239 pylons_app = make_app(
240 config.registry._pylons_compat_global_config,
241 **config.registry._pylons_compat_settings)
242 config.registry._pylons_compat_config = pylons_app.config
243
244 pylons_app_as_view = wsgiapp(pylons_app)
245
246 # Protect from VCS Server error related pages when server is not available
247 vcs_server_enabled = asbool(settings.get('vcs.server.enable', 'true'))
248 if not vcs_server_enabled:
249 pylons_app_as_view = DisableVCSPagesWrapper(pylons_app_as_view)
250
251
252 def pylons_app_with_error_handler(context, request):
253 """
254 Handle exceptions from rc pylons app:
255
256 - old webob type exceptions get converted to pyramid exceptions
257 - pyramid exceptions are passed to the error handler view
258 """
259 try:
260 response = pylons_app_as_view(context, request)
261 if 400 <= response.status_int <= 599: # webob type error responses
262 return error_handler(
263 webob_to_pyramid_http_response(response), request)
264 except HTTPError as e: # pyramid type exceptions
265 return error_handler(e, request)
266 except Exception:
267 if settings.get('debugtoolbar.enabled', False):
268 raise
269 return error_handler(HTTPInternalServerError(), request)
270 return response
271
272 324 # This is the glue which allows us to migrate in chunks. By registering the
273 325 # pylons based application as the "Not Found" view in Pyramid, we will
274 326 # fallback to the old application each time the new one does not yet know
275 327 # how to handle a request.
276 config.add_notfound_view(pylons_app_with_error_handler)
328 config.add_notfound_view(make_not_found_view(config))
277 329
278 if settings.get('debugtoolbar.enabled', False):
279 # if toolbar, then only http type exceptions get caught and rendered
280 ExcClass = HTTPError
281 else:
330 if not settings.get('debugtoolbar.enabled', False):
282 331 # if no toolbar, then any exception gets caught and rendered
283 ExcClass = Exception
284 config.add_view(error_handler, context=ExcClass)
332 config.add_view(error_handler, context=Exception)
333
334 config.add_view(error_handler, context=HTTPError)
285 335
286 336
287 def includeme_last(config):
288 """
289 The static file catchall needs to be last in the view configuration.
290 """
291 settings = config.registry.settings
337 def includeme_first(config):
338 # redirect automatic browser favicon.ico requests to correct place
339 def favicon_redirect(context, request):
340 return HTTPFound(
341 request.static_path('rhodecode:public/images/favicon.ico'))
292 342
293 # Note: johbo: I would prefer to register a prefix for static files at some
294 # point, e.g. move them under '_static/'. This would fully avoid that we
295 # can have name clashes with a repository name. Imaging someone calling his
296 # repo "css" ;-) Also having an external web server to serve out the static
297 # files seems to be easier to set up if they have a common prefix.
298 #
299 # Example: config.add_static_view('_static', path='rhodecode:public')
300 #
301 # It might be an option to register both paths for a while and then migrate
302 # over to the new location.
343 config.add_view(favicon_redirect, route_name='favicon')
344 config.add_route('favicon', '/favicon.ico')
303 345
304 # Serving static files with a catchall.
305 if settings['static_files']:
306 config.add_route('catchall_static', '/*subpath')
307 config.add_view(
308 static_view('rhodecode:public'), route_name='catchall_static')
346 config.add_static_view(
347 '_static/deform', 'deform:static')
348 config.add_static_view(
349 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
309 350
310 351
311 352 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
@@ -322,19 +363,14 b' def wrap_app_in_wsgi_middlewares(pyramid'
322 363 pyramid_app = HttpsFixup(pyramid_app, settings)
323 364
324 365 # Add RoutesMiddleware to support the pylons compatibility tween during
325
326 366 # migration to pyramid.
327 pyramid_app = RoutesMiddleware(
328 pyramid_app, config.registry._pylons_compat_config['routes.map'])
367 pyramid_app = SkippableRoutesMiddleware(
368 pyramid_app, config.registry._pylons_compat_config['routes.map'],
369 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
329 370
330 if asbool(settings.get('appenlight', 'false')):
331 pyramid_app, _ = wrap_in_appenlight_if_enabled(
332 pyramid_app, config.registry._pylons_compat_config)
371 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
333 372
334 # TODO: johbo: Don't really see why we enable the gzip middleware when
335 # serving static files, might be something that should have its own setting
336 # as well?
337 if settings['static_files']:
373 if settings['gzip_responses']:
338 374 pyramid_app = make_gzip_middleware(
339 375 pyramid_app, settings, compress_level=1)
340 376
@@ -376,12 +412,63 b' def sanitize_settings_and_apply_defaults'
376 412 # should allow to pass in a prefix.
377 413 settings.setdefault('rhodecode.api.url', '/_admin/api')
378 414
379 _bool_setting(settings, 'vcs.server.enable', 'true')
380 _bool_setting(settings, 'static_files', 'true')
415 # Sanitize generic settings.
416 _list_setting(settings, 'default_encoding', 'UTF-8')
381 417 _bool_setting(settings, 'is_test', 'false')
418 _bool_setting(settings, 'gzip_responses', 'false')
419
420 # Call split out functions that sanitize settings for each topic.
421 _sanitize_appenlight_settings(settings)
422 _sanitize_vcs_settings(settings)
382 423
383 424 return settings
384 425
385 426
427 def _sanitize_appenlight_settings(settings):
428 _bool_setting(settings, 'appenlight', 'false')
429
430
431 def _sanitize_vcs_settings(settings):
432 """
433 Applies settings defaults and does type conversion for all VCS related
434 settings.
435 """
436 _string_setting(settings, 'vcs.svn.compatible_version', '')
437 _string_setting(settings, 'git_rev_filter', '--all')
438 _string_setting(settings, 'vcs.hooks.protocol', 'pyro4')
439 _string_setting(settings, 'vcs.server', '')
440 _string_setting(settings, 'vcs.server.log_level', 'debug')
441 _string_setting(settings, 'vcs.server.protocol', 'pyro4')
442 _bool_setting(settings, 'startup.import_repos', 'false')
443 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
444 _bool_setting(settings, 'vcs.server.enable', 'true')
445 _bool_setting(settings, 'vcs.start_server', 'false')
446 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
447 _int_setting(settings, 'vcs.connection_timeout', 3600)
448
449
450 def _int_setting(settings, name, default):
451 settings[name] = int(settings.get(name, default))
452
453
386 454 def _bool_setting(settings, name, default):
387 settings[name] = asbool(settings.get(name, default))
455 input = settings.get(name, default)
456 if isinstance(input, unicode):
457 input = input.encode('utf8')
458 settings[name] = asbool(input)
459
460
461 def _list_setting(settings, name, default):
462 raw_value = settings.get(name, default)
463
464 old_separator = ','
465 if old_separator in raw_value:
466 # If we get a comma separated list, pass it to our own function.
467 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
468 else:
469 # Otherwise we assume it uses pyramids space/newline separation.
470 settings[name] = aslist(raw_value)
471
472
473 def _string_setting(settings, name, default):
474 settings[name] = settings.get(name, default).lower()
@@ -36,6 +36,7 b' from rhodecode.config import routing_lin'
36 36
37 37 # prefix for non repository related links needs to be prefixed with `/`
38 38 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
39 40
40 41 # Default requirements for URL parts
41 42 URL_NAME_REQUIREMENTS = {
@@ -51,6 +52,19 b' URL_NAME_REQUIREMENTS = {'
51 52 }
52 53
53 54
55 def add_route_requirements(route_path, requirements):
56 """
57 Adds regex requirements to pyramid routes using a mapping dict
58
59 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 '/{action}/{id:\d+}'
61
62 """
63 for key, regex in requirements.items():
64 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 return route_path
66
67
54 68 class JSRoutesMapper(Mapper):
55 69 """
56 70 Wrapper for routes.Mapper to make pyroutes compatible url definitions
@@ -546,6 +560,13 b' def make_map(config):'
546 560 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
547 561 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
548 562 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
563 m.connect('my_account_notifications', '/my_account/notifications',
564 action='my_notifications',
565 conditions={'method': ['GET']})
566 m.connect('my_account_notifications_toggle_visibility',
567 '/my_account/toggle_visibility',
568 action='my_notifications_toggle_visibility',
569 conditions={'method': ['POST']})
549 570
550 571 # NOTIFICATION REST ROUTES
551 572 with rmap.submapper(path_prefix=ADMIN_PREFIX,
@@ -554,7 +575,6 b' def make_map(config):'
554 575 action='index', conditions={'method': ['GET']})
555 576 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
556 577 action='mark_all_read', conditions={'method': ['POST']})
557
558 578 m.connect('/notifications/{notification_id}',
559 579 action='update', conditions={'method': ['PUT']})
560 580 m.connect('/notifications/{notification_id}',
@@ -850,7 +870,7 b' def make_map(config):'
850 870 conditions={'function': check_repo, 'method': ['DELETE']},
851 871 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
852 872
853 rmap.connect('changeset_info', '/changeset_info/{repo_name}/{revision}',
873 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
854 874 controller='changeset', action='changeset_info',
855 875 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
856 876
@@ -1090,9 +1110,9 b' def make_map(config):'
1090 1110 conditions={'function': check_repo},
1091 1111 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1092 1112
1093 rmap.connect('files_metadata_list_home',
1094 '/{repo_name}/metadata_list/{revision}/{f_path}',
1095 controller='files', action='metadata_list',
1113 rmap.connect('files_nodetree_full',
1114 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1115 controller='files', action='nodetree_full',
1096 1116 conditions={'function': check_repo},
1097 1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1098 1118
@@ -49,22 +49,18 b' def configure_vcs(config):'
49 49 Patch VCS config with some RhodeCode specific stuff
50 50 """
51 51 from rhodecode.lib.vcs import conf
52 from rhodecode.lib.utils2 import aslist
53 52 conf.settings.BACKENDS = {
54 53 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
55 54 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
56 55 'svn': 'rhodecode.lib.vcs.backends.svn.SubversionRepository',
57 56 }
58 57
59 conf.settings.HG_USE_REBASE_FOR_MERGING = config.get(
60 'rhodecode_hg_use_rebase_for_merging', False)
61 conf.settings.GIT_REV_FILTER = shlex.split(
62 config.get('git_rev_filter', '--all').strip())
63 conf.settings.DEFAULT_ENCODINGS = aslist(config.get('default_encoding',
64 'UTF-8'), sep=',')
65 conf.settings.ALIASES[:] = config.get('vcs.backends')
66 conf.settings.SVN_COMPATIBLE_VERSION = config.get(
67 'vcs.svn.compatible_version')
58 conf.settings.HOOKS_PROTOCOL = config['vcs.hooks.protocol']
59 conf.settings.HOOKS_DIRECT_CALLS = config['vcs.hooks.direct_calls']
60 conf.settings.GIT_REV_FILTER = shlex.split(config['git_rev_filter'])
61 conf.settings.DEFAULT_ENCODINGS = config['default_encoding']
62 conf.settings.ALIASES[:] = config['vcs.backends']
63 conf.settings.SVN_COMPATIBLE_VERSION = config['vcs.svn.compatible_version']
68 64
69 65
70 66 def initialize_database(config):
@@ -90,8 +86,7 b' def initialize_test_environment(settings'
90 86
91 87
92 88 def get_vcs_server_protocol(config):
93 protocol = config.get('vcs.server.protocol', 'pyro4')
94 return protocol
89 return config['vcs.server.protocol']
95 90
96 91
97 92 def set_instance_id(config):
@@ -25,15 +25,18 b' gist controller for RhodeCode'
25 25
26 26 import time
27 27 import logging
28 import traceback
28
29 29 import formencode
30 import peppercorn
30 31 from formencode import htmlfill
31 32
32 33 from pylons import request, response, tmpl_context as c, url
33 34 from pylons.controllers.util import abort, redirect
34 35 from pylons.i18n.translation import _
36 from webob.exc import HTTPNotFound, HTTPForbidden
37 from sqlalchemy.sql.expression import or_
35 38
36 from rhodecode.model.forms import GistForm
39
37 40 from rhodecode.model.gist import GistModel
38 41 from rhodecode.model.meta import Session
39 42 from rhodecode.model.db import Gist, User
@@ -44,9 +47,10 b' from rhodecode.lib.auth import LoginRequ'
44 47 from rhodecode.lib.utils import jsonify
45 48 from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime
46 49 from rhodecode.lib.ext_json import json
47 from webob.exc import HTTPNotFound, HTTPForbidden
48 from sqlalchemy.sql.expression import or_
49 50 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
51 from rhodecode.model import validation_schema
52 from rhodecode.model.validation_schema.schemas import gist_schema
53
50 54
51 55 log = logging.getLogger(__name__)
52 56
@@ -56,11 +60,11 b' class GistsController(BaseController):'
56 60
57 61 def __load_defaults(self, extra_values=None):
58 62 c.lifetime_values = [
59 (str(-1), _('forever')),
60 (str(5), _('5 minutes')),
61 (str(60), _('1 hour')),
62 (str(60 * 24), _('1 day')),
63 (str(60 * 24 * 30), _('1 month')),
63 (-1, _('forever')),
64 (5, _('5 minutes')),
65 (60, _('1 hour')),
66 (60 * 24, _('1 day')),
67 (60 * 24 * 30, _('1 month')),
64 68 ]
65 69 if extra_values:
66 70 c.lifetime_values.append(extra_values)
@@ -136,40 +140,56 b' class GistsController(BaseController):'
136 140 """POST /admin/gists: Create a new item"""
137 141 # url('gists')
138 142 self.__load_defaults()
139 gist_form = GistForm([x[0] for x in c.lifetime_values],
140 [x[0] for x in c.acl_options])()
143
144 data = dict(request.POST)
145 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
146 data['nodes'] = [{
147 'filename': data['filename'],
148 'content': data.get('content'),
149 'mimetype': data.get('mimetype') # None is autodetect
150 }]
151
152 data['gist_type'] = (
153 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
154 data['gist_acl_level'] = (
155 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
156
157 schema = gist_schema.GistSchema().bind(
158 lifetime_options=[x[0] for x in c.lifetime_values])
159
141 160 try:
142 form_result = gist_form.to_python(dict(request.POST))
143 # TODO: multiple files support, from the form
144 filename = form_result['filename'] or Gist.DEFAULT_FILENAME
145 nodes = {
146 filename: {
147 'content': form_result['content'],
148 'lexer': form_result['mimetype'] # None is autodetect
149 }
150 }
151 _public = form_result['public']
152 gist_type = Gist.GIST_PUBLIC if _public else Gist.GIST_PRIVATE
153 gist_acl_level = form_result.get(
154 'acl_level', Gist.ACL_LEVEL_PRIVATE)
161
162 schema_data = schema.deserialize(data)
163 # convert to safer format with just KEYs so we sure no duplicates
164 schema_data['nodes'] = gist_schema.sequence_to_nodes(
165 schema_data['nodes'])
166
155 167 gist = GistModel().create(
156 description=form_result['description'],
168 gist_id=schema_data['gistid'], # custom access id not real ID
169 description=schema_data['description'],
157 170 owner=c.rhodecode_user.user_id,
158 gist_mapping=nodes,
159 gist_type=gist_type,
160 lifetime=form_result['lifetime'],
161 gist_id=form_result['gistid'],
162 gist_acl_level=gist_acl_level
171 gist_mapping=schema_data['nodes'],
172 gist_type=schema_data['gist_type'],
173 lifetime=schema_data['lifetime'],
174 gist_acl_level=schema_data['gist_acl_level']
163 175 )
164 176 Session().commit()
165 177 new_gist_id = gist.gist_access_id
166 except formencode.Invalid as errors:
167 defaults = errors.value
178 except validation_schema.Invalid as errors:
179 defaults = data
180 errors = errors.asdict()
181
182 if 'nodes.0.content' in errors:
183 errors['content'] = errors['nodes.0.content']
184 del errors['nodes.0.content']
185 if 'nodes.0.filename' in errors:
186 errors['filename'] = errors['nodes.0.filename']
187 del errors['nodes.0.filename']
168 188
169 189 return formencode.htmlfill.render(
170 190 render('admin/gists/new.html'),
171 191 defaults=defaults,
172 errors=errors.error_dict or {},
192 errors=errors,
173 193 prefix_error=False,
174 194 encoding="UTF-8",
175 195 force_defaults=False
@@ -243,7 +263,8 b' class GistsController(BaseController):'
243 263 log.exception("Exception in gist show")
244 264 raise HTTPNotFound()
245 265 if format == 'raw':
246 content = '\n\n'.join([f.content for f in c.files if (f_path is None or f.path == f_path)])
266 content = '\n\n'.join([f.content for f in c.files
267 if (f_path is None or f.path == f_path)])
247 268 response.content_type = 'text/plain'
248 269 return content
249 270 return render('admin/gists/show.html')
@@ -252,32 +273,35 b' class GistsController(BaseController):'
252 273 @NotAnonymous()
253 274 @auth.CSRFRequired()
254 275 def edit(self, gist_id):
276 self.__load_defaults()
255 277 self._add_gist_to_context(gist_id)
256 278
257 279 owner = c.gist.gist_owner == c.rhodecode_user.user_id
258 280 if not (h.HasPermissionAny('hg.admin')() or owner):
259 281 raise HTTPForbidden()
260 282
261 rpost = request.POST
262 nodes = {}
263 _file_data = zip(rpost.getall('org_files'), rpost.getall('files'),
264 rpost.getall('mimetypes'), rpost.getall('contents'))
265 for org_filename, filename, mimetype, content in _file_data:
266 nodes[org_filename] = {
267 'org_filename': org_filename,
268 'filename': filename,
269 'content': content,
270 'lexer': mimetype,
271 }
283 data = peppercorn.parse(request.POST.items())
284
285 schema = gist_schema.GistSchema()
286 schema = schema.bind(
287 # '0' is special value to leave lifetime untouched
288 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
289 )
290
272 291 try:
292 schema_data = schema.deserialize(data)
293 # convert to safer format with just KEYs so we sure no duplicates
294 schema_data['nodes'] = gist_schema.sequence_to_nodes(
295 schema_data['nodes'])
296
273 297 GistModel().update(
274 298 gist=c.gist,
275 description=rpost['description'],
299 description=schema_data['description'],
276 300 owner=c.gist.owner,
277 gist_mapping=nodes,
278 gist_type=c.gist.gist_type,
279 lifetime=rpost['lifetime'],
280 gist_acl_level=rpost['acl_level']
301 gist_mapping=schema_data['nodes'],
302 gist_type=schema_data['gist_type'],
303 lifetime=schema_data['lifetime'],
304 gist_acl_level=schema_data['gist_acl_level']
281 305 )
282 306
283 307 Session().commit()
@@ -287,6 +311,10 b' class GistsController(BaseController):'
287 311 # store only DB stuff for gist
288 312 Session().commit()
289 313 h.flash(_('Successfully updated gist data'), category='success')
314 except validation_schema.Invalid as errors:
315 errors = errors.asdict()
316 h.flash(_('Error occurred during update of gist {}: {}').format(
317 gist_id, errors), category='error')
290 318 except Exception:
291 319 log.exception("Exception in gist edit")
292 320 h.flash(_('Error occurred during update of gist %s') % gist_id,
@@ -317,7 +345,7 b' class GistsController(BaseController):'
317 345 # this cannot use timeago, since it's used in select2 as a value
318 346 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
319 347 self.__load_defaults(
320 extra_values=('0', _('%(expiry)s - current value') % {'expiry': expiry}))
348 extra_values=(0, _('%(expiry)s - current value') % {'expiry': expiry}))
321 349 return render('admin/gists/edit.html')
322 350
323 351 @LoginRequired()
@@ -346,3 +346,17 b' class MyAccountController(BaseController'
346 346 h.flash(_("Auth token successfully deleted"), category='success')
347 347
348 348 return redirect(url('my_account_auth_tokens'))
349
350 def my_notifications(self):
351 c.active = 'notifications'
352 return render('admin/my_account/my_account.html')
353
354 @auth.CSRFRequired()
355 def my_notifications_toggle_visibility(self):
356 user = c.rhodecode_user.get_instance()
357 user_data = user.user_data
358 status = user_data.get('notification_status', False)
359 user_data['notification_status'] = not status
360 user.user_data = user_data
361 Session().commit()
362 return redirect(url('my_account_notifications'))
@@ -86,6 +86,7 b' class NotificationsController(BaseContro'
86 86
87 87 return render('admin/notifications/notifications.html')
88 88
89
89 90 @auth.CSRFRequired()
90 91 def mark_all_read(self):
91 92 if request.is_xhr:
@@ -33,6 +33,7 b' from pylons.controllers.util import redi'
33 33 from pylons.i18n.translation import _
34 34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
35 35
36 import rhodecode
36 37 from rhodecode.lib import auth, helpers as h
37 38 from rhodecode.lib.auth import (
38 39 LoginRequired, HasPermissionAllDecorator,
@@ -42,7 +43,7 b' from rhodecode.lib.base import BaseRepoC'
42 43 from rhodecode.lib.ext_json import json
43 44 from rhodecode.lib.exceptions import AttachedForksError
44 45 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
45 from rhodecode.lib.utils2 import safe_int
46 from rhodecode.lib.utils2 import safe_int, str2bool
46 47 from rhodecode.lib.vcs import RepositoryError
47 48 from rhodecode.model.db import (
48 49 User, Repository, UserFollowing, RepoGroup, RepositoryField)
@@ -779,6 +780,8 b' class ReposController(BaseRepoController'
779 780 c.repo_info = self._load_repo(repo_name)
780 781 defaults = self._vcs_form_defaults(repo_name)
781 782 c.inherit_global_settings = defaults['inherit_global_settings']
783 c.labs_active = str2bool(
784 rhodecode.CONFIG.get('labs_settings_active', 'true'))
782 785
783 786 return htmlfill.render(
784 787 render('admin/repos/repo_edit.html'),
@@ -79,7 +79,7 b' class SettingsController(BaseController)'
79 79 def __before__(self):
80 80 super(SettingsController, self).__before__()
81 81 c.labs_active = str2bool(
82 rhodecode.CONFIG.get('labs_settings_active', 'false'))
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
83 83 c.navlist = navigation_list(request)
84 84
85 85 def _get_hg_ui_settings(self):
@@ -790,13 +790,6 b' LabSetting = collections.namedtuple('
790 790 # rhodecode.model.forms.LabsSettingsForm.
791 791 _LAB_SETTINGS = [
792 792 LabSetting(
793 key='rhodecode_hg_use_rebase_for_merging',
794 type='bool',
795 group=lazy_ugettext('Mercurial server-side merge'),
796 label=lazy_ugettext('Use rebase instead of creating a merge commit when merging via web interface'),
797 help='' # Do not translate the empty string!
798 ),
799 LabSetting(
800 793 key='rhodecode_proxy_subversion_http_requests',
801 794 type='bool',
802 795 group=lazy_ugettext('Subversion HTTP Support'),
@@ -83,9 +83,6 b' class UsersController(BaseController):'
83 83 from rhodecode.lib.utils import PartialRenderer
84 84 _render = PartialRenderer('data_table/_dt_elements.html')
85 85
86 def grav_tmpl(user_email, size):
87 return _render("user_gravatar", user_email, size)
88
89 86 def username(user_id, username):
90 87 return _render("user_name", user_id, username)
91 88
@@ -100,9 +97,7 b' class UsersController(BaseController):'
100 97 users_data = []
101 98 for user in c.users_list:
102 99 users_data.append({
103 "gravatar": grav_tmpl(user.email, 20),
104 "username": h.link_to(
105 user.username, h.url('user_profile', username=user.username)),
100 "username": h.gravatar_with_user(user.username),
106 101 "username_raw": user.username,
107 102 "email": user.email,
108 103 "first_name": h.escape(user.name),
@@ -351,7 +351,8 b' class ChangesetController(BaseRepoContro'
351 351 f_path=request.POST.get('f_path'),
352 352 line_no=request.POST.get('line'),
353 353 status_change=(ChangesetStatus.get_status_lbl(status)
354 if status else None)
354 if status else None),
355 status_change_type=status
355 356 )
356 357 # get status if set !
357 358 if status:
@@ -136,11 +136,13 b' class FilesController(BaseRepoController'
136 136 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
137 137 return caches.get_cache_manager('repo_cache_long', _namespace)
138 138
139 def _get_tree_at_commit(self, repo_name, commit_id, f_path):
139 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
140 full_load=False, force=False):
140 141 def _cached_tree():
141 142 log.debug('Generating cached file tree for %s, %s, %s',
142 143 repo_name, commit_id, f_path)
143 return render('files/files_browser.html')
144 c.full_load = full_load
145 return render('files/files_browser_tree.html')
144 146
145 147 cache_manager = self.__get_tree_cache_manager(
146 148 repo_name, caches.FILE_TREE)
@@ -148,6 +150,10 b' class FilesController(BaseRepoController'
148 150 cache_key = caches.compute_key_from_params(
149 151 repo_name, commit_id, f_path)
150 152
153 if force:
154 # we want to force recompute of caches
155 cache_manager.remove_value(cache_key)
156
151 157 return cache_manager.get(cache_key, createfunc=_cached_tree)
152 158
153 159 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
@@ -165,22 +171,6 b' class FilesController(BaseRepoController'
165 171 repo_name, commit_id, f_path)
166 172 return cache_manager.get(cache_key, createfunc=_cached_nodes)
167 173
168 def _get_metadata_at_commit(self, repo_name, commit, dir_node):
169 def _cached_metadata():
170 log.debug('Generating cached metadata for %s, %s, %s',
171 repo_name, commit.raw_id, safe_str(dir_node.path))
172
173 data = ScmModel().get_dirnode_metadata(commit, dir_node)
174 return data
175
176 cache_manager = self.__get_tree_cache_manager(
177 repo_name, caches.FILE_TREE_META)
178
179 cache_key = caches.compute_key_from_params(
180 repo_name, commit.raw_id, safe_str(dir_node.path))
181
182 return cache_manager.get(cache_key, createfunc=_cached_metadata)
183
184 174 @LoginRequired()
185 175 @HasRepoPermissionAnyDecorator(
186 176 'repository.read', 'repository.write', 'repository.admin')
@@ -246,6 +236,7 b' class FilesController(BaseRepoController'
246 236 c.authors = []
247 237 c.file_tree = self._get_tree_at_commit(
248 238 repo_name, c.commit.raw_id, f_path)
239
249 240 except RepositoryError as e:
250 241 h.flash(safe_str(e), category='error')
251 242 raise HTTPNotFound()
@@ -1092,23 +1083,32 b' class FilesController(BaseRepoController'
1092 1083 @XHRRequired()
1093 1084 @HasRepoPermissionAnyDecorator(
1094 1085 'repository.read', 'repository.write', 'repository.admin')
1095 @jsonify
1096 def metadata_list(self, repo_name, revision, f_path):
1086 def nodetree_full(self, repo_name, commit_id, f_path):
1097 1087 """
1098 Returns a json dict that contains commit date, author, revision
1099 and id for the specified repo, revision and file path
1088 Returns rendered html of file tree that contains commit date,
1089 author, revision for the specified combination of
1090 repo, commit_id and file path
1100 1091
1101 1092 :param repo_name: name of the repository
1102 :param revision: revision of files
1093 :param commit_id: commit_id of file tree
1103 1094 :param f_path: file path of the requested directory
1104 1095 """
1105 1096
1106 commit = self.__get_commit_or_redirect(revision, repo_name)
1097 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1107 1098 try:
1108 file_node = commit.get_node(f_path)
1099 dir_node = commit.get_node(f_path)
1109 1100 except RepositoryError as e:
1110 return {'error': safe_str(e)}
1101 return 'error {}'.format(safe_str(e))
1102
1103 if dir_node.is_file():
1104 return ''
1111 1105
1112 metadata = self._get_metadata_at_commit(
1113 repo_name, commit, file_node)
1114 return {'metadata': metadata}
1106 c.file = dir_node
1107 c.commit = commit
1108
1109 # using force=True here, make a little trick. We flush the cache and
1110 # compute it using the same key as without full_load, so the fully
1111 # loaded cached tree is now returned instead of partial
1112 return self._get_tree_at_commit(
1113 repo_name, commit.raw_id, dir_node.path, full_load=True,
1114 force=True)
@@ -244,7 +244,7 b' class JournalController(BaseController):'
244 244 try:
245 245 self.scm_model.toggle_following_user(
246 246 user_id, c.rhodecode_user.user_id)
247 Session.commit()
247 Session().commit()
248 248 return 'ok'
249 249 except Exception:
250 250 raise HTTPBadRequest()
@@ -254,7 +254,7 b' class JournalController(BaseController):'
254 254 try:
255 255 self.scm_model.toggle_following_repo(
256 256 repo_id, c.rhodecode_user.user_id)
257 Session.commit()
257 Session().commit()
258 258 return 'ok'
259 259 except Exception:
260 260 raise HTTPBadRequest()
@@ -32,6 +32,7 b' from pylons.i18n.translation import _'
32 32 from sqlalchemy.sql import func
33 33 from sqlalchemy.sql.expression import or_
34 34
35 from rhodecode import events
35 36 from rhodecode.lib import auth, diffs, helpers as h
36 37 from rhodecode.lib.ext_json import json
37 38 from rhodecode.lib.base import (
@@ -640,6 +641,9 b' class PullrequestsController(BaseRepoCon'
640 641 pull_request_id = safe_int(pull_request_id)
641 642 c.pull_request = PullRequest.get_or_404(pull_request_id)
642 643
644 c.template_context['pull_request_data']['pull_request_id'] = \
645 pull_request_id
646
643 647 # pull_requests repo_name we opened it against
644 648 # ie. target_repo must match
645 649 if repo_name != c.pull_request.target_repo.repo_name:
@@ -758,9 +762,13 b' class PullrequestsController(BaseRepoCon'
758 762 line_no=request.POST.get('line'),
759 763 status_change=(ChangesetStatus.get_status_lbl(status)
760 764 if status and allowed_to_change_status else None),
765 status_change_type=(status
766 if status and allowed_to_change_status else None),
761 767 closing_pr=close_pr
762 768 )
763 769
770
771
764 772 if allowed_to_change_status:
765 773 old_calculated_status = pull_request.calculated_review_status()
766 774 # get status if set !
@@ -774,6 +782,7 b' class PullrequestsController(BaseRepoCon'
774 782 )
775 783
776 784 Session().flush()
785 events.trigger(events.PullRequestCommentEvent(pull_request, comm))
777 786 # we now calculate the status of pull request, and based on that
778 787 # calculation we set the commits status
779 788 calculated_status = pull_request.calculated_review_status()
@@ -35,6 +35,7 b' from rhodecode.lib.helpers import Page'
35 35 from rhodecode.lib.utils2 import safe_str, safe_int
36 36 from rhodecode.lib.index import searcher_from_config
37 37 from rhodecode.model import validation_schema
38 from rhodecode.model.validation_schema.schemas import search_schema
38 39
39 40 log = logging.getLogger(__name__)
40 41
@@ -48,7 +49,7 b' class SearchController(BaseRepoControlle'
48 49 formatted_results = []
49 50 execution_time = ''
50 51
51 schema = validation_schema.SearchParamsSchema()
52 schema = search_schema.SearchParamsSchema()
52 53
53 54 search_params = {}
54 55 errors = []
@@ -75,7 +76,6 b' class SearchController(BaseRepoControlle'
75 76 page_limit = search_params['page_limit']
76 77 requested_page = search_params['requested_page']
77 78
78
79 79 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
80 80 ip_addr=self.ip_addr)
81 81
@@ -24,14 +24,12 b' Summary controller for RhodeCode Enterpr'
24 24
25 25 import logging
26 26 from string import lower
27 from itertools import product
28 27
29 28 from pylons import tmpl_context as c, request
30 29 from pylons.i18n.translation import _
31 30 from beaker.cache import cache_region, region_invalidate
32 31
33 from rhodecode.config.conf import (
34 ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP)
32 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
35 33 from rhodecode.controllers import utils
36 34 from rhodecode.controllers.changelog import _load_changelog_summary
37 35 from rhodecode.lib import caches, helpers as h
@@ -49,10 +47,6 b' from rhodecode.model.db import Statistic'
49 47
50 48 log = logging.getLogger(__name__)
51 49
52 README_FILES = [''.join([x[0][0], x[1][0]])
53 for x in sorted(list(product(ALL_READMES, ALL_EXTS)),
54 key=lambda y:y[0][1] + y[1][1])]
55
56 50
57 51 class SummaryController(BaseRepoController):
58 52
@@ -62,6 +56,7 b' class SummaryController(BaseRepoControll'
62 56 def __get_readme_data(self, db_repo):
63 57 repo_name = db_repo.repo_name
64 58 log.debug('Looking for README file')
59 default_renderer = c.visual.default_renderer
65 60
66 61 @cache_region('long_term')
67 62 def _generate_readme(cache_key):
@@ -73,7 +68,7 b' class SummaryController(BaseRepoControll'
73 68 if isinstance(commit, EmptyCommit):
74 69 raise EmptyRepositoryError()
75 70 renderer = MarkupRenderer()
76 for f in README_FILES:
71 for f in renderer.pick_readme_order(default_renderer):
77 72 try:
78 73 node = commit.get_node(f)
79 74 except NodeDoesNotExistError:
@@ -241,7 +236,7 b' class SummaryController(BaseRepoControll'
241 236 (_("Tag"), repo.tags, 'tag'),
242 237 (_("Bookmark"), repo.bookmarks, 'book'),
243 238 ]
244 res = self._create_reference_data(repo, refs_to_create)
239 res = self._create_reference_data(repo, repo_name, refs_to_create)
245 240 data = {
246 241 'more': False,
247 242 'results': res
@@ -258,14 +253,14 b' class SummaryController(BaseRepoControll'
258 253 # TODO: enable when vcs can handle bookmarks filters
259 254 # (_("Bookmarks"), repo.bookmarks, "book"),
260 255 ]
261 res = self._create_reference_data(repo, refs_to_create)
256 res = self._create_reference_data(repo, repo_name, refs_to_create)
262 257 data = {
263 258 'more': False,
264 259 'results': res
265 260 }
266 261 return data
267 262
268 def _create_reference_data(self, repo, refs_to_create):
263 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
269 264 format_ref_id = utils.get_format_ref_id(repo)
270 265
271 266 result = []
@@ -274,28 +269,32 b' class SummaryController(BaseRepoControll'
274 269 result.append({
275 270 'text': title,
276 271 'children': self._create_reference_items(
277 repo, refs, ref_type, format_ref_id),
272 repo, full_repo_name, refs, ref_type, format_ref_id),
278 273 })
279 274 return result
280 275
281 def _create_reference_items(self, repo, refs, ref_type, format_ref_id):
276 def _create_reference_items(self, repo, full_repo_name, refs, ref_type,
277 format_ref_id):
282 278 result = []
283 279 is_svn = h.is_svn(repo)
284 for name, raw_id in refs.iteritems():
280 for ref_name, raw_id in refs.iteritems():
281 files_url = self._create_files_url(
282 repo, full_repo_name, ref_name, raw_id, is_svn)
285 283 result.append({
286 'text': name,
287 'id': format_ref_id(name, raw_id),
284 'text': ref_name,
285 'id': format_ref_id(ref_name, raw_id),
288 286 'raw_id': raw_id,
289 287 'type': ref_type,
290 'files_url': self._create_files_url(repo, name, raw_id, is_svn)
288 'files_url': files_url,
291 289 })
292 290 return result
293 291
294 def _create_files_url(self, repo, name, raw_id, is_svn):
295 use_commit_id = '/' in name or is_svn
292 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id,
293 is_svn):
294 use_commit_id = '/' in ref_name or is_svn
296 295 return h.url(
297 296 'files_home',
298 repo_name=repo.name,
299 f_path=name if is_svn else '',
300 revision=raw_id if use_commit_id else name,
301 at=name)
297 repo_name=full_repo_name,
298 f_path=ref_name if is_svn else '',
299 revision=raw_id if use_commit_id else ref_name,
300 at=ref_name)
1 NO CONTENT: file renamed from rhodecode/interfaces.py to rhodecode/events/interfaces.py
@@ -17,37 +17,49 b''
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 from zope.interface import implementer
20 from rhodecode.interfaces import (
20
21 from rhodecode.translation import lazy_ugettext
22 from rhodecode.events.base import RhodecodeEvent
23 from rhodecode.events.interfaces import (
21 24 IUserRegistered, IUserPreCreate, IUserPreUpdate)
22 25
23 26
24 27 @implementer(IUserRegistered)
25 class UserRegistered(object):
28 class UserRegistered(RhodecodeEvent):
26 29 """
27 30 An instance of this class is emitted as an :term:`event` whenever a user
28 31 account is registered.
29 32 """
33 name = 'user-register'
34 display_name = lazy_ugettext('user registered')
35
30 36 def __init__(self, user, session):
31 37 self.user = user
32 38 self.session = session
33 39
34 40
35 41 @implementer(IUserPreCreate)
36 class UserPreCreate(object):
42 class UserPreCreate(RhodecodeEvent):
37 43 """
38 44 An instance of this class is emitted as an :term:`event` before a new user
39 45 object is created.
40 46 """
47 name = 'user-pre-create'
48 display_name = lazy_ugettext('user pre create')
49
41 50 def __init__(self, user_data):
42 51 self.user_data = user_data
43 52
44 53
45 54 @implementer(IUserPreUpdate)
46 class UserPreUpdate(object):
55 class UserPreUpdate(RhodecodeEvent):
47 56 """
48 57 An instance of this class is emitted as an :term:`event` before a user
49 58 object is updated.
50 59 """
60 name = 'user-pre-update'
61 display_name = lazy_ugettext('user pre update')
62
51 63 def __init__(self, user, user_data):
52 64 self.user = user
53 65 self.user_data = user_data
This diff has been collapsed as it changes many lines, (1901 lines changed) Show them Hide them
@@ -6,9 +6,9 b''
6 6 #, fuzzy
7 7 msgid ""
8 8 msgstr ""
9 "Project-Id-Version: rhodecode-enterprise-ce 4.2.0\n"
9 "Project-Id-Version: rhodecode-enterprise-ce 4.3.0\n"
10 10 "Report-Msgid-Bugs-To: marcin@rhodecode.com\n"
11 "POT-Creation-Date: 2016-06-30 17:18+0000\n"
11 "POT-Creation-Date: 2016-08-02 20:55+0000\n"
12 12 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 14 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,12 +18,13 b' msgstr ""'
18 18 "Generated-By: Babel 1.3\n"
19 19
20 20 #: rhodecode/admin/navigation.py:74 rhodecode/authentication/routes.py:60
21 #: rhodecode/integrations/views.py:126
21 22 #: rhodecode/templates/admin/permissions/permissions.html:36
22 23 msgid "Global"
23 24 msgstr ""
24 25
25 26 #: rhodecode/admin/navigation.py:75
26 #: rhodecode/templates/admin/repos/repo_edit.html:48
27 #: rhodecode/templates/admin/repos/repo_edit.html:52
27 28 msgid "VCS"
28 29 msgstr ""
29 30
@@ -36,7 +37,7 b' msgid "Remap and Rescan"'
36 37 msgstr ""
37 38
38 39 #: rhodecode/admin/navigation.py:78
39 #: rhodecode/templates/admin/repos/repo_edit.html:54
40 #: rhodecode/templates/admin/repos/repo_edit.html:58
40 41 msgid "Issue Tracker"
41 42 msgstr ""
42 43
@@ -45,7 +46,8 b' msgstr ""'
45 46 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:94
46 47 #: rhodecode/templates/admin/users/user_add.html:86
47 48 #: rhodecode/templates/admin/users/user_edit_profile.html:65
48 #: rhodecode/templates/admin/users/users.html:91
49 #: rhodecode/templates/admin/users/users.html:90
50 #: rhodecode/templates/email_templates/user_registration.mako:25
49 51 #: rhodecode/templates/users/user_profile.html:51
50 52 msgid "Email"
51 53 msgstr ""
@@ -59,15 +61,27 b' msgid "Full Text Search"'
59 61 msgstr ""
60 62
61 63 #: rhodecode/admin/navigation.py:83
64 #: rhodecode/templates/admin/integrations/base.html:21
65 #: rhodecode/templates/admin/integrations/edit.html:8
66 #: rhodecode/templates/admin/integrations/edit.html:19
67 #: rhodecode/templates/admin/integrations/list.html:15
68 #: rhodecode/templates/admin/integrations/list.html:19
69 #: rhodecode/templates/admin/integrations/list.html:26
70 #: rhodecode/templates/admin/repos/repo_edit.html:72
71 #: rhodecode/templates/base/base.html:84
72 msgid "Integrations"
73 msgstr ""
74
75 #: rhodecode/admin/navigation.py:85
62 76 #: rhodecode/templates/admin/settings/settings_system.html:47
63 77 msgid "System Info"
64 78 msgstr ""
65 79
66 #: rhodecode/admin/navigation.py:84
80 #: rhodecode/admin/navigation.py:86
67 81 msgid "Open Source Licenses"
68 82 msgstr ""
69 83
70 #: rhodecode/admin/navigation.py:91
84 #: rhodecode/admin/navigation.py:93
71 85 msgid "Labs"
72 86 msgstr ""
73 87
@@ -75,7 +89,9 b' msgstr ""'
75 89 msgid "Enable or disable this authentication plugin."
76 90 msgstr ""
77 91
78 #: rhodecode/authentication/schema.py:37
92 #: rhodecode/authentication/schema.py:37 rhodecode/integrations/schema.py:37
93 #: rhodecode/templates/admin/integrations/list.html:62
94 #: rhodecode/templates/admin/my_account/my_account_notifications.html:14
79 95 msgid "Enabled"
80 96 msgstr ""
81 97
@@ -217,7 +233,7 b' msgstr ""'
217 233 #: rhodecode/templates/login.html:50 rhodecode/templates/register.html:48
218 234 #: rhodecode/templates/admin/my_account/my_account.html:30
219 235 #: rhodecode/templates/admin/users/user_add.html:44
220 #: rhodecode/templates/base/base.html:314
236 #: rhodecode/templates/base/base.html:315
221 237 #: rhodecode/templates/debug_style/login.html:45
222 238 msgid "Password"
223 239 msgstr ""
@@ -266,12 +282,12 b' msgstr ""'
266 282 msgid "LDAP Attribute to map to user name"
267 283 msgstr ""
268 284
269 #: rhodecode/authentication/plugins/auth_ldap.py:144
270 msgid "The LDAP Login attribute of the CN must be specified"
285 #: rhodecode/authentication/plugins/auth_ldap.py:145
286 msgid "Login Attribute"
271 287 msgstr ""
272 288
273 289 #: rhodecode/authentication/plugins/auth_ldap.py:146
274 msgid "Login Attribute"
290 msgid "The LDAP Login attribute of the CN must be specified"
275 291 msgstr ""
276 292
277 293 #: rhodecode/authentication/plugins/auth_ldap.py:151
@@ -298,7 +314,7 b' msgstr ""'
298 314 msgid "Email Attribute"
299 315 msgstr ""
300 316
301 #: rhodecode/authentication/plugins/auth_ldap.py:348
317 #: rhodecode/authentication/plugins/auth_ldap.py:351
302 318 msgid "LDAP"
303 319 msgstr ""
304 320
@@ -331,7 +347,7 b' msgid "Rhodecode Token Auth"'
331 347 msgstr ""
332 348
333 349 #: rhodecode/controllers/changelog.py:90 rhodecode/controllers/compare.py:63
334 #: rhodecode/controllers/pullrequests.py:279
350 #: rhodecode/controllers/pullrequests.py:280
335 351 msgid "There are no commits yet"
336 352 msgstr ""
337 353
@@ -367,8 +383,8 b' msgid "No such commit exists for this re'
367 383 msgstr ""
368 384
369 385 #: rhodecode/controllers/changeset.py:335
370 #: rhodecode/controllers/pullrequests.py:746
371 #: rhodecode/model/pull_request.py:836
386 #: rhodecode/controllers/pullrequests.py:750
387 #: rhodecode/model/pull_request.py:843
372 388 #, python-format
373 389 msgid "Status change %(transition_icon)s %(status)s"
374 390 msgstr ""
@@ -423,100 +439,100 b' msgstr ""'
423 439 msgid "There are no files yet. %s"
424 440 msgstr ""
425 441
426 #: rhodecode/controllers/files.py:390 rhodecode/controllers/files.py:443
427 #: rhodecode/controllers/files.py:474 rhodecode/controllers/files.py:549
428 #: rhodecode/controllers/files.py:594 rhodecode/controllers/files.py:685
442 #: rhodecode/controllers/files.py:381 rhodecode/controllers/files.py:434
443 #: rhodecode/controllers/files.py:465 rhodecode/controllers/files.py:540
444 #: rhodecode/controllers/files.py:585 rhodecode/controllers/files.py:676
429 445 #, python-format
430 446 msgid "This repository has been locked by %s on %s"
431 447 msgstr ""
432 448
449 #: rhodecode/controllers/files.py:389 rhodecode/controllers/files.py:442
450 msgid "You can only delete files with revision being a valid branch "
451 msgstr ""
452
433 453 #: rhodecode/controllers/files.py:398 rhodecode/controllers/files.py:451
434 msgid "You can only delete files with revision being a valid branch "
435 msgstr ""
436
437 #: rhodecode/controllers/files.py:407 rhodecode/controllers/files.py:460
438 454 #, python-format
439 455 msgid "Deleted file %s via RhodeCode Enterprise"
440 456 msgstr ""
441 457
442 #: rhodecode/controllers/files.py:427
458 #: rhodecode/controllers/files.py:418
443 459 #, python-format
444 460 msgid "Successfully deleted file %s"
445 461 msgstr ""
446 462
447 #: rhodecode/controllers/files.py:430 rhodecode/controllers/files.py:536
448 #: rhodecode/controllers/files.py:673
463 #: rhodecode/controllers/files.py:421 rhodecode/controllers/files.py:527
464 #: rhodecode/controllers/files.py:664
449 465 msgid "Error occurred during commit"
450 466 msgstr ""
451 467
452 #: rhodecode/controllers/files.py:482 rhodecode/controllers/files.py:557
468 #: rhodecode/controllers/files.py:473 rhodecode/controllers/files.py:548
453 469 msgid "You can only edit files with revision being a valid branch "
454 470 msgstr ""
455 471
456 #: rhodecode/controllers/files.py:494 rhodecode/controllers/files.py:569
472 #: rhodecode/controllers/files.py:485 rhodecode/controllers/files.py:560
457 473 #, python-format
458 474 msgid "Edited file %s via RhodeCode Enterprise"
459 475 msgstr ""
460 476
461 #: rhodecode/controllers/files.py:511
477 #: rhodecode/controllers/files.py:502
462 478 msgid "No changes"
463 479 msgstr ""
464 480
465 #: rhodecode/controllers/files.py:533 rhodecode/controllers/files.py:662
481 #: rhodecode/controllers/files.py:524 rhodecode/controllers/files.py:653
466 482 #, python-format
467 483 msgid "Successfully committed to %s"
468 484 msgstr ""
469 485
470 #: rhodecode/controllers/files.py:607 rhodecode/controllers/files.py:696
486 #: rhodecode/controllers/files.py:598 rhodecode/controllers/files.py:687
471 487 msgid "Added file via RhodeCode Enterprise"
472 488 msgstr ""
473 489
474 #: rhodecode/controllers/files.py:632
490 #: rhodecode/controllers/files.py:623
475 491 msgid "No filename"
476 492 msgstr ""
477 493
478 #: rhodecode/controllers/files.py:665
494 #: rhodecode/controllers/files.py:656
479 495 msgid "The location specified must be a relative path and must not contain .. in the path"
480 496 msgstr ""
481 497
482 #: rhodecode/controllers/files.py:719
498 #: rhodecode/controllers/files.py:710
483 499 msgid "Downloads disabled"
484 500 msgstr ""
485 501
486 #: rhodecode/controllers/files.py:725
502 #: rhodecode/controllers/files.py:716
487 503 #, python-format
488 504 msgid "Unknown revision %s"
489 505 msgstr ""
490 506
491 #: rhodecode/controllers/files.py:727
507 #: rhodecode/controllers/files.py:718
492 508 msgid "Empty repository"
493 509 msgstr ""
494 510
495 #: rhodecode/controllers/files.py:729 rhodecode/controllers/files.py:763
511 #: rhodecode/controllers/files.py:720 rhodecode/controllers/files.py:754
496 512 msgid "Unknown archive type"
497 513 msgstr ""
498 514
499 #: rhodecode/controllers/files.py:930
515 #: rhodecode/controllers/files.py:921
500 516 #, python-format
501 517 msgid "Commit %(commit)s does not exist."
502 518 msgstr ""
503 519
504 #: rhodecode/controllers/files.py:947
520 #: rhodecode/controllers/files.py:938
505 521 #, python-format
506 522 msgid "%(file_path)s has not changed between %(commit_1)s and %(commit_2)s."
507 523 msgstr ""
508 524
509 #: rhodecode/controllers/files.py:1014
525 #: rhodecode/controllers/files.py:1005
510 526 msgid "Changesets"
511 527 msgstr ""
512 528
513 #: rhodecode/controllers/files.py:1035 rhodecode/controllers/summary.py:256
514 #: rhodecode/model/pull_request.py:1051 rhodecode/model/scm.py:783
529 #: rhodecode/controllers/files.py:1026 rhodecode/controllers/summary.py:251
530 #: rhodecode/model/pull_request.py:1059 rhodecode/model/scm.py:780
515 531 #: rhodecode/templates/base/vcs_settings.html:138
516 532 msgid "Branches"
517 533 msgstr ""
518 534
519 #: rhodecode/controllers/files.py:1039 rhodecode/model/scm.py:798
535 #: rhodecode/controllers/files.py:1030 rhodecode/model/scm.py:795
520 536 #: rhodecode/templates/base/vcs_settings.html:163
521 537 msgid "Tags"
522 538 msgstr ""
@@ -531,13 +547,13 b' msgid "Groups"'
531 547 msgstr ""
532 548
533 549 #: rhodecode/controllers/home.py:212 rhodecode/controllers/home.py:247
534 #: rhodecode/controllers/pullrequests.py:382
550 #: rhodecode/controllers/pullrequests.py:383
535 551 #: rhodecode/templates/admin/repo_groups/repo_group_edit_perms.html:128
536 552 #: rhodecode/templates/admin/repos/repo_add.html:15
537 553 #: rhodecode/templates/admin/repos/repo_add.html:19
538 554 #: rhodecode/templates/admin/users/user_edit_advanced.html:11
539 #: rhodecode/templates/base/base.html:79 rhodecode/templates/base/base.html:149
540 #: rhodecode/templates/base/base.html:626
555 #: rhodecode/templates/base/base.html:78 rhodecode/templates/base/base.html:150
556 #: rhodecode/templates/base/base.html:627
541 557 msgid "Repositories"
542 558 msgstr ""
543 559
@@ -554,93 +570,93 b' msgstr ""'
554 570 msgid "journal"
555 571 msgstr ""
556 572
557 #: rhodecode/controllers/pullrequests.py:293
573 #: rhodecode/controllers/pullrequests.py:294
558 574 msgid "Commit does not exist"
559 575 msgstr ""
560 576
561 #: rhodecode/controllers/pullrequests.py:405
577 #: rhodecode/controllers/pullrequests.py:406
562 578 msgid "Pull request requires a title with min. 3 chars"
563 579 msgstr ""
564 580
565 #: rhodecode/controllers/pullrequests.py:407
581 #: rhodecode/controllers/pullrequests.py:408
566 582 msgid "Error creating pull request: {}"
567 583 msgstr ""
568 584
569 #: rhodecode/controllers/pullrequests.py:454
585 #: rhodecode/controllers/pullrequests.py:455
570 586 msgid "Successfully opened new pull request"
571 587 msgstr ""
572 588
573 #: rhodecode/controllers/pullrequests.py:457
589 #: rhodecode/controllers/pullrequests.py:458
574 590 msgid "Error occurred during sending pull request"
575 591 msgstr ""
576 592
577 #: rhodecode/controllers/pullrequests.py:497
593 #: rhodecode/controllers/pullrequests.py:498
578 594 msgid "Cannot update closed pull requests."
579 595 msgstr ""
580 596
581 #: rhodecode/controllers/pullrequests.py:503
597 #: rhodecode/controllers/pullrequests.py:504
582 598 msgid "Pull request title & description updated."
583 599 msgstr ""
584 600
585 #: rhodecode/controllers/pullrequests.py:513
601 #: rhodecode/controllers/pullrequests.py:514
586 602 msgid "Pull request updated to \"{source_commit_id}\" with {count_added} added, {count_removed} removed commits."
587 603 msgstr ""
588 604
589 #: rhodecode/controllers/pullrequests.py:523
605 #: rhodecode/controllers/pullrequests.py:524
590 606 msgid "Nothing changed in pull request."
591 607 msgstr ""
592 608
593 #: rhodecode/controllers/pullrequests.py:526
609 #: rhodecode/controllers/pullrequests.py:527
594 610 msgid "Skipping update of pull request due to reference type: {reference_type}"
595 611 msgstr ""
596 612
597 #: rhodecode/controllers/pullrequests.py:533
613 #: rhodecode/controllers/pullrequests.py:534
598 614 msgid "Update failed due to missing commits."
599 615 msgstr ""
600 616
601 #: rhodecode/controllers/pullrequests.py:579
617 #: rhodecode/controllers/pullrequests.py:580
602 618 msgid "Pull request reviewer approval is pending."
603 619 msgstr ""
604 620
605 #: rhodecode/controllers/pullrequests.py:593
621 #: rhodecode/controllers/pullrequests.py:594
606 622 msgid "Pull request was successfully merged and closed."
607 623 msgstr ""
608 624
609 #: rhodecode/controllers/pullrequests.py:631
625 #: rhodecode/controllers/pullrequests.py:632
610 626 msgid "Successfully deleted pull request"
611 627 msgstr ""
612 628
613 #: rhodecode/controllers/pullrequests.py:664
629 #: rhodecode/controllers/pullrequests.py:668
614 630 msgid "Reviewer approval is pending."
615 631 msgstr ""
616 632
617 #: rhodecode/controllers/pullrequests.py:706
633 #: rhodecode/controllers/pullrequests.py:710
618 634 msgid "Close Pull Request"
619 635 msgstr ""
620 636
621 #: rhodecode/controllers/pullrequests.py:750
622 #: rhodecode/model/pull_request.py:840
637 #: rhodecode/controllers/pullrequests.py:754
638 #: rhodecode/model/pull_request.py:847
623 639 msgid "Closing with"
624 640 msgstr ""
625 641
626 #: rhodecode/controllers/pullrequests.py:795
642 #: rhodecode/controllers/pullrequests.py:802
627 643 #, python-format
628 644 msgid "Closing pull request on other statuses than rejected or approved is forbidden. Calculated status from all reviewers is currently: %s"
629 645 msgstr ""
630 646
631 #: rhodecode/controllers/summary.py:240
647 #: rhodecode/controllers/summary.py:235
632 648 msgid "Branch"
633 649 msgstr ""
634 650
635 #: rhodecode/controllers/summary.py:241
651 #: rhodecode/controllers/summary.py:236
636 652 msgid "Tag"
637 653 msgstr ""
638 654
639 #: rhodecode/controllers/summary.py:242
655 #: rhodecode/controllers/summary.py:237
640 656 msgid "Bookmark"
641 657 msgstr ""
642 658
643 #: rhodecode/controllers/summary.py:257
659 #: rhodecode/controllers/summary.py:252
644 660 msgid "Closed branches"
645 661 msgstr ""
646 662
@@ -652,83 +668,87 b' msgstr ""'
652 668 msgid "Error occurred during update of default values"
653 669 msgstr ""
654 670
655 #: rhodecode/controllers/admin/gists.py:59
671 #: rhodecode/controllers/admin/gists.py:63
656 672 #: rhodecode/controllers/admin/my_account.py:307
657 #: rhodecode/controllers/admin/users.py:436
673 #: rhodecode/controllers/admin/users.py:431
658 674 msgid "forever"
659 675 msgstr ""
660 676
661 #: rhodecode/controllers/admin/gists.py:60
677 #: rhodecode/controllers/admin/gists.py:64
662 678 #: rhodecode/controllers/admin/my_account.py:308
663 #: rhodecode/controllers/admin/users.py:437
679 #: rhodecode/controllers/admin/users.py:432
664 680 msgid "5 minutes"
665 681 msgstr ""
666 682
667 #: rhodecode/controllers/admin/gists.py:61
683 #: rhodecode/controllers/admin/gists.py:65
668 684 #: rhodecode/controllers/admin/my_account.py:309
669 #: rhodecode/controllers/admin/users.py:438
685 #: rhodecode/controllers/admin/users.py:433
670 686 msgid "1 hour"
671 687 msgstr ""
672 688
673 #: rhodecode/controllers/admin/gists.py:62
689 #: rhodecode/controllers/admin/gists.py:66
674 690 #: rhodecode/controllers/admin/my_account.py:310
675 #: rhodecode/controllers/admin/users.py:439
691 #: rhodecode/controllers/admin/users.py:434
676 692 msgid "1 day"
677 693 msgstr ""
678 694
679 #: rhodecode/controllers/admin/gists.py:63
680 #: rhodecode/controllers/admin/my_account.py:311
681 #: rhodecode/controllers/admin/users.py:440
682 msgid "1 month"
683 msgstr ""
684
685 695 #: rhodecode/controllers/admin/gists.py:67
696 #: rhodecode/controllers/admin/my_account.py:311
697 #: rhodecode/controllers/admin/users.py:435
698 msgid "1 month"
699 msgstr ""
700
701 #: rhodecode/controllers/admin/gists.py:71
686 702 #: rhodecode/controllers/admin/my_account.py:313
687 #: rhodecode/controllers/admin/users.py:442
703 #: rhodecode/controllers/admin/users.py:437
688 704 msgid "Lifetime"
689 705 msgstr ""
690 706
691 #: rhodecode/controllers/admin/gists.py:69
707 #: rhodecode/controllers/admin/gists.py:73
692 708 msgid "Requires registered account"
693 709 msgstr ""
694 710
695 #: rhodecode/controllers/admin/gists.py:70
711 #: rhodecode/controllers/admin/gists.py:74
696 712 msgid "Can be accessed by anonymous users"
697 713 msgstr ""
698 714
699 #: rhodecode/controllers/admin/gists.py:180
715 #: rhodecode/controllers/admin/gists.py:200
700 716 msgid "Error occurred during gist creation"
701 717 msgstr ""
702 718
703 #: rhodecode/controllers/admin/gists.py:211
719 #: rhodecode/controllers/admin/gists.py:231
704 720 #, python-format
705 721 msgid "Deleted gist %s"
706 722 msgstr ""
707 723
708 #: rhodecode/controllers/admin/gists.py:284
724 #: rhodecode/controllers/admin/gists.py:308
709 725 msgid "Successfully updated gist content"
710 726 msgstr ""
711 727
712 #: rhodecode/controllers/admin/gists.py:289
728 #: rhodecode/controllers/admin/gists.py:313
713 729 msgid "Successfully updated gist data"
714 730 msgstr ""
715 731
716 #: rhodecode/controllers/admin/gists.py:292
732 #: rhodecode/controllers/admin/gists.py:316
733 msgid "Error occurred during update of gist {}: {}"
734 msgstr ""
735
736 #: rhodecode/controllers/admin/gists.py:320
717 737 #, python-format
718 738 msgid "Error occurred during update of gist %s"
719 739 msgstr ""
720 740
721 #: rhodecode/controllers/admin/gists.py:315
741 #: rhodecode/controllers/admin/gists.py:343
722 742 #: rhodecode/templates/admin/gists/show.html:67
723 743 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:19
724 744 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:42
725 745 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:16
726 746 #: rhodecode/templates/admin/users/user_edit_auth_tokens.html:38
727 #: rhodecode/templates/data_table/_dt_elements.html:253
747 #: rhodecode/templates/data_table/_dt_elements.html:255
728 748 msgid "never"
729 749 msgstr ""
730 750
731 #: rhodecode/controllers/admin/gists.py:320
751 #: rhodecode/controllers/admin/gists.py:348
732 752 #, python-format
733 753 msgid "%(expiry)s - current value"
734 754 msgstr ""
@@ -742,7 +762,7 b' msgid "Your account was updated successf'
742 762 msgstr ""
743 763
744 764 #: rhodecode/controllers/admin/my_account.py:143
745 #: rhodecode/controllers/admin/users.py:223
765 #: rhodecode/controllers/admin/users.py:218
746 766 #, python-format
747 767 msgid "Error occurred during update of user %s"
748 768 msgstr ""
@@ -756,38 +776,38 b' msgid "Error occurred during update of u'
756 776 msgstr ""
757 777
758 778 #: rhodecode/controllers/admin/my_account.py:261
759 #: rhodecode/controllers/admin/users.py:616
779 #: rhodecode/controllers/admin/users.py:611
760 780 #, python-format
761 781 msgid "Added new email address `%s` for user account"
762 782 msgstr ""
763 783
764 784 #: rhodecode/controllers/admin/my_account.py:268
765 #: rhodecode/controllers/admin/users.py:623
785 #: rhodecode/controllers/admin/users.py:618
766 786 msgid "An error occurred during email saving"
767 787 msgstr ""
768 788
769 789 #: rhodecode/controllers/admin/my_account.py:278
770 #: rhodecode/controllers/admin/users.py:638
790 #: rhodecode/controllers/admin/users.py:633
771 791 msgid "Removed email address from user account"
772 792 msgstr ""
773 793
774 794 #: rhodecode/controllers/admin/my_account.py:316
775 #: rhodecode/controllers/admin/users.py:445
795 #: rhodecode/controllers/admin/users.py:440
776 796 msgid "Role"
777 797 msgstr ""
778 798
779 799 #: rhodecode/controllers/admin/my_account.py:329
780 #: rhodecode/controllers/admin/users.py:469
800 #: rhodecode/controllers/admin/users.py:464
781 801 msgid "Auth token successfully created"
782 802 msgstr ""
783 803
784 804 #: rhodecode/controllers/admin/my_account.py:342
785 #: rhodecode/controllers/admin/users.py:488
805 #: rhodecode/controllers/admin/users.py:483
786 806 msgid "Auth token successfully reset"
787 807 msgstr ""
788 808
789 809 #: rhodecode/controllers/admin/my_account.py:346
790 #: rhodecode/controllers/admin/users.py:492
810 #: rhodecode/controllers/admin/users.py:487
791 811 msgid "Auth token successfully deleted"
792 812 msgstr ""
793 813
@@ -862,170 +882,170 b' msgstr ""'
862 882 msgid "Repository Group permissions updated"
863 883 msgstr ""
864 884
865 #: rhodecode/controllers/admin/repos.py:128
885 #: rhodecode/controllers/admin/repos.py:129
866 886 #, python-format
867 887 msgid "Error creating repository %s: invalid certificate"
868 888 msgstr ""
869 889
870 #: rhodecode/controllers/admin/repos.py:132
890 #: rhodecode/controllers/admin/repos.py:133
871 891 #, python-format
872 892 msgid "Error creating repository %s"
873 893 msgstr ""
874 894
875 #: rhodecode/controllers/admin/repos.py:264
895 #: rhodecode/controllers/admin/repos.py:265
876 896 #, python-format
877 897 msgid "Created repository %s from %s"
878 898 msgstr ""
879 899
880 #: rhodecode/controllers/admin/repos.py:273
900 #: rhodecode/controllers/admin/repos.py:274
881 901 #, python-format
882 902 msgid "Forked repository %s as %s"
883 903 msgstr ""
884 904
885 #: rhodecode/controllers/admin/repos.py:276
905 #: rhodecode/controllers/admin/repos.py:277
886 906 #, python-format
887 907 msgid "Created repository %s"
888 908 msgstr ""
889 909
890 #: rhodecode/controllers/admin/repos.py:318
910 #: rhodecode/controllers/admin/repos.py:319
891 911 #, python-format
892 912 msgid "Repository %s updated successfully"
893 913 msgstr ""
894 914
895 #: rhodecode/controllers/admin/repos.py:337
915 #: rhodecode/controllers/admin/repos.py:338
896 916 #, python-format
897 917 msgid "Error occurred during update of repository %s"
898 918 msgstr ""
899 919
900 #: rhodecode/controllers/admin/repos.py:365
920 #: rhodecode/controllers/admin/repos.py:366
901 921 #, python-format
902 922 msgid "Detached %s forks"
903 923 msgstr ""
904 924
905 #: rhodecode/controllers/admin/repos.py:368
925 #: rhodecode/controllers/admin/repos.py:369
906 926 #, python-format
907 927 msgid "Deleted %s forks"
908 928 msgstr ""
909 929
910 #: rhodecode/controllers/admin/repos.py:373
930 #: rhodecode/controllers/admin/repos.py:374
911 931 #, python-format
912 932 msgid "Deleted repository %s"
913 933 msgstr ""
914 934
915 #: rhodecode/controllers/admin/repos.py:376
935 #: rhodecode/controllers/admin/repos.py:377
916 936 #, python-format
917 937 msgid "Cannot delete %s it still contains attached forks"
918 938 msgstr ""
919 939
920 #: rhodecode/controllers/admin/repos.py:381
940 #: rhodecode/controllers/admin/repos.py:382
921 941 #, python-format
922 942 msgid "An error occurred during deletion of %s"
923 943 msgstr ""
924 944
925 #: rhodecode/controllers/admin/repos.py:435
945 #: rhodecode/controllers/admin/repos.py:436
926 946 msgid "Repository permissions updated"
927 947 msgstr ""
928 948
929 #: rhodecode/controllers/admin/repos.py:466
949 #: rhodecode/controllers/admin/repos.py:467
930 950 msgid "An error occurred during creation of field"
931 951 msgstr ""
932 952
933 #: rhodecode/controllers/admin/repos.py:481
953 #: rhodecode/controllers/admin/repos.py:482
934 954 msgid "An error occurred during removal of field"
935 955 msgstr ""
936 956
937 #: rhodecode/controllers/admin/repos.py:520
957 #: rhodecode/controllers/admin/repos.py:521
938 958 msgid "Updated repository visibility in public journal"
939 959 msgstr ""
940 960
941 #: rhodecode/controllers/admin/repos.py:524
961 #: rhodecode/controllers/admin/repos.py:525
942 962 msgid "An error occurred during setting this repository in public journal"
943 963 msgstr ""
944 964
945 #: rhodecode/controllers/admin/repos.py:548
965 #: rhodecode/controllers/admin/repos.py:549
946 966 msgid "Nothing"
947 967 msgstr ""
948 968
949 #: rhodecode/controllers/admin/repos.py:550
969 #: rhodecode/controllers/admin/repos.py:551
950 970 #, python-format
951 971 msgid "Marked repo %s as fork of %s"
952 972 msgstr ""
953 973
954 #: rhodecode/controllers/admin/repos.py:557
974 #: rhodecode/controllers/admin/repos.py:558
955 975 msgid "An error occurred during this operation"
956 976 msgstr ""
957 977
958 #: rhodecode/controllers/admin/repos.py:575
978 #: rhodecode/controllers/admin/repos.py:576
959 979 msgid "Locked repository"
960 980 msgstr ""
961 981
962 #: rhodecode/controllers/admin/repos.py:578
982 #: rhodecode/controllers/admin/repos.py:579
963 983 msgid "Unlocked repository"
964 984 msgstr ""
965 985
966 #: rhodecode/controllers/admin/repos.py:581
967 #: rhodecode/controllers/admin/repos.py:610
986 #: rhodecode/controllers/admin/repos.py:582
987 #: rhodecode/controllers/admin/repos.py:611
968 988 msgid "An error occurred during unlocking"
969 989 msgstr ""
970 990
971 #: rhodecode/controllers/admin/repos.py:600
991 #: rhodecode/controllers/admin/repos.py:601
972 992 msgid "Unlocked"
973 993 msgstr ""
974 994
975 #: rhodecode/controllers/admin/repos.py:604
995 #: rhodecode/controllers/admin/repos.py:605
976 996 msgid "Locked"
977 997 msgstr ""
978 998
979 #: rhodecode/controllers/admin/repos.py:606
999 #: rhodecode/controllers/admin/repos.py:607
980 1000 #, python-format
981 1001 msgid "Repository has been %s"
982 1002 msgstr ""
983 1003
984 #: rhodecode/controllers/admin/repos.py:621
1004 #: rhodecode/controllers/admin/repos.py:622
985 1005 msgid "Cache invalidation successful"
986 1006 msgstr ""
987 1007
988 #: rhodecode/controllers/admin/repos.py:625
1008 #: rhodecode/controllers/admin/repos.py:626
989 1009 msgid "An error occurred during cache invalidation"
990 1010 msgstr ""
991 1011
992 #: rhodecode/controllers/admin/repos.py:645
1012 #: rhodecode/controllers/admin/repos.py:646
993 1013 msgid "Pulled from remote location"
994 1014 msgstr ""
995 1015
996 #: rhodecode/controllers/admin/repos.py:648
1016 #: rhodecode/controllers/admin/repos.py:649
997 1017 msgid "An error occurred during pull from remote location"
998 1018 msgstr ""
999 1019
1000 #: rhodecode/controllers/admin/repos.py:670
1020 #: rhodecode/controllers/admin/repos.py:671
1001 1021 msgid "An error occurred during deletion of repository stats"
1002 1022 msgstr ""
1003 1023
1004 #: rhodecode/controllers/admin/repos.py:717
1024 #: rhodecode/controllers/admin/repos.py:718
1005 1025 msgid "Error occurred during deleting issue tracker entry"
1006 1026 msgstr ""
1007 1027
1008 #: rhodecode/controllers/admin/repos.py:720
1028 #: rhodecode/controllers/admin/repos.py:721
1009 1029 #: rhodecode/controllers/admin/settings.py:363
1010 1030 msgid "Removed issue tracker entry"
1011 1031 msgstr ""
1012 1032
1013 #: rhodecode/controllers/admin/repos.py:750
1033 #: rhodecode/controllers/admin/repos.py:751
1014 1034 #: rhodecode/controllers/admin/settings.py:409
1015 1035 msgid "Updated issue tracker entries"
1016 1036 msgstr ""
1017 1037
1018 #: rhodecode/controllers/admin/repos.py:809
1038 #: rhodecode/controllers/admin/repos.py:812
1019 1039 #: rhodecode/controllers/admin/settings.py:142
1020 1040 #: rhodecode/controllers/admin/settings.py:719
1021 1041 msgid "Some form inputs contain invalid data."
1022 1042 msgstr ""
1023 1043
1024 #: rhodecode/controllers/admin/repos.py:827
1044 #: rhodecode/controllers/admin/repos.py:830
1025 1045 msgid "Error occurred during updating repository VCS settings"
1026 1046 msgstr ""
1027 1047
1028 #: rhodecode/controllers/admin/repos.py:831
1048 #: rhodecode/controllers/admin/repos.py:834
1029 1049 #: rhodecode/controllers/admin/settings.py:168
1030 1050 msgid "Updated VCS settings"
1031 1051 msgstr ""
@@ -1091,26 +1111,18 b' msgid "Updated Labs settings"'
1091 1111 msgstr ""
1092 1112
1093 1113 #: rhodecode/controllers/admin/settings.py:795
1094 msgid "Mercurial server-side merge"
1114 msgid "Subversion HTTP Support"
1095 1115 msgstr ""
1096 1116
1097 1117 #: rhodecode/controllers/admin/settings.py:796
1098 msgid "Use rebase instead of creating a merge commit when merging via web interface"
1118 msgid "Proxy subversion HTTP requests"
1099 1119 msgstr ""
1100 1120
1101 1121 #: rhodecode/controllers/admin/settings.py:802
1102 msgid "Subversion HTTP Support"
1103 msgstr ""
1104
1105 #: rhodecode/controllers/admin/settings.py:803
1106 msgid "Proxy subversion HTTP requests"
1107 msgstr ""
1108
1109 #: rhodecode/controllers/admin/settings.py:809
1110 1122 msgid "Subversion HTTP Server URL"
1111 1123 msgstr ""
1112 1124
1113 #: rhodecode/controllers/admin/settings.py:811
1125 #: rhodecode/controllers/admin/settings.py:804
1114 1126 msgid "e.g. http://localhost:8080/"
1115 1127 msgstr ""
1116 1128
@@ -1155,121 +1167,281 b' msgid "User Group global permissions upd'
1155 1167 msgstr ""
1156 1168
1157 1169 #: rhodecode/controllers/admin/user_groups.py:440
1158 #: rhodecode/controllers/admin/users.py:566
1170 #: rhodecode/controllers/admin/users.py:561
1159 1171 msgid "An error occurred during permissions saving"
1160 1172 msgstr ""
1161 1173
1162 #: rhodecode/controllers/admin/users.py:147
1174 #: rhodecode/controllers/admin/users.py:142
1163 1175 #, python-format
1164 1176 msgid "Created user %(user_link)s"
1165 1177 msgstr ""
1166 1178
1167 #: rhodecode/controllers/admin/users.py:162
1179 #: rhodecode/controllers/admin/users.py:157
1168 1180 #, python-format
1169 1181 msgid "Error occurred during creation of user %s"
1170 1182 msgstr ""
1171 1183
1172 #: rhodecode/controllers/admin/users.py:206
1184 #: rhodecode/controllers/admin/users.py:201
1173 1185 msgid "User updated successfully"
1174 1186 msgstr ""
1175 1187
1188 #: rhodecode/controllers/admin/users.py:252
1189 #, python-format
1190 msgid "Detached %s repositories"
1191 msgstr ""
1192
1176 1193 #: rhodecode/controllers/admin/users.py:257
1177 1194 #, python-format
1178 msgid "Detached %s repositories"
1179 msgstr ""
1180
1181 #: rhodecode/controllers/admin/users.py:262
1182 #, python-format
1183 1195 msgid "Deleted %s repositories"
1184 1196 msgstr ""
1185 1197
1198 #: rhodecode/controllers/admin/users.py:265
1199 #, python-format
1200 msgid "Detached %s repository groups"
1201 msgstr ""
1202
1186 1203 #: rhodecode/controllers/admin/users.py:270
1187 1204 #, python-format
1188 msgid "Detached %s repository groups"
1189 msgstr ""
1190
1191 #: rhodecode/controllers/admin/users.py:275
1192 #, python-format
1193 1205 msgid "Deleted %s repository groups"
1194 1206 msgstr ""
1195 1207
1208 #: rhodecode/controllers/admin/users.py:278
1209 #, python-format
1210 msgid "Detached %s user groups"
1211 msgstr ""
1212
1196 1213 #: rhodecode/controllers/admin/users.py:283
1197 1214 #, python-format
1198 msgid "Detached %s user groups"
1199 msgstr ""
1200
1201 #: rhodecode/controllers/admin/users.py:288
1202 #, python-format
1203 1215 msgid "Deleted %s user groups"
1204 1216 msgstr ""
1205 1217
1206 #: rhodecode/controllers/admin/users.py:299
1218 #: rhodecode/controllers/admin/users.py:294
1207 1219 msgid "Successfully deleted user"
1208 1220 msgstr ""
1209 1221
1210 #: rhodecode/controllers/admin/users.py:305
1222 #: rhodecode/controllers/admin/users.py:300
1211 1223 msgid "An error occurred during deletion of user"
1212 1224 msgstr ""
1213 1225
1214 #: rhodecode/controllers/admin/users.py:324
1226 #: rhodecode/controllers/admin/users.py:319
1215 1227 msgid "Force password change disabled for user"
1216 1228 msgstr ""
1217 1229
1218 #: rhodecode/controllers/admin/users.py:326
1230 #: rhodecode/controllers/admin/users.py:321
1219 1231 msgid "Force password change enabled for user"
1220 1232 msgstr ""
1221 1233
1222 #: rhodecode/controllers/admin/users.py:330
1234 #: rhodecode/controllers/admin/users.py:325
1223 1235 msgid "An error occurred during password reset for user"
1224 1236 msgstr ""
1225 1237
1226 #: rhodecode/controllers/admin/users.py:356
1238 #: rhodecode/controllers/admin/users.py:351
1227 1239 #, python-format
1228 1240 msgid "Created repository group `%s`"
1229 1241 msgstr ""
1230 1242
1231 #: rhodecode/controllers/admin/users.py:360
1243 #: rhodecode/controllers/admin/users.py:355
1232 1244 msgid "An error occurred during repository group creation for user"
1233 1245 msgstr ""
1234 1246
1235 #: rhodecode/controllers/admin/users.py:379
1236 #: rhodecode/controllers/admin/users.py:400
1237 #: rhodecode/controllers/admin/users.py:430
1238 #: rhodecode/controllers/admin/users.py:461
1239 #: rhodecode/controllers/admin/users.py:478
1240 #: rhodecode/controllers/admin/users.py:501
1241 #: rhodecode/controllers/admin/users.py:575
1242 #: rhodecode/controllers/admin/users.py:588
1243 #: rhodecode/controllers/admin/users.py:646
1247 #: rhodecode/controllers/admin/users.py:374
1248 #: rhodecode/controllers/admin/users.py:395
1249 #: rhodecode/controllers/admin/users.py:425
1250 #: rhodecode/controllers/admin/users.py:456
1251 #: rhodecode/controllers/admin/users.py:473
1252 #: rhodecode/controllers/admin/users.py:496
1253 #: rhodecode/controllers/admin/users.py:570
1254 #: rhodecode/controllers/admin/users.py:583
1255 #: rhodecode/controllers/admin/users.py:641
1244 1256 msgid "You can't edit this user"
1245 1257 msgstr ""
1246 1258
1247 #: rhodecode/controllers/admin/users.py:414
1259 #: rhodecode/controllers/admin/users.py:409
1248 1260 msgid "The user participates as reviewer in pull requests and cannot be deleted. You can set the user to \"inactive\" instead of deleting it."
1249 1261 msgstr ""
1250 1262
1251 #: rhodecode/controllers/admin/users.py:550
1263 #: rhodecode/controllers/admin/users.py:545
1252 1264 msgid "User global permissions updated successfully"
1253 1265 msgstr ""
1254 1266
1255 #: rhodecode/controllers/admin/users.py:678
1267 #: rhodecode/controllers/admin/users.py:673
1256 1268 #, python-format
1257 1269 msgid "An error occurred during ip saving:%s"
1258 1270 msgstr ""
1259 1271
1260 #: rhodecode/controllers/admin/users.py:693
1272 #: rhodecode/controllers/admin/users.py:688
1261 1273 msgid "An error occurred during ip saving"
1262 1274 msgstr ""
1263 1275
1264 #: rhodecode/controllers/admin/users.py:697
1276 #: rhodecode/controllers/admin/users.py:692
1265 1277 #, python-format
1266 1278 msgid "Added ips %s to user whitelist"
1267 1279 msgstr ""
1268 1280
1269 #: rhodecode/controllers/admin/users.py:715
1281 #: rhodecode/controllers/admin/users.py:710
1270 1282 msgid "Removed ip address from user whitelist"
1271 1283 msgstr ""
1272 1284
1285 #: rhodecode/events/pullrequest.py:65
1286 msgid "pullrequest created"
1287 msgstr ""
1288
1289 #: rhodecode/events/pullrequest.py:74
1290 msgid "pullrequest closed"
1291 msgstr ""
1292
1293 #: rhodecode/events/pullrequest.py:83
1294 msgid "pullrequest commits updated"
1295 msgstr ""
1296
1297 #: rhodecode/events/pullrequest.py:92
1298 msgid "pullrequest review changed"
1299 msgstr ""
1300
1301 #: rhodecode/events/pullrequest.py:101
1302 msgid "pullrequest merged"
1303 msgstr ""
1304
1305 #: rhodecode/events/pullrequest.py:110
1306 msgid "pullrequest commented"
1307 msgstr ""
1308
1309 #: rhodecode/events/repo.py:135
1310 msgid "repository pre create"
1311 msgstr ""
1312
1313 #: rhodecode/events/repo.py:144
1314 msgid "repository created"
1315 msgstr ""
1316
1317 #: rhodecode/events/repo.py:153
1318 msgid "repository pre delete"
1319 msgstr ""
1320
1321 #: rhodecode/events/repo.py:162
1322 msgid "repository deleted"
1323 msgstr ""
1324
1325 #: rhodecode/events/repo.py:193
1326 msgid "repository pre pull"
1327 msgstr ""
1328
1329 #: rhodecode/events/repo.py:202
1330 msgid "repository pull"
1331 msgstr ""
1332
1333 #: rhodecode/events/repo.py:211
1334 msgid "repository pre push"
1335 msgstr ""
1336
1337 #: rhodecode/events/repo.py:222
1338 msgid "repository push"
1339 msgstr ""
1340
1341 #: rhodecode/events/user.py:34
1342 msgid "user registered"
1343 msgstr ""
1344
1345 #: rhodecode/events/user.py:48
1346 msgid "user pre create"
1347 msgstr ""
1348
1349 #: rhodecode/events/user.py:61
1350 msgid "user pre update"
1351 msgstr ""
1352
1353 #: rhodecode/integrations/schema.py:35
1354 msgid "Enable or disable this integration."
1355 msgstr ""
1356
1357 #: rhodecode/integrations/schema.py:42
1358 msgid "Short name for this integration."
1359 msgstr ""
1360
1361 #: rhodecode/integrations/schema.py:44
1362 msgid "Integration name"
1363 msgstr ""
1364
1365 #: rhodecode/integrations/views.py:172
1366 msgid "Integration {integration_name} deleted successfully."
1367 msgstr ""
1368
1369 #: rhodecode/integrations/views.py:200
1370 msgid "Errors exist when saving integration settings. Please check the form inputs."
1371 msgstr ""
1372
1373 #: rhodecode/integrations/views.py:220
1374 msgid "Integration {integration_name} updated successfully."
1375 msgstr ""
1376
1377 #: rhodecode/integrations/types/slack.py:45
1378 msgid "Slack service URL"
1379 msgstr ""
1380
1381 #: rhodecode/integrations/types/slack.py:46
1382 msgid "This can be setup at the <a href=\"https://my.slack.com/services/new/incoming-webhook/\">slack app manager</a>"
1383 msgstr ""
1384
1385 #: rhodecode/integrations/types/slack.py:59 rhodecode/templates/login.html:43
1386 #: rhodecode/templates/register.html:41
1387 #: rhodecode/templates/admin/admin_log.html:7
1388 #: rhodecode/templates/admin/my_account/my_account_profile.html:24
1389 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:21
1390 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:66
1391 #: rhodecode/templates/admin/users/user_add.html:35
1392 #: rhodecode/templates/admin/users/user_edit_profile.html:39
1393 #: rhodecode/templates/admin/users/users.html:88
1394 #: rhodecode/templates/base/base.html:306
1395 #: rhodecode/templates/debug_style/login.html:36
1396 #: rhodecode/templates/email_templates/user_registration.mako:23
1397 #: rhodecode/templates/users/user_profile.html:27
1398 msgid "Username"
1399 msgstr ""
1400
1401 #: rhodecode/integrations/types/slack.py:60
1402 msgid "Username to show notifications coming from."
1403 msgstr ""
1404
1405 #: rhodecode/integrations/types/slack.py:69
1406 msgid "Channel"
1407 msgstr ""
1408
1409 #: rhodecode/integrations/types/slack.py:70
1410 msgid "Channel to send notifications to."
1411 msgstr ""
1412
1413 #: rhodecode/integrations/types/slack.py:79
1414 msgid "Emoji"
1415 msgstr ""
1416
1417 #: rhodecode/integrations/types/slack.py:80
1418 msgid "Emoji to use eg. :studio_microphone:"
1419 msgstr ""
1420
1421 #: rhodecode/integrations/types/slack.py:107
1422 msgid "Slack"
1423 msgstr ""
1424
1425 #: rhodecode/integrations/types/webhook.py:41
1426 msgid "Webhook URL"
1427 msgstr ""
1428
1429 #: rhodecode/integrations/types/webhook.py:42
1430 msgid "URL of the webhook to receive POST event."
1431 msgstr ""
1432
1433 #: rhodecode/integrations/types/webhook.py:51
1434 msgid "Secret Token"
1435 msgstr ""
1436
1437 #: rhodecode/integrations/types/webhook.py:52
1438 msgid "String used to validate received payloads."
1439 msgstr ""
1440
1441 #: rhodecode/integrations/types/webhook.py:62
1442 msgid "Webhook"
1443 msgstr ""
1444
1273 1445 #: rhodecode/lib/action_parser.py:89
1274 1446 msgid "[deleted] repository"
1275 1447 msgstr ""
@@ -1411,11 +1583,15 b' msgstr ""'
1411 1583 msgid "You need to be signed in to view this page"
1412 1584 msgstr ""
1413 1585
1414 #: rhodecode/lib/base.py:511
1586 #: rhodecode/lib/base.py:545
1415 1587 #, python-format
1416 1588 msgid "The repository at %(repo_name)s cannot be located."
1417 1589 msgstr ""
1418 1590
1591 #: rhodecode/lib/diffs.py:56
1592 msgid "Click to comment"
1593 msgstr ""
1594
1419 1595 #: rhodecode/lib/diffs.py:71
1420 1596 msgid "Binary file"
1421 1597 msgstr ""
@@ -1428,36 +1604,40 b' msgstr ""'
1428 1604 msgid "No changes detected"
1429 1605 msgstr ""
1430 1606
1431 #: rhodecode/lib/helpers.py:1434
1607 #: rhodecode/lib/diffs.py:631
1608 msgid "Click to select line"
1609 msgstr ""
1610
1611 #: rhodecode/lib/helpers.py:1481
1432 1612 #, python-format
1433 1613 msgid " and %s more"
1434 1614 msgstr ""
1435 1615
1436 #: rhodecode/lib/helpers.py:1438
1616 #: rhodecode/lib/helpers.py:1485
1437 1617 msgid "No Files"
1438 1618 msgstr ""
1439 1619
1440 #: rhodecode/lib/helpers.py:1511
1620 #: rhodecode/lib/helpers.py:1558
1441 1621 msgid "new file"
1442 1622 msgstr ""
1443 1623
1444 #: rhodecode/lib/helpers.py:1514
1624 #: rhodecode/lib/helpers.py:1561
1445 1625 msgid "mod"
1446 1626 msgstr ""
1447 1627
1448 #: rhodecode/lib/helpers.py:1517
1628 #: rhodecode/lib/helpers.py:1564
1449 1629 msgid "del"
1450 1630 msgstr ""
1451 1631
1452 #: rhodecode/lib/helpers.py:1520
1632 #: rhodecode/lib/helpers.py:1567
1453 1633 msgid "rename"
1454 1634 msgstr ""
1455 1635
1456 #: rhodecode/lib/helpers.py:1525
1636 #: rhodecode/lib/helpers.py:1572
1457 1637 msgid "chmod"
1458 1638 msgstr ""
1459 1639
1460 #: rhodecode/lib/helpers.py:1767
1640 #: rhodecode/lib/helpers.py:1819
1461 1641 msgid ""
1462 1642 "Example filter terms:\n"
1463 1643 " repository:vcs\n"
@@ -1476,89 +1656,91 b' msgid ""'
1476 1656 " \"username:test AND repository:test*\"\n"
1477 1657 msgstr ""
1478 1658
1479 #: rhodecode/lib/helpers.py:1787
1659 #: rhodecode/lib/helpers.py:1839
1480 1660 #, python-format
1481 1661 msgid "%s repository is not mapped to db perhaps it was created or renamed from the filesystem please run the application again in order to rescan repositories"
1482 1662 msgstr ""
1483 1663
1484 #: rhodecode/lib/utils2.py:453
1664 #: rhodecode/lib/utils2.py:454
1485 1665 #, python-format
1486 1666 msgid "%d year"
1487 1667 msgid_plural "%d years"
1488 1668 msgstr[0] ""
1489 1669 msgstr[1] ""
1490 1670
1491 #: rhodecode/lib/utils2.py:454
1671 #: rhodecode/lib/utils2.py:455
1492 1672 #, python-format
1493 1673 msgid "%d month"
1494 1674 msgid_plural "%d months"
1495 1675 msgstr[0] ""
1496 1676 msgstr[1] ""
1497 1677
1498 #: rhodecode/lib/utils2.py:455
1678 #: rhodecode/lib/utils2.py:456
1499 1679 #, python-format
1500 1680 msgid "%d day"
1501 1681 msgid_plural "%d days"
1502 1682 msgstr[0] ""
1503 1683 msgstr[1] ""
1504 1684
1505 #: rhodecode/lib/utils2.py:456
1685 #: rhodecode/lib/utils2.py:457
1506 1686 #, python-format
1507 1687 msgid "%d hour"
1508 1688 msgid_plural "%d hours"
1509 1689 msgstr[0] ""
1510 1690 msgstr[1] ""
1511 1691
1512 #: rhodecode/lib/utils2.py:457
1692 #: rhodecode/lib/utils2.py:458
1513 1693 #, python-format
1514 1694 msgid "%d minute"
1515 1695 msgid_plural "%d minutes"
1516 1696 msgstr[0] ""
1517 1697 msgstr[1] ""
1518 1698
1519 #: rhodecode/lib/utils2.py:458
1699 #: rhodecode/lib/utils2.py:459
1520 1700 #, python-format
1521 1701 msgid "%d second"
1522 1702 msgid_plural "%d seconds"
1523 1703 msgstr[0] ""
1524 1704 msgstr[1] ""
1525 1705
1526 #: rhodecode/lib/utils2.py:476
1706 #: rhodecode/lib/utils2.py:477
1527 1707 #, python-format
1528 1708 msgid "in %s"
1529 1709 msgstr ""
1530 1710
1531 #: rhodecode/lib/utils2.py:482
1711 #: rhodecode/lib/utils2.py:483
1532 1712 #, python-format
1533 1713 msgid "%s ago"
1534 1714 msgstr ""
1535 1715
1536 #: rhodecode/lib/utils2.py:492
1716 #: rhodecode/lib/utils2.py:493
1537 1717 #, python-format
1538 1718 msgid "%s, %s ago"
1539 1719 msgstr ""
1540 1720
1541 #: rhodecode/lib/utils2.py:494
1721 #: rhodecode/lib/utils2.py:495
1542 1722 #, python-format
1543 1723 msgid "in %s, %s"
1544 1724 msgstr ""
1545 1725
1546 #: rhodecode/lib/utils2.py:496
1726 #: rhodecode/lib/utils2.py:497
1547 1727 #, python-format
1548 1728 msgid "%s and %s"
1549 1729 msgstr ""
1550 1730
1551 #: rhodecode/lib/utils2.py:498
1731 #: rhodecode/lib/utils2.py:499
1552 1732 #, python-format
1553 1733 msgid "%s and %s ago"
1554 1734 msgstr ""
1555 1735
1556 #: rhodecode/lib/utils2.py:500
1736 #: rhodecode/lib/utils2.py:501
1557 1737 #, python-format
1558 1738 msgid "in %s and %s"
1559 1739 msgstr ""
1560 1740
1561 #: rhodecode/lib/utils2.py:504
1741 #: rhodecode/lib/utils2.py:505 rhodecode/public/js/scripts.js:25035
1742 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:49
1743 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:174
1562 1744 msgid "just now"
1563 1745 msgstr ""
1564 1746
@@ -1584,7 +1766,8 b' msgstr ""'
1584 1766 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:818
1585 1767 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:824
1586 1768 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:946
1587 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:971 rhodecode/model/db.py:2291
1769 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:971
1770 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2293 rhodecode/model/db.py:2285
1588 1771 msgid "Repository no access"
1589 1772 msgstr ""
1590 1773
@@ -1610,7 +1793,8 b' msgstr ""'
1610 1793 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:819
1611 1794 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:825
1612 1795 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:947
1613 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:972 rhodecode/model/db.py:2292
1796 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:972
1797 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2294 rhodecode/model/db.py:2286
1614 1798 msgid "Repository read access"
1615 1799 msgstr ""
1616 1800
@@ -1636,7 +1820,8 b' msgstr ""'
1636 1820 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:820
1637 1821 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:826
1638 1822 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:948
1639 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:973 rhodecode/model/db.py:2293
1823 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:973
1824 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2295 rhodecode/model/db.py:2287
1640 1825 msgid "Repository write access"
1641 1826 msgstr ""
1642 1827
@@ -1662,7 +1847,8 b' msgstr ""'
1662 1847 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:821
1663 1848 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:827
1664 1849 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:949
1665 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:974 rhodecode/model/db.py:2294
1850 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:974
1851 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2296 rhodecode/model/db.py:2288
1666 1852 msgid "Repository admin access"
1667 1853 msgstr ""
1668 1854
@@ -1728,7 +1914,8 b' msgstr ""'
1728 1914 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:839
1729 1915 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:845
1730 1916 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:967
1731 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:992 rhodecode/model/db.py:2312
1917 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:992
1918 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2314 rhodecode/model/db.py:2306
1732 1919 msgid "Repository creation disabled"
1733 1920 msgstr ""
1734 1921
@@ -1754,7 +1941,8 b' msgstr ""'
1754 1941 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:840
1755 1942 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:846
1756 1943 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:968
1757 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:993 rhodecode/model/db.py:2313
1944 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:993
1945 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2315 rhodecode/model/db.py:2307
1758 1946 msgid "Repository creation enabled"
1759 1947 msgstr ""
1760 1948
@@ -1780,7 +1968,8 b' msgstr ""'
1780 1968 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:844
1781 1969 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:850
1782 1970 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:972
1783 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:997 rhodecode/model/db.py:2317
1971 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:997
1972 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2319 rhodecode/model/db.py:2311
1784 1973 msgid "Repository forking disabled"
1785 1974 msgstr ""
1786 1975
@@ -1806,7 +1995,8 b' msgstr ""'
1806 1995 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:845
1807 1996 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:851
1808 1997 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:973
1809 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:998 rhodecode/model/db.py:2318
1998 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:998
1999 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2320 rhodecode/model/db.py:2312
1810 2000 msgid "Repository forking enabled"
1811 2001 msgstr ""
1812 2002
@@ -1853,7 +2043,8 b' msgstr ""'
1853 2043 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1186
1854 2044 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1196
1855 2045 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1318
1856 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1343 rhodecode/model/db.py:2950
2046 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1343
2047 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2952 rhodecode/model/db.py:2944
1857 2048 msgid "Not Reviewed"
1858 2049 msgstr ""
1859 2050
@@ -1879,7 +2070,8 b' msgstr ""'
1879 2070 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1187
1880 2071 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1197
1881 2072 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1319
1882 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1344 rhodecode/model/db.py:2951
2073 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1344
2074 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2953 rhodecode/model/db.py:2945
1883 2075 msgid "Approved"
1884 2076 msgstr ""
1885 2077
@@ -1905,7 +2097,8 b' msgstr ""'
1905 2097 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1188
1906 2098 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1198
1907 2099 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1320
1908 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1345 rhodecode/model/db.py:2952
2100 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1345
2101 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2954 rhodecode/model/db.py:2946
1909 2102 msgid "Rejected"
1910 2103 msgstr ""
1911 2104
@@ -1931,7 +2124,8 b' msgstr ""'
1931 2124 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:1189
1932 2125 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:1199
1933 2126 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:1321
1934 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1346 rhodecode/model/db.py:2953
2127 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1346
2128 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2955 rhodecode/model/db.py:2947
1935 2129 msgid "Under Review"
1936 2130 msgstr ""
1937 2131
@@ -1954,7 +2148,8 b' msgstr ""'
1954 2148 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:823
1955 2149 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:829
1956 2150 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:951
1957 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:976 rhodecode/model/db.py:2296
2151 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:976
2152 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2298 rhodecode/model/db.py:2290
1958 2153 msgid "Repository group no access"
1959 2154 msgstr ""
1960 2155
@@ -1977,7 +2172,8 b' msgstr ""'
1977 2172 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:824
1978 2173 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:830
1979 2174 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:952
1980 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:977 rhodecode/model/db.py:2297
2175 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:977
2176 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2299 rhodecode/model/db.py:2291
1981 2177 msgid "Repository group read access"
1982 2178 msgstr ""
1983 2179
@@ -2000,7 +2196,8 b' msgstr ""'
2000 2196 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:825
2001 2197 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:831
2002 2198 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:953
2003 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:978 rhodecode/model/db.py:2298
2199 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:978
2200 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2300 rhodecode/model/db.py:2292
2004 2201 msgid "Repository group write access"
2005 2202 msgstr ""
2006 2203
@@ -2023,7 +2220,8 b' msgstr ""'
2023 2220 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:826
2024 2221 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:832
2025 2222 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:954
2026 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:979 rhodecode/model/db.py:2299
2223 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:979
2224 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2301 rhodecode/model/db.py:2293
2027 2225 msgid "Repository group admin access"
2028 2226 msgstr ""
2029 2227
@@ -2045,7 +2243,8 b' msgstr ""'
2045 2243 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:828
2046 2244 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:834
2047 2245 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:956
2048 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:981 rhodecode/model/db.py:2301
2246 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:981
2247 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2303 rhodecode/model/db.py:2295
2049 2248 msgid "User group no access"
2050 2249 msgstr ""
2051 2250
@@ -2067,7 +2266,8 b' msgstr ""'
2067 2266 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:829
2068 2267 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:835
2069 2268 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:957
2070 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:982 rhodecode/model/db.py:2302
2269 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:982
2270 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2304 rhodecode/model/db.py:2296
2071 2271 msgid "User group read access"
2072 2272 msgstr ""
2073 2273
@@ -2089,7 +2289,8 b' msgstr ""'
2089 2289 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:830
2090 2290 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:836
2091 2291 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:958
2092 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:983 rhodecode/model/db.py:2303
2292 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:983
2293 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2305 rhodecode/model/db.py:2297
2093 2294 msgid "User group write access"
2094 2295 msgstr ""
2095 2296
@@ -2111,7 +2312,8 b' msgstr ""'
2111 2312 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:831
2112 2313 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:837
2113 2314 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:959
2114 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:984 rhodecode/model/db.py:2304
2315 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:984
2316 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2306 rhodecode/model/db.py:2298
2115 2317 msgid "User group admin access"
2116 2318 msgstr ""
2117 2319
@@ -2133,7 +2335,8 b' msgstr ""'
2133 2335 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:833
2134 2336 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:839
2135 2337 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:961
2136 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:986 rhodecode/model/db.py:2306
2338 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:986
2339 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2308 rhodecode/model/db.py:2300
2137 2340 msgid "Repository Group creation disabled"
2138 2341 msgstr ""
2139 2342
@@ -2155,7 +2358,8 b' msgstr ""'
2155 2358 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:834
2156 2359 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:840
2157 2360 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:962
2158 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:987 rhodecode/model/db.py:2307
2361 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:987
2362 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2309 rhodecode/model/db.py:2301
2159 2363 msgid "Repository Group creation enabled"
2160 2364 msgstr ""
2161 2365
@@ -2177,7 +2381,8 b' msgstr ""'
2177 2381 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:836
2178 2382 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:842
2179 2383 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:964
2180 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:989 rhodecode/model/db.py:2309
2384 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:989
2385 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2311 rhodecode/model/db.py:2303
2181 2386 msgid "User Group creation disabled"
2182 2387 msgstr ""
2183 2388
@@ -2199,7 +2404,8 b' msgstr ""'
2199 2404 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:837
2200 2405 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:843
2201 2406 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:965
2202 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:990 rhodecode/model/db.py:2310
2407 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:990
2408 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2312 rhodecode/model/db.py:2304
2203 2409 msgid "User Group creation enabled"
2204 2410 msgstr ""
2205 2411
@@ -2221,7 +2427,8 b' msgstr ""'
2221 2427 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:847
2222 2428 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:853
2223 2429 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:975
2224 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1000 rhodecode/model/db.py:2320
2430 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1000
2431 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2322 rhodecode/model/db.py:2314
2225 2432 msgid "Registration disabled"
2226 2433 msgstr ""
2227 2434
@@ -2243,7 +2450,8 b' msgstr ""'
2243 2450 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:848
2244 2451 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:854
2245 2452 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:976
2246 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1001 rhodecode/model/db.py:2321
2453 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1001
2454 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2323 rhodecode/model/db.py:2315
2247 2455 msgid "User Registration with manual account activation"
2248 2456 msgstr ""
2249 2457
@@ -2265,7 +2473,8 b' msgstr ""'
2265 2473 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:849
2266 2474 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:855
2267 2475 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:977
2268 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1002 rhodecode/model/db.py:2322
2476 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1002
2477 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2324 rhodecode/model/db.py:2316
2269 2478 msgid "User Registration with automatic account activation"
2270 2479 msgstr ""
2271 2480
@@ -2287,7 +2496,8 b' msgstr ""'
2287 2496 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:851
2288 2497 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:857
2289 2498 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:979
2290 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1004 rhodecode/model/db.py:2324
2499 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1004
2500 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2326 rhodecode/model/db.py:2318
2291 2501 msgid "Manual activation of external account"
2292 2502 msgstr ""
2293 2503
@@ -2309,7 +2519,8 b' msgstr ""'
2309 2519 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:852
2310 2520 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:858
2311 2521 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:980
2312 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1005 rhodecode/model/db.py:2325
2522 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1005
2523 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2327 rhodecode/model/db.py:2319
2313 2524 msgid "Automatic activation of external account"
2314 2525 msgstr ""
2315 2526
@@ -2325,7 +2536,8 b' msgstr ""'
2325 2536 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:841
2326 2537 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:847
2327 2538 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:969
2328 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:994 rhodecode/model/db.py:2314
2539 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:994
2540 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2316 rhodecode/model/db.py:2308
2329 2541 msgid "Repository creation enabled with write permission to a repository group"
2330 2542 msgstr ""
2331 2543
@@ -2341,7 +2553,8 b' msgstr ""'
2341 2553 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:842
2342 2554 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:848
2343 2555 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:970
2344 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:995 rhodecode/model/db.py:2315
2556 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:995
2557 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2317 rhodecode/model/db.py:2309
2345 2558 msgid "Repository creation disabled with write permission to a repository group"
2346 2559 msgstr ""
2347 2560
@@ -2354,7 +2567,8 b' msgstr ""'
2354 2567 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:816
2355 2568 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:822
2356 2569 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:944
2357 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:969 rhodecode/model/db.py:2289
2570 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:969
2571 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2291 rhodecode/model/db.py:2283
2358 2572 msgid "RhodeCode Super Administrator"
2359 2573 msgstr ""
2360 2574
@@ -2365,7 +2579,8 b' msgstr ""'
2365 2579 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:854
2366 2580 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:860
2367 2581 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:982
2368 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1007 rhodecode/model/db.py:2327
2582 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1007
2583 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2329 rhodecode/model/db.py:2321
2369 2584 msgid "Inherit object permissions from default user disabled"
2370 2585 msgstr ""
2371 2586
@@ -2376,10 +2591,35 b' msgstr ""'
2376 2591 #: rhodecode/lib/dbmigrate/schema/db_3_2_0_0.py:855
2377 2592 #: rhodecode/lib/dbmigrate/schema/db_3_3_0_0.py:861
2378 2593 #: rhodecode/lib/dbmigrate/schema/db_3_5_0_0.py:983
2379 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1008 rhodecode/model/db.py:2328
2594 #: rhodecode/lib/dbmigrate/schema/db_3_7_0_0.py:1008
2595 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2330 rhodecode/model/db.py:2322
2380 2596 msgid "Inherit object permissions from default user enabled"
2381 2597 msgstr ""
2382 2598
2599 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:909 rhodecode/model/db.py:910
2600 msgid "all"
2601 msgstr ""
2602
2603 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:910 rhodecode/model/db.py:911
2604 msgid "http/web interface"
2605 msgstr ""
2606
2607 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:911 rhodecode/model/db.py:912
2608 msgid "vcs (git/hg/svn protocol)"
2609 msgstr ""
2610
2611 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:912 rhodecode/model/db.py:913
2612 msgid "api calls"
2613 msgstr ""
2614
2615 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:913 rhodecode/model/db.py:914
2616 msgid "feed access"
2617 msgstr ""
2618
2619 #: rhodecode/lib/dbmigrate/schema/db_4_3_0_0.py:2069 rhodecode/model/db.py:2061
2620 msgid "No parent"
2621 msgstr ""
2622
2383 2623 #: rhodecode/lib/index/whoosh.py:148
2384 2624 msgid "Invalid search query. Try quoting it."
2385 2625 msgstr ""
@@ -2424,48 +2664,32 b' msgstr ""'
2424 2664 msgid "Your password reset link was sent"
2425 2665 msgstr ""
2426 2666
2427 #: rhodecode/login/views.py:333
2667 #: rhodecode/login/views.py:336
2428 2668 msgid "Your password reset was successful, a new password has been sent to your email"
2429 2669 msgstr ""
2430 2670
2431 #: rhodecode/model/db.py:909
2432 msgid "all"
2433 msgstr ""
2434
2435 #: rhodecode/model/db.py:910
2436 msgid "http/web interface"
2437 msgstr ""
2438
2439 #: rhodecode/model/db.py:911
2440 msgid "vcs (git/hg protocol)"
2441 msgstr ""
2442
2443 #: rhodecode/model/db.py:912
2444 msgid "api calls"
2445 msgstr ""
2446
2447 #: rhodecode/model/db.py:913
2448 msgid "feed access"
2449 msgstr ""
2450
2451 #: rhodecode/model/db.py:2067
2452 msgid "No parent"
2453 msgstr ""
2454
2455 #: rhodecode/model/forms.py:66
2671 #: rhodecode/model/comment.py:263
2672 msgid "made a comment"
2673 msgstr ""
2674
2675 #: rhodecode/model/comment.py:264
2676 msgid "Refresh page"
2677 msgstr ""
2678
2679 #: rhodecode/model/forms.py:85
2456 2680 msgid "Please enter a login"
2457 2681 msgstr ""
2458 2682
2459 #: rhodecode/model/forms.py:67
2683 #: rhodecode/model/forms.py:86
2460 2684 #, python-format
2461 2685 msgid "Enter a value %(min)i characters long or more"
2462 2686 msgstr ""
2463 2687
2464 #: rhodecode/model/forms.py:76
2688 #: rhodecode/model/forms.py:95
2465 2689 msgid "Please enter a password"
2466 2690 msgstr ""
2467 2691
2468 #: rhodecode/model/forms.py:77
2692 #: rhodecode/model/forms.py:96
2469 2693 #, python-format
2470 2694 msgid "Enter %(min)i characters or more"
2471 2695 msgstr ""
@@ -2574,43 +2798,43 b' msgid ""'
2574 2798 " %(pr_title)s"
2575 2799 msgstr ""
2576 2800
2577 #: rhodecode/model/pull_request.py:448
2801 #: rhodecode/model/pull_request.py:449
2578 2802 msgid "Pull request merged and closed"
2579 2803 msgstr ""
2580 2804
2581 #: rhodecode/model/pull_request.py:867
2805 #: rhodecode/model/pull_request.py:874
2582 2806 msgid "Server-side pull request merging is disabled."
2583 2807 msgstr ""
2584 2808
2585 #: rhodecode/model/pull_request.py:869
2809 #: rhodecode/model/pull_request.py:876
2586 2810 msgid "This pull request is closed."
2587 2811 msgstr ""
2588 2812
2589 #: rhodecode/model/pull_request.py:880
2813 #: rhodecode/model/pull_request.py:887
2590 2814 msgid "Pull request merging is not supported."
2591 2815 msgstr ""
2592 2816
2593 #: rhodecode/model/pull_request.py:898
2817 #: rhodecode/model/pull_request.py:905
2594 2818 msgid "Target repository large files support is disabled."
2595 2819 msgstr ""
2596 2820
2597 #: rhodecode/model/pull_request.py:901
2821 #: rhodecode/model/pull_request.py:908
2598 2822 msgid "Source repository large files support is disabled."
2599 2823 msgstr ""
2600 2824
2601 #: rhodecode/model/pull_request.py:1050 rhodecode/model/scm.py:791
2825 #: rhodecode/model/pull_request.py:1058 rhodecode/model/scm.py:788
2602 2826 msgid "Bookmarks"
2603 2827 msgstr ""
2604 2828
2605 #: rhodecode/model/pull_request.py:1055
2829 #: rhodecode/model/pull_request.py:1063
2606 2830 msgid "Commit IDs"
2607 2831 msgstr ""
2608 2832
2609 #: rhodecode/model/pull_request.py:1058
2833 #: rhodecode/model/pull_request.py:1066
2610 2834 msgid "Closed Branches"
2611 2835 msgstr ""
2612 2836
2613 #: rhodecode/model/scm.py:773
2837 #: rhodecode/model/scm.py:770
2614 2838 msgid "latest tip"
2615 2839 msgstr ""
2616 2840
@@ -2811,6 +3035,7 b' msgid "Revisions %(revs)s are already pa'
2811 3035 msgstr ""
2812 3036
2813 3037 #: rhodecode/model/validators.py:933
3038 #: rhodecode/model/validation_schema/validators.py:14
2814 3039 msgid "Please enter a valid IPv4 or IpV6 address"
2815 3040 msgstr ""
2816 3041
@@ -2824,31 +3049,389 b' msgid "Key name can only consist of lett'
2824 3049 msgstr ""
2825 3050
2826 3051 #: rhodecode/model/validators.py:976
2827 msgid "Filename cannot be inside a directory"
2828 msgstr ""
2829
2830 #: rhodecode/model/validators.py:992
2831 3052 #, python-format
2832 3053 msgid "Plugins %(loaded)s and %(next_to_load)s both export the same name"
2833 3054 msgstr ""
2834 3055
2835 #: rhodecode/model/validators.py:995
3056 #: rhodecode/model/validators.py:979
2836 3057 #, python-format
2837 3058 msgid "The plugin \"%(plugin_id)s\" is missing an includeme function."
2838 3059 msgstr ""
2839 3060
2840 #: rhodecode/model/validators.py:998
3061 #: rhodecode/model/validators.py:982
2841 3062 #, python-format
2842 3063 msgid "Can not load plugin \"%(plugin_id)s\""
2843 3064 msgstr ""
2844 3065
2845 #: rhodecode/model/validators.py:1000
3066 #: rhodecode/model/validators.py:984
2846 3067 #, python-format
2847 3068 msgid "No plugin available with ID \"%(plugin_id)s\""
2848 3069 msgstr ""
2849 3070
2850 #: rhodecode/model/validators.py:1067
2851 msgid "This gistid is already in use"
3071 #: rhodecode/model/validation_schema/schemas/gist_schema.py:89
3072 msgid "Gist with name {} already exists"
3073 msgstr ""
3074
3075 #: rhodecode/model/validation_schema/schemas/gist_schema.py:95
3076 msgid "Filename {} cannot be inside a directory"
3077 msgstr ""
3078
3079 #: rhodecode/model/validation_schema/schemas/gist_schema.py:132
3080 msgid "Duplicated value for filename found: `{}`"
3081 msgstr ""
3082
3083 #: rhodecode/public/js/scripts.js:23039
3084 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:16
3085 #: rhodecode/public/js/src/plugins/jquery.autocomplete.js:87
3086 msgid "No results"
3087 msgstr ""
3088
3089 #: rhodecode/public/js/scripts.js:24970
3090 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:66
3091 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:109
3092 msgid "{0} year"
3093 msgstr ""
3094
3095 #: rhodecode/public/js/scripts.js:24971
3096 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:62
3097 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:110
3098 msgid "{0} month"
3099 msgstr ""
3100
3101 #: rhodecode/public/js/scripts.js:24972
3102 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:57
3103 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:111
3104 msgid "{0} day"
3105 msgstr ""
3106
3107 #: rhodecode/public/js/scripts.js:24973
3108 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:59
3109 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:112
3110 msgid "{0} hour"
3111 msgstr ""
3112
3113 #: rhodecode/public/js/scripts.js:24974
3114 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:61
3115 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:113
3116 msgid "{0} min"
3117 msgstr ""
3118
3119 #: rhodecode/public/js/scripts.js:24975
3120 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:65
3121 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:114
3122 msgid "{0} sec"
3123 msgstr ""
3124
3125 #: rhodecode/public/js/scripts.js:24995
3126 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:46
3127 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:134
3128 msgid "in {0}"
3129 msgstr ""
3130
3131 #: rhodecode/public/js/scripts.js:25003
3132 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:54
3133 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:142
3134 msgid "{0} ago"
3135 msgstr ""
3136
3137 #: rhodecode/public/js/scripts.js:25015
3138 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:68
3139 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:154
3140 msgid "{0}, {1} ago"
3141 msgstr ""
3142
3143 #: rhodecode/public/js/scripts.js:25017
3144 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:48
3145 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:156
3146 msgid "in {0}, {1}"
3147 msgstr ""
3148
3149 #: rhodecode/public/js/scripts.js:25021
3150 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:55
3151 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:160
3152 msgid "{0} and {1}"
3153 msgstr ""
3154
3155 #: rhodecode/public/js/scripts.js:25023
3156 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:56
3157 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:162
3158 msgid "{0} and {1} ago"
3159 msgstr ""
3160
3161 #: rhodecode/public/js/scripts.js:25025
3162 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:47
3163 #: rhodecode/public/js/src/plugins/jquery.timeago-extension.js:164
3164 msgid "in {0} and {1}"
3165 msgstr ""
3166
3167 #: rhodecode/public/js/scripts.js:39304
3168 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:7
3169 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:4
3170 msgid "Loading more results..."
3171 msgstr ""
3172
3173 #: rhodecode/public/js/scripts.js:39307
3174 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:27
3175 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:7
3176 msgid "Searching..."
3177 msgstr ""
3178
3179 #: rhodecode/public/js/scripts.js:39310
3180 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:11
3181 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:10
3182 msgid "No matches found"
3183 msgstr ""
3184
3185 #: rhodecode/public/js/scripts.js:39313
3186 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:6
3187 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:13
3188 msgid "Loading failed"
3189 msgstr ""
3190
3191 #: rhodecode/public/js/scripts.js:39317
3192 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:20
3193 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:17
3194 msgid "One result is available, press enter to select it."
3195 msgstr ""
3196
3197 #: rhodecode/public/js/scripts.js:39319
3198 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:64
3199 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:19
3200 msgid "{0} results are available, use up and down arrow keys to navigate."
3201 msgstr ""
3202
3203 #: rhodecode/public/js/scripts.js:39324
3204 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:25
3205 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:24
3206 msgid "Please enter {0} or more character"
3207 msgstr ""
3208
3209 #: rhodecode/public/js/scripts.js:39326
3210 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:26
3211 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:26
3212 msgid "Please enter {0} or more characters"
3213 msgstr ""
3214
3215 #: rhodecode/public/js/scripts.js:39331
3216 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:23
3217 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:31
3218 msgid "Please delete {0} character"
3219 msgstr ""
3220
3221 #: rhodecode/public/js/scripts.js:39333
3222 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:24
3223 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:33
3224 msgid "Please delete {0} characters"
3225 msgstr ""
3226
3227 #: rhodecode/public/js/scripts.js:39337
3228 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:40
3229 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:37
3230 msgid "You can only select {0} item"
3231 msgstr ""
3232
3233 #: rhodecode/public/js/scripts.js:39339
3234 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:41
3235 #: rhodecode/public/js/rhodecode/i18n/select2/translations.js:39
3236 msgid "You can only select {0} items"
3237 msgstr ""
3238
3239 #: rhodecode/public/js/scripts.js:40911
3240 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:29
3241 #: rhodecode/public/js/src/rhodecode/codemirror.js:369
3242 msgid "Set status to Approved"
3243 msgstr ""
3244
3245 #: rhodecode/public/js/scripts.js:40929
3246 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:30
3247 #: rhodecode/public/js/src/rhodecode/codemirror.js:387
3248 msgid "Set status to Rejected"
3249 msgstr ""
3250
3251 #: rhodecode/public/js/scripts.js:41308
3252 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:2
3253 #: rhodecode/public/js/src/rhodecode/comments.js:235
3254 msgid "Add another comment"
3255 msgstr ""
3256
3257 #: rhodecode/public/js/scripts.js:41526
3258 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:35
3259 #: rhodecode/public/js/src/rhodecode/comments.js:453
3260 msgid "Status Review"
3261 msgstr ""
3262
3263 #: rhodecode/public/js/scripts.js:41540
3264 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:3
3265 #: rhodecode/public/js/src/rhodecode/comments.js:467
3266 msgid "Comment text will be set automatically based on currently selected status ({0}) ..."
3267 msgstr ""
3268
3269 #: rhodecode/public/js/scripts.js:41653
3270 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:37
3271 #: rhodecode/public/js/src/rhodecode/comments.js:580
3272 msgid "Submitting..."
3273 msgstr ""
3274
3275 #: rhodecode/public/js/scripts.js:41703
3276 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:5
3277 #: rhodecode/public/js/src/rhodecode/comments.js:630
3278 #: rhodecode/templates/files/files_browser_tree.html:47
3279 msgid "Loading ..."
3280 msgstr ""
3281
3282 #: rhodecode/public/js/scripts.js:41903
3283 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:51
3284 #: rhodecode/public/js/src/rhodecode/files.js:150
3285 msgid "truncated result"
3286 msgstr ""
3287
3288 #: rhodecode/public/js/scripts.js:41905
3289 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:52
3290 #: rhodecode/public/js/src/rhodecode/files.js:152
3291 msgid "truncated results"
3292 msgstr ""
3293
3294 #: rhodecode/public/js/scripts.js:41914
3295 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:12
3296 #: rhodecode/public/js/src/rhodecode/files.js:161
3297 msgid "No matching files"
3298 msgstr ""
3299
3300 #: rhodecode/public/js/scripts.js:42049
3301 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:28
3302 #: rhodecode/public/js/src/rhodecode/files.js:296
3303 msgid "Selection link"
3304 msgstr ""
3305
3306 #: rhodecode/public/js/scripts.js:42089
3307 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:36
3308 #: rhodecode/public/js/src/rhodecode/followers.js:26
3309 msgid "Stop following this repository"
3310 msgstr ""
3311
3312 #: rhodecode/public/js/scripts.js:42090
3313 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:38
3314 #: rhodecode/public/js/src/rhodecode/followers.js:27
3315 msgid "Unfollow"
3316 msgstr ""
3317
3318 #: rhodecode/public/js/scripts.js:42099
3319 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:34
3320 #: rhodecode/public/js/src/rhodecode/followers.js:36
3321 msgid "Start following this repository"
3322 msgstr ""
3323
3324 #: rhodecode/public/js/scripts.js:42100
3325 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:4
3326 #: rhodecode/public/js/src/rhodecode/followers.js:37
3327 msgid "Follow"
3328 msgstr ""
3329
3330 #: rhodecode/public/js/scripts.js:43049
3331 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:44
3332 #: rhodecode/public/js/src/rhodecode.js:142
3333 msgid "file"
3334 msgstr ""
3335
3336 #: rhodecode/public/js/scripts.js:43069
3337 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:31
3338 #: rhodecode/public/js/src/rhodecode.js:162
3339 msgid "Show more"
3340 msgstr ""
3341
3342 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:8
3343 msgid "No bookmarks available yet."
3344 msgstr ""
3345
3346 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:9
3347 msgid "No branches available yet."
3348 msgstr ""
3349
3350 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:10
3351 msgid "No gists available yet."
3352 msgstr ""
3353
3354 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:13
3355 msgid "No pull requests available yet."
3356 msgstr ""
3357
3358 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:14
3359 msgid "No repositories available yet."
3360 msgstr ""
3361
3362 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:15
3363 msgid "No repository groups available yet."
3364 msgstr ""
3365
3366 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:17
3367 msgid "No tags available yet."
3368 msgstr ""
3369
3370 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:18
3371 msgid "No user groups available yet."
3372 msgstr ""
3373
3374 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:19
3375 msgid "No users available yet."
3376 msgstr ""
3377
3378 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:21
3379 #: rhodecode/templates/changelog/changelog.html:62
3380 msgid "Open new pull request"
3381 msgstr ""
3382
3383 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:22
3384 msgid "Open new pull request for selected commit"
3385 msgstr ""
3386
3387 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:32
3388 msgid "Show selected commit __S"
3389 msgstr ""
3390
3391 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:33
3392 msgid "Show selected commits __S ... __E"
3393 msgstr ""
3394
3395 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:39
3396 msgid "Updating..."
3397 msgstr ""
3398
3399 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:42
3400 #: rhodecode/templates/admin/auth/auth_settings.html:71
3401 msgid "disabled"
3402 msgstr ""
3403
3404 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:43
3405 #: rhodecode/templates/admin/auth/auth_settings.html:71
3406 msgid "enabled"
3407 msgstr ""
3408
3409 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:45
3410 msgid "files"
3411 msgstr ""
3412
3413 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:50
3414 msgid "specify commit"
3415 msgstr ""
3416
3417 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:53
3418 msgid "{0} active out of {1} users"
3419 msgstr ""
3420
3421 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:58
3422 msgid "{0} days"
3423 msgstr ""
3424
3425 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:60
3426 msgid "{0} hours"
3427 msgstr ""
3428
3429 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:63
3430 msgid "{0} months"
3431 msgstr ""
3432
3433 #: rhodecode/public/js/rhodecode/i18n/js_translations.js:67
3434 msgid "{0} years"
2852 3435 msgstr ""
2853 3436
2854 3437 #: rhodecode/templates/index.html:5
@@ -2909,7 +3492,7 b' msgstr ""'
2909 3492 #: rhodecode/templates/base/perms_summary.html:102
2910 3493 #: rhodecode/templates/bookmarks/bookmarks.html:59
2911 3494 #: rhodecode/templates/branches/branches.html:58
2912 #: rhodecode/templates/files/files_browser.html:49
3495 #: rhodecode/templates/files/files_browser_tree.html:5
2913 3496 #: rhodecode/templates/pullrequests/pullrequests.html:100
2914 3497 #: rhodecode/templates/tags/tags.html:59
2915 3498 msgid "Name"
@@ -2918,6 +3501,7 b' msgstr ""'
2918 3501 #: rhodecode/templates/index_base.html:100
2919 3502 #: rhodecode/templates/index_base.html:125
2920 3503 #: rhodecode/templates/admin/gists/index.html:114
3504 #: rhodecode/templates/admin/integrations/list.html:63
2921 3505 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:77
2922 3506 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:45
2923 3507 #: rhodecode/templates/admin/repo_groups/repo_group_edit_settings.html:42
@@ -2933,8 +3517,9 b' msgstr ""'
2933 3517 #: rhodecode/templates/base/issue_tracker_settings.html:10
2934 3518 #: rhodecode/templates/changeset/changeset.html:53
2935 3519 #: rhodecode/templates/compare/compare_commits.html:24
3520 #: rhodecode/templates/email_templates/commit_comment.mako:82
2936 3521 #: rhodecode/templates/email_templates/pull_request_review.mako:30
2937 #: rhodecode/templates/email_templates/pull_request_review.mako:67
3522 #: rhodecode/templates/email_templates/pull_request_review.mako:51
2938 3523 #: rhodecode/templates/files/file_tree_detail.html:5
2939 3524 #: rhodecode/templates/files/file_tree_detail.html:12
2940 3525 #: rhodecode/templates/forks/fork.html:48
@@ -2972,12 +3557,12 b' msgstr ""'
2972 3557 #: rhodecode/templates/admin/repos/repos.html:63
2973 3558 #: rhodecode/templates/bookmarks/bookmarks.html:66
2974 3559 #: rhodecode/templates/branches/branches.html:65
2975 #: rhodecode/templates/changelog/changelog.html:106
2976 #: rhodecode/templates/changelog/changelog_summary_data.html:6
3560 #: rhodecode/templates/changelog/changelog.html:104
3561 #: rhodecode/templates/changelog/changelog_summary_data.html:8
2977 3562 #: rhodecode/templates/changeset/changeset.html:36
2978 3563 #: rhodecode/templates/compare/compare_commits.html:22
2979 #: rhodecode/templates/email_templates/commit_comment.mako:16
2980 3564 #: rhodecode/templates/email_templates/commit_comment.mako:45
3565 #: rhodecode/templates/email_templates/commit_comment.mako:81
2981 3566 #: rhodecode/templates/search/search_commit.html:6
2982 3567 #: rhodecode/templates/tags/tags.html:66
2983 3568 msgid "Commit"
@@ -2993,7 +3578,7 b' msgid "Home"'
2993 3578 msgstr ""
2994 3579
2995 3580 #: rhodecode/templates/login.html:5 rhodecode/templates/login.html:35
2996 #: rhodecode/templates/login.html:64 rhodecode/templates/base/base.html:328
3581 #: rhodecode/templates/login.html:64 rhodecode/templates/base/base.html:329
2997 3582 #: rhodecode/templates/debug_style/login.html:60
2998 3583 msgid "Sign In"
2999 3584 msgstr ""
@@ -3002,20 +3587,6 b' msgstr ""'
3002 3587 msgid "Go to the registration page to create a new account."
3003 3588 msgstr ""
3004 3589
3005 #: rhodecode/templates/login.html:43 rhodecode/templates/register.html:41
3006 #: rhodecode/templates/admin/admin_log.html:5
3007 #: rhodecode/templates/admin/my_account/my_account_profile.html:24
3008 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:21
3009 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:66
3010 #: rhodecode/templates/admin/users/user_add.html:35
3011 #: rhodecode/templates/admin/users/user_edit_profile.html:39
3012 #: rhodecode/templates/admin/users/users.html:89
3013 #: rhodecode/templates/base/base.html:305
3014 #: rhodecode/templates/debug_style/login.html:36
3015 #: rhodecode/templates/users/user_profile.html:27
3016 msgid "Username"
3017 msgstr ""
3018
3019 3590 #: rhodecode/templates/login.html:58
3020 3591 msgid "Remember me"
3021 3592 msgstr ""
@@ -3051,7 +3622,7 b' msgid "Send password reset email"'
3051 3622 msgstr ""
3052 3623
3053 3624 #: rhodecode/templates/password_reset.html:60
3054 msgid "Password reset link will be send to matching email address"
3625 msgid "Password reset link will be sent to matching email address"
3055 3626 msgstr ""
3056 3627
3057 3628 #: rhodecode/templates/register.html:35
@@ -3072,7 +3643,7 b' msgstr ""'
3072 3643 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:76
3073 3644 #: rhodecode/templates/admin/users/user_add.html:68
3074 3645 #: rhodecode/templates/admin/users/user_edit_profile.html:47
3075 #: rhodecode/templates/admin/users/users.html:93
3646 #: rhodecode/templates/admin/users/users.html:92
3076 3647 msgid "First Name"
3077 3648 msgstr ""
3078 3649
@@ -3082,7 +3653,7 b' msgstr ""'
3082 3653 #: rhodecode/templates/admin/my_account/my_account_profile_edit.html:85
3083 3654 #: rhodecode/templates/admin/users/user_add.html:77
3084 3655 #: rhodecode/templates/admin/users/user_edit_profile.html:56
3085 #: rhodecode/templates/admin/users/users.html:95
3656 #: rhodecode/templates/admin/users/users.html:94
3086 3657 msgid "Last Name"
3087 3658 msgstr ""
3088 3659
@@ -3096,7 +3667,7 b' msgstr ""'
3096 3667
3097 3668 #: rhodecode/templates/admin/admin.html:5
3098 3669 #: rhodecode/templates/admin/admin.html:15
3099 #: rhodecode/templates/base/base.html:78
3670 #: rhodecode/templates/base/base.html:77
3100 3671 msgid "Admin journal"
3101 3672 msgstr ""
3102 3673
@@ -3121,17 +3692,17 b' msgstr[1] ""'
3121 3692 msgid "Example Queries"
3122 3693 msgstr ""
3123 3694
3124 #: rhodecode/templates/admin/admin_log.html:6
3695 #: rhodecode/templates/admin/admin_log.html:8
3125 3696 #: rhodecode/templates/admin/my_account/my_account_repos.html:37
3126 3697 #: rhodecode/templates/admin/repo_groups/repo_groups.html:62
3127 3698 #: rhodecode/templates/admin/repos/repo_edit_fields.html:13
3128 3699 #: rhodecode/templates/admin/repos/repos.html:69
3129 3700 #: rhodecode/templates/admin/user_groups/user_groups.html:66
3130 #: rhodecode/templates/admin/users/users.html:106
3701 #: rhodecode/templates/admin/users/users.html:105
3131 3702 msgid "Action"
3132 3703 msgstr ""
3133 3704
3134 #: rhodecode/templates/admin/admin_log.html:7
3705 #: rhodecode/templates/admin/admin_log.html:9
3135 3706 #: rhodecode/templates/admin/defaults/defaults.html:31
3136 3707 #: rhodecode/templates/admin/permissions/permissions_objects.html:13
3137 3708 #: rhodecode/templates/search/search_commit.html:5
@@ -3139,18 +3710,18 b' msgstr ""'
3139 3710 msgid "Repository"
3140 3711 msgstr ""
3141 3712
3142 #: rhodecode/templates/admin/admin_log.html:8
3713 #: rhodecode/templates/admin/admin_log.html:10
3143 3714 #: rhodecode/templates/bookmarks/bookmarks.html:61
3144 3715 #: rhodecode/templates/branches/branches.html:60
3145 3716 #: rhodecode/templates/tags/tags.html:61
3146 3717 msgid "Date"
3147 3718 msgstr ""
3148 3719
3149 #: rhodecode/templates/admin/admin_log.html:9
3720 #: rhodecode/templates/admin/admin_log.html:11
3150 3721 msgid "From IP"
3151 3722 msgstr ""
3152 3723
3153 #: rhodecode/templates/admin/admin_log.html:44
3724 #: rhodecode/templates/admin/admin_log.html:46
3154 3725 msgid "No actions yet"
3155 3726 msgstr ""
3156 3727
@@ -3162,6 +3733,9 b' msgstr ""'
3162 3733 #: rhodecode/templates/admin/auth/auth_settings.html:12
3163 3734 #: rhodecode/templates/admin/auth/plugin_settings.html:12
3164 3735 #: rhodecode/templates/admin/defaults/defaults.html:12
3736 #: rhodecode/templates/admin/integrations/base.html:19
3737 #: rhodecode/templates/admin/integrations/edit.html:15
3738 #: rhodecode/templates/admin/integrations/list.html:8
3165 3739 #: rhodecode/templates/admin/permissions/permissions.html:12
3166 3740 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:12
3167 3741 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:12
@@ -3179,9 +3753,9 b' msgstr ""'
3179 3753 #: rhodecode/templates/admin/users/user_add.html:11
3180 3754 #: rhodecode/templates/admin/users/user_edit.html:12
3181 3755 #: rhodecode/templates/admin/users/users.html:13
3182 #: rhodecode/templates/admin/users/users.html:102
3183 #: rhodecode/templates/base/base.html:405
3184 #: rhodecode/templates/base/base.html:412
3756 #: rhodecode/templates/admin/users/users.html:101
3757 #: rhodecode/templates/base/base.html:406
3758 #: rhodecode/templates/base/base.html:413
3185 3759 msgid "Admin"
3186 3760 msgstr ""
3187 3761
@@ -3206,14 +3780,6 b' msgstr ""'
3206 3780 msgid "Available Built-in Plugins"
3207 3781 msgstr ""
3208 3782
3209 #: rhodecode/templates/admin/auth/auth_settings.html:71
3210 msgid "enabled"
3211 msgstr ""
3212
3213 #: rhodecode/templates/admin/auth/auth_settings.html:71
3214 msgid "disabled"
3215 msgstr ""
3216
3217 3783 #: rhodecode/templates/admin/auth/auth_settings.html:81
3218 3784 #: rhodecode/templates/admin/auth/plugin_settings.html:87
3219 3785 #: rhodecode/templates/admin/defaults/defaults_repositories.html:63
@@ -3254,6 +3820,7 b' msgstr ""'
3254 3820
3255 3821 #: rhodecode/templates/admin/defaults/defaults_repositories.html:14
3256 3822 #: rhodecode/templates/admin/gists/index.html:110
3823 #: rhodecode/templates/admin/integrations/list.html:64
3257 3824 #: rhodecode/templates/admin/repos/repo_add_base.html:62
3258 3825 #: rhodecode/templates/admin/repos/repo_edit_fields.html:12
3259 3826 msgid "Type"
@@ -3320,18 +3887,18 b' msgstr ""'
3320 3887 msgid "Gist access level"
3321 3888 msgstr ""
3322 3889
3323 #: rhodecode/templates/admin/gists/edit.html:59
3890 #: rhodecode/templates/admin/gists/edit.html:62
3324 3891 #: rhodecode/templates/admin/gists/new.html:50
3325 3892 #: rhodecode/templates/files/files_add.html:74
3326 3893 #: rhodecode/templates/files/files_edit.html:78
3327 3894 msgid "plain"
3328 3895 msgstr ""
3329 3896
3330 #: rhodecode/templates/admin/gists/edit.html:103
3897 #: rhodecode/templates/admin/gists/edit.html:107
3331 3898 msgid "Update Gist"
3332 3899 msgstr ""
3333 3900
3334 #: rhodecode/templates/admin/gists/edit.html:104
3901 #: rhodecode/templates/admin/gists/edit.html:108
3335 3902 #: rhodecode/templates/base/issue_tracker_settings.html:74
3336 3903 #: rhodecode/templates/changeset/changeset_file_comment.html:139
3337 3904 #: rhodecode/templates/files/files_add.html:102
@@ -3392,14 +3959,14 b' msgstr ""'
3392 3959
3393 3960 #: rhodecode/templates/admin/gists/index.html:108
3394 3961 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:24
3395 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:87
3962 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:89
3396 3963 #: rhodecode/templates/bookmarks/bookmarks.html:63
3397 3964 #: rhodecode/templates/branches/branches.html:62
3398 #: rhodecode/templates/changelog/changelog.html:102
3399 #: rhodecode/templates/changelog/changelog_summary_data.html:10
3965 #: rhodecode/templates/changelog/changelog.html:110
3966 #: rhodecode/templates/changelog/changelog_summary_data.html:11
3400 3967 #: rhodecode/templates/changeset/changeset.html:164
3401 3968 #: rhodecode/templates/compare/compare_commits.html:21
3402 #: rhodecode/templates/files/files_browser.html:53
3969 #: rhodecode/templates/files/files_browser_tree.html:9
3403 3970 #: rhodecode/templates/pullrequests/pullrequest_show.html:169
3404 3971 #: rhodecode/templates/pullrequests/pullrequests.html:102
3405 3972 #: rhodecode/templates/search/search_commit.html:16
@@ -3482,9 +4049,10 b' msgid "Gist"'
3482 4049 msgstr ""
3483 4050
3484 4051 #: rhodecode/templates/admin/gists/show.html:49
4052 #: rhodecode/templates/admin/integrations/list.html:110
3485 4053 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:56
3486 4054 #: rhodecode/templates/admin/my_account/my_account_emails.html:32
3487 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:61
4055 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:63
3488 4056 #: rhodecode/templates/admin/permissions/permissions_ips.html:26
3489 4057 #: rhodecode/templates/admin/repos/repo_edit_fields.html:25
3490 4058 #: rhodecode/templates/admin/settings/settings_hooks.html:46
@@ -3496,10 +4064,10 b' msgstr ""'
3496 4064 #: rhodecode/templates/base/vcs_settings.html:172
3497 4065 #: rhodecode/templates/changeset/changeset_file_comment.html:49
3498 4066 #: rhodecode/templates/changeset/changeset_file_comment.html:99
3499 #: rhodecode/templates/data_table/_dt_elements.html:117
3500 #: rhodecode/templates/data_table/_dt_elements.html:174
3501 #: rhodecode/templates/data_table/_dt_elements.html:188
3502 #: rhodecode/templates/data_table/_dt_elements.html:200
4067 #: rhodecode/templates/data_table/_dt_elements.html:119
4068 #: rhodecode/templates/data_table/_dt_elements.html:176
4069 #: rhodecode/templates/data_table/_dt_elements.html:190
4070 #: rhodecode/templates/data_table/_dt_elements.html:202
3503 4071 #: rhodecode/templates/debug_style/buttons.html:132
3504 4072 #: rhodecode/templates/files/files_source.html:33
3505 4073 #: rhodecode/templates/files/files_source.html:37
@@ -3512,14 +4080,15 b' msgid "Confirm to delete this Gist"'
3512 4080 msgstr ""
3513 4081
3514 4082 #: rhodecode/templates/admin/gists/show.html:56
4083 #: rhodecode/templates/admin/integrations/list.html:103
3515 4084 #: rhodecode/templates/admin/my_account/my_account_profile.html:5
3516 4085 #: rhodecode/templates/base/issue_tracker_settings.html:61
3517 4086 #: rhodecode/templates/changeset/changeset_file_comment.html:145
3518 4087 #: rhodecode/templates/changeset/changeset_file_comment.html:292
3519 #: rhodecode/templates/data_table/_dt_elements.html:112
3520 #: rhodecode/templates/data_table/_dt_elements.html:170
3521 #: rhodecode/templates/data_table/_dt_elements.html:183
3522 #: rhodecode/templates/data_table/_dt_elements.html:196
4088 #: rhodecode/templates/data_table/_dt_elements.html:114
4089 #: rhodecode/templates/data_table/_dt_elements.html:172
4090 #: rhodecode/templates/data_table/_dt_elements.html:185
4091 #: rhodecode/templates/data_table/_dt_elements.html:198
3523 4092 #: rhodecode/templates/debug_style/buttons.html:128
3524 4093 #: rhodecode/templates/files/files_add.html:204
3525 4094 #: rhodecode/templates/files/files_edit.html:165
@@ -3549,8 +4118,44 b' msgstr ""'
3549 4118 msgid "Show as raw"
3550 4119 msgstr ""
3551 4120
4121 #: rhodecode/templates/admin/integrations/base.html:12
4122 msgid "Integrations settings"
4123 msgstr ""
4124
4125 #: rhodecode/templates/admin/integrations/edit.html:17
4126 #: rhodecode/templates/admin/integrations/list.html:10
4127 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:44
4128 #: rhodecode/templates/admin/repos/repo_edit.html:15
4129 #: rhodecode/templates/admin/repos/repo_edit.html:43
4130 #: rhodecode/templates/admin/settings/settings.html:14
4131 #: rhodecode/templates/admin/user_groups/user_group_edit.html:33
4132 #: rhodecode/templates/base/base.html:86 rhodecode/templates/base/base.html:251
4133 msgid "Settings"
4134 msgstr ""
4135
4136 #: rhodecode/templates/admin/integrations/edit.html:36
4137 #, python-format
4138 msgid "Create new %(integration_type)s integration"
4139 msgstr ""
4140
4141 #: rhodecode/templates/admin/integrations/list.html:31
4142 msgid "Create new integration"
4143 msgstr ""
4144
4145 #: rhodecode/templates/admin/integrations/list.html:56
4146 msgid "Current integrations"
4147 msgstr ""
4148
4149 #: rhodecode/templates/admin/integrations/list.html:65
4150 msgid "Actions"
4151 msgstr ""
4152
4153 #: rhodecode/templates/admin/integrations/list.html:89
4154 msgid "unknown integration"
4155 msgstr ""
4156
3552 4157 #: rhodecode/templates/admin/my_account/my_account.html:5
3553 #: rhodecode/templates/base/base.html:342
4158 #: rhodecode/templates/base/base.html:343
3554 4159 msgid "My account"
3555 4160 msgstr ""
3556 4161
@@ -3586,7 +4191,7 b' msgstr ""'
3586 4191
3587 4192 #: rhodecode/templates/admin/my_account/my_account.html:40
3588 4193 #: rhodecode/templates/admin/notifications/notifications.html:33
3589 #: rhodecode/templates/base/base.html:242
4194 #: rhodecode/templates/base/base.html:243
3590 4195 msgid "Pull Requests"
3591 4196 msgstr ""
3592 4197
@@ -3594,6 +4199,10 b' msgstr ""'
3594 4199 msgid "My Permissions"
3595 4200 msgstr ""
3596 4201
4202 #: rhodecode/templates/admin/my_account/my_account.html:42
4203 msgid "My Live Notifications"
4204 msgstr ""
4205
3597 4206 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:3
3598 4207 msgid "Authentication Tokens"
3599 4208 msgstr ""
@@ -3603,7 +4212,7 b' msgid "Built-in tokens can be used to au'
3603 4212 msgstr ""
3604 4213
3605 4214 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:8
3606 msgid "Each token can have a role. VCS tokens can be used together with the authtoken auth plugin for git/hg operations."
4215 msgid "Each token can have a role. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations."
3607 4216 msgstr ""
3608 4217
3609 4218 #: rhodecode/templates/admin/my_account/my_account_auth_tokens.html:14
@@ -3680,6 +4289,19 b' msgstr ""'
3680 4289 msgid "New email address"
3681 4290 msgstr ""
3682 4291
4292 #: rhodecode/templates/admin/my_account/my_account_notifications.html:3
4293 msgid "Your live notification settings"
4294 msgstr ""
4295
4296 #: rhodecode/templates/admin/my_account/my_account_notifications.html:14
4297 #: rhodecode/templates/admin/notifications/show_notification.html:12
4298 msgid "Notifications"
4299 msgstr ""
4300
4301 #: rhodecode/templates/admin/my_account/my_account_notifications.html:14
4302 msgid "Disabled"
4303 msgstr ""
4304
3683 4305 #: rhodecode/templates/admin/my_account/my_account_password.html:3
3684 4306 msgid "Change Your Account Password"
3685 4307 msgstr ""
@@ -3734,35 +4356,35 b' msgid "Pull Requests You Opened"'
3734 4356 msgstr ""
3735 4357
3736 4358 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:23
3737 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:86
4359 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:88
3738 4360 msgid "Target Repo"
3739 4361 msgstr ""
3740 4362
3741 4363 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:26
3742 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:89
4364 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:91
3743 4365 #: rhodecode/templates/admin/settings/settings_global.html:9
3744 4366 #: rhodecode/templates/email_templates/pull_request_review.mako:28
3745 #: rhodecode/templates/email_templates/pull_request_review.mako:65
4367 #: rhodecode/templates/email_templates/pull_request_review.mako:48
3746 4368 #: rhodecode/templates/pullrequests/pullrequest.html:38
3747 4369 #: rhodecode/templates/pullrequests/pullrequests.html:104
3748 4370 msgid "Title"
3749 4371 msgstr ""
3750 4372
3751 4373 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:27
3752 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:90
4374 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:92
3753 4375 msgid "Opened On"
3754 4376 msgstr ""
3755 4377
3756 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:41
3757 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:103
3758 #: rhodecode/templates/changelog/changelog.html:141
4378 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:43
4379 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:107
4380 #: rhodecode/templates/changelog/changelog.html:153
3759 4381 #: rhodecode/templates/compare/compare_commits.html:49
3760 4382 #: rhodecode/templates/search/search_commit.html:36
3761 4383 msgid "Expand commit message"
3762 4384 msgstr ""
3763 4385
3764 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:50
3765 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:112
4386 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:52
4387 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:116
3766 4388 #: rhodecode/templates/changeset/changeset_file_comment.html:284
3767 4389 #: rhodecode/templates/pullrequests/pullrequest_show.html:14
3768 4390 #: rhodecode/templates/pullrequests/pullrequest_show.html:112
@@ -3770,19 +4392,19 b' msgstr ""'
3770 4392 msgid "Closed"
3771 4393 msgstr ""
3772 4394
3773 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:62
4395 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:64
3774 4396 msgid "Confirm to delete this pull request"
3775 4397 msgstr ""
3776 4398
3777 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:69
4399 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:71
3778 4400 msgid "You currently have no open pull requests."
3779 4401 msgstr ""
3780 4402
3781 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:77
4403 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:79
3782 4404 msgid "Pull Requests You Participate In"
3783 4405 msgstr ""
3784 4406
3785 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:125
4407 #: rhodecode/templates/admin/my_account/my_account_pullrequests.html:129
3786 4408 msgid "There are currently no open pull requests requiring your participation."
3787 4409 msgstr ""
3788 4410
@@ -3822,19 +4444,15 b' msgstr ""'
3822 4444 msgid "Show notification"
3823 4445 msgstr ""
3824 4446
3825 #: rhodecode/templates/admin/notifications/show_notification.html:12
3826 msgid "Notifications"
3827 msgstr ""
3828
3829 4447 #: rhodecode/templates/admin/permissions/permissions.html:5
3830 4448 msgid "Permissions Administration"
3831 4449 msgstr ""
3832 4450
3833 4451 #: rhodecode/templates/admin/permissions/permissions.html:14
3834 4452 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:45
3835 #: rhodecode/templates/admin/repos/repo_edit.html:42
4453 #: rhodecode/templates/admin/repos/repo_edit.html:46
3836 4454 #: rhodecode/templates/admin/user_groups/user_group_edit.html:34
3837 #: rhodecode/templates/base/base.html:83
4455 #: rhodecode/templates/base/base.html:82
3838 4456 msgid "Permissions"
3839 4457 msgstr ""
3840 4458
@@ -3963,7 +4581,7 b' msgstr ""'
3963 4581
3964 4582 #: rhodecode/templates/admin/repo_groups/repo_group_add.html:14
3965 4583 #: rhodecode/templates/admin/users/user_edit_advanced.html:12
3966 #: rhodecode/templates/base/base.html:80 rhodecode/templates/base/base.html:152
4584 #: rhodecode/templates/base/base.html:79 rhodecode/templates/base/base.html:153
3967 4585 msgid "Repository groups"
3968 4586 msgstr ""
3969 4587
@@ -3994,17 +4612,8 b' msgstr ""'
3994 4612 msgid "Add Child Group"
3995 4613 msgstr ""
3996 4614
3997 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:44
3998 #: rhodecode/templates/admin/repos/repo_edit.html:15
3999 #: rhodecode/templates/admin/repos/repo_edit.html:39
4000 #: rhodecode/templates/admin/settings/settings.html:14
4001 #: rhodecode/templates/admin/user_groups/user_group_edit.html:33
4002 #: rhodecode/templates/base/base.html:86 rhodecode/templates/base/base.html:250
4003 msgid "Settings"
4004 msgstr ""
4005
4006 4615 #: rhodecode/templates/admin/repo_groups/repo_group_edit.html:46
4007 #: rhodecode/templates/admin/repos/repo_edit.html:45
4616 #: rhodecode/templates/admin/repos/repo_edit.html:49
4008 4617 #: rhodecode/templates/admin/user_groups/user_group_edit.html:35
4009 4618 #: rhodecode/templates/admin/users/user_edit.html:35
4010 4619 msgid "Advanced"
@@ -4177,7 +4786,7 b' msgid "Import Existing Repository ?"'
4177 4786 msgstr ""
4178 4787
4179 4788 #: rhodecode/templates/admin/repos/repo_add_base.html:23
4180 #: rhodecode/templates/base/base.html:197
4789 #: rhodecode/templates/base/base.html:198
4181 4790 msgid "Clone from"
4182 4791 msgstr ""
4183 4792
@@ -4244,19 +4853,19 b' msgstr ""'
4244 4853 msgid "%s repository settings"
4245 4854 msgstr ""
4246 4855
4247 #: rhodecode/templates/admin/repos/repo_edit.html:51
4856 #: rhodecode/templates/admin/repos/repo_edit.html:55
4248 4857 msgid "Extra Fields"
4249 4858 msgstr ""
4250 4859
4251 #: rhodecode/templates/admin/repos/repo_edit.html:57
4252 msgid "Caches"
4253 msgstr ""
4254
4255 4860 #: rhodecode/templates/admin/repos/repo_edit.html:61
4256 msgid "Remote"
4861 msgid "Caches"
4257 4862 msgstr ""
4258 4863
4259 4864 #: rhodecode/templates/admin/repos/repo_edit.html:65
4865 msgid "Remote"
4866 msgstr ""
4867
4868 #: rhodecode/templates/admin/repos/repo_edit.html:69
4260 4869 #: rhodecode/templates/summary/components.html:135
4261 4870 msgid "Statistics"
4262 4871 msgstr ""
@@ -4358,7 +4967,7 b' msgid "Delete forks"'
4358 4967 msgstr ""
4359 4968
4360 4969 #: rhodecode/templates/admin/repos/repo_edit_advanced.html:139
4361 #: rhodecode/templates/data_table/_dt_elements.html:118
4970 #: rhodecode/templates/data_table/_dt_elements.html:120
4362 4971 #, python-format
4363 4972 msgid "Confirm to delete this repository: %s"
4364 4973 msgstr ""
@@ -4419,7 +5028,7 b' msgstr ""'
4419 5028 #: rhodecode/templates/admin/user_groups/user_groups.html:62
4420 5029 #: rhodecode/templates/admin/users/user_add.html:97
4421 5030 #: rhodecode/templates/admin/users/user_edit_profile.html:90
4422 #: rhodecode/templates/admin/users/users.html:100
5031 #: rhodecode/templates/admin/users/users.html:99
4423 5032 msgid "Active"
4424 5033 msgstr ""
4425 5034
@@ -4575,7 +5184,7 b' msgid "http[s] url where from repository'
4575 5184 msgstr ""
4576 5185
4577 5186 #: rhodecode/templates/admin/repos/repo_edit_settings.html:56
4578 #: rhodecode/templates/data_table/_dt_elements.html:158
5187 #: rhodecode/templates/data_table/_dt_elements.html:160
4579 5188 #: rhodecode/templates/forks/fork.html:58
4580 5189 msgid "Repository group"
4581 5190 msgstr ""
@@ -4823,12 +5432,11 b' msgid "Server Announcement"'
4823 5432 msgstr ""
4824 5433
4825 5434 #: rhodecode/templates/admin/settings/settings_global.html:80
4826 msgid "Custom js/css code added at the end of the <header> tag."
5435 msgid "Custom js/css code added at the end of the <header/> tag."
4827 5436 msgstr ""
4828 5437
4829 5438 #: rhodecode/templates/admin/settings/settings_global.html:81
4830 #: rhodecode/templates/admin/settings/settings_global.html:103
4831 msgid "Use <script> or <css> tags to define custom styling or scripting"
5439 msgid "Use <script/> or <css/> tags to define custom styling or scripting"
4832 5440 msgstr ""
4833 5441
4834 5442 #: rhodecode/templates/admin/settings/settings_global.html:88
@@ -4839,6 +5447,10 b' msgstr ""'
4839 5447 msgid "Custom js/css code added at the end of the <body> tag."
4840 5448 msgstr ""
4841 5449
5450 #: rhodecode/templates/admin/settings/settings_global.html:103
5451 msgid "Use <script> or <css> tags to define custom styling or scripting"
5452 msgstr ""
5453
4842 5454 #: rhodecode/templates/admin/settings/settings_hooks.html:3
4843 5455 msgid "Built in Mercurial hooks - read only"
4844 5456 msgstr ""
@@ -5209,7 +5821,7 b' msgstr ""'
5209 5821
5210 5822 #: rhodecode/templates/admin/user_groups/user_group_add.html:13
5211 5823 #: rhodecode/templates/admin/users/user_edit_advanced.html:13
5212 #: rhodecode/templates/base/base.html:82 rhodecode/templates/base/base.html:155
5824 #: rhodecode/templates/base/base.html:81 rhodecode/templates/base/base.html:156
5213 5825 msgid "User groups"
5214 5826 msgstr ""
5215 5827
@@ -5300,8 +5912,8 b' msgid "Change owner of this user group."'
5300 5912 msgstr ""
5301 5913
5302 5914 #: rhodecode/templates/admin/user_groups/user_group_edit_settings.html:59
5303 #: rhodecode/templates/base/base.html:257
5304 #: rhodecode/templates/base/base.html:399
5915 #: rhodecode/templates/base/base.html:258
5916 #: rhodecode/templates/base/base.html:400
5305 5917 #: rhodecode/templates/search/search.html:64
5306 5918 msgid "Search"
5307 5919 msgstr ""
@@ -5345,7 +5957,7 b' msgstr ""'
5345 5957
5346 5958 #: rhodecode/templates/admin/users/user_add.html:13
5347 5959 #: rhodecode/templates/admin/users/user_edit.html:14
5348 #: rhodecode/templates/base/base.html:81
5960 #: rhodecode/templates/base/base.html:80
5349 5961 msgid "Users"
5350 5962 msgstr ""
5351 5963
@@ -5408,7 +6020,7 b' msgid "Source of Record"'
5408 6020 msgstr ""
5409 6021
5410 6022 #: rhodecode/templates/admin/users/user_edit_advanced.html:8
5411 #: rhodecode/templates/admin/users/users.html:98
6023 #: rhodecode/templates/admin/users/users.html:97
5412 6024 msgid "Last login"
5413 6025 msgstr ""
5414 6026
@@ -5505,7 +6117,7 b' msgid "Detach user groups"'
5505 6117 msgstr ""
5506 6118
5507 6119 #: rhodecode/templates/admin/users/user_edit_advanced.html:135
5508 #: rhodecode/templates/data_table/_dt_elements.html:189
6120 #: rhodecode/templates/data_table/_dt_elements.html:191
5509 6121 #, python-format
5510 6122 msgid "Confirm to delete this user: %s"
5511 6123 msgstr ""
@@ -5585,21 +6197,21 b' msgstr ""'
5585 6197 msgid "Users administration"
5586 6198 msgstr ""
5587 6199
5588 #: rhodecode/templates/admin/users/users.html:104
6200 #: rhodecode/templates/admin/users/users.html:103
5589 6201 msgid "Authentication type"
5590 6202 msgstr ""
5591 6203
5592 #: rhodecode/templates/base/base.html:45
6204 #: rhodecode/templates/base/base.html:44
5593 6205 #: rhodecode/templates/errors/error_document.html:51
5594 6206 msgid "Support"
5595 6207 msgstr ""
5596 6208
5597 #: rhodecode/templates/base/base.html:52
6209 #: rhodecode/templates/base/base.html:51
5598 6210 #, python-format
5599 6211 msgid "RhodeCode instance id: %s"
5600 6212 msgstr ""
5601 6213
5602 #: rhodecode/templates/base/base.html:84
6214 #: rhodecode/templates/base/base.html:83
5603 6215 msgid "Authentication"
5604 6216 msgstr ""
5605 6217
@@ -5613,40 +6225,40 b' msgstr ""'
5613 6225 msgid "Show More"
5614 6226 msgstr ""
5615 6227
5616 #: rhodecode/templates/base/base.html:189
6228 #: rhodecode/templates/base/base.html:190
5617 6229 msgid "Fork of"
5618 6230 msgstr ""
5619 6231
5620 #: rhodecode/templates/base/base.html:206
6232 #: rhodecode/templates/base/base.html:207
5621 6233 #, python-format
5622 6234 msgid "Repository locked by %(user)s"
5623 6235 msgstr ""
5624 6236
5625 #: rhodecode/templates/base/base.html:211
6237 #: rhodecode/templates/base/base.html:212
5626 6238 msgid "Repository not locked. Pull repository to lock it."
5627 6239 msgstr ""
5628 6240
5629 #: rhodecode/templates/base/base.html:229
5630 #: rhodecode/templates/data_table/_dt_elements.html:12
5631 #: rhodecode/templates/data_table/_dt_elements.html:13
5632 #: rhodecode/templates/data_table/_dt_elements.html:147
5633 msgid "Summary"
5634 msgstr ""
5635
5636 6241 #: rhodecode/templates/base/base.html:230
6242 #: rhodecode/templates/data_table/_dt_elements.html:12
6243 #: rhodecode/templates/data_table/_dt_elements.html:13
6244 #: rhodecode/templates/data_table/_dt_elements.html:149
6245 msgid "Summary"
6246 msgstr ""
6247
6248 #: rhodecode/templates/base/base.html:231
5637 6249 #: rhodecode/templates/data_table/_dt_elements.html:17
5638 6250 #: rhodecode/templates/data_table/_dt_elements.html:18
5639 6251 msgid "Changelog"
5640 6252 msgstr ""
5641 6253
5642 #: rhodecode/templates/base/base.html:231
6254 #: rhodecode/templates/base/base.html:232
5643 6255 #: rhodecode/templates/data_table/_dt_elements.html:22
5644 6256 #: rhodecode/templates/data_table/_dt_elements.html:23
5645 6257 #: rhodecode/templates/files/files.html:15
5646 6258 msgid "Files"
5647 6259 msgstr ""
5648 6260
5649 #: rhodecode/templates/base/base.html:233
6261 #: rhodecode/templates/base/base.html:234
5650 6262 #: rhodecode/templates/bookmarks/bookmarks.html:68
5651 6263 #: rhodecode/templates/branches/branches.html:67
5652 6264 #: rhodecode/templates/files/file_diff.html:11
@@ -5655,29 +6267,29 b' msgstr ""'
5655 6267 msgid "Compare"
5656 6268 msgstr ""
5657 6269
5658 #: rhodecode/templates/base/base.html:238
6270 #: rhodecode/templates/base/base.html:239
5659 6271 #, python-format
5660 6272 msgid "Show Pull Requests for %s"
5661 6273 msgstr ""
5662 6274
5663 #: rhodecode/templates/base/base.html:247
6275 #: rhodecode/templates/base/base.html:248
5664 6276 msgid "Options"
5665 6277 msgstr ""
5666 6278
5667 #: rhodecode/templates/base/base.html:254
6279 #: rhodecode/templates/base/base.html:255
5668 6280 #: rhodecode/templates/forks/forks_data.html:30
5669 6281 msgid "Compare fork"
5670 6282 msgstr ""
5671 6283
5672 #: rhodecode/templates/base/base.html:261
6284 #: rhodecode/templates/base/base.html:262
5673 6285 msgid "Unlock"
5674 6286 msgstr ""
5675 6287
5676 #: rhodecode/templates/base/base.html:263
6288 #: rhodecode/templates/base/base.html:264
5677 6289 msgid "Lock"
5678 6290 msgstr ""
5679 6291
5680 #: rhodecode/templates/base/base.html:268
6292 #: rhodecode/templates/base/base.html:269
5681 6293 #: rhodecode/templates/data_table/_dt_elements.html:27
5682 6294 #: rhodecode/templates/data_table/_dt_elements.html:28
5683 6295 #: rhodecode/templates/forks/forks_data.html:8
@@ -5687,73 +6299,73 b' msgid_plural "Forks"'
5687 6299 msgstr[0] ""
5688 6300 msgstr[1] ""
5689 6301
5690 #: rhodecode/templates/base/base.html:269
6302 #: rhodecode/templates/base/base.html:270
5691 6303 msgid "Create Pull Request"
5692 6304 msgstr ""
5693 6305
5694 #: rhodecode/templates/base/base.html:291
6306 #: rhodecode/templates/base/base.html:292
5695 6307 msgid "Sign in"
5696 6308 msgstr ""
5697 6309
5698 #: rhodecode/templates/base/base.html:299
6310 #: rhodecode/templates/base/base.html:300
5699 6311 #: rhodecode/templates/debug_style/login.html:28
5700 6312 msgid "Sign in to your account"
5701 6313 msgstr ""
5702 6314
5703 #: rhodecode/templates/base/base.html:315
6315 #: rhodecode/templates/base/base.html:316
5704 6316 #: rhodecode/templates/debug_style/login.html:46
5705 6317 msgid "(Forgot password?)"
5706 6318 msgstr ""
5707 6319
5708 #: rhodecode/templates/base/base.html:324
6320 #: rhodecode/templates/base/base.html:325
5709 6321 #: rhodecode/templates/debug_style/login.html:56
5710 6322 msgid "Don't have an account ?"
5711 6323 msgstr ""
5712 6324
5713 #: rhodecode/templates/base/base.html:345
6325 #: rhodecode/templates/base/base.html:346
5714 6326 msgid "Sign Out"
5715 6327 msgstr ""
5716 6328
5717 #: rhodecode/templates/base/base.html:381
5718 msgid "Show activity journal"
5719 msgstr ""
5720
5721 6329 #: rhodecode/templates/base/base.html:382
6330 msgid "Show activity journal"
6331 msgstr ""
6332
6333 #: rhodecode/templates/base/base.html:383
5722 6334 #: rhodecode/templates/journal/journal.html:4
5723 6335 #: rhodecode/templates/journal/journal.html:14
5724 6336 msgid "Journal"
5725 6337 msgstr ""
5726 6338
5727 #: rhodecode/templates/base/base.html:387
6339 #: rhodecode/templates/base/base.html:388
5728 6340 msgid "Show Public activity journal"
5729 6341 msgstr ""
5730 6342
5731 #: rhodecode/templates/base/base.html:388
6343 #: rhodecode/templates/base/base.html:389
5732 6344 msgid "Public journal"
5733 6345 msgstr ""
5734 6346
5735 #: rhodecode/templates/base/base.html:393
5736 msgid "Show Gists"
5737 msgstr ""
5738
5739 6347 #: rhodecode/templates/base/base.html:394
6348 msgid "Show Gists"
6349 msgstr ""
6350
6351 #: rhodecode/templates/base/base.html:395
5740 6352 msgid "Gists"
5741 6353 msgstr ""
5742 6354
5743 #: rhodecode/templates/base/base.html:398
6355 #: rhodecode/templates/base/base.html:399
5744 6356 msgid "Search in repositories you have access to"
5745 6357 msgstr ""
5746 6358
5747 #: rhodecode/templates/base/base.html:404
6359 #: rhodecode/templates/base/base.html:405
5748 6360 msgid "Admin settings"
5749 6361 msgstr ""
5750 6362
5751 #: rhodecode/templates/base/base.html:411
6363 #: rhodecode/templates/base/base.html:412
5752 6364 msgid "Delegated Admin settings"
5753 6365 msgstr ""
5754 6366
5755 #: rhodecode/templates/base/base.html:421
5756 6367 #: rhodecode/templates/base/base.html:422
6368 #: rhodecode/templates/base/base.html:423
5757 6369 #: rhodecode/templates/debug_style/buttons.html:5
5758 6370 #: rhodecode/templates/debug_style/code-block.html:6
5759 6371 #: rhodecode/templates/debug_style/collapsable-content.html:5
@@ -5774,15 +6386,15 b' msgstr ""'
5774 6386 msgid "Style"
5775 6387 msgstr ""
5776 6388
5777 #: rhodecode/templates/base/base.html:479
6389 #: rhodecode/templates/base/base.html:480
5778 6390 msgid "Go to"
5779 6391 msgstr ""
5780 6392
5781 #: rhodecode/templates/base/base.html:590
6393 #: rhodecode/templates/base/base.html:591
5782 6394 msgid "Keyboard shortcuts"
5783 6395 msgstr ""
5784 6396
5785 #: rhodecode/templates/base/base.html:598
6397 #: rhodecode/templates/base/base.html:599
5786 6398 msgid "Site-wide shortcuts"
5787 6399 msgstr ""
5788 6400
@@ -5957,7 +6569,7 b' msgstr ""'
5957 6569 msgid "No permission defined"
5958 6570 msgstr ""
5959 6571
5960 #: rhodecode/templates/base/root.html:151
6572 #: rhodecode/templates/base/root.html:120
5961 6573 msgid "Please enable JavaScript to use RhodeCode Enterprise"
5962 6574 msgstr ""
5963 6575
@@ -6077,6 +6689,22 b' msgstr ""'
6077 6689 msgid "During the update of a pull request, the position of inline comments will be updated and outdated inline comments will be hidden."
6078 6690 msgstr ""
6079 6691
6692 #: rhodecode/templates/base/vcs_settings.html:222
6693 msgid "Labs settings"
6694 msgstr ""
6695
6696 #: rhodecode/templates/base/vcs_settings.html:222
6697 msgid "These features are considered experimental and may not work as expected."
6698 msgstr ""
6699
6700 #: rhodecode/templates/base/vcs_settings.html:229
6701 msgid "Mercurial server-side merge"
6702 msgstr ""
6703
6704 #: rhodecode/templates/base/vcs_settings.html:234
6705 msgid "Use rebase instead of creating a merge commit when merging via web interface"
6706 msgstr ""
6707
6080 6708 #: rhodecode/templates/bookmarks/bookmarks.html:5
6081 6709 #, python-format
6082 6710 msgid "%s Bookmarks"
@@ -6091,8 +6719,8 b' msgid "Compare Selected Bookmarks"'
6091 6719 msgstr ""
6092 6720
6093 6721 #: rhodecode/templates/bookmarks/bookmarks_data.html:13
6094 #: rhodecode/templates/changelog/changelog.html:180
6095 #: rhodecode/templates/changelog/changelog_summary_data.html:53
6722 #: rhodecode/templates/changelog/changelog.html:183
6723 #: rhodecode/templates/changelog/changelog_summary_data.html:62
6096 6724 #: rhodecode/templates/changeset/changeset.html:92
6097 6725 #: rhodecode/templates/files/base.html:10
6098 6726 #, python-format
@@ -6113,8 +6741,8 b' msgid "Compare Selected Branches"'
6113 6741 msgstr ""
6114 6742
6115 6743 #: rhodecode/templates/branches/branches_data.html:12
6116 #: rhodecode/templates/changelog/changelog.html:172
6117 #: rhodecode/templates/changelog/changelog_summary_data.html:67
6744 #: rhodecode/templates/changelog/changelog.html:175
6745 #: rhodecode/templates/changelog/changelog_summary_data.html:76
6118 6746 #: rhodecode/templates/changeset/changeset.html:105
6119 6747 #: rhodecode/templates/files/base.html:23
6120 6748 #, python-format
@@ -6144,10 +6772,6 b' msgstr ""'
6144 6772 msgid "Compare fork with Parent (%s)"
6145 6773 msgstr ""
6146 6774
6147 #: rhodecode/templates/changelog/changelog.html:62
6148 msgid "Open new pull request"
6149 msgstr ""
6150
6151 6775 #: rhodecode/templates/changelog/changelog.html:68
6152 6776 #: rhodecode/templates/changelog/changelog.html:69
6153 6777 msgid "Clear selection"
@@ -6157,43 +6781,49 b' msgstr ""'
6157 6781 msgid "Clear filter"
6158 6782 msgstr ""
6159 6783
6160 #: rhodecode/templates/changelog/changelog.html:103
6161 #: rhodecode/templates/changelog/changelog_summary_data.html:9
6162 msgid "Age"
6163 msgstr ""
6164
6165 #: rhodecode/templates/changelog/changelog.html:105
6784 #: rhodecode/templates/changelog/changelog.html:107
6166 6785 #: rhodecode/templates/files/files_add.html:93
6167 6786 #: rhodecode/templates/files/files_delete.html:60
6168 6787 #: rhodecode/templates/files/files_edit.html:96
6169 6788 msgid "Commit Message"
6170 6789 msgstr ""
6171 6790
6172 #: rhodecode/templates/changelog/changelog.html:108
6173 #: rhodecode/templates/changelog/changelog_summary_data.html:11
6791 #: rhodecode/templates/changelog/changelog.html:109
6792 #: rhodecode/templates/changelog/changelog_summary_data.html:10
6793 msgid "Age"
6794 msgstr ""
6795
6796 #: rhodecode/templates/changelog/changelog.html:112
6797 #: rhodecode/templates/changelog/changelog_summary_data.html:12
6174 6798 msgid "Refs"
6175 6799 msgstr ""
6176 6800
6177 #: rhodecode/templates/changelog/changelog.html:122
6178 #: rhodecode/templates/changelog/changelog_summary_data.html:22
6801 #: rhodecode/templates/changelog/changelog.html:126
6802 #: rhodecode/templates/changelog/changelog_summary_data.html:21
6179 6803 #, python-format
6180 6804 msgid ""
6181 6805 "Commit status: %s\n"
6182 6806 "Click to open associated pull request #%s"
6183 6807 msgstr ""
6184 6808
6185 #: rhodecode/templates/changelog/changelog.html:126
6809 #: rhodecode/templates/changelog/changelog.html:130
6810 #: rhodecode/templates/changelog/changelog_summary_data.html:25
6186 6811 #, python-format
6187 6812 msgid "Commit status: %s"
6188 6813 msgstr ""
6189 6814
6190 #: rhodecode/templates/changelog/changelog.html:162
6191 #: rhodecode/templates/changelog/changelog_summary_data.html:33
6815 #: rhodecode/templates/changelog/changelog.html:136
6816 #: rhodecode/templates/changelog/changelog_summary_data.html:31
6817 msgid "Commit status: Not Reviewed"
6818 msgstr ""
6819
6820 #: rhodecode/templates/changelog/changelog.html:141
6821 #: rhodecode/templates/changelog/changelog_summary_data.html:36
6192 6822 msgid "Commit has comments"
6193 6823 msgstr ""
6194 6824
6195 #: rhodecode/templates/changelog/changelog.html:188
6196 #: rhodecode/templates/changelog/changelog_summary_data.html:60
6825 #: rhodecode/templates/changelog/changelog.html:191
6826 #: rhodecode/templates/changelog/changelog_summary_data.html:69
6197 6827 #: rhodecode/templates/changeset/changeset.html:99
6198 6828 #: rhodecode/templates/files/base.html:17
6199 6829 #: rhodecode/templates/tags/tags_data.html:12
@@ -6201,16 +6831,16 b' msgstr ""'
6201 6831 msgid "Tag %s"
6202 6832 msgstr ""
6203 6833
6204 #: rhodecode/templates/changelog/changelog.html:338
6834 #: rhodecode/templates/changelog/changelog.html:341
6205 6835 msgid "Filter changelog"
6206 6836 msgstr ""
6207 6837
6208 #: rhodecode/templates/changelog/changelog.html:411
6838 #: rhodecode/templates/changelog/changelog.html:414
6209 6839 msgid "There are no changes yet"
6210 6840 msgstr ""
6211 6841
6212 6842 #: rhodecode/templates/changelog/changelog_details.html:4
6213 #: rhodecode/templates/pullrequests/pullrequest_show.html:358
6843 #: rhodecode/templates/pullrequests/pullrequest_show.html:362
6214 6844 msgid "Removed"
6215 6845 msgstr ""
6216 6846
@@ -6241,25 +6871,25 b' msgstr ""'
6241 6871 msgid "Show File"
6242 6872 msgstr ""
6243 6873
6244 #: rhodecode/templates/changelog/changelog_summary_data.html:8
6874 #: rhodecode/templates/changelog/changelog_summary_data.html:9
6245 6875 #: rhodecode/templates/search/search_commit.html:8
6246 6876 msgid "Commit message"
6247 6877 msgstr ""
6248 6878
6249 #: rhodecode/templates/changelog/changelog_summary_data.html:91
6879 #: rhodecode/templates/changelog/changelog_summary_data.html:100
6250 6880 msgid "Add or upload files directly via RhodeCode:"
6251 6881 msgstr ""
6252 6882
6253 #: rhodecode/templates/changelog/changelog_summary_data.html:94
6883 #: rhodecode/templates/changelog/changelog_summary_data.html:103
6254 6884 #: rhodecode/templates/files/files_browser.html:25
6255 6885 msgid "Add New File"
6256 6886 msgstr ""
6257 6887
6258 #: rhodecode/templates/changelog/changelog_summary_data.html:102
6888 #: rhodecode/templates/changelog/changelog_summary_data.html:111
6259 6889 msgid "Push new repo:"
6260 6890 msgstr ""
6261 6891
6262 #: rhodecode/templates/changelog/changelog_summary_data.html:113
6892 #: rhodecode/templates/changelog/changelog_summary_data.html:122
6263 6893 msgid "Existing repository?"
6264 6894 msgstr ""
6265 6895
@@ -6335,7 +6965,7 b' msgstr ""'
6335 6965
6336 6966 #: rhodecode/templates/changeset/changeset.html:145
6337 6967 #: rhodecode/templates/changeset/changeset.html:147
6338 #: rhodecode/tests/functional/test_changeset_comments.py:217
6968 #: rhodecode/tests/functional/test_commit_comments.py:263
6339 6969 #, python-format
6340 6970 msgid "%d Commit comment"
6341 6971 msgid_plural "%d Commit comments"
@@ -6346,22 +6976,22 b' msgstr[1] ""'
6346 6976 #: rhodecode/templates/changeset/changeset.html:153
6347 6977 #: rhodecode/templates/pullrequests/pullrequest_show.html:145
6348 6978 #: rhodecode/templates/pullrequests/pullrequest_show.html:147
6349 #: rhodecode/tests/functional/test_changeset_comments.py:224
6979 #: rhodecode/tests/functional/test_commit_comments.py:270
6350 6980 #, python-format
6351 6981 msgid "%d Inline Comment"
6352 6982 msgid_plural "%d Inline Comments"
6353 6983 msgstr[0] ""
6354 6984 msgstr[1] ""
6355 6985
6356 #: rhodecode/templates/changeset/changeset.html:177
6986 #: rhodecode/templates/changeset/changeset.html:175
6357 6987 msgid "Browse files at current commit"
6358 6988 msgstr ""
6359 6989
6990 #: rhodecode/templates/changeset/changeset.html:175
6991 msgid "Browse files"
6992 msgstr ""
6993
6360 6994 #: rhodecode/templates/changeset/changeset.html:177
6361 msgid "Browse files"
6362 msgstr ""
6363
6364 #: rhodecode/templates/changeset/changeset.html:179
6365 6995 #: rhodecode/templates/changeset/changeset_range.html:59
6366 6996 #: rhodecode/templates/compare/compare_diff.html:255
6367 6997 #: rhodecode/templates/files/file_diff.html:77
@@ -6369,7 +6999,7 b' msgstr ""'
6369 6999 msgid "Expand All"
6370 7000 msgstr ""
6371 7001
6372 #: rhodecode/templates/changeset/changeset.html:179
7002 #: rhodecode/templates/changeset/changeset.html:177
6373 7003 #: rhodecode/templates/changeset/changeset_range.html:59
6374 7004 #: rhodecode/templates/compare/compare_diff.html:255
6375 7005 #: rhodecode/templates/files/file_diff.html:77
@@ -6377,30 +7007,32 b' msgstr ""'
6377 7007 msgid "Collapse All"
6378 7008 msgstr ""
6379 7009
6380 #: rhodecode/templates/changeset/changeset.html:190
7010 #: rhodecode/templates/changeset/changeset.html:188
6381 7011 #: rhodecode/templates/compare/compare_diff.html:263
6382 7012 #: rhodecode/templates/pullrequests/pullrequest_show.html:274
6383 7013 msgid "No files"
6384 7014 msgstr ""
6385 7015
6386 #: rhodecode/templates/changeset/changeset.html:227
7016 #: rhodecode/templates/changeset/changeset.html:225
6387 7017 #: rhodecode/templates/files/file_diff.html:128
7018 #: rhodecode/templates/pullrequests/pullrequest_show.html:315
6388 7019 msgid "Show comments"
6389 7020 msgstr ""
6390 7021
6391 #: rhodecode/templates/changeset/changeset.html:228
7022 #: rhodecode/templates/changeset/changeset.html:226
6392 7023 #: rhodecode/templates/files/file_diff.html:129
7024 #: rhodecode/templates/pullrequests/pullrequest_show.html:316
6393 7025 msgid "Hide comments"
6394 7026 msgstr ""
6395 7027
6396 #: rhodecode/templates/changeset/changeset.html:245
7028 #: rhodecode/templates/changeset/changeset.html:243
6397 7029 #: rhodecode/templates/changeset/diff_block.html:25
6398 7030 #: rhodecode/templates/changeset/diff_block.html:46
6399 7031 #: rhodecode/templates/files/file_diff.html:146
6400 7032 msgid "Diff was truncated. File content available only in full diff."
6401 7033 msgstr ""
6402 7034
6403 #: rhodecode/templates/changeset/changeset.html:245
7035 #: rhodecode/templates/changeset/changeset.html:243
6404 7036 #: rhodecode/templates/changeset/diff_block.html:7
6405 7037 #: rhodecode/templates/changeset/diff_block.html:10
6406 7038 #: rhodecode/templates/changeset/diff_block.html:25
@@ -6410,22 +7042,22 b' msgstr ""'
6410 7042 msgid "Showing a big diff might take some time and resources, continue?"
6411 7043 msgstr ""
6412 7044
6413 #: rhodecode/templates/changeset/changeset.html:245
7045 #: rhodecode/templates/changeset/changeset.html:243
6414 7046 #: rhodecode/templates/changeset/diff_block.html:7
6415 7047 #: rhodecode/templates/changeset/diff_block.html:10
6416 7048 #: rhodecode/templates/changeset/diff_block.html:25
6417 7049 #: rhodecode/templates/changeset/diff_block.html:46
6418 7050 #: rhodecode/templates/files/file_diff.html:146
6419 #: rhodecode/templates/pullrequests/pullrequest_show.html:386
6420 #: rhodecode/templates/pullrequests/pullrequest_show.html:392
7051 #: rhodecode/templates/pullrequests/pullrequest_show.html:390
7052 #: rhodecode/templates/pullrequests/pullrequest_show.html:396
6421 7053 msgid "Show full diff"
6422 7054 msgstr ""
6423 7055
6424 #: rhodecode/templates/changeset/changeset.html:314
7056 #: rhodecode/templates/changeset/changeset.html:312
6425 7057 msgid "No Child Commits"
6426 7058 msgstr ""
6427 7059
6428 #: rhodecode/templates/changeset/changeset.html:350
7060 #: rhodecode/templates/changeset/changeset.html:348
6429 7061 msgid "No Parent Commits"
6430 7062 msgstr ""
6431 7063
@@ -6478,6 +7110,8 b' msgstr ""'
6478 7110 #: rhodecode/templates/changeset/changeset_file_comment.html:146
6479 7111 #: rhodecode/templates/changeset/changeset_file_comment.html:293
6480 7112 #: rhodecode/templates/compare/compare_diff.html:57
7113 #: rhodecode/templates/email_templates/commit_comment.mako:87
7114 #: rhodecode/templates/email_templates/pull_request_comment.mako:93
6481 7115 msgid "Comment"
6482 7116 msgstr ""
6483 7117
@@ -6624,12 +7258,15 b' msgid "Compare Commits"'
6624 7258 msgstr ""
6625 7259
6626 7260 #: rhodecode/templates/compare/compare_diff.html:46
7261 #: rhodecode/templates/email_templates/pull_request_review.mako:50
6627 7262 #: rhodecode/templates/files/file_diff.html:56
6628 7263 #: rhodecode/templates/pullrequests/pullrequest_show.html:85
6629 7264 msgid "Target"
6630 7265 msgstr ""
6631 7266
6632 7267 #: rhodecode/templates/compare/compare_diff.html:47
7268 #: rhodecode/templates/email_templates/pull_request_comment.mako:92
7269 #: rhodecode/templates/email_templates/pull_request_review.mako:49
6633 7270 #: rhodecode/templates/files/file_diff.html:62
6634 7271 #: rhodecode/templates/files/files_source.html:18
6635 7272 msgid "Source"
@@ -6679,36 +7316,36 b' msgstr ""'
6679 7316 msgid "Subscribe to %s atom feed"
6680 7317 msgstr ""
6681 7318
6682 #: rhodecode/templates/data_table/_dt_elements.html:127
7319 #: rhodecode/templates/data_table/_dt_elements.html:129
6683 7320 msgid "Creating"
6684 7321 msgstr ""
6685 7322
6686 #: rhodecode/templates/data_table/_dt_elements.html:129
7323 #: rhodecode/templates/data_table/_dt_elements.html:131
6687 7324 msgid "Created"
6688 7325 msgstr ""
6689 7326
6690 #: rhodecode/templates/data_table/_dt_elements.html:175
7327 #: rhodecode/templates/data_table/_dt_elements.html:177
6691 7328 #, python-format
6692 7329 msgid "Confirm to delete this group: %s with %s repository"
6693 7330 msgid_plural "Confirm to delete this group: %s with %s repositories"
6694 7331 msgstr[0] ""
6695 7332 msgstr[1] ""
6696 7333
6697 #: rhodecode/templates/data_table/_dt_elements.html:201
7334 #: rhodecode/templates/data_table/_dt_elements.html:203
6698 7335 #, python-format
6699 7336 msgid "Confirm to delete this user group: %s"
6700 7337 msgstr ""
6701 7338
6702 #: rhodecode/templates/data_table/_dt_elements.html:218
7339 #: rhodecode/templates/data_table/_dt_elements.html:220
6703 7340 msgid "User group"
6704 7341 msgstr ""
6705 7342
6706 #: rhodecode/templates/data_table/_dt_elements.html:262
7343 #: rhodecode/templates/data_table/_dt_elements.html:264
6707 7344 #: rhodecode/templates/forks/fork.html:81
6708 7345 msgid "Private"
6709 7346 msgstr ""
6710 7347
6711 #: rhodecode/templates/data_table/_dt_elements.html:287
7348 #: rhodecode/templates/data_table/_dt_elements.html:289
6712 7349 #, python-format
6713 7350 msgid "Pull request #%(pr_number)s"
6714 7351 msgstr ""
@@ -6812,85 +7449,130 b' msgstr ""'
6812 7449 msgid "Form vertical"
6813 7450 msgstr ""
6814 7451
6815 #: rhodecode/templates/email_templates/base.mako:16
7452 #: rhodecode/templates/email_templates/base.mako:7
6816 7453 #, python-format
6817 7454 msgid "This is a notification from RhodeCode. %(instance_url)s"
6818 7455 msgstr ""
6819 7456
6820 #: rhodecode/templates/email_templates/commit_comment.mako:5
6821 #: rhodecode/templates/email_templates/pull_request_comment.mako:5
7457 #: rhodecode/templates/email_templates/base.mako:90
7458 msgid "RhodeCode"
7459 msgstr ""
7460
7461 #: rhodecode/templates/email_templates/commit_comment.mako:16
7462 #: rhodecode/templates/email_templates/pull_request_comment.mako:17
6822 7463 msgid "[mention]"
6823 7464 msgstr ""
6824 7465
6825 #: rhodecode/templates/email_templates/commit_comment.mako:5
6826 #, python-format
6827 msgid "%(user)s commented on commit of %(repo_name)s"
6828 msgstr ""
6829
6830 #: rhodecode/templates/email_templates/commit_comment.mako:14
6831 #: rhodecode/templates/email_templates/commit_comment.mako:41
6832 #: rhodecode/templates/email_templates/pull_request_comment.mako:15
6833 #: rhodecode/templates/email_templates/pull_request_comment.mako:51
6834 msgid "Comment link"
7466 #: rhodecode/templates/email_templates/commit_comment.mako:19
7467 #, python-format
7468 msgid "%(user)s commented on commit `%(commit_id)s` (file: `%(comment_file)s`)"
6835 7469 msgstr ""
6836 7470
6837 7471 #: rhodecode/templates/email_templates/commit_comment.mako:19
7472 #: rhodecode/templates/email_templates/commit_comment.mako:22
7473 #: rhodecode/templates/email_templates/commit_comment.mako:24
7474 #, python-format
7475 msgid "in the %(repo_name)s repository"
7476 msgstr ""
7477
7478 #: rhodecode/templates/email_templates/commit_comment.mako:22
7479 #, python-format
7480 msgid "%(user)s commented on commit `%(commit_id)s` (status: %(status)s)"
7481 msgstr ""
7482
7483 #: rhodecode/templates/email_templates/commit_comment.mako:24
7484 #: rhodecode/templates/email_templates/commit_comment.mako:78
7485 #, python-format
7486 msgid "%(user)s commented on commit `%(commit_id)s`"
7487 msgstr ""
7488
6838 7489 #: rhodecode/templates/email_templates/commit_comment.mako:43
6839 #: rhodecode/templates/email_templates/pull_request_comment.mako:20
6840 #: rhodecode/templates/email_templates/pull_request_comment.mako:54
7490 #: rhodecode/templates/email_templates/pull_request_comment.mako:43
7491 msgid "Comment link"
7492 msgstr ""
7493
7494 #: rhodecode/templates/email_templates/commit_comment.mako:48
7495 #: rhodecode/templates/email_templates/pull_request_comment.mako:48
6841 7496 #, python-format
6842 7497 msgid "File: %(comment_file)s on line %(comment_line)s"
6843 7498 msgstr ""
6844 7499
6845 #: rhodecode/templates/email_templates/commit_comment.mako:28
6846 #: rhodecode/templates/email_templates/commit_comment.mako:56
7500 #: rhodecode/templates/email_templates/commit_comment.mako:54
6847 7501 msgid "Commit status was changed to"
6848 7502 msgstr ""
6849 7503
6850 #: rhodecode/templates/email_templates/commit_comment.mako:35
6851 #, python-format
6852 msgid "%(user)s commented on a file in commit of %(repo_url)s."
6853 msgstr ""
6854
6855 #: rhodecode/templates/email_templates/commit_comment.mako:37
6856 #, python-format
6857 msgid "%(user)s commented on a commit of %(repo_url)s."
6858 msgstr ""
6859
6860 #: rhodecode/templates/email_templates/commit_comment.mako:47
6861 #: rhodecode/templates/files/files_detail.html:5
6862 #: rhodecode/templates/files/files_detail.html:12
6863 msgid "Commit Description"
6864 msgstr ""
6865
6866 #: rhodecode/templates/email_templates/pull_request_comment.mako:5
6867 #, python-format
6868 msgid "%(user)s commented on pull request #%(pr_id)s: \"%(pr_title)s\""
6869 msgstr ""
6870
6871 #: rhodecode/templates/email_templates/pull_request_comment.mako:17
6872 #: rhodecode/templates/email_templates/pull_request_comment.mako:52
7504 #: rhodecode/templates/email_templates/commit_comment.mako:76
7505 #, python-format
7506 msgid "%(user)s commented on commit `%(commit_id)s` (file:`%(comment_file)s`)"
7507 msgstr ""
7508
7509 #: rhodecode/templates/email_templates/commit_comment.mako:76
7510 #: rhodecode/templates/email_templates/commit_comment.mako:78
7511 #, python-format
7512 msgid "in the %(repo)s repository"
7513 msgstr ""
7514
7515 #: rhodecode/templates/email_templates/commit_comment.mako:85
7516 msgid "Status"
7517 msgstr ""
7518
7519 #: rhodecode/templates/email_templates/commit_comment.mako:85
7520 msgid "The commit status was changed to"
7521 msgstr ""
7522
7523 #: rhodecode/templates/email_templates/commit_comment.mako:87
7524 #: rhodecode/templates/email_templates/pull_request_comment.mako:93
7525 #, python-format
7526 msgid "Comment on line: %(comment_line)s"
7527 msgstr ""
7528
7529 #: rhodecode/templates/email_templates/password_reset.mako:30
7530 msgid "Generate new password here"
7531 msgstr ""
7532
7533 #: rhodecode/templates/email_templates/pull_request_comment.mako:20
7534 #, python-format
7535 msgid "%(user)s commented on pull request #%(pr_id)s \"%(pr_title)s\" (file: `%(comment_file)s`)"
7536 msgstr ""
7537
7538 #: rhodecode/templates/email_templates/pull_request_comment.mako:23
7539 #, python-format
7540 msgid "%(user)s commented on pull request #%(pr_id)s \"%(pr_title)s\" (status: %(status)s)"
7541 msgstr ""
7542
7543 #: rhodecode/templates/email_templates/pull_request_comment.mako:25
7544 #: rhodecode/templates/email_templates/pull_request_comment.mako:82
7545 #, python-format
7546 msgid "%(user)s commented on pull request #%(pr_id)s \"%(pr_title)s\""
7547 msgstr ""
7548
7549 #: rhodecode/templates/email_templates/pull_request_comment.mako:45
6873 7550 msgid "Source repository"
6874 7551 msgstr ""
6875 7552
6876 #: rhodecode/templates/email_templates/pull_request_comment.mako:29
6877 #: rhodecode/templates/email_templates/pull_request_comment.mako:63
6878 msgid "Pull request status was changed to"
6879 msgstr ""
6880
6881 #: rhodecode/templates/email_templates/pull_request_comment.mako:31
6882 #: rhodecode/templates/email_templates/pull_request_comment.mako:65
6883 msgid "Pull request was closed with status"
6884 msgstr ""
6885
6886 #: rhodecode/templates/email_templates/pull_request_comment.mako:37
6887 #, python-format
6888 msgid "%(user)s commented on a file on pull request #%(pr_id)s: \"%(pr_title)s\"."
6889 msgstr ""
6890
6891 #: rhodecode/templates/email_templates/pull_request_comment.mako:43
6892 #, python-format
6893 msgid "%(user)s commented on a pull request #%(pr_id)s \"%(pr_title)s\"."
7553 #: rhodecode/templates/email_templates/pull_request_comment.mako:54
7554 #, python-format
7555 msgid "%(user)s submitted pull request #%(pr_id)s status: *%(status)s*"
7556 msgstr ""
7557
7558 #: rhodecode/templates/email_templates/pull_request_comment.mako:56
7559 #, python-format
7560 msgid "%(user)s submitted pull request #%(pr_id)s status: *%(status)s and closed*"
7561 msgstr ""
7562
7563 #: rhodecode/templates/email_templates/pull_request_comment.mako:80
7564 #, python-format
7565 msgid "%(user)s commented on pull request #%(pr_id)s \"%(pr_title)s\" (file:`%(comment_file)s`)"
7566 msgstr ""
7567
7568 #: rhodecode/templates/email_templates/pull_request_comment.mako:86
7569 #, python-format
7570 msgid "submitted pull request status: %(status)s"
7571 msgstr ""
7572
7573 #: rhodecode/templates/email_templates/pull_request_comment.mako:88
7574 #, python-format
7575 msgid "submitted pull request status: %(status)s and closed"
6894 7576 msgstr ""
6895 7577
6896 7578 #: rhodecode/templates/email_templates/pull_request_review.mako:5
@@ -6899,18 +7581,15 b' msgid "%(user)s wants you to review pull'
6899 7581 msgstr ""
6900 7582
6901 7583 #: rhodecode/templates/email_templates/pull_request_review.mako:17
6902 #: rhodecode/templates/email_templates/pull_request_review.mako:54
6903 7584 #, python-format
6904 7585 msgid "Pull request from %(source_ref_type)s:%(source_ref_name)s of %(repo_url)s into %(target_ref_type)s:%(target_ref_name)s"
6905 7586 msgstr ""
6906 7587
6907 7588 #: rhodecode/templates/email_templates/pull_request_review.mako:26
6908 #: rhodecode/templates/email_templates/pull_request_review.mako:63
6909 7589 msgid "Link"
6910 7590 msgstr ""
6911 7591
6912 7592 #: rhodecode/templates/email_templates/pull_request_review.mako:35
6913 #: rhodecode/templates/email_templates/pull_request_review.mako:72
6914 7593 #, python-format
6915 7594 msgid "Commit (%(num)s)"
6916 7595 msgid_plural "Commits (%(num)s)"
@@ -6922,6 +7601,25 b' msgstr[1] ""'
6922 7601 msgid "%(user)s wants you to review pull request #%(pr_id)s: \"%(pr_title)s\"."
6923 7602 msgstr ""
6924 7603
7604 #: rhodecode/templates/email_templates/pull_request_review.mako:49
7605 #, python-format
7606 msgid "%(source_ref_type)s of %(source_repo_url)s"
7607 msgstr ""
7608
7609 #: rhodecode/templates/email_templates/pull_request_review.mako:50
7610 #, python-format
7611 msgid "%(target_ref_type)s of %(target_repo_url)s"
7612 msgstr ""
7613
7614 #: rhodecode/templates/email_templates/pull_request_review.mako:52
7615 #: rhodecode/templates/summary/components.html:95
7616 #: rhodecode/templates/summary/components.html:98
7617 #, python-format
7618 msgid "%(num)s Commit"
7619 msgid_plural "%(num)s Commits"
7620 msgstr[0] ""
7621 msgstr[1] ""
7622
6925 7623 #: rhodecode/templates/email_templates/test.mako:5
6926 7624 msgid "hello \"world\""
6927 7625 msgstr ""
@@ -6930,6 +7628,21 b' msgstr ""'
6930 7628 msgid "Translation"
6931 7629 msgstr ""
6932 7630
7631 #: rhodecode/templates/email_templates/user_registration.mako:22
7632 #, python-format
7633 msgid "New user %(user)s has registered on %(date)s"
7634 msgstr ""
7635
7636 #: rhodecode/templates/email_templates/user_registration.mako:24
7637 msgid "Full Name"
7638 msgstr ""
7639
7640 #: rhodecode/templates/email_templates/user_registration.mako:26
7641 #: rhodecode/templates/users/user.html:29
7642 #: rhodecode/templates/users/user_profile.html:5
7643 msgid "Profile"
7644 msgstr ""
7645
6933 7646 #: rhodecode/templates/errors/error_document.html:39
6934 7647 #, python-format
6935 7648 msgid "You will be redirected to %s in %s seconds"
@@ -6941,8 +7654,8 b' msgid "%(user)s commited on %(date)s UTC'
6941 7654 msgstr ""
6942 7655
6943 7656 #: rhodecode/templates/feed/atom_feed_entry.mako:26
6944 #: rhodecode/templates/pullrequests/pullrequest_show.html:386
6945 #: rhodecode/templates/pullrequests/pullrequest_show.html:392
7657 #: rhodecode/templates/pullrequests/pullrequest_show.html:390
7658 #: rhodecode/templates/pullrequests/pullrequest_show.html:396
6946 7659 msgid "Commit was too big and was cut off..."
6947 7660 msgstr ""
6948 7661
@@ -6986,7 +7699,7 b' msgstr[1] ""'
6986 7699 msgid "Show All"
6987 7700 msgstr ""
6988 7701
6989 #: rhodecode/templates/files/file_authors_box.html:26
7702 #: rhodecode/templates/files/file_authors_box.html:25
6990 7703 msgid "last author"
6991 7704 msgstr ""
6992 7705
@@ -7021,7 +7734,7 b' msgstr ""'
7021 7734 msgid "%s Files"
7022 7735 msgstr ""
7023 7736
7024 #: rhodecode/templates/files/files.html:143
7737 #: rhodecode/templates/files/files.html:131
7025 7738 msgid "Switch To Commit"
7026 7739 msgstr ""
7027 7740
@@ -7117,22 +7830,18 b' msgstr ""'
7117 7830 msgid "Loading file list..."
7118 7831 msgstr ""
7119 7832
7120 #: rhodecode/templates/files/files_browser.html:50
7833 #: rhodecode/templates/files/files_browser_tree.html:6
7121 7834 msgid "Size"
7122 7835 msgstr ""
7123 7836
7124 #: rhodecode/templates/files/files_browser.html:51
7837 #: rhodecode/templates/files/files_browser_tree.html:7
7125 7838 msgid "Modified"
7126 7839 msgstr ""
7127 7840
7128 #: rhodecode/templates/files/files_browser.html:52
7841 #: rhodecode/templates/files/files_browser_tree.html:8
7129 7842 msgid "Last Commit"
7130 7843 msgstr ""
7131 7844
7132 #: rhodecode/templates/files/files_browser.html:89
7133 msgid "Loading..."
7134 msgstr ""
7135
7136 7845 #: rhodecode/templates/files/files_delete.html:4
7137 7846 #, python-format
7138 7847 msgid "%s Files Delete"
@@ -7157,6 +7866,11 b' msgstr ""'
7157 7866 msgid "Delete File"
7158 7867 msgstr ""
7159 7868
7869 #: rhodecode/templates/files/files_detail.html:5
7870 #: rhodecode/templates/files/files_detail.html:12
7871 msgid "Commit Description"
7872 msgstr ""
7873
7160 7874 #: rhodecode/templates/files/files_detail.html:35
7161 7875 msgid "File last commit"
7162 7876 msgstr ""
@@ -7315,7 +8029,7 b' msgstr ""'
7315 8029 msgid "Forked"
7316 8030 msgstr ""
7317 8031
7318 #: rhodecode/templates/forks/forks_data.html:48
8032 #: rhodecode/templates/forks/forks_data.html:46
7319 8033 msgid "There are no forks yet"
7320 8034 msgstr ""
7321 8035
@@ -7331,7 +8045,7 b' msgstr ""'
7331 8045 msgid "RSS journal feed"
7332 8046 msgstr ""
7333 8047
7334 #: rhodecode/templates/journal/journal_data.html:53
8048 #: rhodecode/templates/journal/journal_data.html:51
7335 8049 msgid "No entries yet"
7336 8050 msgstr ""
7337 8051
@@ -7503,13 +8217,13 b' msgid_plural "Compare View: %s commits"'
7503 8217 msgstr[0] ""
7504 8218 msgstr[1] ""
7505 8219
7506 #: rhodecode/templates/pullrequests/pullrequest_show.html:330
7507 #: rhodecode/templates/pullrequests/pullrequest_show.html:365
8220 #: rhodecode/templates/pullrequests/pullrequest_show.html:334
8221 #: rhodecode/templates/pullrequests/pullrequest_show.html:369
7508 8222 msgid "Outdated Inline Comments"
7509 8223 msgstr ""
7510 8224
7511 #: rhodecode/templates/pullrequests/pullrequest_show.html:386
7512 #: rhodecode/templates/pullrequests/pullrequest_show.html:392
8225 #: rhodecode/templates/pullrequests/pullrequest_show.html:390
8226 #: rhodecode/templates/pullrequests/pullrequest_show.html:396
7513 8227 msgid "Showing a huge diff might take some time and resources"
7514 8228 msgstr ""
7515 8229
@@ -7688,14 +8402,6 b' msgstr ""'
7688 8402 msgid "Information"
7689 8403 msgstr ""
7690 8404
7691 #: rhodecode/templates/summary/components.html:95
7692 #: rhodecode/templates/summary/components.html:98
7693 #, python-format
7694 msgid "%(num)s Commit"
7695 msgid_plural "%(num)s Commits"
7696 msgstr[0] ""
7697 msgstr[1] ""
7698
7699 8405 #: rhodecode/templates/summary/components.html:102
7700 8406 msgid "Number of Repository Forks"
7701 8407 msgstr ""
@@ -7759,11 +8465,6 b' msgstr ""'
7759 8465 msgid "Compare Selected Tags"
7760 8466 msgstr ""
7761 8467
7762 #: rhodecode/templates/users/user.html:29
7763 #: rhodecode/templates/users/user_profile.html:5
7764 msgid "Profile"
7765 msgstr ""
7766
7767 8468 #: rhodecode/templates/users/user_profile.html:35
7768 8469 msgid "First name"
7769 8470 msgstr ""
@@ -205,11 +205,12 b' def vcs_operation_context('
205 205
206 206 class BasicAuth(AuthBasicAuthenticator):
207 207
208 def __init__(self, realm, authfunc, auth_http_code=None,
208 def __init__(self, realm, authfunc, registry, auth_http_code=None,
209 209 initial_call_detection=False):
210 210 self.realm = realm
211 211 self.initial_call = initial_call_detection
212 212 self.authfunc = authfunc
213 self.registry = registry
213 214 self._rc_auth_http_code = auth_http_code
214 215
215 216 def _get_response_from_code(self, http_code):
@@ -242,7 +243,8 b' class BasicAuth(AuthBasicAuthenticator):'
242 243 if len(_parts) == 2:
243 244 username, password = _parts
244 245 if self.authfunc(
245 username, password, environ, VCS_TYPE):
246 username, password, environ, VCS_TYPE,
247 registry=self.registry):
246 248 return username
247 249 if username and password:
248 250 # we mark that we actually executed authentication once, at
@@ -254,7 +256,11 b' class BasicAuth(AuthBasicAuthenticator):'
254 256 __call__ = authenticate
255 257
256 258
257 def attach_context_attributes(context):
259 def attach_context_attributes(context, request):
260 """
261 Attach variables into template context called `c`, please note that
262 request could be pylons or pyramid request in here.
263 """
258 264 rc_config = SettingsModel().get_all_settings(cache=True)
259 265
260 266 context.rhodecode_version = rhodecode.__version__
@@ -320,6 +326,36 b' def attach_context_attributes(context):'
320 326 'appenlight.api_public_key', '')
321 327 context.appenlight_server_url = config.get('appenlight.server_url', '')
322 328
329 # JS template context
330 context.template_context = {
331 'repo_name': None,
332 'repo_type': None,
333 'repo_landing_commit': None,
334 'rhodecode_user': {
335 'username': None,
336 'email': None,
337 'notification_status': False
338 },
339 'visual': {
340 'default_renderer': None
341 },
342 'commit_data': {
343 'commit_id': None
344 },
345 'pull_request_data': {'pull_request_id': None},
346 'timeago': {
347 'refresh_time': 120 * 1000,
348 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
349 },
350 'pylons_dispatch': {
351 # 'controller': request.environ['pylons.routes_dict']['controller'],
352 # 'action': request.environ['pylons.routes_dict']['action'],
353 },
354 'pyramid_dispatch': {
355
356 },
357 'extra': {'plugins': {}}
358 }
323 359 # END CONFIG VARS
324 360
325 361 # TODO: This dosn't work when called from pylons compatibility tween.
@@ -380,7 +416,7 b' class BaseController(WSGIController):'
380 416 """
381 417 # on each call propagate settings calls into global settings.
382 418 set_rhodecode_config(config)
383 attach_context_attributes(c)
419 attach_context_attributes(c, request)
384 420
385 421 # TODO: Remove this when fixed in attach_context_attributes()
386 422 c.repo_name = get_repo_slug(request) # can be empty
@@ -21,6 +21,7 b''
21 21
22 22 import beaker
23 23 import logging
24 import threading
24 25
25 26 from beaker.cache import _cache_decorate, cache_regions, region_invalidate
26 27
@@ -35,7 +36,7 b" FILE_SEARCH_TREE_META = 'cache_file_sear"
35 36 SUMMARY_STATS = 'cache_summary_stats'
36 37
37 38 # This list of caches gets purged when invalidation happens
38 USED_REPO_CACHES = (FILE_TREE, FILE_TREE_META, FILE_TREE_META)
39 USED_REPO_CACHES = (FILE_TREE, FILE_SEARCH_TREE_META)
39 40
40 41 DEFAULT_CACHE_MANAGER_CONFIG = {
41 42 'type': 'memorylru_base',
@@ -170,7 +171,7 b' class InvalidationContext(object):'
170 171 safe_str(self.repo_name), safe_str(self.cache_type))
171 172
172 173 def __init__(self, compute_func, repo_name, cache_type,
173 raise_exception=False):
174 raise_exception=False, thread_scoped=False):
174 175 self.compute_func = compute_func
175 176 self.repo_name = repo_name
176 177 self.cache_type = cache_type
@@ -178,6 +179,13 b' class InvalidationContext(object):'
178 179 repo_name, cache_type)
179 180 self.raise_exception = raise_exception
180 181
182 # Append the thread id to the cache key if this invalidation context
183 # should be scoped to the current thread.
184 if thread_scoped:
185 thread_id = threading.current_thread().ident
186 self.cache_key = '{cache_key}_{thread_id}'.format(
187 cache_key=self.cache_key, thread_id=thread_id)
188
181 189 def get_cache_obj(self):
182 190 cache_key = CacheKey.get_cache_key(
183 191 self.repo_name, self.cache_type)
@@ -22,6 +22,7 b' celery libs for RhodeCode'
22 22 """
23 23
24 24
25 import pylons
25 26 import socket
26 27 import logging
27 28
@@ -29,16 +30,23 b' import rhodecode'
29 30
30 31 from os.path import join as jn
31 32 from pylons import config
33 from celery.task import Task
34 from pyramid.request import Request
35 from pyramid.scripting import prepare
36 from pyramid.threadlocal import get_current_request
32 37
33 38 from decorator import decorator
34 39
35 40 from zope.cachedescriptors.property import Lazy as LazyProperty
36 41
37 42 from rhodecode.config import utils
38 from rhodecode.lib.utils2 import safe_str, md5_safe, aslist
43 from rhodecode.lib.utils2 import (
44 safe_str, md5_safe, aslist, get_routes_generator_for_server_url,
45 get_server_url)
39 46 from rhodecode.lib.pidlock import DaemonLock, LockHeld
40 47 from rhodecode.lib.vcs import connect_vcs
41 48 from rhodecode.model import meta
49 from rhodecode.lib.auth import AuthUser
42 50
43 51 log = logging.getLogger(__name__)
44 52
@@ -52,6 +60,76 b' class ResultWrapper(object):'
52 60 return self.task
53 61
54 62
63 class RhodecodeCeleryTask(Task):
64 """
65 This is a celery task which will create a rhodecode app instance context
66 for the task, patch pyramid + pylons threadlocals with the original request
67 that created the task and also add the user to the context.
68
69 This class as a whole should be removed once the pylons port is complete
70 and a pyramid only solution for celery is implemented as per issue #4139
71 """
72
73 def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
74 link=None, link_error=None, **options):
75 """ queue the job to run (we are in web request context here) """
76
77 request = get_current_request()
78
79 if request:
80 # we hook into kwargs since it is the only way to pass our data to
81 # the celery worker in celery 2.2
82 kwargs.update({
83 '_rhodecode_proxy_data': {
84 'environ': {
85 'PATH_INFO': request.environ['PATH_INFO'],
86 'SCRIPT_NAME': request.environ['SCRIPT_NAME'],
87 'HTTP_HOST': request.environ.get('HTTP_HOST',
88 request.environ['SERVER_NAME']),
89 'SERVER_NAME': request.environ['SERVER_NAME'],
90 'SERVER_PORT': request.environ['SERVER_PORT'],
91 'wsgi.url_scheme': request.environ['wsgi.url_scheme'],
92 },
93 'auth_user': {
94 'ip_addr': request.user.ip_addr,
95 'user_id': request.user.user_id
96 },
97 }
98 })
99 return super(RhodecodeCeleryTask, self).apply_async(
100 args, kwargs, task_id, producer, link, link_error, **options)
101
102 def __call__(self, *args, **kwargs):
103 """ rebuild the context and then run task on celery worker """
104 proxy_data = kwargs.pop('_rhodecode_proxy_data', {})
105
106 if not proxy_data:
107 return super(RhodecodeCeleryTask, self).__call__(*args, **kwargs)
108
109 log.debug('using celery proxy data to run task: %r', proxy_data)
110
111 from rhodecode.config.routing import make_map
112
113 request = Request.blank('/', environ=proxy_data['environ'])
114 request.user = AuthUser(user_id=proxy_data['auth_user']['user_id'],
115 ip_addr=proxy_data['auth_user']['ip_addr'])
116
117 pyramid_request = prepare(request) # set pyramid threadlocal request
118
119 # pylons routing
120 if not rhodecode.CONFIG.get('routes.map'):
121 rhodecode.CONFIG['routes.map'] = make_map(config)
122 pylons.url._push_object(get_routes_generator_for_server_url(
123 get_server_url(request.environ)
124 ))
125
126 try:
127 return super(RhodecodeCeleryTask, self).__call__(*args, **kwargs)
128 finally:
129 pyramid_request['closer']()
130 pylons.url._pop_object()
131
132
55 133 def run_task(task, *args, **kwargs):
56 134 if rhodecode.CELERY_ENABLED:
57 135 celery_is_up = False
@@ -131,16 +209,16 b' def dbsession(func):'
131 209 def vcsconnection(func):
132 210 def __wrapper(func, *fargs, **fkwargs):
133 211 if rhodecode.CELERY_ENABLED and not rhodecode.CELERY_EAGER:
134 backends = config['vcs.backends'] = aslist(
135 config.get('vcs.backends', 'hg,git'), sep=',')
212 settings = rhodecode.PYRAMID_SETTINGS
213 backends = settings['vcs.backends']
136 214 for alias in rhodecode.BACKENDS.keys():
137 215 if alias not in backends:
138 216 del rhodecode.BACKENDS[alias]
139 utils.configure_pyro4(config)
140 utils.configure_vcs(config)
217 utils.configure_pyro4(settings)
218 utils.configure_vcs(settings)
141 219 connect_vcs(
142 config['vcs.server'],
143 utils.get_vcs_server_protocol(config))
220 settings['vcs.server'],
221 utils.get_vcs_server_protocol(settings))
144 222 ret = func(*fargs, **fkwargs)
145 223 return ret
146 224
@@ -33,7 +33,7 b' from pylons import config'
33 33 import rhodecode
34 34 from rhodecode.lib.celerylib import (
35 35 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
36 get_session, vcsconnection)
36 get_session, vcsconnection, RhodecodeCeleryTask)
37 37 from rhodecode.lib.hooks_base import log_create_repository
38 38 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
39 39 from rhodecode.lib.utils import add_cache, action_logger
@@ -56,7 +56,7 b' def get_logger(cls):'
56 56 return log
57 57
58 58
59 @task(ignore_result=True)
59 @task(ignore_result=True, base=RhodecodeCeleryTask)
60 60 @dbsession
61 61 def send_email(recipients, subject, body='', html_body='', email_config=None):
62 62 """
@@ -70,7 +70,7 b' def send_email(recipients, subject, body'
70 70 """
71 71 log = get_logger(send_email)
72 72
73 email_config = email_config or config
73 email_config = email_config or rhodecode.CONFIG
74 74 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
75 75 if not recipients:
76 76 # if recipients are not defined we send to email_config + all admins
@@ -104,7 +104,7 b' def send_email(recipients, subject, body'
104 104 return True
105 105
106 106
107 @task(ignore_result=False)
107 @task(ignore_result=True, base=RhodecodeCeleryTask)
108 108 @dbsession
109 109 @vcsconnection
110 110 def create_repo(form_data, cur_user):
@@ -197,7 +197,7 b' def create_repo(form_data, cur_user):'
197 197 return True
198 198
199 199
200 @task(ignore_result=False)
200 @task(ignore_result=True, base=RhodecodeCeleryTask)
201 201 @dbsession
202 202 @vcsconnection
203 203 def create_repo_fork(form_data, cur_user):
@@ -18,15 +18,16 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import pylons
22 import rhodecode
23
21 24 from celery.loaders.base import BaseLoader
22 from pylons import config
23 25
24 26 to_pylons = lambda x: x.replace('_', '.').lower()
25 27 to_celery = lambda x: x.replace('.', '_').upper()
26 28
27 29 LIST_PARAMS = """CELERY_IMPORTS ADMINS ROUTES""".split()
28 30
29
30 31 class PylonsSettingsProxy(object):
31 32 """Pylons Settings Proxy
32 33
@@ -35,9 +36,11 b' class PylonsSettingsProxy(object):'
35 36 """
36 37 def __getattr__(self, key):
37 38 pylons_key = to_pylons(key)
39 proxy_config = rhodecode.PYRAMID_SETTINGS or pylons.config
38 40 try:
39 value = config[pylons_key]
40 if key in LIST_PARAMS:return value.split()
41 value = proxy_config[pylons_key]
42 if key in LIST_PARAMS:
43 return value.split()
41 44 return self.type_converter(value)
42 45 except KeyError:
43 46 raise AttributeError(pylons_key)
@@ -56,7 +59,8 b' class PylonsSettingsProxy(object):'
56 59
57 60 def __setattr__(self, key, value):
58 61 pylons_key = to_pylons(key)
59 config[pylons_key] = value
62 proxy_config = rhodecode.PYRAMID_SETTINGS or pylons.config
63 proxy_config[pylons_key] = value
60 64
61 65 def __setitem__(self, key, value):
62 66 self.__setattr__(key, value)
@@ -86,3 +90,8 b' class PylonsLoader(BaseLoader):'
86 90 Import task modules.
87 91 """
88 92 self.import_default_modules()
93 from rhodecode.config.middleware import make_pyramid_app
94
95 # adding to self to keep a reference around
96 self.pyramid_app = make_pyramid_app(
97 pylons.config, **pylons.config['app_conf'])
@@ -49,11 +49,11 b' class OPS(object):'
49 49 def wrap_to_table(str_):
50 50 return '''<table class="code-difftable">
51 51 <tr class="line no-comment">
52 <td class="add-comment-line"><span class="add-comment-content"></span></td>
52 <td class="add-comment-line tooltip" title="%s"><span class="add-comment-content"></span></td>
53 53 <td class="lineno new"></td>
54 54 <td class="code no-comment"><pre>%s</pre></td>
55 55 </tr>
56 </table>''' % str_
56 </table>''' % (_('Click to comment'), str_)
57 57
58 58
59 59 def wrapped_diff(filenode_old, filenode_new, diff_limit=None, file_limit=None,
@@ -626,7 +626,9 b' class DiffProcessor(object):'
626 626 """
627 627
628 628 if condition:
629 return '''<a href="%(url)s">%(label)s</a>''' % {
629 return '''<a href="%(url)s" class="tooltip"
630 title="%(title)s">%(label)s</a>''' % {
631 'title': _('Click to select line'),
630 632 'url': url,
631 633 'label': label
632 634 }
@@ -44,7 +44,7 b' from pygments.formatters.html import Htm'
44 44 from pygments import highlight as code_highlight
45 45 from pygments.lexers import (
46 46 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
47 from pylons import url
47 from pylons import url as pylons_url
48 48 from pylons.i18n.translation import _, ungettext
49 49 from pyramid.threadlocal import get_current_request
50 50
@@ -69,6 +69,7 b' from webhelpers2.number import format_by'
69 69
70 70 from rhodecode.lib.annotate import annotate_highlight
71 71 from rhodecode.lib.action_parser import action_parser
72 from rhodecode.lib.ext_json import json
72 73 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
73 74 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
74 75 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
@@ -84,10 +85,46 b' from rhodecode.model.settings import Iss'
84 85
85 86 log = logging.getLogger(__name__)
86 87
88
87 89 DEFAULT_USER = User.DEFAULT_USER
88 90 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
89 91
90 92
93 def url(*args, **kw):
94 return pylons_url(*args, **kw)
95
96
97 def pylons_url_current(*args, **kw):
98 """
99 This function overrides pylons.url.current() which returns the current
100 path so that it will also work from a pyramid only context. This
101 should be removed once port to pyramid is complete.
102 """
103 if not args and not kw:
104 request = get_current_request()
105 return request.path
106 return pylons_url.current(*args, **kw)
107
108 url.current = pylons_url_current
109
110
111 def asset(path, ver=None):
112 """
113 Helper to generate a static asset file path for rhodecode assets
114
115 eg. h.asset('images/image.png', ver='3923')
116
117 :param path: path of asset
118 :param ver: optional version query param to append as ?ver=
119 """
120 request = get_current_request()
121 query = {}
122 if ver:
123 query = {'ver': ver}
124 return request.static_path(
125 'rhodecode:public/{}'.format(path), _query=query)
126
127
91 128 def html_escape(text, html_escape_table=None):
92 129 """Produce entities within text."""
93 130 if not html_escape_table:
@@ -771,19 +808,17 b' def discover_user(author):'
771 808 def email_or_none(author):
772 809 # extract email from the commit string
773 810 _email = author_email(author)
811
812 # If we have an email, use it, otherwise
813 # see if it contains a username we can get an email from
774 814 if _email != '':
775 # check it against RhodeCode database, and use the MAIN email for this
776 # user
777 user = User.get_by_email(_email, case_insensitive=True, cache=True)
778 if user is not None:
779 return user.email
780 815 return _email
816 else:
817 user = User.get_by_username(
818 author_name(author), case_insensitive=True, cache=True)
781 819
782 # See if it contains a username we can get an email from
783 user = User.get_by_username(author_name(author), case_insensitive=True,
784 cache=True)
785 820 if user is not None:
786 return user.email
821 return user.email
787 822
788 823 # No valid email, not a valid user in the system, none!
789 824 return None
@@ -819,6 +854,20 b' def person(author, show_attr="username_a'
819 854 return _author or _email
820 855
821 856
857 def author_string(email):
858 if email:
859 user = User.get_by_email(email, case_insensitive=True, cache=True)
860 if user:
861 if user.firstname or user.lastname:
862 return '%s %s &lt;%s&gt;' % (user.firstname, user.lastname, email)
863 else:
864 return email
865 else:
866 return email
867 else:
868 return None
869
870
822 871 def person_by_id(id_, show_attr="username_and_name"):
823 872 # attr to return from fetched user
824 873 person_getter = lambda usr: getattr(usr, show_attr)
@@ -905,7 +954,8 b' def bool2icon(value):'
905 954 #==============================================================================
906 955 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
907 956 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
908 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token
957 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
958 csrf_token_key
909 959
910 960
911 961 #==============================================================================
@@ -1601,7 +1651,7 b' def urlify_commits(text_, repository):'
1601 1651 'pref': pref,
1602 1652 'cls': 'revision-link',
1603 1653 'url': url('changeset_home', repo_name=repository,
1604 revision=commit_id),
1654 revision=commit_id, qualified=True),
1605 1655 'commit_id': commit_id,
1606 1656 'suf': suf
1607 1657 }
@@ -1611,7 +1661,8 b' def urlify_commits(text_, repository):'
1611 1661 return newtext
1612 1662
1613 1663
1614 def _process_url_func(match_obj, repo_name, uid, entry):
1664 def _process_url_func(match_obj, repo_name, uid, entry,
1665 return_raw_data=False):
1615 1666 pref = ''
1616 1667 if match_obj.group().startswith(' '):
1617 1668 pref = ' '
@@ -1637,7 +1688,7 b' def _process_url_func(match_obj, repo_na'
1637 1688 named_vars.update(match_obj.groupdict())
1638 1689 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1639 1690
1640 return tmpl % {
1691 data = {
1641 1692 'pref': pref,
1642 1693 'cls': 'issue-tracker-link',
1643 1694 'url': _url,
@@ -1645,9 +1696,15 b' def _process_url_func(match_obj, repo_na'
1645 1696 'issue-prefix': entry['pref'],
1646 1697 'serv': entry['url'],
1647 1698 }
1699 if return_raw_data:
1700 return {
1701 'id': issue_id,
1702 'url': _url
1703 }
1704 return tmpl % data
1648 1705
1649 1706
1650 def process_patterns(text_string, repo_name, config):
1707 def process_patterns(text_string, repo_name, config=None):
1651 1708 repo = None
1652 1709 if repo_name:
1653 1710 # Retrieving repo_name to avoid invalid repo_name to explode on
@@ -1657,11 +1714,9 b' def process_patterns(text_string, repo_n'
1657 1714 settings_model = IssueTrackerSettingsModel(repo=repo)
1658 1715 active_entries = settings_model.get_settings(cache=True)
1659 1716
1717 issues_data = []
1660 1718 newtext = text_string
1661 1719 for uid, entry in active_entries.items():
1662 url_func = partial(
1663 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1664
1665 1720 log.debug('found issue tracker entry with uid %s' % (uid,))
1666 1721
1667 1722 if not (entry['pat'] and entry['url']):
@@ -1679,10 +1734,20 b' def process_patterns(text_string, repo_n'
1679 1734 entry['pat'])
1680 1735 continue
1681 1736
1737 data_func = partial(
1738 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1739 return_raw_data=True)
1740
1741 for match_obj in pattern.finditer(text_string):
1742 issues_data.append(data_func(match_obj))
1743
1744 url_func = partial(
1745 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1746
1682 1747 newtext = pattern.sub(url_func, newtext)
1683 1748 log.debug('processed prefix:uid `%s`' % (uid,))
1684 1749
1685 return newtext
1750 return newtext, issues_data
1686 1751
1687 1752
1688 1753 def urlify_commit_message(commit_text, repository=None):
@@ -1694,22 +1759,22 b' def urlify_commit_message(commit_text, r'
1694 1759 :param repository:
1695 1760 """
1696 1761 from pylons import url # doh, we need to re-import url to mock it later
1697 from rhodecode import CONFIG
1698 1762
1699 1763 def escaper(string):
1700 1764 return string.replace('<', '&lt;').replace('>', '&gt;')
1701 1765
1702 1766 newtext = escaper(commit_text)
1767
1768 # extract http/https links and make them real urls
1769 newtext = urlify_text(newtext, safe=False)
1770
1703 1771 # urlify commits - extract commit ids and make link out of them, if we have
1704 1772 # the scope of repository present.
1705 1773 if repository:
1706 1774 newtext = urlify_commits(newtext, repository)
1707 1775
1708 # extract http/https links and make them real urls
1709 newtext = urlify_text(newtext, safe=False)
1710
1711 1776 # process issue tracker patterns
1712 newtext = process_patterns(newtext, repository or '', CONFIG)
1777 newtext, issues = process_patterns(newtext, repository or '')
1713 1778
1714 1779 return literal(newtext)
1715 1780
@@ -1721,21 +1786,11 b' def rst(source, mentions=False):'
1721 1786
1722 1787 def markdown(source, mentions=False):
1723 1788 return literal('<div class="markdown-block">%s</div>' %
1724 MarkupRenderer.markdown(source, flavored=False,
1789 MarkupRenderer.markdown(source, flavored=True,
1725 1790 mentions=mentions))
1726 1791
1727 1792 def renderer_from_filename(filename, exclude=None):
1728 from rhodecode.config.conf import MARKDOWN_EXTS, RST_EXTS
1729
1730 def _filter(elements):
1731 if isinstance(exclude, (list, tuple)):
1732 return [x for x in elements if x not in exclude]
1733 return elements
1734
1735 if filename.endswith(tuple(_filter([x[0] for x in MARKDOWN_EXTS if x[0]]))):
1736 return 'markdown'
1737 if filename.endswith(tuple(_filter([x[0] for x in RST_EXTS if x[0]]))):
1738 return 'rst'
1793 return MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1739 1794
1740 1795
1741 1796 def render(source, renderer='rst', mentions=False):
@@ -1827,11 +1882,15 b' def secure_form(url, method="POST", mult'
1827 1882
1828 1883 """
1829 1884 from webhelpers.pylonslib.secure_form import insecure_form
1830 from rhodecode.lib.auth import get_csrf_token, csrf_token_key
1831 1885 form = insecure_form(url, method, multipart, **attrs)
1832 token = HTML.div(hidden(csrf_token_key, get_csrf_token()), style="display: none;")
1886 token = csrf_input()
1833 1887 return literal("%s\n%s" % (form, token))
1834 1888
1889 def csrf_input():
1890 return literal(
1891 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1892 csrf_token_key, csrf_token_key, get_csrf_token()))
1893
1835 1894 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1836 1895 select_html = select(name, selected, options, **attrs)
1837 1896 select2 = """
@@ -1887,6 +1946,16 b' def route_path(*args, **kwds):'
1887 1946 return req.route_path(*args, **kwds)
1888 1947
1889 1948
1949 def static_url(*args, **kwds):
1950 """
1951 Wrapper around pyramids `route_path` function. It is used to generate
1952 URLs from within pylons views or templates. This will be removed when
1953 pyramid migration if finished.
1954 """
1955 req = get_current_request()
1956 return req.static_url(*args, **kwds)
1957
1958
1890 1959 def resource_path(*args, **kwds):
1891 1960 """
1892 1961 Wrapper around pyramids `route_path` function. It is used to generate
@@ -27,6 +27,7 b' import os'
27 27 import collections
28 28
29 29 import rhodecode
30 from rhodecode import events
30 31 from rhodecode.lib import helpers as h
31 32 from rhodecode.lib.utils import action_logger
32 33 from rhodecode.lib.utils2 import safe_str
@@ -102,6 +103,9 b' def pre_push(extras):'
102 103 # Calling hooks after checking the lock, for consistent behavior
103 104 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
104 105
106 events.trigger(events.RepoPrePushEvent(repo_name=extras.repository,
107 extras=extras))
108
105 109 return HookResponse(0, output)
106 110
107 111
@@ -128,6 +132,8 b' def pre_pull(extras):'
128 132
129 133 # Calling hooks after checking the lock, for consistent behavior
130 134 pre_pull_extension(**extras)
135 events.trigger(events.RepoPrePullEvent(repo_name=extras.repository,
136 extras=extras))
131 137
132 138 return HookResponse(0, output)
133 139
@@ -138,6 +144,8 b' def post_pull(extras):'
138 144 action = 'pull'
139 145 action_logger(user, action, extras.repository, extras.ip, commit=True)
140 146
147 events.trigger(events.RepoPullEvent(repo_name=extras.repository,
148 extras=extras))
141 149 # extension hook call
142 150 post_pull_extension(**extras)
143 151
@@ -171,6 +179,10 b' def post_push(extras):'
171 179 action_logger(
172 180 extras.username, action, extras.repository, extras.ip, commit=True)
173 181
182 events.trigger(events.RepoPushEvent(repo_name=extras.repository,
183 pushed_commit_ids=commit_ids,
184 extras=extras))
185
174 186 # extension hook call
175 187 post_push_extension(
176 188 repo_store_path=Repository.base_path(),
@@ -20,14 +20,19 b''
20 20
21 21 import json
22 22 import logging
23 import urlparse
23 24 import threading
24 25 from BaseHTTPServer import BaseHTTPRequestHandler
25 26 from SocketServer import TCPServer
27 from routes.util import URLGenerator
26 28
27 29 import Pyro4
30 import pylons
31 import rhodecode
28 32
29 33 from rhodecode.lib import hooks_base
30 from rhodecode.lib.utils2 import AttributeDict
34 from rhodecode.lib.utils2 import (
35 AttributeDict, safe_str, get_routes_generator_for_server_url)
31 36
32 37
33 38 log = logging.getLogger(__name__)
@@ -194,10 +199,14 b' def prepare_callback_daemon(extras, prot'
194 199 callback_daemon = DummyHooksCallbackDaemon()
195 200 extras['hooks_module'] = callback_daemon.hooks_module
196 201 else:
197 callback_daemon = (
198 Pyro4HooksCallbackDaemon()
199 if protocol == 'pyro4'
200 else HttpHooksCallbackDaemon())
202 if protocol == 'pyro4':
203 callback_daemon = Pyro4HooksCallbackDaemon()
204 elif protocol == 'http':
205 callback_daemon = HttpHooksCallbackDaemon()
206 else:
207 log.error('Unsupported callback daemon protocol "%s"', protocol)
208 raise Exception('Unsupported callback daemon protocol.')
209
201 210 extras['hooks_uri'] = callback_daemon.hooks_uri
202 211 extras['hooks_protocol'] = protocol
203 212
@@ -236,6 +245,8 b' class Hooks(object):'
236 245
237 246 def _call_hook(self, hook, extras):
238 247 extras = AttributeDict(extras)
248 pylons_router = get_routes_generator_for_server_url(extras.server_url)
249 pylons.url._push_object(pylons_router)
239 250
240 251 try:
241 252 result = hook(extras)
@@ -248,6 +259,9 b' class Hooks(object):'
248 259 'exception': type(error).__name__,
249 260 'exception_args': error_args,
250 261 }
262 finally:
263 pylons.url._pop_object()
264
251 265 return {
252 266 'status': result.status,
253 267 'output': result.output,
@@ -21,6 +21,7 b''
21 21 import pylons
22 22 import webob
23 23
24 from rhodecode import events
24 25 from rhodecode.lib import hooks_base
25 26 from rhodecode.lib import utils2
26 27
@@ -76,6 +77,7 b' def trigger_log_create_pull_request_hook'
76 77
77 78 extras = _get_rc_scm_extras(username, repo_name, repo_alias,
78 79 'create_pull_request')
80 events.trigger(events.PullRequestCreateEvent(pull_request))
79 81 extras.update(pull_request.get_api_data())
80 82 hooks_base.log_create_pull_request(**extras)
81 83
@@ -95,6 +97,7 b' def trigger_log_merge_pull_request_hook('
95 97
96 98 extras = _get_rc_scm_extras(username, repo_name, repo_alias,
97 99 'merge_pull_request')
100 events.trigger(events.PullRequestMergeEvent(pull_request))
98 101 extras.update(pull_request.get_api_data())
99 102 hooks_base.log_merge_pull_request(**extras)
100 103
@@ -114,6 +117,7 b' def trigger_log_close_pull_request_hook('
114 117
115 118 extras = _get_rc_scm_extras(username, repo_name, repo_alias,
116 119 'close_pull_request')
120 events.trigger(events.PullRequestCloseEvent(pull_request))
117 121 extras.update(pull_request.get_api_data())
118 122 hooks_base.log_close_pull_request(**extras)
119 123
@@ -133,6 +137,7 b' def trigger_log_review_pull_request_hook'
133 137
134 138 extras = _get_rc_scm_extras(username, repo_name, repo_alias,
135 139 'review_pull_request')
140 events.trigger(events.PullRequestReviewEvent(pull_request))
136 141 extras.update(pull_request.get_api_data())
137 142 hooks_base.log_review_pull_request(**extras)
138 143
@@ -152,5 +157,6 b' def trigger_log_update_pull_request_hook'
152 157
153 158 extras = _get_rc_scm_extras(username, repo_name, repo_alias,
154 159 'update_pull_request')
160 events.trigger(events.PullRequestUpdateEvent(pull_request))
155 161 extras.update(pull_request.get_api_data())
156 162 hooks_base.log_update_pull_request(**extras)
@@ -49,9 +49,9 b' FILE_SCHEMA = Schema('
49 49 extension=ID(stored=True),
50 50 commit_id=TEXT(stored=True),
51 51
52 size=NUMERIC(stored=True),
52 size=NUMERIC(int, 64, signed=False, stored=True),
53 53 mimetype=TEXT(stored=True),
54 lines=NUMERIC(stored=True),
54 lines=NUMERIC(int, 64, signed=False, stored=True),
55 55 )
56 56
57 57
@@ -63,7 +63,7 b' COMMIT_SCHEMA = Schema('
63 63 repository_id=NUMERIC(unique=True, stored=True),
64 64 commit_idx=NUMERIC(stored=True, sortable=True),
65 65 commit_idx_sort=ID(),
66 date=NUMERIC(stored=True, sortable=True),
66 date=NUMERIC(int, 64, signed=False, stored=True, sortable=True),
67 67 owner=TEXT(stored=True),
68 68 author=TEXT(stored=True),
69 69 message=FieldType(format=Characters(), analyzer=ANALYZER,
@@ -22,6 +22,8 b' import re'
22 22
23 23 import markdown
24 24
25 from mdx_gfm import GithubFlavoredMarkdownExtension # noqa
26
25 27
26 28 class FlavoredCheckboxExtension(markdown.Extension):
27 29
@@ -65,8 +67,6 b' class FlavoredCheckboxPostprocessor(mark'
65 67 return html.replace(before, after)
66 68
67 69
68
69
70 70 # Global Vars
71 71 URLIZE_RE = '(%s)' % '|'.join([
72 72 r'<(?:f|ht)tps?://[^>]*>',
@@ -75,6 +75,7 b" URLIZE_RE = '(%s)' % '|'.join(["
75 75 r'[^(<\s]+\.(?:com|net|org)\b',
76 76 ])
77 77
78
78 79 class UrlizePattern(markdown.inlinepatterns.Pattern):
79 80 """ Return a link Element given an autolink (`http://example/com`). """
80 81 def handleMatch(self, m):
@@ -23,17 +23,19 b''
23 23 Renderer for markup languages with ability to parse using rst or markdown
24 24 """
25 25
26
27 26 import re
28 27 import os
29 28 import logging
29 import itertools
30
30 31 from mako.lookup import TemplateLookup
31 32
32 33 from docutils.core import publish_parts
33 34 from docutils.parsers.rst import directives
34 35 import markdown
35 36
36 from rhodecode.lib.markdown_ext import FlavoredCheckboxExtension, UrlizeExtension
37 from rhodecode.lib.markdown_ext import (
38 UrlizeExtension, GithubFlavoredMarkdownExtension)
37 39 from rhodecode.lib.utils2 import safe_unicode, md5_safe, MENTIONS_REGEX
38 40
39 41 log = logging.getLogger(__name__)
@@ -49,6 +51,37 b' class MarkupRenderer(object):'
49 51 RST_PAT = re.compile(r'\.re?st$', re.IGNORECASE)
50 52 PLAIN_PAT = re.compile(r'^readme$', re.IGNORECASE)
51 53
54 # list of readme files to search in file tree and display in summary
55 # attached weights defines the search order lower is first
56 ALL_READMES = [
57 ('readme', 0), ('README', 0), ('Readme', 0),
58 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
59 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
60 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
61 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
62 ]
63 # extension together with weights. Lower is first means we control how
64 # extensions are attached to readme names with those.
65 PLAIN_EXTS = [
66 ('', 0), # special case that renders READMES names without extension
67 ('.text', 2), ('.TEXT', 2),
68 ('.txt', 3), ('.TXT', 3)
69 ]
70
71 RST_EXTS = [
72 ('.rst', 1), ('.rest', 1),
73 ('.RST', 2), ('.REST', 2)
74 ]
75
76 MARKDOWN_EXTS = [
77 ('.md', 1), ('.MD', 1),
78 ('.mkdn', 2), ('.MKDN', 2),
79 ('.mdown', 3), ('.MDOWN', 3),
80 ('.markdown', 4), ('.MARKDOWN', 4)
81 ]
82
83 ALL_EXTS = PLAIN_EXTS + MARKDOWN_EXTS + RST_EXTS
84
52 85 def _detect_renderer(self, source, filename=None):
53 86 """
54 87 runs detection of what renderer should be used for generating html
@@ -71,6 +104,49 b' class MarkupRenderer(object):'
71 104
72 105 return getattr(MarkupRenderer, detected_renderer)
73 106
107 @classmethod
108 def renderer_from_filename(cls, filename, exclude):
109 """
110 Detect renderer markdown/rst from filename and optionally use exclude
111 list to remove some options. This is mostly used in helpers.
112 Returns None when no renderer can be detected.
113 """
114 def _filter(elements):
115 if isinstance(exclude, (list, tuple)):
116 return [x for x in elements if x not in exclude]
117 return elements
118
119 if filename.endswith(
120 tuple(_filter([x[0] for x in cls.MARKDOWN_EXTS if x[0]]))):
121 return 'markdown'
122 if filename.endswith(tuple(_filter([x[0] for x in cls.RST_EXTS if x[0]]))):
123 return 'rst'
124
125 return None
126
127 @classmethod
128 def generate_readmes(cls, all_readmes, extensions):
129 combined = itertools.product(all_readmes, extensions)
130 # sort by filename weight(y[0][1]) + extensions weight(y[1][1])
131 prioritized_readmes = sorted(combined, key=lambda y: y[0][1] + y[1][1])
132 # filename, extension
133 return [''.join([x[0][0], x[1][0]]) for x in prioritized_readmes]
134
135 def pick_readme_order(self, default_renderer):
136
137 if default_renderer == 'markdown':
138 markdown = self.generate_readmes(self.ALL_READMES, self.MARKDOWN_EXTS)
139 readme_order = markdown + self.generate_readmes(
140 self.ALL_READMES, self.RST_EXTS + self.PLAIN_EXTS)
141 elif default_renderer == 'rst':
142 markdown = self.generate_readmes(self.ALL_READMES, self.RST_EXTS)
143 readme_order = markdown + self.generate_readmes(
144 self.ALL_READMES, self.MARKDOWN_EXTS + self.PLAIN_EXTS)
145 else:
146 readme_order = self.generate_readmes(self.ALL_READMES, self.ALL_EXTS)
147
148 return readme_order
149
74 150 def render(self, source, filename=None):
75 151 """
76 152 Renders a given filename using detected renderer
@@ -141,15 +217,13 b' class MarkupRenderer(object):'
141 217 return '<br />' + source.replace("\n", '<br />')
142 218
143 219 @classmethod
144 def markdown(cls, source, safe=True, flavored=False, mentions=False):
220 def markdown(cls, source, safe=True, flavored=True, mentions=False):
145 221 # It does not allow to insert inline HTML. In presence of HTML tags, it
146 222 # will replace them instead with [HTML_REMOVED]. This is controlled by
147 223 # the safe_mode=True parameter of the markdown method.
148 224 extensions = ['codehilite', 'extra', 'def_list', 'sane_lists']
149 225 if flavored:
150 extensions.append('nl2br')
151 extensions.append(FlavoredCheckboxExtension())
152 extensions.append(UrlizeExtension())
226 extensions.append(GithubFlavoredMarkdownExtension())
153 227
154 228 if mentions:
155 229 mention_pat = re.compile(MENTIONS_REGEX)
@@ -171,7 +245,7 b' class MarkupRenderer(object):'
171 245 except Exception:
172 246 log.exception('Error when rendering Markdown')
173 247 if safe:
174 log.debug('Fallbacking to render in plain mode')
248 log.debug('Fallback to render in plain mode')
175 249 return cls.plain(source)
176 250 else:
177 251 raise
@@ -190,8 +264,9 b' class MarkupRenderer(object):'
190 264
191 265 source = safe_unicode(source)
192 266 try:
193 docutils_settings = dict([(alias, None) for alias in
194 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
267 docutils_settings = dict(
268 [(alias, None) for alias in
269 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
195 270
196 271 docutils_settings.update({'input_encoding': 'unicode',
197 272 'report_level': 4})
@@ -25,7 +25,6 b' middleware to handle appenlight publishi'
25 25 from appenlight_client import make_appenlight_middleware
26 26 from appenlight_client.exceptions import get_current_traceback
27 27 from appenlight_client.wsgi import AppenlightWSGIWrapper
28 from paste.deploy.converters import asbool
29 28
30 29
31 30 def track_exception(environ):
@@ -50,7 +49,7 b' def track_extra_information(environ, sec'
50 49 environ['appenlight.extra'][section] = value
51 50
52 51
53 def wrap_in_appenlight_if_enabled(app, config, appenlight_client=None):
52 def wrap_in_appenlight_if_enabled(app, settings, appenlight_client=None):
54 53 """
55 54 Wraps the given `app` for appenlight support.
56 55
@@ -64,10 +63,10 b' def wrap_in_appenlight_if_enabled(app, c'
64 63 This is in use to support our setup of the vcs related middlewares.
65 64
66 65 """
67 if asbool(config['app_conf'].get('appenlight')):
66 if settings['appenlight']:
68 67 app = RemoteTracebackTracker(app)
69 68 if not appenlight_client:
70 app = make_appenlight_middleware(app, config)
69 app = make_appenlight_middleware(app, settings)
71 70 appenlight_client = app.appenlight_client
72 71 else:
73 72 app = AppenlightWSGIWrapper(app, appenlight_client)
@@ -30,7 +30,7 b' from rhodecode.lib.utils import is_valid'
30 30 class SimpleSvnApp(object):
31 31 IGNORED_HEADERS = [
32 32 'connection', 'keep-alive', 'content-encoding',
33 'transfer-encoding']
33 'transfer-encoding', 'content-length']
34 34
35 35 def __init__(self, config):
36 36 self.config = config
@@ -79,12 +79,18 b' class SimpleSvnApp(object):'
79 79 return headers
80 80
81 81 def _get_response_headers(self, headers):
82 return [
82 headers = [
83 83 (h, headers[h])
84 84 for h in headers
85 85 if h.lower() not in self.IGNORED_HEADERS
86 86 ]
87 87
88 # Add custom response header to indicate that this is a VCS response
89 # and which backend is used.
90 headers.append(('X-RhodeCode-Backend', 'svn'))
91
92 return headers
93
88 94
89 95 class SimpleSvn(simplevcs.SimpleVCS):
90 96
@@ -42,8 +42,10 b' from rhodecode.lib.exceptions import ('
42 42 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
43 43 from rhodecode.lib.middleware import appenlight
44 44 from rhodecode.lib.middleware.utils import scm_app
45 from rhodecode.lib.utils import is_valid_repo
45 from rhodecode.lib.utils import (
46 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
46 47 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
48 from rhodecode.lib.vcs.conf import settings as vcs_settings
47 49 from rhodecode.model import meta
48 50 from rhodecode.model.db import User, Repository
49 51 from rhodecode.model.scm import ScmModel
@@ -80,23 +82,24 b' class SimpleVCS(object):'
80 82
81 83 SCM = 'unknown'
82 84
83 def __init__(self, application, config):
85 def __init__(self, application, config, registry):
86 self.registry = registry
84 87 self.application = application
85 88 self.config = config
86 89 # base path of repo locations
87 self.basepath = self.config['base_path']
90 self.basepath = get_rhodecode_base_path()
88 91 # authenticate this VCS request using authfunc
89 92 auth_ret_code_detection = \
90 93 str2bool(self.config.get('auth_ret_code_detection', False))
91 self.authenticate = BasicAuth('', authenticate,
92 config.get('auth_ret_code'),
93 auth_ret_code_detection)
94 self.authenticate = BasicAuth(
95 '', authenticate, registry, config.get('auth_ret_code'),
96 auth_ret_code_detection)
94 97 self.ip_addr = '0.0.0.0'
95 98
96 99 @property
97 100 def scm_app(self):
98 101 custom_implementation = self.config.get('vcs.scm_app_implementation')
99 if custom_implementation:
102 if custom_implementation and custom_implementation != 'pyro4':
100 103 log.info(
101 104 "Using custom implementation of scm_app: %s",
102 105 custom_implementation)
@@ -282,15 +285,15 b' class SimpleVCS(object):'
282 285
283 286 # try to auth based on environ, container auth methods
284 287 log.debug('Running PRE-AUTH for container based authentication')
285 pre_auth = authenticate('', '', environ,VCS_TYPE)
288 pre_auth = authenticate(
289 '', '', environ, VCS_TYPE, registry=self.registry)
286 290 if pre_auth and pre_auth.get('username'):
287 291 username = pre_auth['username']
288 292 log.debug('PRE-AUTH got %s as username', username)
289 293
290 294 # If not authenticated by the container, running basic auth
291 295 if not username:
292 self.authenticate.realm = \
293 safe_str(self.config['rhodecode_realm'])
296 self.authenticate.realm = get_rhodecode_realm()
294 297
295 298 try:
296 299 result = self.authenticate(environ)
@@ -349,6 +352,7 b' class SimpleVCS(object):'
349 352 log.info(
350 353 '%s action on %s repo "%s" by "%s" from %s',
351 354 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
355
352 356 return self._generate_vcs_response(
353 357 environ, start_response, repo_path, repo_name, extras, action)
354 358
@@ -429,8 +433,8 b' class SimpleVCS(object):'
429 433
430 434 def _prepare_callback_daemon(self, extras):
431 435 return prepare_callback_daemon(
432 extras, protocol=self.config.get('vcs.hooks.protocol'),
433 use_direct_calls=self.config.get('vcs.hooks.direct_calls'))
436 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
437 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
434 438
435 439
436 440 def _should_check_locking(query_string):
@@ -39,12 +39,12 b' log = logging.getLogger(__name__)'
39 39
40 40 def create_git_wsgi_app(repo_path, repo_name, config):
41 41 url = _vcs_streaming_url() + 'git/'
42 return VcsHttpProxy(url, repo_path, repo_name, config)
42 return VcsHttpProxy(url, repo_path, repo_name, config, 'git')
43 43
44 44
45 45 def create_hg_wsgi_app(repo_path, repo_name, config):
46 46 url = _vcs_streaming_url() + 'hg/'
47 return VcsHttpProxy(url, repo_path, repo_name, config)
47 return VcsHttpProxy(url, repo_path, repo_name, config, 'hg')
48 48
49 49
50 50 def _vcs_streaming_url():
@@ -67,7 +67,7 b' class VcsHttpProxy(object):'
67 67 server as well.
68 68 """
69 69
70 def __init__(self, url, repo_path, repo_name, config):
70 def __init__(self, url, repo_path, repo_name, config, backend):
71 71 """
72 72 :param str url: The URL of the VCSServer to call.
73 73 """
@@ -75,6 +75,7 b' class VcsHttpProxy(object):'
75 75 self._repo_name = repo_name
76 76 self._repo_path = repo_path
77 77 self._config = config
78 self._backend = backend
78 79 log.debug(
79 80 "Creating VcsHttpProxy for repo %s, url %s",
80 81 repo_name, url)
@@ -115,6 +116,10 b' class VcsHttpProxy(object):'
115 116 if not wsgiref.util.is_hop_by_hop(h)
116 117 ]
117 118
119 # Add custom response header to indicate that this is a VCS response
120 # and which backend is used.
121 response_headers.append(('X-RhodeCode-Backend', self._backend))
122
118 123 # TODO: johbo: Better way to get the status including text?
119 124 status = str(response.status_code)
120 125 start_response(status, response_headers)
@@ -72,7 +72,11 b' def is_svn(environ):'
72 72 Returns True if requests target is Subversion server
73 73 """
74 74 http_dav = environ.get('HTTP_DAV', '')
75 is_svn_path = 'subversion' in http_dav
75 magic_path_segment = rhodecode.CONFIG.get(
76 'rhodecode_subversion_magic_path', '/!svn')
77 is_svn_path = (
78 'subversion' in http_dav or
79 magic_path_segment in environ['PATH_INFO'])
76 80 log.debug(
77 81 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
78 82 is_svn_path)
@@ -122,23 +126,24 b' class GunzipMiddleware(object):'
122 126
123 127 class VCSMiddleware(object):
124 128
125 def __init__(self, app, config, appenlight_client):
129 def __init__(self, app, config, appenlight_client, registry):
126 130 self.application = app
127 131 self.config = config
128 132 self.appenlight_client = appenlight_client
133 self.registry = registry
129 134
130 135 def _get_handler_app(self, environ):
131 136 app = None
132 137 if is_hg(environ):
133 app = SimpleHg(self.application, self.config)
138 app = SimpleHg(self.application, self.config, self.registry)
134 139
135 140 if is_git(environ):
136 app = SimpleGit(self.application, self.config)
141 app = SimpleGit(self.application, self.config, self.registry)
137 142
138 143 proxy_svn = rhodecode.CONFIG.get(
139 144 'rhodecode_proxy_subversion_http_requests', False)
140 145 if proxy_svn and is_svn(environ):
141 app = SimpleSvn(self.application, self.config)
146 app = SimpleSvn(self.application, self.config, self.registry)
142 147
143 148 if app:
144 149 app = GunzipMiddleware(app)
@@ -19,7 +19,7 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 depracated make-config paster command for RhodeCode
22 deprecated make-config paster command for RhodeCode
23 23 """
24 24
25 25 import os
@@ -23,8 +23,6 b' def get_plugin_settings(prefix, settings'
23 23 """
24 24 Returns plugin settings. Use::
25 25
26
27
28 26 :param prefix:
29 27 :param settings:
30 28 :return:
@@ -45,21 +43,15 b' def register_rhodecode_plugin(config, pl'
45 43 'javascript': None,
46 44 'static': None,
47 45 'css': None,
48 'top_nav': None,
46 'nav': None,
49 47 'fulltext_indexer': None,
50 48 'sqlalchemy_migrations': None,
51 49 'default_values_setter': None,
52 'resource_types': [],
53 'url_gen': None
50 'url_gen': None,
51 'template_hooks': {}
54 52 }
55 53 config.registry.rhodecode_plugins[plugin_name].update(
56 54 plugin_config)
57 # inform RC what kind of resource types we have available
58 # so we can avoid failing when a plugin is removed but data
59 # is still present in the db
60 if plugin_config.get('resource_types'):
61 config.registry.resource_types.extend(
62 plugin_config['resource_types'])
63 55
64 56 config.action(
65 57 'register_rhodecode_plugin={}'.format(plugin_name), register)
@@ -352,17 +352,17 b' def is_valid_repo_group(repo_group_name,'
352 352 return False
353 353
354 354
355 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
355 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
356 356 while True:
357 357 ok = raw_input(prompt)
358 if ok in ('y', 'ye', 'yes'):
358 if ok.lower() in ('y', 'ye', 'yes'):
359 359 return True
360 if ok in ('n', 'no', 'nop', 'nope'):
360 if ok.lower() in ('n', 'no', 'nop', 'nope'):
361 361 return False
362 362 retries = retries - 1
363 363 if retries < 0:
364 364 raise IOError
365 print complaint
365 print(complaint)
366 366
367 367 # propagated from mercurial documentation
368 368 ui_sections = [
@@ -475,6 +475,25 b' def set_rhodecode_config(config):'
475 475 config[k] = v
476 476
477 477
478 def get_rhodecode_realm():
479 """
480 Return the rhodecode realm from database.
481 """
482 from rhodecode.model.settings import SettingsModel
483 realm = SettingsModel().get_setting_by_name('realm')
484 return safe_str(realm.app_settings_value)
485
486
487 def get_rhodecode_base_path():
488 """
489 Returns the base path. The base path is the filesystem path which points
490 to the repository store.
491 """
492 from rhodecode.model.settings import SettingsModel
493 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
494 return safe_str(paths_ui.ui_value)
495
496
478 497 def map_groups(path):
479 498 """
480 499 Given a full path to a repository, create all nested groups that this
@@ -958,8 +977,10 b' class PartialRenderer(object):'
958 977
959 978
960 979 def password_changed(auth_user, session):
961 if auth_user.username == User.DEFAULT_USER:
980 # Never report password change in case of default user or anonymous user.
981 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
962 982 return False
983
963 984 password_hash = md5(auth_user.password) if auth_user.password else None
964 985 rhodecode_user = session.get('rhodecode_user', {})
965 986 session_password_hash = rhodecode_user.get('password', '')
@@ -41,6 +41,7 b' import pygments.lexers'
41 41 import sqlalchemy
42 42 import sqlalchemy.engine.url
43 43 import webob
44 import routes.util
44 45
45 46 import rhodecode
46 47
@@ -858,3 +859,28 b' class Optional(object):'
858 859 if isinstance(val, cls):
859 860 return val.getval()
860 861 return val
862
863
864 def get_routes_generator_for_server_url(server_url):
865 parsed_url = urlobject.URLObject(server_url)
866 netloc = safe_str(parsed_url.netloc)
867 script_name = safe_str(parsed_url.path)
868
869 if ':' in netloc:
870 server_name, server_port = netloc.split(':')
871 else:
872 server_name = netloc
873 server_port = (parsed_url.scheme == 'https' and '443' or '80')
874
875 environ = {
876 'REQUEST_METHOD': 'GET',
877 'PATH_INFO': '/',
878 'SERVER_NAME': server_name,
879 'SERVER_PORT': server_port,
880 'SCRIPT_NAME': script_name,
881 }
882 if parsed_url.scheme == 'https':
883 environ['HTTPS'] = 'on'
884 environ['wsgi.url_scheme'] = 'https'
885
886 return routes.util.URLGenerator(rhodecode.CONFIG['routes.map'], environ)
@@ -29,7 +29,7 b" VERSION = (0, 5, 0, 'dev')"
29 29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30 30
31 31 __all__ = [
32 'get_version', 'get_repo', 'get_backend',
32 'get_version', 'get_vcs_instance', 'get_backend',
33 33 'VCSError', 'RepositoryError', 'CommitError'
34 34 ]
35 35
@@ -40,17 +40,29 b' import time'
40 40 import urlparse
41 41 from cStringIO import StringIO
42 42
43 import pycurl
44 43 import Pyro4
45 44 from Pyro4.errors import CommunicationError
46 45
47 46 from rhodecode.lib.vcs.conf import settings
48 from rhodecode.lib.vcs.backends import get_repo, get_backend
47 from rhodecode.lib.vcs.backends import get_vcs_instance, get_backend
49 48 from rhodecode.lib.vcs.exceptions import (
50 49 VCSError, RepositoryError, CommitError)
51 50
51 log = logging.getLogger(__name__)
52 52
53 log = logging.getLogger(__name__)
53 # The pycurl library directly accesses C API functions and is not patched by
54 # gevent. This will potentially lead to deadlocks due to incompatibility to
55 # gevent. Therefore we check if gevent is active and import a gevent compatible
56 # wrapper in that case.
57 try:
58 from gevent import monkey
59 if monkey.is_module_patched('__builtin__'):
60 import geventcurl as pycurl
61 log.debug('Using gevent comapatible pycurl: %s', pycurl)
62 else:
63 import pycurl
64 except ImportError:
65 import pycurl
54 66
55 67
56 68 def get_version():
@@ -64,11 +76,11 b' def connect_pyro4(server_and_port):'
64 76 from rhodecode.lib.vcs import connection, client
65 77 from rhodecode.lib.middleware.utils import scm_app
66 78
67 git_remote = client.ThreadlocalProxyFactory(
79 git_remote = client.RequestScopeProxyFactory(
68 80 settings.pyro_remote(settings.PYRO_GIT, server_and_port))
69 hg_remote = client.ThreadlocalProxyFactory(
81 hg_remote = client.RequestScopeProxyFactory(
70 82 settings.pyro_remote(settings.PYRO_HG, server_and_port))
71 svn_remote = client.ThreadlocalProxyFactory(
83 svn_remote = client.RequestScopeProxyFactory(
72 84 settings.pyro_remote(settings.PYRO_SVN, server_and_port))
73 85
74 86 connection.Git = client.RepoMaker(proxy_factory=git_remote)
@@ -22,7 +22,8 b''
22 22 VCS Backends module
23 23 """
24 24
25 import os
25 import logging
26
26 27 from pprint import pformat
27 28
28 29 from rhodecode.lib.vcs.conf import settings
@@ -31,31 +32,29 b' from rhodecode.lib.vcs.utils.helpers imp'
31 32 from rhodecode.lib.vcs.utils.imports import import_class
32 33
33 34
34 def get_repo(path=None, alias=None, create=False):
35 log = logging.getLogger(__name__)
36
37
38 def get_vcs_instance(repo_path, *args, **kwargs):
35 39 """
36 Returns ``Repository`` object of type linked with given ``alias`` at
37 the specified ``path``. If ``alias`` is not given it will try to guess it
38 using get_scm method
40 Given a path to a repository an instance of the corresponding vcs backend
41 repository class is created and returned. If no repository can be found
42 for the path it returns None. Arguments and keyword arguments are passed
43 to the vcs backend repository class.
39 44 """
40 if create:
41 if not (path or alias):
42 raise TypeError(
43 "If create is specified, we need path and scm type")
44 return get_backend(alias)(path, create=True)
45 if path is None:
46 path = os.path.abspath(os.path.curdir)
47 45 try:
48 scm, path = get_scm(path, search_path_up=True)
49 path = os.path.abspath(path)
50 alias = scm
46 vcs_alias = get_scm(repo_path)[0]
47 log.debug(
48 'Creating instance of %s repository from %s', vcs_alias, repo_path)
49 backend = get_backend(vcs_alias)
51 50 except VCSError:
52 raise VCSError("No scm found at %s" % path)
53 if alias is None:
54 alias = get_scm(path)[0]
51 log.exception(
52 'Perhaps this repository is in db and not in '
53 'filesystem run rescan repositories with '
54 '"destroy old data" option from admin panel')
55 return None
55 56
56 backend = get_backend(alias)
57 repo = backend(path, create=create)
58 return repo
57 return backend(repo_path=repo_path, *args, **kwargs)
59 58
60 59
61 60 def get_backend(alias):
@@ -365,7 +365,8 b' class BaseRepository(object):'
365 365 raise NotImplementedError
366 366
367 367 def merge(self, target_ref, source_repo, source_ref, workspace_id,
368 user_name='', user_email='', message='', dry_run=False):
368 user_name='', user_email='', message='', dry_run=False,
369 use_rebase=False):
369 370 """
370 371 Merge the revisions specified in `source_ref` from `source_repo`
371 372 onto the `target_ref` of this repository.
@@ -388,6 +389,8 b' class BaseRepository(object):'
388 389 :param user_email: Merge commit `user_email`.
389 390 :param message: Merge commit `message`.
390 391 :param dry_run: If `True` the merge will not take place.
392 :param use_rebase: If `True` commits from the source will be rebased
393 on top of the target instead of being merged.
391 394 """
392 395 if dry_run:
393 396 message = message or 'sample_message'
@@ -407,7 +410,8 b' class BaseRepository(object):'
407 410 try:
408 411 return self._merge_repo(
409 412 shadow_repository_path, target_ref, source_repo,
410 source_ref, message, user_name, user_email, dry_run=dry_run)
413 source_ref, message, user_name, user_email, dry_run=dry_run,
414 use_rebase=use_rebase)
411 415 except RepositoryError:
412 416 log.exception(
413 417 'Unexpected failure when running merge, dry-run=%s',
@@ -864,7 +868,7 b' class BaseCommit(object):'
864 868 for f in files:
865 869 f_path = os.path.join(prefix, f.path)
866 870 file_info.append(
867 (f_path, f.mode, f.is_link(), f._get_content()))
871 (f_path, f.mode, f.is_link(), f.raw_bytes))
868 872
869 873 if write_metadata:
870 874 metadata = [
@@ -1328,6 +1332,10 b' class EmptyCommit(BaseCommit):'
1328 1332 def short_id(self):
1329 1333 return self.raw_id[:12]
1330 1334
1335 @LazyProperty
1336 def id(self):
1337 return self.raw_id
1338
1331 1339 def get_file_commit(self, path):
1332 1340 return self
1333 1341
@@ -833,7 +833,8 b' class GitRepository(BaseRepository):'
833 833
834 834 def _merge_repo(self, shadow_repository_path, target_ref,
835 835 source_repo, source_ref, merge_message,
836 merger_name, merger_email, dry_run=False):
836 merger_name, merger_email, dry_run=False,
837 use_rebase=False):
837 838 if target_ref.commit_id != self.branches[target_ref.name]:
838 839 return MergeResponse(
839 840 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
@@ -25,15 +25,15 b' HG repository module'
25 25 import logging
26 26 import binascii
27 27 import os
28 import re
29 28 import shutil
30 29 import urllib
31 30
32 31 from zope.cachedescriptors.property import Lazy as LazyProperty
33 32
34 33 from rhodecode.lib.compat import OrderedDict
35 from rhodecode.lib.datelib import (date_to_timestamp_plus_offset,
36 utcdate_fromtimestamp, makedate, date_astimestamp)
34 from rhodecode.lib.datelib import (
35 date_to_timestamp_plus_offset, utcdate_fromtimestamp, makedate,
36 date_astimestamp)
37 37 from rhodecode.lib.utils import safe_unicode, safe_str
38 38 from rhodecode.lib.vcs import connection
39 39 from rhodecode.lib.vcs.backends.base import (
@@ -42,7 +42,6 b' from rhodecode.lib.vcs.backends.base imp'
42 42 from rhodecode.lib.vcs.backends.hg.commit import MercurialCommit
43 43 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
44 44 from rhodecode.lib.vcs.backends.hg.inmemory import MercurialInMemoryCommit
45 from rhodecode.lib.vcs.conf import settings
46 45 from rhodecode.lib.vcs.exceptions import (
47 46 EmptyRepositoryError, RepositoryError, TagAlreadyExistError,
48 47 TagDoesNotExistError, CommitDoesNotExistError)
@@ -176,6 +175,7 b' class MercurialRepository(BaseRepository'
176 175
177 176 self._remote.tag(
178 177 name, commit.raw_id, message, local, user, date, tz)
178 self._remote.invalidate_vcs_cache()
179 179
180 180 # Reinitialize tags
181 181 self.tags = self._get_tags()
@@ -203,6 +203,7 b' class MercurialRepository(BaseRepository'
203 203 date, tz = date_to_timestamp_plus_offset(date)
204 204
205 205 self._remote.tag(name, nullid, message, local, user, date, tz)
206 self._remote.invalidate_vcs_cache()
206 207 self.tags = self._get_tags()
207 208
208 209 @LazyProperty
@@ -262,6 +263,7 b' class MercurialRepository(BaseRepository'
262 263 def strip(self, commit_id, branch=None):
263 264 self._remote.strip(commit_id, update=False, backup="none")
264 265
266 self._remote.invalidate_vcs_cache()
265 267 self.commit_ids = self._get_all_commit_ids()
266 268 self._rebuild_cache(self.commit_ids)
267 269
@@ -531,6 +533,7 b' class MercurialRepository(BaseRepository'
531 533 """
532 534 url = self._get_url(url)
533 535 self._remote.pull(url, commit_ids=commit_ids)
536 self._remote.invalidate_vcs_cache()
534 537
535 538 def _local_clone(self, clone_path):
536 539 """
@@ -577,7 +580,7 b' class MercurialRepository(BaseRepository'
577 580 push_branches=push_branches)
578 581
579 582 def _local_merge(self, target_ref, merge_message, user_name, user_email,
580 source_ref):
583 source_ref, use_rebase=False):
581 584 """
582 585 Merge the given source_revision into the checked out revision.
583 586
@@ -597,13 +600,14 b' class MercurialRepository(BaseRepository'
597 600 # In this case we should force a commit message
598 601 return source_ref.commit_id, True
599 602
600 if settings.HG_USE_REBASE_FOR_MERGING:
603 if use_rebase:
601 604 try:
602 605 bookmark_name = 'rcbook%s%s' % (source_ref.commit_id,
603 606 target_ref.commit_id)
604 607 self.bookmark(bookmark_name, revision=source_ref.commit_id)
605 608 self._remote.rebase(
606 609 source=source_ref.commit_id, dest=target_ref.commit_id)
610 self._remote.invalidate_vcs_cache()
607 611 self._update(bookmark_name)
608 612 return self._identify(), True
609 613 except RepositoryError:
@@ -612,15 +616,19 b' class MercurialRepository(BaseRepository'
612 616 log.exception('Error while rebasing shadow repo during merge.')
613 617
614 618 # Cleanup any rebase leftovers
619 self._remote.invalidate_vcs_cache()
615 620 self._remote.rebase(abort=True)
621 self._remote.invalidate_vcs_cache()
616 622 self._remote.update(clean=True)
617 623 raise
618 624 else:
619 625 try:
620 626 self._remote.merge(source_ref.commit_id)
627 self._remote.invalidate_vcs_cache()
621 628 self._remote.commit(
622 629 message=safe_str(merge_message),
623 630 username=safe_str('%s <%s>' % (user_name, user_email)))
631 self._remote.invalidate_vcs_cache()
624 632 return self._identify(), True
625 633 except RepositoryError:
626 634 # Cleanup any merge leftovers
@@ -659,7 +667,8 b' class MercurialRepository(BaseRepository'
659 667
660 668 def _merge_repo(self, shadow_repository_path, target_ref,
661 669 source_repo, source_ref, merge_message,
662 merger_name, merger_email, dry_run=False):
670 merger_name, merger_email, dry_run=False,
671 use_rebase=False):
663 672 if target_ref.commit_id not in self._heads():
664 673 return MergeResponse(
665 674 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
@@ -690,7 +699,7 b' class MercurialRepository(BaseRepository'
690 699 try:
691 700 merge_commit_id, needs_push = shadow_repo._local_merge(
692 701 target_ref, merge_message, merger_name, merger_email,
693 source_ref)
702 source_ref, use_rebase=use_rebase)
694 703 merge_possible = True
695 704 except RepositoryError as e:
696 705 log.exception('Failure when doing local merge on hg shadow repo')
@@ -771,11 +780,13 b' class MercurialRepository(BaseRepository'
771 780
772 781 options = {option_name: [ref]}
773 782 self._remote.pull_cmd(repository_path, hooks=False, **options)
783 self._remote.invalidate_vcs_cache()
774 784
775 785 def bookmark(self, bookmark, revision=None):
776 786 if isinstance(bookmark, unicode):
777 787 bookmark = safe_str(bookmark)
778 788 self._remote.bookmark(bookmark, revision=revision)
789 self._remote.invalidate_vcs_cache()
779 790
780 791
781 792 class MercurialIndexBasedCollectionGenerator(CollectionGenerator):
@@ -203,6 +203,10 b' class SubversionCommit(base.BaseCommit):'
203 203 changed_files.update(files)
204 204 return list(changed_files)
205 205
206 @LazyProperty
207 def id(self):
208 return self.raw_id
209
206 210 @property
207 211 def added(self):
208 212 return nodes.AddedFileNodesGenerator(
@@ -34,6 +34,7 b' from urllib2 import URLError'
34 34 import msgpack
35 35 import Pyro4
36 36 import requests
37 from pyramid.threadlocal import get_current_request
37 38 from Pyro4.errors import CommunicationError, ConnectionClosedError, DaemonError
38 39
39 40 from rhodecode.lib.vcs import exceptions
@@ -190,19 +191,67 b' class RepoMaker(object):'
190 191 return _wrap_remote_call(remote_proxy, func)
191 192
192 193
193 class ThreadlocalProxyFactory(object):
194 class RequestScopeProxyFactory(object):
194 195 """
195 Creates one Pyro4 proxy per thread on demand.
196 This factory returns pyro proxy instances based on a per request scope.
197 It returns the same instance if called from within the same request and
198 different instances if called from different requests.
196 199 """
197 200
198 201 def __init__(self, remote_uri):
199 202 self._remote_uri = remote_uri
200 self._thread_local = threading.local()
203 self._proxy_pool = []
204 self._borrowed_proxies = {}
205
206 def __call__(self, request=None):
207 """
208 Wrapper around `getProxy`.
209 """
210 request = request or get_current_request()
211 return self.getProxy(request)
212
213 def getProxy(self, request):
214 """
215 Call this to get the pyro proxy instance for the request.
216 """
217
218 # If called without a request context we return new proxy instances
219 # on every call. This allows to run e.g. invoke tasks.
220 if request is None:
221 log.info('Creating pyro proxy without request context for '
222 'remote_uri=%s', self._remote_uri)
223 return Pyro4.Proxy(self._remote_uri)
201 224
202 def __call__(self):
203 if not hasattr(self._thread_local, 'proxy'):
204 self._thread_local.proxy = Pyro4.Proxy(self._remote_uri)
205 return self._thread_local.proxy
225 # If there is an already borrowed proxy for the request context we
226 # return that instance instead of creating a new one.
227 if request in self._borrowed_proxies:
228 return self._borrowed_proxies[request]
229
230 # Get proxy from pool or create new instance.
231 try:
232 proxy = self._proxy_pool.pop()
233 except IndexError:
234 log.info('Creating pyro proxy for remote_uri=%s', self._remote_uri)
235 proxy = Pyro4.Proxy(self._remote_uri)
236
237 # Mark proxy as borrowed for the request context and add a callback
238 # that returns it when the request processing is finished.
239 self._borrowed_proxies[request] = proxy
240 request.add_finished_callback(self._returnProxy)
241
242 return proxy
243
244 def _returnProxy(self, request):
245 """
246 Callback that gets called by pyramid when the request is finished.
247 It puts the proxy back into the pool.
248 """
249 if request in self._borrowed_proxies:
250 proxy = self._borrowed_proxies.pop(request)
251 self._proxy_pool.append(proxy)
252 else:
253 log.warn('Return proxy for remote_uri=%s but no proxy borrowed '
254 'for this request.', self._remote_uri)
206 255
207 256
208 257 class RemoteRepo(object):
@@ -211,7 +260,7 b' class RemoteRepo(object):'
211 260 self._wire = {
212 261 "path": path,
213 262 "config": config,
214 "context": uuid.uuid4(),
263 "context": self._create_vcs_cache_context(),
215 264 }
216 265 if with_wire:
217 266 self._wire.update(with_wire)
@@ -238,6 +287,19 b' class RemoteRepo(object):'
238 287 def __getitem__(self, key):
239 288 return self.revision(key)
240 289
290 def _create_vcs_cache_context(self):
291 """
292 Creates a unique string which is passed to the VCSServer on every
293 remote call. It is used as cache key in the VCSServer.
294 """
295 return str(uuid.uuid4())
296
297 def invalidate_vcs_cache(self):
298 """
299 This is a no-op method for the pyro4 backend but we want to have the
300 same API for client.RemoteRepo and client_http.RemoteRepo classes.
301 """
302
241 303
242 304 def _get_proxy_method(proxy, name):
243 305 try:
@@ -90,7 +90,7 b' class RemoteRepo(object):'
90 90 self._wire = {
91 91 "path": path,
92 92 "config": config,
93 "context": str(uuid.uuid4()),
93 "context": self._create_vcs_cache_context(),
94 94 }
95 95 if with_wire:
96 96 self._wire.update(with_wire)
@@ -125,6 +125,21 b' class RemoteRepo(object):'
125 125 def __getitem__(self, key):
126 126 return self.revision(key)
127 127
128 def _create_vcs_cache_context(self):
129 """
130 Creates a unique string which is passed to the VCSServer on every
131 remote call. It is used as cache key in the VCSServer.
132 """
133 return str(uuid.uuid4())
134
135 def invalidate_vcs_cache(self):
136 """
137 This invalidates the context which is sent to the VCSServer on every
138 call to a remote method. It forces the VCSServer to create a fresh
139 repository instance on the next call to a remote method.
140 """
141 self._wire['context'] = self._create_vcs_cache_context()
142
128 143
129 144 class RemoteObject(object):
130 145
@@ -29,8 +29,6 b" DEFAULT_ENCODINGS = ['utf8']"
29 29 # It can also be ['--branches', '--tags']
30 30 GIT_REV_FILTER = ['--all']
31 31
32 HG_USE_REBASE_FOR_MERGING = False
33
34 32 # Compatibility version when creating SVN repositories. None means newest.
35 33 # Other available options are: pre-1.4-compatible, pre-1.5-compatible,
36 34 # pre-1.6-compatible, pre-1.8-compatible
@@ -51,6 +49,9 b' ARCHIVE_SPECS = {'
51 49 'zip': ('application/zip', '.zip'),
52 50 }
53 51
52 HOOKS_PROTOCOL = None
53 HOOKS_DIRECT_CALLS = False
54
54 55 PYRO_PORT = 9900
55 56
56 57 PYRO_GIT = 'git_remote'
@@ -28,6 +28,7 b' import stat'
28 28 from zope.cachedescriptors.property import Lazy as LazyProperty
29 29
30 30 from rhodecode.lib.utils import safe_unicode, safe_str
31 from rhodecode.lib.utils2 import md5
31 32 from rhodecode.lib.vcs import path as vcspath
32 33 from rhodecode.lib.vcs.backends.base import EmptyCommit, FILEMODE_DEFAULT
33 34 from rhodecode.lib.vcs.conf.mtypes import get_mimetypes_db
@@ -318,22 +319,35 b' class FileNode(Node):'
318 319 mode = self._mode
319 320 return mode
320 321
321 def _get_content(self):
322 @LazyProperty
323 def raw_bytes(self):
324 """
325 Returns lazily the raw bytes of the FileNode.
326 """
322 327 if self.commit:
323 content = self.commit.get_file_content(self.path)
328 if self._content is None:
329 self._content = self.commit.get_file_content(self.path)
330 content = self._content
324 331 else:
325 332 content = self._content
326 333 return content
327 334
328 @property
335 @LazyProperty
336 def md5(self):
337 """
338 Returns md5 of the file node.
339 """
340 return md5(self.raw_bytes)
341
342 @LazyProperty
329 343 def content(self):
330 344 """
331 345 Returns lazily content of the FileNode. If possible, would try to
332 346 decode content from UTF-8.
333 347 """
334 content = self._get_content()
348 content = self.raw_bytes
335 349
336 if bool(content and '\0' in content):
350 if self.is_binary:
337 351 return content
338 352 return safe_unicode(content)
339 353
@@ -467,12 +481,12 b' class FileNode(Node):'
467 481 else:
468 482 return NodeState.NOT_CHANGED
469 483
470 @property
484 @LazyProperty
471 485 def is_binary(self):
472 486 """
473 487 Returns True if file has binary content.
474 488 """
475 _bin = '\0' in self._get_content()
489 _bin = self.raw_bytes and '\0' in self.raw_bytes
476 490 return _bin
477 491
478 492 @LazyProperty
@@ -502,7 +516,7 b' class FileNode(Node):'
502 516 all_lines, empty_lines = 0, 0
503 517
504 518 if not self.is_binary:
505 content = self._get_content()
519 content = self.content
506 520 if count_empty:
507 521 all_lines = 0
508 522 empty_lines = 0
@@ -717,22 +731,10 b' class LargeFileNode(FileNode):'
717 731 we override check since the LargeFileNode path is system absolute
718 732 """
719 733
720 def _get_content(self):
734 def raw_bytes(self):
721 735 if self.commit:
722 736 with open(self.path, 'rb') as f:
723 737 content = f.read()
724 738 else:
725 739 content = self._content
726 return content
727
728 @property
729 def content(self):
730 """
731 Returns lazily content of the `FileNode`. If possible, would try to
732 decode content from UTF-8.
733 """
734 content = self._get_content()
735
736 if bool(content and '\0' in content):
737 return content
738 return safe_unicode(content)
740 return content No newline at end of file
@@ -38,39 +38,19 b' from rhodecode.lib.vcs.exceptions import'
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 def get_scm(path, search_path_up=False, explicit_alias=None):
41 def get_scm(path):
42 42 """
43 43 Returns one of alias from ``ALIASES`` (in order of precedence same as
44 shortcuts given in ``ALIASES``) and top working dir path for the given
44 shortcuts given in ``ALIASES``) and working dir path for the given
45 45 argument. If no scm-specific directory is found or more than one scm is
46 46 found at that directory, ``VCSError`` is raised.
47
48 :param search_path_up: if set to ``True``, this function would try to
49 move up to parent directory every time no scm is recognized for the
50 currently checked path. Default: ``False``.
51 :param explicit_alias: can be one of available backend aliases, when given
52 it will return given explicit alias in repositories under more than one
53 version control, if explicit_alias is different than found it will raise
54 VCSError
55 47 """
56 48 if not os.path.isdir(path):
57 49 raise VCSError("Given path %s is not a directory" % path)
58 50
59 def get_scms(path):
60 return [(scm, path) for scm in get_scms_for_path(path)]
61
62 found_scms = get_scms(path)
63 while not found_scms and search_path_up:
64 newpath = os.path.abspath(os.path.join(path, os.pardir))
65 if newpath == path:
66 break
67 path = newpath
68 found_scms = get_scms(path)
51 found_scms = [(scm, path) for scm in get_scms_for_path(path)]
69 52
70 53 if len(found_scms) > 1:
71 for scm in found_scms:
72 if scm[0] == explicit_alias:
73 return scm
74 54 found = ', '.join((x[0] for x in found_scms))
75 55 raise VCSError(
76 56 'More than one [%s] scm found at given path %s' % (found, path))
@@ -327,8 +327,11 b' class LoginView(object):'
327 327 if self.request.GET and self.request.GET.get('key'):
328 328 try:
329 329 user = User.get_by_auth_token(self.request.GET.get('key'))
330 password_reset_url = self.request.route_url(
331 'reset_password_confirmation',
332 _query={'key': user.api_key})
330 333 data = {'email': user.email}
331 UserModel().reset_password(data)
334 UserModel().reset_password(data, password_reset_url)
332 335 self.session.flash(
333 336 _('Your password reset was successful, '
334 337 'a new password has been sent to your email'),
@@ -145,17 +145,6 b' class BaseModel(object):'
145 145 return self._get_instance(
146 146 db.Permission, permission, callback=db.Permission.get_by_key)
147 147
148 def send_event(self, event):
149 """
150 Helper method to send an event. This wraps the pyramid logic to send an
151 event.
152 """
153 # For the first step we are using pyramids thread locals here. If the
154 # event mechanism works out as a good solution we should think about
155 # passing the registry into the constructor to get rid of it.
156 registry = get_current_registry()
157 registry.notify(event)
158
159 148 @classmethod
160 149 def get_all(cls):
161 150 """
@@ -26,10 +26,15 b' import logging'
26 26 import traceback
27 27 import collections
28 28
29 from datetime import datetime
30
31 from pylons.i18n.translation import _
32 from pyramid.threadlocal import get_current_registry
29 33 from sqlalchemy.sql.expression import null
30 34 from sqlalchemy.sql.functions import coalesce
31 35
32 36 from rhodecode.lib import helpers as h, diffs
37 from rhodecode.lib.channelstream import channelstream_request
33 38 from rhodecode.lib.utils import action_logger
34 39 from rhodecode.lib.utils2 import extract_mentioned_users
35 40 from rhodecode.model import BaseModel
@@ -77,7 +82,8 b' class ChangesetCommentsModel(BaseModel):'
77 82 return global_renderer
78 83
79 84 def create(self, text, repo, user, revision=None, pull_request=None,
80 f_path=None, line_no=None, status_change=None, closing_pr=False,
85 f_path=None, line_no=None, status_change=None,
86 status_change_type=None, closing_pr=False,
81 87 send_email=True, renderer=None):
82 88 """
83 89 Creates new comment for commit or pull request.
@@ -91,7 +97,8 b' class ChangesetCommentsModel(BaseModel):'
91 97 :param pull_request:
92 98 :param f_path:
93 99 :param line_no:
94 :param status_change:
100 :param status_change: Label for status change
101 :param status_change_type: type of status change
95 102 :param closing_pr:
96 103 :param send_email:
97 104 """
@@ -134,89 +141,83 b' class ChangesetCommentsModel(BaseModel):'
134 141
135 142 Session().add(comment)
136 143 Session().flush()
137
138 if send_email:
139 kwargs = {
140 'user': user,
141 'renderer_type': renderer,
142 'repo_name': repo.repo_name,
143 'status_change': status_change,
144 'comment_body': text,
145 'comment_file': f_path,
146 'comment_line': line_no,
147 }
144 kwargs = {
145 'user': user,
146 'renderer_type': renderer,
147 'repo_name': repo.repo_name,
148 'status_change': status_change,
149 'status_change_type': status_change_type,
150 'comment_body': text,
151 'comment_file': f_path,
152 'comment_line': line_no,
153 }
148 154
149 if commit_obj:
150 recipients = ChangesetComment.get_users(
151 revision=commit_obj.raw_id)
152 # add commit author if it's in RhodeCode system
153 cs_author = User.get_from_cs_author(commit_obj.author)
154 if not cs_author:
155 # use repo owner if we cannot extract the author correctly
156 cs_author = repo.user
157 recipients += [cs_author]
155 if commit_obj:
156 recipients = ChangesetComment.get_users(
157 revision=commit_obj.raw_id)
158 # add commit author if it's in RhodeCode system
159 cs_author = User.get_from_cs_author(commit_obj.author)
160 if not cs_author:
161 # use repo owner if we cannot extract the author correctly
162 cs_author = repo.user
163 recipients += [cs_author]
164
165 commit_comment_url = self.get_url(comment)
158 166
159 commit_comment_url = h.url(
160 'changeset_home',
161 repo_name=repo.repo_name,
162 revision=commit_obj.raw_id,
163 anchor='comment-%s' % comment.comment_id,
164 qualified=True,)
167 target_repo_url = h.link_to(
168 repo.repo_name,
169 h.url('summary_home',
170 repo_name=repo.repo_name, qualified=True))
165 171
166 target_repo_url = h.link_to(
167 repo.repo_name,
168 h.url('summary_home',
169 repo_name=repo.repo_name, qualified=True))
170
171 # commit specifics
172 kwargs.update({
173 'commit': commit_obj,
174 'commit_message': commit_obj.message,
175 'commit_target_repo': target_repo_url,
176 'commit_comment_url': commit_comment_url,
177 })
172 # commit specifics
173 kwargs.update({
174 'commit': commit_obj,
175 'commit_message': commit_obj.message,
176 'commit_target_repo': target_repo_url,
177 'commit_comment_url': commit_comment_url,
178 })
178 179
179 elif pull_request_obj:
180 # get the current participants of this pull request
181 recipients = ChangesetComment.get_users(
182 pull_request_id=pull_request_obj.pull_request_id)
183 # add pull request author
184 recipients += [pull_request_obj.author]
180 elif pull_request_obj:
181 # get the current participants of this pull request
182 recipients = ChangesetComment.get_users(
183 pull_request_id=pull_request_obj.pull_request_id)
184 # add pull request author
185 recipients += [pull_request_obj.author]
185 186
186 # add the reviewers to notification
187 recipients += [x.user for x in pull_request_obj.reviewers]
187 # add the reviewers to notification
188 recipients += [x.user for x in pull_request_obj.reviewers]
188 189
189 pr_target_repo = pull_request_obj.target_repo
190 pr_source_repo = pull_request_obj.source_repo
190 pr_target_repo = pull_request_obj.target_repo
191 pr_source_repo = pull_request_obj.source_repo
191 192
192 pr_comment_url = h.url(
193 'pullrequest_show',
194 repo_name=pr_target_repo.repo_name,
195 pull_request_id=pull_request_obj.pull_request_id,
196 anchor='comment-%s' % comment.comment_id,
197 qualified=True,)
193 pr_comment_url = h.url(
194 'pullrequest_show',
195 repo_name=pr_target_repo.repo_name,
196 pull_request_id=pull_request_obj.pull_request_id,
197 anchor='comment-%s' % comment.comment_id,
198 qualified=True,)
198 199
199 # set some variables for email notification
200 pr_target_repo_url = h.url(
201 'summary_home', repo_name=pr_target_repo.repo_name,
202 qualified=True)
200 # set some variables for email notification
201 pr_target_repo_url = h.url(
202 'summary_home', repo_name=pr_target_repo.repo_name,
203 qualified=True)
203 204
204 pr_source_repo_url = h.url(
205 'summary_home', repo_name=pr_source_repo.repo_name,
206 qualified=True)
205 pr_source_repo_url = h.url(
206 'summary_home', repo_name=pr_source_repo.repo_name,
207 qualified=True)
207 208
208 # pull request specifics
209 kwargs.update({
210 'pull_request': pull_request_obj,
211 'pr_id': pull_request_obj.pull_request_id,
212 'pr_target_repo': pr_target_repo,
213 'pr_target_repo_url': pr_target_repo_url,
214 'pr_source_repo': pr_source_repo,
215 'pr_source_repo_url': pr_source_repo_url,
216 'pr_comment_url': pr_comment_url,
217 'pr_closing': closing_pr,
218 })
219
209 # pull request specifics
210 kwargs.update({
211 'pull_request': pull_request_obj,
212 'pr_id': pull_request_obj.pull_request_id,
213 'pr_target_repo': pr_target_repo,
214 'pr_target_repo_url': pr_target_repo_url,
215 'pr_source_repo': pr_source_repo,
216 'pr_source_repo_url': pr_source_repo_url,
217 'pr_comment_url': pr_comment_url,
218 'pr_closing': closing_pr,
219 })
220 if send_email:
220 221 # pre-generate the subject for notification itself
221 222 (subject,
222 223 _h, _e, # we don't care about those
@@ -245,6 +246,44 b' class ChangesetCommentsModel(BaseModel):'
245 246 )
246 247 action_logger(user, action, comment.repo)
247 248
249 registry = get_current_registry()
250 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
251 channelstream_config = rhodecode_plugins.get('channelstream', {})
252 msg_url = ''
253 if commit_obj:
254 msg_url = commit_comment_url
255 repo_name = repo.repo_name
256 elif pull_request_obj:
257 msg_url = pr_comment_url
258 repo_name = pr_target_repo.repo_name
259
260 if channelstream_config.get('enabled'):
261 message = '<strong>{}</strong> {} - ' \
262 '<a onclick="window.location=\'{}\';' \
263 'window.location.reload()">' \
264 '<strong>{}</strong></a>'
265 message = message.format(
266 user.username, _('made a comment'), msg_url,
267 _('Show it now'))
268 channel = '/repo${}$/pr/{}'.format(
269 repo_name,
270 pull_request_id
271 )
272 payload = {
273 'type': 'message',
274 'timestamp': datetime.utcnow(),
275 'user': 'system',
276 'exclude_users': [user.username],
277 'channel': channel,
278 'message': {
279 'message': message,
280 'level': 'info',
281 'topic': '/notifications'
282 }
283 }
284 channelstream_request(channelstream_config, [payload],
285 '/message', raise_exc=False)
286
248 287 return comment
249 288
250 289 def delete(self, comment):
@@ -271,6 +310,23 b' class ChangesetCommentsModel(BaseModel):'
271 310 q = q.order_by(ChangesetComment.created_on)
272 311 return q.all()
273 312
313 def get_url(self, comment):
314 comment = self.__get_commit_comment(comment)
315 if comment.pull_request:
316 return h.url(
317 'pullrequest_show',
318 repo_name=comment.pull_request.target_repo.repo_name,
319 pull_request_id=comment.pull_request.pull_request_id,
320 anchor='comment-%s' % comment.comment_id,
321 qualified=True,)
322 else:
323 return h.url(
324 'changeset_home',
325 repo_name=comment.repo.repo_name,
326 revision=comment.revision,
327 anchor='comment-%s' % comment.comment_id,
328 qualified=True,)
329
274 330 def get_comments(self, repo_id, revision=None, pull_request=None):
275 331 """
276 332 Gets main comments based on revision or pull_request_id
@@ -49,7 +49,7 b' from zope.cachedescriptors.property impo'
49 49 from pylons import url
50 50 from pylons.i18n.translation import lazy_ugettext as _
51 51
52 from rhodecode.lib.vcs import get_backend
52 from rhodecode.lib.vcs import get_backend, get_vcs_instance
53 53 from rhodecode.lib.vcs.utils.helpers import get_scm
54 54 from rhodecode.lib.vcs.exceptions import VCSError
55 55 from rhodecode.lib.vcs.backends.base import (
@@ -57,6 +57,7 b' from rhodecode.lib.vcs.backends.base imp'
57 57 from rhodecode.lib.utils2 import (
58 58 str2bool, safe_str, get_commit_safe, safe_unicode, remove_prefix, md5_safe,
59 59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict)
60 from rhodecode.lib.jsonalchemy import MutationObj, JsonType, JSONDict
60 61 from rhodecode.lib.ext_json import json
61 62 from rhodecode.lib.caching_query import FromCache
62 63 from rhodecode.lib.encrypt import AESCipher
@@ -720,7 +721,7 b' class User(Base, BaseModel):'
720 721
721 722 if cache:
722 723 q = q.options(FromCache("sql_cache_short",
723 "get_email_key_%s" % email))
724 "get_email_key_%s" % _hash_key(email)))
724 725
725 726 ret = q.scalar()
726 727 if ret is None:
@@ -908,7 +909,7 b' class UserApiKeys(Base, BaseModel):'
908 909 return {
909 910 cls.ROLE_ALL: _('all'),
910 911 cls.ROLE_HTTP: _('http/web interface'),
911 cls.ROLE_VCS: _('vcs (git/hg protocol)'),
912 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
912 913 cls.ROLE_API: _('api calls'),
913 914 cls.ROLE_FEED: _('feed access'),
914 915 }.get(role, role)
@@ -1330,6 +1331,8 b' class Repository(Base, BaseModel):'
1330 1331 cascade="all, delete, delete-orphan")
1331 1332 ui = relationship('RepoRhodeCodeUi', cascade="all")
1332 1333 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1334 integrations = relationship('Integration',
1335 cascade="all, delete, delete-orphan")
1333 1336
1334 1337 def __unicode__(self):
1335 1338 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
@@ -1641,6 +1644,7 b' class Repository(Base, BaseModel):'
1641 1644 'repo_name': repo.repo_name,
1642 1645 'repo_type': repo.repo_type,
1643 1646 'clone_uri': repo.clone_uri or '',
1647 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1644 1648 'private': repo.private,
1645 1649 'created_on': repo.created_on,
1646 1650 'description': repo.description,
@@ -1861,7 +1865,8 b' class Repository(Base, BaseModel):'
1861 1865 cs_cache = cs_cache.__json__()
1862 1866
1863 1867 def is_outdated(new_cs_cache):
1864 if new_cs_cache['raw_id'] != self.changeset_cache['raw_id']:
1868 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1869 new_cs_cache['revision'] != self.changeset_cache['revision']):
1865 1870 return True
1866 1871 return False
1867 1872
@@ -1972,7 +1977,7 b' class Repository(Base, BaseModel):'
1972 1977 return self._get_instance()
1973 1978
1974 1979 invalidator_context = CacheKey.repo_context_cache(
1975 _get_repo, self.repo_name, None)
1980 _get_repo, self.repo_name, None, thread_scoped=True)
1976 1981
1977 1982 with invalidator_context as context:
1978 1983 context.invalidate()
@@ -1981,27 +1986,16 b' class Repository(Base, BaseModel):'
1981 1986 return repo
1982 1987
1983 1988 def _get_instance(self, cache=True, config=None):
1984 repo_full_path = self.repo_full_path
1985 try:
1986 vcs_alias = get_scm(repo_full_path)[0]
1987 log.debug(
1988 'Creating instance of %s repository from %s',
1989 vcs_alias, repo_full_path)
1990 backend = get_backend(vcs_alias)
1991 except VCSError:
1992 log.exception(
1993 'Perhaps this repository is in db and not in '
1994 'filesystem run rescan repositories with '
1995 '"destroy old data" option from admin panel')
1996 return
1997
1998 1989 config = config or self._config
1999 1990 custom_wire = {
2000 1991 'cache': cache # controls the vcs.remote cache
2001 1992 }
2002 repo = backend(
2003 safe_str(repo_full_path), config=config, create=False,
2004 with_wire=custom_wire)
1993
1994 repo = get_vcs_instance(
1995 repo_path=safe_str(self.repo_full_path),
1996 config=config,
1997 with_wire=custom_wire,
1998 create=False)
2005 1999
2006 2000 return repo
2007 2001
@@ -2854,7 +2848,8 b' class CacheKey(Base, BaseModel):'
2854 2848 return None
2855 2849
2856 2850 @classmethod
2857 def repo_context_cache(cls, compute_func, repo_name, cache_type):
2851 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2852 thread_scoped=False):
2858 2853 """
2859 2854 @cache_region('long_term')
2860 2855 def _heavy_calculation(cache_key):
@@ -2870,7 +2865,8 b' class CacheKey(Base, BaseModel):'
2870 2865 assert computed == 'result'
2871 2866 """
2872 2867 from rhodecode.lib import caches
2873 return caches.InvalidationContext(compute_func, repo_name, cache_type)
2868 return caches.InvalidationContext(
2869 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2874 2870
2875 2871
2876 2872 class ChangesetComment(Base, BaseModel):
@@ -3111,10 +3107,9 b' class PullRequest(Base, _PullRequestBase'
3111 3107 merge_status = PullRequestModel().merge_status(pull_request)
3112 3108 data = {
3113 3109 'pull_request_id': pull_request.pull_request_id,
3114 'url': url('pullrequest_show',
3115 repo_name=pull_request.target_repo.repo_name,
3116 pull_request_id=pull_request.pull_request_id,
3117 qualified=True),
3110 'url': url('pullrequest_show', repo_name=self.target_repo.repo_name,
3111 pull_request_id=self.pull_request_id,
3112 qualified=True),
3118 3113 'title': pull_request.title,
3119 3114 'description': pull_request.description,
3120 3115 'status': pull_request.status,
@@ -3395,10 +3390,9 b' class Gist(Base, BaseModel):'
3395 3390 # SCM functions
3396 3391
3397 3392 def scm_instance(self, **kwargs):
3398 from rhodecode.lib.vcs import get_repo
3399 base_path = self.base_path()
3400 return get_repo(os.path.join(*map(safe_str,
3401 [base_path, self.gist_access_id])))
3393 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3394 return get_vcs_instance(
3395 repo_path=safe_str(full_repo_path), create=False)
3402 3396
3403 3397
3404 3398 class DbMigrateVersion(Base, BaseModel):
@@ -3474,3 +3468,32 b' class ExternalIdentity(Base, BaseModel):'
3474 3468 query = cls.query()
3475 3469 query = query.filter(cls.local_user_id == local_user_id)
3476 3470 return query
3471
3472
3473 class Integration(Base, BaseModel):
3474 __tablename__ = 'integrations'
3475 __table_args__ = (
3476 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3477 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3478 )
3479
3480 integration_id = Column('integration_id', Integer(), primary_key=True)
3481 integration_type = Column('integration_type', String(255))
3482 enabled = Column('enabled', Boolean(), nullable=False)
3483 name = Column('name', String(255), nullable=False)
3484
3485 settings = Column(
3486 'settings_json', MutationObj.as_mutable(
3487 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3488 repo_id = Column(
3489 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3490 nullable=True, unique=None, default=None)
3491 repo = relationship('Repository', lazy='joined')
3492
3493 def __repr__(self):
3494 if self.repo:
3495 scope = 'repo=%r' % self.repo
3496 else:
3497 scope = 'global'
3498
3499 return '<Integration(%r, %r)>' % (self.integration_type, scope)
@@ -41,19 +41,38 b' for SELECT use formencode.All(OneOf(list'
41 41
42 42 """
43 43
44 import deform
44 45 import logging
46 import formencode
45 47
46 import formencode
48 from pkg_resources import resource_filename
47 49 from formencode import All, Pipe
48 50
49 51 from pylons.i18n.translation import _
50 52
51 53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
52 55 from rhodecode.model import validators as v
53 56
54 57 log = logging.getLogger(__name__)
55 58
56 59
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
63
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
69 return self.load(template_name)(**kw)
70
71
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 deform.Form.set_default_renderer(form_renderer)
74
75
57 76 def LoginForm():
58 77 class _LoginForm(formencode.Schema):
59 78 allow_extra_fields = True
@@ -382,6 +401,7 b' class _BaseVcsSettingsForm(formencode.Sc'
382 401
383 402 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
384 403 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
404 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
385 405
386 406
387 407 def ApplicationUiSettingsForm():
@@ -415,7 +435,6 b' def LabsSettingsForm():'
415 435 allow_extra_fields = True
416 436 filter_extra_fields = False
417 437
418 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
419 438 rhodecode_proxy_subversion_http_requests = v.StringBoolean(
420 439 if_missing=False)
421 440 rhodecode_subversion_http_server_url = v.UnicodeString(
@@ -536,23 +555,6 b' def PullRequestForm(repo_id):'
536 555 return _PullRequestForm
537 556
538 557
539 def GistForm(lifetime_options, acl_level_options):
540 class _GistForm(formencode.Schema):
541
542 gistid = All(v.UniqGistId(), v.UnicodeString(strip=True, min=3, not_empty=False, if_missing=None))
543 filename = All(v.BasePath()(),
544 v.UnicodeString(strip=True, required=False))
545 description = v.UnicodeString(required=False, if_missing=u'')
546 lifetime = v.OneOf(lifetime_options)
547 mimetype = v.UnicodeString(required=False, if_missing=None)
548 content = v.UnicodeString(required=True, not_empty=True)
549 public = v.UnicodeString(required=False, if_missing=u'')
550 private = v.UnicodeString(required=False, if_missing=u'')
551 acl_level = v.OneOf(acl_level_options)
552
553 return _GistForm
554
555
556 558 def IssueTrackerPatternsForm():
557 559 class _IssueTrackerPatternsForm(formencode.Schema):
558 560 allow_extra_fields = True
@@ -107,7 +107,7 b' class GistModel(BaseModel):'
107 107
108 108 :param description: description of the gist
109 109 :param owner: user who created this gist
110 :param gist_mapping: mapping {filename:{'content':content},...}
110 :param gist_mapping: mapping [{'filename': 'file1.txt', 'content': content}, ...}]
111 111 :param gist_type: type of gist private/public
112 112 :param lifetime: in minutes, -1 == forever
113 113 :param gist_acl_level: acl level for this gist
@@ -141,25 +141,10 b' class GistModel(BaseModel):'
141 141 repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC,
142 142 use_global_config=True)
143 143
144 processed_mapping = {}
145 for filename in gist_mapping:
146 if filename != os.path.basename(filename):
147 raise Exception('Filename cannot be inside a directory')
148
149 content = gist_mapping[filename]['content']
150 # TODO: expand support for setting explicit lexers
151 # if lexer is None:
152 # try:
153 # guess_lexer = pygments.lexers.guess_lexer_for_filename
154 # lexer = guess_lexer(filename,content)
155 # except pygments.util.ClassNotFound:
156 # lexer = 'text'
157 processed_mapping[filename] = {'content': content}
158
159 144 # now create single multifile commit
160 145 message = 'added file'
161 message += 's: ' if len(processed_mapping) > 1 else ': '
162 message += ', '.join([x for x in processed_mapping])
146 message += 's: ' if len(gist_mapping) > 1 else ': '
147 message += ', '.join([x for x in gist_mapping])
163 148
164 149 # fake RhodeCode Repository object
165 150 fake_repo = AttributeDict({
@@ -170,7 +155,7 b' class GistModel(BaseModel):'
170 155 ScmModel().create_nodes(
171 156 user=owner.user_id, repo=fake_repo,
172 157 message=message,
173 nodes=processed_mapping,
158 nodes=gist_mapping,
174 159 trigger_push_hook=False
175 160 )
176 161
@@ -196,7 +181,6 b' class GistModel(BaseModel):'
196 181 gist = self._get_gist(gist)
197 182 gist_repo = gist.scm_instance()
198 183
199 lifetime = safe_int(lifetime, -1)
200 184 if lifetime == 0: # preserve old value
201 185 gist_expires = gist.gist_expires
202 186 else:
@@ -207,9 +191,9 b' class GistModel(BaseModel):'
207 191 gist_mapping_op = {}
208 192 for k, v in gist_mapping.items():
209 193 # add, mod, del
210 if not v['org_filename'] and v['filename']:
194 if not v['filename_org'] and v['filename']:
211 195 op = 'add'
212 elif v['org_filename'] and not v['filename']:
196 elif v['filename_org'] and not v['filename']:
213 197 op = 'del'
214 198 else:
215 199 op = 'mod'
@@ -327,7 +327,8 b' class EmailNotificationModel(BaseModel):'
327 327 :return:
328 328 """
329 329 _kwargs = {
330 'instance_url': h.url('home', qualified=True)
330 'instance_url': h.url('home', qualified=True),
331 'rhodecode_instance_name': getattr(c, 'rhodecode_name', '')
331 332 }
332 333 _kwargs.update(kwargs)
333 334 return _kwargs
@@ -339,7 +340,7 b' class EmailNotificationModel(BaseModel):'
339 340 def render_email(self, type_, **kwargs):
340 341 """
341 342 renders template for email, and returns a tuple of
342 (subject, email_headers, email_body)
343 (subject, email_headers, email_html_body, email_plaintext_body)
343 344 """
344 345 # translator and helpers inject
345 346 _kwargs = self._update_kwargs_for_render(kwargs)
@@ -31,7 +31,6 b' import datetime'
31 31 from pylons.i18n.translation import _
32 32 from pylons.i18n.translation import lazy_ugettext
33 33
34 import rhodecode
35 34 from rhodecode.lib import helpers as h, hooks_utils, diffs
36 35 from rhodecode.lib.compat import OrderedDict
37 36 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
@@ -41,13 +40,14 b' from rhodecode.lib.utils import action_l'
41 40 from rhodecode.lib.utils2 import safe_unicode, safe_str, md5_safe
42 41 from rhodecode.lib.vcs.backends.base import (
43 42 Reference, MergeResponse, MergeFailureReason)
43 from rhodecode.lib.vcs.conf import settings as vcs_settings
44 44 from rhodecode.lib.vcs.exceptions import (
45 45 CommitDoesNotExistError, EmptyRepositoryError)
46 46 from rhodecode.model import BaseModel
47 47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 48 from rhodecode.model.comment import ChangesetCommentsModel
49 49 from rhodecode.model.db import (
50 PullRequest, PullRequestReviewers, Notification, ChangesetStatus,
50 PullRequest, PullRequestReviewers, ChangesetStatus,
51 51 PullRequestVersion, ChangesetComment)
52 52 from rhodecode.model.meta import Session
53 53 from rhodecode.model.notification import NotificationModel, \
@@ -423,11 +423,11 b' class PullRequestModel(BaseModel):'
423 423 }
424 424
425 425 workspace_id = self._workspace_id(pull_request)
426 protocol = rhodecode.CONFIG.get('vcs.hooks.protocol')
427 use_direct_calls = rhodecode.CONFIG.get('vcs.hooks.direct_calls')
426 use_rebase = self._use_rebase_for_merging(pull_request)
428 427
429 428 callback_daemon, extras = prepare_callback_daemon(
430 extras, protocol=protocol, use_direct_calls=use_direct_calls)
429 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
430 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
431 431
432 432 with callback_daemon:
433 433 # TODO: johbo: Implement a clean way to run a config_override
@@ -437,7 +437,7 b' class PullRequestModel(BaseModel):'
437 437 merge_state = target_vcs.merge(
438 438 target_ref, source_vcs, pull_request.source_ref_parts,
439 439 workspace_id, user_name=user.username,
440 user_email=user.email, message=message)
440 user_email=user.email, message=message, use_rebase=use_rebase)
441 441 return merge_state
442 442
443 443 def _comment_and_close_pr(self, pull_request, user, merge_state):
@@ -747,6 +747,12 b' class PullRequestModel(BaseModel):'
747 747
748 748 return ids_to_add, ids_to_remove
749 749
750 def get_url(self, pull_request):
751 return h.url('pullrequest_show',
752 repo_name=safe_str(pull_request.target_repo.repo_name),
753 pull_request_id=pull_request.pull_request_id,
754 qualified=True)
755
750 756 def notify_reviewers(self, pull_request, reviewers_ids):
751 757 # notification to reviewers
752 758 if not reviewers_ids:
@@ -847,6 +853,7 b' class PullRequestModel(BaseModel):'
847 853 f_path=None,
848 854 line_no=None,
849 855 status_change=ChangesetStatus.get_status_lbl(status),
856 status_change_type=status,
850 857 closing_pr=True
851 858 )
852 859
@@ -955,9 +962,10 b' class PullRequestModel(BaseModel):'
955 962 def _refresh_merge_state(self, pull_request, target_vcs, target_reference):
956 963 workspace_id = self._workspace_id(pull_request)
957 964 source_vcs = pull_request.source_repo.scm_instance()
965 use_rebase = self._use_rebase_for_merging(pull_request)
958 966 merge_state = target_vcs.merge(
959 967 target_reference, source_vcs, pull_request.source_ref_parts,
960 workspace_id, dry_run=True)
968 workspace_id, dry_run=True, use_rebase=use_rebase)
961 969
962 970 # Do not store the response if there was an unknown error.
963 971 if merge_state.failure_reason != MergeFailureReason.UNKNOWN:
@@ -1126,6 +1134,11 b' class PullRequestModel(BaseModel):'
1126 1134 settings = settings_model.get_general_settings()
1127 1135 return settings.get('rhodecode_pr_merge_enabled', False)
1128 1136
1137 def _use_rebase_for_merging(self, pull_request):
1138 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
1139 settings = settings_model.get_general_settings()
1140 return settings.get('rhodecode_hg_use_rebase_for_merging', False)
1141
1129 1142 def _log_action(self, action, user, pull_request):
1130 1143 action_logger(
1131 1144 user,
@@ -34,6 +34,7 b' from sqlalchemy.sql import func'
34 34 from sqlalchemy.sql.expression import true, or_
35 35 from zope.cachedescriptors.property import Lazy as LazyProperty
36 36
37 from rhodecode import events
37 38 from rhodecode.lib import helpers as h
38 39 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 40 from rhodecode.lib.caching_query import FromCache
@@ -140,6 +141,10 b' class RepoModel(BaseModel):'
140 141
141 142 return None
142 143
144 def get_url(self, repo):
145 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
146 qualified=True)
147
143 148 def get_users(self, name_contains=None, limit=20, only_active=True):
144 149 # TODO: mikhail: move this method to the UserModel.
145 150 query = self.sa.query(User)
@@ -470,6 +475,8 b' class RepoModel(BaseModel):'
470 475 parent_repo = fork_of
471 476 new_repo.fork = parent_repo
472 477
478 events.trigger(events.RepoPreCreateEvent(new_repo))
479
473 480 self.sa.add(new_repo)
474 481
475 482 EMPTY_PERM = 'repository.none'
@@ -525,11 +532,13 b' class RepoModel(BaseModel):'
525 532 # now automatically start following this repository as owner
526 533 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
527 534 owner.user_id)
535
528 536 # we need to flush here, in order to check if database won't
529 537 # throw any exceptions, create filesystem dirs at the very end
530 538 self.sa.flush()
539 events.trigger(events.RepoCreateEvent(new_repo))
540 return new_repo
531 541
532 return new_repo
533 542 except Exception:
534 543 log.error(traceback.format_exc())
535 544 raise
@@ -633,6 +642,7 b' class RepoModel(BaseModel):'
633 642 raise AttachedForksError()
634 643
635 644 old_repo_dict = repo.get_dict()
645 events.trigger(events.RepoPreDeleteEvent(repo))
636 646 try:
637 647 self.sa.delete(repo)
638 648 if fs_remove:
@@ -644,6 +654,7 b' class RepoModel(BaseModel):'
644 654 'deleted_on': time.time(),
645 655 })
646 656 log_delete_repository(**old_repo_dict)
657 events.trigger(events.RepoDeleteEvent(repo))
647 658 except Exception:
648 659 log.error(traceback.format_exc())
649 660 raise
@@ -33,6 +33,7 b' import traceback'
33 33
34 34 from zope.cachedescriptors.property import Lazy as LazyProperty
35 35
36 from rhodecode import events
36 37 from rhodecode.model import BaseModel
37 38 from rhodecode.model.db import (
38 39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
@@ -257,6 +258,9 b' class RepoGroupModel(BaseModel):'
257 258 log_create_repository_group(
258 259 created_by=user.username, **repo_group.get_dict())
259 260
261 # Trigger create event.
262 events.trigger(events.RepoGroupCreateEvent(repo_group))
263
260 264 return new_repo_group
261 265 except Exception:
262 266 self.sa.rollback()
@@ -455,6 +459,9 b' class RepoGroupModel(BaseModel):'
455 459
456 460 self._rename_group(old_path, new_path)
457 461
462 # Trigger update event.
463 events.trigger(events.RepoGroupUpdateEvent(repo_group))
464
458 465 return repo_group
459 466 except Exception:
460 467 log.error(traceback.format_exc())
@@ -469,6 +476,9 b' class RepoGroupModel(BaseModel):'
469 476 else:
470 477 log.debug('skipping removal from filesystem')
471 478
479 # Trigger delete event.
480 events.trigger(events.RepoGroupDeleteEvent(repo_group))
481
472 482 except Exception:
473 483 log.error('Error removing repo_group %s', repo_group)
474 484 raise
@@ -279,11 +279,7 b' class ScmModel(BaseModel):'
279 279 if repo:
280 280 config = repo._config
281 281 config.set('extensions', 'largefiles', '')
282 cs_cache = None
283 if delete:
284 # if we do a hard clear, reset last-commit to Empty
285 cs_cache = EmptyCommit()
286 repo.update_commit_cache(config=config, cs_cache=cs_cache)
282 repo.update_commit_cache(config=config, cs_cache=None)
287 283 caches.clear_repo_caches(repo_name)
288 284
289 285 def toggle_following_repo(self, follow_repo_id, user_id):
@@ -482,7 +478,7 b' class ScmModel(BaseModel):'
482 478 return data
483 479
484 480 def get_nodes(self, repo_name, commit_id, root_path='/', flat=True,
485 extended_info=False, content=False):
481 extended_info=False, content=False, max_file_bytes=None):
486 482 """
487 483 recursive walk in root dir and return a set of all path in that dir
488 484 based on repository walk function
@@ -490,7 +486,8 b' class ScmModel(BaseModel):'
490 486 :param repo_name: name of repository
491 487 :param commit_id: commit id for which to list nodes
492 488 :param root_path: root path to list
493 :param flat: return as a list, if False returns a dict with decription
489 :param flat: return as a list, if False returns a dict with description
490 :param max_file_bytes: will not return file contents over this limit
494 491
495 492 """
496 493 _files = list()
@@ -503,31 +500,31 b' class ScmModel(BaseModel):'
503 500 for f in files:
504 501 _content = None
505 502 _data = f.unicode_path
503 over_size_limit = (max_file_bytes is not None
504 and f.size > max_file_bytes)
505
506 506 if not flat:
507 507 _data = {
508 508 "name": f.unicode_path,
509 509 "type": "file",
510 510 }
511 511 if extended_info:
512 _content = safe_str(f.content)
513 512 _data.update({
514 "md5": md5(_content),
513 "md5": f.md5,
515 514 "binary": f.is_binary,
516 515 "size": f.size,
517 516 "extension": f.extension,
518
519 517 "mimetype": f.mimetype,
520 518 "lines": f.lines()[0]
521 519 })
520
522 521 if content:
523 522 full_content = None
524 if not f.is_binary:
525 # in case we loaded the _content already
526 # re-use it, or load from f[ile]
527 full_content = _content or safe_str(f.content)
523 if not f.is_binary and not over_size_limit:
524 full_content = safe_str(f.content)
528 525
529 526 _data.update({
530 "content": full_content
527 "content": full_content,
531 528 })
532 529 _files.append(_data)
533 530 for d in dirs:
@@ -198,7 +198,7 b' class SettingsModel(BaseModel):'
198 198 # update if set
199 199 res.app_settings_value = val
200 200
201 Session.add(res)
201 Session().add(res)
202 202 return res
203 203
204 204 def invalidate_settings_cache(self):
@@ -401,7 +401,9 b' class IssueTrackerSettingsModel(object):'
401 401 class VcsSettingsModel(object):
402 402
403 403 INHERIT_SETTINGS = 'inherit_vcs_settings'
404 GENERAL_SETTINGS = ('use_outdated_comments', 'pr_merge_enabled')
404 GENERAL_SETTINGS = (
405 'use_outdated_comments', 'pr_merge_enabled',
406 'hg_use_rebase_for_merging')
405 407 HOOKS_SETTINGS = (
406 408 ('hooks', 'changegroup.repo_size'),
407 409 ('hooks', 'changegroup.push_logger'),
@@ -32,7 +32,7 b' import ipaddress'
32 32 from sqlalchemy.exc import DatabaseError
33 33 from sqlalchemy.sql.expression import true, false
34 34
35 from rhodecode.events import UserPreCreate, UserPreUpdate
35 from rhodecode import events
36 36 from rhodecode.lib.utils2 import (
37 37 safe_unicode, get_current_rhodecode_user, action_logger_generic,
38 38 AttributeDict)
@@ -270,12 +270,12 b' class UserModel(BaseModel):'
270 270 # raises UserCreationError if it's not allowed for any reason to
271 271 # create new active user, this also executes pre-create hooks
272 272 check_allowed_create_user(user_data, cur_user, strict_check=True)
273 self.send_event(UserPreCreate(user_data))
273 events.trigger(events.UserPreCreate(user_data))
274 274 new_user = User()
275 275 edit = False
276 276 else:
277 277 log.debug('updating user %s', username)
278 self.send_event(UserPreUpdate(user, user_data))
278 events.trigger(events.UserPreUpdate(user, user_data))
279 279 new_user = user
280 280 edit = True
281 281
@@ -532,7 +532,7 b' class UserModel(BaseModel):'
532 532
533 533 return True
534 534
535 def reset_password(self, data):
535 def reset_password(self, data, pwd_reset_url):
536 536 from rhodecode.lib.celerylib import tasks, run_task
537 537 from rhodecode.model.notification import EmailNotificationModel
538 538 from rhodecode.lib import auth
@@ -557,6 +557,7 b' class UserModel(BaseModel):'
557 557
558 558 email_kwargs = {
559 559 'new_password': new_passwd,
560 'password_reset_url': pwd_reset_url,
560 561 'user': user,
561 562 'email': user_email,
562 563 'date': datetime.datetime.now()
@@ -970,22 +970,6 b' def FieldKey():'
970 970 return _validator
971 971
972 972
973 def BasePath():
974 class _validator(formencode.validators.FancyValidator):
975 messages = {
976 'badPath': _(u'Filename cannot be inside a directory'),
977 }
978
979 def _to_python(self, value, state):
980 return value
981
982 def validate_python(self, value, state):
983 if value != os.path.basename(value):
984 raise formencode.Invalid(self.message('badPath', state),
985 value, state)
986 return _validator
987
988
989 973 def ValidAuthPlugins():
990 974 class _validator(formencode.validators.FancyValidator):
991 975 messages = {
@@ -1061,26 +1045,6 b' def ValidAuthPlugins():'
1061 1045 return _validator
1062 1046
1063 1047
1064 def UniqGistId():
1065 class _validator(formencode.validators.FancyValidator):
1066 messages = {
1067 'gistid_taken': _(u'This gistid is already in use')
1068 }
1069
1070 def _to_python(self, value, state):
1071 return repo_name_slug(value.lower())
1072
1073 def validate_python(self, value, state):
1074 existing = Gist.get_by_access_id(value)
1075 if existing:
1076 msg = M(self, 'gistid_taken', state)
1077 raise formencode.Invalid(
1078 msg, value, state, error_dict={'gistid': msg}
1079 )
1080
1081 return _validator
1082
1083
1084 1048 def ValidPattern():
1085 1049
1086 1050 class _Validator(formencode.validators.FancyValidator):
@@ -494,16 +494,33 b' div.codeblock {'
494 494 }
495 495
496 496 .code-highlighttable,
497 div.codeblock .code-body table {
498 width: 0 !important;
499 border: 0px !important;
500 margin: 0;
501 letter-spacing: normal;
497 div.codeblock {
498
499 &.readme {
500 background-color: white;
501 }
502
503 .markdown-block table {
504 border-collapse: collapse;
502 505
506 th,
507 td {
508 padding: .5em !important;
509 border: @border-thickness solid @border-default-color !important;
510 }
511 }
503 512
504 td {
513 table {
514 width: 0 !important;
505 515 border: 0px !important;
506 vertical-align: top;
516 margin: 0;
517 letter-spacing: normal;
518
519
520 td {
521 border: 0px !important;
522 vertical-align: top;
523 }
507 524 }
508 525 }
509 526
@@ -165,7 +165,7 b' div.CodeMirror span.CodeMirror-nonmatchi'
165 165 }
166 166
167 167 /* The fake, visible scrollbars. Used to force redraw during scrolling
168 before actuall scrolling happens, thus preventing shaking and
168 before actual scrolling happens, thus preventing shaking and
169 169 flickering artifacts. */
170 170 .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
171 171 position: absolute;
@@ -207,6 +207,11 b' div.CodeMirror span.CodeMirror-nonmatchi'
207 207 z-index: 4;
208 208 height: 100%;
209 209 }
210 .CodeMirror-gutter-background {
211 position: absolute;
212 top: 0; bottom: 0;
213 z-index: 4;
214 }
210 215 .CodeMirror-gutter-elt {
211 216 position: absolute;
212 217 cursor: default;
@@ -280,7 +285,7 b' div.CodeMirror span.CodeMirror-nonmatchi'
280 285 overflow: hidden;
281 286 visibility: hidden;
282 287 }
283 .CodeMirror-measure pre { position: static; }
288
284 289
285 290 .CodeMirror div.CodeMirror-cursor {
286 291 position: absolute;
@@ -288,11 +293,17 b' div.CodeMirror span.CodeMirror-nonmatchi'
288 293 width: 0;
289 294 }
290 295
296 .CodeMirror-measure pre { position: static; }
297
291 298 div.CodeMirror-cursors {
292 299 visibility: hidden;
293 300 position: relative;
294 301 z-index: 3;
295 302 }
303 div.CodeMirror-dragcursors {
304 visibility: visible;
305 }
306
296 307 .CodeMirror-focused div.CodeMirror-cursors {
297 308 visibility: visible;
298 309 }
@@ -300,8 +311,8 b' div.CodeMirror-cursors {'
300 311 .CodeMirror-selected { background: #d9d9d9; }
301 312 .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
302 313 .CodeMirror-crosshair { cursor: crosshair; }
303 .CodeMirror ::selection { background: #d7d4f0; }
304 .CodeMirror ::-moz-selection { background: #d7d4f0; }
314 .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
315 .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
305 316
306 317 .cm-searching {
307 318 background: #ffa;
@@ -177,8 +177,7 b' div.markdown-block p,'
177 177 div.markdown-block blockquote,
178 178 div.markdown-block dl,
179 179 div.markdown-block li,
180 div.markdown-block table,
181 div.markdown-block pre {
180 div.markdown-block table {
182 181 margin: 15px 0 !important;
183 182 margin: 3px 0px 13px 0px !important;
184 183 color: #424242 !important;
@@ -189,6 +188,17 b' div.markdown-block pre {'
189 188 line-height: 140% !important;
190 189 }
191 190
191 div.markdown-block pre {
192 margin: 15px 0 !important;
193 margin: 3px 0px 13px 0px !important;
194 padding: .5em;
195 color: #424242 !important;
196 font-size: 13px !important;
197 overflow: visible !important;
198 line-height: 140% !important;
199 background-color: @grey7;
200 }
201
192 202 div.markdown-block img {
193 203 padding: 4px;
194 204 border: @border-thickness solid @grey5;
@@ -265,7 +275,8 b' div.markdown-block code {'
265 275 div.markdown-block pre {
266 276 border: @border-thickness solid @grey5;
267 277 overflow: auto;
268 padding: 4px 8px;
278 padding: .5em;
279 background-color: @grey7;
269 280 }
270 281
271 282 div.markdown-block pre > code {
@@ -25,6 +25,8 b''
25 25 @import 'comments';
26 26 @import 'panels-bootstrap';
27 27 @import 'panels';
28 @import 'toastr';
29 @import 'deform';
28 30
29 31
30 32 //--- BASE ------------------//
@@ -141,7 +143,7 b' input.inline[type="file"] {'
141 143 h1 {
142 144 color: @grey2;
143 145 }
144
146
145 147 .error-branding {
146 148 font-family: @text-semibold;
147 149 color: @grey4;
@@ -310,13 +312,19 b' ul.auth_plugins {'
310 312 .td-status {
311 313 padding-left: .5em;
312 314 }
313 .truncate {
315 .log-container .truncate {
314 316 height: 2.75em;
315 317 white-space: pre-line;
316 318 }
317 319 table.rctable .user {
318 320 padding-left: 0;
319 321 }
322 table.rctable {
323 td.td-description,
324 .rc-user {
325 min-width: auto;
326 }
327 }
320 328 }
321 329
322 330 // Pull Requests
@@ -670,6 +678,13 b' label {'
670 678 }
671 679 }
672 680
681 .user-inline-data {
682 display: inline-block;
683 float: left;
684 padding-left: .5em;
685 line-height: 1.3em;
686 }
687
673 688 .rc-user { // gravatar + user wrapper
674 689 float: left;
675 690 position: relative;
@@ -1009,9 +1024,9 b' label {'
1009 1024 padding: .9em;
1010 1025 color: @grey3;
1011 1026 background-color: @grey7;
1012 border-right: @border-thickness solid @border-default-color;
1013 border-bottom: @border-thickness solid @border-default-color;
1014 border-left: @border-thickness solid @border-default-color;
1027 border-right: @border-thickness solid @border-default-color;
1028 border-bottom: @border-thickness solid @border-default-color;
1029 border-left: @border-thickness solid @border-default-color;
1015 1030 }
1016 1031
1017 1032 #repo_vcs_settings {
@@ -1601,6 +1616,10 b' BIN_FILENODE = 7'
1601 1616 float: right;
1602 1617 }
1603 1618
1619 #notification-status{
1620 display: inline;
1621 }
1622
1604 1623 // Repositories
1605 1624
1606 1625 #summary.fields{
@@ -1864,15 +1883,6 b' h3.files_location{'
1864 1883 }
1865 1884 }
1866 1885
1867 .file_author{
1868 margin-bottom: @padding;
1869
1870 div{
1871 display: inline-block;
1872 margin-right: 0.5em;
1873 }
1874 }
1875
1876 1886 .browser-cur-rev{
1877 1887 margin-bottom: @textmargin;
1878 1888 }
@@ -1949,7 +1959,7 b' div.search-feedback-items {'
1949 1959 padding:0px 0px 0px 96px;
1950 1960 }
1951 1961
1952 div.search-code-body {
1962 div.search-code-body {
1953 1963 background-color: #ffffff; padding: 5px 0 5px 10px;
1954 1964 pre {
1955 1965 .match { background-color: #faffa6;}
@@ -1,4 +1,3 b''
1
2 1 /* required */
3 2 .mergely-column textarea { width: 80px; height: 200px; }
4 3 .mergely-column { float: left; }
@@ -12,17 +11,19 b''
12 11 .mergely-column { border: 1px solid #ccc; }
13 12 .mergely-active { border: 1px solid #a3d1ff; }
14 13
15 .mergely.a.rhs.start { border-top: 1px solid #ddffdd; }
16 .mergely.a.lhs.start.end,
17 .mergely.a.rhs.end { border-bottom: 1px solid #ddffdd; }
18 .mergely.a.rhs { background-color: #ddffdd; }
19 .mergely.a.lhs.start.end.first { border-bottom: 0; border-top: 1px solid #ddffdd; }
14 .mergely.a,.mergely.d,.mergely.c { color: #000; }
20 15
21 .mergely.d.lhs { background-color: #edc0c0; }
16 .mergely.a.rhs.start { border-top: 1px solid #a3d1ff; }
17 .mergely.a.lhs.start.end,
18 .mergely.a.rhs.end { border-bottom: 1px solid #a3d1ff; }
19 .mergely.a.rhs { background-color: #ddeeff; }
20 .mergely.a.lhs.start.end.first { border-bottom: 0; border-top: 1px solid #a3d1ff; }
21
22 .mergely.d.lhs { background-color: #ffe9e9; }
22 23 .mergely.d.lhs.end,
23 .mergely.d.rhs.start.end { border-bottom: 1px solid #ffdddd; }
24 .mergely.d.rhs.start.end.first { border-bottom: 0; border-top: 1px solid #ffdddd; }
25 .mergely.d.lhs.start { border-top: 1px solid #ffdddd; }
24 .mergely.d.rhs.start.end { border-bottom: 1px solid #f8e8e8; }
25 .mergely.d.rhs.start.end.first { border-bottom: 0; border-top: 1px solid #f8e8e8; }
26 .mergely.d.lhs.start { border-top: 1px solid #f8e8e8; }
26 27
27 28 .mergely.c.lhs,
28 29 .mergely.c.rhs { background-color: #fafafa; }
@@ -31,11 +32,19 b''
31 32 .mergely.c.lhs.end,
32 33 .mergely.c.rhs.end { border-bottom: 1px solid #a3a3a3; }
33 34
34 .mergely.ch.a.rhs { background-color: #ddffdd; }
35 .mergely.ch.d.lhs { background-color: #ffdddd; }
36
35 .mergely.ch.a.rhs { background-color: #ddeeff; }
36 .mergely.ch.d.lhs { background-color: #ffe9e9; text-decoration: line-through; color: red !important; }
37 37
38 38 .mergely-margin #compare-lhs-margin,
39 39 .mergely-margin #compare-rhs-margin {
40 40 cursor: pointer
41 41 }
42
43 .mergely.current.start { border-top: 1px solid #000 !important; }
44 .mergely.current.end { border-bottom: 1px solid #000 !important; }
45 .mergely.current.lhs.a.start.end,
46 .mergely.current.rhs.d.start.end { border-top: 0 !important; }
47 .mergely.current.CodeMirror-linenumber { color: #F9F9F9; font-weight: bold; background-color: #777; }
48
49 .CodeMirror-linenumber { cursor: pointer; }
50 .CodeMirror-code { color: #717171; } No newline at end of file
@@ -138,6 +138,10 b''
138 138
139 139 .summary .sidebar-right-content {
140 140 margin-bottom: @space;
141
142 .rc-user {
143 min-width: 0;
144 }
141 145 }
142 146
143 147 .fieldset {
@@ -168,6 +172,10 b''
168 172 overflow-x: auto;
169 173 }
170 174 }
175 .commit.truncate-wrap {
176 overflow:hidden;
177 text-overflow: ellipsis;
178 }
171 179 }
172 180
173 181 // expand commit message
@@ -254,15 +262,3 b''
254 262
255 263 }
256 264
257 #readme {
258 width: 100%;
259
260 .readme {
261 overflow-x: auto;
262 border: @border-thickness solid @border-default-color;
263 .border-radius(@border-radius);
264 }
265 .readme_box {
266 margin: 15px;
267 }
268 }
@@ -123,11 +123,6 b' table.dataTable {'
123 123 }
124 124 }
125 125
126 &.td-gravatar {
127 width: 16px;
128 padding: 0 5px;
129 }
130
131 126 &.tags-col {
132 127 padding-right: 0;
133 128 }
@@ -297,15 +297,15 b' jQuery.extend(Mgly.diff.prototype, {'
297 297 },
298 298 _optimize: function(ctx) {
299 299 var start = 0, end = 0;
300 while (start < ctx.length) {
301 while ((start < ctx.length) && (ctx.modified[start] == undefined || ctx.modified[start] == false)) {
300 while (start < ctx.codes.length) {
301 while ((start < ctx.codes.length) && (ctx.modified[start] == undefined || ctx.modified[start] == false)) {
302 302 start++;
303 303 }
304 304 end = start;
305 while ((end < ctx.length) && (ctx.modified[end] == true)) {
305 while ((end < ctx.codes.length) && (ctx.modified[end] == true)) {
306 306 end++;
307 307 }
308 if ((end < ctx.length) && (ctx.ctx[start] == ctx.codes[end])) {
308 if ((end < ctx.codes.length) && (ctx.codes[start] == ctx.codes[end])) {
309 309 ctx.modified[start] = false;
310 310 ctx.modified[end] = true;
311 311 }
@@ -438,8 +438,7 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
438 438 if (this.resized) this.resized();
439 439 },
440 440 _debug: '', //scroll,draw,calc,diff,markup,change
441 resized: function() { },
442 finished: function () { }
441 resized: function() { }
443 442 };
444 443 var cmsettings = {
445 444 mode: 'text/plain',
@@ -497,7 +496,7 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
497 496 if (direction == 'next') {
498 497 this._current_diff = Math.min(++this._current_diff, this.changes.length - 1);
499 498 }
500 else {
499 else if (direction == 'prev') {
501 500 this._current_diff = Math.max(--this._current_diff, 0);
502 501 }
503 502 this._scroll_to_change(this.changes[this._current_diff]);
@@ -540,10 +539,10 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
540 539 if (this.settings.hasOwnProperty('sidebar')) {
541 540 // dynamically enable sidebars
542 541 if (this.settings.sidebar) {
543 jQuery(this.element).find('.mergely-margin').css({display: 'block'});
542 this.element.find('.mergely-margin').css({display: 'block'});
544 543 }
545 544 else {
546 jQuery(this.element).find('.mergely-margin').css({display: 'none'});
545 this.element.find('.mergely-margin').css({display: 'none'});
547 546 }
548 547 }
549 548 var le, re;
@@ -690,12 +689,12 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
690 689 jQuery('<style type="text/css">' + cmstyle + '</style>').appendTo('head');
691 690
692 691 //bind
693 var rhstx = jQuery('#' + this.id + '-rhs').get(0);
692 var rhstx = this.element.find('#' + this.id + '-rhs').get(0);
694 693 if (!rhstx) {
695 694 console.error('rhs textarea not defined - Mergely not initialized properly');
696 695 return;
697 696 }
698 var lhstx = jQuery('#' + this.id + '-lhs').get(0);
697 var lhstx = this.element.find('#' + this.id + '-lhs').get(0);
699 698 if (!rhstx) {
700 699 console.error('lhs textarea not defined - Mergely not initialized properly');
701 700 return;
@@ -728,6 +727,38 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
728 727 );
729 728 sz(true);
730 729 }
730
731 // scrollToDiff() from gutter
732 function gutterClicked(side, line, ev) {
733 // The "Merge left/right" buttons are also located in the gutter.
734 // Don't interfere with them:
735 if (ev.target && (jQuery(ev.target).closest('.merge-button').length > 0)) {
736 return;
737 }
738
739 // See if the user clicked the line number of a difference:
740 var i, change;
741 for (i = 0; i < this.changes.length; i++) {
742 change = this.changes[i];
743 if (line >= change[side+'-line-from'] && line <= change[side+'-line-to']) {
744 this._current_diff = i;
745 // I really don't like this here - something about gutterClick does not
746 // like mutating editor here. Need to trigger the scroll to diff from
747 // a timeout.
748 setTimeout(function() { this.scrollToDiff(); }.bind(this), 10);
749 break;
750 }
751 }
752 }
753
754 this.editor[this.id + '-lhs'].on('gutterClick', function(cm, n, gutterClass, ev) {
755 gutterClicked.call(this, 'lhs', n, ev);
756 }.bind(this));
757
758 this.editor[this.id + '-rhs'].on('gutterClick', function(cm, n, gutterClass, ev) {
759 gutterClicked.call(this, 'rhs', n, ev);
760 }.bind(this));
761
731 762 //bind
732 763 var setv;
733 764 if (this.settings.lhs) {
@@ -745,23 +776,10 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
745 776 var self = this;
746 777 var led = self.editor[self.id+'-lhs'];
747 778 var red = self.editor[self.id+'-rhs'];
748
749 var yref = led.getScrollerElement().offsetHeight * 0.5; // center between >0 and 1/2
750
751 779 // set cursors
752 780 led.setCursor(Math.max(change["lhs-line-from"],0), 0); // use led.getCursor().ch ?
753 781 red.setCursor(Math.max(change["rhs-line-from"],0), 0);
754
755 // using directly CodeMirror breaks canvas alignment
756 // var ly = led.charCoords({line: Math.max(change["lhs-line-from"],0), ch: 0}, "local").top;
757
758 // calculate scroll offset for current change. Warning: returns relative y position so we scroll to 0 first.
759 led.scrollTo(null, 0);
760 red.scrollTo(null, 0);
761 self._calculate_offsets(self.id+'-lhs', self.id+'-rhs', [change]);
762 led.scrollTo(null, Math.max(change["lhs-y-start"]-yref, 0));
763 red.scrollTo(null, Math.max(change["rhs-y-start"]-yref, 0));
764 // right pane should simply follows
782 led.scrollIntoView({line: change["lhs-line-to"]});
765 783 },
766 784
767 785 _scrolling: function(editor_name) {
@@ -922,13 +940,11 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
922 940 this.trace('change', 'diff time', timer.stop());
923 941 this.changes = Mgly.DiffParser(d.normal_form());
924 942 this.trace('change', 'parse time', timer.stop());
925
926 943 if (this._current_diff === undefined && this.changes.length) {
927 944 // go to first difference on start-up
928 945 this._current_diff = 0;
929 946 this._scroll_to_change(this.changes[0]);
930 947 }
931
932 948 this.trace('change', 'scroll_to_change time', timer.stop());
933 949 this._calculate_offsets(editor_name1, editor_name2, this.changes);
934 950 this.trace('change', 'offsets time', timer.stop());
@@ -992,7 +1008,7 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
992 1008
993 1009 // this is the distance from the top of the screen to the top of the
994 1010 // content of the first codemirror editor
995 var topnode = jQuery('#' + this.id + ' .CodeMirror-measure').first();
1011 var topnode = this.element.find('.CodeMirror-measure').first();
996 1012 var top_offset = topnode.offset().top - 4;
997 1013 if(!top_offset) return false;
998 1014
@@ -1121,11 +1137,12 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1121 1137 return changes;
1122 1138 },
1123 1139 _markup_changes: function (editor_name1, editor_name2, changes) {
1124 jQuery('.merge-button').remove(); // clear
1140 this.element.find('.merge-button').remove(); //clear
1125 1141
1126 1142 var self = this;
1127 1143 var led = this.editor[editor_name1];
1128 1144 var red = this.editor[editor_name2];
1145 var current_diff = this._current_diff;
1129 1146
1130 1147 var timer = new Mgly.Timer();
1131 1148 led.operation(function() {
@@ -1140,6 +1157,12 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1140 1157 led.addLineClass(llf, 'background', 'start');
1141 1158 led.addLineClass(llt, 'background', 'end');
1142 1159
1160 if (current_diff == i) {
1161 if (llf != llt) {
1162 led.addLineClass(llf, 'background', 'current');
1163 }
1164 led.addLineClass(llt, 'background', 'current');
1165 }
1143 1166 if (llf == 0 && llt == 0 && rlf == 0) {
1144 1167 led.addLineClass(llf, 'background', clazz.join(' '));
1145 1168 led.addLineClass(llf, 'background', 'first');
@@ -1186,6 +1209,12 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1186 1209 red.addLineClass(rlf, 'background', 'start');
1187 1210 red.addLineClass(rlt, 'background', 'end');
1188 1211
1212 if (current_diff == i) {
1213 if (rlf != rlt) {
1214 red.addLineClass(rlf, 'background', 'current');
1215 }
1216 red.addLineClass(rlt, 'background', 'current');
1217 }
1189 1218 if (rlf == 0 && rlt == 0 && llf == 0) {
1190 1219 red.addLineClass(rlf, 'background', clazz.join(' '));
1191 1220 red.addLineClass(rlf, 'background', 'first');
@@ -1286,11 +1315,12 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1286 1315 self.chfns[self.id + '-rhs'].push(m[0].markText(m[1], m[2], m[3]));
1287 1316 }
1288 1317 });
1318
1289 1319 this.trace('change', 'LCS markup time', timer.stop());
1290 1320
1291 1321 // merge buttons
1292 1322 var ed = {lhs:led, rhs:red};
1293 jQuery('.merge-button').on('click', function(ev){
1323 this.element.find('.merge-button').on('click', function(ev){
1294 1324 // side of mouseenter
1295 1325 var side = 'rhs';
1296 1326 var oside = 'lhs';
@@ -1314,6 +1344,35 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1314 1344 self._merge_change(change, side, oside);
1315 1345 return false;
1316 1346 });
1347
1348 // gutter markup
1349 var lhsLineNumbers = $('#mergely-lhs ~ .CodeMirror').find('.CodeMirror-linenumber');
1350 var rhsLineNumbers = $('#mergely-rhs ~ .CodeMirror').find('.CodeMirror-linenumber');
1351 rhsLineNumbers.removeClass('mergely current');
1352 lhsLineNumbers.removeClass('mergely current');
1353 for (var i = 0; i < changes.length; ++i) {
1354 if (current_diff == i && change.op !== 'd') {
1355 var change = changes[i];
1356 var j, jf = change['rhs-line-from'], jt = change['rhs-line-to'] + 1;
1357 for (j = jf; j < jt; j++) {
1358 var n = (j + 1).toString();
1359 rhsLineNumbers
1360 .filter(function(i, node) { return $(node).text() === n; })
1361 .addClass('mergely current');
1362 }
1363 }
1364 if (current_diff == i && change.op !== 'a') {
1365 var change = changes[i];
1366 jf = change['lhs-line-from'], jt = change['lhs-line-to'] + 1;
1367 for (j = jf; j < jt; j++) {
1368 var n = (j + 1).toString();
1369 lhsLineNumbers
1370 .filter(function(i, node) { return $(node).text() === n; })
1371 .addClass('mergely current');
1372 }
1373 }
1374 }
1375
1317 1376 this.trace('change', 'markup buttons time', timer.stop());
1318 1377 },
1319 1378 _merge_change : function(change, side, oside) {
@@ -1373,8 +1432,8 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1373 1432 var gutter_height = jQuery(this.editor[editor_name1].getScrollerElement()).children(':first-child').height();
1374 1433 var dcanvas = document.getElementById(editor_name1 + '-' + editor_name2 + '-canvas');
1375 1434 if (dcanvas == undefined) throw 'Failed to find: ' + editor_name1 + '-' + editor_name2 + '-canvas';
1376 var clhs = jQuery('#' + this.id + '-lhs-margin');
1377 var crhs = jQuery('#' + this.id + '-rhs-margin');
1435 var clhs = this.element.find('#' + this.id + '-lhs-margin');
1436 var crhs = this.element.find('#' + this.id + '-rhs-margin');
1378 1437 return {
1379 1438 visible_page_height: visible_page_height,
1380 1439 gutter_height: gutter_height,
@@ -1405,7 +1464,7 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1405 1464 this.trace('draw', 'lhs-scroller-top', ex.lhs_scroller.scrollTop());
1406 1465 this.trace('draw', 'rhs-scroller-top', ex.rhs_scroller.scrollTop());
1407 1466
1408 jQuery.each(jQuery.find('#' + this.id + ' canvas'), function () {
1467 jQuery.each(this.element.find('canvas'), function () {
1409 1468 jQuery(this).get(0).height = ex.visible_page_height;
1410 1469 });
1411 1470
@@ -1427,6 +1486,10 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1427 1486 var vp = this._get_viewport(editor_name1, editor_name2);
1428 1487 for (var i = 0; i < changes.length; ++i) {
1429 1488 var change = changes[i];
1489 var fill = this.settings.fgcolor[change['op']];
1490 if (this._current_diff==i) {
1491 fill = '#000';
1492 }
1430 1493
1431 1494 this.trace('draw', change);
1432 1495 // margin indicators
@@ -1437,14 +1500,14 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1437 1500 this.trace('draw', 'marker calculated', lhs_y_start, lhs_y_end, rhs_y_start, rhs_y_end);
1438 1501
1439 1502 ctx_lhs.beginPath();
1440 ctx_lhs.fillStyle = this.settings.fgcolor[(this._current_diff==i?'c':'')+change['op']];
1503 ctx_lhs.fillStyle = fill;
1441 1504 ctx_lhs.strokeStyle = '#000';
1442 1505 ctx_lhs.lineWidth = 0.5;
1443 1506 ctx_lhs.fillRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5));
1444 1507 ctx_lhs.strokeRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5));
1445 1508
1446 1509 ctx_rhs.beginPath();
1447 ctx_rhs.fillStyle = this.settings.fgcolor[(this._current_diff==i?'c':'')+change['op']];
1510 ctx_rhs.fillStyle = fill;
1448 1511 ctx_rhs.strokeStyle = '#000';
1449 1512 ctx_rhs.lineWidth = 0.5;
1450 1513 ctx_rhs.fillRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5));
@@ -1463,7 +1526,7 b' jQuery.extend(Mgly.CodeMirrorDiffView.pr'
1463 1526
1464 1527 // draw left box
1465 1528 ctx.beginPath();
1466 ctx.strokeStyle = this.settings.fgcolor[(this._current_diff==i?'c':'')+change['op']];
1529 ctx.strokeStyle = fill;
1467 1530 ctx.lineWidth = (this._current_diff==i) ? 1.5 : 1;
1468 1531
1469 1532 var rectWidth = this.draw_lhs_width;
@@ -25,8 +25,12 b' CodeMirror.defineMode("clike", function('
25 25 multiLineStrings = parserConfig.multiLineStrings,
26 26 indentStatements = parserConfig.indentStatements !== false,
27 27 indentSwitch = parserConfig.indentSwitch !== false,
28 namespaceSeparator = parserConfig.namespaceSeparator;
29 var isOperatorChar = /[+\-*&%=<>!?|\/]/;
28 namespaceSeparator = parserConfig.namespaceSeparator,
29 isPunctuationChar = parserConfig.isPunctuationChar || /[\[\]{}\(\),;\:\.]/,
30 numberStart = parserConfig.numberStart || /[\d\.]/,
31 number = parserConfig.number || /^(?:0x[a-f\d]+|0b[01]+|(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)(u|ll?|l|f)?/i,
32 isOperatorChar = parserConfig.isOperatorChar || /[+\-*&%=<>!?|\/]/,
33 endStatement = parserConfig.endStatement || /^[;:,]$/;
30 34
31 35 var curPunc, isDefKeyword;
32 36
@@ -40,13 +44,14 b' CodeMirror.defineMode("clike", function('
40 44 state.tokenize = tokenString(ch);
41 45 return state.tokenize(stream, state);
42 46 }
43 if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
47 if (isPunctuationChar.test(ch)) {
44 48 curPunc = ch;
45 49 return null;
46 50 }
47 if (/\d/.test(ch)) {
48 stream.eatWhile(/[\w\.]/);
49 return "number";
51 if (numberStart.test(ch)) {
52 stream.backUp(1)
53 if (stream.match(number)) return "number"
54 stream.next()
50 55 }
51 56 if (ch == "/") {
52 57 if (stream.eat("*")) {
@@ -67,17 +72,17 b' CodeMirror.defineMode("clike", function('
67 72 stream.eatWhile(/[\w\$_\xa1-\uffff]/);
68 73
69 74 var cur = stream.current();
70 if (keywords.propertyIsEnumerable(cur)) {
71 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
72 if (defKeywords.propertyIsEnumerable(cur)) isDefKeyword = true;
75 if (contains(keywords, cur)) {
76 if (contains(blockKeywords, cur)) curPunc = "newstatement";
77 if (contains(defKeywords, cur)) isDefKeyword = true;
73 78 return "keyword";
74 79 }
75 if (types.propertyIsEnumerable(cur)) return "variable-3";
76 if (builtin.propertyIsEnumerable(cur)) {
77 if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
80 if (contains(types, cur)) return "variable-3";
81 if (contains(builtin, cur)) {
82 if (contains(blockKeywords, cur)) curPunc = "newstatement";
78 83 return "builtin";
79 84 }
80 if (atoms.propertyIsEnumerable(cur)) return "atom";
85 if (contains(atoms, cur)) return "atom";
81 86 return "variable";
82 87 }
83 88
@@ -168,8 +173,7 b' CodeMirror.defineMode("clike", function('
168 173 if (style == "comment" || style == "meta") return style;
169 174 if (ctx.align == null) ctx.align = true;
170 175
171 if ((curPunc == ";" || curPunc == ":" || curPunc == ","))
172 while (isStatement(state.context.type)) popContext(state);
176 if (endStatement.test(curPunc)) while (isStatement(state.context.type)) popContext(state);
173 177 else if (curPunc == "{") pushContext(state, stream.column(), "}");
174 178 else if (curPunc == "[") pushContext(state, stream.column(), "]");
175 179 else if (curPunc == "(") pushContext(state, stream.column(), ")");
@@ -212,8 +216,16 b' CodeMirror.defineMode("clike", function('
212 216 if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass;
213 217 var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
214 218 if (isStatement(ctx.type) && firstChar == "}") ctx = ctx.prev;
219 if (hooks.indent) {
220 var hook = hooks.indent(state, ctx, textAfter);
221 if (typeof hook == "number") return hook
222 }
215 223 var closing = firstChar == ctx.type;
216 224 var switchBlock = ctx.prev && ctx.prev.type == "switchstatement";
225 if (parserConfig.allmanIndentation && /[{(]/.test(firstChar)) {
226 while (ctx.type != "top" && ctx.type != "}") ctx = ctx.prev
227 return ctx.indented
228 }
217 229 if (isStatement(ctx.type))
218 230 return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit);
219 231 if (ctx.align && (!dontAlignCalls || ctx.type != ")"))
@@ -238,27 +250,30 b' CodeMirror.defineMode("clike", function('
238 250 for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
239 251 return obj;
240 252 }
253 function contains(words, word) {
254 if (typeof words === "function") {
255 return words(word);
256 } else {
257 return words.propertyIsEnumerable(word);
258 }
259 }
241 260 var cKeywords = "auto if break case register continue return default do sizeof " +
242 "static else struct switch extern typedef float union for " +
243 "goto while enum const volatile";
261 "static else struct switch extern typedef union for goto while enum const volatile";
244 262 var cTypes = "int long char short double float unsigned signed void size_t ptrdiff_t";
245 263
246 264 function cppHook(stream, state) {
247 if (!state.startOfLine) return false;
248 for (;;) {
249 if (stream.skipTo("\\")) {
250 stream.next();
251 if (stream.eol()) {
252 state.tokenize = cppHook;
253 break;
254 }
255 } else {
256 stream.skipToEnd();
257 state.tokenize = null;
258 break;
265 if (!state.startOfLine) return false
266 for (var ch, next = null; ch = stream.peek();) {
267 if (ch == "\\" && stream.match(/^.$/)) {
268 next = cppHook
269 break
270 } else if (ch == "/" && stream.match(/^\/[\/\*]/, false)) {
271 break
259 272 }
273 stream.next()
260 274 }
261 return "meta";
275 state.tokenize = next
276 return "meta"
262 277 }
263 278
264 279 function pointerHook(_stream, state) {
@@ -266,6 +281,11 b' CodeMirror.defineMode("clike", function('
266 281 return false;
267 282 }
268 283
284 function cpp14Literal(stream) {
285 stream.eatWhile(/[\w\.']/);
286 return "number";
287 }
288
269 289 function cpp11StringHook(stream, state) {
270 290 stream.backUp(1);
271 291 // Raw strings.
@@ -373,6 +393,16 b' CodeMirror.defineMode("clike", function('
373 393 "U": cpp11StringHook,
374 394 "L": cpp11StringHook,
375 395 "R": cpp11StringHook,
396 "0": cpp14Literal,
397 "1": cpp14Literal,
398 "2": cpp14Literal,
399 "3": cpp14Literal,
400 "4": cpp14Literal,
401 "5": cpp14Literal,
402 "6": cpp14Literal,
403 "7": cpp14Literal,
404 "8": cpp14Literal,
405 "9": cpp14Literal,
376 406 token: function(stream, state, style) {
377 407 if (style == "variable" && stream.peek() == "(" &&
378 408 (state.prevToken == ";" || state.prevToken == null ||
@@ -398,6 +428,7 b' CodeMirror.defineMode("clike", function('
398 428 defKeywords: words("class interface package enum"),
399 429 typeFirstDefinitions: true,
400 430 atoms: words("true false null"),
431 endStatement: /^[;:]$/,
401 432 hooks: {
402 433 "@": function(stream) {
403 434 stream.eatWhile(/[\w\$_]/);
@@ -453,7 +484,7 b' CodeMirror.defineMode("clike", function('
453 484 keywords: words(
454 485
455 486 /* scala */
456 "abstract case catch class def do else extends false final finally for forSome if " +
487 "abstract case catch class def do else extends final finally for forSome if " +
457 488 "implicit import lazy match new null object override package private protected return " +
458 489 "sealed super this throw trait try type val var while with yield _ : = => <- <: " +
459 490 "<% >: # @ " +
@@ -501,6 +532,59 b' CodeMirror.defineMode("clike", function('
501 532 modeProps: {closeBrackets: {triples: '"'}}
502 533 });
503 534
535 function tokenKotlinString(tripleString){
536 return function (stream, state) {
537 var escaped = false, next, end = false;
538 while (!stream.eol()) {
539 if (!tripleString && !escaped && stream.match('"') ) {end = true; break;}
540 if (tripleString && stream.match('"""')) {end = true; break;}
541 next = stream.next();
542 if(!escaped && next == "$" && stream.match('{'))
543 stream.skipTo("}");
544 escaped = !escaped && next == "\\" && !tripleString;
545 }
546 if (end || !tripleString)
547 state.tokenize = null;
548 return "string";
549 }
550 }
551
552 def("text/x-kotlin", {
553 name: "clike",
554 keywords: words(
555 /*keywords*/
556 "package as typealias class interface this super val " +
557 "var fun for is in This throw return " +
558 "break continue object if else while do try when !in !is as? " +
559
560 /*soft keywords*/
561 "file import where by get set abstract enum open inner override private public internal " +
562 "protected catch finally out final vararg reified dynamic companion constructor init " +
563 "sealed field property receiver param sparam lateinit data inline noinline tailrec " +
564 "external annotation crossinline const operator infix"
565 ),
566 types: words(
567 /* package java.lang */
568 "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " +
569 "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " +
570 "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
571 "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
572 ),
573 intendSwitch: false,
574 indentStatements: false,
575 multiLineStrings: true,
576 blockKeywords: words("catch class do else finally for if where try while enum"),
577 defKeywords: words("class val var object package interface fun"),
578 atoms: words("true false null this"),
579 hooks: {
580 '"': function(stream, state) {
581 state.tokenize = tokenKotlinString(stream.match('""'));
582 return state.tokenize(stream, state);
583 }
584 },
585 modeProps: {closeBrackets: {triples: '"'}}
586 });
587
504 588 def(["x-shader/x-vertex", "x-shader/x-fragment"], {
505 589 name: "clike",
506 590 keywords: words("sampler1D sampler2D sampler3D samplerCube " +
@@ -583,9 +667,106 b' CodeMirror.defineMode("clike", function('
583 667 stream.eatWhile(/[\w\$]/);
584 668 return "keyword";
585 669 },
586 "#": cppHook
670 "#": cppHook,
671 indent: function(_state, ctx, textAfter) {
672 if (ctx.type == "statement" && /^@\w/.test(textAfter)) return ctx.indented
673 }
587 674 },
588 675 modeProps: {fold: "brace"}
589 676 });
590 677
678 def("text/x-squirrel", {
679 name: "clike",
680 keywords: words("base break clone continue const default delete enum extends function in class" +
681 " foreach local resume return this throw typeof yield constructor instanceof static"),
682 types: words(cTypes),
683 blockKeywords: words("case catch class else for foreach if switch try while"),
684 defKeywords: words("function local class"),
685 typeFirstDefinitions: true,
686 atoms: words("true false null"),
687 hooks: {"#": cppHook},
688 modeProps: {fold: ["brace", "include"]}
689 });
690
691 // Ceylon Strings need to deal with interpolation
692 var stringTokenizer = null;
693 function tokenCeylonString(type) {
694 return function(stream, state) {
695 var escaped = false, next, end = false;
696 while (!stream.eol()) {
697 if (!escaped && stream.match('"') &&
698 (type == "single" || stream.match('""'))) {
699 end = true;
700 break;
701 }
702 if (!escaped && stream.match('``')) {
703 stringTokenizer = tokenCeylonString(type);
704 end = true;
705 break;
706 }
707 next = stream.next();
708 escaped = type == "single" && !escaped && next == "\\";
709 }
710 if (end)
711 state.tokenize = null;
712 return "string";
713 }
714 }
715
716 def("text/x-ceylon", {
717 name: "clike",
718 keywords: words("abstracts alias assembly assert assign break case catch class continue dynamic else" +
719 " exists extends finally for function given if import in interface is let module new" +
720 " nonempty object of out outer package return satisfies super switch then this throw" +
721 " try value void while"),
722 types: function(word) {
723 // In Ceylon all identifiers that start with an uppercase are types
724 var first = word.charAt(0);
725 return (first === first.toUpperCase() && first !== first.toLowerCase());
726 },
727 blockKeywords: words("case catch class dynamic else finally for function if interface module new object switch try while"),
728 defKeywords: words("class dynamic function interface module object package value"),
729 builtin: words("abstract actual aliased annotation by default deprecated doc final formal late license" +
730 " native optional sealed see serializable shared suppressWarnings tagged throws variable"),
731 isPunctuationChar: /[\[\]{}\(\),;\:\.`]/,
732 isOperatorChar: /[+\-*&%=<>!?|^~:\/]/,
733 numberStart: /[\d#$]/,
734 number: /^(?:#[\da-fA-F_]+|\$[01_]+|[\d_]+[kMGTPmunpf]?|[\d_]+\.[\d_]+(?:[eE][-+]?\d+|[kMGTPmunpf]|)|)/i,
735 multiLineStrings: true,
736 typeFirstDefinitions: true,
737 atoms: words("true false null larger smaller equal empty finished"),
738 indentSwitch: false,
739 styleDefs: false,
740 hooks: {
741 "@": function(stream) {
742 stream.eatWhile(/[\w\$_]/);
743 return "meta";
744 },
745 '"': function(stream, state) {
746 state.tokenize = tokenCeylonString(stream.match('""') ? "triple" : "single");
747 return state.tokenize(stream, state);
748 },
749 '`': function(stream, state) {
750 if (!stringTokenizer || !stream.match('`')) return false;
751 state.tokenize = stringTokenizer;
752 stringTokenizer = null;
753 return state.tokenize(stream, state);
754 },
755 "'": function(stream) {
756 stream.eatWhile(/[\w\$_\xa1-\uffff]/);
757 return "atom";
758 },
759 token: function(_stream, state, style) {
760 if ((style == "variable" || style == "variable-3") &&
761 state.prevToken == ".") {
762 return "variable-2";
763 }
764 }
765 },
766 modeProps: {
767 fold: ["brace", "import"],
768 closeBrackets: {triples: '"'}
769 }
770 });
771
591 772 });
@@ -59,7 +59,8 b' CodeMirror.defineMode("clojure", functio'
59 59 sign: /[+-]/,
60 60 exponent: /e/i,
61 61 keyword_char: /[^\s\(\[\;\)\]]/,
62 symbol: /[\w*+!\-\._?:<>\/\xa1-\uffff]/
62 symbol: /[\w*+!\-\._?:<>\/\xa1-\uffff]/,
63 block_indent: /^(?:def|with)[^\/]+$|\/(?:def|with)/
63 64 };
64 65
65 66 function stateStack(indent, type, prev) { // represents a state stack object
@@ -96,6 +97,9 b' CodeMirror.defineMode("clojure", functio'
96 97 if ( '.' == stream.peek() ) {
97 98 stream.eat('.');
98 99 stream.eatWhile(tests.digit);
100 } else if ('/' == stream.peek() ) {
101 stream.eat('/');
102 stream.eatWhile(tests.digit);
99 103 }
100 104
101 105 if ( stream.eat(tests.exponent) ) {
@@ -139,7 +143,7 b' CodeMirror.defineMode("clojure", functio'
139 143 }
140 144
141 145 // skip spaces
142 if (stream.eatSpace()) {
146 if (state.mode != "string" && stream.eatSpace()) {
143 147 return null;
144 148 }
145 149 var returnType = null;
@@ -187,7 +191,7 b' CodeMirror.defineMode("clojure", functio'
187 191 }
188 192
189 193 if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) ||
190 /^(?:def|with)/.test(keyWord))) { // indent-word
194 tests.block_indent.test(keyWord))) { // indent-word
191 195 pushStack(state, indentTemp + INDENT_WORD_SKIP, ch);
192 196 } else { // non-indent word
193 197 // we continue eating the spaces
@@ -240,5 +244,6 b' CodeMirror.defineMode("clojure", functio'
240 244 });
241 245
242 246 CodeMirror.defineMIME("text/x-clojure", "clojure");
247 CodeMirror.defineMIME("text/x-clojurescript", "clojure");
243 248
244 249 });
@@ -25,7 +25,7 b' CodeMirror.defineMode("coffeescript", fu'
25 25 var operators = /^(?:->|=>|\+[+=]?|-[\-=]?|\*[\*=]?|\/[\/=]?|[=!]=|<[><]?=?|>>?=?|%=?|&=?|\|=?|\^=?|\~|!|\?|(or|and|\|\||&&|\?)=)/;
26 26 var delimiters = /^(?:[()\[\]{},:`=;]|\.\.?\.?)/;
27 27 var identifiers = /^[_A-Za-z$][_A-Za-z$0-9]*/;
28 var properties = /^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/;
28 var atProp = /^@[_A-Za-z$][_A-Za-z$0-9]*/;
29 29
30 30 var wordOperators = wordRegexp(["and", "or", "not",
31 31 "is", "isnt", "in",
@@ -145,6 +145,8 b' CodeMirror.defineMode("coffeescript", fu'
145 145 }
146 146 }
147 147
148
149
148 150 // Handle operators and delimiters
149 151 if (stream.match(operators) || stream.match(wordOperators)) {
150 152 return "operator";
@@ -157,6 +159,10 b' CodeMirror.defineMode("coffeescript", fu'
157 159 return "atom";
158 160 }
159 161
162 if (stream.match(atProp) || state.prop && stream.match(identifiers)) {
163 return "property";
164 }
165
160 166 if (stream.match(keywords)) {
161 167 return "keyword";
162 168 }
@@ -165,10 +171,6 b' CodeMirror.defineMode("coffeescript", fu'
165 171 return "variable";
166 172 }
167 173
168 if (stream.match(properties)) {
169 return "property";
170 }
171
172 174 // Handle non-detected items
173 175 stream.next();
174 176 return ERRORCLASS;
@@ -265,24 +267,11 b' CodeMirror.defineMode("coffeescript", fu'
265 267 var style = state.tokenize(stream, state);
266 268 var current = stream.current();
267 269
268 // Handle "." connected identifiers
269 if (current === ".") {
270 style = state.tokenize(stream, state);
271 current = stream.current();
272 if (/^\.[\w$]+$/.test(current)) {
273 return "variable";
274 } else {
275 return ERRORCLASS;
276 }
277 }
278
279 270 // Handle scope changes.
280 271 if (current === "return") {
281 272 state.dedent = true;
282 273 }
283 if (((current === "->" || current === "=>") &&
284 !state.lambda &&
285 !stream.peek())
274 if (((current === "->" || current === "=>") && stream.eol())
286 275 || style === "indent") {
287 276 indent(stream, state);
288 277 }
@@ -324,8 +313,7 b' CodeMirror.defineMode("coffeescript", fu'
324 313 return {
325 314 tokenize: tokenBase,
326 315 scope: {offset:basecolumn || 0, type:"coffee", prev: null, align: false},
327 lastToken: null,
328 lambda: false,
316 prop: false,
329 317 dedent: 0
330 318 };
331 319 },
@@ -335,12 +323,9 b' CodeMirror.defineMode("coffeescript", fu'
335 323 if (fillAlign && stream.sol()) fillAlign.align = false;
336 324
337 325 var style = tokenLexer(stream, state);
338 if (fillAlign && style && style != "comment") fillAlign.align = true;
339
340 state.lastToken = {style:style, content: stream.current()};
341
342 if (stream.eol() && stream.lambda) {
343 state.lambda = false;
326 if (style && style != "comment") {
327 if (fillAlign) fillAlign.align = true;
328 state.prop = style == "punctuation" && stream.current() == "."
344 329 }
345 330
346 331 return style;
@@ -365,5 +350,6 b' CodeMirror.defineMode("coffeescript", fu'
365 350 });
366 351
367 352 CodeMirror.defineMIME("text/x-coffeescript", "coffeescript");
353 CodeMirror.defineMIME("text/coffeescript", "coffeescript");
368 354
369 355 });
@@ -12,6 +12,7 b''
12 12 "use strict";
13 13
14 14 CodeMirror.defineMode("css", function(config, parserConfig) {
15 var inline = parserConfig.inline
15 16 if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
16 17
17 18 var indentUnit = config.indentUnit,
@@ -19,13 +20,15 b' CodeMirror.defineMode("css", function(co'
19 20 documentTypes = parserConfig.documentTypes || {},
20 21 mediaTypes = parserConfig.mediaTypes || {},
21 22 mediaFeatures = parserConfig.mediaFeatures || {},
23 mediaValueKeywords = parserConfig.mediaValueKeywords || {},
22 24 propertyKeywords = parserConfig.propertyKeywords || {},
23 25 nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
24 26 fontProperties = parserConfig.fontProperties || {},
25 27 counterDescriptors = parserConfig.counterDescriptors || {},
26 28 colorKeywords = parserConfig.colorKeywords || {},
27 29 valueKeywords = parserConfig.valueKeywords || {},
28 allowNested = parserConfig.allowNested;
30 allowNested = parserConfig.allowNested,
31 supportsAtComponent = parserConfig.supportsAtComponent === true;
29 32
30 33 var type, override;
31 34 function ret(style, tp) { type = tp; return style; }
@@ -119,13 +122,14 b' CodeMirror.defineMode("css", function(co'
119 122 this.prev = prev;
120 123 }
121 124
122 function pushContext(state, stream, type) {
123 state.context = new Context(type, stream.indentation() + indentUnit, state.context);
125 function pushContext(state, stream, type, indent) {
126 state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
124 127 return type;
125 128 }
126 129
127 130 function popContext(state) {
128 state.context = state.context.prev;
131 if (state.context.prev)
132 state.context = state.context.prev;
129 133 return state.context.type;
130 134 }
131 135
@@ -157,9 +161,13 b' CodeMirror.defineMode("css", function(co'
157 161 return pushContext(state, stream, "block");
158 162 } else if (type == "}" && state.context.prev) {
159 163 return popContext(state);
160 } else if (/@(media|supports|(-moz-)?document)/.test(type)) {
164 } else if (supportsAtComponent && /@component/.test(type)) {
165 return pushContext(state, stream, "atComponentBlock");
166 } else if (/^@(-moz-)?document$/.test(type)) {
167 return pushContext(state, stream, "documentTypes");
168 } else if (/^@(media|supports|(-moz-)?document|import)$/.test(type)) {
161 169 return pushContext(state, stream, "atBlock");
162 } else if (/@(font-face|counter-style)/.test(type)) {
170 } else if (/^@(font-face|counter-style)/.test(type)) {
163 171 state.stateArg = type;
164 172 return "restricted_atBlock_before";
165 173 } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
@@ -219,7 +227,7 b' CodeMirror.defineMode("css", function(co'
219 227 if (type == "}" || type == "{") return popAndPass(type, stream, state);
220 228 if (type == "(") return pushContext(state, stream, "parens");
221 229
222 if (type == "hash" && !/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) {
230 if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) {
223 231 override += " error";
224 232 } else if (type == "word") {
225 233 wordAsValue(stream);
@@ -252,33 +260,56 b' CodeMirror.defineMode("css", function(co'
252 260 return pass(type, stream, state);
253 261 };
254 262
263 states.documentTypes = function(type, stream, state) {
264 if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
265 override = "tag";
266 return state.context.type;
267 } else {
268 return states.atBlock(type, stream, state);
269 }
270 };
271
255 272 states.atBlock = function(type, stream, state) {
256 273 if (type == "(") return pushContext(state, stream, "atBlock_parens");
257 if (type == "}") return popAndPass(type, stream, state);
274 if (type == "}" || type == ";") return popAndPass(type, stream, state);
258 275 if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
259 276
277 if (type == "interpolation") return pushContext(state, stream, "interpolation");
278
260 279 if (type == "word") {
261 280 var word = stream.current().toLowerCase();
262 281 if (word == "only" || word == "not" || word == "and" || word == "or")
263 282 override = "keyword";
264 else if (documentTypes.hasOwnProperty(word))
265 override = "tag";
266 283 else if (mediaTypes.hasOwnProperty(word))
267 284 override = "attribute";
268 285 else if (mediaFeatures.hasOwnProperty(word))
269 286 override = "property";
287 else if (mediaValueKeywords.hasOwnProperty(word))
288 override = "keyword";
270 289 else if (propertyKeywords.hasOwnProperty(word))
271 290 override = "property";
272 291 else if (nonStandardPropertyKeywords.hasOwnProperty(word))
273 292 override = "string-2";
274 293 else if (valueKeywords.hasOwnProperty(word))
275 294 override = "atom";
295 else if (colorKeywords.hasOwnProperty(word))
296 override = "keyword";
276 297 else
277 298 override = "error";
278 299 }
279 300 return state.context.type;
280 301 };
281 302
303 states.atComponentBlock = function(type, stream, state) {
304 if (type == "}")
305 return popAndPass(type, stream, state);
306 if (type == "{")
307 return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
308 if (type == "word")
309 override = "error";
310 return state.context.type;
311 };
312
282 313 states.atBlock_parens = function(type, stream, state) {
283 314 if (type == ")") return popContext(state);
284 315 if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
@@ -336,9 +367,9 b' CodeMirror.defineMode("css", function(co'
336 367 return {
337 368 startState: function(base) {
338 369 return {tokenize: null,
339 state: "top",
370 state: inline ? "block" : "top",
340 371 stateArg: null,
341 context: new Context("top", base || 0, null)};
372 context: new Context(inline ? "block" : "top", base || 0, null)};
342 373 },
343 374
344 375 token: function(stream, state) {
@@ -357,12 +388,18 b' CodeMirror.defineMode("css", function(co'
357 388 var cx = state.context, ch = textAfter && textAfter.charAt(0);
358 389 var indent = cx.indent;
359 390 if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
360 if (cx.prev &&
361 (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "restricted_atBlock") ||
362 ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
363 ch == "{" && (cx.type == "at" || cx.type == "atBlock"))) {
364 indent = cx.indent - indentUnit;
365 cx = cx.prev;
391 if (cx.prev) {
392 if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
393 cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
394 // Resume indentation from parent context.
395 cx = cx.prev;
396 indent = cx.indent;
397 } else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
398 ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
399 // Dedent relative to current context.
400 indent = Math.max(0, cx.indent - indentUnit);
401 cx = cx.prev;
402 }
366 403 }
367 404 return indent;
368 405 },
@@ -399,17 +436,24 b' CodeMirror.defineMode("css", function(co'
399 436 "min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
400 437 "max-color", "color-index", "min-color-index", "max-color-index",
401 438 "monochrome", "min-monochrome", "max-monochrome", "resolution",
402 "min-resolution", "max-resolution", "scan", "grid"
439 "min-resolution", "max-resolution", "scan", "grid", "orientation",
440 "device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
441 "pointer", "any-pointer", "hover", "any-hover"
403 442 ], mediaFeatures = keySet(mediaFeatures_);
404 443
444 var mediaValueKeywords_ = [
445 "landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
446 "interlace", "progressive"
447 ], mediaValueKeywords = keySet(mediaValueKeywords_);
448
405 449 var propertyKeywords_ = [
406 450 "align-content", "align-items", "align-self", "alignment-adjust",
407 451 "alignment-baseline", "anchor-point", "animation", "animation-delay",
408 452 "animation-direction", "animation-duration", "animation-fill-mode",
409 453 "animation-iteration-count", "animation-name", "animation-play-state",
410 454 "animation-timing-function", "appearance", "azimuth", "backface-visibility",
411 "background", "background-attachment", "background-clip", "background-color",
412 "background-image", "background-origin", "background-position",
455 "background", "background-attachment", "background-blend-mode", "background-clip",
456 "background-color", "background-image", "background-origin", "background-position",
413 457 "background-repeat", "background-size", "baseline-shift", "binding",
414 458 "bleed", "bookmark-label", "bookmark-level", "bookmark-state",
415 459 "bookmark-target", "border", "border-bottom", "border-bottom-color",
@@ -553,11 +597,12 b' CodeMirror.defineMode("css", function(co'
553 597 "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
554 598 "cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
555 599 "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
556 "col-resize", "collapse", "column", "compact", "condensed", "contain", "content",
600 "col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
601 "compact", "condensed", "contain", "content",
557 602 "content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
558 "cross", "crosshair", "currentcolor", "cursive", "cyclic", "dashed", "decimal",
603 "cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
559 604 "decimal-leading-zero", "default", "default-button", "destination-atop",
560 "destination-in", "destination-out", "destination-over", "devanagari",
605 "destination-in", "destination-out", "destination-over", "devanagari", "difference",
561 606 "disc", "discard", "disclosure-closed", "disclosure-open", "document",
562 607 "dot-dash", "dot-dot-dash",
563 608 "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
@@ -568,23 +613,23 b' CodeMirror.defineMode("css", function(co'
568 613 "ethiopic-halehame-gez", "ethiopic-halehame-om-et",
569 614 "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
570 615 "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
571 "ethiopic-numeric", "ew-resize", "expanded", "extends", "extra-condensed",
572 "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "footnotes",
616 "ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
617 "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
573 618 "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
574 "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew",
619 "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
575 620 "help", "hidden", "hide", "higher", "highlight", "highlighttext",
576 "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
621 "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore",
577 622 "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
578 623 "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
579 624 "inline-block", "inline-flex", "inline-table", "inset", "inside", "intrinsic", "invert",
580 625 "italic", "japanese-formal", "japanese-informal", "justify", "kannada",
581 626 "katakana", "katakana-iroha", "keep-all", "khmer",
582 627 "korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
583 "landscape", "lao", "large", "larger", "left", "level", "lighter",
628 "landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten",
584 629 "line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
585 630 "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
586 631 "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
587 "lower-roman", "lowercase", "ltr", "malayalam", "match", "matrix", "matrix3d",
632 "lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d",
588 633 "media-controls-background", "media-current-time-display",
589 634 "media-fullscreen-button", "media-mute-button", "media-play-button",
590 635 "media-return-to-realtime-button", "media-rewind-button",
@@ -593,7 +638,7 b' CodeMirror.defineMode("css", function(co'
593 638 "media-volume-slider-container", "media-volume-sliderthumb", "medium",
594 639 "menu", "menulist", "menulist-button", "menulist-text",
595 640 "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
596 "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize",
641 "mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
597 642 "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
598 643 "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
599 644 "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
@@ -606,8 +651,8 b' CodeMirror.defineMode("css", function(co'
606 651 "relative", "repeat", "repeating-linear-gradient",
607 652 "repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse",
608 653 "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
609 "rotateZ", "round", "row-resize", "rtl", "run-in", "running",
610 "s-resize", "sans-serif", "scale", "scale3d", "scaleX", "scaleY", "scaleZ",
654 "rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
655 "s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
611 656 "scroll", "scrollbar", "se-resize", "searchfield",
612 657 "searchfield-cancel-button", "searchfield-decoration",
613 658 "searchfield-results-button", "searchfield-results-decoration",
@@ -615,8 +660,8 b' CodeMirror.defineMode("css", function(co'
615 660 "simp-chinese-formal", "simp-chinese-informal", "single",
616 661 "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
617 662 "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
618 "small", "small-caps", "small-caption", "smaller", "solid", "somali",
619 "source-atop", "source-in", "source-out", "source-over", "space", "spell-out", "square",
663 "small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
664 "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "spell-out", "square",
620 665 "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub",
621 666 "subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "table",
622 667 "table-caption", "table-cell", "table-column", "table-column-group",
@@ -633,12 +678,13 b' CodeMirror.defineMode("css", function(co'
633 678 "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
634 679 "var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
635 680 "visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
636 "window", "windowframe", "windowtext", "words", "x-large", "x-small", "xor",
681 "window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
637 682 "xx-large", "xx-small"
638 683 ], valueKeywords = keySet(valueKeywords_);
639 684
640 var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(propertyKeywords_)
641 .concat(nonStandardPropertyKeywords_).concat(colorKeywords_).concat(valueKeywords_);
685 var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)
686 .concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)
687 .concat(valueKeywords_);
642 688 CodeMirror.registerHelper("hintWords", "css", allWords);
643 689
644 690 function tokenCComment(stream, state) {
@@ -657,6 +703,7 b' CodeMirror.defineMode("css", function(co'
657 703 documentTypes: documentTypes,
658 704 mediaTypes: mediaTypes,
659 705 mediaFeatures: mediaFeatures,
706 mediaValueKeywords: mediaValueKeywords,
660 707 propertyKeywords: propertyKeywords,
661 708 nonStandardPropertyKeywords: nonStandardPropertyKeywords,
662 709 fontProperties: fontProperties,
@@ -676,6 +723,7 b' CodeMirror.defineMode("css", function(co'
676 723 CodeMirror.defineMIME("text/x-scss", {
677 724 mediaTypes: mediaTypes,
678 725 mediaFeatures: mediaFeatures,
726 mediaValueKeywords: mediaValueKeywords,
679 727 propertyKeywords: propertyKeywords,
680 728 nonStandardPropertyKeywords: nonStandardPropertyKeywords,
681 729 colorKeywords: colorKeywords,
@@ -717,6 +765,7 b' CodeMirror.defineMode("css", function(co'
717 765 CodeMirror.defineMIME("text/x-less", {
718 766 mediaTypes: mediaTypes,
719 767 mediaFeatures: mediaFeatures,
768 mediaValueKeywords: mediaValueKeywords,
720 769 propertyKeywords: propertyKeywords,
721 770 nonStandardPropertyKeywords: nonStandardPropertyKeywords,
722 771 colorKeywords: colorKeywords,
@@ -751,4 +800,26 b' CodeMirror.defineMode("css", function(co'
751 800 helperType: "less"
752 801 });
753 802
803 CodeMirror.defineMIME("text/x-gss", {
804 documentTypes: documentTypes,
805 mediaTypes: mediaTypes,
806 mediaFeatures: mediaFeatures,
807 propertyKeywords: propertyKeywords,
808 nonStandardPropertyKeywords: nonStandardPropertyKeywords,
809 fontProperties: fontProperties,
810 counterDescriptors: counterDescriptors,
811 colorKeywords: colorKeywords,
812 valueKeywords: valueKeywords,
813 supportsAtComponent: true,
814 tokenHooks: {
815 "/": function(stream, state) {
816 if (!stream.eat("*")) return false;
817 state.tokenize = tokenCComment;
818 return tokenCComment(stream, state);
819 }
820 },
821 name: "css",
822 helperType: "gss"
823 });
824
754 825 });
@@ -60,9 +60,9 b''
60 60 };
61 61 var indentUnit = config.indentUnit;
62 62 var curPunc;
63 var funcs = wordRegexp(["abs", "acos", "allShortestPaths", "asin", "atan", "atan2", "avg", "ceil", "coalesce", "collect", "cos", "cot", "count", "degrees", "e", "endnode", "exp", "extract", "filter", "floor", "haversin", "head", "id", "keys", "labels", "last", "left", "length", "log", "log10", "lower", "ltrim", "max", "min", "node", "nodes", "percentileCont", "percentileDisc", "pi", "radians", "rand", "range", "reduce", "rel", "relationship", "relationships", "replace", "right", "round", "rtrim", "shortestPath", "sign", "sin", "split", "sqrt", "startnode", "stdev", "stdevp", "str", "substring", "sum", "tail", "tan", "timestamp", "toFloat", "toInt", "trim", "type", "upper"]);
64 var preds = wordRegexp(["all", "and", "any", "has", "in", "none", "not", "or", "single", "xor"]);
65 var keywords = wordRegexp(["as", "asc", "ascending", "assert", "by", "case", "commit", "constraint", "create", "csv", "cypher", "delete", "desc", "descending", "distinct", "drop", "else", "end", "explain", "false", "fieldterminator", "foreach", "from", "headers", "in", "index", "is", "limit", "load", "match", "merge", "null", "on", "optional", "order", "periodic", "profile", "remove", "return", "scan", "set", "skip", "start", "then", "true", "union", "unique", "unwind", "using", "when", "where", "with"]);
63 var funcs = wordRegexp(["abs", "acos", "allShortestPaths", "asin", "atan", "atan2", "avg", "ceil", "coalesce", "collect", "cos", "cot", "count", "degrees", "e", "endnode", "exp", "extract", "filter", "floor", "haversin", "head", "id", "keys", "labels", "last", "left", "length", "log", "log10", "lower", "ltrim", "max", "min", "node", "nodes", "percentileCont", "percentileDisc", "pi", "radians", "rand", "range", "reduce", "rel", "relationship", "relationships", "replace", "reverse", "right", "round", "rtrim", "shortestPath", "sign", "sin", "size", "split", "sqrt", "startnode", "stdev", "stdevp", "str", "substring", "sum", "tail", "tan", "timestamp", "toFloat", "toInt", "toString", "trim", "type", "upper"]);
64 var preds = wordRegexp(["all", "and", "any", "contains", "exists", "has", "in", "none", "not", "or", "single", "xor"]);
65 var keywords = wordRegexp(["as", "asc", "ascending", "assert", "by", "case", "commit", "constraint", "create", "csv", "cypher", "delete", "desc", "descending", "detach", "distinct", "drop", "else", "end", "ends", "explain", "false", "fieldterminator", "foreach", "from", "headers", "in", "index", "is", "join", "limit", "load", "match", "merge", "null", "on", "optional", "order", "periodic", "profile", "remove", "return", "scan", "set", "skip", "start", "starts", "then", "true", "union", "unique", "unwind", "using", "when", "where", "with"]);
66 66 var operatorChars = /[*+\-<>=&|~%^]/;
67 67
68 68 return {
@@ -15,7 +15,7 b''
15 15 "implements get native operator set typedef with enum throw rethrow " +
16 16 "assert break case continue default in return new deferred async await " +
17 17 "try catch finally do else for if switch while import library export " +
18 "part of show hide is").split(" ");
18 "part of show hide is as").split(" ");
19 19 var blockKeywords = "try catch finally do else for if switch while".split(" ");
20 20 var atoms = "true false null".split(" ");
21 21 var builtins = "void bool num int double dynamic var String".split(" ");
@@ -26,21 +26,101 b''
26 26 return obj;
27 27 }
28 28
29 function pushInterpolationStack(state) {
30 (state.interpolationStack || (state.interpolationStack = [])).push(state.tokenize);
31 }
32
33 function popInterpolationStack(state) {
34 return (state.interpolationStack || (state.interpolationStack = [])).pop();
35 }
36
37 function sizeInterpolationStack(state) {
38 return state.interpolationStack ? state.interpolationStack.length : 0;
39 }
40
29 41 CodeMirror.defineMIME("application/dart", {
30 42 name: "clike",
31 43 keywords: set(keywords),
32 multiLineStrings: true,
33 44 blockKeywords: set(blockKeywords),
34 45 builtin: set(builtins),
35 46 atoms: set(atoms),
36 47 hooks: {
37 48 "@": function(stream) {
38 stream.eatWhile(/[\w\$_]/);
49 stream.eatWhile(/[\w\$_\.]/);
39 50 return "meta";
51 },
52
53 // custom string handling to deal with triple-quoted strings and string interpolation
54 "'": function(stream, state) {
55 return tokenString("'", stream, state, false);
56 },
57 "\"": function(stream, state) {
58 return tokenString("\"", stream, state, false);
59 },
60 "r": function(stream, state) {
61 var peek = stream.peek();
62 if (peek == "'" || peek == "\"") {
63 return tokenString(stream.next(), stream, state, true);
64 }
65 return false;
66 },
67
68 "}": function(_stream, state) {
69 // "}" is end of interpolation, if interpolation stack is non-empty
70 if (sizeInterpolationStack(state) > 0) {
71 state.tokenize = popInterpolationStack(state);
72 return null;
73 }
74 return false;
40 75 }
41 76 }
42 77 });
43 78
79 function tokenString(quote, stream, state, raw) {
80 var tripleQuoted = false;
81 if (stream.eat(quote)) {
82 if (stream.eat(quote)) tripleQuoted = true;
83 else return "string"; //empty string
84 }
85 function tokenStringHelper(stream, state) {
86 var escaped = false;
87 while (!stream.eol()) {
88 if (!raw && !escaped && stream.peek() == "$") {
89 pushInterpolationStack(state);
90 state.tokenize = tokenInterpolation;
91 return "string";
92 }
93 var next = stream.next();
94 if (next == quote && !escaped && (!tripleQuoted || stream.match(quote + quote))) {
95 state.tokenize = null;
96 break;
97 }
98 escaped = !raw && !escaped && next == "\\";
99 }
100 return "string";
101 }
102 state.tokenize = tokenStringHelper;
103 return tokenStringHelper(stream, state);
104 }
105
106 function tokenInterpolation(stream, state) {
107 stream.eat("$");
108 if (stream.eat("{")) {
109 // let clike handle the content of ${...},
110 // we take over again when "}" appears (see hooks).
111 state.tokenize = null;
112 } else {
113 state.tokenize = tokenInterpolationIdentifier;
114 }
115 return null;
116 }
117
118 function tokenInterpolationIdentifier(stream, state) {
119 stream.eatWhile(/[\w_]/);
120 state.tokenize = popInterpolationStack(state);
121 return "variable";
122 }
123
44 124 CodeMirror.registerHelper("hintWords", "application/dart", keywords.concat(atoms).concat(builtins));
45 125
46 126 // This is needed to make loading through meta.js work.
@@ -14,14 +14,14 b''
14 14 "use strict";
15 15
16 16 CodeMirror.defineMode("django:inner", function() {
17 var keywords = ["block", "endblock", "for", "endfor", "true", "false",
18 "loop", "none", "self", "super", "if", "endif", "as",
19 "else", "import", "with", "endwith", "without", "context", "ifequal", "endifequal",
20 "ifnotequal", "endifnotequal", "extends", "include", "load", "comment",
21 "endcomment", "empty", "url", "static", "trans", "blocktrans", "now", "regroup",
22 "lorem", "ifchanged", "endifchanged", "firstof", "debug", "cycle", "csrf_token",
23 "autoescape", "endautoescape", "spaceless", "ssi", "templatetag",
24 "verbatim", "endverbatim", "widthratio"],
17 var keywords = ["block", "endblock", "for", "endfor", "true", "false", "filter", "endfilter",
18 "loop", "none", "self", "super", "if", "elif", "endif", "as", "else", "import",
19 "with", "endwith", "without", "context", "ifequal", "endifequal", "ifnotequal",
20 "endifnotequal", "extends", "include", "load", "comment", "endcomment",
21 "empty", "url", "static", "trans", "blocktrans", "endblocktrans", "now",
22 "regroup", "lorem", "ifchanged", "endifchanged", "firstof", "debug", "cycle",
23 "csrf_token", "autoescape", "endautoescape", "spaceless", "endspaceless",
24 "ssi", "templatetag", "verbatim", "endverbatim", "widthratio"],
25 25 filters = ["add", "addslashes", "capfirst", "center", "cut", "date",
26 26 "default", "default_if_none", "dictsort",
27 27 "dictsortreversed", "divisibleby", "escape", "escapejs",
@@ -35,11 +35,13 b''
35 35 "truncatechars_html", "truncatewords", "truncatewords_html",
36 36 "unordered_list", "upper", "urlencode", "urlize",
37 37 "urlizetrunc", "wordcount", "wordwrap", "yesno"],
38 operators = ["==", "!=", "<", ">", "<=", ">=", "in", "not", "or", "and"];
38 operators = ["==", "!=", "<", ">", "<=", ">="],
39 wordOperators = ["in", "not", "or", "and"];
39 40
40 41 keywords = new RegExp("^\\b(" + keywords.join("|") + ")\\b");
41 42 filters = new RegExp("^\\b(" + filters.join("|") + ")\\b");
42 43 operators = new RegExp("^\\b(" + operators.join("|") + ")\\b");
44 wordOperators = new RegExp("^\\b(" + wordOperators.join("|") + ")\\b");
43 45
44 46 // We have to return "null" instead of null, in order to avoid string
45 47 // styling as the default, when using Django templates inside HTML
@@ -59,7 +61,7 b''
59 61
60 62 // Ignore completely any stream series that do not match the
61 63 // Django template opening tags.
62 while (stream.next() != null && !stream.match("{{", false) && !stream.match("{%", false)) {}
64 while (stream.next() != null && !stream.match(/\{[{%#]/, false)) {}
63 65 return null;
64 66 }
65 67
@@ -270,6 +272,11 b''
270 272 return "operator";
271 273 }
272 274
275 // Attempt to match a word operator
276 if (stream.match(wordOperators)) {
277 return "keyword";
278 }
279
273 280 // Attempt to match a keyword
274 281 var keywordMatch = stream.match(keywords);
275 282 if (keywordMatch) {
@@ -310,9 +317,8 b''
310 317
311 318 // Mark everything as comment inside the tag and the tag itself.
312 319 function inComment (stream, state) {
313 if (stream.match("#}")) {
314 state.tokenize = tokenBase;
315 }
320 if (stream.match(/^.*?#\}/)) state.tokenize = tokenBase
321 else stream.skipToEnd()
316 322 return "comment";
317 323 }
318 324
@@ -69,7 +69,10 b''
69 69 token: null,
70 70 next: "start"
71 71 }
72 ]
72 ],
73 meta: {
74 lineComment: "#"
75 }
73 76 });
74 77
75 78 CodeMirror.defineMIME("text/x-dockerfile", "dockerfile");
@@ -202,4 +202,4 b''
202 202 });
203 203
204 204 CodeMirror.defineMIME("text/x-elm", "elm");
205 })();
205 });
@@ -220,8 +220,6 b' CodeMirror.defineMode("erlang", function'
220 220 }else{
221 221 return rval(state,stream,"function");
222 222 }
223 }else if (is_member(w,operatorAtomWords)) {
224 return rval(state,stream,"operator");
225 223 }else if (lookahead(stream) == ":") {
226 224 if (w == "erlang") {
227 225 return rval(state,stream,"builtin");
@@ -230,8 +228,6 b' CodeMirror.defineMode("erlang", function'
230 228 }
231 229 }else if (is_member(w,["true","false"])) {
232 230 return rval(state,stream,"boolean");
233 }else if (is_member(w,["true","false"])) {
234 return rval(state,stream,"boolean");
235 231 }else{
236 232 return rval(state,stream,"atom");
237 233 }
@@ -11,6 +11,8 b''
11 11 })(function(CodeMirror) {
12 12 "use strict";
13 13
14 var urlRE = /^((?:(?:aaas?|about|acap|adiumxtra|af[ps]|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|cap|chrome(?:-extension)?|cid|coap|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-(?:playcontainer|playsingle)|dns|doi|dtn|dvb|ed2k|facetime|feed|file|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|hcp|https?|iax|icap|icon|im|imap|info|ipn|ipp|irc[6s]?|iris(?:\.beep|\.lwz|\.xpc|\.xpcs)?|itms|jar|javascript|jms|keyparc|lastfm|ldaps?|magnet|mailto|maps|market|message|mid|mms|ms-help|msnim|msrps?|mtqp|mumble|mupdate|mvn|news|nfs|nih?|nntp|notes|oid|opaquelocktoken|palm|paparazzi|platform|pop|pres|proxy|psyc|query|res(?:ource)?|rmi|rsync|rtmp|rtsp|secondlife|service|session|sftp|sgn|shttp|sieve|sips?|skype|sm[bs]|snmp|soap\.beeps?|soldat|spotify|ssh|steam|svn|tag|teamspeak|tel(?:net)?|tftp|things|thismessage|tip|tn3270|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|view-source|webcal|wss?|wtai|wyciwyg|xcon(?:-userid)?|xfire|xmlrpc\.beeps?|xmpp|xri|ymsgr|z39\.50[rs]?):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i
15
14 16 CodeMirror.defineMode("gfm", function(config, modeConfig) {
15 17 var codeDepth = 0;
16 18 function blankLine(state) {
@@ -37,7 +39,7 b' CodeMirror.defineMode("gfm", function(co'
37 39
38 40 // Hack to prevent formatting override inside code blocks (block and inline)
39 41 if (state.codeBlock) {
40 if (stream.match(/^```/)) {
42 if (stream.match(/^```+/)) {
41 43 state.codeBlock = false;
42 44 return null;
43 45 }
@@ -47,7 +49,7 b' CodeMirror.defineMode("gfm", function(co'
47 49 if (stream.sol()) {
48 50 state.code = false;
49 51 }
50 if (stream.sol() && stream.match(/^```/)) {
52 if (stream.sol() && stream.match(/^```+/)) {
51 53 stream.skipToEnd();
52 54 state.codeBlock = true;
53 55 return null;
@@ -78,25 +80,29 b' CodeMirror.defineMode("gfm", function(co'
78 80 }
79 81 if (stream.sol() || state.ateSpace) {
80 82 state.ateSpace = false;
81 if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) {
82 // User/Project@SHA
83 // User@SHA
84 // SHA
85 state.combineTokens = true;
86 return "link";
87 } else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) {
88 // User/Project#Num
89 // User#Num
90 // #Num
91 state.combineTokens = true;
92 return "link";
83 if (modeConfig.gitHubSpice !== false) {
84 if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) {
85 // User/Project@SHA
86 // User@SHA
87 // SHA
88 state.combineTokens = true;
89 return "link";
90 } else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) {
91 // User/Project#Num
92 // User#Num
93 // #Num
94 state.combineTokens = true;
95 return "link";
96 }
93 97 }
94 98 }
95 if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i) &&
96 stream.string.slice(stream.start - 2, stream.start) != "](") {
99 if (stream.match(urlRE) &&
100 stream.string.slice(stream.start - 2, stream.start) != "](" &&
101 (stream.start == 0 || /\W/.test(stream.string.charAt(stream.start - 1)))) {
97 102 // URLs
98 103 // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
99 104 // And then (issue #1160) simplified to make it not crash the Chrome Regexp engine
105 // And then limited url schemes to the CommonMark list, so foo:bar isn't matched as a URL
100 106 state.combineTokens = true;
101 107 return "link";
102 108 }
@@ -109,15 +115,16 b' CodeMirror.defineMode("gfm", function(co'
109 115 var markdownConfig = {
110 116 underscoresBreakWords: false,
111 117 taskLists: true,
112 fencedCodeBlocks: true,
118 fencedCodeBlocks: '```',
113 119 strikethrough: true
114 120 };
115 121 for (var attr in modeConfig) {
116 122 markdownConfig[attr] = modeConfig[attr];
117 123 }
118 124 markdownConfig.name = "markdown";
119 CodeMirror.defineMIME("gfmBase", markdownConfig);
120 return CodeMirror.overlayMode(CodeMirror.getMode(config, "gfmBase"), gfmOverlay);
125 return CodeMirror.overlayMode(CodeMirror.getMode(config, markdownConfig), gfmOverlay);
126
121 127 }, "markdown");
122 128
129 CodeMirror.defineMIME("text/x-gfm", "gfm");
123 130 });
@@ -86,7 +86,7 b' CodeMirror.defineMode("go", function(con'
86 86 var escaped = false, next, end = false;
87 87 while ((next = stream.next()) != null) {
88 88 if (next == quote && !escaped) {end = true; break;}
89 escaped = !escaped && next == "\\";
89 escaped = !escaped && quote != "`" && next == "\\";
90 90 }
91 91 if (end || !(escaped || quote == "`"))
92 92 state.tokenize = tokenBase;
@@ -85,8 +85,10 b''
85 85 state.tokenize = rubyInQuote(")");
86 86 return state.tokenize(stream, state);
87 87 } else if (ch == "{") {
88 state.tokenize = rubyInQuote("}");
89 return state.tokenize(stream, state);
88 if (!stream.match(/^\{%.*/)) {
89 state.tokenize = rubyInQuote("}");
90 return state.tokenize(stream, state);
91 }
90 92 }
91 93 }
92 94
@@ -3,15 +3,15 b''
3 3
4 4 (function(mod) {
5 5 if (typeof exports == "object" && typeof module == "object") // CommonJS
6 mod(require("../../lib/codemirror"), require("../../addon/mode/simple"));
6 mod(require("../../lib/codemirror"), require("../../addon/mode/simple"), require("../../addon/mode/multiplex"));
7 7 else if (typeof define == "function" && define.amd) // AMD
8 define(["../../lib/codemirror", "../../addon/mode/simple"], mod);
8 define(["../../lib/codemirror", "../../addon/mode/simple", "../../addon/mode/multiplex"], mod);
9 9 else // Plain browser env
10 10 mod(CodeMirror);
11 11 })(function(CodeMirror) {
12 12 "use strict";
13 13
14 CodeMirror.defineSimpleMode("handlebars", {
14 CodeMirror.defineSimpleMode("handlebars-tags", {
15 15 start: [
16 16 { regex: /\{\{!--/, push: "dash_comment", token: "comment" },
17 17 { regex: /\{\{!/, push: "comment", token: "comment" },
@@ -21,8 +21,8 b''
21 21 { regex: /\}\}/, pop: true, token: "tag" },
22 22
23 23 // Double and single quotes
24 { regex: /"(?:[^\\]|\\.)*?"/, token: "string" },
25 { regex: /'(?:[^\\]|\\.)*?'/, token: "string" },
24 { regex: /"(?:[^\\"]|\\.)*"?/, token: "string" },
25 { regex: /'(?:[^\\']|\\.)*'?/, token: "string" },
26 26
27 27 // Handlebars keywords
28 28 { regex: />|[#\/]([A-Za-z_]\w*)/, token: "keyword" },
@@ -49,5 +49,14 b''
49 49 ]
50 50 });
51 51
52 CodeMirror.defineMode("handlebars", function(config, parserConfig) {
53 var handlebars = CodeMirror.getMode(config, "handlebars-tags");
54 if (!parserConfig || !parserConfig.base) return handlebars;
55 return CodeMirror.multiplexingMode(
56 CodeMirror.getMode(config, parserConfig.base),
57 {open: "{{", close: "}}", mode: handlebars, parseDelimiters: true}
58 );
59 });
60
52 61 CodeMirror.defineMIME("text/x-handlebars-template", "handlebars");
53 62 });
@@ -16,23 +16,21 b' CodeMirror.defineMode("haxe", function(c'
16 16
17 17 // Tokenizer
18 18
19 var keywords = function(){
20 function kw(type) {return {type: type, style: "keyword"};}
21 var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
22 var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"};
19 function kw(type) {return {type: type, style: "keyword"};}
20 var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
21 var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"};
23 22 var type = kw("typedef");
24 return {
25 "if": A, "while": A, "else": B, "do": B, "try": B,
26 "return": C, "break": C, "continue": C, "new": C, "throw": C,
27 "var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"),
23 var keywords = {
24 "if": A, "while": A, "else": B, "do": B, "try": B,
25 "return": C, "break": C, "continue": C, "new": C, "throw": C,
26 "var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"),
28 27 "public": attribute, "private": attribute, "cast": kw("cast"), "import": kw("import"), "macro": kw("macro"),
29 "function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"),
30 "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
31 "in": operator, "never": kw("property_access"), "trace":kw("trace"),
28 "function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"),
29 "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
30 "in": operator, "never": kw("property_access"), "trace":kw("trace"),
32 31 "class": type, "abstract":type, "enum":type, "interface":type, "typedef":type, "extends":type, "implements":type, "dynamic":type,
33 "true": atom, "false": atom, "null": atom
34 };
35 }();
32 "true": atom, "false": atom, "null": atom
33 };
36 34
37 35 var isOperatorChar = /[+\-*&%=<>!?|]/;
38 36
@@ -41,14 +39,13 b' CodeMirror.defineMode("haxe", function(c'
41 39 return f(stream, state);
42 40 }
43 41
44 function nextUntilUnescaped(stream, end) {
42 function toUnescaped(stream, end) {
45 43 var escaped = false, next;
46 44 while ((next = stream.next()) != null) {
47 45 if (next == end && !escaped)
48 return false;
46 return true;
49 47 escaped = !escaped && next == "\\";
50 48 }
51 return escaped;
52 49 }
53 50
54 51 // Used as scratch variables to communicate multiple values without
@@ -61,70 +58,58 b' CodeMirror.defineMode("haxe", function(c'
61 58
62 59 function haxeTokenBase(stream, state) {
63 60 var ch = stream.next();
64 if (ch == '"' || ch == "'")
61 if (ch == '"' || ch == "'") {
65 62 return chain(stream, state, haxeTokenString(ch));
66 else if (/[\[\]{}\(\),;\:\.]/.test(ch))
63 } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
67 64 return ret(ch);
68 else if (ch == "0" && stream.eat(/x/i)) {
65 } else if (ch == "0" && stream.eat(/x/i)) {
69 66 stream.eatWhile(/[\da-f]/i);
70 67 return ret("number", "number");
71 }
72 else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
73 stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
68 } else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
69 stream.match(/^\d*(?:\.\d*(?!\.))?(?:[eE][+\-]?\d+)?/);
74 70 return ret("number", "number");
75 }
76 else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) {
77 nextUntilUnescaped(stream, "/");
71 } else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) {
72 toUnescaped(stream, "/");
78 73 stream.eatWhile(/[gimsu]/);
79 74 return ret("regexp", "string-2");
80 }
81 else if (ch == "/") {
75 } else if (ch == "/") {
82 76 if (stream.eat("*")) {
83 77 return chain(stream, state, haxeTokenComment);
84 }
85 else if (stream.eat("/")) {
78 } else if (stream.eat("/")) {
86 79 stream.skipToEnd();
87 80 return ret("comment", "comment");
88 }
89 else {
81 } else {
90 82 stream.eatWhile(isOperatorChar);
91 83 return ret("operator", null, stream.current());
92 84 }
93 }
94 else if (ch == "#") {
85 } else if (ch == "#") {
95 86 stream.skipToEnd();
96 87 return ret("conditional", "meta");
97 }
98 else if (ch == "@") {
88 } else if (ch == "@") {
99 89 stream.eat(/:/);
100 90 stream.eatWhile(/[\w_]/);
101 91 return ret ("metadata", "meta");
102 }
103 else if (isOperatorChar.test(ch)) {
92 } else if (isOperatorChar.test(ch)) {
104 93 stream.eatWhile(isOperatorChar);
105 94 return ret("operator", null, stream.current());
106 }
107 else {
108 var word;
109 if(/[A-Z]/.test(ch))
110 {
111 stream.eatWhile(/[\w_<>]/);
112 word = stream.current();
113 return ret("type", "variable-3", word);
114 }
115 else
116 {
95 } else {
96 var word;
97 if(/[A-Z]/.test(ch)) {
98 stream.eatWhile(/[\w_<>]/);
99 word = stream.current();
100 return ret("type", "variable-3", word);
101 } else {
117 102 stream.eatWhile(/[\w_]/);
118 103 var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
119 104 return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
120 105 ret("variable", "variable", word);
121 }
106 }
122 107 }
123 108 }
124 109
125 110 function haxeTokenString(quote) {
126 111 return function(stream, state) {
127 if (!nextUntilUnescaped(stream, quote))
112 if (toUnescaped(stream, quote))
128 113 state.tokenize = haxeTokenBase;
129 114 return ret("string", "string");
130 115 };
@@ -176,27 +161,25 b' CodeMirror.defineMode("haxe", function(c'
176 161 cc.pop()();
177 162 if (cx.marked) return cx.marked;
178 163 if (type == "variable" && inScope(state, content)) return "variable-2";
179 if (type == "variable" && imported(state, content)) return "variable-3";
164 if (type == "variable" && imported(state, content)) return "variable-3";
180 165 return style;
181 166 }
182 167 }
183 168 }
184 169
185 function imported(state, typename)
186 {
187 if (/[a-z]/.test(typename.charAt(0)))
188 return false;
189 var len = state.importedtypes.length;
190 for (var i = 0; i<len; i++)
191 if(state.importedtypes[i]==typename) return true;
170 function imported(state, typename) {
171 if (/[a-z]/.test(typename.charAt(0)))
172 return false;
173 var len = state.importedtypes.length;
174 for (var i = 0; i<len; i++)
175 if(state.importedtypes[i]==typename) return true;
192 176 }
193 177
194
195 178 function registerimport(importname) {
196 var state = cx.state;
197 for (var t = state.importedtypes; t; t = t.next)
198 if(t.name == importname) return;
199 state.importedtypes = { name: importname, next: state.importedtypes };
179 var state = cx.state;
180 for (var t = state.importedtypes; t; t = t.next)
181 if(t.name == importname) return;
182 state.importedtypes = { name: importname, next: state.importedtypes };
200 183 }
201 184 // Combinator utils
202 185
@@ -208,13 +191,20 b' CodeMirror.defineMode("haxe", function(c'
208 191 pass.apply(null, arguments);
209 192 return true;
210 193 }
194 function inList(name, list) {
195 for (var v = list; v; v = v.next)
196 if (v.name == name) return true;
197 return false;
198 }
211 199 function register(varname) {
212 200 var state = cx.state;
213 201 if (state.context) {
214 202 cx.marked = "def";
215 for (var v = state.localVars; v; v = v.next)
216 if (v.name == varname) return;
203 if (inList(varname, state.localVars)) return;
217 204 state.localVars = {name: varname, next: state.localVars};
205 } else if (state.globalVars) {
206 if (inList(varname, state.globalVars)) return;
207 state.globalVars = {name: varname, next: state.globalVars};
218 208 }
219 209 }
220 210
@@ -229,6 +219,7 b' CodeMirror.defineMode("haxe", function(c'
229 219 cx.state.localVars = cx.state.context.vars;
230 220 cx.state.context = cx.state.context.prev;
231 221 }
222 popcontext.lex = true;
232 223 function pushlex(type, info) {
233 224 var result = function() {
234 225 var state = cx.state;
@@ -252,7 +243,7 b' CodeMirror.defineMode("haxe", function(c'
252 243 if (type == wanted) return cont();
253 244 else if (wanted == ";") return pass();
254 245 else return cont(f);
255 };
246 }
256 247 return f;
257 248 }
258 249
@@ -266,25 +257,26 b' CodeMirror.defineMode("haxe", function(c'
266 257 if (type == "attribute") return cont(maybeattribute);
267 258 if (type == "function") return cont(functiondef);
268 259 if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
269 poplex, statement, poplex);
260 poplex, statement, poplex);
270 261 if (type == "variable") return cont(pushlex("stat"), maybelabel);
271 262 if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
272 block, poplex, poplex);
263 block, poplex, poplex);
273 264 if (type == "case") return cont(expression, expect(":"));
274 265 if (type == "default") return cont(expect(":"));
275 266 if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
276 statement, poplex, popcontext);
267 statement, poplex, popcontext);
277 268 if (type == "import") return cont(importdef, expect(";"));
278 269 if (type == "typedef") return cont(typedef);
279 270 return pass(pushlex("stat"), expression, expect(";"), poplex);
280 271 }
281 272 function expression(type) {
282 273 if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
274 if (type == "type" ) return cont(maybeoperator);
283 275 if (type == "function") return cont(functiondef);
284 276 if (type == "keyword c") return cont(maybeexpression);
285 277 if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
286 278 if (type == "operator") return cont(expression);
287 if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
279 if (type == "[") return cont(pushlex("]"), commasep(maybeexpression, "]"), poplex, maybeoperator);
288 280 if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
289 281 return cont();
290 282 }
@@ -318,14 +310,14 b' CodeMirror.defineMode("haxe", function(c'
318 310 }
319 311
320 312 function importdef (type, value) {
321 if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
322 else if(type == "variable" || type == "property" || type == "." || value == "*") return cont(importdef);
313 if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
314 else if(type == "variable" || type == "property" || type == "." || value == "*") return cont(importdef);
323 315 }
324 316
325 317 function typedef (type, value)
326 318 {
327 if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
328 else if (type == "type" && /[A-Z]/.test(value.charAt(0))) { return cont(); }
319 if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); }
320 else if (type == "type" && /[A-Z]/.test(value.charAt(0))) { return cont(); }
329 321 }
330 322
331 323 function maybelabel(type) {
@@ -363,16 +355,19 b' CodeMirror.defineMode("haxe", function(c'
363 355 if (type == ",") return cont(vardef1);
364 356 }
365 357 function forspec1(type, value) {
366 if (type == "variable") {
367 register(value);
368 }
369 return cont(pushlex(")"), pushcontext, forin, expression, poplex, statement, popcontext);
358 if (type == "variable") {
359 register(value);
360 return cont(forin, expression)
361 } else {
362 return pass()
363 }
370 364 }
371 365 function forin(_type, value) {
372 366 if (value == "in") return cont();
373 367 }
374 368 function functiondef(type, value) {
375 if (type == "variable") {register(value); return cont(functiondef);}
369 //function names starting with upper-case letters are recognised as types, so cludging them together here.
370 if (type == "variable" || type == "type") {register(value); return cont(functiondef);}
376 371 if (value == "new") return cont(functiondef);
377 372 if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, typeuse, statement, popcontext);
378 373 }
@@ -392,21 +387,23 b' CodeMirror.defineMode("haxe", function(c'
392 387 }
393 388
394 389 // Interface
395
396 390 return {
397 391 startState: function(basecolumn) {
398 var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"];
399 return {
392 var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"];
393 var state = {
400 394 tokenize: haxeTokenBase,
401 395 reAllowed: true,
402 396 kwAllowed: true,
403 397 cc: [],
404 398 lexical: new HaxeLexical((basecolumn || 0) - indentUnit, 0, "block", false),
405 399 localVars: parserConfig.localVars,
406 importedtypes: defaulttypes,
400 importedtypes: defaulttypes,
407 401 context: parserConfig.localVars && {vars: parserConfig.localVars},
408 402 indented: 0
409 403 };
404 if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
405 state.globalVars = parserConfig.globalVars;
406 return state;
410 407 },
411 408
412 409 token: function(stream, state) {
@@ -9,113 +9,142 b''
9 9 else // Plain browser env
10 10 mod(CodeMirror);
11 11 })(function(CodeMirror) {
12 "use strict";
13
14 CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
15 var htmlMode = CodeMirror.getMode(config, {name: "xml",
16 htmlMode: true,
17 multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
18 multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag});
19 var cssMode = CodeMirror.getMode(config, "css");
20
21 var scriptTypes = [], scriptTypesConf = parserConfig && parserConfig.scriptTypes;
22 scriptTypes.push({matches: /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i,
23 mode: CodeMirror.getMode(config, "javascript")});
24 if (scriptTypesConf) for (var i = 0; i < scriptTypesConf.length; ++i) {
25 var conf = scriptTypesConf[i];
26 scriptTypes.push({matches: conf.matches, mode: conf.mode && CodeMirror.getMode(config, conf.mode)});
27 }
28 scriptTypes.push({matches: /./,
29 mode: CodeMirror.getMode(config, "text/plain")});
12 "use strict";
30 13
31 function html(stream, state) {
32 var tagName = state.htmlState.tagName;
33 if (tagName) tagName = tagName.toLowerCase();
34 var style = htmlMode.token(stream, state.htmlState);
35 if (tagName == "script" && /\btag\b/.test(style) && stream.current() == ">") {
36 // Script block: mode to change to depends on type attribute
37 var scriptType = stream.string.slice(Math.max(0, stream.pos - 100), stream.pos).match(/\btype\s*=\s*("[^"]+"|'[^']+'|\S+)[^<]*$/i);
38 scriptType = scriptType ? scriptType[1] : "";
39 if (scriptType && /[\"\']/.test(scriptType.charAt(0))) scriptType = scriptType.slice(1, scriptType.length - 1);
40 for (var i = 0; i < scriptTypes.length; ++i) {
41 var tp = scriptTypes[i];
42 if (typeof tp.matches == "string" ? scriptType == tp.matches : tp.matches.test(scriptType)) {
43 if (tp.mode) {
44 state.token = script;
45 state.localMode = tp.mode;
46 state.localState = tp.mode.startState && tp.mode.startState(htmlMode.indent(state.htmlState, ""));
47 }
48 break;
49 }
50 }
51 } else if (tagName == "style" && /\btag\b/.test(style) && stream.current() == ">") {
52 state.token = css;
53 state.localMode = cssMode;
54 state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
55 }
56 return style;
57 }
14 var defaultTags = {
15 script: [
16 ["lang", /(javascript|babel)/i, "javascript"],
17 ["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i, "javascript"],
18 ["type", /./, "text/plain"],
19 [null, null, "javascript"]
20 ],
21 style: [
22 ["lang", /^css$/i, "css"],
23 ["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"],
24 ["type", /./, "text/plain"],
25 [null, null, "css"]
26 ]
27 };
28
58 29 function maybeBackup(stream, pat, style) {
59 var cur = stream.current();
60 var close = cur.search(pat);
61 if (close > -1) stream.backUp(cur.length - close);
62 else if (cur.match(/<\/?$/)) {
30 var cur = stream.current(), close = cur.search(pat);
31 if (close > -1) {
32 stream.backUp(cur.length - close);
33 } else if (cur.match(/<\/?$/)) {
63 34 stream.backUp(cur.length);
64 35 if (!stream.match(pat, false)) stream.match(cur);
65 36 }
66 37 return style;
67 38 }
68 function script(stream, state) {
69 if (stream.match(/^<\/\s*script\s*>/i, false)) {
70 state.token = html;
71 state.localState = state.localMode = null;
72 return null;
73 }
74 return maybeBackup(stream, /<\/\s*script\s*>/,
75 state.localMode.token(stream, state.localState));
39
40 var attrRegexpCache = {};
41 function getAttrRegexp(attr) {
42 var regexp = attrRegexpCache[attr];
43 if (regexp) return regexp;
44 return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*");
45 }
46
47 function getAttrValue(stream, attr) {
48 var pos = stream.pos, match;
49 while (pos >= 0 && stream.string.charAt(pos) !== "<") pos--;
50 if (pos < 0) return pos;
51 if (match = stream.string.slice(pos, stream.pos).match(getAttrRegexp(attr)))
52 return match[2];
53 return "";
76 54 }
77 function css(stream, state) {
78 if (stream.match(/^<\/\s*style\s*>/i, false)) {
79 state.token = html;
80 state.localState = state.localMode = null;
81 return null;
55
56 function getTagRegexp(tagName, anchored) {
57 return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i");
58 }
59
60 function addTags(from, to) {
61 for (var tag in from) {
62 var dest = to[tag] || (to[tag] = []);
63 var source = from[tag];
64 for (var i = source.length - 1; i >= 0; i--)
65 dest.unshift(source[i])
82 66 }
83 return maybeBackup(stream, /<\/\s*style\s*>/,
84 cssMode.token(stream, state.localState));
67 }
68
69 function findMatchingMode(tagInfo, stream) {
70 for (var i = 0; i < tagInfo.length; i++) {
71 var spec = tagInfo[i];
72 if (!spec[0] || spec[1].test(getAttrValue(stream, spec[0]))) return spec[2];
73 }
85 74 }
86 75
87 return {
88 startState: function() {
89 var state = htmlMode.startState();
90 return {token: html, localMode: null, localState: null, htmlState: state};
91 },
76 CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
77 var htmlMode = CodeMirror.getMode(config, {
78 name: "xml",
79 htmlMode: true,
80 multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
81 multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag
82 });
92 83
93 copyState: function(state) {
94 if (state.localState)
95 var local = CodeMirror.copyState(state.localMode, state.localState);
96 return {token: state.token, localMode: state.localMode, localState: local,
97 htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
98 },
84 var tags = {};
85 var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;
86 addTags(defaultTags, tags);
87 if (configTags) addTags(configTags, tags);
88 if (configScript) for (var i = configScript.length - 1; i >= 0; i--)
89 tags.script.unshift(["type", configScript[i].matches, configScript[i].mode])
99 90
100 token: function(stream, state) {
101 return state.token(stream, state);
102 },
91 function html(stream, state) {
92 var tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase();
93 var tagInfo = tagName && tags.hasOwnProperty(tagName) && tags[tagName];
94
95 var style = htmlMode.token(stream, state.htmlState), modeSpec;
103 96
104 indent: function(state, textAfter) {
105 if (!state.localMode || /^\s*<\//.test(textAfter))
106 return htmlMode.indent(state.htmlState, textAfter);
107 else if (state.localMode.indent)
108 return state.localMode.indent(state.localState, textAfter);
109 else
110 return CodeMirror.Pass;
111 },
97 if (tagInfo && /\btag\b/.test(style) && stream.current() === ">" &&
98 (modeSpec = findMatchingMode(tagInfo, stream))) {
99 var mode = CodeMirror.getMode(config, modeSpec);
100 var endTagA = getTagRegexp(tagName, true), endTag = getTagRegexp(tagName, false);
101 state.token = function (stream, state) {
102 if (stream.match(endTagA, false)) {
103 state.token = html;
104 state.localState = state.localMode = null;
105 return null;
106 }
107 return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
108 };
109 state.localMode = mode;
110 state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, ""));
111 }
112 return style;
113 };
114
115 return {
116 startState: function () {
117 var state = htmlMode.startState();
118 return {token: html, localMode: null, localState: null, htmlState: state};
119 },
112 120
113 innerMode: function(state) {
114 return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
115 }
116 };
117 }, "xml", "javascript", "css");
121 copyState: function (state) {
122 var local;
123 if (state.localState) {
124 local = CodeMirror.copyState(state.localMode, state.localState);
125 }
126 return {token: state.token, localMode: state.localMode, localState: local,
127 htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
128 },
129
130 token: function (stream, state) {
131 return state.token(stream, state);
132 },
118 133
119 CodeMirror.defineMIME("text/html", "htmlmixed");
134 indent: function (state, textAfter) {
135 if (!state.localMode || /^\s*<\//.test(textAfter))
136 return htmlMode.indent(state.htmlState, textAfter);
137 else if (state.localMode.indent)
138 return state.localMode.indent(state.localState, textAfter);
139 else
140 return CodeMirror.Pass;
141 },
120 142
143 innerMode: function (state) {
144 return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
145 }
146 };
147 }, "xml", "javascript", "css");
148
149 CodeMirror.defineMIME("text/html", "htmlmixed");
121 150 });
@@ -74,7 +74,7 b" CodeMirror.defineMode('jade', function ("
74 74 res.javaScriptArguments = this.javaScriptArguments;
75 75 res.javaScriptArgumentsDepth = this.javaScriptArgumentsDepth;
76 76 res.isInterpolating = this.isInterpolating;
77 res.interpolationNesting = this.intpolationNesting;
77 res.interpolationNesting = this.interpolationNesting;
78 78
79 79 res.jsState = CodeMirror.copyState(jsMode, this.jsState);
80 80
@@ -167,7 +167,7 b" CodeMirror.defineMode('jade', function ("
167 167 if (state.interpolationNesting < 0) {
168 168 stream.next();
169 169 state.isInterpolating = false;
170 return 'puncutation';
170 return 'punctuation';
171 171 }
172 172 } else if (stream.peek() === '{') {
173 173 state.interpolationNesting++;
@@ -583,7 +583,7 b" CodeMirror.defineMode('jade', function ("
583 583 copyState: copyState,
584 584 token: nextToken
585 585 };
586 });
586 }, 'javascript', 'css', 'htmlmixed');
587 587
588 588 CodeMirror.defineMIME('text/x-jade', 'jade');
589 589
@@ -13,6 +13,11 b''
13 13 })(function(CodeMirror) {
14 14 "use strict";
15 15
16 function expressionAllowed(stream, state, backUp) {
17 return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
18 (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
19 }
20
16 21 CodeMirror.defineMode("javascript", function(config, parserConfig) {
17 22 var indentUnit = config.indentUnit;
18 23 var statementIndent = parserConfig.statementIndent;
@@ -30,13 +35,13 b' CodeMirror.defineMode("javascript", func'
30 35
31 36 var jsKeywords = {
32 37 "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
33 "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
38 "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C,
34 39 "var": kw("var"), "const": kw("var"), "let": kw("var"),
35 40 "function": kw("function"), "catch": kw("catch"),
36 41 "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
37 42 "in": operator, "typeof": operator, "instanceof": operator,
38 43 "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
39 "this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"),
44 "this": kw("this"), "class": kw("class"), "super": kw("atom"),
40 45 "yield": C, "export": kw("export"), "import": kw("import"), "extends": C
41 46 };
42 47
@@ -45,18 +50,23 b' CodeMirror.defineMode("javascript", func'
45 50 var type = {type: "variable", style: "variable-3"};
46 51 var tsKeywords = {
47 52 // object-like things
48 "interface": kw("interface"),
49 "extends": kw("extends"),
50 "constructor": kw("constructor"),
53 "interface": kw("class"),
54 "implements": C,
55 "namespace": C,
56 "module": kw("module"),
57 "enum": kw("module"),
51 58
52 59 // scope modifiers
53 "public": kw("public"),
54 "private": kw("private"),
55 "protected": kw("protected"),
56 "static": kw("static"),
60 "public": kw("modifier"),
61 "private": kw("modifier"),
62 "protected": kw("modifier"),
63 "abstract": kw("modifier"),
64
65 // operators
66 "as": operator,
57 67
58 68 // types
59 "string": type, "number": type, "bool": type, "any": type
69 "string": type, "number": type, "boolean": type, "any": type
60 70 };
61 71
62 72 for (var attr in tsKeywords) {
@@ -105,6 +115,12 b' CodeMirror.defineMode("javascript", func'
105 115 } else if (ch == "0" && stream.eat(/x/i)) {
106 116 stream.eatWhile(/[\da-f]/i);
107 117 return ret("number", "number");
118 } else if (ch == "0" && stream.eat(/o/i)) {
119 stream.eatWhile(/[0-7]/i);
120 return ret("number", "number");
121 } else if (ch == "0" && stream.eat(/b/i)) {
122 stream.eatWhile(/[01]/i);
123 return ret("number", "number");
108 124 } else if (/\d/.test(ch)) {
109 125 stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
110 126 return ret("number", "number");
@@ -115,8 +131,7 b' CodeMirror.defineMode("javascript", func'
115 131 } else if (stream.eat("/")) {
116 132 stream.skipToEnd();
117 133 return ret("comment", "comment");
118 } else if (state.lastType == "operator" || state.lastType == "keyword c" ||
119 state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
134 } else if (expressionAllowed(stream, state, 1)) {
120 135 readRegexp(stream);
121 136 stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
122 137 return ret("regexp", "string-2");
@@ -275,8 +290,8 b' CodeMirror.defineMode("javascript", func'
275 290 return false;
276 291 }
277 292 var state = cx.state;
293 cx.marked = "def";
278 294 if (state.context) {
279 cx.marked = "def";
280 295 if (inList(state.localVars)) return;
281 296 state.localVars = {name: varname, next: state.localVars};
282 297 } else {
@@ -347,10 +362,10 b' CodeMirror.defineMode("javascript", func'
347 362 if (type == "default") return cont(expect(":"));
348 363 if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
349 364 statement, poplex, popcontext);
350 if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
351 365 if (type == "class") return cont(pushlex("form"), className, poplex);
352 if (type == "export") return cont(pushlex("form"), afterExport, poplex);
353 if (type == "import") return cont(pushlex("form"), afterImport, poplex);
366 if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
367 if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
368 if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex)
354 369 return pass(pushlex("stat"), expression, expect(";"), poplex);
355 370 }
356 371 function expression(type) {
@@ -374,7 +389,8 b' CodeMirror.defineMode("javascript", func'
374 389 if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
375 390 if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
376 391 if (type == "{") return contCommasep(objprop, "}", null, maybeop);
377 if (type == "quasi") { return pass(quasi, maybeop); }
392 if (type == "quasi") return pass(quasi, maybeop);
393 if (type == "new") return cont(maybeTarget(noComma));
378 394 return cont();
379 395 }
380 396 function maybeexpression(type) {
@@ -425,6 +441,18 b' CodeMirror.defineMode("javascript", func'
425 441 findFatArrow(cx.stream, cx.state);
426 442 return pass(type == "{" ? statement : expressionNoComma);
427 443 }
444 function maybeTarget(noComma) {
445 return function(type) {
446 if (type == ".") return cont(noComma ? targetNoComma : target);
447 else return pass(noComma ? expressionNoComma : expression);
448 };
449 }
450 function target(_, value) {
451 if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
452 }
453 function targetNoComma(_, value) {
454 if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
455 }
428 456 function maybelabel(type) {
429 457 if (type == ":") return cont(poplex, statement);
430 458 return pass(maybeoperatorComma, expect(";"), poplex);
@@ -442,8 +470,12 b' CodeMirror.defineMode("javascript", func'
442 470 return cont(afterprop);
443 471 } else if (type == "jsonld-keyword") {
444 472 return cont(afterprop);
473 } else if (type == "modifier") {
474 return cont(objprop)
445 475 } else if (type == "[") {
446 476 return cont(expression, expect("]"), afterprop);
477 } else if (type == "spread") {
478 return cont(expression);
447 479 }
448 480 }
449 481 function getterSetter(type) {
@@ -492,7 +524,9 b' CodeMirror.defineMode("javascript", func'
492 524 return pass(pattern, maybetype, maybeAssign, vardefCont);
493 525 }
494 526 function pattern(type, value) {
527 if (type == "modifier") return cont(pattern)
495 528 if (type == "variable") { register(value); return cont(); }
529 if (type == "spread") return cont(pattern);
496 530 if (type == "[") return contCommasep(pattern, "]");
497 531 if (type == "{") return contCommasep(proppattern, "}");
498 532 }
@@ -502,6 +536,8 b' CodeMirror.defineMode("javascript", func'
502 536 return cont(maybeAssign);
503 537 }
504 538 if (type == "variable") cx.marked = "property";
539 if (type == "spread") return cont(pattern);
540 if (type == "}") return pass();
505 541 return cont(expect(":"), pattern, maybeAssign);
506 542 }
507 543 function maybeAssign(_type, value) {
@@ -572,10 +608,6 b' CodeMirror.defineMode("javascript", func'
572 608 cx.marked = "property";
573 609 return cont();
574 610 }
575 function afterModule(type, value) {
576 if (type == "string") return cont(statement);
577 if (type == "variable") { register(value); return cont(maybeFrom); }
578 }
579 611 function afterExport(_type, value) {
580 612 if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
581 613 if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
@@ -628,7 +660,7 b' CodeMirror.defineMode("javascript", func'
628 660 lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
629 661 localVars: parserConfig.localVars,
630 662 context: parserConfig.localVars && {vars: parserConfig.localVars},
631 indented: 0
663 indented: basecolumn || 0
632 664 };
633 665 if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
634 666 state.globalVars = parserConfig.globalVars;
@@ -684,7 +716,13 b' CodeMirror.defineMode("javascript", func'
684 716
685 717 helperType: jsonMode ? "json" : "javascript",
686 718 jsonldMode: jsonldMode,
687 jsonMode: jsonMode
719 jsonMode: jsonMode,
720
721 expressionAllowed: expressionAllowed,
722 skipExpression: function(state) {
723 var top = state.cc[state.cc.length - 1]
724 if (top == expression || top == expressionNoComma) state.cc.pop()
725 }
688 726 };
689 727 });
690 728
@@ -18,35 +18,34 b' CodeMirror.defineMode("julia", function('
18 18 return new RegExp("^((" + words.join(")|(") + "))\\b");
19 19 }
20 20
21 var operators = parserConf.operators || /^\.?[|&^\\%*+\-<>!=\/]=?|\?|~|:|\$|\.[<>]|<<=?|>>>?=?|\.[<>=]=|->?|\/\/|\bin\b/;
21 var operators = parserConf.operators || /^\.?[|&^\\%*+\-<>!=\/]=?|\?|~|:|\$|\.[<>]|<<=?|>>>?=?|\.[<>=]=|->?|\/\/|\bin\b(?!\()|[\u2208\u2209](?!\()/;
22 22 var delimiters = parserConf.delimiters || /^[;,()[\]{}]/;
23 var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*!*/;
23 var identifiers = parserConf.identifiers || /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*!*/;
24 24 var blockOpeners = ["begin", "function", "type", "immutable", "let", "macro", "for", "while", "quote", "if", "else", "elseif", "try", "finally", "catch", "do"];
25 25 var blockClosers = ["end", "else", "elseif", "catch", "finally"];
26 var keywordList = ['if', 'else', 'elseif', 'while', 'for', 'begin', 'let', 'end', 'do', 'try', 'catch', 'finally', 'return', 'break', 'continue', 'global', 'local', 'const', 'export', 'import', 'importall', 'using', 'function', 'macro', 'module', 'baremodule', 'type', 'immutable', 'quote', 'typealias', 'abstract', 'bitstype', 'ccall'];
27 var builtinList = ['true', 'false', 'enumerate', 'open', 'close', 'nothing', 'NaN', 'Inf', 'print', 'println', 'Int', 'Int8', 'Uint8', 'Int16', 'Uint16', 'Int32', 'Uint32', 'Int64', 'Uint64', 'Int128', 'Uint128', 'Bool', 'Char', 'Float16', 'Float32', 'Float64', 'Array', 'Vector', 'Matrix', 'String', 'UTF8String', 'ASCIIString', 'error', 'warn', 'info', '@printf'];
26 var keywordList = ['if', 'else', 'elseif', 'while', 'for', 'begin', 'let', 'end', 'do', 'try', 'catch', 'finally', 'return', 'break', 'continue', 'global', 'local', 'const', 'export', 'import', 'importall', 'using', 'function', 'macro', 'module', 'baremodule', 'type', 'immutable', 'quote', 'typealias', 'abstract', 'bitstype'];
27 var builtinList = ['true', 'false', 'nothing', 'NaN', 'Inf'];
28 28
29 29 //var stringPrefixes = new RegExp("^[br]?('|\")")
30 var stringPrefixes = /^(`|'|"{3}|([br]?"))/;
30 var stringPrefixes = /^(`|'|"{3}|([brv]?"))/;
31 31 var keywords = wordRegexp(keywordList);
32 32 var builtins = wordRegexp(builtinList);
33 33 var openers = wordRegexp(blockOpeners);
34 34 var closers = wordRegexp(blockClosers);
35 35 var macro = /^@[_A-Za-z][_A-Za-z0-9]*/;
36 var symbol = /^:[_A-Za-z][_A-Za-z0-9]*/;
36 var symbol = /^:[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*!*/;
37 var typeAnnotation = /^::[^.,;"{()=$\s]+({[^}]*}+)*/;
37 38
38 function in_array(state) {
39 var ch = cur_scope(state);
40 if(ch=="[" || ch=="{") {
39 function inArray(state) {
40 var ch = currentScope(state);
41 if (ch == '[') {
41 42 return true;
42 43 }
43 else {
44 return false;
45 }
44 return false;
46 45 }
47 46
48 function cur_scope(state) {
49 if(state.scopes.length==0) {
47 function currentScope(state) {
48 if (state.scopes.length == 0) {
50 49 return null;
51 50 }
52 51 return state.scopes[state.scopes.length - 1];
@@ -54,20 +53,34 b' CodeMirror.defineMode("julia", function('
54 53
55 54 // tokenizers
56 55 function tokenBase(stream, state) {
56 //Handle multiline comments
57 if (stream.match(/^#=\s*/)) {
58 state.scopes.push('#=');
59 }
60 if (currentScope(state) == '#=' && stream.match(/^=#/)) {
61 state.scopes.pop();
62 return 'comment';
63 }
64 if (state.scopes.indexOf('#=') >= 0) {
65 if (!stream.match(/.*?(?=(#=|=#))/)) {
66 stream.skipToEnd();
67 }
68 return 'comment';
69 }
70
57 71 // Handle scope changes
58 var leaving_expr = state.leaving_expr;
59 if(stream.sol()) {
60 leaving_expr = false;
72 var leavingExpr = state.leavingExpr;
73 if (stream.sol()) {
74 leavingExpr = false;
61 75 }
62 state.leaving_expr = false;
63 if(leaving_expr) {
64 if(stream.match(/^'+/)) {
76 state.leavingExpr = false;
77 if (leavingExpr) {
78 if (stream.match(/^'+/)) {
65 79 return 'operator';
66 80 }
67
68 81 }
69 82
70 if(stream.match(/^\.{2,3}/)) {
83 if (stream.match(/^\.{2,3}/)) {
71 84 return 'operator';
72 85 }
73 86
@@ -76,56 +89,51 b' CodeMirror.defineMode("julia", function('
76 89 }
77 90
78 91 var ch = stream.peek();
79 // Handle Comments
92
93 // Handle single line comments
80 94 if (ch === '#') {
81 stream.skipToEnd();
82 return 'comment';
83 }
84 if(ch==='[') {
85 state.scopes.push("[");
95 stream.skipToEnd();
96 return 'comment';
86 97 }
87 98
88 if(ch==='{') {
89 state.scopes.push("{");
99 if (ch === '[') {
100 state.scopes.push('[');
90 101 }
91 102
92 var scope=cur_scope(state);
103 var scope = currentScope(state);
93 104
94 if(scope==='[' && ch===']') {
105 if (scope == '[' && ch === ']') {
95 106 state.scopes.pop();
96 state.leaving_expr=true;
107 state.leavingExpr = true;
97 108 }
98 109
99 if(scope==='{' && ch==='}') {
110 if (scope == '(' && ch === ')') {
100 111 state.scopes.pop();
101 state.leaving_expr=true;
102 }
103
104 if(ch===')') {
105 state.leaving_expr = true;
112 state.leavingExpr = true;
106 113 }
107 114
108 115 var match;
109 if(!in_array(state) && (match=stream.match(openers, false))) {
116 if (!inArray(state) && (match=stream.match(openers, false))) {
110 117 state.scopes.push(match);
111 118 }
112 119
113 if(!in_array(state) && stream.match(closers, false)) {
120 if (!inArray(state) && stream.match(closers, false)) {
114 121 state.scopes.pop();
115 122 }
116 123
117 if(in_array(state)) {
118 if(stream.match(/^end/)) {
124 if (inArray(state)) {
125 if (state.lastToken == 'end' && stream.match(/^:/)) {
126 return 'operator';
127 }
128 if (stream.match(/^end/)) {
119 129 return 'number';
120 130 }
121
122 131 }
123 132
124 if(stream.match(/^=>/)) {
133 if (stream.match(/^=>/)) {
125 134 return 'operator';
126 135 }
127 136
128
129 137 // Handle Number Literals
130 138 if (stream.match(/^[0-9\.]/, false)) {
131 139 var imMatcher = RegExp(/^im\b/);
@@ -134,10 +142,11 b' CodeMirror.defineMode("julia", function('
134 142 if (stream.match(/^\d*\.(?!\.)\d+([ef][\+\-]?\d+)?/i)) { floatLiteral = true; }
135 143 if (stream.match(/^\d+\.(?!\.)\d*/)) { floatLiteral = true; }
136 144 if (stream.match(/^\.\d+/)) { floatLiteral = true; }
145 if (stream.match(/^0x\.[0-9a-f]+p[\+\-]?\d+/i)) { floatLiteral = true; }
137 146 if (floatLiteral) {
138 147 // Float literals may be "imaginary"
139 148 stream.match(imMatcher);
140 state.leaving_expr = true;
149 state.leavingExpr = true;
141 150 return 'number';
142 151 }
143 152 // Integers
@@ -157,18 +166,27 b' CodeMirror.defineMode("julia", function('
157 166 if (intLiteral) {
158 167 // Integer literals may be "long"
159 168 stream.match(imMatcher);
160 state.leaving_expr = true;
169 state.leavingExpr = true;
161 170 return 'number';
162 171 }
163 172 }
164 173
165 if(stream.match(/^(::)|(<:)/)) {
174 if (stream.match(/^<:/)) {
166 175 return 'operator';
167 176 }
168 177
178 if (stream.match(typeAnnotation)) {
179 return 'builtin';
180 }
181
169 182 // Handle symbols
170 if(!leaving_expr && stream.match(symbol)) {
171 return 'string';
183 if (!leavingExpr && stream.match(symbol) || stream.match(/:\./)) {
184 return 'builtin';
185 }
186
187 // Handle parametric types
188 if (stream.match(/^{[^}]*}(?=\()/)) {
189 return 'builtin';
172 190 }
173 191
174 192 // Handle operators and Delimiters
@@ -176,7 +194,6 b' CodeMirror.defineMode("julia", function('
176 194 return 'operator';
177 195 }
178 196
179
180 197 // Handle Strings
181 198 if (stream.match(stringPrefixes)) {
182 199 state.tokenize = tokenStringFactory(stream.current());
@@ -187,7 +204,6 b' CodeMirror.defineMode("julia", function('
187 204 return 'meta';
188 205 }
189 206
190
191 207 if (stream.match(delimiters)) {
192 208 return null;
193 209 }
@@ -200,21 +216,74 b' CodeMirror.defineMode("julia", function('
200 216 return 'builtin';
201 217 }
202 218
219 var isDefinition = state.isDefinition ||
220 state.lastToken == 'function' ||
221 state.lastToken == 'macro' ||
222 state.lastToken == 'type' ||
223 state.lastToken == 'immutable';
203 224
204 225 if (stream.match(identifiers)) {
205 state.leaving_expr=true;
226 if (isDefinition) {
227 if (stream.peek() === '.') {
228 state.isDefinition = true;
229 return 'variable';
230 }
231 state.isDefinition = false;
232 return 'def';
233 }
234 if (stream.match(/^({[^}]*})*\(/, false)) {
235 return callOrDef(stream, state);
236 }
237 state.leavingExpr = true;
206 238 return 'variable';
207 239 }
240
208 241 // Handle non-detected items
209 242 stream.next();
210 243 return ERRORCLASS;
211 244 }
212 245
246 function callOrDef(stream, state) {
247 var match = stream.match(/^(\(\s*)/);
248 if (match) {
249 if (state.firstParenPos < 0)
250 state.firstParenPos = state.scopes.length;
251 state.scopes.push('(');
252 state.charsAdvanced += match[1].length;
253 }
254 if (currentScope(state) == '(' && stream.match(/^\)/)) {
255 state.scopes.pop();
256 state.charsAdvanced += 1;
257 if (state.scopes.length <= state.firstParenPos) {
258 var isDefinition = stream.match(/^\s*?=(?!=)/, false);
259 stream.backUp(state.charsAdvanced);
260 state.firstParenPos = -1;
261 state.charsAdvanced = 0;
262 if (isDefinition)
263 return 'def';
264 return 'builtin';
265 }
266 }
267 // Unfortunately javascript does not support multiline strings, so we have
268 // to undo anything done upto here if a function call or definition splits
269 // over two or more lines.
270 if (stream.match(/^$/g, false)) {
271 stream.backUp(state.charsAdvanced);
272 while (state.scopes.length > state.firstParenPos + 1)
273 state.scopes.pop();
274 state.firstParenPos = -1;
275 state.charsAdvanced = 0;
276 return 'builtin';
277 }
278 state.charsAdvanced += stream.match(/^([^()]*)/)[1].length;
279 return callOrDef(stream, state);
280 }
281
213 282 function tokenStringFactory(delimiter) {
214 while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
283 while ('bruv'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) {
215 284 delimiter = delimiter.substr(1);
216 285 }
217 var singleline = delimiter.length == 1;
286 var singleline = delimiter == "'";
218 287 var OUTCLASS = 'string';
219 288
220 289 function tokenString(stream, state) {
@@ -245,45 +314,41 b' CodeMirror.defineMode("julia", function('
245 314 return tokenString;
246 315 }
247 316
248 function tokenLexer(stream, state) {
249 var style = state.tokenize(stream, state);
250 var current = stream.current();
251
252 // Handle '.' connected identifiers
253 if (current === '.') {
254 style = stream.match(identifiers, false) ? null : ERRORCLASS;
255 if (style === null && state.lastStyle === 'meta') {
256 // Apply 'meta' style to '.' connected identifiers when
257 // appropriate.
258 style = 'meta';
259 }
260 return style;
261 }
262
263 return style;
264 }
265
266 317 var external = {
267 318 startState: function() {
268 319 return {
269 320 tokenize: tokenBase,
270 321 scopes: [],
271 leaving_expr: false
322 lastToken: null,
323 leavingExpr: false,
324 isDefinition: false,
325 charsAdvanced: 0,
326 firstParenPos: -1
272 327 };
273 328 },
274 329
275 330 token: function(stream, state) {
276 var style = tokenLexer(stream, state);
277 state.lastStyle = style;
331 var style = state.tokenize(stream, state);
332 var current = stream.current();
333
334 if (current && style) {
335 state.lastToken = current;
336 }
337
338 // Handle '.' connected identifiers
339 if (current === '.') {
340 style = stream.match(identifiers, false) || stream.match(macro, false) ||
341 stream.match(/\(/, false) ? 'operator' : ERRORCLASS;
342 }
278 343 return style;
279 344 },
280 345
281 346 indent: function(state, textAfter) {
282 347 var delta = 0;
283 if(textAfter=="end" || textAfter=="]" || textAfter=="}" || textAfter=="else" || textAfter=="elseif" || textAfter=="catch" || textAfter=="finally") {
348 if (textAfter == "end" || textAfter == "]" || textAfter == "}" || textAfter == "else" || textAfter == "elseif" || textAfter == "catch" || textAfter == "finally") {
284 349 delta = -1;
285 350 }
286 return (state.scopes.length + delta) * 4;
351 return (state.scopes.length + delta) * _conf.indentUnit;
287 352 },
288 353
289 354 lineComment: "#",
@@ -39,8 +39,10 b' CodeMirror.defineMode("markdown", functi'
39 39 if (modeCfg.underscoresBreakWords === undefined)
40 40 modeCfg.underscoresBreakWords = true;
41 41
42 // Turn on fenced code blocks? ("```" to start/end)
43 if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false;
42 // Use `fencedCodeBlocks` to configure fenced code blocks. false to
43 // disable, string to specify a precise regexp that the fence should
44 // match, and true to allow three or more backticks or tildes (as
45 // per CommonMark).
44 46
45 47 // Turn on task lists? ("- [ ] " and "- [x] ")
46 48 if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
@@ -49,32 +51,46 b' CodeMirror.defineMode("markdown", functi'
49 51 if (modeCfg.strikethrough === undefined)
50 52 modeCfg.strikethrough = false;
51 53
54 // Allow token types to be overridden by user-provided token types.
55 if (modeCfg.tokenTypeOverrides === undefined)
56 modeCfg.tokenTypeOverrides = {};
57
52 58 var codeDepth = 0;
53 59
54 var header = 'header'
55 , code = 'comment'
56 , quote = 'quote'
57 , list1 = 'variable-2'
58 , list2 = 'variable-3'
59 , list3 = 'keyword'
60 , hr = 'hr'
61 , image = 'tag'
62 , formatting = 'formatting'
63 , linkinline = 'link'
64 , linkemail = 'link'
65 , linktext = 'link'
66 , linkhref = 'string'
67 , em = 'em'
68 , strong = 'strong'
69 , strikethrough = 'strikethrough';
60 var tokenTypes = {
61 header: "header",
62 code: "comment",
63 quote: "quote",
64 list1: "variable-2",
65 list2: "variable-3",
66 list3: "keyword",
67 hr: "hr",
68 image: "tag",
69 formatting: "formatting",
70 linkInline: "link",
71 linkEmail: "link",
72 linkText: "link",
73 linkHref: "string",
74 em: "em",
75 strong: "strong",
76 strikethrough: "strikethrough"
77 };
78
79 for (var tokenType in tokenTypes) {
80 if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) {
81 tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType];
82 }
83 }
70 84
71 85 var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/
72 86 , ulRE = /^[*\-+]\s+/
73 87 , olRE = /^[0-9]+([.)])\s+/
74 88 , taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
75 , atxHeaderRE = /^(#+)(?: |$)/
89 , atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/
76 90 , setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/
77 , textRE = /^[^#!\[\]*_\\<>` "'(~]+/;
91 , textRE = /^[^#!\[\]*_\\<>` "'(~]+/
92 , fencedCodeRE = new RegExp("^(" + (modeCfg.fencedCodeBlocks === true ? "~~~+|```+" : modeCfg.fencedCodeBlocks) +
93 ")[ \\t]*([\\w+#]*)");
78 94
79 95 function switchInline(stream, state, f) {
80 96 state.f = state.inline = f;
@@ -86,6 +102,9 b' CodeMirror.defineMode("markdown", functi'
86 102 return f(stream, state);
87 103 }
88 104
105 function lineIsEmpty(line) {
106 return !line || !/\S/.test(line.string)
107 }
89 108
90 109 // Blocks
91 110
@@ -110,7 +129,8 b' CodeMirror.defineMode("markdown", functi'
110 129 state.trailingSpace = 0;
111 130 state.trailingSpaceNewLine = false;
112 131 // Mark this line as blank
113 state.thisLineHasContent = false;
132 state.prevLine = state.thisLine
133 state.thisLine = null
114 134 return null;
115 135 }
116 136
@@ -141,10 +161,10 b' CodeMirror.defineMode("markdown", functi'
141 161 var match = null;
142 162 if (state.indentationDiff >= 4) {
143 163 stream.skipToEnd();
144 if (prevLineIsIndentedCode || !state.prevLineHasContent) {
164 if (prevLineIsIndentedCode || lineIsEmpty(state.prevLine)) {
145 165 state.indentation -= 4;
146 166 state.indentedCode = true;
147 return code;
167 return tokenTypes.code;
148 168 } else {
149 169 return null;
150 170 }
@@ -155,7 +175,8 b' CodeMirror.defineMode("markdown", functi'
155 175 if (modeCfg.highlightFormatting) state.formatting = "header";
156 176 state.f = state.inline;
157 177 return getType(state);
158 } else if (state.prevLineHasContent && !state.quote && !prevLineIsList && !prevLineIsIndentedCode && (match = stream.match(setextHeaderRE))) {
178 } else if (!lineIsEmpty(state.prevLine) && !state.quote && !prevLineIsList &&
179 !prevLineIsIndentedCode && (match = stream.match(setextHeaderRE))) {
159 180 state.header = match[0].charAt(0) == '=' ? 1 : 2;
160 181 if (modeCfg.highlightFormatting) state.formatting = "header";
161 182 state.f = state.inline;
@@ -169,8 +190,8 b' CodeMirror.defineMode("markdown", functi'
169 190 return switchInline(stream, state, footnoteLink);
170 191 } else if (stream.match(hrRE, true)) {
171 192 state.hr = true;
172 return hr;
173 } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) {
193 return tokenTypes.hr;
194 } else if ((lineIsEmpty(state.prevLine) || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) {
174 195 var listType = null;
175 196 if (stream.match(ulRE, true)) {
176 197 listType = 'ul';
@@ -178,7 +199,7 b' CodeMirror.defineMode("markdown", functi'
178 199 stream.match(olRE, true);
179 200 listType = 'ol';
180 201 }
181 state.indentation += 4;
202 state.indentation = stream.column() + stream.current().length;
182 203 state.list = true;
183 204 state.listDepth++;
184 205 if (modeCfg.taskLists && stream.match(taskListRE, false)) {
@@ -187,9 +208,10 b' CodeMirror.defineMode("markdown", functi'
187 208 state.f = state.inline;
188 209 if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
189 210 return getType(state);
190 } else if (modeCfg.fencedCodeBlocks && stream.match(/^```[ \t]*([\w+#]*)/, true)) {
211 } else if (modeCfg.fencedCodeBlocks && (match = stream.match(fencedCodeRE, true))) {
212 state.fencedChars = match[1]
191 213 // try switching mode
192 state.localMode = getMode(RegExp.$1);
214 state.localMode = getMode(match[2]);
193 215 if (state.localMode) state.localState = state.localMode.startState();
194 216 state.f = state.block = local;
195 217 if (modeCfg.highlightFormatting) state.formatting = "code-block";
@@ -202,7 +224,8 b' CodeMirror.defineMode("markdown", functi'
202 224
203 225 function htmlBlock(stream, state) {
204 226 var style = htmlMode.token(stream, state.htmlState);
205 if ((htmlFound && state.htmlState.tagStart === null && !state.htmlState.context) ||
227 if ((htmlFound && state.htmlState.tagStart === null &&
228 (!state.htmlState.context && state.htmlState.tokenize.isInText)) ||
206 229 (state.md_inside && stream.current().indexOf(">") > -1)) {
207 230 state.f = inlineNormal;
208 231 state.block = blockNormal;
@@ -212,7 +235,7 b' CodeMirror.defineMode("markdown", functi'
212 235 }
213 236
214 237 function local(stream, state) {
215 if (stream.sol() && stream.match("```", false)) {
238 if (state.fencedChars && stream.match(state.fencedChars, false)) {
216 239 state.localMode = state.localState = null;
217 240 state.f = state.block = leavingLocal;
218 241 return null;
@@ -220,14 +243,15 b' CodeMirror.defineMode("markdown", functi'
220 243 return state.localMode.token(stream, state.localState);
221 244 } else {
222 245 stream.skipToEnd();
223 return code;
246 return tokenTypes.code;
224 247 }
225 248 }
226 249
227 250 function leavingLocal(stream, state) {
228 stream.match("```");
251 stream.match(state.fencedChars);
229 252 state.block = blockNormal;
230 253 state.f = inlineNormal;
254 state.fencedChars = null;
231 255 if (modeCfg.highlightFormatting) state.formatting = "code-block";
232 256 state.code = true;
233 257 var returnType = getType(state);
@@ -240,22 +264,22 b' CodeMirror.defineMode("markdown", functi'
240 264 var styles = [];
241 265
242 266 if (state.formatting) {
243 styles.push(formatting);
267 styles.push(tokenTypes.formatting);
244 268
245 269 if (typeof state.formatting === "string") state.formatting = [state.formatting];
246 270
247 271 for (var i = 0; i < state.formatting.length; i++) {
248 styles.push(formatting + "-" + state.formatting[i]);
272 styles.push(tokenTypes.formatting + "-" + state.formatting[i]);
249 273
250 274 if (state.formatting[i] === "header") {
251 styles.push(formatting + "-" + state.formatting[i] + "-" + state.header);
275 styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.header);
252 276 }
253 277
254 278 // Add `formatting-quote` and `formatting-quote-#` for blockquotes
255 279 // Add `error` instead if the maximum blockquote nesting depth is passed
256 280 if (state.formatting[i] === "quote") {
257 281 if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
258 styles.push(formatting + "-" + state.formatting[i] + "-" + state.quote);
282 styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.quote);
259 283 } else {
260 284 styles.push("error");
261 285 }
@@ -273,38 +297,36 b' CodeMirror.defineMode("markdown", functi'
273 297 }
274 298
275 299 if (state.linkHref) {
276 styles.push(linkhref, "url");
300 styles.push(tokenTypes.linkHref, "url");
277 301 } else { // Only apply inline styles to non-url text
278 if (state.strong) { styles.push(strong); }
279 if (state.em) { styles.push(em); }
280 if (state.strikethrough) { styles.push(strikethrough); }
281
282 if (state.linkText) { styles.push(linktext); }
283
284 if (state.code) { styles.push(code); }
302 if (state.strong) { styles.push(tokenTypes.strong); }
303 if (state.em) { styles.push(tokenTypes.em); }
304 if (state.strikethrough) { styles.push(tokenTypes.strikethrough); }
305 if (state.linkText) { styles.push(tokenTypes.linkText); }
306 if (state.code) { styles.push(tokenTypes.code); }
285 307 }
286 308
287 if (state.header) { styles.push(header); styles.push(header + "-" + state.header); }
309 if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); }
288 310
289 311 if (state.quote) {
290 styles.push(quote);
312 styles.push(tokenTypes.quote);
291 313
292 314 // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth
293 315 if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
294 styles.push(quote + "-" + state.quote);
316 styles.push(tokenTypes.quote + "-" + state.quote);
295 317 } else {
296 styles.push(quote + "-" + modeCfg.maxBlockquoteDepth);
318 styles.push(tokenTypes.quote + "-" + modeCfg.maxBlockquoteDepth);
297 319 }
298 320 }
299 321
300 322 if (state.list !== false) {
301 323 var listMod = (state.listDepth - 1) % 3;
302 324 if (!listMod) {
303 styles.push(list1);
325 styles.push(tokenTypes.list1);
304 326 } else if (listMod === 1) {
305 styles.push(list2);
327 styles.push(tokenTypes.list2);
306 328 } else {
307 styles.push(list3);
329 styles.push(tokenTypes.list3);
308 330 }
309 331 }
310 332
@@ -360,7 +382,8 b' CodeMirror.defineMode("markdown", functi'
360 382 stream.next();
361 383 if (modeCfg.highlightFormatting) {
362 384 var type = getType(state);
363 return type ? type + " formatting-escape" : "formatting-escape";
385 var formattingEscape = tokenTypes.formatting + "-escape";
386 return type ? type + " " + formattingEscape : formattingEscape;
364 387 }
365 388 }
366 389
@@ -374,7 +397,7 b' CodeMirror.defineMode("markdown", functi'
374 397 matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
375 398 var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
376 399 if (stream.match(new RegExp(regex), true)) {
377 return linkhref;
400 return tokenTypes.linkHref;
378 401 }
379 402 }
380 403
@@ -405,7 +428,7 b' CodeMirror.defineMode("markdown", functi'
405 428 if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
406 429 stream.match(/\[[^\]]*\]/);
407 430 state.inline = state.f = linkHref;
408 return image;
431 return tokenTypes.image;
409 432 }
410 433
411 434 if (ch === '[' && stream.match(/.*\](\(.*\)| ?\[.*\])/, false)) {
@@ -431,7 +454,7 b' CodeMirror.defineMode("markdown", functi'
431 454 } else {
432 455 type = "";
433 456 }
434 return type + linkinline;
457 return type + tokenTypes.linkInline;
435 458 }
436 459
437 460 if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) {
@@ -443,15 +466,14 b' CodeMirror.defineMode("markdown", functi'
443 466 } else {
444 467 type = "";
445 468 }
446 return type + linkemail;
469 return type + tokenTypes.linkEmail;
447 470 }
448 471
449 if (ch === '<' && stream.match(/^\w/, false)) {
450 if (stream.string.indexOf(">") != -1) {
451 var atts = stream.string.substring(1,stream.string.indexOf(">"));
452 if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) {
453 state.md_inside = true;
454 }
472 if (ch === '<' && stream.match(/^(!--|\w)/, false)) {
473 var end = stream.string.indexOf(">", stream.pos);
474 if (end != -1) {
475 var atts = stream.string.substring(stream.start, end);
476 if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) state.md_inside = true;
455 477 }
456 478 stream.backUp(1);
457 479 state.htmlState = CodeMirror.startState(htmlMode);
@@ -553,12 +575,12 b' CodeMirror.defineMode("markdown", functi'
553 575 } else {
554 576 type = "";
555 577 }
556 return type + linkinline;
578 return type + tokenTypes.linkInline;
557 579 }
558 580
559 581 stream.match(/^[^>]+/, true);
560 582
561 return linkinline;
583 return tokenTypes.linkInline;
562 584 }
563 585
564 586 function linkHref(stream, state) {
@@ -598,7 +620,7 b' CodeMirror.defineMode("markdown", functi'
598 620 }
599 621
600 622 function footnoteLink(stream, state) {
601 if (stream.match(/^[^\]]*\]:/, false)) {
623 if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) {
602 624 state.f = footnoteLinkInside;
603 625 stream.next(); // Consume [
604 626 if (modeCfg.highlightFormatting) state.formatting = "link";
@@ -617,9 +639,9 b' CodeMirror.defineMode("markdown", functi'
617 639 return returnType;
618 640 }
619 641
620 stream.match(/^[^\]]+/, true);
642 stream.match(/^([^\]\\]|\\.)+/, true);
621 643
622 return linktext;
644 return tokenTypes.linkText;
623 645 }
624 646
625 647 function footnoteUrl(stream, state) {
@@ -636,7 +658,7 b' CodeMirror.defineMode("markdown", functi'
636 658 stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
637 659 }
638 660 state.f = state.inline = inlineNormal;
639 return linkhref + " url";
661 return tokenTypes.linkHref + " url";
640 662 }
641 663
642 664 var savedInlineRE = [];
@@ -656,8 +678,8 b' CodeMirror.defineMode("markdown", functi'
656 678 return {
657 679 f: blockNormal,
658 680
659 prevLineHasContent: false,
660 thisLineHasContent: false,
681 prevLine: null,
682 thisLine: null,
661 683
662 684 block: blockNormal,
663 685 htmlState: null,
@@ -680,7 +702,8 b' CodeMirror.defineMode("markdown", functi'
680 702 quote: 0,
681 703 trailingSpace: 0,
682 704 trailingSpaceNewLine: false,
683 strikethrough: false
705 strikethrough: false,
706 fencedChars: null
684 707 };
685 708 },
686 709
@@ -688,8 +711,8 b' CodeMirror.defineMode("markdown", functi'
688 711 return {
689 712 f: s.f,
690 713
691 prevLineHasContent: s.prevLineHasContent,
692 thisLineHasContent: s.thisLineHasContent,
714 prevLine: s.prevLine,
715 thisLine: s.thisLine,
693 716
694 717 block: s.block,
695 718 htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),
@@ -702,6 +725,7 b' CodeMirror.defineMode("markdown", functi'
702 725 text: s.text,
703 726 formatting: false,
704 727 linkTitle: s.linkTitle,
728 code: s.code,
705 729 em: s.em,
706 730 strong: s.strong,
707 731 strikethrough: s.strikethrough,
@@ -714,7 +738,8 b' CodeMirror.defineMode("markdown", functi'
714 738 indentedCode: s.indentedCode,
715 739 trailingSpace: s.trailingSpace,
716 740 trailingSpaceNewLine: s.trailingSpaceNewLine,
717 md_inside: s.md_inside
741 md_inside: s.md_inside,
742 fencedChars: s.fencedChars
718 743 };
719 744 },
720 745
@@ -723,28 +748,25 b' CodeMirror.defineMode("markdown", functi'
723 748 // Reset state.formatting
724 749 state.formatting = false;
725 750
726 if (stream.sol()) {
727 var forceBlankLine = !!state.header || state.hr;
751 if (stream != state.thisLine) {
752 var forceBlankLine = state.header || state.hr;
728 753
729 754 // Reset state.header and state.hr
730 755 state.header = 0;
731 756 state.hr = false;
732 757
733 758 if (stream.match(/^\s*$/, true) || forceBlankLine) {
734 state.prevLineHasContent = false;
735 759 blankLine(state);
736 return forceBlankLine ? this.token(stream, state) : null;
737 } else {
738 state.prevLineHasContent = state.thisLineHasContent;
739 state.thisLineHasContent = true;
760 if (!forceBlankLine) return null
761 state.prevLine = null
740 762 }
741 763
764 state.prevLine = state.thisLine
765 state.thisLine = stream
766
742 767 // Reset state.taskList
743 768 state.taskList = false;
744 769
745 // Reset state.code
746 state.code = false;
747
748 770 // Reset state.trailingSpace
749 771 state.trailingSpace = 0;
750 772 state.trailingSpaceNewLine = false;
@@ -14,18 +14,22 b''
14 14 CodeMirror.modeInfo = [
15 15 {name: "APL", mime: "text/apl", mode: "apl", ext: ["dyalog", "apl"]},
16 16 {name: "PGP", mimes: ["application/pgp", "application/pgp-keys", "application/pgp-signature"], mode: "asciiarmor", ext: ["pgp"]},
17 {name: "ASN.1", mime: "text/x-ttcn-asn", mode: "asn.1", ext: ["asn, asn1"]},
17 {name: "ASN.1", mime: "text/x-ttcn-asn", mode: "asn.1", ext: ["asn", "asn1"]},
18 18 {name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk", file: /^extensions\.conf$/i},
19 {name: "Brainfuck", mime: "text/x-brainfuck", mode: "brainfuck", ext: ["b", "bf"]},
19 20 {name: "C", mime: "text/x-csrc", mode: "clike", ext: ["c", "h"]},
20 21 {name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx"], alias: ["cpp"]},
21 22 {name: "Cobol", mime: "text/x-cobol", mode: "cobol", ext: ["cob", "cpy"]},
22 23 {name: "C#", mime: "text/x-csharp", mode: "clike", ext: ["cs"], alias: ["csharp"]},
23 24 {name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj"]},
25 {name: "ClojureScript", mime: "text/x-clojurescript", mode: "clojure", ext: ["cljs"]},
26 {name: "Closure Stylesheets (GSS)", mime: "text/x-gss", mode: "css", ext: ["gss"]},
24 27 {name: "CMake", mime: "text/x-cmake", mode: "cmake", ext: ["cmake", "cmake.in"], file: /^CMakeLists.txt$/},
25 28 {name: "CoffeeScript", mime: "text/x-coffeescript", mode: "coffeescript", ext: ["coffee"], alias: ["coffee", "coffee-script"]},
26 29 {name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"], alias: ["lisp"]},
27 30 {name: "Cypher", mime: "application/x-cypher-query", mode: "cypher", ext: ["cyp", "cypher"]},
28 31 {name: "Cython", mime: "text/x-cython", mode: "python", ext: ["pyx", "pxd", "pxi"]},
32 {name: "Crystal", mime: "text/x-crystal", mode: "crystal", ext: ["cr"]},
29 33 {name: "CSS", mime: "text/css", mode: "css", ext: ["css"]},
30 34 {name: "CQL", mime: "text/x-cassandra", mode: "sql", ext: ["cql"]},
31 35 {name: "D", mime: "text/x-d", mode: "d", ext: ["d"]},
@@ -53,6 +57,7 b''
53 57 {name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy"]},
54 58 {name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]},
55 59 {name: "Haskell", mime: "text/x-haskell", mode: "haskell", ext: ["hs"]},
60 {name: "Haskell (Literate)", mime: "text/x-literate-haskell", mode: "haskell-literate", ext: ["lhs"]},
56 61 {name: "Haxe", mime: "text/x-haxe", mode: "haxe", ext: ["hx"]},
57 62 {name: "HXML", mime: "text/x-hxml", mode: "haxe", ext: ["hxml"]},
58 63 {name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded", ext: ["aspx"], alias: ["asp", "aspx"]},
@@ -66,9 +71,10 b''
66 71 mode: "javascript", ext: ["js"], alias: ["ecmascript", "js", "node"]},
67 72 {name: "JSON", mimes: ["application/json", "application/x-json"], mode: "javascript", ext: ["json", "map"], alias: ["json5"]},
68 73 {name: "JSON-LD", mime: "application/ld+json", mode: "javascript", ext: ["jsonld"], alias: ["jsonld"]},
74 {name: "JSX", mime: "text/jsx", mode: "jsx", ext: ["jsx"]},
69 75 {name: "Jinja2", mime: "null", mode: "jinja2"},
70 76 {name: "Julia", mime: "text/x-julia", mode: "julia", ext: ["jl"]},
71 {name: "Kotlin", mime: "text/x-kotlin", mode: "kotlin", ext: ["kt"]},
77 {name: "Kotlin", mime: "text/x-kotlin", mode: "clike", ext: ["kt"]},
72 78 {name: "LESS", mime: "text/x-less", mode: "css", ext: ["less"]},
73 79 {name: "LiveScript", mime: "text/x-livescript", mode: "livescript", ext: ["ls"], alias: ["ls"]},
74 80 {name: "Lua", mime: "text/x-lua", mode: "lua", ext: ["lua"]},
@@ -81,10 +87,12 b''
81 87 {name: "MS SQL", mime: "text/x-mssql", mode: "sql"},
82 88 {name: "MySQL", mime: "text/x-mysql", mode: "sql"},
83 89 {name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx", file: /nginx.*\.conf$/i},
90 {name: "NSIS", mime: "text/x-nsis", mode: "nsis", ext: ["nsh", "nsi"]},
84 91 {name: "NTriples", mime: "text/n-triples", mode: "ntriples", ext: ["nt"]},
85 92 {name: "Objective C", mime: "text/x-objectivec", mode: "clike", ext: ["m", "mm"]},
86 93 {name: "OCaml", mime: "text/x-ocaml", mode: "mllike", ext: ["ml", "mli", "mll", "mly"]},
87 94 {name: "Octave", mime: "text/x-octave", mode: "octave", ext: ["m"]},
95 {name: "Oz", mime: "text/x-oz", mode: "oz", ext: ["oz"]},
88 96 {name: "Pascal", mime: "text/x-pascal", mode: "pascal", ext: ["p", "pas"]},
89 97 {name: "PEG.js", mime: "null", mode: "pegjs", ext: ["jsonld"]},
90 98 {name: "Perl", mime: "text/x-perl", mode: "perl", ext: ["pl", "pm"]},
@@ -106,7 +114,7 b''
106 114 {name: "Scala", mime: "text/x-scala", mode: "clike", ext: ["scala"]},
107 115 {name: "Scheme", mime: "text/x-scheme", mode: "scheme", ext: ["scm", "ss"]},
108 116 {name: "SCSS", mime: "text/x-scss", mode: "css", ext: ["scss"]},
109 {name: "Shell", mime: "text/x-sh", mode: "shell", ext: ["sh", "ksh", "bash"], alias: ["bash", "sh", "zsh"]},
117 {name: "Shell", mime: "text/x-sh", mode: "shell", ext: ["sh", "ksh", "bash"], alias: ["bash", "sh", "zsh"], file: /^PKGBUILD$/},
110 118 {name: "Sieve", mime: "application/sieve", mode: "sieve", ext: ["siv", "sieve"]},
111 119 {name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim", ext: ["slim"]},
112 120 {name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk", ext: ["st"]},
@@ -116,6 +124,7 b''
116 124 {name: "SPARQL", mime: "application/sparql-query", mode: "sparql", ext: ["rq", "sparql"], alias: ["sparul"]},
117 125 {name: "Spreadsheet", mime: "text/x-spreadsheet", mode: "spreadsheet", alias: ["excel", "formula"]},
118 126 {name: "SQL", mime: "text/x-sql", mode: "sql", ext: ["sql"]},
127 {name: "Squirrel", mime: "text/x-squirrel", mode: "clike", ext: ["nut"]},
119 128 {name: "Swift", mime: "text/x-swift", mode: "swift", ext: ["swift"]},
120 129 {name: "MariaDB", mime: "text/x-mariadb", mode: "sql"},
121 130 {name: "sTeX", mime: "text/x-stex", mode: "stex"},
@@ -137,10 +146,14 b''
137 146 {name: "VBScript", mime: "text/vbscript", mode: "vbscript", ext: ["vbs"]},
138 147 {name: "Velocity", mime: "text/velocity", mode: "velocity", ext: ["vtl"]},
139 148 {name: "Verilog", mime: "text/x-verilog", mode: "verilog", ext: ["v"]},
149 {name: "VHDL", mime: "text/x-vhdl", mode: "vhdl", ext: ["vhd", "vhdl"]},
140 150 {name: "XML", mimes: ["application/xml", "text/xml"], mode: "xml", ext: ["xml", "xsl", "xsd"], alias: ["rss", "wsdl", "xsd"]},
141 151 {name: "XQuery", mime: "application/xquery", mode: "xquery", ext: ["xy", "xquery"]},
142 152 {name: "YAML", mime: "text/x-yaml", mode: "yaml", ext: ["yaml", "yml"], alias: ["yml"]},
143 {name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]}
153 {name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]},
154 {name: "mscgen", mime: "text/x-mscgen", mode: "mscgen", ext: ["mscgen", "mscin", "msc"]},
155 {name: "xu", mime: "text/x-xu", mode: "mscgen", ext: ["xu"]},
156 {name: "msgenny", mime: "text/x-msgenny", mode: "mscgen", ext: ["msgenny"]}
144 157 ];
145 158 // Ensure all modes have a mime property for backwards compatibility
146 159 for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
@@ -113,7 +113,7 b' MIME_TO_EXT = {'
113 113 "text/x-fortran": {"exts": ["*.f","*.f90","*.F","*.F90","*.for","*.f77"], "mode": "fortran"},
114 114 "text/x-fsharp": {"exts": ["*.fs","*.fsi"], "mode": "mllike"},
115 115 "text/x-gas": {"exts": ["*.s","*.S"], "mode": "gas"},
116 "text/x-gfm": {"exts": ["*.md","*.MD"], "mode": "markdown"},
116 "text/x-gfm": {"exts": ["*.md","*.MD"], "mode": "gfm"},
117 117 "text/x-gherkin": {"exts": ["*.feature"], "mode": ""},
118 118 "text/x-glslsrc": {"exts": ["*.vert","*.frag","*.geo"], "mode": ""},
119 119 "text/x-gnuplot": {"exts": ["*.plot","*.plt"], "mode": ""},
@@ -137,11 +137,11 b' MIME_TO_EXT = {'
137 137 "text/x-julia": {"exts": ["*.jl"], "mode": "julia"},
138 138 "text/x-kconfig": {"exts": ["Kconfig","*Config.in*","external.in*","standard-modules.in"], "mode": ""},
139 139 "text/x-koka": {"exts": ["*.kk","*.kki"], "mode": ""},
140 "text/x-kotlin": {"exts": ["*.kt"], "mode": "kotlin"},
140 "text/x-kotlin": {"exts": ["*.kt"], "mode": "clike"},
141 141 "text/x-lasso": {"exts": ["*.lasso","*.lasso[89]"], "mode": ""},
142 142 "text/x-latex": {"exts": ["*.ltx","*.text"], "mode": "stex"},
143 143 "text/x-less": {"exts": ["*.less"], "mode": "css"},
144 "text/x-literate-haskell": {"exts": ["*.lhs"], "mode": ""},
144 "text/x-literate-haskell": {"exts": ["*.lhs"], "mode": "haskell-literate"},
145 145 "text/x-livescript": {"exts": ["*.ls"], "mode": "livescript"},
146 146 "text/x-llvm": {"exts": ["*.ll"], "mode": ""},
147 147 "text/x-logos": {"exts": ["*.x","*.xi","*.xm","*.xmi"], "mode": ""},
@@ -162,7 +162,7 b' MIME_TO_EXT = {'
162 162 "text/x-newspeak": {"exts": ["*.ns2"], "mode": ""},
163 163 "text/x-nginx-conf": {"exts": ["*.conf"], "mode": "nginx"},
164 164 "text/x-nimrod": {"exts": ["*.nim","*.nimrod"], "mode": ""},
165 "text/x-nsis": {"exts": ["*.nsi","*.nsh"], "mode": ""},
165 "text/x-nsis": {"exts": ["*.nsi","*.nsh"], "mode": "nsis"},
166 166 "text/x-objdump": {"exts": ["*.objdump"], "mode": ""},
167 167 "text/x-objective-c": {"exts": ["*.m","*.h"], "mode": ""},
168 168 "text/x-objective-c++": {"exts": ["*.mm","*.hh"], "mode": ""},
@@ -217,7 +217,7 b' MIME_TO_EXT = {'
217 217 "text/x-vb": {"exts": ["*.vb"], "mode": "vb"},
218 218 "text/x-vbnet": {"exts": ["*.vb","*.bas"], "mode": ""},
219 219 "text/x-verilog": {"exts": ["*.v"], "mode": "verilog"},
220 "text/x-vhdl": {"exts": ["*.vhdl","*.vhd"], "mode": ""},
220 "text/x-vhdl": {"exts": ["*.vhdl","*.vhd"], "mode": "vhdl"},
221 221 "text/x-vim": {"exts": ["*.vim",".vimrc",".exrc",".gvimrc","_vimrc","_exrc","_gvimrc","vimrc","gvimrc"], "mode": ""},
222 222 "text/x-windows-registry": {"exts": ["*.reg"], "mode": ""},
223 223 "text/x-xtend": {"exts": ["*.xtend"], "mode": ""},
@@ -173,6 +173,6 b' CodeMirror.defineMode("nginx", function('
173 173 };
174 174 });
175 175
176 CodeMirror.defineMIME("text/nginx", "text/x-nginx-conf");
176 CodeMirror.defineMIME("text/x-nginx-conf", "nginx");
177 177
178 178 });
@@ -86,7 +86,7 b''
86 86 "die echo empty exit eval include include_once isset list require require_once return " +
87 87 "print unset __halt_compiler self static parent yield insteadof finally";
88 88 var phpAtoms = "true false null TRUE FALSE NULL __CLASS__ __DIR__ __FILE__ __LINE__ __METHOD__ __FUNCTION__ __NAMESPACE__ __TRAIT__";
89 var phpBuiltin = "func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count";
89 var phpBuiltin = "func_num_args func_get_arg func_get_args strlen strcmp strncmp strcasecmp strncasecmp each error_reporting define defined trigger_error user_error set_error_handler restore_error_handler get_declared_classes get_loaded_extensions extension_loaded get_extension_funcs debug_backtrace constant bin2hex hex2bin sleep usleep time mktime gmmktime strftime gmstrftime strtotime date gmdate getdate localtime checkdate flush wordwrap htmlspecialchars htmlentities html_entity_decode md5 md5_file crc32 getimagesize image_type_to_mime_type phpinfo phpversion phpcredits strnatcmp strnatcasecmp substr_count strspn strcspn strtok strtoupper strtolower strpos strrpos strrev hebrev hebrevc nl2br basename dirname pathinfo stripslashes stripcslashes strstr stristr strrchr str_shuffle str_word_count strcoll substr substr_replace quotemeta ucfirst ucwords strtr addslashes addcslashes rtrim str_replace str_repeat count_chars chunk_split trim ltrim strip_tags similar_text explode implode setlocale localeconv parse_str str_pad chop strchr sprintf printf vprintf vsprintf sscanf fscanf parse_url urlencode urldecode rawurlencode rawurldecode readlink linkinfo link unlink exec system escapeshellcmd escapeshellarg passthru shell_exec proc_open proc_close rand srand getrandmax mt_rand mt_srand mt_getrandmax base64_decode base64_encode abs ceil floor round is_finite is_nan is_infinite bindec hexdec octdec decbin decoct dechex base_convert number_format fmod ip2long long2ip getenv putenv getopt microtime gettimeofday getrusage uniqid quoted_printable_decode set_time_limit get_cfg_var magic_quotes_runtime set_magic_quotes_runtime get_magic_quotes_gpc get_magic_quotes_runtime import_request_variables error_log serialize unserialize memory_get_usage var_dump var_export debug_zval_dump print_r highlight_file show_source highlight_string ini_get ini_get_all ini_set ini_alter ini_restore get_include_path set_include_path restore_include_path setcookie header headers_sent connection_aborted connection_status ignore_user_abort parse_ini_file is_uploaded_file move_uploaded_file intval floatval doubleval strval gettype settype is_null is_resource is_bool is_long is_float is_int is_integer is_double is_real is_numeric is_string is_array is_object is_scalar ereg ereg_replace eregi eregi_replace split spliti join sql_regcase dl pclose popen readfile rewind rmdir umask fclose feof fgetc fgets fgetss fread fopen fpassthru ftruncate fstat fseek ftell fflush fwrite fputs mkdir rename copy tempnam tmpfile file file_get_contents file_put_contents stream_select stream_context_create stream_context_set_params stream_context_set_option stream_context_get_options stream_filter_prepend stream_filter_append fgetcsv flock get_meta_tags stream_set_write_buffer set_file_buffer set_socket_blocking stream_set_blocking socket_set_blocking stream_get_meta_data stream_register_wrapper stream_wrapper_register stream_set_timeout socket_set_timeout socket_get_status realpath fnmatch fsockopen pfsockopen pack unpack get_browser crypt opendir closedir chdir getcwd rewinddir readdir dir glob fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype file_exists is_writable is_writeable is_readable is_executable is_file is_dir is_link stat lstat chown touch clearstatcache mail ob_start ob_flush ob_clean ob_end_flush ob_end_clean ob_get_flush ob_get_clean ob_get_length ob_get_level ob_get_status ob_get_contents ob_implicit_flush ob_list_handlers ksort krsort natsort natcasesort asort arsort sort rsort usort uasort uksort shuffle array_walk count end prev next reset current key min max in_array array_search extract compact array_fill range array_multisort array_push array_pop array_shift array_unshift array_splice array_slice array_merge array_merge_recursive array_keys array_values array_count_values array_reverse array_reduce array_pad array_flip array_change_key_case array_rand array_unique array_intersect array_intersect_assoc array_diff array_diff_assoc array_sum array_filter array_map array_chunk array_key_exists pos sizeof key_exists assert assert_options version_compare ftok str_rot13 aggregate session_name session_module_name session_save_path session_id session_regenerate_id session_decode session_register session_unregister session_is_registered session_encode session_start session_destroy session_unset session_set_save_handler session_cache_limiter session_cache_expire session_set_cookie_params session_get_cookie_params session_write_close preg_match preg_match_all preg_replace preg_replace_callback preg_split preg_quote preg_grep overload ctype_alnum ctype_alpha ctype_cntrl ctype_digit ctype_lower ctype_graph ctype_print ctype_punct ctype_space ctype_upper ctype_xdigit virtual apache_request_headers apache_note apache_lookup_uri apache_child_terminate apache_setenv apache_response_headers apache_get_version getallheaders mysql_connect mysql_pconnect mysql_close mysql_select_db mysql_create_db mysql_drop_db mysql_query mysql_unbuffered_query mysql_db_query mysql_list_dbs mysql_list_tables mysql_list_fields mysql_list_processes mysql_error mysql_errno mysql_affected_rows mysql_insert_id mysql_result mysql_num_rows mysql_num_fields mysql_fetch_row mysql_fetch_array mysql_fetch_assoc mysql_fetch_object mysql_data_seek mysql_fetch_lengths mysql_fetch_field mysql_field_seek mysql_free_result mysql_field_name mysql_field_table mysql_field_len mysql_field_type mysql_field_flags mysql_escape_string mysql_real_escape_string mysql_stat mysql_thread_id mysql_client_encoding mysql_get_client_info mysql_get_host_info mysql_get_proto_info mysql_get_server_info mysql_info mysql mysql_fieldname mysql_fieldtable mysql_fieldlen mysql_fieldtype mysql_fieldflags mysql_selectdb mysql_createdb mysql_dropdb mysql_freeresult mysql_numfields mysql_numrows mysql_listdbs mysql_listtables mysql_listfields mysql_db_name mysql_dbname mysql_tablename mysql_table_name pg_connect pg_pconnect pg_close pg_connection_status pg_connection_busy pg_connection_reset pg_host pg_dbname pg_port pg_tty pg_options pg_ping pg_query pg_send_query pg_cancel_query pg_fetch_result pg_fetch_row pg_fetch_assoc pg_fetch_array pg_fetch_object pg_fetch_all pg_affected_rows pg_get_result pg_result_seek pg_result_status pg_free_result pg_last_oid pg_num_rows pg_num_fields pg_field_name pg_field_num pg_field_size pg_field_type pg_field_prtlen pg_field_is_null pg_get_notify pg_get_pid pg_result_error pg_last_error pg_last_notice pg_put_line pg_end_copy pg_copy_to pg_copy_from pg_trace pg_untrace pg_lo_create pg_lo_unlink pg_lo_open pg_lo_close pg_lo_read pg_lo_write pg_lo_read_all pg_lo_import pg_lo_export pg_lo_seek pg_lo_tell pg_escape_string pg_escape_bytea pg_unescape_bytea pg_client_encoding pg_set_client_encoding pg_meta_data pg_convert pg_insert pg_update pg_delete pg_select pg_exec pg_getlastoid pg_cmdtuples pg_errormessage pg_numrows pg_numfields pg_fieldname pg_fieldsize pg_fieldtype pg_fieldnum pg_fieldprtlen pg_fieldisnull pg_freeresult pg_result pg_loreadall pg_locreate pg_lounlink pg_loopen pg_loclose pg_loread pg_lowrite pg_loimport pg_loexport http_response_code get_declared_traits getimagesizefromstring socket_import_stream stream_set_chunk_size trait_exists header_register_callback class_uses session_status session_register_shutdown echo print global static exit array empty eval isset unset die include require include_once require_once json_decode json_encode json_last_error json_last_error_msg curl_close curl_copy_handle curl_errno curl_error curl_escape curl_exec curl_file_create curl_getinfo curl_init curl_multi_add_handle curl_multi_close curl_multi_exec curl_multi_getcontent curl_multi_info_read curl_multi_init curl_multi_remove_handle curl_multi_select curl_multi_setopt curl_multi_strerror curl_pause curl_reset curl_setopt_array curl_setopt curl_share_close curl_share_init curl_share_setopt curl_strerror curl_unescape curl_version mysqli_affected_rows mysqli_autocommit mysqli_change_user mysqli_character_set_name mysqli_close mysqli_commit mysqli_connect_errno mysqli_connect_error mysqli_connect mysqli_data_seek mysqli_debug mysqli_dump_debug_info mysqli_errno mysqli_error_list mysqli_error mysqli_fetch_all mysqli_fetch_array mysqli_fetch_assoc mysqli_fetch_field_direct mysqli_fetch_field mysqli_fetch_fields mysqli_fetch_lengths mysqli_fetch_object mysqli_fetch_row mysqli_field_count mysqli_field_seek mysqli_field_tell mysqli_free_result mysqli_get_charset mysqli_get_client_info mysqli_get_client_stats mysqli_get_client_version mysqli_get_connection_stats mysqli_get_host_info mysqli_get_proto_info mysqli_get_server_info mysqli_get_server_version mysqli_info mysqli_init mysqli_insert_id mysqli_kill mysqli_more_results mysqli_multi_query mysqli_next_result mysqli_num_fields mysqli_num_rows mysqli_options mysqli_ping mysqli_prepare mysqli_query mysqli_real_connect mysqli_real_escape_string mysqli_real_query mysqli_reap_async_query mysqli_refresh mysqli_rollback mysqli_select_db mysqli_set_charset mysqli_set_local_infile_default mysqli_set_local_infile_handler mysqli_sqlstate mysqli_ssl_set mysqli_stat mysqli_stmt_init mysqli_store_result mysqli_thread_id mysqli_thread_safe mysqli_use_result mysqli_warning_count";
90 90 CodeMirror.registerHelper("hintWords", "php", [phpKeywords, phpAtoms, phpBuiltin].join(" ").split(" "));
91 91 CodeMirror.registerHelper("wordChars", "php", /[\w$]/);
92 92
@@ -105,14 +105,15 b''
105 105 return "variable-2";
106 106 },
107 107 "<": function(stream, state) {
108 if (stream.match(/<</)) {
109 var nowDoc = stream.eat("'");
108 var before;
109 if (before = stream.match(/<<\s*/)) {
110 var quoted = stream.eat(/['"]/);
110 111 stream.eatWhile(/[\w\.]/);
111 var delim = stream.current().slice(3 + (nowDoc ? 1 : 0));
112 if (nowDoc) stream.eat("'");
112 var delim = stream.current().slice(before[0].length + (quoted ? 2 : 1));
113 if (quoted) stream.eat(quoted);
113 114 if (delim) {
114 115 (state.tokStack || (state.tokStack = [])).push(delim, 0);
115 state.tokenize = phpString(delim, nowDoc ? false : true);
116 state.tokenize = phpString(delim, quoted != "'");
116 117 return "string";
117 118 }
118 119 }
@@ -159,6 +160,7 b''
159 160 if (!isPHP) {
160 161 if (stream.match(/^<\?\w*/)) {
161 162 state.curMode = phpMode;
163 if (!state.php) state.php = CodeMirror.startState(phpMode, htmlMode.indent(state.html, ""))
162 164 state.curState = state.php;
163 165 return "meta";
164 166 }
@@ -182,6 +184,7 b''
182 184 } else if (isPHP && state.php.tokenize == null && stream.match("?>")) {
183 185 state.curMode = htmlMode;
184 186 state.curState = state.html;
187 if (!state.php.context.prev) state.php = null;
185 188 return "meta";
186 189 } else {
187 190 return phpMode.token(stream, state.curState);
@@ -190,7 +193,8 b''
190 193
191 194 return {
192 195 startState: function() {
193 var html = CodeMirror.startState(htmlMode), php = CodeMirror.startState(phpMode);
196 var html = CodeMirror.startState(htmlMode)
197 var php = parserConfig.startOpen ? CodeMirror.startState(phpMode) : null
194 198 return {html: html,
195 199 php: php,
196 200 curMode: parserConfig.startOpen ? phpMode : htmlMode,
@@ -200,7 +204,7 b''
200 204
201 205 copyState: function(state) {
202 206 var html = state.html, htmlNew = CodeMirror.copyState(htmlMode, html),
203 php = state.php, phpNew = CodeMirror.copyState(phpMode, php), cur;
207 php = state.php, phpNew = php && CodeMirror.copyState(phpMode, php), cur;
204 208 if (state.curMode == htmlMode) cur = htmlNew;
205 209 else cur = phpNew;
206 210 return {html: htmlNew, php: phpNew, curMode: state.curMode, curState: cur,
@@ -48,18 +48,18 b''
48 48 CodeMirror.defineMode("python", function(conf, parserConf) {
49 49 var ERRORCLASS = "error";
50 50
51 var singleDelimiters = parserConf.singleDelimiters || new RegExp("^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]");
52 var doubleOperators = parserConf.doubleOperators || new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
53 var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
54 var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
51 var singleDelimiters = parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.]/;
52 var doubleOperators = parserConf.doubleOperators || /^([!<>]==|<>|<<|>>|\/\/|\*\*)/;
53 var doubleDelimiters = parserConf.doubleDelimiters || /^(\+=|\-=|\*=|%=|\/=|&=|\|=|\^=)/;
54 var tripleDelimiters = parserConf.tripleDelimiters || /^(\/\/=|>>=|<<=|\*\*=)/;
55 55
56 56 if (parserConf.version && parseInt(parserConf.version, 10) == 3){
57 57 // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
58 var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!@]");
59 var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*");
58 var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!@]/;
59 var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/;
60 60 } else {
61 var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
62 var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*");
61 var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!]/;
62 var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/;
63 63 }
64 64
65 65 var hangingIndent = parserConf.hangingIndent || conf.indentUnit;
@@ -160,13 +160,16 b''
160 160
161 161 // Handle operators and Delimiters
162 162 if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters))
163 return null;
163 return "punctuation";
164 164
165 165 if (stream.match(doubleOperators) || stream.match(singleOperators))
166 166 return "operator";
167 167
168 168 if (stream.match(singleDelimiters))
169 return null;
169 return "punctuation";
170
171 if (state.lastToken == "." && stream.match(identifiers))
172 return "property";
170 173
171 174 if (stream.match(keywords) || stream.match(wordOperators))
172 175 return "keyword";
@@ -246,17 +249,6 b''
246 249 var style = state.tokenize(stream, state);
247 250 var current = stream.current();
248 251
249 // Handle '.' connected identifiers
250 if (current == ".") {
251 style = stream.match(identifiers, false) ? null : ERRORCLASS;
252 if (style == null && state.lastStyle == "meta") {
253 // Apply 'meta' style to '.' connected identifiers when
254 // appropriate.
255 style = "meta";
256 }
257 return style;
258 }
259
260 252 // Handle decorators
261 253 if (current == "@"){
262 254 if(parserConf.version && parseInt(parserConf.version, 10) == 3){
@@ -267,7 +259,7 b''
267 259 }
268 260
269 261 if ((style == "variable" || style == "builtin")
270 && state.lastStyle == "meta")
262 && state.lastToken == "meta")
271 263 style = "meta";
272 264
273 265 // Handle scope changes.
@@ -300,7 +292,6 b''
300 292 return {
301 293 tokenize: tokenBase,
302 294 scopes: [{offset: basecolumn || 0, type: "py", align: null}],
303 lastStyle: null,
304 295 lastToken: null,
305 296 lambda: false,
306 297 dedent: 0
@@ -312,11 +303,9 b''
312 303 if (addErr) state.errorToken = false;
313 304 var style = tokenLexer(stream, state);
314 305
315 state.lastStyle = style;
316
317 var current = stream.current();
318 if (current && style)
319 state.lastToken = current;
306 if (style && style != "comment")
307 state.lastToken = (style == "keyword" || style == "punctuation") ? stream.current() : style;
308 if (style == "punctuation") style = null;
320 309
321 310 if (stream.eol() && state.lambda)
322 311 state.lambda = false;
@@ -34,10 +34,10 b' CodeMirror.defineMIME("text/x-rpm-change'
34 34 // Quick and dirty spec file highlighting
35 35
36 36 CodeMirror.defineMode("rpm-spec", function() {
37 var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/;
37 var arch = /^(i386|i586|i686|x86_64|ppc64le|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/;
38 38
39 var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/;
40 var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/;
39 var preamble = /^[a-zA-Z0-9()]+:/;
40 var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pretrans|posttrans|pre|post|triggerin|triggerun|verifyscript|check|triggerpostun|triggerprein|trigger)/;
41 41 var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros
42 42 var control_flow_simple = /^%(else|endif)/; // rpm control flow macros
43 43 var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros
@@ -55,8 +55,8 b' CodeMirror.defineMode("rpm-spec", functi'
55 55 if (ch == "#") { stream.skipToEnd(); return "comment"; }
56 56
57 57 if (stream.sol()) {
58 if (stream.match(preamble)) { return "preamble"; }
59 if (stream.match(section)) { return "section"; }
58 if (stream.match(preamble)) { return "header"; }
59 if (stream.match(section)) { return "atom"; }
60 60 }
61 61
62 62 if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT'
@@ -73,21 +73,29 b' CodeMirror.defineMode("rpm-spec", functi'
73 73 if (stream.eol()) { state.controlFlow = false; }
74 74 }
75 75
76 if (stream.match(arch)) { return "number"; }
76 if (stream.match(arch)) {
77 if (stream.eol()) { state.controlFlow = false; }
78 return "number";
79 }
77 80
78 81 // Macros like '%make_install' or '%attr(0775,root,root)'
79 82 if (stream.match(/^%[\w]+/)) {
80 83 if (stream.match(/^\(/)) { state.macroParameters = true; }
81 return "macro";
84 return "keyword";
82 85 }
83 86 if (state.macroParameters) {
84 87 if (stream.match(/^\d+/)) { return "number";}
85 88 if (stream.match(/^\)/)) {
86 89 state.macroParameters = false;
87 return "macro";
90 return "keyword";
88 91 }
89 92 }
90 if (stream.match(/^%\{\??[\w \-]+\}/)) { return "macro"; } // Macros like '%{defined fedora}'
93
94 // Macros like '%{defined fedora}'
95 if (stream.match(/^%\{\??[\w \-\:\!]+\}/)) {
96 if (stream.eol()) { state.controlFlow = false; }
97 return "def";
98 }
91 99
92 100 //TODO: Include bash script sub-parser (CodeMirror supports that)
93 101 stream.next();
@@ -25,7 +25,7 b' CodeMirror.defineMode("ruby", function(c'
25 25 "caller", "lambda", "proc", "public", "protected", "private", "require", "load",
26 26 "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__"
27 27 ]);
28 var indentWords = wordObj(["def", "class", "case", "for", "while", "module", "then",
28 var indentWords = wordObj(["def", "class", "case", "for", "while", "until", "module", "then",
29 29 "catch", "loop", "proc", "begin"]);
30 30 var dedentWords = wordObj(["end", "until"]);
31 31 var matching = {"[": "]", "{": "}", "(": ")"};
@@ -37,7 +37,6 b' CodeMirror.defineMode("ruby", function(c'
37 37 }
38 38
39 39 function tokenBase(stream, state) {
40 curPunc = null;
41 40 if (stream.sol() && stream.match("=begin") && stream.eol()) {
42 41 state.tokenize.push(readBlockComment);
43 42 return "comment";
@@ -232,6 +231,7 b' CodeMirror.defineMode("ruby", function(c'
232 231 },
233 232
234 233 token: function(stream, state) {
234 curPunc = null;
235 235 if (stream.sol()) state.indented = stream.indentation();
236 236 var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype;
237 237 var thisTok = curPunc;
@@ -275,7 +275,7 b' CodeMirror.defineMode("ruby", function(c'
275 275 (state.continuedLine ? config.indentUnit : 0);
276 276 },
277 277
278 electricChars: "}de", // enD and rescuE
278 electricInput: /^\s*(?:end|rescue|\})$/,
279 279 lineComment: "#"
280 280 };
281 281 });
@@ -3,449 +3,69 b''
3 3
4 4 (function(mod) {
5 5 if (typeof exports == "object" && typeof module == "object") // CommonJS
6 mod(require("../../lib/codemirror"));
6 mod(require("../../lib/codemirror"), require("../../addon/mode/simple"));
7 7 else if (typeof define == "function" && define.amd) // AMD
8 define(["../../lib/codemirror"], mod);
8 define(["../../lib/codemirror", "../../addon/mode/simple"], mod);
9 9 else // Plain browser env
10 10 mod(CodeMirror);
11 11 })(function(CodeMirror) {
12 12 "use strict";
13 13
14 CodeMirror.defineMode("rust", function() {
15 var indentUnit = 4, altIndentUnit = 2;
16 var valKeywords = {
17 "if": "if-style", "while": "if-style", "loop": "else-style", "else": "else-style",
18 "do": "else-style", "ret": "else-style", "fail": "else-style",
19 "break": "atom", "cont": "atom", "const": "let", "resource": "fn",
20 "let": "let", "fn": "fn", "for": "for", "alt": "alt", "iface": "iface",
21 "impl": "impl", "type": "type", "enum": "enum", "mod": "mod",
22 "as": "op", "true": "atom", "false": "atom", "assert": "op", "check": "op",
23 "claim": "op", "native": "ignore", "unsafe": "ignore", "import": "else-style",
24 "export": "else-style", "copy": "op", "log": "op", "log_err": "op",
25 "use": "op", "bind": "op", "self": "atom", "struct": "enum"
26 };
27 var typeKeywords = function() {
28 var keywords = {"fn": "fn", "block": "fn", "obj": "obj"};
29 var atoms = "bool uint int i8 i16 i32 i64 u8 u16 u32 u64 float f32 f64 str char".split(" ");
30 for (var i = 0, e = atoms.length; i < e; ++i) keywords[atoms[i]] = "atom";
31 return keywords;
32 }();
33 var operatorChar = /[+\-*&%=<>!?|\.@]/;
34
35 // Tokenizer
36
37 // Used as scratch variable to communicate multiple values without
38 // consing up tons of objects.
39 var tcat, content;
40 function r(tc, style) {
41 tcat = tc;
42 return style;
43 }
44
45 function tokenBase(stream, state) {
46 var ch = stream.next();
47 if (ch == '"') {
48 state.tokenize = tokenString;
49 return state.tokenize(stream, state);
50 }
51 if (ch == "'") {
52 tcat = "atom";
53 if (stream.eat("\\")) {
54 if (stream.skipTo("'")) { stream.next(); return "string"; }
55 else { return "error"; }
56 } else {
57 stream.next();
58 return stream.eat("'") ? "string" : "error";
59 }
60 }
61 if (ch == "/") {
62 if (stream.eat("/")) { stream.skipToEnd(); return "comment"; }
63 if (stream.eat("*")) {
64 state.tokenize = tokenComment(1);
65 return state.tokenize(stream, state);
66 }
67 }
68 if (ch == "#") {
69 if (stream.eat("[")) { tcat = "open-attr"; return null; }
70 stream.eatWhile(/\w/);
71 return r("macro", "meta");
72 }
73 if (ch == ":" && stream.match(":<")) {
74 return r("op", null);
75 }
76 if (ch.match(/\d/) || (ch == "." && stream.eat(/\d/))) {
77 var flp = false;
78 if (!stream.match(/^x[\da-f]+/i) && !stream.match(/^b[01]+/)) {
79 stream.eatWhile(/\d/);
80 if (stream.eat(".")) { flp = true; stream.eatWhile(/\d/); }
81 if (stream.match(/^e[+\-]?\d+/i)) { flp = true; }
82 }
83 if (flp) stream.match(/^f(?:32|64)/);
84 else stream.match(/^[ui](?:8|16|32|64)/);
85 return r("atom", "number");
86 }
87 if (ch.match(/[()\[\]{}:;,]/)) return r(ch, null);
88 if (ch == "-" && stream.eat(">")) return r("->", null);
89 if (ch.match(operatorChar)) {
90 stream.eatWhile(operatorChar);
91 return r("op", null);
92 }
93 stream.eatWhile(/\w/);
94 content = stream.current();
95 if (stream.match(/^::\w/)) {
96 stream.backUp(1);
97 return r("prefix", "variable-2");
98 }
99 if (state.keywords.propertyIsEnumerable(content))
100 return r(state.keywords[content], content.match(/true|false/) ? "atom" : "keyword");
101 return r("name", "variable");
102 }
103
104 function tokenString(stream, state) {
105 var ch, escaped = false;
106 while (ch = stream.next()) {
107 if (ch == '"' && !escaped) {
108 state.tokenize = tokenBase;
109 return r("atom", "string");
110 }
111 escaped = !escaped && ch == "\\";
112 }
113 // Hack to not confuse the parser when a string is split in
114 // pieces.
115 return r("op", "string");
116 }
117
118 function tokenComment(depth) {
119 return function(stream, state) {
120 var lastCh = null, ch;
121 while (ch = stream.next()) {
122 if (ch == "/" && lastCh == "*") {
123 if (depth == 1) {
124 state.tokenize = tokenBase;
125 break;
126 } else {
127 state.tokenize = tokenComment(depth - 1);
128 return state.tokenize(stream, state);
129 }
130 }
131 if (ch == "*" && lastCh == "/") {
132 state.tokenize = tokenComment(depth + 1);
133 return state.tokenize(stream, state);
134 }
135 lastCh = ch;
136 }
137 return "comment";
138 };
139 }
140
141 // Parser
142
143 var cx = {state: null, stream: null, marked: null, cc: null};
144 function pass() {
145 for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
146 }
147 function cont() {
148 pass.apply(null, arguments);
149 return true;
150 }
151
152 function pushlex(type, info) {
153 var result = function() {
154 var state = cx.state;
155 state.lexical = {indented: state.indented, column: cx.stream.column(),
156 type: type, prev: state.lexical, info: info};
157 };
158 result.lex = true;
159 return result;
160 }
161 function poplex() {
162 var state = cx.state;
163 if (state.lexical.prev) {
164 if (state.lexical.type == ")")
165 state.indented = state.lexical.indented;
166 state.lexical = state.lexical.prev;
167 }
168 }
169 function typecx() { cx.state.keywords = typeKeywords; }
170 function valcx() { cx.state.keywords = valKeywords; }
171 poplex.lex = typecx.lex = valcx.lex = true;
172
173 function commasep(comb, end) {
174 function more(type) {
175 if (type == ",") return cont(comb, more);
176 if (type == end) return cont();
177 return cont(more);
178 }
179 return function(type) {
180 if (type == end) return cont();
181 return pass(comb, more);
182 };
183 }
14 CodeMirror.defineSimpleMode("rust",{
15 start: [
16 // string and byte string
17 {regex: /b?"/, token: "string", next: "string"},
18 // raw string and raw byte string
19 {regex: /b?r"/, token: "string", next: "string_raw"},
20 {regex: /b?r#+"/, token: "string", next: "string_raw_hash"},
21 // character
22 {regex: /'(?:[^'\\]|\\(?:[nrt0'"]|x[\da-fA-F]{2}|u\{[\da-fA-F]{6}\}))'/, token: "string-2"},
23 // byte
24 {regex: /b'(?:[^']|\\(?:['\\nrt0]|x[\da-fA-F]{2}))'/, token: "string-2"},
184 25
185 function stat_of(comb, tag) {
186 return cont(pushlex("stat", tag), comb, poplex, block);
187 }
188 function block(type) {
189 if (type == "}") return cont();
190 if (type == "let") return stat_of(letdef1, "let");
191 if (type == "fn") return stat_of(fndef);
192 if (type == "type") return cont(pushlex("stat"), tydef, endstatement, poplex, block);
193 if (type == "enum") return stat_of(enumdef);
194 if (type == "mod") return stat_of(mod);
195 if (type == "iface") return stat_of(iface);
196 if (type == "impl") return stat_of(impl);
197 if (type == "open-attr") return cont(pushlex("]"), commasep(expression, "]"), poplex);
198 if (type == "ignore" || type.match(/[\]\);,]/)) return cont(block);
199 return pass(pushlex("stat"), expression, poplex, endstatement, block);
200 }
201 function endstatement(type) {
202 if (type == ";") return cont();
203 return pass();
204 }
205 function expression(type) {
206 if (type == "atom" || type == "name") return cont(maybeop);
207 if (type == "{") return cont(pushlex("}"), exprbrace, poplex);
208 if (type.match(/[\[\(]/)) return matchBrackets(type, expression);
209 if (type.match(/[\]\)\};,]/)) return pass();
210 if (type == "if-style") return cont(expression, expression);
211 if (type == "else-style" || type == "op") return cont(expression);
212 if (type == "for") return cont(pattern, maybetype, inop, expression, expression);
213 if (type == "alt") return cont(expression, altbody);
214 if (type == "fn") return cont(fndef);
215 if (type == "macro") return cont(macro);
216 return cont();
217 }
218 function maybeop(type) {
219 if (content == ".") return cont(maybeprop);
220 if (content == "::<"){return cont(typarams, maybeop);}
221 if (type == "op" || content == ":") return cont(expression);
222 if (type == "(" || type == "[") return matchBrackets(type, expression);
223 return pass();
224 }
225 function maybeprop() {
226 if (content.match(/^\w+$/)) {cx.marked = "variable"; return cont(maybeop);}
227 return pass(expression);
228 }
229 function exprbrace(type) {
230 if (type == "op") {
231 if (content == "|") return cont(blockvars, poplex, pushlex("}", "block"), block);
232 if (content == "||") return cont(poplex, pushlex("}", "block"), block);
233 }
234 if (content == "mutable" || (content.match(/^\w+$/) && cx.stream.peek() == ":"
235 && !cx.stream.match("::", false)))
236 return pass(record_of(expression));
237 return pass(block);
238 }
239 function record_of(comb) {
240 function ro(type) {
241 if (content == "mutable" || content == "with") {cx.marked = "keyword"; return cont(ro);}
242 if (content.match(/^\w*$/)) {cx.marked = "variable"; return cont(ro);}
243 if (type == ":") return cont(comb, ro);
244 if (type == "}") return cont();
245 return cont(ro);
246 }
247 return ro;
248 }
249 function blockvars(type) {
250 if (type == "name") {cx.marked = "def"; return cont(blockvars);}
251 if (type == "op" && content == "|") return cont();
252 return cont(blockvars);
253 }
254
255 function letdef1(type) {
256 if (type.match(/[\]\)\};]/)) return cont();
257 if (content == "=") return cont(expression, letdef2);
258 if (type == ",") return cont(letdef1);
259 return pass(pattern, maybetype, letdef1);
260 }
261 function letdef2(type) {
262 if (type.match(/[\]\)\};,]/)) return pass(letdef1);
263 else return pass(expression, letdef2);
264 }
265 function maybetype(type) {
266 if (type == ":") return cont(typecx, rtype, valcx);
267 return pass();
268 }
269 function inop(type) {
270 if (type == "name" && content == "in") {cx.marked = "keyword"; return cont();}
271 return pass();
272 }
273 function fndef(type) {
274 if (content == "@" || content == "~") {cx.marked = "keyword"; return cont(fndef);}
275 if (type == "name") {cx.marked = "def"; return cont(fndef);}
276 if (content == "<") return cont(typarams, fndef);
277 if (type == "{") return pass(expression);
278 if (type == "(") return cont(pushlex(")"), commasep(argdef, ")"), poplex, fndef);
279 if (type == "->") return cont(typecx, rtype, valcx, fndef);
280 if (type == ";") return cont();
281 return cont(fndef);
282 }
283 function tydef(type) {
284 if (type == "name") {cx.marked = "def"; return cont(tydef);}
285 if (content == "<") return cont(typarams, tydef);
286 if (content == "=") return cont(typecx, rtype, valcx);
287 return cont(tydef);
288 }
289 function enumdef(type) {
290 if (type == "name") {cx.marked = "def"; return cont(enumdef);}
291 if (content == "<") return cont(typarams, enumdef);
292 if (content == "=") return cont(typecx, rtype, valcx, endstatement);
293 if (type == "{") return cont(pushlex("}"), typecx, enumblock, valcx, poplex);
294 return cont(enumdef);
295 }
296 function enumblock(type) {
297 if (type == "}") return cont();
298 if (type == "(") return cont(pushlex(")"), commasep(rtype, ")"), poplex, enumblock);
299 if (content.match(/^\w+$/)) cx.marked = "def";
300 return cont(enumblock);
301 }
302 function mod(type) {
303 if (type == "name") {cx.marked = "def"; return cont(mod);}
304 if (type == "{") return cont(pushlex("}"), block, poplex);
305 return pass();
306 }
307 function iface(type) {
308 if (type == "name") {cx.marked = "def"; return cont(iface);}
309 if (content == "<") return cont(typarams, iface);
310 if (type == "{") return cont(pushlex("}"), block, poplex);
311 return pass();
312 }
313 function impl(type) {
314 if (content == "<") return cont(typarams, impl);
315 if (content == "of" || content == "for") {cx.marked = "keyword"; return cont(rtype, impl);}
316 if (type == "name") {cx.marked = "def"; return cont(impl);}
317 if (type == "{") return cont(pushlex("}"), block, poplex);
318 return pass();
319 }
320 function typarams() {
321 if (content == ">") return cont();
322 if (content == ",") return cont(typarams);
323 if (content == ":") return cont(rtype, typarams);
324 return pass(rtype, typarams);
325 }
326 function argdef(type) {
327 if (type == "name") {cx.marked = "def"; return cont(argdef);}
328 if (type == ":") return cont(typecx, rtype, valcx);
329 return pass();
330 }
331 function rtype(type) {
332 if (type == "name") {cx.marked = "variable-3"; return cont(rtypemaybeparam); }
333 if (content == "mutable") {cx.marked = "keyword"; return cont(rtype);}
334 if (type == "atom") return cont(rtypemaybeparam);
335 if (type == "op" || type == "obj") return cont(rtype);
336 if (type == "fn") return cont(fntype);
337 if (type == "{") return cont(pushlex("{"), record_of(rtype), poplex);
338 return matchBrackets(type, rtype);
339 }
340 function rtypemaybeparam() {
341 if (content == "<") return cont(typarams);
342 return pass();
343 }
344 function fntype(type) {
345 if (type == "(") return cont(pushlex("("), commasep(rtype, ")"), poplex, fntype);
346 if (type == "->") return cont(rtype);
347 return pass();
348 }
349 function pattern(type) {
350 if (type == "name") {cx.marked = "def"; return cont(patternmaybeop);}
351 if (type == "atom") return cont(patternmaybeop);
352 if (type == "op") return cont(pattern);
353 if (type.match(/[\]\)\};,]/)) return pass();
354 return matchBrackets(type, pattern);
355 }
356 function patternmaybeop(type) {
357 if (type == "op" && content == ".") return cont();
358 if (content == "to") {cx.marked = "keyword"; return cont(pattern);}
359 else return pass();
360 }
361 function altbody(type) {
362 if (type == "{") return cont(pushlex("}", "alt"), altblock1, poplex);
363 return pass();
364 }
365 function altblock1(type) {
366 if (type == "}") return cont();
367 if (type == "|") return cont(altblock1);
368 if (content == "when") {cx.marked = "keyword"; return cont(expression, altblock2);}
369 if (type.match(/[\]\);,]/)) return cont(altblock1);
370 return pass(pattern, altblock2);
371 }
372 function altblock2(type) {
373 if (type == "{") return cont(pushlex("}", "alt"), block, poplex, altblock1);
374 else return pass(altblock1);
375 }
376
377 function macro(type) {
378 if (type.match(/[\[\(\{]/)) return matchBrackets(type, expression);
379 return pass();
380 }
381 function matchBrackets(type, comb) {
382 if (type == "[") return cont(pushlex("]"), commasep(comb, "]"), poplex);
383 if (type == "(") return cont(pushlex(")"), commasep(comb, ")"), poplex);
384 if (type == "{") return cont(pushlex("}"), commasep(comb, "}"), poplex);
385 return cont();
386 }
387
388 function parse(state, stream, style) {
389 var cc = state.cc;
390 // Communicate our context to the combinators.
391 // (Less wasteful than consing up a hundred closures on every call.)
392 cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
393
394 while (true) {
395 var combinator = cc.length ? cc.pop() : block;
396 if (combinator(tcat)) {
397 while(cc.length && cc[cc.length - 1].lex)
398 cc.pop()();
399 return cx.marked || style;
400 }
401 }
402 }
403
404 return {
405 startState: function() {
406 return {
407 tokenize: tokenBase,
408 cc: [],
409 lexical: {indented: -indentUnit, column: 0, type: "top", align: false},
410 keywords: valKeywords,
411 indented: 0
412 };
413 },
414
415 token: function(stream, state) {
416 if (stream.sol()) {
417 if (!state.lexical.hasOwnProperty("align"))
418 state.lexical.align = false;
419 state.indented = stream.indentation();
420 }
421 if (stream.eatSpace()) return null;
422 tcat = content = null;
423 var style = state.tokenize(stream, state);
424 if (style == "comment") return style;
425 if (!state.lexical.hasOwnProperty("align"))
426 state.lexical.align = true;
427 if (tcat == "prefix") return style;
428 if (!content) content = stream.current();
429 return parse(state, stream, style);
430 },
431
432 indent: function(state, textAfter) {
433 if (state.tokenize != tokenBase) return 0;
434 var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
435 type = lexical.type, closing = firstChar == type;
436 if (type == "stat") return lexical.indented + indentUnit;
437 if (lexical.align) return lexical.column + (closing ? 0 : 1);
438 return lexical.indented + (closing ? 0 : (lexical.info == "alt" ? altIndentUnit : indentUnit));
439 },
440
441 electricChars: "{}",
26 {regex: /(?:(?:[0-9][0-9_]*)(?:(?:[Ee][+-]?[0-9_]+)|\.[0-9_]+(?:[Ee][+-]?[0-9_]+)?)(?:f32|f64)?)|(?:0(?:b[01_]+|(?:o[0-7_]+)|(?:x[0-9a-fA-F_]+))|(?:[0-9][0-9_]*))(?:u8|u16|u32|u64|i8|i16|i32|i64|isize|usize)?/,
27 token: "number"},
28 {regex: /(let(?:\s+mut)?|fn|enum|mod|struct|type)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/, token: ["keyword", null, "def"]},
29 {regex: /(?:abstract|alignof|as|box|break|continue|const|crate|do|else|enum|extern|fn|for|final|if|impl|in|loop|macro|match|mod|move|offsetof|override|priv|proc|pub|pure|ref|return|self|sizeof|static|struct|super|trait|type|typeof|unsafe|unsized|use|virtual|where|while|yield)\b/, token: "keyword"},
30 {regex: /\b(?:Self|isize|usize|char|bool|u8|u16|u32|u64|f16|f32|f64|i8|i16|i32|i64|str|Option)\b/, token: "atom"},
31 {regex: /\b(?:true|false|Some|None|Ok|Err)\b/, token: "builtin"},
32 {regex: /\b(fn)(\s+)([a-zA-Z_][a-zA-Z0-9_]*)/,
33 token: ["keyword", null ,"def"]},
34 {regex: /#!?\[.*\]/, token: "meta"},
35 {regex: /\/\/.*/, token: "comment"},
36 {regex: /\/\*/, token: "comment", next: "comment"},
37 {regex: /[-+\/*=<>!]+/, token: "operator"},
38 {regex: /[a-zA-Z_]\w*!/,token: "variable-3"},
39 {regex: /[a-zA-Z_]\w*/, token: "variable"},
40 {regex: /[\{\[\(]/, indent: true},
41 {regex: /[\}\]\)]/, dedent: true}
42 ],
43 string: [
44 {regex: /"/, token: "string", next: "start"},
45 {regex: /(?:[^\\"]|\\(?:.|$))*/, token: "string"}
46 ],
47 string_raw: [
48 {regex: /"/, token: "string", next: "start"},
49 {regex: /[^"]*/, token: "string"}
50 ],
51 string_raw_hash: [
52 {regex: /"#+/, token: "string", next: "start"},
53 {regex: /(?:[^"]|"(?!#))*/, token: "string"}
54 ],
55 comment: [
56 {regex: /.*?\*\//, token: "comment", next: "start"},
57 {regex: /.*/, token: "comment"}
58 ],
59 meta: {
60 dontIndentStates: ["comment"],
61 electricInput: /^\s*\}$/,
442 62 blockCommentStart: "/*",
443 63 blockCommentEnd: "*/",
444 64 lineComment: "//",
445 65 fold: "brace"
446 };
66 }
447 67 });
448 68
69
449 70 CodeMirror.defineMIME("text/x-rustsrc", "rust");
450
451 71 });
@@ -165,7 +165,9 b' CodeMirror.defineMode("sparql", function'
165 165 return context.col + (closing ? 0 : 1);
166 166 else
167 167 return context.indent + (closing ? 0 : indentUnit);
168 }
168 },
169
170 lineComment: "#"
169 171 };
170 172 });
171 173
@@ -257,7 +257,7 b' CodeMirror.defineMode("sql", function(co'
257 257 }
258 258
259 259 // these keywords are used by all SQL dialects (however, a mode can still overwrite it)
260 var sqlKeywords = "alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where ";
260 var sqlKeywords = "alter and as asc between by count create delete desc distinct drop from group having in insert into is join like not on or order select set table union update values where limit";
261 261
262 262 // turn a space-separated list into an array
263 263 function set(str) {
@@ -126,19 +126,16 b''
126 126 if (stream.match(/^&{1}\s*$/)) {
127 127 return ["variable-3", "reference"];
128 128 }
129 // Variable
130 if (ch == "$" && stream.match(/^\$[\w-]+/i)) {
131 return ["variable-2", "variable-name"];
132 }
133 129 // Word operator
134 130 if (stream.match(wordOperatorKeywordsRegexp)) {
135 131 return ["operator", "operator"];
136 132 }
137 133 // Word
138 if (stream.match(/^[-_]*[a-z0-9]+[\w-]*/i)) {
134 if (stream.match(/^\$?[-_]*[a-z0-9]+[\w-]*/i)) {
135 // Variable
139 136 if (stream.match(/^(\.|\[)[\w-\'\"\]]+/i, false)) {
140 137 if (!wordIsTag(stream.current())) {
141 stream.match(/[\w-]+/);
138 stream.match(/\./);
142 139 return ["variable-2", "variable-name"];
143 140 }
144 141 }
@@ -323,7 +320,7 b''
323 320 return pushContext(state, stream, "block", 0);
324 321 }
325 322 if (type == "variable-name") {
326 if ((stream.indentation() == 0 && startOfLine(stream)) || wordIsBlock(firstWordOfLine(stream))) {
323 if (stream.string.match(/^\s?\$[\w-\.\[\]\'\"]+$/) || wordIsBlock(firstWordOfLine(stream))) {
327 324 return pushContext(state, stream, "variableName");
328 325 }
329 326 else {
@@ -429,6 +426,11 b''
429 426 return pushContext(state, stream, "block");
430 427 }
431 428 if (word == "return") return pushContext(state, stream, "block", 0);
429
430 // Placeholder selector
431 if (override == "variable-2" && stream.string.match(/^\s?\$[\w-\.\[\]\'\"]+$/)) {
432 return pushContext(state, stream, "block");
433 }
432 434 }
433 435 return state.context.type;
434 436 };
@@ -639,7 +641,6 b''
639 641 states.variableName = function(type, stream, state) {
640 642 if (type == "string" || type == "[" || type == "]" || stream.current().match(/^(\.|\$)/)) {
641 643 if (stream.current().match(/^\.[\w-]+/i)) override = "variable-2";
642 if (endOfLine(stream)) return popContext(state);
643 644 return "variableName";
644 645 }
645 646 return popAndPass(type, stream, state);
@@ -735,7 +736,7 b''
735 736 var nonStandardPropertyKeywords_ = ["scrollbar-arrow-color","scrollbar-base-color","scrollbar-dark-shadow-color","scrollbar-face-color","scrollbar-highlight-color","scrollbar-shadow-color","scrollbar-3d-light-color","scrollbar-track-color","shape-inside","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","zoom"];
736 737 var fontProperties_ = ["font-family","src","unicode-range","font-variant","font-feature-settings","font-stretch","font-weight","font-style"];
737 738 var colorKeywords_ = ["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"];
738 var valueKeywords_ = ["above","absolute","activeborder","additive","activecaption","afar","after-white-space","ahead","alias","all","all-scroll","alphabetic","alternate","always","amharic","amharic-abegede","antialiased","appworkspace","arabic-indic","armenian","asterisks","attr","auto","avoid","avoid-column","avoid-page","avoid-region","background","backwards","baseline","below","bidi-override","binary","bengali","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","bullets","button","button-bevel","buttonface","buttonhighlight","buttonshadow","buttontext","calc","cambodian","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-decimal","cjk-earthly-branch","cjk-heavenly-stem","cjk-ideographic","clear","clip","close-quote","col-resize","collapse","column","compact","condensed","contain","content","content-box","context-menu","continuous","copy","counter","counters","cover","crop","cross","crosshair","currentcolor","cursive","cyclic","dashed","decimal","decimal-leading-zero","default","default-button","destination-atop","destination-in","destination-out","destination-over","devanagari","disc","discard","disclosure-closed","disclosure-open","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic","ethiopic-abegede","ethiopic-abegede-am-et","ethiopic-abegede-gez","ethiopic-abegede-ti-er","ethiopic-abegede-ti-et","ethiopic-halehame-aa-er","ethiopic-halehame-aa-et","ethiopic-halehame-am-et","ethiopic-halehame-gez","ethiopic-halehame-om-et","ethiopic-halehame-sid-et","ethiopic-halehame-so-et","ethiopic-halehame-ti-er","ethiopic-halehame-ti-et","ethiopic-halehame-tig","ethiopic-numeric","ew-resize","expanded","extends","extra-condensed","extra-expanded","fantasy","fast","fill","fixed","flat","flex","footnotes","forwards","from","geometricPrecision","georgian","graytext","groove","gujarati","gurmukhi","hand","hangul","hangul-consonant","hebrew","help","hidden","hide","higher","highlight","highlighttext","hiragana","hiragana-iroha","horizontal","hsl","hsla","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-flex","inline-table","inset","inside","intrinsic","invert","italic","japanese-formal","japanese-informal","justify","kannada","katakana","katakana-iroha","keep-all","khmer","korean-hangul-formal","korean-hanja-formal","korean-hanja-informal","landscape","lao","large","larger","left","level","lighter","line-through","linear","linear-gradient","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-alpha","lower-armenian","lower-greek","lower-hexadecimal","lower-latin","lower-norwegian","lower-roman","lowercase","ltr","malayalam","match","matrix","matrix3d","media-controls-background","media-current-time-display","media-fullscreen-button","media-mute-button","media-play-button","media-return-to-realtime-button","media-rewind-button","media-seek-back-button","media-seek-forward-button","media-slider","media-sliderthumb","media-time-remaining-display","media-volume-slider","media-volume-slider-container","media-volume-sliderthumb","medium","menu","menulist","menulist-button","menulist-text","menulist-textfield","menutext","message-box","middle","min-intrinsic","mix","mongolian","monospace","move","multiple","myanmar","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","numbers","numeric","nw-resize","nwse-resize","oblique","octal","open-quote","optimizeLegibility","optimizeSpeed","oriya","oromo","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","persian","perspective","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radial-gradient","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeating-linear-gradient","repeating-radial-gradient","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","rotate","rotate3d","rotateX","rotateY","rotateZ","round","row-resize","rtl","run-in","running","s-resize","sans-serif","scale","scale3d","scaleX","scaleY","scaleZ","scroll","scrollbar","se-resize","searchfield","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","semi-condensed","semi-expanded","separate","serif","show","sidama","simp-chinese-formal","simp-chinese-informal","single","skew","skewX","skewY","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","solid","somali","source-atop","source-in","source-out","source-over","space","spell-out","square","square-button","start","static","status-bar","stretch","stroke","sub","subpixel-antialiased","super","sw-resize","symbolic","symbols","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","tamil","telugu","text","text-bottom","text-top","textarea","textfield","thai","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","tibetan","tigre","tigrinya-er","tigrinya-er-abegede","tigrinya-et","tigrinya-et-abegede","to","top","trad-chinese-formal","trad-chinese-informal","translate","translate3d","translateX","translateY","translateZ","transparent","ultra-condensed","ultra-expanded","underline","up","upper-alpha","upper-armenian","upper-greek","upper-hexadecimal","upper-latin","upper-norwegian","upper-roman","uppercase","urdu","url","var","vertical","vertical-text","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","words","x-large","x-small","xor","xx-large","xx-small","bicubic","optimizespeed","grayscale"];
739 var valueKeywords_ = ["above","absolute","activeborder","additive","activecaption","afar","after-white-space","ahead","alias","all","all-scroll","alphabetic","alternate","always","amharic","amharic-abegede","antialiased","appworkspace","arabic-indic","armenian","asterisks","attr","auto","avoid","avoid-column","avoid-page","avoid-region","background","backwards","baseline","below","bidi-override","binary","bengali","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","bullets","button","button-bevel","buttonface","buttonhighlight","buttonshadow","buttontext","calc","cambodian","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-decimal","cjk-earthly-branch","cjk-heavenly-stem","cjk-ideographic","clear","clip","close-quote","col-resize","collapse","column","compact","condensed","contain","content","content-box","context-menu","continuous","copy","counter","counters","cover","crop","cross","crosshair","currentcolor","cursive","cyclic","dashed","decimal","decimal-leading-zero","default","default-button","destination-atop","destination-in","destination-out","destination-over","devanagari","disc","discard","disclosure-closed","disclosure-open","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic","ethiopic-abegede","ethiopic-abegede-am-et","ethiopic-abegede-gez","ethiopic-abegede-ti-er","ethiopic-abegede-ti-et","ethiopic-halehame-aa-er","ethiopic-halehame-aa-et","ethiopic-halehame-am-et","ethiopic-halehame-gez","ethiopic-halehame-om-et","ethiopic-halehame-sid-et","ethiopic-halehame-so-et","ethiopic-halehame-ti-er","ethiopic-halehame-ti-et","ethiopic-halehame-tig","ethiopic-numeric","ew-resize","expanded","extends","extra-condensed","extra-expanded","fantasy","fast","fill","fixed","flat","flex","footnotes","forwards","from","geometricPrecision","georgian","graytext","groove","gujarati","gurmukhi","hand","hangul","hangul-consonant","hebrew","help","hidden","hide","higher","highlight","highlighttext","hiragana","hiragana-iroha","horizontal","hsl","hsla","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-flex","inline-table","inset","inside","intrinsic","invert","italic","japanese-formal","japanese-informal","justify","kannada","katakana","katakana-iroha","keep-all","khmer","korean-hangul-formal","korean-hanja-formal","korean-hanja-informal","landscape","lao","large","larger","left","level","lighter","line-through","linear","linear-gradient","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-alpha","lower-armenian","lower-greek","lower-hexadecimal","lower-latin","lower-norwegian","lower-roman","lowercase","ltr","malayalam","match","matrix","matrix3d","media-controls-background","media-current-time-display","media-fullscreen-button","media-mute-button","media-play-button","media-return-to-realtime-button","media-rewind-button","media-seek-back-button","media-seek-forward-button","media-slider","media-sliderthumb","media-time-remaining-display","media-volume-slider","media-volume-slider-container","media-volume-sliderthumb","medium","menu","menulist","menulist-button","menulist-text","menulist-textfield","menutext","message-box","middle","min-intrinsic","mix","mongolian","monospace","move","multiple","myanmar","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","numbers","numeric","nw-resize","nwse-resize","oblique","octal","open-quote","optimizeLegibility","optimizeSpeed","oriya","oromo","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","persian","perspective","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radial-gradient","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeating-linear-gradient","repeating-radial-gradient","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","rotate","rotate3d","rotateX","rotateY","rotateZ","round","row-resize","rtl","run-in","running","s-resize","sans-serif","scale","scale3d","scaleX","scaleY","scaleZ","scroll","scrollbar","se-resize","searchfield","searchfield-cancel-button","searchfield-decoration","searchfield-results-button","searchfield-results-decoration","semi-condensed","semi-expanded","separate","serif","show","sidama","simp-chinese-formal","simp-chinese-informal","single","skew","skewX","skewY","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","solid","somali","source-atop","source-in","source-out","source-over","space","spell-out","square","square-button","start","static","status-bar","stretch","stroke","sub","subpixel-antialiased","super","sw-resize","symbolic","symbols","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","tamil","telugu","text","text-bottom","text-top","textarea","textfield","thai","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","tibetan","tigre","tigrinya-er","tigrinya-er-abegede","tigrinya-et","tigrinya-et-abegede","to","top","trad-chinese-formal","trad-chinese-informal","translate","translate3d","translateX","translateY","translateZ","transparent","ultra-condensed","ultra-expanded","underline","up","upper-alpha","upper-armenian","upper-greek","upper-hexadecimal","upper-latin","upper-norwegian","upper-roman","uppercase","urdu","url","var","vertical","vertical-text","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","words","x-large","x-small","xor","xx-large","xx-small","bicubic","optimizespeed","grayscale","row","row-reverse","wrap","wrap-reverse","column-reverse","flex-start","flex-end","space-between","space-around"];
739 740
740 741 var wordOperatorKeywords_ = ["in","and","or","not","is not","is a","is","isnt","defined","if unless"],
741 742 blockKeywords_ = ["for","if","else","unless", "from", "to"],
@@ -13,189 +13,188 b''
13 13 })(function(CodeMirror) {
14 14 "use strict"
15 15
16 function trim(str) { return /^\s*(.*?)\s*$/.exec(str)[1] }
17
18 var separators = [" ","\\\+","\\\-","\\\(","\\\)","\\\*","/",":","\\\?","\\\<","\\\>"," ","\\\."]
19 var tokens = new RegExp(separators.join("|"),"g")
20
21 function getWord(string, pos) {
22 var index = -1, count = 1
23 var words = string.split(tokens)
24 for (var i = 0; i < words.length; i++) {
25 for(var j = 1; j <= words[i].length; j++) {
26 if (count==pos) index = i
27 count++
28 }
29 count++
30 }
31 var ret = ["", ""]
32 if (pos == 0) {
33 ret[1] = words[0]
34 ret[0] = null
35 } else {
36 ret[1] = words[index]
37 ret[0] = words[index-1]
38 }
39 return ret
16 function wordSet(words) {
17 var set = {}
18 for (var i = 0; i < words.length; i++) set[words[i]] = true
19 return set
40 20 }
41 21
42 CodeMirror.defineMode("swift", function() {
43 var keywords=["var","let","class","deinit","enum","extension","func","import","init","let","protocol","static","struct","subscript","typealias","var","as","dynamicType","is","new","super","self","Self","Type","__COLUMN__","__FILE__","__FUNCTION__","__LINE__","break","case","continue","default","do","else","fallthrough","if","in","for","return","switch","where","while","associativity","didSet","get","infix","inout","left","mutating","none","nonmutating","operator","override","postfix","precedence","prefix","right","set","unowned","unowned(safe)","unowned(unsafe)","weak","willSet"]
44 var commonConstants=["Infinity","NaN","undefined","null","true","false","on","off","yes","no","nil","null","this","super"]
45 var types=["String","bool","int","string","double","Double","Int","Float","float","public","private","extension"]
46 var numbers=["0","1","2","3","4","5","6","7","8","9"]
47 var operators=["+","-","/","*","%","=","|","&","<",">"]
48 var punc=[";",",",".","(",")","{","}","[","]"]
49 var delimiters=/^(?:[()\[\]{},:`=;]|\.\.?\.?)/
50 var identifiers=/^[_A-Za-z$][_A-Za-z$0-9]*/
51 var properties=/^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*/
52 var regexPrefixes=/^(\/{3}|\/)/
22 var keywords = wordSet(["var","let","class","deinit","enum","extension","func","import","init","protocol",
23 "static","struct","subscript","typealias","as","dynamicType","is","new","super",
24 "self","Self","Type","__COLUMN__","__FILE__","__FUNCTION__","__LINE__","break","case",
25 "continue","default","do","else","fallthrough","if","in","for","return","switch",
26 "where","while","associativity","didSet","get","infix","inout","left","mutating",
27 "none","nonmutating","operator","override","postfix","precedence","prefix","right",
28 "set","unowned","weak","willSet"])
29 var definingKeywords = wordSet(["var","let","class","enum","extension","func","import","protocol","struct",
30 "typealias","dynamicType","for"])
31 var atoms = wordSet(["Infinity","NaN","undefined","null","true","false","on","off","yes","no","nil","null",
32 "this","super"])
33 var types = wordSet(["String","bool","int","string","double","Double","Int","Float","float","public",
34 "private","extension"])
35 var operators = "+-/*%=|&<>#"
36 var punc = ";,.(){}[]"
37 var number = /^-?(?:(?:[\d_]+\.[_\d]*|\.[_\d]+|0o[0-7_\.]+|0b[01_\.]+)(?:e-?[\d_]+)?|0x[\d_a-f\.]+(?:p-?[\d_]+)?)/i
38 var identifier = /^[_A-Za-z$][_A-Za-z$0-9]*/
39 var property = /^[@\.][_A-Za-z$][_A-Za-z$0-9]*/
40 var regexp = /^\/(?!\s)(?:\/\/)?(?:\\.|[^\/])+\//
41
42 function tokenBase(stream, state, prev) {
43 if (stream.sol()) state.indented = stream.indentation()
44 if (stream.eatSpace()) return null
45
46 var ch = stream.peek()
47 if (ch == "/") {
48 if (stream.match("//")) {
49 stream.skipToEnd()
50 return "comment"
51 }
52 if (stream.match("/*")) {
53 state.tokenize.push(tokenComment)
54 return tokenComment(stream, state)
55 }
56 if (stream.match(regexp)) return "string-2"
57 }
58 if (operators.indexOf(ch) > -1) {
59 stream.next()
60 return "operator"
61 }
62 if (punc.indexOf(ch) > -1) {
63 stream.next()
64 stream.match("..")
65 return "punctuation"
66 }
67 if (ch == '"' || ch == "'") {
68 stream.next()
69 var tokenize = tokenString(ch)
70 state.tokenize.push(tokenize)
71 return tokenize(stream, state)
72 }
73
74 if (stream.match(number)) return "number"
75 if (stream.match(property)) return "property"
76
77 if (stream.match(identifier)) {
78 var ident = stream.current()
79 if (keywords.hasOwnProperty(ident)) {
80 if (definingKeywords.hasOwnProperty(ident))
81 state.prev = "define"
82 return "keyword"
83 }
84 if (types.hasOwnProperty(ident)) return "variable-2"
85 if (atoms.hasOwnProperty(ident)) return "atom"
86 if (prev == "define") return "def"
87 return "variable"
88 }
53 89
90 stream.next()
91 return null
92 }
93
94 function tokenUntilClosingParen() {
95 var depth = 0
96 return function(stream, state, prev) {
97 var inner = tokenBase(stream, state, prev)
98 if (inner == "punctuation") {
99 if (stream.current() == "(") ++depth
100 else if (stream.current() == ")") {
101 if (depth == 0) {
102 stream.backUp(1)
103 state.tokenize.pop()
104 return state.tokenize[state.tokenize.length - 1](stream, state)
105 }
106 else --depth
107 }
108 }
109 return inner
110 }
111 }
112
113 function tokenString(quote) {
114 return function(stream, state) {
115 var ch, escaped = false
116 while (ch = stream.next()) {
117 if (escaped) {
118 if (ch == "(") {
119 state.tokenize.push(tokenUntilClosingParen())
120 return "string"
121 }
122 escaped = false
123 } else if (ch == quote) {
124 break
125 } else {
126 escaped = ch == "\\"
127 }
128 }
129 state.tokenize.pop()
130 return "string"
131 }
132 }
133
134 function tokenComment(stream, state) {
135 stream.match(/^(?:[^*]|\*(?!\/))*/)
136 if (stream.match("*/")) state.tokenize.pop()
137 return "comment"
138 }
139
140 function Context(prev, align, indented) {
141 this.prev = prev
142 this.align = align
143 this.indented = indented
144 }
145
146 function pushContext(state, stream) {
147 var align = stream.match(/^\s*($|\/[\/\*])/, false) ? null : stream.column() + 1
148 state.context = new Context(state.context, align, state.indented)
149 }
150
151 function popContext(state) {
152 if (state.context) {
153 state.indented = state.context.indented
154 state.context = state.context.prev
155 }
156 }
157
158 CodeMirror.defineMode("swift", function(config) {
54 159 return {
55 160 startState: function() {
56 161 return {
57 prev: false,
58 string: false,
59 escape: false,
60 inner: false,
61 comment: false,
62 num_left: 0,
63 num_right: 0,
64 doubleString: false,
65 singleString: false
162 prev: null,
163 context: null,
164 indented: 0,
165 tokenize: []
66 166 }
67 167 },
68 token: function(stream, state) {
69 if (stream.eatSpace()) return null
70 168
71 var ch = stream.next()
72 if (state.string) {
73 if (state.escape) {
74 state.escape = false
75 return "string"
76 } else {
77 if ((ch == "\"" && (state.doubleString && !state.singleString) ||
78 (ch == "'" && (!state.doubleString && state.singleString))) &&
79 !state.escape) {
80 state.string = false
81 state.doubleString = false
82 state.singleString = false
83 return "string"
84 } else if (ch == "\\" && stream.peek() == "(") {
85 state.inner = true
86 state.string = false
87 return "keyword"
88 } else if (ch == "\\" && stream.peek() != "(") {
89 state.escape = true
90 state.string = true
91 return "string"
92 } else {
93 return "string"
94 }
95 }
96 } else if (state.comment) {
97 if (ch == "*" && stream.peek() == "/") {
98 state.prev = "*"
99 return "comment"
100 } else if (ch == "/" && state.prev == "*") {
101 state.prev = false
102 state.comment = false
103 return "comment"
104 }
105 return "comment"
106 } else {
107 if (ch == "/") {
108 if (stream.peek() == "/") {
109 stream.skipToEnd()
110 return "comment"
111 }
112 if (stream.peek() == "*") {
113 state.comment = true
114 return "comment"
115 }
116 }
117 if (ch == "(" && state.inner) {
118 state.num_left++
119 return null
120 }
121 if (ch == ")" && state.inner) {
122 state.num_right++
123 if (state.num_left == state.num_right) {
124 state.inner=false
125 state.string=true
126 }
127 return null
128 }
169 token: function(stream, state) {
170 var prev = state.prev
171 state.prev = null
172 var tokenize = state.tokenize[state.tokenize.length - 1] || tokenBase
173 var style = tokenize(stream, state, prev)
174 if (!style || style == "comment") state.prev = prev
175 else if (!state.prev) state.prev = style
129 176
130 var ret = getWord(stream.string, stream.pos)
131 var the_word = ret[1]
132 var prev_word = ret[0]
177 if (style == "punctuation") {
178 var bracket = /[\(\[\{]|([\]\)\}])/.exec(stream.current())
179 if (bracket) (bracket[1] ? popContext : pushContext)(state, stream)
180 }
133 181
134 if (operators.indexOf(ch + "") > -1) return "operator"
135 if (punc.indexOf(ch) > -1) return "punctuation"
136
137 if (typeof the_word != "undefined") {
138 the_word = trim(the_word)
139 if (typeof prev_word != "undefined") prev_word = trim(prev_word)
140 if (the_word.charAt(0) == "#") return null
141
142 if (types.indexOf(the_word) > -1) return "def"
143 if (commonConstants.indexOf(the_word) > -1) return "atom"
144 if (numbers.indexOf(the_word) > -1) return "number"
145
146 if ((numbers.indexOf(the_word.charAt(0) + "") > -1 ||
147 operators.indexOf(the_word.charAt(0) + "") > -1) &&
148 numbers.indexOf(ch) > -1) {
149 return "number"
150 }
182 return style
183 },
151 184
152 if (keywords.indexOf(the_word) > -1 ||
153 keywords.indexOf(the_word.split(tokens)[0]) > -1)
154 return "keyword"
155 if (keywords.indexOf(prev_word) > -1) return "def"
156 }
157 if (ch == '"' && !state.doubleString) {
158 state.string = true
159 state.doubleString = true
160 return "string"
161 }
162 if (ch == "'" && !state.singleString) {
163 state.string = true
164 state.singleString = true
165 return "string"
166 }
167 if (ch == "(" && state.inner)
168 state.num_left++
169 if (ch == ")" && state.inner) {
170 state.num_right++
171 if (state.num_left == state.num_right) {
172 state.inner = false
173 state.string = true
174 }
175 return null
176 }
177 if (stream.match(/^-?[0-9\.]/, false)) {
178 if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i) ||
179 stream.match(/^-?\d+\.\d*/) ||
180 stream.match(/^-?\.\d+/)) {
181 if (stream.peek() == ".") stream.backUp(1)
182 return "number"
183 }
184 if (stream.match(/^-?0x[0-9a-f]+/i) ||
185 stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/) ||
186 stream.match(/^-?0(?![\dx])/i))
187 return "number"
188 }
189 if (stream.match(regexPrefixes)) {
190 if (stream.current()!="/" || stream.match(/^.*\//,false)) return "string"
191 else stream.backUp(1)
192 }
193 if (stream.match(delimiters)) return "punctuation"
194 if (stream.match(identifiers)) return "variable"
195 if (stream.match(properties)) return "property"
196 return "variable"
197 }
198 }
185 indent: function(state, textAfter) {
186 var cx = state.context
187 if (!cx) return 0
188 var closing = /^[\]\}\)]/.test(textAfter)
189 if (cx.align != null) return cx.align - (closing ? 1 : 0)
190 return cx.indented + (closing ? 0 : config.indentUnit)
191 },
192
193 electricInput: /^\s*[\)\}\]]$/,
194
195 lineComment: "//",
196 blockCommentStart: "/*",
197 blockCommentEnd: "*/"
199 198 }
200 199 })
201 200
@@ -11,54 +11,56 b''
11 11 })(function(CodeMirror) {
12 12 "use strict";
13 13
14 CodeMirror.defineMode("xml", function(config, parserConfig) {
15 var indentUnit = config.indentUnit;
16 var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1;
17 var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag;
18 if (multilineTagIndentPastTag == null) multilineTagIndentPastTag = true;
14 var htmlConfig = {
15 autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
16 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
17 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
18 'track': true, 'wbr': true, 'menuitem': true},
19 implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
20 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
21 'th': true, 'tr': true},
22 contextGrabbers: {
23 'dd': {'dd': true, 'dt': true},
24 'dt': {'dd': true, 'dt': true},
25 'li': {'li': true},
26 'option': {'option': true, 'optgroup': true},
27 'optgroup': {'optgroup': true},
28 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
29 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
30 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
31 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
32 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
33 'rp': {'rp': true, 'rt': true},
34 'rt': {'rp': true, 'rt': true},
35 'tbody': {'tbody': true, 'tfoot': true},
36 'td': {'td': true, 'th': true},
37 'tfoot': {'tbody': true},
38 'th': {'td': true, 'th': true},
39 'thead': {'tbody': true, 'tfoot': true},
40 'tr': {'tr': true}
41 },
42 doNotIndent: {"pre": true},
43 allowUnquoted: true,
44 allowMissing: true,
45 caseFold: true
46 }
19 47
20 var Kludges = parserConfig.htmlMode ? {
21 autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
22 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
23 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
24 'track': true, 'wbr': true, 'menuitem': true},
25 implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
26 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
27 'th': true, 'tr': true},
28 contextGrabbers: {
29 'dd': {'dd': true, 'dt': true},
30 'dt': {'dd': true, 'dt': true},
31 'li': {'li': true},
32 'option': {'option': true, 'optgroup': true},
33 'optgroup': {'optgroup': true},
34 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
35 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
36 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
37 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
38 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
39 'rp': {'rp': true, 'rt': true},
40 'rt': {'rp': true, 'rt': true},
41 'tbody': {'tbody': true, 'tfoot': true},
42 'td': {'td': true, 'th': true},
43 'tfoot': {'tbody': true},
44 'th': {'td': true, 'th': true},
45 'thead': {'tbody': true, 'tfoot': true},
46 'tr': {'tr': true}
47 },
48 doNotIndent: {"pre": true},
49 allowUnquoted: true,
50 allowMissing: true,
51 caseFold: true
52 } : {
53 autoSelfClosers: {},
54 implicitlyClosed: {},
55 contextGrabbers: {},
56 doNotIndent: {},
57 allowUnquoted: false,
58 allowMissing: false,
59 caseFold: false
60 };
61 var alignCDATA = parserConfig.alignCDATA;
48 var xmlConfig = {
49 autoSelfClosers: {},
50 implicitlyClosed: {},
51 contextGrabbers: {},
52 doNotIndent: {},
53 allowUnquoted: false,
54 allowMissing: false,
55 caseFold: false
56 }
57
58 CodeMirror.defineMode("xml", function(editorConf, config_) {
59 var indentUnit = editorConf.indentUnit
60 var config = {}
61 var defaults = config_.htmlMode ? htmlConfig : xmlConfig
62 for (var prop in defaults) config[prop] = defaults[prop]
63 for (var prop in config_) config[prop] = config_[prop]
62 64
63 65 // Return variables for tokenizers
64 66 var type, setStyle;
@@ -109,6 +111,7 b' CodeMirror.defineMode("xml", function(co'
109 111 return null;
110 112 }
111 113 }
114 inText.isInText = true;
112 115
113 116 function inTag(stream, state) {
114 117 var ch = stream.next();
@@ -187,7 +190,7 b' CodeMirror.defineMode("xml", function(co'
187 190 this.tagName = tagName;
188 191 this.indent = state.indented;
189 192 this.startOfLine = startOfLine;
190 if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
193 if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
191 194 this.noIndent = true;
192 195 }
193 196 function popContext(state) {
@@ -200,8 +203,8 b' CodeMirror.defineMode("xml", function(co'
200 203 return;
201 204 }
202 205 parentTagName = state.context.tagName;
203 if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
204 !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
206 if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
207 !config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
205 208 return;
206 209 }
207 210 popContext(state);
@@ -232,7 +235,7 b' CodeMirror.defineMode("xml", function(co'
232 235 if (type == "word") {
233 236 var tagName = stream.current();
234 237 if (state.context && state.context.tagName != tagName &&
235 Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName))
238 config.implicitlyClosed.hasOwnProperty(state.context.tagName))
236 239 popContext(state);
237 240 if (state.context && state.context.tagName == tagName) {
238 241 setStyle = "tag";
@@ -268,7 +271,7 b' CodeMirror.defineMode("xml", function(co'
268 271 var tagName = state.tagName, tagStart = state.tagStart;
269 272 state.tagName = state.tagStart = null;
270 273 if (type == "selfcloseTag" ||
271 Kludges.autoSelfClosers.hasOwnProperty(tagName)) {
274 config.autoSelfClosers.hasOwnProperty(tagName)) {
272 275 maybePopContext(state, tagName);
273 276 } else {
274 277 maybePopContext(state, tagName);
@@ -281,12 +284,12 b' CodeMirror.defineMode("xml", function(co'
281 284 }
282 285 function attrEqState(type, stream, state) {
283 286 if (type == "equals") return attrValueState;
284 if (!Kludges.allowMissing) setStyle = "error";
287 if (!config.allowMissing) setStyle = "error";
285 288 return attrState(type, stream, state);
286 289 }
287 290 function attrValueState(type, stream, state) {
288 291 if (type == "string") return attrContinuedState;
289 if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;}
292 if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
290 293 setStyle = "error";
291 294 return attrState(type, stream, state);
292 295 }
@@ -296,12 +299,14 b' CodeMirror.defineMode("xml", function(co'
296 299 }
297 300
298 301 return {
299 startState: function() {
300 return {tokenize: inText,
301 state: baseState,
302 indented: 0,
303 tagName: null, tagStart: null,
304 context: null};
302 startState: function(baseIndent) {
303 var state = {tokenize: inText,
304 state: baseState,
305 indented: baseIndent || 0,
306 tagName: null, tagStart: null,
307 context: null}
308 if (baseIndent != null) state.baseIndent = baseIndent
309 return state
305 310 },
306 311
307 312 token: function(stream, state) {
@@ -334,19 +339,19 b' CodeMirror.defineMode("xml", function(co'
334 339 return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
335 340 // Indent the starts of attribute names.
336 341 if (state.tagName) {
337 if (multilineTagIndentPastTag)
342 if (config.multilineTagIndentPastTag !== false)
338 343 return state.tagStart + state.tagName.length + 2;
339 344 else
340 return state.tagStart + indentUnit * multilineTagIndentFactor;
345 return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
341 346 }
342 if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
347 if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
343 348 var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
344 349 if (tagAfter && tagAfter[1]) { // Closing tag spotted
345 350 while (context) {
346 351 if (context.tagName == tagAfter[2]) {
347 352 context = context.prev;
348 353 break;
349 } else if (Kludges.implicitlyClosed.hasOwnProperty(context.tagName)) {
354 } else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
350 355 context = context.prev;
351 356 } else {
352 357 break;
@@ -354,25 +359,30 b' CodeMirror.defineMode("xml", function(co'
354 359 }
355 360 } else if (tagAfter) { // Opening tag spotted
356 361 while (context) {
357 var grabbers = Kludges.contextGrabbers[context.tagName];
362 var grabbers = config.contextGrabbers[context.tagName];
358 363 if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
359 364 context = context.prev;
360 365 else
361 366 break;
362 367 }
363 368 }
364 while (context && !context.startOfLine)
369 while (context && context.prev && !context.startOfLine)
365 370 context = context.prev;
366 371 if (context) return context.indent + indentUnit;
367 else return 0;
372 else return state.baseIndent || 0;
368 373 },
369 374
370 375 electricInput: /<\/[\s\w:]+>$/,
371 376 blockCommentStart: "<!--",
372 377 blockCommentEnd: "-->",
373 378
374 configuration: parserConfig.htmlMode ? "html" : "xml",
375 helperType: parserConfig.htmlMode ? "html" : "xml"
379 configuration: config.htmlMode ? "html" : "xml",
380 helperType: config.htmlMode ? "html" : "xml",
381
382 skipAttribute: function(state) {
383 if (state.state == attrValueState)
384 state.state = attrState
385 }
376 386 };
377 387 });
378 388
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': 'disabled',
12 'enabled': 'enabled',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': 'Open new pull request',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': 'disabled',
49 'enabled': 'enabled',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'just now',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': 'disabled',
12 'enabled': 'enabled',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': 'Open new pull request',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': 'disabled',
49 'enabled': 'enabled',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'jetzt gerade',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': 'disabled',
12 'enabled': 'enabled',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': 'Open new pull request',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': 'disabled',
49 'enabled': 'enabled',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'just now',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': 'disabled',
12 'enabled': 'enabled',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': 'Open new pull request',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': 'disabled',
49 'enabled': 'enabled',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'just now',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': 'Désactivé',
12 'enabled': 'enabled',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': 'Nouvelle requête de pull',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': 'Désactivé',
49 'enabled': 'enabled',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'à l’instant',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': 'disabilitato',
12 'enabled': 'abilitato',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': 'Apri una richiesta PULL',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Seleziona una commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': 'disabilitato',
49 'enabled': 'abilitato',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'proprio ora',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': '無効',
12 'enabled': '有効',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': '新しいプルリクエストを作成',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': '無効',
49 'enabled': '有効',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'たったいま',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': 'disabled',
12 'enabled': 'enabled',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': 'Otwórz nową prośbę o połączenie gałęzi',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': 'disabled',
49 'enabled': 'enabled',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'przed chwilą',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': 'desabilitado',
12 'enabled': 'enabled',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': 'Crie novo pull request',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': 'desabilitado',
49 'enabled': 'enabled',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'agora há pouco',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': 'отключено',
12 'enabled': 'enabled',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': 'Создать новый pull запрос',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': 'отключено',
49 'enabled': 'enabled',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': 'прямо сейчас',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -5,35 +5,71 b''
5 5 */
6 6 //JS translations map
7 7 var _TM = {
8 '{0} active out of {1} users': '{0} active out of {1} users',
9 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
10 8 'Add another comment': 'Add another comment',
11 'disabled': '禁用',
12 'enabled': 'enabled',
9 'Comment text will be set automatically based on currently selected status ({0}) ...': 'Comment text will be set automatically based on currently selected status ({0}) ...',
13 10 'Follow': 'Follow',
14 'loading ...': 'loading ...',
15 11 'Loading ...': 'Loading ...',
16 12 'Loading failed': 'Loading failed',
17 13 'Loading more results...': 'Loading more results...',
14 'No bookmarks available yet.': 'No bookmarks available yet.',
15 'No branches available yet.': 'No branches available yet.',
16 'No gists available yet.': 'No gists available yet.',
18 17 'No matches found': 'No matches found',
19 18 'No matching files': 'No matching files',
19 'No pull requests available yet.': 'No pull requests available yet.',
20 'No repositories available yet.': 'No repositories available yet.',
21 'No repository groups available yet.': 'No repository groups available yet.',
20 22 'No results': 'No results',
23 'No tags available yet.': 'No tags available yet.',
24 'No user groups available yet.': 'No user groups available yet.',
25 'No users available yet.': 'No users available yet.',
21 26 'One result is available, press enter to select it.': 'One result is available, press enter to select it.',
22 27 'Open new pull request': '新建拉取请求',
23 28 'Open new pull request for selected commit': 'Open new pull request for selected commit',
29 'Please delete {0} character': 'Please delete {0} character',
30 'Please delete {0} characters': 'Please delete {0} characters',
31 'Please enter {0} or more character': 'Please enter {0} or more character',
32 'Please enter {0} or more characters': 'Please enter {0} or more characters',
24 33 'Searching...': 'Searching...',
25 'Select changeset': 'Select changeset',
26 'Select commit': 'Select commit',
27 34 'Selection link': 'Selection link',
28 35 'Set status to Approved': 'Set status to Approved',
29 36 'Set status to Rejected': 'Set status to Rejected',
37 'Show more': 'Show more',
30 38 'Show selected commit __S': 'Show selected commit __S',
31 39 'Show selected commits __S ... __E': 'Show selected commits __S ... __E',
40 'Start following this repository': 'Start following this repository',
41 'Status Review': 'Status Review',
42 'Stop following this repository': 'Stop following this repository',
43 'Submitting...': 'Submitting...',
44 'Unfollow': 'Unfollow',
45 'Updating...': 'Updating...',
46 'You can only select {0} item': 'You can only select {0} item',
47 'You can only select {0} items': 'You can only select {0} items',
48 'disabled': '禁用',
49 'enabled': 'enabled',
50 'file': 'file',
51 'files': 'files',
52 'in {0}': 'in {0}',
53 'in {0} and {1}': 'in {0} and {1}',
54 'in {0}, {1}': 'in {0}, {1}',
55 'just now': '刚才',
32 56 'specify commit': 'specify commit',
33 'Start following this repository': 'Start following this repository',
34 'Stop following this repository': 'Stop following this repository',
35 57 'truncated result': 'truncated result',
36 58 'truncated results': 'truncated results',
37 'Unfollow': 'Unfollow',
38 'Updating...': 'Updating...'
59 '{0} active out of {1} users': '{0} active out of {1} users',
60 '{0} ago': '{0} ago',
61 '{0} and {1}': '{0} and {1}',
62 '{0} and {1} ago': '{0} and {1} ago',
63 '{0} day': '{0} day',
64 '{0} days': '{0} days',
65 '{0} hour': '{0} hour',
66 '{0} hours': '{0} hours',
67 '{0} min': '{0} min',
68 '{0} month': '{0} month',
69 '{0} months': '{0} months',
70 '{0} results are available, use up and down arrow keys to navigate.': '{0} results are available, use up and down arrow keys to navigate.',
71 '{0} sec': '{0} sec',
72 '{0} year': '{0} year',
73 '{0} years': '{0} years',
74 '{0}, {1} ago': '{0}, {1} ago'
39 75 }; No newline at end of file
@@ -4,7 +4,8 b''
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 * This file is automatically generated when the app starts up. *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 9 * *
9 10 * To add a route here pass jsroute=True to the route definition in the app *
10 11 * *
@@ -27,7 +28,7 b' function registerRCRoutes() {'
27 28 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
28 29 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
29 30 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
30 pyroutes.register('changeset_info', '/changeset_info/%(repo_name)s/%(revision)s', ['repo_name', 'revision']);
31 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
31 32 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
32 33 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
33 34 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
@@ -44,7 +45,7 b' function registerRCRoutes() {'
44 45 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 46 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
46 47 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('files_metadata_list_home', '/%(repo_name)s/metadata_list/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
48 49 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
49 50 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
50 51 }
This diff has been collapsed as it changes many lines, (573 lines changed) Show them Hide them
@@ -13,7 +13,7 b''
13 13 else if (typeof define == "function" && define.amd) // AMD
14 14 return define([], mod);
15 15 else // Plain browser env
16 this.CodeMirror = mod();
16 (this || window).CodeMirror = mod();
17 17 })(function() {
18 18 "use strict";
19 19
@@ -21,27 +21,29 b''
21 21
22 22 // Kludges for bugs and behavior differences that can't be feature
23 23 // detected are enabled based on userAgent etc sniffing.
24
25 var gecko = /gecko\/\d/i.test(navigator.userAgent);
26 var ie_upto10 = /MSIE \d/.test(navigator.userAgent);
27 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);
24 var userAgent = navigator.userAgent;
25 var platform = navigator.platform;
26
27 var gecko = /gecko\/\d/i.test(userAgent);
28 var ie_upto10 = /MSIE \d/.test(userAgent);
29 var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent);
28 30 var ie = ie_upto10 || ie_11up;
29 31 var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]);
30 var webkit = /WebKit\//.test(navigator.userAgent);
31 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
32 var chrome = /Chrome\//.test(navigator.userAgent);
33 var presto = /Opera\//.test(navigator.userAgent);
32 var webkit = /WebKit\//.test(userAgent);
33 var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent);
34 var chrome = /Chrome\//.test(userAgent);
35 var presto = /Opera\//.test(userAgent);
34 36 var safari = /Apple Computer/.test(navigator.vendor);
35 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
36 var phantom = /PhantomJS/.test(navigator.userAgent);
37
38 var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
37 var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent);
38 var phantom = /PhantomJS/.test(userAgent);
39
40 var ios = /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent);
39 41 // This is woefully incomplete. Suggestions for alternative methods welcome.
40 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
41 var mac = ios || /Mac/.test(navigator.platform);
42 var windows = /win/i.test(navigator.platform);
43
44 var presto_version = presto && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
42 var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);
43 var mac = ios || /Mac/.test(platform);
44 var windows = /win/i.test(platform);
45
46 var presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/);
45 47 if (presto_version) presto_version = Number(presto_version[1]);
46 48 if (presto_version && presto_version >= 15) { presto = false; webkit = true; }
47 49 // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
@@ -65,7 +67,7 b''
65 67 setGuttersForLineNumbers(options);
66 68
67 69 var doc = options.value;
68 if (typeof doc == "string") doc = new Doc(doc, options.mode);
70 if (typeof doc == "string") doc = new Doc(doc, options.mode, null, options.lineSeparator);
69 71 this.doc = doc;
70 72
71 73 var input = new CodeMirror.inputStyles[options.inputStyle](this);
@@ -87,6 +89,7 b''
87 89 focused: false,
88 90 suppressEdits: false, // used to disable editing during key handlers when in readOnly mode
89 91 pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in input.poll
92 selectingText: false,
90 93 draggingText: false,
91 94 highlight: new Delayed(), // stores highlight worker timeout
92 95 keySeq: null, // Unfinished key sequence
@@ -407,7 +410,7 b''
407 410 if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal");
408 411 });
409 412
410 this.checkedOverlay = false;
413 this.checkedZeroWidth = false;
411 414 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
412 415 if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px";
413 416 }
@@ -442,29 +445,43 b''
442 445 this.horiz.firstChild.style.width = "0";
443 446 }
444 447
445 if (!this.checkedOverlay && measure.clientHeight > 0) {
446 if (sWidth == 0) this.overlayHack();
447 this.checkedOverlay = true;
448 if (!this.checkedZeroWidth && measure.clientHeight > 0) {
449 if (sWidth == 0) this.zeroWidthHack();
450 this.checkedZeroWidth = true;
448 451 }
449 452
450 453 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0};
451 454 },
452 455 setScrollLeft: function(pos) {
453 456 if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos;
457 if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz);
454 458 },
455 459 setScrollTop: function(pos) {
456 460 if (this.vert.scrollTop != pos) this.vert.scrollTop = pos;
457 },
458 overlayHack: function() {
461 if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert);
462 },
463 zeroWidthHack: function() {
459 464 var w = mac && !mac_geMountainLion ? "12px" : "18px";
460 this.horiz.style.minHeight = this.vert.style.minWidth = w;
461 var self = this;
462 var barMouseDown = function(e) {
463 if (e_target(e) != self.vert && e_target(e) != self.horiz)
464 operation(self.cm, onMouseDown)(e);
465 };
466 on(this.vert, "mousedown", barMouseDown);
467 on(this.horiz, "mousedown", barMouseDown);
465 this.horiz.style.height = this.vert.style.width = w;
466 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none";
467 this.disableHoriz = new Delayed;
468 this.disableVert = new Delayed;
469 },
470 enableZeroWidthBar: function(bar, delay) {
471 bar.style.pointerEvents = "auto";
472 function maybeDisable() {
473 // To find out whether the scrollbar is still visible, we
474 // check whether the element under the pixel in the bottom
475 // left corner of the scrollbar box is the scrollbar box
476 // itself (when the bar is still visible) or its filler child
477 // (when the bar is hidden). If it is still visible, we keep
478 // it enabled, if it's hidden, we disable pointer events.
479 var box = bar.getBoundingClientRect();
480 var elt = document.elementFromPoint(box.left + 1, box.bottom - 1);
481 if (elt != bar) bar.style.pointerEvents = "none";
482 else delay.set(1000, maybeDisable);
483 }
484 delay.set(1000, maybeDisable);
468 485 },
469 486 clear: function() {
470 487 var parent = this.horiz.parentNode;
@@ -714,7 +731,7 b''
714 731 // width and height.
715 732 removeChildren(display.cursorDiv);
716 733 removeChildren(display.selectionDiv);
717 display.gutters.style.height = 0;
734 display.gutters.style.height = display.sizer.style.minHeight = 0;
718 735
719 736 if (different) {
720 737 display.lastWrapHeight = update.wrapperHeight;
@@ -806,7 +823,7 b''
806 823 // given line.
807 824 function updateWidgetHeight(line) {
808 825 if (line.widgets) for (var i = 0; i < line.widgets.length; ++i)
809 line.widgets[i].height = line.widgets[i].node.offsetHeight;
826 line.widgets[i].height = line.widgets[i].node.parentNode.offsetHeight;
810 827 }
811 828
812 829 // Do a bulk-read of the DOM positions and sizes needed to draw the
@@ -955,12 +972,22 b''
955 972 lineView.node.removeChild(lineView.gutter);
956 973 lineView.gutter = null;
957 974 }
975 if (lineView.gutterBackground) {
976 lineView.node.removeChild(lineView.gutterBackground);
977 lineView.gutterBackground = null;
978 }
979 if (lineView.line.gutterClass) {
980 var wrap = ensureLineWrapped(lineView);
981 lineView.gutterBackground = elt("div", null, "CodeMirror-gutter-background " + lineView.line.gutterClass,
982 "left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) +
983 "px; width: " + dims.gutterTotalWidth + "px");
984 wrap.insertBefore(lineView.gutterBackground, lineView.text);
985 }
958 986 var markers = lineView.line.gutterMarkers;
959 987 if (cm.options.lineNumbers || markers) {
960 988 var wrap = ensureLineWrapped(lineView);
961 989 var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", "left: " +
962 (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) +
963 "px; width: " + dims.gutterTotalWidth + "px");
990 (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px");
964 991 cm.display.input.setUneditable(gutterWrap);
965 992 wrap.insertBefore(gutterWrap, lineView.text);
966 993 if (lineView.line.gutterClass)
@@ -1067,10 +1094,6 b''
1067 1094 if (!cm.state.focused) { cm.display.input.focus(); onFocus(cm); }
1068 1095 }
1069 1096
1070 function isReadOnly(cm) {
1071 return cm.options.readOnly || cm.doc.cantEdit;
1072 }
1073
1074 1097 // This will be set to an array of strings when copying, so that,
1075 1098 // when pasting, we know what kind of selections the copied text
1076 1099 // was made out of.
@@ -1082,13 +1105,18 b''
1082 1105 if (!sel) sel = doc.sel;
1083 1106
1084 1107 var paste = cm.state.pasteIncoming || origin == "paste";
1085 var textLines = splitLines(inserted), multiPaste = null;
1108 var textLines = doc.splitLines(inserted), multiPaste = null;
1086 1109 // When pasing N lines into N selections, insert one line per selection
1087 1110 if (paste && sel.ranges.length > 1) {
1088 if (lastCopied && lastCopied.join("\n") == inserted)
1089 multiPaste = sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines);
1090 else if (textLines.length == sel.ranges.length)
1111 if (lastCopied && lastCopied.join("\n") == inserted) {
1112 if (sel.ranges.length % lastCopied.length == 0) {
1113 multiPaste = [];
1114 for (var i = 0; i < lastCopied.length; i++)
1115 multiPaste.push(doc.splitLines(lastCopied[i]));
1116 }
1117 } else if (textLines.length == sel.ranges.length) {
1091 1118 multiPaste = map(textLines, function(l) { return [l]; });
1119 }
1092 1120 }
1093 1121
1094 1122 // Normal behavior is to insert the new text into every selection
@@ -1120,7 +1148,8 b''
1120 1148 var pasted = e.clipboardData && e.clipboardData.getData("text/plain");
1121 1149 if (pasted) {
1122 1150 e.preventDefault();
1123 runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); });
1151 if (!cm.isReadOnly() && !cm.options.disableInput)
1152 runInOp(cm, function() { applyTextInput(cm, pasted, 0, null, "paste"); });
1124 1153 return true;
1125 1154 }
1126 1155 }
@@ -1222,13 +1251,14 b''
1222 1251 });
1223 1252
1224 1253 on(te, "paste", function(e) {
1225 if (handlePaste(e, cm)) return true;
1254 if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return
1226 1255
1227 1256 cm.state.pasteIncoming = true;
1228 1257 input.fastPoll();
1229 1258 });
1230 1259
1231 1260 function prepareCopyCut(e) {
1261 if (signalDOMEvent(cm, e)) return
1232 1262 if (cm.somethingSelected()) {
1233 1263 lastCopied = cm.getSelections();
1234 1264 if (input.inaccurateSelection) {
@@ -1256,7 +1286,7 b''
1256 1286 on(te, "copy", prepareCopyCut);
1257 1287
1258 1288 on(display.scroller, "paste", function(e) {
1259 if (eventInWidget(display, e)) return;
1289 if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return;
1260 1290 cm.state.pasteIncoming = true;
1261 1291 input.focus();
1262 1292 });
@@ -1268,6 +1298,7 b''
1268 1298
1269 1299 on(te, "compositionstart", function() {
1270 1300 var start = cm.getCursor("from");
1301 if (input.composing) input.composing.range.clear()
1271 1302 input.composing = {
1272 1303 start: start,
1273 1304 range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"})
@@ -1388,8 +1419,8 b''
1388 1419 // will be the case when there is a lot of text in the textarea,
1389 1420 // in which case reading its value would be expensive.
1390 1421 if (this.contextMenuPending || !cm.state.focused ||
1391 (hasSelection(input) && !prevInput) ||
1392 isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq)
1422 (hasSelection(input) && !prevInput && !this.composing) ||
1423 cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
1393 1424 return false;
1394 1425
1395 1426 var text = input.value;
@@ -1516,6 +1547,10 b''
1516 1547 }
1517 1548 },
1518 1549
1550 readOnlyChanged: function(val) {
1551 if (!val) this.reset();
1552 },
1553
1519 1554 setUneditable: nothing,
1520 1555
1521 1556 needsContentAttribute: false
@@ -1534,10 +1569,11 b''
1534 1569 init: function(display) {
1535 1570 var input = this, cm = input.cm;
1536 1571 var div = input.div = display.lineDiv;
1537 div.contentEditable = "true";
1538 1572 disableBrowserMagic(div);
1539 1573
1540 on(div, "paste", function(e) { handlePaste(e, cm); })
1574 on(div, "paste", function(e) {
1575 if (!signalDOMEvent(cm, e)) handlePaste(e, cm);
1576 })
1541 1577
1542 1578 on(div, "compositionstart", function(e) {
1543 1579 var data = e.data;
@@ -1575,11 +1611,12 b''
1575 1611
1576 1612 on(div, "input", function() {
1577 1613 if (input.composing) return;
1578 if (!input.pollContent())
1614 if (cm.isReadOnly() || !input.pollContent())
1579 1615 runInOp(input.cm, function() {regChange(cm);});
1580 1616 });
1581 1617
1582 1618 function onCopyCut(e) {
1619 if (signalDOMEvent(cm, e)) return
1583 1620 if (cm.somethingSelected()) {
1584 1621 lastCopied = cm.getSelections();
1585 1622 if (e.type == "cut") cm.replaceSelection("", null, "cut");
@@ -1655,8 +1692,13 b''
1655 1692 try { var rng = range(start.node, start.offset, end.offset, end.node); }
1656 1693 catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible
1657 1694 if (rng) {
1658 sel.removeAllRanges();
1659 sel.addRange(rng);
1695 if (!gecko && this.cm.state.focused) {
1696 sel.collapse(start.node, start.offset);
1697 if (!rng.collapsed) sel.addRange(rng);
1698 } else {
1699 sel.removeAllRanges();
1700 sel.addRange(rng);
1701 }
1660 1702 if (old && sel.anchorNode == null) sel.addRange(old);
1661 1703 else if (gecko) this.startGracePeriod();
1662 1704 }
@@ -1756,7 +1798,7 b''
1756 1798 var toNode = display.view[toIndex + 1].node.previousSibling;
1757 1799 }
1758 1800
1759 var newText = splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));
1801 var newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine));
1760 1802 var oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length));
1761 1803 while (newText.length > 1 && oldText.length > 1) {
1762 1804 if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine--; }
@@ -1800,17 +1842,24 b''
1800 1842 this.div.focus();
1801 1843 },
1802 1844 applyComposition: function(composing) {
1803 if (composing.data && composing.data != composing.startData)
1845 if (this.cm.isReadOnly())
1846 operation(this.cm, regChange)(this.cm)
1847 else if (composing.data && composing.data != composing.startData)
1804 1848 operation(this.cm, applyTextInput)(this.cm, composing.data, 0, composing.sel);
1805 1849 },
1806 1850
1807 1851 setUneditable: function(node) {
1808 node.setAttribute("contenteditable", "false");
1852 node.contentEditable = "false"
1809 1853 },
1810 1854
1811 1855 onKeyPress: function(e) {
1812 1856 e.preventDefault();
1813 operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0);
1857 if (!this.cm.isReadOnly())
1858 operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0);
1859 },
1860
1861 readOnlyChanged: function(val) {
1862 this.div.contentEditable = String(val != "nocursor")
1814 1863 },
1815 1864
1816 1865 onContextMenu: nothing,
@@ -1912,7 +1961,7 b''
1912 1961 }
1913 1962
1914 1963 function domTextBetween(cm, from, to, fromLine, toLine) {
1915 var text = "", closing = false;
1964 var text = "", closing = false, lineSep = cm.doc.lineSeparator();
1916 1965 function recognizeMarker(id) { return function(marker) { return marker.id == id; }; }
1917 1966 function walk(node) {
1918 1967 if (node.nodeType == 1) {
@@ -1926,7 +1975,7 b''
1926 1975 if (markerID) {
1927 1976 var found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID));
1928 1977 if (found.length && (range = found[0].find()))
1929 text += getBetween(cm.doc, range.from, range.to).join("\n");
1978 text += getBetween(cm.doc, range.from, range.to).join(lineSep);
1930 1979 return;
1931 1980 }
1932 1981 if (node.getAttribute("contenteditable") == "false") return;
@@ -1938,7 +1987,7 b''
1938 1987 var val = node.nodeValue;
1939 1988 if (!val) return;
1940 1989 if (closing) {
1941 text += "\n";
1990 text += lineSep;
1942 1991 closing = false;
1943 1992 }
1944 1993 text += val;
@@ -2110,7 +2159,7 b''
2110 2159
2111 2160 // Give beforeSelectionChange handlers a change to influence a
2112 2161 // selection update.
2113 function filterSelectionChange(doc, sel) {
2162 function filterSelectionChange(doc, sel, options) {
2114 2163 var obj = {
2115 2164 ranges: sel.ranges,
2116 2165 update: function(ranges) {
@@ -2118,7 +2167,8 b''
2118 2167 for (var i = 0; i < ranges.length; i++)
2119 2168 this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor),
2120 2169 clipPos(doc, ranges[i].head));
2121 }
2170 },
2171 origin: options && options.origin
2122 2172 };
2123 2173 signal(doc, "beforeSelectionChange", doc, obj);
2124 2174 if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj);
@@ -2144,7 +2194,7 b''
2144 2194
2145 2195 function setSelectionNoUndo(doc, sel, options) {
2146 2196 if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange"))
2147 sel = filterSelectionChange(doc, sel);
2197 sel = filterSelectionChange(doc, sel, options);
2148 2198
2149 2199 var bias = options && options.bias ||
2150 2200 (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
@@ -2178,8 +2228,9 b''
2178 2228 var out;
2179 2229 for (var i = 0; i < sel.ranges.length; i++) {
2180 2230 var range = sel.ranges[i];
2181 var newAnchor = skipAtomic(doc, range.anchor, bias, mayClear);
2182 var newHead = skipAtomic(doc, range.head, bias, mayClear);
2231 var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];
2232 var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);
2233 var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);
2183 2234 if (out || newAnchor != range.anchor || newHead != range.head) {
2184 2235 if (!out) out = sel.ranges.slice(0, i);
2185 2236 out[i] = new Range(newAnchor, newHead);
@@ -2188,54 +2239,59 b''
2188 2239 return out ? normalizeSelection(out, sel.primIndex) : sel;
2189 2240 }
2190 2241
2191 // Ensure a given position is not inside an atomic range.
2192 function skipAtomic(doc, pos, bias, mayClear) {
2193 var flipped = false, curPos = pos;
2194 var dir = bias || 1;
2195 doc.cantEdit = false;
2196 search: for (;;) {
2197 var line = getLine(doc, curPos.line);
2198 if (line.markedSpans) {
2199 for (var i = 0; i < line.markedSpans.length; ++i) {
2200 var sp = line.markedSpans[i], m = sp.marker;
2201 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
2202 (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
2203 if (mayClear) {
2204 signal(m, "beforeCursorEnter");
2205 if (m.explicitlyCleared) {
2206 if (!line.markedSpans) break;
2207 else {--i; continue;}
2208 }
2209 }
2210 if (!m.atomic) continue;
2211 var newPos = m.find(dir < 0 ? -1 : 1);
2212 if (cmp(newPos, curPos) == 0) {
2213 newPos.ch += dir;
2214 if (newPos.ch < 0) {
2215 if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
2216 else newPos = null;
2217 } else if (newPos.ch > line.text.length) {
2218 if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
2219 else newPos = null;
2220 }
2221 if (!newPos) {
2222 if (flipped) {
2223 // Driven in a corner -- no valid cursor position found at all
2224 // -- try again *with* clearing, if we didn't already
2225 if (!mayClear) return skipAtomic(doc, pos, bias, true);
2226 // Otherwise, turn off editing until further notice, and return the start of the doc
2227 doc.cantEdit = true;
2228 return Pos(doc.first, 0);
2229 }
2230 flipped = true; newPos = pos; dir = -dir;
2231 }
2232 }
2233 curPos = newPos;
2234 continue search;
2242 function skipAtomicInner(doc, pos, oldPos, dir, mayClear) {
2243 var line = getLine(doc, pos.line);
2244 if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
2245 var sp = line.markedSpans[i], m = sp.marker;
2246 if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) &&
2247 (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) {
2248 if (mayClear) {
2249 signal(m, "beforeCursorEnter");
2250 if (m.explicitlyCleared) {
2251 if (!line.markedSpans) break;
2252 else {--i; continue;}
2235 2253 }
2236 2254 }
2237 }
2238 return curPos;
2255 if (!m.atomic) continue;
2256
2257 if (oldPos) {
2258 var near = m.find(dir < 0 ? 1 : -1), diff;
2259 if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) near = movePos(doc, near, -dir, line);
2260 if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0))
2261 return skipAtomicInner(doc, near, pos, dir, mayClear);
2262 }
2263
2264 var far = m.find(dir < 0 ? -1 : 1);
2265 if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) far = movePos(doc, far, dir, line);
2266 return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null;
2267 }
2268 }
2269 return pos;
2270 }
2271
2272 // Ensure a given position is not inside an atomic range.
2273 function skipAtomic(doc, pos, oldPos, bias, mayClear) {
2274 var dir = bias || 1;
2275 var found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) ||
2276 (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) ||
2277 skipAtomicInner(doc, pos, oldPos, -dir, mayClear) ||
2278 (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true));
2279 if (!found) {
2280 doc.cantEdit = true;
2281 return Pos(doc.first, 0);
2282 }
2283 return found;
2284 }
2285
2286 function movePos(doc, pos, dir, line) {
2287 if (dir < 0 && pos.ch == 0) {
2288 if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1));
2289 else return null;
2290 } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) {
2291 if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0);
2292 else return null;
2293 } else {
2294 return new Pos(pos.line, pos.ch + dir);
2239 2295 }
2240 2296 }
2241 2297
@@ -2255,7 +2311,7 b''
2255 2311 var range = doc.sel.ranges[i];
2256 2312 var collapsed = range.empty();
2257 2313 if (collapsed || cm.options.showCursorWhenSelecting)
2258 drawSelectionCursor(cm, range, curFragment);
2314 drawSelectionCursor(cm, range.head, curFragment);
2259 2315 if (!collapsed)
2260 2316 drawSelectionRange(cm, range, selFragment);
2261 2317 }
@@ -2263,8 +2319,8 b''
2263 2319 }
2264 2320
2265 2321 // Draws a cursor for the given range
2266 function drawSelectionCursor(cm, range, output) {
2267 var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine);
2322 function drawSelectionCursor(cm, head, output) {
2323 var pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine);
2268 2324
2269 2325 var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor"));
2270 2326 cursor.style.left = pos.left + "px";
@@ -2388,8 +2444,8 b''
2388 2444
2389 2445 doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) {
2390 2446 if (doc.frontier >= cm.display.viewFrom) { // Visible
2391 var oldStyles = line.styles;
2392 var highlighted = highlightLine(cm, line, state, true);
2447 var oldStyles = line.styles, tooLong = line.text.length > cm.options.maxHighlightLength;
2448 var highlighted = highlightLine(cm, line, tooLong ? copyState(doc.mode, state) : state, true);
2393 2449 line.styles = highlighted.styles;
2394 2450 var oldCls = line.styleClasses, newCls = highlighted.classes;
2395 2451 if (newCls) line.styleClasses = newCls;
@@ -2398,9 +2454,10 b''
2398 2454 oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass);
2399 2455 for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
2400 2456 if (ischange) changedLines.push(doc.frontier);
2401 line.stateAfter = copyState(doc.mode, state);
2457 line.stateAfter = tooLong ? state : copyState(doc.mode, state);
2402 2458 } else {
2403 processLine(cm, line.text, state);
2459 if (line.text.length <= cm.options.maxHighlightLength)
2460 processLine(cm, line.text, state);
2404 2461 line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
2405 2462 }
2406 2463 ++doc.frontier;
@@ -2545,10 +2602,12 b''
2545 2602 function prepareMeasureForLine(cm, line) {
2546 2603 var lineN = lineNo(line);
2547 2604 var view = findViewForLine(cm, lineN);
2548 if (view && !view.text)
2605 if (view && !view.text) {
2549 2606 view = null;
2550 else if (view && view.changes)
2607 } else if (view && view.changes) {
2551 2608 updateLineForChanges(cm, view, lineN, getDimensions(cm));
2609 cm.curOp.forceUpdate = true;
2610 }
2552 2611 if (!view)
2553 2612 view = updateExternalMeasurement(cm, line);
2554 2613
@@ -2961,12 +3020,12 b''
2961 3020 var callbacks = group.delayedCallbacks, i = 0;
2962 3021 do {
2963 3022 for (; i < callbacks.length; i++)
2964 callbacks[i]();
3023 callbacks[i].call(null);
2965 3024 for (var j = 0; j < group.ops.length; j++) {
2966 3025 var op = group.ops[j];
2967 3026 if (op.cursorActivityHandlers)
2968 3027 while (op.cursorActivityCalled < op.cursorActivityHandlers.length)
2969 op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm);
3028 op.cursorActivityHandlers[op.cursorActivityCalled++].call(null, op.cm);
2970 3029 }
2971 3030 } while (i < callbacks.length);
2972 3031 }
@@ -3060,7 +3119,8 b''
3060 3119
3061 3120 if (cm.state.focused && op.updateInput)
3062 3121 cm.display.input.reset(op.typing);
3063 if (op.focus && op.focus == activeElt()) ensureFocus(op.cm);
3122 if (op.focus && op.focus == activeElt() && (!document.hasFocus || document.hasFocus()))
3123 ensureFocus(op.cm);
3064 3124 }
3065 3125
3066 3126 function endOperation_finish(op) {
@@ -3375,7 +3435,7 b''
3375 3435 return dx * dx + dy * dy > 20 * 20;
3376 3436 }
3377 3437 on(d.scroller, "touchstart", function(e) {
3378 if (!isMouseLikeTouchEvent(e)) {
3438 if (!signalDOMEvent(cm, e) && !isMouseLikeTouchEvent(e)) {
3379 3439 clearTimeout(touchFinished);
3380 3440 var now = +new Date;
3381 3441 d.activeTouch = {start: now, moved: false,
@@ -3426,9 +3486,11 b''
3426 3486 on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
3427 3487
3428 3488 d.dragFunctions = {
3429 simple: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);},
3489 enter: function(e) {if (!signalDOMEvent(cm, e)) e_stop(e);},
3490 over: function(e) {if (!signalDOMEvent(cm, e)) { onDragOver(cm, e); e_stop(e); }},
3430 3491 start: function(e){onDragStart(cm, e);},
3431 drop: operation(cm, onDrop)
3492 drop: operation(cm, onDrop),
3493 leave: function() {clearDragCursor(cm);}
3432 3494 };
3433 3495
3434 3496 var inp = d.input.getField();
@@ -3445,8 +3507,9 b''
3445 3507 var funcs = cm.display.dragFunctions;
3446 3508 var toggle = value ? on : off;
3447 3509 toggle(cm.display.scroller, "dragstart", funcs.start);
3448 toggle(cm.display.scroller, "dragenter", funcs.simple);
3449 toggle(cm.display.scroller, "dragover", funcs.simple);
3510 toggle(cm.display.scroller, "dragenter", funcs.enter);
3511 toggle(cm.display.scroller, "dragover", funcs.over);
3512 toggle(cm.display.scroller, "dragleave", funcs.leave);
3450 3513 toggle(cm.display.scroller, "drop", funcs.drop);
3451 3514 }
3452 3515 }
@@ -3501,7 +3564,7 b''
3501 3564 // not interfere with, such as a scrollbar or widget.
3502 3565 function onMouseDown(e) {
3503 3566 var cm = this, display = cm.display;
3504 if (display.activeTouch && display.input.supportsTouch() || signalDOMEvent(cm, e)) return;
3567 if (signalDOMEvent(cm, e) || display.activeTouch && display.input.supportsTouch()) return;
3505 3568 display.shift = e.shiftKey;
3506 3569
3507 3570 if (eventInWidget(display, e)) {
@@ -3519,7 +3582,10 b''
3519 3582
3520 3583 switch (e_button(e)) {
3521 3584 case 1:
3522 if (start)
3585 // #3261: make sure, that we're not starting a second selection
3586 if (cm.state.selectingText)
3587 cm.state.selectingText(e);
3588 else if (start)
3523 3589 leftButtonDown(cm, e, start);
3524 3590 else if (e_target(e) == display.scroller)
3525 3591 e_preventDefault(e);
@@ -3554,7 +3620,7 b''
3554 3620 }
3555 3621
3556 3622 var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained;
3557 if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) &&
3623 if (cm.options.dragDrop && dragAndDrop && !cm.isReadOnly() &&
3558 3624 type == "single" && (contained = sel.contains(start)) > -1 &&
3559 3625 (cmp((contained = sel.ranges[contained]).from(), start) < 0 || start.xRel > 0) &&
3560 3626 (cmp(contained.to(), start) > 0 || start.xRel < 0))
@@ -3639,7 +3705,8 b''
3639 3705 setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex),
3640 3706 {scroll: false, origin: "*mouse"});
3641 3707 } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single" && !e.shiftKey) {
3642 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0));
3708 setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0),
3709 {scroll: false, origin: "*mouse"});
3643 3710 startSel = doc.sel;
3644 3711 } else {
3645 3712 replaceOneSelection(doc, ourIndex, ourRange, sel_mouse);
@@ -3717,6 +3784,7 b''
3717 3784 }
3718 3785
3719 3786 function done(e) {
3787 cm.state.selectingText = false;
3720 3788 counter = Infinity;
3721 3789 e_preventDefault(e);
3722 3790 display.input.focus();
@@ -3730,13 +3798,14 b''
3730 3798 else extend(e);
3731 3799 });
3732 3800 var up = operation(cm, done);
3801 cm.state.selectingText = up;
3733 3802 on(document, "mousemove", move);
3734 3803 on(document, "mouseup", up);
3735 3804 }
3736 3805
3737 3806 // Determines whether an event happened in the gutter, and fires the
3738 3807 // handlers for the corresponding event.
3739 function gutterEvent(cm, e, type, prevent, signalfn) {
3808 function gutterEvent(cm, e, type, prevent) {
3740 3809 try { var mX = e.clientX, mY = e.clientY; }
3741 3810 catch(e) { return false; }
3742 3811 if (mX >= Math.floor(cm.display.gutters.getBoundingClientRect().right)) return false;
@@ -3753,14 +3822,14 b''
3753 3822 if (g && g.getBoundingClientRect().right >= mX) {
3754 3823 var line = lineAtHeight(cm.doc, mY);
3755 3824 var gutter = cm.options.gutters[i];
3756 signalfn(cm, type, cm, line, gutter, e);
3825 signal(cm, type, cm, line, gutter, e);
3757 3826 return e_defaultPrevented(e);
3758 3827 }
3759 3828 }
3760 3829 }
3761 3830
3762 3831 function clickInGutter(cm, e) {
3763 return gutterEvent(cm, e, "gutterClick", true, signalLater);
3832 return gutterEvent(cm, e, "gutterClick", true);
3764 3833 }
3765 3834
3766 3835 // Kludge to work around strange IE behavior where it'll sometimes
@@ -3769,23 +3838,32 b''
3769 3838
3770 3839 function onDrop(e) {
3771 3840 var cm = this;
3841 clearDragCursor(cm);
3772 3842 if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e))
3773 3843 return;
3774 3844 e_preventDefault(e);
3775 3845 if (ie) lastDrop = +new Date;
3776 3846 var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
3777 if (!pos || isReadOnly(cm)) return;
3847 if (!pos || cm.isReadOnly()) return;
3778 3848 // Might be a file drop, in which case we simply extract the text
3779 3849 // and insert it.
3780 3850 if (files && files.length && window.FileReader && window.File) {
3781 3851 var n = files.length, text = Array(n), read = 0;
3782 3852 var loadFile = function(file, i) {
3853 if (cm.options.allowDropFileTypes &&
3854 indexOf(cm.options.allowDropFileTypes, file.type) == -1)
3855 return;
3856
3783 3857 var reader = new FileReader;
3784 3858 reader.onload = operation(cm, function() {
3785 text[i] = reader.result;
3859 var content = reader.result;
3860 if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = "";
3861 text[i] = content;
3786 3862 if (++read == n) {
3787 3863 pos = clipPos(cm.doc, pos);
3788 var change = {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"};
3864 var change = {from: pos, to: pos,
3865 text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())),
3866 origin: "paste"};
3789 3867 makeChange(cm.doc, change);
3790 3868 setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change)));
3791 3869 }
@@ -3839,6 +3917,25 b''
3839 3917 }
3840 3918 }
3841 3919
3920 function onDragOver(cm, e) {
3921 var pos = posFromMouse(cm, e);
3922 if (!pos) return;
3923 var frag = document.createDocumentFragment();
3924 drawSelectionCursor(cm, pos, frag);
3925 if (!cm.display.dragCursor) {
3926 cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors");
3927 cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv);
3928 }
3929 removeChildrenAndAdd(cm.display.dragCursor, frag);
3930 }
3931
3932 function clearDragCursor(cm) {
3933 if (cm.display.dragCursor) {
3934 cm.display.lineSpace.removeChild(cm.display.dragCursor);
3935 cm.display.dragCursor = null;
3936 }
3937 }
3938
3842 3939 // SCROLL EVENTS
3843 3940
3844 3941 // Sync the scrollable area and scrollbars, ensure the viewport
@@ -3903,8 +4000,9 b''
3903 4000
3904 4001 var display = cm.display, scroll = display.scroller;
3905 4002 // Quit if there's nothing to scroll here
3906 if (!(dx && scroll.scrollWidth > scroll.clientWidth ||
3907 dy && scroll.scrollHeight > scroll.clientHeight)) return;
4003 var canScrollX = scroll.scrollWidth > scroll.clientWidth;
4004 var canScrollY = scroll.scrollHeight > scroll.clientHeight;
4005 if (!(dx && canScrollX || dy && canScrollY)) return;
3908 4006
3909 4007 // Webkit browsers on OS X abort momentum scrolls when the target
3910 4008 // of the scroll event is removed from the scrollable element.
@@ -3928,10 +4026,15 b''
3928 4026 // scrolling entirely here. It'll be slightly off from native, but
3929 4027 // better than glitching out.
3930 4028 if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
3931 if (dy)
4029 if (dy && canScrollY)
3932 4030 setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
3933 4031 setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
3934 e_preventDefault(e);
4032 // Only prevent default scrolling if vertical scrolling is
4033 // actually possible. Otherwise, it causes vertical scroll
4034 // jitter on OSX trackpads when deltaX is small and deltaY
4035 // is large (issue #3579)
4036 if (!dy || (dy && canScrollY))
4037 e_preventDefault(e);
3935 4038 display.wheelStartX = null; // Abort measurement, if in progress
3936 4039 return;
3937 4040 }
@@ -3980,7 +4083,7 b''
3980 4083 cm.display.input.ensurePolled();
3981 4084 var prevShift = cm.display.shift, done = false;
3982 4085 try {
3983 if (isReadOnly(cm)) cm.state.suppressEdits = true;
4086 if (cm.isReadOnly()) cm.state.suppressEdits = true;
3984 4087 if (dropShift) cm.display.shift = false;
3985 4088 done = bound(cm) != Pass;
3986 4089 } finally {
@@ -4159,12 +4262,13 b''
4159 4262 // right-click take effect on it.
4160 4263 function onContextMenu(cm, e) {
4161 4264 if (eventInWidget(cm.display, e) || contextMenuInGutter(cm, e)) return;
4265 if (signalDOMEvent(cm, e, "contextmenu")) return;
4162 4266 cm.display.input.onContextMenu(e);
4163 4267 }
4164 4268
4165 4269 function contextMenuInGutter(cm, e) {
4166 4270 if (!hasHandler(cm, "gutterContextMenu")) return false;
4167 return gutterEvent(cm, e, "gutterContextMenu", false, signal);
4271 return gutterEvent(cm, e, "gutterContextMenu", false);
4168 4272 }
4169 4273
4170 4274 // UPDATING
@@ -4468,7 +4572,7 b''
4468 4572 function replaceRange(doc, code, from, to, origin) {
4469 4573 if (!to) to = from;
4470 4574 if (cmp(to, from) < 0) { var tmp = to; to = from; from = tmp; }
4471 if (typeof code == "string") code = splitLines(code);
4575 if (typeof code == "string") code = doc.splitLines(code);
4472 4576 makeChange(doc, {from: from, to: to, text: code, origin: origin});
4473 4577 }
4474 4578
@@ -4712,10 +4816,9 b''
4712 4816 function findPosH(doc, pos, dir, unit, visually) {
4713 4817 var line = pos.line, ch = pos.ch, origDir = dir;
4714 4818 var lineObj = getLine(doc, line);
4715 var possible = true;
4716 4819 function findNextLine() {
4717 4820 var l = line + dir;
4718 if (l < doc.first || l >= doc.first + doc.size) return (possible = false);
4821 if (l < doc.first || l >= doc.first + doc.size) return false
4719 4822 line = l;
4720 4823 return lineObj = getLine(doc, l);
4721 4824 }
@@ -4725,14 +4828,16 b''
4725 4828 if (!boundToLine && findNextLine()) {
4726 4829 if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
4727 4830 else ch = dir < 0 ? lineObj.text.length : 0;
4728 } else return (possible = false);
4831 } else return false
4729 4832 } else ch = next;
4730 4833 return true;
4731 4834 }
4732 4835
4733 if (unit == "char") moveOnce();
4734 else if (unit == "column") moveOnce(true);
4735 else if (unit == "word" || unit == "group") {
4836 if (unit == "char") {
4837 moveOnce()
4838 } else if (unit == "column") {
4839 moveOnce(true)
4840 } else if (unit == "word" || unit == "group") {
4736 4841 var sawType = null, group = unit == "group";
4737 4842 var helper = doc.cm && doc.cm.getHelper(pos, "wordChars");
4738 4843 for (var first = true;; first = false) {
@@ -4752,8 +4857,8 b''
4752 4857 if (dir > 0 && !moveOnce(!first)) break;
4753 4858 }
4754 4859 }
4755 var result = skipAtomic(doc, Pos(line, ch), origDir, true);
4756 if (!possible) result.hitSide = true;
4860 var result = skipAtomic(doc, Pos(line, ch), pos, origDir, true);
4861 if (!cmp(pos, result)) result.hitSide = true;
4757 4862 return result;
4758 4863 }
4759 4864
@@ -5045,7 +5150,7 b''
5045 5150
5046 5151 execCommand: function(cmd) {
5047 5152 if (commands.hasOwnProperty(cmd))
5048 return commands[cmd](this);
5153 return commands[cmd].call(null, this);
5049 5154 },
5050 5155
5051 5156 triggerElectric: methodOp(function(text) { triggerElectric(this, text); }),
@@ -5140,6 +5245,7 b''
5140 5245 signal(this, "overwriteToggle", this, this.state.overwrite);
5141 5246 },
5142 5247 hasFocus: function() { return this.display.input.getField() == activeElt(); },
5248 isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit); },
5143 5249
5144 5250 scrollTo: methodOp(function(x, y) {
5145 5251 if (x != null || y != null) resolveScrollToPos(this);
@@ -5263,6 +5369,22 b''
5263 5369 clearCaches(cm);
5264 5370 regChange(cm);
5265 5371 }, true);
5372 option("lineSeparator", null, function(cm, val) {
5373 cm.doc.lineSep = val;
5374 if (!val) return;
5375 var newBreaks = [], lineNo = cm.doc.first;
5376 cm.doc.iter(function(line) {
5377 for (var pos = 0;;) {
5378 var found = line.text.indexOf(val, pos);
5379 if (found == -1) break;
5380 pos = found + val.length;
5381 newBreaks.push(Pos(lineNo, found));
5382 }
5383 lineNo++;
5384 });
5385 for (var i = newBreaks.length - 1; i >= 0; i--)
5386 replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length))
5387 });
5266 5388 option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val, old) {
5267 5389 cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
5268 5390 if (old != CodeMirror.Init) cm.refresh();
@@ -5321,11 +5443,12 b''
5321 5443 cm.display.disabled = true;
5322 5444 } else {
5323 5445 cm.display.disabled = false;
5324 if (!val) cm.display.input.reset();
5325 }
5446 }
5447 cm.display.input.readOnlyChanged(val)
5326 5448 });
5327 5449 option("disableInput", false, function(cm, val) {if (!val) cm.display.input.reset();}, true);
5328 5450 option("dragDrop", true, dragDropChanged);
5451 option("allowDropFileTypes", null);
5329 5452
5330 5453 option("cursorBlinkRate", 530);
5331 5454 option("cursorScrollMargin", 0);
@@ -5613,7 +5736,8 b''
5613 5736 } else if (cur.line > cm.doc.first) {
5614 5737 var prev = getLine(cm.doc, cur.line - 1).text;
5615 5738 if (prev)
5616 cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1),
5739 cm.replaceRange(line.charAt(0) + cm.doc.lineSeparator() +
5740 prev.charAt(prev.length - 1),
5617 5741 Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose");
5618 5742 }
5619 5743 }
@@ -5627,10 +5751,10 b''
5627 5751 var len = cm.listSelections().length;
5628 5752 for (var i = 0; i < len; i++) {
5629 5753 var range = cm.listSelections()[i];
5630 cm.replaceRange("\n", range.anchor, range.head, "+input");
5754 cm.replaceRange(cm.doc.lineSeparator(), range.anchor, range.head, "+input");
5631 5755 cm.indentLine(range.from().line + 1, null, true);
5632 ensureCursorVisible(cm);
5633 5756 }
5757 ensureCursorVisible(cm);
5634 5758 });
5635 5759 },
5636 5760 toggleOverwrite: function(cm) {cm.toggleOverwrite();}
@@ -6558,7 +6682,7 b''
6558 6682 parentStyle += "width: " + cm.display.wrapper.clientWidth + "px;";
6559 6683 removeChildrenAndAdd(cm.display.measure, elt("div", [widget.node], null, parentStyle));
6560 6684 }
6561 return widget.height = widget.node.offsetHeight;
6685 return widget.height = widget.node.parentNode.offsetHeight;
6562 6686 }
6563 6687
6564 6688 function addLineWidget(doc, handle, node, options) {
@@ -6747,7 +6871,9 b''
6747 6871
6748 6872 function getLineStyles(cm, line, updateFrontier) {
6749 6873 if (!line.styles || line.styles[0] != cm.state.modeGen) {
6750 var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
6874 var state = getStateBefore(cm, lineNo(line));
6875 var result = highlightLine(cm, line, line.text.length > cm.options.maxHighlightLength ? copyState(cm.doc.mode, state) : state);
6876 line.stateAfter = state;
6751 6877 line.styles = result.styles;
6752 6878 if (result.classes) line.styleClasses = result.classes;
6753 6879 else if (line.styleClasses) line.styleClasses = null;
@@ -6764,7 +6890,7 b''
6764 6890 var stream = new StringStream(text, cm.options.tabSize);
6765 6891 stream.start = stream.pos = startAt || 0;
6766 6892 if (text == "") callBlankLine(mode, state);
6767 while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) {
6893 while (!stream.eol()) {
6768 6894 readToken(mode, stream, state);
6769 6895 stream.start = stream.pos;
6770 6896 }
@@ -6791,7 +6917,7 b''
6791 6917 // is needed on Webkit to be able to get line-level bounding
6792 6918 // rectangles for it (in measureChar).
6793 6919 var content = elt("span", null, null, webkit ? "padding-right: .1px" : null);
6794 var builder = {pre: elt("pre", [content]), content: content,
6920 var builder = {pre: elt("pre", [content], "CodeMirror-line"), content: content,
6795 6921 col: 0, pos: 0, cm: cm,
6796 6922 splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")};
6797 6923 lineView.measure = {};
@@ -6881,6 +7007,10 b''
6881 7007 txt.setAttribute("role", "presentation");
6882 7008 txt.setAttribute("cm-text", "\t");
6883 7009 builder.col += tabWidth;
7010 } else if (m[0] == "\r" || m[0] == "\n") {
7011 var txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar"));
7012 txt.setAttribute("cm-text", m[0]);
7013 builder.col += 1;
6884 7014 } else {
6885 7015 var txt = builder.cm.options.specialCharPlaceholder(m[0]);
6886 7016 txt.setAttribute("cm-text", m[0]);
@@ -6962,7 +7092,7 b''
6962 7092 if (nextChange == pos) { // Update current marker set
6963 7093 spanStyle = spanEndStyle = spanStartStyle = title = css = "";
6964 7094 collapsed = null; nextChange = Infinity;
6965 var foundBookmarks = [];
7095 var foundBookmarks = [], endStyles
6966 7096 for (var j = 0; j < spans.length; ++j) {
6967 7097 var sp = spans[j], m = sp.marker;
6968 7098 if (m.type == "bookmark" && sp.from == pos && m.widgetNode) {
@@ -6973,9 +7103,9 b''
6973 7103 spanEndStyle = "";
6974 7104 }
6975 7105 if (m.className) spanStyle += " " + m.className;
6976 if (m.css) css = m.css;
7106 if (m.css) css = (css ? css + ";" : "") + m.css;
6977 7107 if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
6978 if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
7108 if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to)
6979 7109 if (m.title && !title) title = m.title;
6980 7110 if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0))
6981 7111 collapsed = sp;
@@ -6983,14 +7113,17 b''
6983 7113 nextChange = sp.from;
6984 7114 }
6985 7115 }
7116 if (endStyles) for (var j = 0; j < endStyles.length; j += 2)
7117 if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j]
7118
7119 if (!collapsed || collapsed.from == pos) for (var j = 0; j < foundBookmarks.length; ++j)
7120 buildCollapsedSpan(builder, 0, foundBookmarks[j]);
6986 7121 if (collapsed && (collapsed.from || 0) == pos) {
6987 7122 buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos,
6988 7123 collapsed.marker, collapsed.from == null);
6989 7124 if (collapsed.to == null) return;
6990 7125 if (collapsed.to == pos) collapsed = false;
6991 7126 }
6992 if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j)
6993 buildCollapsedSpan(builder, 0, foundBookmarks[j]);
6994 7127 }
6995 7128 if (pos >= len) break;
6996 7129
@@ -7226,8 +7359,8 b''
7226 7359 };
7227 7360
7228 7361 var nextDocId = 0;
7229 var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
7230 if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
7362 var Doc = CodeMirror.Doc = function(text, mode, firstLine, lineSep) {
7363 if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep);
7231 7364 if (firstLine == null) firstLine = 0;
7232 7365
7233 7366 BranchChunk.call(this, [new LeafChunk([new Line("", null)])]);
@@ -7241,8 +7374,10 b''
7241 7374 this.history = new History(null);
7242 7375 this.id = ++nextDocId;
7243 7376 this.modeOption = mode;
7244
7245 if (typeof text == "string") text = splitLines(text);
7377 this.lineSep = lineSep;
7378 this.extend = false;
7379
7380 if (typeof text == "string") text = this.splitLines(text);
7246 7381 updateDoc(this, {from: start, to: start, text: text});
7247 7382 setSelection(this, simpleSelection(start), sel_dontScroll);
7248 7383 };
@@ -7272,12 +7407,12 b''
7272 7407 getValue: function(lineSep) {
7273 7408 var lines = getLines(this, this.first, this.first + this.size);
7274 7409 if (lineSep === false) return lines;
7275 return lines.join(lineSep || "\n");
7410 return lines.join(lineSep || this.lineSeparator());
7276 7411 },
7277 7412 setValue: docMethodOp(function(code) {
7278 7413 var top = Pos(this.first, 0), last = this.first + this.size - 1;
7279 7414 makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
7280 text: splitLines(code), origin: "setValue", full: true}, true);
7415 text: this.splitLines(code), origin: "setValue", full: true}, true);
7281 7416 setSelection(this, simpleSelection(top));
7282 7417 }),
7283 7418 replaceRange: function(code, from, to, origin) {
@@ -7288,7 +7423,7 b''
7288 7423 getRange: function(from, to, lineSep) {
7289 7424 var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
7290 7425 if (lineSep === false) return lines;
7291 return lines.join(lineSep || "\n");
7426 return lines.join(lineSep || this.lineSeparator());
7292 7427 },
7293 7428
7294 7429 getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
@@ -7328,10 +7463,11 b''
7328 7463 extendSelection(this, clipPos(this, head), other && clipPos(this, other), options);
7329 7464 }),
7330 7465 extendSelections: docMethodOp(function(heads, options) {
7331 extendSelections(this, clipPosArray(this, heads, options));
7466 extendSelections(this, clipPosArray(this, heads), options);
7332 7467 }),
7333 7468 extendSelectionsBy: docMethodOp(function(f, options) {
7334 extendSelections(this, map(this.sel.ranges, f), options);
7469 var heads = map(this.sel.ranges, f);
7470 extendSelections(this, clipPosArray(this, heads), options);
7335 7471 }),
7336 7472 setSelections: docMethodOp(function(ranges, primary, options) {
7337 7473 if (!ranges.length) return;
@@ -7354,13 +7490,13 b''
7354 7490 lines = lines ? lines.concat(sel) : sel;
7355 7491 }
7356 7492 if (lineSep === false) return lines;
7357 else return lines.join(lineSep || "\n");
7493 else return lines.join(lineSep || this.lineSeparator());
7358 7494 },
7359 7495 getSelections: function(lineSep) {
7360 7496 var parts = [], ranges = this.sel.ranges;
7361 7497 for (var i = 0; i < ranges.length; i++) {
7362 7498 var sel = getBetween(this, ranges[i].from(), ranges[i].to());
7363 if (lineSep !== false) sel = sel.join(lineSep || "\n");
7499 if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator());
7364 7500 parts[i] = sel;
7365 7501 }
7366 7502 return parts;
@@ -7375,7 +7511,7 b''
7375 7511 var changes = [], sel = this.sel;
7376 7512 for (var i = 0; i < sel.ranges.length; i++) {
7377 7513 var range = sel.ranges[i];
7378 changes[i] = {from: range.from(), to: range.to(), text: splitLines(code[i]), origin: origin};
7514 changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin};
7379 7515 }
7380 7516 var newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse);
7381 7517 for (var i = changes.length - 1; i >= 0; i--)
@@ -7456,7 +7592,7 b''
7456 7592 removeLineWidget: function(widget) { widget.clear(); },
7457 7593
7458 7594 markText: function(from, to, options) {
7459 return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
7595 return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range");
7460 7596 },
7461 7597 setBookmark: function(pos, options) {
7462 7598 var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
@@ -7525,7 +7661,8 b''
7525 7661 },
7526 7662
7527 7663 copy: function(copyHistory) {
7528 var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
7664 var doc = new Doc(getLines(this, this.first, this.first + this.size),
7665 this.modeOption, this.first, this.lineSep);
7529 7666 doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
7530 7667 doc.sel = this.sel;
7531 7668 doc.extend = false;
@@ -7541,7 +7678,7 b''
7541 7678 var from = this.first, to = this.first + this.size;
7542 7679 if (options.from != null && options.from > from) from = options.from;
7543 7680 if (options.to != null && options.to < to) to = options.to;
7544 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
7681 var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep);
7545 7682 if (options.sharedHist) copy.history = this.history;
7546 7683 (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
7547 7684 copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
@@ -7570,7 +7707,13 b''
7570 7707 iterLinkedDocs: function(f) {linkedDocs(this, f);},
7571 7708
7572 7709 getMode: function() {return this.mode;},
7573 getEditor: function() {return this.cm;}
7710 getEditor: function() {return this.cm;},
7711
7712 splitLines: function(str) {
7713 if (this.lineSep) return str.split(this.lineSep);
7714 return splitLinesAuto(str);
7715 },
7716 lineSeparator: function() { return this.lineSep || "\n"; }
7574 7717 });
7575 7718
7576 7719 // Public alias.
@@ -8010,24 +8153,30 b''
8010 8153 }
8011 8154 };
8012 8155
8156 var noHandlers = []
8157 function getHandlers(emitter, type, copy) {
8158 var arr = emitter._handlers && emitter._handlers[type]
8159 if (copy) return arr && arr.length > 0 ? arr.slice() : noHandlers
8160 else return arr || noHandlers
8161 }
8162
8013 8163 var off = CodeMirror.off = function(emitter, type, f) {
8014 8164 if (emitter.removeEventListener)
8015 8165 emitter.removeEventListener(type, f, false);
8016 8166 else if (emitter.detachEvent)
8017 8167 emitter.detachEvent("on" + type, f);
8018 8168 else {
8019 var arr = emitter._handlers && emitter._handlers[type];
8020 if (!arr) return;
8021 for (var i = 0; i < arr.length; ++i)
8022 if (arr[i] == f) { arr.splice(i, 1); break; }
8169 var handlers = getHandlers(emitter, type, false)
8170 for (var i = 0; i < handlers.length; ++i)
8171 if (handlers[i] == f) { handlers.splice(i, 1); break; }
8023 8172 }
8024 8173 };
8025 8174
8026 8175 var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
8027 var arr = emitter._handlers && emitter._handlers[type];
8028 if (!arr) return;
8176 var handlers = getHandlers(emitter, type, true)
8177 if (!handlers.length) return;
8029 8178 var args = Array.prototype.slice.call(arguments, 2);
8030 for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
8179 for (var i = 0; i < handlers.length; ++i) handlers[i].apply(null, args);
8031 8180 };
8032 8181
8033 8182 var orphanDelayedCallbacks = null;
@@ -8040,8 +8189,8 b''
8040 8189 // them to be executed when the last operation ends, or, if no
8041 8190 // operation is active, when a timeout fires.
8042 8191 function signalLater(emitter, type /*, values...*/) {
8043 var arr = emitter._handlers && emitter._handlers[type];
8044 if (!arr) return;
8192 var arr = getHandlers(emitter, type, false)
8193 if (!arr.length) return;
8045 8194 var args = Array.prototype.slice.call(arguments, 2), list;
8046 8195 if (operationGroup) {
8047 8196 list = operationGroup.delayedCallbacks;
@@ -8081,8 +8230,7 b''
8081 8230 }
8082 8231
8083 8232 function hasHandler(emitter, type) {
8084 var arr = emitter._handlers && emitter._handlers[type];
8085 return arr && arr.length > 0;
8233 return getHandlers(emitter, type).length > 0
8086 8234 }
8087 8235
8088 8236 // Add on and off methods to a constructor's prototype, to make
@@ -8129,7 +8277,7 b''
8129 8277
8130 8278 // The inverse of countColumn -- find the offset that corresponds to
8131 8279 // a particular column.
8132 function findColumn(string, goal, tabSize) {
8280 var findColumn = CodeMirror.findColumn = function(string, goal, tabSize) {
8133 8281 for (var pos = 0, col = 0;;) {
8134 8282 var nextTab = string.indexOf("\t", pos);
8135 8283 if (nextTab == -1) nextTab = string.length;
@@ -8269,7 +8417,12 b''
8269 8417 } while (child = child.parentNode);
8270 8418 };
8271 8419
8272 function activeElt() { return document.activeElement; }
8420 function activeElt() {
8421 var activeElement = document.activeElement;
8422 while (activeElement && activeElement.root && activeElement.root.activeElement)
8423 activeElement = activeElement.root.activeElement;
8424 return activeElement;
8425 }
8273 8426 // Older versions of IE throws unspecified error when touching
8274 8427 // document.activeElement in some cases (during loading, in iframe)
8275 8428 if (ie && ie_version < 11) activeElt = function() {
@@ -8371,7 +8524,7 b''
8371 8524
8372 8525 // See if "".split is the broken IE version, if so, provide an
8373 8526 // alternative way to split lines.
8374 var splitLines = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
8527 var splitLinesAuto = CodeMirror.splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
8375 8528 var pos = 0, result = [], l = string.length;
8376 8529 while (pos <= l) {
8377 8530 var nl = string.indexOf("\n", pos);
@@ -8417,14 +8570,16 b''
8417 8570
8418 8571 // KEY NAMES
8419 8572
8420 var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
8421 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
8422 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
8423 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete",
8424 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
8425 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
8426 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"};
8427 CodeMirror.keyNames = keyNames;
8573 var keyNames = CodeMirror.keyNames = {
8574 3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
8575 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
8576 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
8577 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod",
8578 106: "*", 107: "=", 109: "-", 110: ".", 111: "/", 127: "Delete",
8579 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
8580 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete",
8581 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"
8582 };
8428 8583 (function() {
8429 8584 // Number keys
8430 8585 for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i);
@@ -8729,7 +8884,7 b''
8729 8884
8730 8885 // THE END
8731 8886
8732 CodeMirror.version = "5.4.0";
8887 CodeMirror.version = "5.11.0";
8733 8888
8734 8889 return CodeMirror;
8735 8890 });
@@ -25,8 +25,18 b''
25 25 };
26 26
27 27 CodeMirror.defineExtension("showHint", function(options) {
28 // We want a single cursor position.
29 if (this.listSelections().length > 1 || this.somethingSelected()) return;
28 options = parseOptions(this, this.getCursor("start"), options);
29 var selections = this.listSelections()
30 if (selections.length > 1) return;
31 // By default, don't allow completion when something is selected.
32 // A hint function can have a `supportsSelection` property to
33 // indicate that it can handle selections.
34 if (this.somethingSelected()) {
35 if (!options.hint.supportsSelection) return;
36 // Don't try with cross-line selections
37 for (var i = 0; i < selections.length; i++)
38 if (selections[i].head.line != selections[i].anchor.line) return;
39 }
30 40
31 41 if (this.state.completionActive) this.state.completionActive.close();
32 42 var completion = this.state.completionActive = new Completion(this, options);
@@ -38,12 +48,12 b''
38 48
39 49 function Completion(cm, options) {
40 50 this.cm = cm;
41 this.options = this.buildOptions(options);
51 this.options = options;
42 52 this.widget = null;
43 53 this.debounce = 0;
44 54 this.tick = 0;
45 this.startPos = this.cm.getCursor();
46 this.startLen = this.cm.getLine(this.startPos.line).length;
55 this.startPos = this.cm.getCursor("start");
56 this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
47 57
48 58 var self = this;
49 59 cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
@@ -99,7 +109,6 b''
99 109
100 110 update: function(first) {
101 111 if (this.tick == null) return;
102 if (this.data) CodeMirror.signal(this.data, "update");
103 112 if (!this.options.hint.async) {
104 113 this.finishUpdate(this.options.hint(this.cm, this.options), first);
105 114 } else {
@@ -111,6 +120,8 b''
111 120 },
112 121
113 122 finishUpdate: function(data, first) {
123 if (this.data) CodeMirror.signal(this.data, "update");
124 if (data && this.data && CodeMirror.cmpPos(data.from, this.data.from)) data = null;
114 125 this.data = data;
115 126
116 127 var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
@@ -123,20 +134,21 b''
123 134 CodeMirror.signal(data, "shown");
124 135 }
125 136 }
126 },
127
128 buildOptions: function(options) {
129 var editor = this.cm.options.hintOptions;
130 var out = {};
131 for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
132 if (editor) for (var prop in editor)
133 if (editor[prop] !== undefined) out[prop] = editor[prop];
134 if (options) for (var prop in options)
135 if (options[prop] !== undefined) out[prop] = options[prop];
136 return out;
137 137 }
138 138 };
139 139
140 function parseOptions(cm, pos, options) {
141 var editor = cm.options.hintOptions;
142 var out = {};
143 for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
144 if (editor) for (var prop in editor)
145 if (editor[prop] !== undefined) out[prop] = editor[prop];
146 if (options) for (var prop in options)
147 if (options[prop] !== undefined) out[prop] = options[prop];
148 if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
149 return out;
150 }
151
140 152 function getText(completion) {
141 153 if (typeof completion == "string") return completion;
142 154 else return completion.text;
@@ -335,34 +347,79 b''
335 347 }
336 348 };
337 349
338 CodeMirror.registerHelper("hint", "auto", function(cm, options) {
339 var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
350 function applicableHelpers(cm, helpers) {
351 if (!cm.somethingSelected()) return helpers
352 var result = []
353 for (var i = 0; i < helpers.length; i++)
354 if (helpers[i].supportsSelection) result.push(helpers[i])
355 return result
356 }
357
358 function resolveAutoHints(cm, pos) {
359 var helpers = cm.getHelpers(pos, "hint"), words
340 360 if (helpers.length) {
341 for (var i = 0; i < helpers.length; i++) {
342 var cur = helpers[i](cm, options);
343 if (cur && cur.list.length) return cur;
361 var async = false, resolved
362 for (var i = 0; i < helpers.length; i++) if (helpers[i].async) async = true
363 if (async) {
364 resolved = function(cm, callback, options) {
365 var app = applicableHelpers(cm, helpers)
366 function run(i, result) {
367 if (i == app.length) return callback(null)
368 var helper = app[i]
369 if (helper.async) {
370 helper(cm, function(result) {
371 if (result) callback(result)
372 else run(i + 1)
373 }, options)
374 } else {
375 var result = helper(cm, options)
376 if (result) callback(result)
377 else run(i + 1)
378 }
379 }
380 run(0)
381 }
382 resolved.async = true
383 } else {
384 resolved = function(cm, options) {
385 var app = applicableHelpers(cm, helpers)
386 for (var i = 0; i < app.length; i++) {
387 var cur = app[i](cm, options)
388 if (cur && cur.list.length) return cur
389 }
390 }
344 391 }
392 resolved.supportsSelection = true
393 return resolved
345 394 } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
346 if (words) return CodeMirror.hint.fromList(cm, {words: words});
395 return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
347 396 } else if (CodeMirror.hint.anyword) {
348 return CodeMirror.hint.anyword(cm, options);
397 return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
398 } else {
399 return function() {}
349 400 }
401 }
402
403 CodeMirror.registerHelper("hint", "auto", {
404 resolve: resolveAutoHints
350 405 });
351 406
352 407 CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
353 408 var cur = cm.getCursor(), token = cm.getTokenAt(cur);
409 var to = CodeMirror.Pos(cur.line, token.end);
410 if (token.string && /\w/.test(token.string[token.string.length - 1])) {
411 var term = token.string, from = CodeMirror.Pos(cur.line, token.start);
412 } else {
413 var term = "", from = to;
414 }
354 415 var found = [];
355 416 for (var i = 0; i < options.words.length; i++) {
356 417 var word = options.words[i];
357 if (word.slice(0, token.string.length) == token.string)
418 if (word.slice(0, term.length) == term)
358 419 found.push(word);
359 420 }
360 421
361 if (found.length) return {
362 list: found,
363 from: CodeMirror.Pos(cur.line, token.start),
364 to: CodeMirror.Pos(cur.line, token.end)
365 };
422 if (found.length) return {list: found, from: from, to: to};
366 423 });
367 424
368 425 CodeMirror.commands.autocomplete = CodeMirror.showHint;
@@ -373,7 +430,7 b''
373 430 alignWithWord: true,
374 431 closeCharacters: /[\s()\[\]{};:>,]/,
375 432 closeOnUnfocus: true,
376 completeOnSingleClick: false,
433 completeOnSingleClick: true,
377 434 container: null,
378 435 customKeys: null,
379 436 extraKeys: null
@@ -37,7 +37,9 b''
37 37 var elt = cm.state.placeholder = document.createElement("pre");
38 38 elt.style.cssText = "height: 0; overflow: visible";
39 39 elt.className = "CodeMirror-placeholder";
40 elt.appendChild(document.createTextNode(cm.getOption("placeholder")));
40 var placeHolder = cm.getOption("placeholder")
41 if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder)
42 elt.appendChild(placeHolder)
41 43 cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
42 44 }
43 45
@@ -159,7 +159,7 b' var showRepoStats = function(target, dat'
159 159 lnk = document.createElement('a');
160 160
161 161 lnk.href = '#';
162 lnk.innerHTML = _ngettext('Show more');
162 lnk.innerHTML = _gettext('Show more');
163 163 lnk.id = 'code_stats_show_more';
164 164 td.appendChild(lnk);
165 165
@@ -223,7 +223,6 b' var formatSelect2SelectionRefs = functio'
223 223 // takes a given html element and scrolls it down offset pixels
224 224 function offsetScroll(element, offset){
225 225 setTimeout(function(){
226 console.log(element);
227 226 var location = element.offset().top;
228 227 // some browsers use body, some use html
229 228 $('html, body').animate({ scrollTop: (location - offset) });
@@ -249,21 +248,26 b' function offsetScroll(element, offset){'
249 248 });
250 249 }
251 250 });
252 // Add tooltips
253 $('tr.line .lineno a').attr("title","Click to select line").addClass('tooltip');
254 $('tr.line .add-comment-line a').attr("title","Click to comment").addClass('tooltip');
251 $('.compare_view_files').on(
252 'mouseenter mouseleave', 'tr.line .lineno a',function(event) {
253 if (event.type === "mouseenter") {
254 $(this).parents('tr.line').addClass('hover');
255 } else {
256 $(this).parents('tr.line').removeClass('hover');
257 }
258 });
255 259
256 // Set colors and styles
257 $('tr.line .lineno a').hover(
258 function(){
259 $(this).parents('tr.line').addClass('hover');
260 }, function(){
261 $(this).parents('tr.line').removeClass('hover');
262 }
263 );
260 $('.compare_view_files').on(
261 'mouseenter mouseleave', 'tr.line .add-comment-line a',function(event){
262 if (event.type === "mouseenter") {
263 $(this).parents('tr.line').addClass('commenting');
264 } else {
265 $(this).parents('tr.line').removeClass('commenting');
266 }
267 });
264 268
265 $('tr.line .lineno a').click(
266 function(){
269 $('.compare_view_files').on(
270 'click', 'tr.line .lineno a',function(event) {
267 271 if ($(this).text() != ""){
268 272 $('tr.line').removeClass('selected');
269 273 $(this).parents("tr.line").addClass('selected');
@@ -271,7 +275,7 b' function offsetScroll(element, offset){'
271 275 // Replace URL without jumping to it if browser supports.
272 276 // Default otherwise
273 277 if (history.pushState) {
274 var new_location = location.href
278 var new_location = location.href;
275 279 if (location.hash){
276 280 new_location = new_location.replace(location.hash, "");
277 281 }
@@ -283,23 +287,14 b' function offsetScroll(element, offset){'
283 287 return false;
284 288 }
285 289 }
286 }
287 );
290 });
288 291
289 $('tr.line .add-comment-line a').hover(
290 function(){
291 $(this).parents('tr.line').addClass('commenting');
292 }, function(){
293 $(this).parents('tr.line').removeClass('commenting');
294 }
295 );
296
297 $('tr.line .add-comment-line a').on('click', function(e){
298 var tr = $(e.currentTarget).parents('tr.line')[0];
299 injectInlineForm(tr);
300 return false;
301 });
302
292 $('.compare_view_files').on(
293 'click', 'tr.line .add-comment-line a',function(event) {
294 var tr = $(event.currentTarget).parents('tr.line')[0];
295 injectInlineForm(tr);
296 return false;
297 });
303 298
304 299 $('.collapse_file').on('click', function(e) {
305 300 e.stopPropagation();
@@ -386,28 +381,14 b' function offsetScroll(element, offset){'
386 381 var tr = lineno.parents('tr.line');
387 382 tr.addClass('selected');
388 383
389 // once we scrolled into our line, trigger chat app
390 if (remainder){
391 tr.find('.add-comment-line a').trigger( "click" );
392 setTimeout(function(){
393 var nextNode = $(tr).next();
394 if(nextNode.hasClass('inline-comments')){
395 nextNode.next().find('.switch-to-chat').trigger( "click" );
396 }
397 else{
398 nextNode.find('.switch-to-chat').trigger( "click" );
399 }
400 // trigger scroll into, later so all elements are already loaded
401 tr[0].scrollIntoView();
402 }, 250);
384 tr[0].scrollIntoView();
403 385
404 }
405 else{
406 tr[0].scrollIntoView();
407 }
386 $.Topic('/ui/plugins/code/anchor_focus').prepareOrPublish({
387 tr:tr,
388 remainder:remainder});
408 389 }
409 390 }
410 };
391 }
411 392
412 393 collapsableContent();
413 394 });
@@ -150,19 +150,6 b' var injectInlineForm = function(tr){'
150 150
151 151 var _form = $(f).find('.inline-form').get(0);
152 152
153 $('.switch-to-chat', _form).on('click', function(evt){
154 var fParent = $(_parent).closest('.injected_diff').parent().prev('*[fid]');
155 var fid = fParent.attr('fid');
156
157 // activate chat and trigger subscription to channels
158 $.Topic('/chat_controller').publish({
159 action:'subscribe_to_channels',
160 data: ['/chat${0}$/fid/{1}/{2}'.format(templateContext.repo_name, fid, lineno)]
161 });
162 $(_form).closest('td').find('.comment-inline-form').addClass('hidden');
163 $(_form).closest('td').find('.chat-holder').removeClass('hidden');
164 });
165
166 153 var pullRequestId = templateContext.pull_request_data.pull_request_id;
167 154 var commitId = templateContext.commit_data.commit_id;
168 155
@@ -205,7 +192,6 b' var injectInlineForm = function(tr){'
205 192 // re trigger the linkification of next/prev navigation
206 193 linkifyComments($('.inline-comment-injected'));
207 194 timeagoActivate();
208 tooltip_activate();
209 195 bindDeleteCommentButtons();
210 196 commentForm.setActionButtonsDisabled(false);
211 197
@@ -224,6 +210,12 b' var injectInlineForm = function(tr){'
224 210 }
225 211 }, 10);
226 212
213 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
214 form:_form,
215 parent:_parent,
216 lineno: lineno,
217 f_path: f_path}
218 );
227 219 };
228 220
229 221 var deleteComment = function(comment_id) {
@@ -545,7 +537,6 b' var CommentForm = (function() {'
545 537 self.resetCommentFormState();
546 538 bindDeleteCommentButtons();
547 539 timeagoActivate();
548 tooltip_activate();
549 540 }
550 541 };
551 542 var submitFailCallback = function(){
@@ -26,10 +26,10 b' TTIP.main = {'
26 26 offset: [15,15],
27 27 maxWidth: 600,
28 28
29 set_listeners: function(tt){
30 $(tt).mouseover(tt, yt.show_tip);
31 $(tt).mousemove(tt, yt.move_tip);
32 $(tt).mouseout(tt, yt.close_tip);
29 setDeferredListeners: function(){
30 $('body').on('mouseover', '.tooltip', yt.show_tip);
31 $('body').on('mousemove', '.tooltip', yt.move_tip);
32 $('body').on('mouseout', '.tooltip', yt.close_tip);
33 33 },
34 34
35 35 init: function(){
@@ -43,19 +43,13 b' TTIP.main = {'
43 43 if(yt.maxWidth !== null){
44 44 $(yt.tipBox).css('max-width', yt.maxWidth+'px');
45 45 }
46
47 var tooltips = $('.tooltip');
48 var ttLen = tooltips.length;
49
50 for(i=0;i<ttLen;i++){
51 yt.set_listeners(tooltips[i]);
52 }
46 yt.setDeferredListeners();
53 47 },
54 48
55 49 show_tip: function(e, el){
56 50 e.stopPropagation();
57 51 e.preventDefault();
58 var el = e.data || el;
52 var el = e.data || e.currentTarget || el;
59 53 if(el.tagName.toLowerCase() === 'img'){
60 54 yt.tipText = el.alt ? el.alt : '';
61 55 } else {
@@ -76,7 +70,7 b' TTIP.main = {'
76 70 move_tip: function(e, el){
77 71 e.stopPropagation();
78 72 e.preventDefault();
79 var el = e.data || el;
73 var el = e.data || e.currentTarget || el;
80 74 var movePos = [e.pageX, e.pageY];
81 75 $(yt.tipBox).css('top', (movePos[1] + yt.offset[1]) + 'px')
82 76 $(yt.tipBox).css('left', (movePos[0] + yt.offset[0]) + 'px')
@@ -85,42 +79,16 b' TTIP.main = {'
85 79 close_tip: function(e, el){
86 80 e.stopPropagation();
87 81 e.preventDefault();
88 var el = e.data || el;
82 var el = e.data || e.currentTarget || el;
89 83 $(yt.tipBox).hide();
90 84 $(el).attr('title', $(el).attr('tt_title'));
91 85 $('#tip-box').hide();
92 86 }
93 87 };
94 88
95 /**
96 * tooltip activate
97 */
98 var tooltip_activate = function(){
99 yt = TTIP.main;
89 // activate tooltips
90 yt = TTIP.main;
91 if ($(document).data('activated-tooltips') !== '1'){
100 92 $(document).ready(yt.init);
101 };
102
103 /**
104 * show changeset tooltip
105 */
106 var show_changeset_tooltip = function(){
107 $('.lazy-cs').mouseover(function(e) {
108 var target = e.currentTarget;
109 var rid = $(target).attr('raw_id');
110 var repo_name = $(target).attr('repo_name');
111 var ttid = 'tt-'+rid;
112 var success = function(o){
113 $(target).addClass('tooltip')
114 $(target).attr('title', o['message']);
115 TTIP.main.show_tip(e, target);
116 }
117 if(rid && !$(target).hasClass('tooltip')){
118 $(target).attr('id', ttid);
119 $(target).attr('title', _gettext('loading ...'));
120 TTIP.main.set_listeners(target);
121 TTIP.main.show_tip(e, target);
122 var url = pyroutes.url('changeset_info', {"repo_name":repo_name, "revision": rid});
123 ajaxGET(url, success);
124 }
125 });
126 };
93 $(document).data('activated-tooltips', '1');
94 }
@@ -47,7 +47,7 b' var pyroutes = (function() {'
47 47 }
48 48 return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
49 49 };
50
50
51 51 str_format.format = function(parse_tree, argv) {
52 52 var cursor = 1,
53 53 tree_length = parse_tree.length,
@@ -81,7 +81,7 b' var pyroutes = (function() {'
81 81 else { // positional argument (implicit)
82 82 arg = argv[cursor++];
83 83 }
84
84
85 85 if (/[^s]/.test(match[8]) && (get_type(arg) !== 'number')) {
86 86 throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
87 87 }
@@ -108,9 +108,9 b' var pyroutes = (function() {'
108 108 }
109 109 return output.join('');
110 110 };
111
111
112 112 str_format.cache = {};
113
113
114 114 str_format.parse = function(fmt) {
115 115 var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
116 116 while (_fmt) {
@@ -161,12 +161,20 b' var pyroutes = (function() {'
161 161 };
162 162 return str_format;
163 163 })();
164
164
165 165 var vsprintf = function(fmt, argv) {
166 166 argv.unshift(fmt);
167 167 return sprintf.apply(null, argv);
168 168 };
169 169 return {
170 'asset': function(path, ver) {
171 var asset_url = ASSET_URL || '/_static/rhodecode/';
172 var ret = asset_url + path;
173 if (ver !== undefined) {
174 ret += '?ver=' + ver;
175 }
176 return ret;
177 },
170 178 'url': function(route_name, params) {
171 179 var result = route_name;
172 180 if (typeof(params) !== 'object'){
@@ -179,18 +187,18 b' var pyroutes = (function() {'
179 187 var param_name = route[1][i];
180 188 if (!params.hasOwnProperty(param_name))
181 189 throw new Error(
182 'parameter '+
183 param_name +
184 ' is missing in route"' +
190 'parameter '+
191 param_name +
192 ' is missing in route"' +
185 193 route_name + '" generation');
186 194 }
187 195 result = sprintf(route[0], params);
188
196
189 197 var ret = [];
190 198 // extra params => GET
191 199 for (var param in params){
192 200 if (route[1].indexOf(param) === -1){
193 ret.push(encodeURIComponent(param) + "=" +
201 ret.push(encodeURIComponent(param) + "=" +
194 202 encodeURIComponent(params[param]));
195 203 }
196 204 }
@@ -202,7 +210,7 b' var pyroutes = (function() {'
202 210 result = APPLICATION_URL + result;
203 211 }
204 212 }
205
213
206 214 return result;
207 215 },
208 216 'register': function(route_name, route_tmpl, req_params) {
@@ -53,3 +53,18 b' def add_localizer(event):'
53 53
54 54 request.localizer = localizer
55 55 request.translate = auto_translate
56
57
58 def scan_repositories_if_enabled(event):
59 """
60 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
61 does a repository scan if enabled in the settings.
62 """
63 from rhodecode.model.scm import ScmModel
64 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
65 settings = event.app.registry.settings
66 vcs_server_enabled = settings['vcs.server.enable']
67 import_on_startup = settings['startup.import_repos']
68 if vcs_server_enabled and import_on_startup:
69 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
70 repo2db_mapper(repositories, remove_obsolete=False)
@@ -1,4 +1,6 b''
1 1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.html"/>
3
2 4 %if c.users_log:
3 5 <table class="rctable admin_log">
4 6 <tr>
@@ -12,11 +14,11 b''
12 14 %for cnt,l in enumerate(c.users_log):
13 15 <tr class="parity${cnt%2}">
14 16 <td class="td-user">
15 %if l.user is not None:
16 ${h.link_to(l.user.username,h.url('edit_user', user_id=l.user.user_id))}
17 %else:
18 ${l.username}
19 %endif
17 %if l.user is not None:
18 ${base.gravatar_with_user(l.user.email)}
19 %else:
20 ${l.username}
21 %endif
20 22 </td>
21 23 <td class="td-journalaction">${h.action_parser(l)[0]()}
22 24 <div class="journal_action_params">
@@ -50,8 +52,6 b''
50 52 //therefore the .one method
51 53 $(document).on('pjax:complete',function(){
52 54 show_more_event();
53 tooltip_activate();
54 show_changeset_tooltip();
55 55 });
56 56
57 57 $(document).pjax('#user_log .pager_link', '#user_log');
@@ -97,7 +97,6 b''
97 97
98 98 </div>
99 99 </div>
100 </%def>
101 100
102 101 ## TODO: Ugly hack to get ldap select elements to work.
103 102 ## Find a solution to integrate this nicely.
@@ -114,3 +113,4 b''
114 113 $("#search_scope").select2(select2Options);
115 114 });
116 115 </script>
116 </%def>
@@ -44,27 +44,31 b''
44 44 <label for='lifetime'>${_('Gist lifetime')}</label>
45 45 ${h.dropdownmenu('lifetime', '0', c.lifetime_options)}
46 46
47 <label for='acl_level'>${_('Gist access level')}</label>
48 ${h.dropdownmenu('acl_level', c.gist.acl_level, c.acl_options)}
47 <label for='gist_acl_level'>${_('Gist access level')}</label>
48 ${h.dropdownmenu('gist_acl_level', c.gist.acl_level, c.acl_options)}
49 49 </div>
50 50 </div>
51 51
52 ## peppercorn schema
53 <input type="hidden" name="__start__" value="nodes:sequence"/>
52 54 % for cnt, file in enumerate(c.files):
55 <input type="hidden" name="__start__" value="file:mapping"/>
53 56 <div id="codeblock" class="codeblock" >
54 57 <div class="code-header">
55 58 <div class="form">
56 59 <div class="fields">
57 <input type="hidden" value="${file.path}" name="org_files">
58 <input id="filename_${h.FID('f',file.path)}" name="files" size="30" type="text" value="${file.path}">
59 ${h.dropdownmenu('mimetypes' ,'plain',[('plain',_('plain'))],enable_filter=True, id='mimetype_'+h.FID('f',file.path))}
60 <input type="hidden" name="filename_org" value="${file.path}" >
61 <input id="filename_${h.FID('f',file.path)}" name="filename" size="30" type="text" value="${file.path}">
62 ${h.dropdownmenu('mimetype' ,'plain',[('plain',_('plain'))],enable_filter=True, id='mimetype_'+h.FID('f',file.path))}
60 63 </div>
61 64 </div>
62 65 </div>
63 66 <div class="editor_container">
64 67 <pre id="editor_pre"></pre>
65 <textarea id="editor_${h.FID('f',file.path)}" name="contents" >${file.content}</textarea>
68 <textarea id="editor_${h.FID('f',file.path)}" name="content" >${file.content}</textarea>
66 69 </div>
67 70 </div>
71 <input type="hidden" name="__end__" />
68 72
69 73 ## dynamic edit box.
70 74 <script type="text/javascript">
@@ -72,7 +76,7 b''
72 76 var myCodeMirror = initCodeMirror(
73 77 "editor_${h.FID('f',file.path)}", '');
74 78
75 var modes_select = $('#mimetype_${h.FID('f',file.path)}');
79 var modes_select = $("#mimetype_${h.FID('f',file.path)}");
76 80 fillCodeMirrorOptions(modes_select);
77 81
78 82 // try to detect the mode based on the file we edit
@@ -86,7 +90,7 b''
86 90 setCodeMirrorMode(myCodeMirror, detected_mode);
87 91 }
88 92
89 var filename_selector = '#filename_${h.FID('f',file.path)}';
93 var filename_selector = "#filename_${h.FID('f',file.path)}";
90 94 // on change of select field set mode
91 95 setCodeMirrorModeFromSelect(
92 96 modes_select, filename_selector, myCodeMirror, null);
@@ -96,8 +100,8 b''
96 100 modes_select, filename_selector, myCodeMirror, null);
97 101 });
98 102 </script>
99
100 103 %endfor
104 <input type="hidden" name="__end__" />
101 105
102 106 <div class="pull-right">
103 107 ${h.submit('update',_('Update Gist'),class_="btn btn-success")}
@@ -118,11 +118,11 b''
118 118 "sort": "expires"}, title: "${_("Expires")}", className: "td-exp" }
119 119 ],
120 120 language: {
121 paginate: DEFAULT_GRID_PAGINATION
121 paginate: DEFAULT_GRID_PAGINATION,
122 emptyTable: _gettext("No gists available yet.")
122 123 },
123 124 "initComplete": function( settings, json ) {
124 125 timeagoActivate();
125 tooltip_activate();
126 126 get_datatable_count();
127 127 }
128 128 });
@@ -130,7 +130,6 b''
130 130 // update the counter when things change
131 131 $('#gist_list_table').on('draw.dt', function() {
132 132 timeagoActivate();
133 tooltip_activate();
134 133 get_datatable_count();
135 134 });
136 135
@@ -39,7 +39,7 b''
39 39 ${h.dropdownmenu('lifetime', '', c.lifetime_options)}
40 40
41 41 <label for='acl_level'>${_('Gist access level')}</label>
42 ${h.dropdownmenu('acl_level', '', c.acl_options)}
42 ${h.dropdownmenu('gist_acl_level', '', c.acl_options)}
43 43
44 44 </div>
45 45 <div id="codeblock" class="codeblock">
@@ -39,6 +39,7 b''
39 39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.url('my_account_watched')}">${_('Watched')}</a></li>
40 40 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.url('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
41 41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('my_account_perms')}">${_('My Permissions')}</a></li>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.url('my_account_notifications')}">${_('My Live Notifications')}</a></li>
42 43 </ul>
43 44 </div>
44 45
@@ -5,7 +5,7 b''
5 5 <div class="panel-body">
6 6 <p>
7 7 ${_('Built-in tokens can be used to authenticate with all possible options.')}<br/>
8 ${_('Each token can have a role. VCS tokens can be used together with the authtoken auth plugin for git/hg operations.')}
8 ${_('Each token can have a role. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations.')}
9 9 </p>
10 10 <table class="rctable auth_tokens">
11 11 <tr>
@@ -1,72 +1,56 b''
1 <%namespace name="base" file="/base/base.html"/>
2
3 1 <div class="panel panel-default">
4 2 <div class="panel-heading">
5 <h3 class="panel-title">${_('Account Emails')}</h3>
3 <h3 class="panel-title">${_('Your live notification settings')}</h3>
6 4 </div>
7 5
8 6 <div class="panel-body">
9 <div class="emails_wrap">
10 <table class="rctable account_emails">
11 <tr>
12 <td class="td-user">
13 ${base.gravatar(c.user.email, 16)}
14 <span class="user email">${c.user.email}</span>
15 </td>
16 <td class="td-tags">
17 <span class="tag tag1">${_('Primary')}</span>
18 </td>
19 </tr>
20 %if c.user_email_map:
21 %for em in c.user_email_map:
22 <tr>
23 <td class="td-user">
24 ${base.gravatar(em.email, 16)}
25 <span class="user email">${em.email}</span>
26 </td>
27 <td class="td-action">
28 ${h.secure_form(url('my_account_emails'),method='delete')}
29 ${h.hidden('del_email_id',em.email_id)}
30 <button class="btn btn-link btn-danger" type="submit" id="remove_email_%s" % em.email_id
31 onclick="return confirm('${_('Confirm to delete this email: %s') % em.email}');">
32 ${_('Delete')}
33 </button>
34 ${h.end_form()}
35 </td>
36 </tr>
37 %endfor
38 %else:
39 <tr class="noborder">
40 <td colspan="3">
41 <div class="td-email">
42 ${_('No additional emails specified')}
43 </div>
44 </td>
45 </tr>
46 %endif
47 </table>
48 </div>
7
8 <p><strong>IMPORTANT:</strong> This feature requires enabled channelstream websocket server to function correctly.</p>
9
10 <p class="hidden">Status of browser notifications permission: <strong id="browser-notification-status"></strong></p>
49 11
50 <div>
51 ${h.secure_form(url('my_account_emails'), method='post')}
52 <div class="form">
53 <!-- fields -->
54 <div class="fields">
55 <div class="field">
56 <div class="label">
57 <label for="new_email">${_('New email address')}:</label>
58 </div>
59 <div class="input">
60 ${h.text('new_email', class_='medium')}
61 </div>
62 </div>
63 <div class="buttons">
64 ${h.submit('save',_('Add'),class_="btn")}
65 ${h.reset('reset',_('Reset'),class_="btn")}
66 </div>
67 </div>
68 </div>
12 ${h.secure_form(url('my_account_notifications_toggle_visibility'), method='post', id='notification-status')}
13 <button class="btn btn-default" type="submit">
14 ${_('Notifications')} <strong>${_('Enabled') if c.rhodecode_user.get_instance().user_data.get('notification_status') else _('Disabled')}</strong>
15 </button>
69 16 ${h.end_form()}
70 </div>
17
18 <a class="btn btn-info" id="test-notification">Test notification</a>
19
71 20 </div>
72 21 </div>
22
23 <script type="application/javascript">
24
25 function checkBrowserStatus(){
26 var browserStatus = 'Unknown';
27
28 if (!("Notification" in window)) {
29 browserStatus = 'Not supported'
30 }
31 else if(Notification.permission === 'denied'){
32 browserStatus = 'Denied';
33 $('.flash_msg').append('<div class="alert alert-error">Notifications are blocked on browser level - you need to enable them in your browser settings.</div>')
34 }
35 else if(Notification.permission === 'granted'){
36 browserStatus = 'Allowed';
37 }
38
39 $('#browser-notification-status').text(browserStatus);
40 };
41
42 checkBrowserStatus();
43
44 $('#test-notification').on('click', function(e){
45 var levels = ['info', 'error', 'warning', 'success'];
46 var level = levels[Math.floor(Math.random()*levels.length)];
47 var payload = {
48 message: {
49 message: 'This is a test notification.',
50 level: level,
51 testMessage: true
52 }
53 };
54 $.Topic('/notifications').publish(payload);
55 })
56 </script>
@@ -32,8 +32,10 b''
32 32 <td class="td-status">
33 33 <div class="${'flag_status %s' % pull_request.calculated_review_status()} pull-left"></div>
34 34 </td>
35 <td class="td-componentname">
36 ${h.link_to(pull_request.target_repo.repo_name,h.url('summary_home',repo_name=pull_request.target_repo.repo_name))}
35 <td class="truncate-wrap td-componentname">
36 <div class="truncate">
37 ${h.link_to(pull_request.target_repo.repo_name,h.url('summary_home',repo_name=pull_request.target_repo.repo_name))}
38 </div>
37 39 </td>
38 40 <td class="user">
39 41 ${base.gravatar_with_user(pull_request.author.email, 16)}
@@ -94,8 +96,10 b''
94 96 <td class="td-status">
95 97 <div class="${'flag_status %s' % pull_request.calculated_review_status()} pull-left"></div>
96 98 </td>
97 <td class="td-componentname">
98 ${h.link_to(pull_request.target_repo.repo_name,h.url('summary_home',repo_name=pull_request.target_repo.repo_name))}
99 <td class="truncate-wrap td-componentname">
100 <div class="truncate">
101 ${h.link_to(pull_request.target_repo.repo_name,h.url('summary_home',repo_name=pull_request.target_repo.repo_name))}
102 </div>
99 103 </td>
100 104 <td class="user">
101 105 ${base.gravatar_with_user(pull_request.author.email, 16)}
@@ -37,11 +37,11 b''
37 37 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
38 38 ],
39 39 language: {
40 paginate: DEFAULT_GRID_PAGINATION
40 paginate: DEFAULT_GRID_PAGINATION,
41 emptyTable: _gettext("No repositories available yet.")
41 42 },
42 43 "initComplete": function( settings, json ) {
43 44 get_datatable_count();
44 tooltip_activate();
45 45 quick_repo_menu();
46 46 }
47 47 });
@@ -35,11 +35,11 b''
35 35 "type": Number}, title: "${_('Commit')}", className: "td-hash" }
36 36 ],
37 37 language: {
38 paginate: DEFAULT_GRID_PAGINATION
38 paginate: DEFAULT_GRID_PAGINATION,
39 emptyTable: _gettext("No repositories available yet.")
39 40 },
40 41 "initComplete": function( settings, json ) {
41 42 get_datatable_count();
42 tooltip_activate();
43 43 quick_repo_menu();
44 44 }
45 45 });
@@ -62,11 +62,11 b''
62 62 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
63 63 ],
64 64 language: {
65 paginate: DEFAULT_GRID_PAGINATION
65 paginate: DEFAULT_GRID_PAGINATION,
66 emptyTable: _gettext("No repository groups available yet.")
66 67 },
67 68 "initComplete": function( settings, json ) {
68 69 get_datatable_count();
69 tooltip_activate();
70 70 quick_repo_menu();
71 71 }
72 72 });
@@ -38,7 +38,6 b''
38 38 </div>
39 39 </div>
40 40 </div>
41 </%def>
42 41
43 42 <script>
44 43 (function worker() {
@@ -72,3 +71,4 b''
72 71 });
73 72 })();
74 73 </script>
74 </%def> No newline at end of file
@@ -23,6 +23,10 b''
23 23 ${self.repo_menu(active='options')}
24 24 </%def>
25 25
26 <%def name="main_content()">
27 <%include file="/admin/repos/repo_edit_${c.active}.html"/>
28 </%def>
29
26 30
27 31 <%def name="main()">
28 32 <div class="box">
@@ -64,14 +68,17 b''
64 68 <li class="${'active' if c.active=='statistics' else ''}">
65 69 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
66 70 </li>
71 <li class="${'active' if c.active=='integrations' else ''}">
72 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
73 </li>
67 74 </ul>
68 75 </div>
69 76
70 77 <div class="main-content-full-width">
71 <%include file="/admin/repos/repo_edit_${c.active}.html"/>
78 ${self.main_content()}
72 79 </div>
73 80
74 81 </div>
75 82 </div>
76 83
77 </%def>
84 </%def> No newline at end of file
@@ -69,11 +69,11 b''
69 69 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
70 70 ],
71 71 language: {
72 paginate: DEFAULT_GRID_PAGINATION
72 paginate: DEFAULT_GRID_PAGINATION,
73 emptyTable:_gettext("No repositories available yet.")
73 74 },
74 75 "initComplete": function( settings, json ) {
75 76 get_datatable_count();
76 tooltip_activate();
77 77 quick_repo_menu();
78 78 }
79 79 });
@@ -18,6 +18,18 b''
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 <%def name="side_bar_nav()">
22 % for navitem in c.navlist:
23 <li class="${'active' if c.active==navitem.key else ''}">
24 <a href="${navitem.url}">${navitem.name}</a>
25 </li>
26 % endfor
27 </%def>
28
29 <%def name="main_content()">
30 <%include file="/admin/settings/settings_${c.active}.html"/>
31 </%def>
32
21 33 <%def name="main()">
22 34 <div class="box">
23 35 <div class="title">
@@ -28,18 +40,14 b''
28 40 <div class='sidebar-col-wrapper'>
29 41 <div class="sidebar">
30 42 <ul class="nav nav-pills nav-stacked">
31 % for navitem in c.navlist:
32 <li class="${'active' if c.active==navitem.key else ''}">
33 <a href="${navitem.url}">${navitem.name}</a>
34 </li>
35 % endfor
43 ${self.side_bar_nav()}
36 44 </ul>
37 45 </div>
38 46
39 47 <div class="main-content-full-width">
40 <%include file="/admin/settings/settings_${c.active}.html"/>
48 ${self.main_content()}
41 49 </div>
42 50 </div>
43 51 </div>
44 52
45 </%def>
53 </%def> No newline at end of file
@@ -77,8 +77,8 b''
77 77 <div style="padding: 10px 0px"></div>
78 78 <div class="textarea text-area">
79 79 ${h.textarea('rhodecode_pre_code',cols=23,rows=5,class_="medium")}
80 <span class="help-block">${_('Custom js/css code added at the end of the <header> tag.')}
81 ${_('Use <script> or <css> tags to define custom styling or scripting')}</span>
80 <span class="help-block">${_('Custom js/css code added at the end of the <header/> tag.')}
81 ${_('Use <script/> or <css/> tags to define custom styling or scripting')}</span>
82 82 </div>
83 83 </div>
84 84 </div>
@@ -84,6 +84,6 b''
84 84 <script>
85 85 $('#check_for_update').click(function(e){
86 86 $('#update_notice').show();
87 $('#update_notice').load("${h.url('admin_settings_system_update')}");
87 $('#update_notice').load("${h.url('admin_settings_system_update',version=c.rhodecode_version, platform=c.platform)}");
88 88 })
89 89 </script>
@@ -66,11 +66,11 b''
66 66 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
67 67 ],
68 68 language: {
69 paginate: DEFAULT_GRID_PAGINATION
69 paginate: DEFAULT_GRID_PAGINATION,
70 emptyTable: _gettext("No user groups available yet.")
70 71 },
71 72 "initComplete": function( settings, json ) {
72 73 get_datatable_count();
73 tooltip_activate();
74 74 }
75 75 });
76 76
@@ -84,7 +84,6 b''
84 84 pageLength: ${c.visual.admin_grid_items},
85 85 order: [[ 1, "asc" ]],
86 86 columns: [
87 { data: {"_": "gravatar"}, className: "td-gravatar" },
88 87 { data: {"_": "username",
89 88 "sort": "username_raw"}, title: "${_('Username')}", className: "td-user" },
90 89 { data: {"_": "email",
@@ -106,11 +105,11 b''
106 105 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
107 106 ],
108 107 language: {
109 paginate: DEFAULT_GRID_PAGINATION
108 paginate: DEFAULT_GRID_PAGINATION,
109 emptyTable: _gettext("No users available yet.")
110 110 },
111 111 "initComplete": function( settings, json ) {
112 112 get_datatable_count();
113 tooltip_activate();
114 113 },
115 114 "createdRow": function ( row, data, index ) {
116 115 if (!data['active_raw']){
@@ -7,7 +7,7 b''
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
@@ -16,7 +16,6 b''
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 ${self.body()}
20 19 </div>
21 20 </div>
22 21 ${self.menu_bar_subnav()}
@@ -82,6 +81,7 b''
82 81 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
83 82 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
84 83 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
84 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
85 85 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 86 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
87 87 </ul>
@@ -135,8 +135,9 b''
135 135
136 136
137 137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 <div class="rc-user tooltip" title="${contact}">
139 ${self.gravatar(h.email_or_none(contact), size)}
138 <% email = h.email_or_none(contact) %>
139 <div class="rc-user tooltip" title="${h.author_string(email)}">
140 ${self.gravatar(email, size)}
140 141 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
141 142 </div>
142 143 </%def>
@@ -579,7 +580,7 b''
579 580 % endif
580 581
581 582 </script>
582 <script src="${h.url('/js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
583 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
583 584 </%def>
584 585
585 586 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
@@ -1,54 +1,20 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html>
3 3
4 <%def name="get_template_context()" filter="n, trim">{
4 <%
5 c.template_context['repo_name'] = getattr(c, 'repo_name', '')
5 6
6 ## repo data
7 repo_name: "${getattr(c, 'repo_name', '')}",
8 % if hasattr(c, 'rhodecode_db_repo'):
9 repo_type: "${c.rhodecode_db_repo.repo_type}",
10 repo_landing_commit: "${c.rhodecode_db_repo.landing_rev[1]}",
11 % else:
12 repo_type: null,
13 repo_landing_commit: null,
14 % endif
7 if hasattr(c, 'rhodecode_db_repo'):
8 c.template_context['repo_type'] = c.rhodecode_db_repo.repo_type
9 c.template_context['repo_landing_commit'] = c.rhodecode_db_repo.landing_rev[1]
15 10
16 ## user data
17 % if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
18 rhodecode_user: {
19 username: "${c.rhodecode_user.username}",
20 email: "${c.rhodecode_user.email}",
21 },
22 % else:
23 rhodecode_user: {
24 username: null,
25 email: null,
26 },
27 % endif
28
29 ## visual settings
30 visual: {
31 default_renderer: "${h.get_visual_attr(c, 'default_renderer')}"
32 },
11 if getattr(c, 'rhodecode_user', None) and c.rhodecode_user.user_id:
12 c.template_context['rhodecode_user']['username'] = c.rhodecode_user.username
13 c.template_context['rhodecode_user']['email'] = c.rhodecode_user.email
14 c.template_context['rhodecode_user']['notification_status'] = c.rhodecode_user.get_instance().user_data.get('notification_status', True)
33 15
34 ## current commit context, filled inside templates that expose that
35 commit_data: {
36 commit_id: null,
37 },
38
39 ## current pr context, filled inside templates that expose that
40 pull_request_data: {
41 pull_request_id: null,
42 },
43
44 ## timeago settings, can be overwritten by custom user settings later
45 timeago: {
46 refresh_time: ${120 * 1000},
47 cutoff_limit: ${1000*60*60*24*7}
48 }
49 }
50
51 </%def>
16 c.template_context['visual']['default_renderer'] = h.get_visual_attr(c, 'default_renderer')
17 %>
52 18
53 19 <html xmlns="http://www.w3.org/1999/xhtml">
54 20 <head>
@@ -58,13 +24,13 b''
58 24 <meta name="robots" content="index, nofollow"/>
59 25 </%def>
60 26 ${self.robots()}
61 <link rel="icon" href="${h.url('/images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
27 <link rel="icon" href="${h.asset('images/favicon.ico', ver=c.rhodecode_version_hash)}" sizes="16x16 32x32" type="image/png" />
62 28
63 29 ## CSS definitions
64 30 <%def name="css()">
65 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
31 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
66 32 <!--[if lt IE 9]>
67 <link rel="stylesheet" type="text/css" href="${h.url('/css/ie.css', ver=c.rhodecode_version_hash)}" media="screen"/>
33 <link rel="stylesheet" type="text/css" href="${h.asset('css/ie.css', ver=c.rhodecode_version_hash)}" media="screen"/>
68 34 <![endif]-->
69 35 ## EXTRA FOR CSS
70 36 ${self.css_extra()}
@@ -77,10 +43,10 b''
77 43
78 44 ## JAVASCRIPT
79 45 <%def name="js()">
80 <script src="${h.url('/js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
46 <script src="${h.asset('js/rhodecode/i18n/%s.js' % c.language, ver=c.rhodecode_version_hash)}"></script>
81 47 <script type="text/javascript">
82 48 // register templateContext to pass template variables to JS
83 var templateContext = ${get_template_context()};
49 var templateContext = ${h.json.dumps(c.template_context)|n};
84 50
85 51 var REPO_NAME = "${getattr(c, 'repo_name', '')}";
86 52 %if hasattr(c, 'rhodecode_db_repo'):
@@ -91,6 +57,7 b''
91 57 var REPO_TYPE = '';
92 58 %endif
93 59 var APPLICATION_URL = "${h.url('home').rstrip('/')}";
60 var ASSET_URL = "${h.asset('')}";
94 61 var DEFAULT_RENDERER = "${h.get_visual_attr(c, 'default_renderer')}";
95 62 var CSRF_TOKEN = "${getattr(c, 'csrf_token', '')}";
96 63 % if getattr(c, 'rhodecode_user', None):
@@ -102,7 +69,9 b''
102 69 var APPENLIGHT = {
103 70 enabled: ${'true' if getattr(c, 'appenlight_enabled', False) else 'false'},
104 71 key: '${getattr(c, "appenlight_api_public_key", "")}',
105 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
72 % if getattr(c, 'appenlight_server_url', None):
73 serverUrl: '${getattr(c, "appenlight_server_url", "")}',
74 % endif
106 75 requestInfo: {
107 76 % if getattr(c, 'rhodecode_user', None):
108 77 ip: '${c.rhodecode_user.ip_addr}',
@@ -111,22 +80,21 b''
111 80 }
112 81 };
113 82 </script>
114
115 83 <!--[if lt IE 9]>
116 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
84 <script language="javascript" type="text/javascript" src="${h.asset('js/excanvas.min.js')}"></script>
117 85 <![endif]-->
118 <script language="javascript" type="text/javascript" src="${h.url('/js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
119 <script language="javascript" type="text/javascript" src="${h.url('/js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
120 <script>CodeMirror.modeURL = "${h.url('/js/mode/%N/%N.js')}";</script>
86 <script language="javascript" type="text/javascript" src="${h.asset('js/rhodecode/routes.js', ver=c.rhodecode_version_hash)}"></script>
87 <script language="javascript" type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
88 ## avoide escaping the %N
89 <script>CodeMirror.modeURL = "${h.asset('') + 'js/mode/%N/%N.js?ver='+c.rhodecode_version_hash}";</script>
90
121 91
122 92 ## JAVASCRIPT EXTRA - optionally inject some extra JS for specificed templates
123 93 ${self.js_extra()}
124 94
125 95 <script type="text/javascript">
126 96 $(document).ready(function(){
127 tooltip_activate();
128 97 show_more_event();
129 show_changeset_tooltip();
130 98 timeagoActivate();
131 99 })
132 100 </script>
@@ -139,6 +107,7 b''
139 107
140 108 <%def name="head_extra()"></%def>
141 109 ${self.head_extra()}
110 <%include file="/base/plugins_base.html"/>
142 111
143 112 ## extra stuff
144 113 %if c.pre_code:
@@ -213,4 +213,32 b''
213 213 </div>
214 214 </div>
215 215 % endif
216
217 ## This condition has to be adapted if we add more labs settings for
218 ## VCS types other than 'hg'
219 % if c.labs_active and (display_globals or repo_type in ['hg']):
220 <div class="panel panel-danger">
221 <div class="panel-heading">
222 <h3 class="panel-title">${_('Labs settings')}: ${_('These features are considered experimental and may not work as expected.')}</h3>
223 </div>
224 <div class="panel-body">
225 <div class="fields">
226
227 <div class="field">
228 <div class="label">
229 <label>${_('Mercurial server-side merge')}:</label>
230 </div>
231 <div class="checkboxes">
232 <div class="checkbox">
233 ${h.checkbox('rhodecode_hg_use_rebase_for_merging' + suffix, 'True', **kwargs)}
234 <label for="rhodecode_hg_use_rebase_for_merging${suffix}">${_('Use rebase instead of creating a merge commit when merging via web interface')}</label>
235 </div>
236 <!-- <p class="help-block">Help message here</p> -->
237 </div>
238 </div>
239
240 </div>
241 </div>
242 </div>
243 % endif
216 244 </%def>
@@ -68,11 +68,11 b''
68 68 "sort": "compare"}, title: "${_('Compare')}", className: "td-compare" }
69 69 ],
70 70 language: {
71 paginate: DEFAULT_GRID_PAGINATION
71 paginate: DEFAULT_GRID_PAGINATION,
72 emptyTable: _gettext("No bookmarks available yet.")
72 73 },
73 74 "initComplete": function(settings, json) {
74 75 get_datatable_count();
75 tooltip_activate();
76 76 timeagoActivate();
77 77 compare_radio_buttons("${c.repo_name}", 'book');
78 78 }
@@ -81,7 +81,6 b''
81 81 // update when things change
82 82 $('#obj_list_table').on('draw.dt', function() {
83 83 get_datatable_count();
84 tooltip_activate();
85 84 timeagoActivate();
86 85 });
87 86
@@ -67,11 +67,11 b''
67 67 "sort": "compare"}, title: "${_('Compare')}", className: "td-compare" }
68 68 ],
69 69 language: {
70 paginate: DEFAULT_GRID_PAGINATION
70 paginate: DEFAULT_GRID_PAGINATION,
71 emptyTable: _gettext("No branches available yet.")
71 72 },
72 73 "initComplete": function( settings, json ) {
73 74 get_datatable_count();
74 tooltip_activate();
75 75 timeagoActivate();
76 76 compare_radio_buttons("${c.repo_name}", 'branch');
77 77 }
@@ -80,7 +80,6 b''
80 80 // update when things change
81 81 $('#obj_list_table').on('draw.dt', function() {
82 82 get_datatable_count();
83 tooltip_activate();
84 83 timeagoActivate();
85 84 });
86 85
@@ -97,14 +97,18 b''
97 97 <div class="table">
98 98 <table id="changesets" class="rctable">
99 99 <tr>
100 <th></th>
100 ## checkbox
101 101 <th></th>
102 <th>${_('Author')}</th>
103 <th>${_('Age')}</th>
102 <th colspan="2"></th>
103
104 <th>${_('Commit')}</th>
105 ## commit message expand arrow
104 106 <th></th>
105 107 <th>${_('Commit Message')}</th>
106 <th>${_('Commit')}</th>
107 <th></th>
108
109 <th>${_('Age')}</th>
110 <th>${_('Author')}</th>
111
108 112 <th>${_('Refs')}</th>
109 113 </tr>
110 114 <tbody>
@@ -128,27 +132,17 b''
128 132 </a>
129 133 %endif
130 134 </div>
135 %else:
136 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
131 137 %endif
132 138 </td>
133 <td class="td-user">
134 ${self.gravatar(h.email_or_none(commit.author))}
135 <span title="${commit.author}" class="user">${h.link_to_user(commit.author, length=22)}</span>
136 </td>
137 <td class="td-time">
138 ${h.age_component(commit.date)}
139 <td class="td-comments comments-col">
140 %if c.comments.get(commit.raw_id):
141 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
142 <i class="icon-comment icon-comment-colored"></i> ${len(c.comments[commit.raw_id])}
143 </a>
144 %endif
139 145 </td>
140
141 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}">
142 <div class="show_more_col">
143 <i class="show_more"></i>&nbsp;
144 </div>
145 </td>
146 <td class="mid td-description">
147 <div class="log-container truncate-wrap">
148 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
149 </div>
150 </td>
151
152 146 <td class="td-hash">
153 147 <code>
154 148 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
@@ -156,13 +150,22 b''
156 150 </a>
157 151 </code>
158 152 </td>
153 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}">
154 <div class="show_more_col">
155 <i class="show_more"></i>&nbsp;
156 </div>
157 </td>
158 <td class="td-description mid">
159 <div class="log-container truncate-wrap">
160 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
161 </div>
162 </td>
159 163
160 <td class="td-comments comments-col">
161 %if c.comments.get(commit.raw_id):
162 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
163 ${len(c.comments[commit.raw_id])} <i class="icon-comment icon-comment-colored"></i>
164 </a>
165 %endif
164 <td class="td-time">
165 ${h.age_component(commit.date)}
166 </td>
167 <td class="td-user">
168 ${self.gravatar_with_user(commit.author)}
166 169 </td>
167 170
168 171 <td class="td-tags tags-col truncate-wrap">
@@ -203,7 +206,7 b''
203 206 ${c.pagination.pager('$link_previous ~2~ $link_next')}
204 207 </div>
205 208
206 <script type="text/javascript" src="${h.url('/js/jquery.commits-graph.js')}"></script>
209 <script type="text/javascript" src="${h.asset('js/jquery.commits-graph.js')}"></script>
207 210 <script type="text/javascript">
208 211 var cache = {};
209 212 $(function(){
@@ -3,8 +3,9 b''
3 3 %if c.repo_commits:
4 4 <table class="rctable repo_summary table_disp">
5 5 <tr>
6
7 <th class="status" colspan="2"></th>
6 8 <th>${_('Commit')}</th>
7 <th class="status" colspan="2"></th>
8 9 <th>${_('Commit message')}</th>
9 10 <th>${_('Age')}</th>
10 11 <th>${_('Author')}</th>
@@ -12,40 +13,48 b''
12 13 </tr>
13 14 %for cnt,cs in enumerate(c.repo_commits):
14 15 <tr class="parity${cnt%2}">
16
17 <td class="td-status">
18 %if c.statuses.get(cs.raw_id):
19 <div class="changeset-status-ico shortlog">
20 %if c.statuses.get(cs.raw_id)[2]:
21 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
22 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
23 </a>
24 %else:
25 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(cs.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
26 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
27 </a>
28 %endif
29 </div>
30 %else:
31 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
32 %endif
33 </td>
34 <td class="td-comments">
35 %if c.comments.get(cs.raw_id,[]):
36 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
37 <i class="icon-comment icon-comment-colored"></i> ${len(c.comments[cs.raw_id])}
38 </a>
39 %endif
40 </td>
15 41 <td class="td-commit">
16 42 <pre><a href="${h.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
17 43 </td>
18 <td class="td-status">
19 %if c.statuses.get(cs.raw_id):
20 <div class="changeset-status-ico shortlog">
21 %if c.statuses.get(cs.raw_id)[2]:
22 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
23 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
24 </a>
25 %else:
26 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
27 %endif
28 </div>
29 %endif
30 </td>
31 <td class="td-comments">
32 %if c.comments.get(cs.raw_id,[]):
33 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
34 <i class="icon-comment icon-comment-colored"></i> ${len(c.comments[cs.raw_id])}
35 </a>
36 %endif
44
45 <td class="td-description mid">
46 <div class="log-container truncate-wrap">
47 <div class="message truncate" id="c-${cs.raw_id}">${h.urlify_commit_message(cs.message, c.repo_name)}</div>
48 </div>
37 49 </td>
38 <td class="td-message">
39 ${h.urlify_commit_message(h.truncate(cs.message, 50), c.repo_name)}
40 </td>
50
41 51 <td class="td-time">
42 52 ${h.age_component(cs.date)}
43 53 </td>
54 <td class="td-user author">
55 ${base.gravatar_with_user(cs.author)}
56 </td>
44 57
45 <td class="td-user author">
46 ${base.gravatar(h.email_or_none(cs.author), 16)}
47 <span title="${cs.author}" class="user">${h.link_to_user(cs.author, length=22)}</span>
48 </td>
49 58 <td class="td-tags truncate-wrap">
50 59 <div class="truncate tags-truncate"><div class="autoexpand">
51 60 %if h.is_hg(c.rhodecode_repo):
@@ -77,7 +86,7 b''
77 86
78 87 <script type="text/javascript">
79 88 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 2000, scrollTo: false });
80 $(document).on('pjax:success', function(){ timeagoActivate(); tooltip_activate(); });
89 $(document).on('pjax:success', function(){ timeagoActivate(); });
81 90 </script>
82 91
83 92 <div class="pagination-wh pagination-left">
@@ -164,12 +164,10 b''
164 164 ${_('Author')}
165 165 </h4>
166 166 </div>
167 <div class="sidebar-right-content">
168 <img alt="gravatar" class="gravatar" title="${h.email_or_none(c.commit.author)}" src="${h.gravatar_url(h.email_or_none(c.commit.author), 40)}" height="16" width="16">
169 <span class="author">
170 ${h.link_to_user(c.commit.author)} - ${h.age_component(c.commit.date)}
171 </span>
172 </div>
167 <div class="sidebar-right-content">
168 ${self.gravatar_with_user(c.commit.author)}
169 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
170 </div>
173 171 </div><!-- end sidebar -->
174 172 </div> <!-- end summary -->
175 173 <div class="cs_files_title">
@@ -103,7 +103,9 b''
103 103 </%def>
104 104
105 105 <%def name="user_gravatar(email, size=16)">
106 <div class="rc-user tooltip" title="${h.author_string(email)}">
106 107 ${base.gravatar(email, 16)}
108 </div>
107 109 </%def>
108 110
109 111 <%def name="repo_actions(repo_name, super_user=True)">
@@ -9,11 +9,11 b''
9 9 </%def>
10 10
11 11 <%def name="js_extra()">
12 <script type="text/javascript" src="${h.url('/js/mergerly.js')}"></script>
12 <script type="text/javascript" src="${h.asset('js/mergerly.js', ver=c.rhodecode_version_hash)}"></script>
13 13 </%def>
14 14
15 15 <%def name="css_extra()">
16 <link rel="stylesheet" type="text/css" href="${h.url('/css/mergerly.css')}"/>
16 <link rel="stylesheet" type="text/css" href="${h.asset('css/mergerly.css', ver=c.rhodecode_version_hash)}"/>
17 17 </%def>
18 18
19 19
@@ -351,9 +351,9 b''
351 351 <td class="mid td-description">
352 352 <div class="log-container truncate-wrap">
353 353 <div id="c-2ffc6faabc7a9c790b1b452943a3f0c047b8b436" class="message truncate" data-message-raw="tests: Test echo method on the server object
354
354
355 355 This only works for Pyro4 so far, have to extend it still for HTTP to work.">tests: Test echo method on the server object
356
356
357 357 This only works for Pyro4 so far, have to extend it still for HTTP to work.</div>
358 358 </div>
359 359 </td>
@@ -365,7 +365,7 b''
365 365 $('.expand_commit').on('click',function(e){
366 366 var target_expand = $(this);
367 367 var cid = target_expand.data('commitId');
368
368
369 369 if (target_expand.hasClass('open')){
370 370 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
371 371 $('#t-'+cid).css({'height': '1.5em', 'max-height': '1.5em', 'text-overflow': 'ellipsis', 'overflow':'hidden', 'white-space':'nowrap'});
@@ -507,7 +507,7 b''
507 507 <td class="td-radio"><input type="radio" value="repository.admin"
508 508 name="u_perm_1" id="u_perm_1_repositoryadmin"></td>
509 509 <td>
510 <img class="gravatar" src="/images/user30.png" height="16" width="16">
510 <img class="gravatar" src="/_static/rhodecode/images/user30.png" height="16" width="16">
511 511 <span class="user">default</span>
512 512 </td>
513 513 <td></td>
@@ -1,17 +1,131 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 ## helpers
4 <%def name="tag_button(text, tag_type=None)">
5 <%
6 color_scheme = {
7 'default': 'border:1px solid #979797;color:#666666;background-color:#f9f9f9',
8 'approved': 'border:1px solid #0ac878;color:#0ac878;background-color:#f9f9f9',
9 'rejected': 'border:1px solid #e85e4d;color:#e85e4d;background-color:#f9f9f9',
10 'under_review': 'border:1px solid #ffc854;color:#ffc854;background-color:#f9f9f9',
11 }
12 %>
13 <pre style="display:inline;border-radius:2px;font-size:12px;padding:.2em;${color_scheme.get(tag_type, color_scheme['default'])}">${text}</pre>
14 </%def>
15
16 <%def name="status_text(text, tag_type=None)">
17 <%
18 color_scheme = {
19 'default': 'color:#666666',
20 'approved': 'color:#0ac878',
21 'rejected': 'color:#e85e4d',
22 'under_review': 'color:#ffc854',
23 }
24 %>
25 <span style="font-weight:bold;font-size:12px;padding:.2em;${color_scheme.get(tag_type, color_scheme['default'])}">${text}</span>
26 </%def>
27
3 28 ## headers we additionally can set for email
4 29 <%def name="headers()" filter="n,trim"></%def>
5 30
6 ## plain text version of the email. Empty by default
7 <%def name="body_plaintext()" filter="n,trim"></%def>
31 <%def name="plaintext_footer()">
32 ${_('This is a notification from RhodeCode. %(instance_url)s') % {'instance_url': instance_url}}
33 </%def>
34
35 <%def name="body_plaintext()" filter="n,trim">
36 ## this example is not called itself but overridden in each template
37 ## the plaintext_footer should be at the bottom of both html and text emails
38 ${self.plaintext_footer()}
39 </%def>
8 40
9 ${self.body()}
41 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
42 <html xmlns="http://www.w3.org/1999/xhtml">
43 <head>
44 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
45 <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
46 <title>${self.subject()}</title>
47 <style type="text/css">
48 /* Based on The MailChimp Reset INLINE: Yes. */
49 #outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
50 body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
51 /* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
52 .ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
53 .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
54 /* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
55 #backgroundTable {margin:0; padding:0; line-height: 100% !important;}
56 /* End reset */
10 57
58 /* defaults for images*/
59 img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
60 a img {border:none;}
61 .image_fix {display:block;}
62
63 body {line-height:1.2em;}
64 p {margin: 0 0 20px;}
65 h1, h2, h3, h4, h5, h6 {color:#323232!important;}
66 a {color:#427cc9;text-decoration:none;outline:none;cursor:pointer;}
67 a:focus {outline:none;}
68 a:hover {color: #305b91;}
69 h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color:#427cc9!important;text-decoration:none!important;}
70 h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {color: #305b91!important;}
71 h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {color: #305b91!important;}
72 table {font-size:13px;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;}
73 table td {padding:.65em 1em .65em 0;border-collapse:collapse;vertical-align:top;text-align:left;}
74 input {display:inline;border-radius:2px;border-style:solid;border: 1px solid #dbd9da;padding:.5em;}
75 input:focus {outline: 1px solid #979797}
76 @media only screen and (-webkit-min-device-pixel-ratio: 2) {
77 /* Put your iPhone 4g styles in here */
78 }
11 79
12 <div>
13 --
14 <br/>
15 <br/>
16 <b>${_('This is a notification from RhodeCode. %(instance_url)s') % {'instance_url': instance_url}}</b>
17 </div>
80 /* Android targeting */
81 @media only screen and (-webkit-device-pixel-ratio:.75){
82 /* Put CSS for low density (ldpi) Android layouts in here */
83 }
84 @media only screen and (-webkit-device-pixel-ratio:1){
85 /* Put CSS for medium density (mdpi) Android layouts in here */
86 }
87 @media only screen and (-webkit-device-pixel-ratio:1.5){
88 /* Put CSS for high density (hdpi) Android layouts in here */
89 }
90 /* end Android targeting */
91
92 </style>
93
94 <!-- Targeting Windows Mobile -->
95 <!--[if IEMobile 7]>
96 <style type="text/css">
97
98 </style>
99 <![endif]-->
100
101 <!--[if gte mso 9]>
102 <style>
103 /* Target Outlook 2007 and 2010 */
104 </style>
105 <![endif]-->
106 </head>
107 <body>
108 <!-- Wrapper/Container Table: Use a wrapper table to control the width and the background color consistently of your email. Use this approach instead of setting attributes on the body tag. -->
109 <table cellpadding="0" cellspacing="0" border="0" id="backgroundTable" align="left" style="margin:1%;width:97%;padding:0;font-family:sans-serif;font-weight:100;border:1px solid #dbd9da">
110 <tr>
111 <td valign="top" style="padding:0;">
112 <table cellpadding="0" cellspacing="0" border="0" align="left" width="100%">
113 <tr><td style="width:100%;padding:7px;background-color:#202020" valign="top">
114 <a style="color:#eeeeee;text-decoration:none;" href="${instance_url}">
115 ${_('RhodeCode')}
116 % if rhodecode_instance_name:
117 - ${rhodecode_instance_name}
118 % endif
119 </a>
120 </td></tr>
121 <tr><td style="padding:15px;" valign="top">${self.body()}</td></tr>
122 </table>
123 </td>
124 </tr>
125 </table>
126 <!-- End of wrapper table -->
127 <p><a style="margin-top:15px;margin-left:1%;font-family:sans-serif;font-weight:100;font-size:11px;color:#666666;text-decoration:none;" href="${instance_url}">
128 ${self.plaintext_footer()}
129 </a></p>
130 </body>
131 </html>
@@ -1,14 +1,43 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
3 4
4 5 <%def name="subject()" filter="n,trim">
5 ${_('[mention]') if mention else ''} ${_('%(user)s commented on commit of %(repo_name)s') % {
6 'user': h.person(user),
7 'repo_name': repo_name
8 } }
6 <%
7 data = {
8 'user': h.person(user),
9 'repo_name': repo_name,
10 'commit_id': h.show_id(commit),
11 'status': status_change,
12 'comment_file': comment_file,
13 'comment_line': comment_line,
14 }
15 %>
16 ${_('[mention]') if mention else ''} \
17
18 % if comment_file:
19 ${_('%(user)s commented on commit `%(commit_id)s` (file: `%(comment_file)s`)') % data} ${_('in the %(repo_name)s repository') % data |n}
20 % else:
21 % if status_change:
22 ${_('%(user)s commented on commit `%(commit_id)s` (status: %(status)s)') % data |n} ${_('in the %(repo_name)s repository') % data |n}
23 % else:
24 ${_('%(user)s commented on commit `%(commit_id)s`') % data |n} ${_('in the %(repo_name)s repository') % data |n}
25 % endif
26 % endif
27
9 28 </%def>
10 29
11 30 <%def name="body_plaintext()" filter="n,trim">
31 <%
32 data = {
33 'user': h.person(user),
34 'repo_name': repo_name,
35 'commit_id': h.show_id(commit),
36 'status': status_change,
37 'comment_file': comment_file,
38 'comment_line': comment_line,
39 }
40 %>
12 41 ${self.subject()}
13 42
14 43 * ${_('Comment link')}: ${commit_comment_url}
@@ -21,37 +50,39 b''
21 50
22 51 ---
23 52
24 ${comment_body|n}
25
26
27 53 %if status_change:
28 54 ${_('Commit status was changed to')}: *${status_change}*
29 55 %endif
30 56
57 ${comment_body|n}
58
59 ${self.plaintext_footer()}
31 60 </%def>
32 61
33 62
34 % if comment_file:
35 <h4>${_('%(user)s commented on a file in commit of %(repo_url)s.') % {'user': h.person(user), 'repo_url': commit_target_repo} |n}</h4>
36 % else:
37 <h4>${_('%(user)s commented on a commit of %(repo_url)s.') % {'user': h.person(user), 'repo_url': commit_target_repo} |n}</h4>
38 % endif
63 <%
64 data = {
65 'user': h.person(user),
66 'comment_file': comment_file,
67 'comment_line': comment_line,
68 'repo': commit_target_repo,
69 'repo_name': repo_name,
70 'commit_id': h.show_id(commit),
71 }
72 %>
73 <table style="text-align:left;vertical-align:middle;">
74 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;">
75 % if comment_file:
76 <h4><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on commit `%(commit_id)s` (file:`%(comment_file)s`)') % data}</a> ${_('in the %(repo)s repository') % data |n}</h4>
77 % else:
78 <h4><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s commented on commit `%(commit_id)s`') % data |n}</a> ${_('in the %(repo)s repository') % data |n}</h4>
79 % endif
80 </td></tr>
81 <tr><td style="padding-right:20px;padding-top:15px;">${_('Commit')}</td><td style="padding-top:15px;"><a href="${commit_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${h.show_id(commit)}</a></td></tr>
82 <tr><td style="padding-right:20px;">${_('Description')}</td><td style="white-space:pre-wrap">${h.urlify_commit_message(commit.message, repo_name)}</td></tr>
39 83
40 <ul>
41 <li>${_('Comment link')}: <a href="${commit_comment_url}">${commit_comment_url}</a></li>
42 %if comment_file:
43 <li>${_('File: %(comment_file)s on line %(comment_line)s') % {'comment_file': comment_file, 'comment_line': comment_line}}</li>
44 %endif
45 <li>${_('Commit')}: ${h.show_id(commit)}</li>
46 <li>
47 ${_('Commit Description')}: <p>${h.urlify_commit_message(commit.message, repo_name)}</p>
48 </li>
49 </ul>
50
51 <hr>
52 <p>${h.render(comment_body, renderer=renderer_type, mentions=True)}</p>
53 <hr/>
54
55 %if status_change:
56 <p>${_('Commit status was changed to')}: <b>${status_change}</b></p>
57 %endif
84 % if status_change:
85 <tr><td style="padding-right:20px;">${_('Status')}</td><td>${_('The commit status was changed to')}: ${base.status_text(status_change, tag_type=status_change_type)}</td></tr>
86 % endif
87 <tr><td style="padding-right:20px;">${(_('Comment on line: %(comment_line)s') if comment_file else _('Comment')) % data}</td><td style="line-height:1.2em;white-space:pre-wrap">${h.render(comment_body, renderer=renderer_type, mentions=True)}</td></tr>
88 </table>
@@ -8,9 +8,14 b''
8 8 ## plain text version of the email. Empty by default
9 9 <%def name="body_plaintext()" filter="n,trim">
10 10 ${body}
11
12 ${self.plaintext_footer()}
11 13 </%def>
12 14
13 15 ## BODY GOES BELOW
14 <div style="white-space: pre-wrap">
15 ${body_plaintext()}
16 </div> No newline at end of file
16 <table style="text-align:left;vertical-align:top;">
17 <tr><td style="padding-right:20px;padding-top:15px;white-space:pre-wrap">${body}</td></tr>
18 </table
19 <p><a style="margin-top:15px;margin-left:1%;font-family:sans-serif;font-weight:100;font-size:11px;display:block;color:#666666;text-decoration:none;" href="${instance_url}">
20 ${self.plaintext_footer()}
21 </a></p> No newline at end of file
@@ -16,9 +16,16 b' There was a request to reset your passwo'
16 16 You can continue, and generate new password by clicking following URL:
17 17 ${password_reset_url}
18 18
19 ${self.plaintext_footer()}
19 20 </%def>
20 21
21 22 ## BODY GOES BELOW
22 <div style="white-space: pre-wrap">
23 ${body_plaintext()}
24 </div> No newline at end of file
23 <p>
24 Hello ${user.username},
25 </p><p>
26 There was a request to reset your password using the email address ${email} on ${h.format_date(date)}
27 <br/>
28 <strong>If you did not request a password reset, please contact your RhodeCode administrator.</strong>
29 </p><p>
30 <a href="${password_reset_url}">${_('Generate new password here')}.</a>
31 </p>
@@ -9,14 +9,22 b' Your new RhodeCode password'
9 9 <%def name="body_plaintext()" filter="n,trim">
10 10 Hi ${user.username},
11 11
12 Below is your new access password for RhodeCode.
12 There was a request to reset your password using the email address ${email} on ${h.format_date(date)}
13
14 *If you didn't do this, please contact your RhodeCode administrator.*
13 15
14 password: ${new_password}
16 You can continue, and generate new password by clicking following URL:
17 ${password_reset_url}
15 18
16 *If you didn't request a new password, please contact your RhodeCode administrator immediately.*
19 ${self.plaintext_footer()}
17 20 </%def>
18 21
19 22 ## BODY GOES BELOW
20 <div style="white-space: pre-wrap">
21 ${body_plaintext()}
22 </div> No newline at end of file
23 <p>
24 Hello ${user.username},
25 </p><p>
26 Below is your new access password for RhodeCode.
27 <br/>
28 <strong>If you didn't request a new password, please contact your RhodeCode administrator.</strong>
29 </p>
30 <p>password: <input value='${new_password}'/></p>
@@ -1,15 +1,44 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
4
3 5
4 6 <%def name="subject()" filter="n,trim">
5 ${_('[mention]') if mention else ''} ${_('%(user)s commented on pull request #%(pr_id)s: "%(pr_title)s"') % {
6 'user': h.person(user),
7 'pr_title': pull_request.title,
8 'pr_id': pull_request.pull_request_id
9 } |n}
7 <%
8 data = {
9 'user': h.person(user),
10 'pr_title': pull_request.title,
11 'pr_id': pull_request.pull_request_id,
12 'status': status_change,
13 'comment_file': comment_file,
14 'comment_line': comment_line,
15 }
16 %>
17
18 ${_('[mention]') if mention else ''} \
19
20 % if comment_file:
21 ${_('%(user)s commented on pull request #%(pr_id)s "%(pr_title)s" (file: `%(comment_file)s`)') % data |n}
22 % else:
23 % if status_change:
24 ${_('%(user)s commented on pull request #%(pr_id)s "%(pr_title)s" (status: %(status)s)') % data |n}
25 % else:
26 ${_('%(user)s commented on pull request #%(pr_id)s "%(pr_title)s"') % data |n}
27 % endif
28 % endif
10 29 </%def>
11 30
12 31 <%def name="body_plaintext()" filter="n,trim">
32 <%
33 data = {
34 'user': h.person(user),
35 'pr_title': pull_request.title,
36 'pr_id': pull_request.pull_request_id,
37 'status': status_change,
38 'comment_file': comment_file,
39 'comment_line': comment_line,
40 }
41 %>
13 42 ${self.subject()}
14 43
15 44 * ${_('Comment link')}: ${pr_comment_url}
@@ -22,45 +51,48 b''
22 51
23 52 ---
24 53
54 %if status_change and not closing_pr:
55 ${_('%(user)s submitted pull request #%(pr_id)s status: *%(status)s*') % data}
56 %elif status_change and closing_pr:
57 ${_('%(user)s submitted pull request #%(pr_id)s status: *%(status)s and closed*') % data}
58 %endif
59
25 60 ${comment_body|n}
26 61
27
28 %if status_change and not closing_pr:
29 ${_('Pull request status was changed to')}: *${status_change}*
30 %elif status_change and closing_pr:
31 ${_('Pull request was closed with status')}: *${status_change}*
32 %endif
33
62 ${self.plaintext_footer()}
34 63 </%def>
35 64
36 % if comment_file:
37 <h4>${_('%(user)s commented on a file on pull request #%(pr_id)s: "%(pr_title)s".') % {
38 'user': h.person(user),
39 'pr_title': pull_request.title,
40 'pr_id': pull_request.pull_request_id
41 } |n}</h4>
42 % else:
43 <h4>${_('%(user)s commented on a pull request #%(pr_id)s "%(pr_title)s".') % {
44 'user': h.person(user),
45 'pr_title': pull_request.title,
46 'pr_id': pull_request.pull_request_id
47 } |n}</h4>
48 % endif
65
66 <%
67 data = {
68 'user': h.person(user),
69 'pr_title': pull_request.title,
70 'pr_id': pull_request.pull_request_id,
71 'status': status_change,
72 'comment_file': comment_file,
73 'comment_line': comment_line,
74 }
75 %>
76 <table style="text-align:left;vertical-align:middle;">
77 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;">
78 <h4><a href="${pr_comment_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">
49 79
50 <ul>
51 <li>${_('Comment link')}: <a href="${pr_comment_url}">${pr_comment_url}</a></li>
52 <li>${_('Source repository')}: <a href="${pr_source_repo_url}">${pr_source_repo.repo_name}</a></li>
53 %if comment_file:
54 <li>${_('File: %(comment_file)s on line %(comment_line)s') % {'comment_file': comment_file, 'comment_line': comment_line}}</li>
80 % if comment_file:
81 ${_('%(user)s commented on pull request #%(pr_id)s "%(pr_title)s" (file:`%(comment_file)s`)') % data |n}
82 % else:
83 ${_('%(user)s commented on pull request #%(pr_id)s "%(pr_title)s"') % data |n}
84 % endif
85 </a>
86 %if status_change and not closing_pr:
87 , ${_('submitted pull request status: %(status)s') % data}
88 %elif status_change and closing_pr:
89 , ${_('submitted pull request status: %(status)s and closed') % data}
55 90 %endif
56 </ul>
57
58 <hr>
59 <p>${h.render(comment_body, renderer=renderer_type, mentions=True)}</p>
60 <hr/>
61
62 %if status_change and not closing_pr:
63 <p>${_('Pull request status was changed to')}: <b>${status_change}</b></p>
64 %elif status_change and closing_pr:
65 <p>${_('Pull request was closed with status')}: <b>${status_change}</b></p>
66 %endif
91 </h4>
92 </td></tr>
93 <tr><td style="padding-right:20px;padding-top:15px;">${_('Source')}</td><td style="padding-top:15px;"><a style="color:#427cc9;text-decoration:none;cursor:pointer" href="${pr_source_repo_url}">${pr_source_repo.repo_name}</a></td></tr>
94 % if status_change:
95 <tr><td style="padding-right:20px;">${_('Submitted status')}</td><td>${base.status_text(status_change, tag_type=status_change_type)}</td></tr>
96 % endif
97 <tr><td style="padding-right:20px;">${(_('Comment on line: %(comment_line)s') if comment_file else _('Comment')) % data}</td><td style="line-height:1.2em;white-space:pre-wrap">${h.render(comment_body, renderer=renderer_type, mentions=True)}</td></tr>
98 </table>
@@ -1,26 +1,37 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 <%namespace name="base" file="base.mako"/>
3 4
4 5 <%def name="subject()" filter="n,trim">
5 ${_('%(user)s wants you to review pull request #%(pr_url)s: "%(pr_title)s"') % {
6 'user': h.person(user),
7 'pr_title': pull_request.title,
8 'pr_url': pull_request.pull_request_id
9 } |n}
6 <%
7 data = {
8 'user': h.person(user),
9 'pr_id': pull_request.pull_request_id,
10 'pr_title': pull_request.title,
11 }
12 %>
13
14 ${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s"') % data |n}
10 15 </%def>
11 16
12 17
13 18 <%def name="body_plaintext()" filter="n,trim">
19 <%
20 data = {
21 'user': h.person(user),
22 'pr_id': pull_request.pull_request_id,
23 'pr_title': pull_request.title,
24 'source_ref_type': pull_request.source_ref_parts.type,
25 'source_ref_name': pull_request.source_ref_parts.name,
26 'target_ref_type': pull_request.target_ref_parts.type,
27 'target_ref_name': pull_request.target_ref_parts.name,
28 'repo_url': pull_request_source_repo_url
29 }
30 %>
14 31 ${self.subject()}
15 32
16 33
17 ${h.literal(_('Pull request from %(source_ref_type)s:%(source_ref_name)s of %(repo_url)s into %(target_ref_type)s:%(target_ref_name)s') % {
18 'source_ref_type': pull_request.source_ref_parts.type,
19 'source_ref_name': pull_request.source_ref_parts.name,
20 'target_ref_type': pull_request.target_ref_parts.type,
21 'target_ref_name': pull_request.target_ref_parts.name,
22 'repo_url': pull_request_source_repo_url
23 })}
34 ${h.literal(_('Pull request from %(source_ref_type)s:%(source_ref_name)s of %(repo_url)s into %(target_ref_type)s:%(target_ref_name)s') % data)}
24 35
25 36
26 37 * ${_('Link')}: ${pull_request_url}
@@ -29,53 +40,46 b''
29 40
30 41 * ${_('Description')}:
31 42
32 ${pull_request.description}
43 ${pull_request.description}
33 44
34 45
35 46 * ${ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}:
36 47
37 48 % for commit_id, message in pull_request_commits:
38 49 - ${h.short_id(commit_id)}
50 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
39 51
40 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
41 52 % endfor
42 53
54 ${self.plaintext_footer()}
43 55 </%def>
44
45
46 <h4>
47 ${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s".') % {
48 'user': h.person(user),
49 'pr_title': pull_request.title,
50 'pr_id': pull_request.pull_request_id
51 } }
52 </h4>
53
54 <p>${h.literal(_('Pull request from %(source_ref_type)s:%(source_ref_name)s of %(repo_url)s into %(target_ref_type)s:%(target_ref_name)s') % {
55 'source_ref_type': pull_request.source_ref_parts.type,
56 'source_ref_name': pull_request.source_ref_parts.name,
57 'target_ref_type': pull_request.target_ref_parts.type,
58 'target_ref_name': pull_request.target_ref_parts.name,
59 'repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url)
60 })}
61 </p>
62
63 <p>${_('Link')}: ${h.link_to(pull_request_url, pull_request_url)}</p>
64
65 <p><strong>${_('Title')}</strong>: ${pull_request.title}</p>
66 <p>
67 <strong>${_('Description')}:</strong><br/>
68 <span style="white-space: pre-wrap;">${pull_request.description}</span>
69 </p>
70
71 <p>
72 <strong>${ungettext('Commit (%(num)s)', 'Commits (%(num)s)', len(pull_request_commits) ) % {'num': len(pull_request_commits)}}</strong>:
73 <ol>
74 % for commit_id, message in pull_request_commits:
75 <li>
76 <pre>${h.short_id(commit_id)}</pre>
77 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
78 </li>
79 % endfor
80 </ol>
81 </p>
56 <%
57 data = {
58 'user': h.person(user),
59 'pr_id': pull_request.pull_request_id,
60 'pr_title': pull_request.title,
61 'source_ref_type': pull_request.source_ref_parts.type,
62 'source_ref_name': pull_request.source_ref_parts.name,
63 'target_ref_type': pull_request.target_ref_parts.type,
64 'target_ref_name': pull_request.target_ref_parts.name,
65 'repo_url': pull_request_source_repo_url,
66 'source_repo_url': h.link_to(pull_request_source_repo.repo_name, pull_request_source_repo_url),
67 'target_repo_url': h.link_to(pull_request_target_repo.repo_name, pull_request_target_repo_url)
68 }
69 %>
70 <table style="text-align:left;vertical-align:middle;">
71 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${pull_request_url}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('%(user)s wants you to review pull request #%(pr_id)s: "%(pr_title)s".') % data }</a></h4></td></tr>
72 <tr><td style="padding-right:20px;padding-top:15px;">${_('Title')}</td><td style="padding-top:15px;">${pull_request.title}</td></tr>
73 <tr><td style="padding-right:20px;">${_('Source')}</td><td>${base.tag_button(pull_request.source_ref_parts.name)} ${h.literal(_('%(source_ref_type)s of %(source_repo_url)s') % data)}</td></tr>
74 <tr><td style="padding-right:20px;">${_('Target')}</td><td>${base.tag_button(pull_request.target_ref_parts.name)} ${h.literal(_('%(target_ref_type)s of %(target_repo_url)s') % data)}</td></tr>
75 <tr><td style="padding-right:20px;">${_('Description')}</td><td style="white-space:pre-wrap">${pull_request.description}</td></tr>
76 <tr><td style="padding-right:20px;">${ungettext('%(num)s Commit', '%(num)s Commits', len(pull_request_commits)) % {'num': len(pull_request_commits)}}</td>
77 <td><ol style="margin:0 0 0 1em;padding:0;text-align:left;">
78 % for commit_id, message in pull_request_commits:
79 <li style="margin:0 0 1em;"><pre style="margin:0 0 .5em">${h.short_id(commit_id)}</pre>
80 ${h.chop_at_smart(message, '\n', suffix_if_chopped='...')}
81 </li>
82 % endfor
83 </ol></td>
84 </tr>
85 </table>
@@ -2,10 +2,9 b''
2 2 <%inherit file="base.mako"/>
3 3
4 4 <%def name="subject()" filter="n,trim">
5 RhodeCode new user registration
5 RhodeCode new user registration: ${user.username}
6 6 </%def>
7 7
8 ## plain text version of the email. Empty by default
9 8 <%def name="body_plaintext()" filter="n,trim">
10 9
11 10 A new user `${user.username}` has registered on ${h.format_date(date)}
@@ -14,9 +13,15 b' A new user `${user.username}` has regist'
14 13 - Full Name: ${user.firstname} ${user.lastname}
15 14 - Email: ${user.email}
16 15 - Profile link: ${h.url('user_profile', username=user.username, qualified=True)}
16
17 ${self.plaintext_footer()}
17 18 </%def>
18 19
19 20 ## BODY GOES BELOW
20 <div style="white-space: pre-wrap">
21 ${body_plaintext()}
22 </div>
21 <table style="text-align:left;vertical-align:middle;">
22 <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${h.url('user_profile', username=user.username, qualified=True)}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('New user %(user)s has registered on %(date)s') % {'user': user.username, 'date': h.format_date(date)}}</h4></td></tr>
23 <tr><td style="padding-right:20px;padding-top:20px;">${_('Username')}</td><td style="line-height:1;padding-top:20px;"><img style="margin-bottom:-5px;text-align:left;border:1px solid #dbd9da" src="${h.gravatar_url(user.email, 16)}" height="16" width="16">&nbsp;${user.username}</td></tr>
24 <tr><td style="padding-right:20px;">${_('Full Name')}</td><td>${user.firstname} ${user.lastname}</td></tr>
25 <tr><td style="padding-right:20px;">${_('Email')}</td><td>${user.email}</td></tr>
26 <tr><td style="padding-right:20px;">${_('Profile')}</td><td><a href="${h.url('user_profile', username=user.username, qualified=True)}">${h.url('user_profile', username=user.username, qualified=True)}</a></td></tr>
27 </table> No newline at end of file
@@ -5,7 +5,7 b''
5 5 <title>Error - ${c.error_message}</title>
6 6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 7 <meta name="robots" content="index, nofollow"/>
8 <link rel="icon" href="${h.url('/images/favicon.ico')}" sizes="16x16 32x32" type="image/png" />
8 <link rel="icon" href="${h.asset('images/favicon.ico')}" sizes="16x16 32x32" type="image/png" />
9 9
10 10
11 11 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
@@ -13,20 +13,20 b''
13 13 <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
14 14 %endif
15 15
16 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen"/>
16 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
17 17 <!--[if IE]>
18 <link rel="stylesheet" type="text/css" href="${h.url('/css/ie.css')}" media="screen"/>
18 <link rel="stylesheet" type="text/css" href="${h.asset('css/ie.css')}" media="screen"/>
19 19 <![endif]-->
20 20 <style>body { background:#eeeeee; }</style>
21 21
22 <script type="text/javascript" src="${h.url('/js/scripts.js')}"></script>
22 <script type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script>
23 23 </head>
24 24 <body>
25 25 <%include file="/base/flash_msg.html"/>
26 26
27 27 <div class="wrapper error_page">
28 28 <div class="sidebar">
29 <a href="${h.url('home')}"><img class="error-page-logo" src="${h.url('/images/RhodeCode_Logo_Black.png')}" alt="RhodeCode"/></a>
29 <a href="${h.url('home')}"><img class="error-page-logo" src="${h.asset('images/RhodeCode_Logo_Black.png')}" alt="RhodeCode"/></a>
30 30 </div>
31 31 <div class="main-content">
32 32 <h1>
@@ -4,11 +4,11 b''
4 4 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
5 5
6 6 <%def name="js_extra()">
7 <script type="text/javascript" src="${h.url('/js/mergerly.js')}"></script>
7 <script type="text/javascript" src="${h.asset('js/mergerly.js')}"></script>
8 8 </%def>
9 9
10 10 <%def name="css_extra()">
11 <link rel="stylesheet" type="text/css" href="${h.url('/css/mergerly.css')}"/>
11 <link rel="stylesheet" type="text/css" href="${h.asset('css/mergerly.css')}"/>
12 12 </%def>
13 13
14 14 <%def name="title()">
@@ -58,7 +58,7 b''
58 58 </td>
59 59 <td>
60 60 <div class="changes pull-right">${h.fancy_file_stats(c.diff_data['stats'])}</div>
61 <div class="comment-bubble pull-right" data-path="${c.node1.path}">
61 <div class="comment-bubble pull-right" data-path="${h.safe_unicode(c.node1.path)}">
62 62 <i class="icon-comment"></i>
63 63 </div>
64 64 </td>
@@ -12,24 +12,18 b''
12 12 </div>
13 13
14 14 % if c.authors:
15 <ul class="pull-left">
15 <ul class="sidebar-right-content">
16 16 % for email, user in sorted(c.authors, key=lambda e: c.file_last_commit.author_email!=e[0]):
17 17 <li class="file_author">
18 <div class="contributor tooltip" title="${h.tooltip(user)}">
19 ${base.gravatar(email, 16)}
20 <span class="author user">
21 ## case initial page load we only have last commit author
22 % if c.file_author:
23 ${h.link_to_user(user)} - ${h.age_component(c.file_last_commit.date)}
24 % else:
25 % if c.file_last_commit.author_email==email:
26 <strong>${h.link_to_user(user)}</strong> (${_('last author')})
27 % else:
28 ${h.link_to_user(user)}
29 % endif
30 % endif
31 </span>
32 </div>
18 <div class="rc-user tooltip" title="${h.author_string(email)}">
19 ${base.gravatar(email, 16)}
20 <span class="user">${h.link_to_user(user)}</span>
21 </div>
22 % if c.file_author:
23 <div class="user-inline-data">- ${h.age_component(c.file_last_commit.date)}</div>
24 % elif c.file_last_commit.author_email==email:
25 <div class="user-inline-data"> (${_('last author')})</div>
26 % endif
33 27 </li>
34 28 % endfor
35 29 </ul>
@@ -6,9 +6,7 b''
6 6 </h4>
7 7 </div>
8 8 <div class="sidebar-right-content">
9 ${base.gravatar(h.email_or_none(c.commit.author), 16)}
10 <span class="author user">
11 ${h.link_to_user(c.commit.author)} - ${h.age_component(c.commit.date)}
12 </span>
9 ${base.gravatar_with_user(c.commit.author)}
10 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
13 11 </div>
14 12
@@ -91,37 +91,26 b''
91 91 if (source_page) {
92 92 return false;
93 93 }
94
95 if ($('#file-tree-wrapper').hasClass('full-load')) {
96 // in case our HTML wrapper has full-load class we don't
97 // trigger the async load of metadata
98 return false;
99 }
100
94 101 var state = getState('metadata');
95 102 var url_data = {
96 103 'repo_name': templateContext.repo_name,
97 'revision': state.commit_id,
104 'commit_id': state.commit_id,
98 105 'f_path': state.f_path
99 106 };
100 107
101 var url = pyroutes.url('files_metadata_list_home', url_data);
108 var url = pyroutes.url('files_nodetree_full', url_data);
102 109
103 110 metadataRequest = $.ajax({url: url});
104 111
105 112 metadataRequest.done(function(data) {
106 var data = data.metadata;
107 var dataLength = data.length;
108 for (var i = 0; i < dataLength; i++) {
109 var rowData = data[i];
110 var name = rowData.name.replace('\\', '\\\\');
111
112 $('td[title="size-' + name + '"]').html(rowData.size);
113 var timeComponent = AgeModule.createTimeComponent(
114 rowData.modified_ts, rowData.modified_at);
115 $('td[title="modified_at-' + name + '"]').html(timeComponent);
116
117 $('td[title="revision-' + name + '"]').html(
118 '<div class="tooltip" title="{0}"><pre>r{1}:{2}</pre></div>'.format(
119 data[i].message, data[i].revision, data[i].short_id));
120 $('td[title="author-' + name + '"]').html(
121 '<span title="{0}">{1}</span>'.format(
122 data[i].author, data[i].user_profile));
123 }
124 tooltip_activate();
113 $('#file-tree').html(data);
125 114 timeagoActivate();
126 115 });
127 116 metadataRequest.fail(function (data, textStatus, errorThrown) {
@@ -134,13 +123,12 b''
134 123
135 124 var callbacks = function() {
136 125 var state = getState('callbacks');
137 tooltip_activate();
138 126 timeagoActivate();
139 127
140 128 // used for history, and switch to
141 129 var initialCommitData = {
142 130 id: null,
143 text: "${_("Switch To Commit")}",
131 text: '${_("Switch To Commit")}',
144 132 type: 'sha',
145 133 raw_id: null,
146 134 files_url: null
@@ -316,7 +304,6 b''
316 304 $('#file_history_overview').hide();
317 305 $('#file_history_overview_full').show();
318 306 timeagoActivate();
319 tooltip_activate();
320 307 } else {
321 308 callbacks();
322 309 }
@@ -332,5 +319,4 b''
332 319
333 320 </script>
334 321
335
336 322 </%def>
@@ -41,67 +41,11 b''
41 41 </div>
42 42 </div>
43 43 </div>
44
45 <div class="browser-body">
46 <table class="code-browser rctable">
47 <thead>
48 <tr>
49 <th>${_('Name')}</th>
50 <th>${_('Size')}</th>
51 <th>${_('Modified')}</th>
52 <th>${_('Last Commit')}</th>
53 <th>${_('Author')}</th>
54 </tr>
55 </thead>
44 ## file tree is computed from caches, and filled in
45 <div id="file-tree">
46 ${c.file_tree}
47 </div>
56 48
57 <tbody id="tbody">
58 %if c.file.parent:
59 <tr class="parity0">
60 <td class="td-componentname">
61 <a href='${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.file.parent.path)}' class="pjax-link">
62 <i class="icon-folder"></i>..
63 </a>
64 </td>
65 <td></td>
66 <td></td>
67 <td></td>
68 <td></td>
69 </tr>
70 %endif
71 %for cnt,node in enumerate(c.file):
72 <tr class="parity${cnt%2}">
73 <td class="td-componentname">
74 %if node.is_submodule():
75 <span class="submodule-dir">
76 ${h.link_to_if(
77 node.url.startswith('http://') or node.url.startswith('https://'),
78 node.name,node.url)}
79 </span>
80 %else:
81 <a href='${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=h.safe_unicode(node.path))}' class="pjax-link">
82 <i class="${"icon-file browser-file" if node.is_file() else "icon-folder browser-dir"}"></i>${node.name}
83 </a>
84 %endif
85 </td>
86 %if node.is_file():
87 <td class="td-size" title="${'size-%s' % node.name}"></td>
88 <td class="td-time" title="${'modified_at-%s' % node.name}">
89 <span class="browser-loading">${_('Loading...')}</span>
90 </td>
91 <td class="td-hash" title="${'revision-%s' % node.name}"></td>
92 <td class="td-user" title="${'author-%s' % node.name}"></td>
93 %else:
94 <td></td>
95 <td></td>
96 <td></td>
97 <td></td>
98 %endif
99 </tr>
100 %endfor
101 </tbody>
102 <tbody id="tbody_filtered"></tbody>
103 </table>
104 </div>
105 49 </div>
106 50
107 51 <script>
@@ -40,7 +40,7 b''
40 40 <%include file='file_tree_author_box.html'/>
41 41 </div>
42 42
43 ${c.file_tree}
43 <%include file='files_browser.html'/>
44 44 % else:
45 45 <div id="file_authors" class="sidebar-right">
46 46 <%include file='file_authors_box.html'/>
@@ -23,8 +23,6 b''
23 23 $(document).on('pjax:success',function(){
24 24 show_more_event();
25 25 timeagoActivate();
26 tooltip_activate();
27 show_changeset_tooltip();
28 26 });
29 27 </script>
30 28 ${c.followers_pager.pager('$link_previous ~2~ $link_next')}
@@ -38,8 +38,6 b''
38 38 $(document).on('pjax:success',function(){
39 39 show_more_event();
40 40 timeagoActivate();
41 tooltip_activate();
42 show_changeset_tooltip();
43 41 });
44 42 </script>
45 43 ${c.forks_pager.pager('$link_previous ~2~ $link_next')}
@@ -102,11 +102,11 b''
102 102 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
103 103 ],
104 104 language: {
105 paginate: DEFAULT_GRID_PAGINATION
105 paginate: DEFAULT_GRID_PAGINATION,
106 emptyTable: _gettext("No repository groups available yet.")
106 107 },
107 108 "drawCallback": function( settings, json ) {
108 109 timeagoActivate();
109 tooltip_activate();
110 110 quick_repo_menu();
111 111 }
112 112 });
@@ -135,11 +135,11 b''
135 135 "sort": "rss"}, title: "rss", className: "td-rss" }
136 136 ],
137 137 language: {
138 paginate: DEFAULT_GRID_PAGINATION
138 paginate: DEFAULT_GRID_PAGINATION,
139 emptyTable: _gettext("No repositories available yet.")
139 140 },
140 141 "drawCallback": function( settings, json ) {
141 142 timeagoActivate();
142 tooltip_activate();
143 143 quick_repo_menu();
144 144 }
145 145 });
@@ -46,8 +46,6 b''
46 46 $('#j_filter').autoGrowInput();
47 47 $(document).on('pjax:success',function(){
48 48 show_more_event();
49 tooltip_activate();
50 show_changeset_tooltip();
51 49 });
52 50 $(document).pjax('#refresh', '#journal',
53 51 {url: "${h.url.current(filter=c.search_term)}", push: false});
@@ -44,8 +44,6 b''
44 44 $(document).on('pjax:success',function(){
45 45 show_more_event();
46 46 timeagoActivate();
47 tooltip_activate();
48 show_changeset_tooltip();
49 47 });
50 48 </script>
51 49 %else:
@@ -14,7 +14,7 b''
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
@@ -25,7 +25,7 b''
25 25
26 26 <div class="loginwrapper">
27 27 <div class="left-column">
28 <img class="sign-in-image" src="${h.url('/images/sign-in.png')}" alt="RhodeCode"/>
28 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
29 29 </div>
30 30 <%block name="above_login_button" />
31 31 <div id="login" class="right-column">
@@ -59,7 +59,7 b''
59 59
60 60 <p class="links">
61 61 ${h.link_to(_('Forgot your password?'), h.route_path('reset_password'))}
62 </p>
62 </p>
63 63
64 64 ${h.submit('sign_in', _('Sign In'), class_="btn sign-in")}
65 65
@@ -14,7 +14,7 b''
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
@@ -25,9 +25,9 b''
25 25
26 26 <div class="loginwrapper">
27 27 <div class="left-column">
28 <img class="sign-in-image" src="${h.url('/images/sign-in.png')}" alt="RhodeCode"/>
28 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
29 29 </div>
30
30
31 31 <div id="register" class="right-column">
32 32 <%include file="/base/flash_msg.html"/>
33 33 <!-- login -->
@@ -57,7 +57,7 b''
57 57 %endif
58 58
59 59 ${h.submit('send', _('Send password reset email'), class_="btn sign-in")}
60 <div class="activation_msg">${_('Password reset link will be send to matching email address')}</div>
60 <div class="activation_msg">${_('Password reset link will be sent to matching email address')}</div>
61 61
62 62 ${h.end_form()}
63 63 </div>
@@ -311,6 +311,10 b''
311 311 %endif
312 312 </td>
313 313 <td class="td-actions rc-form">
314 <div data-comment-id="${FID}" class="btn-link show-inline-comments comments-visible">
315 <span class="comments-show">${_('Show comments')}</span>
316 <span class="comments-hide">${_('Hide comments')}</span>
317 </div>
314 318 </td>
315 319 </tr>
316 320 <tr id="tr_${FID}">
@@ -559,6 +563,23 b''
559 563 $('#close_pull_request').on('click', function(e){
560 564 closePullRequest("${c.repo_name}", "${c.pull_request.pull_request_id}");
561 565 });
566
567 $('.show-inline-comments').on('click', function(e){
568 var boxid = $(this).attr('data-comment-id');
569 var button = $(this);
570
571 if(button.hasClass("comments-visible")) {
572 $('#{0} .inline-comments'.format(boxid)).each(function(index){
573 $(this).hide();
574 })
575 button.removeClass("comments-visible");
576 } else {
577 $('#{0} .inline-comments'.format(boxid)).each(function(index){
578 $(this).show();
579 })
580 button.addClass("comments-visible");
581 }
582 });
562 583 })
563 584 </script>
564 585
@@ -108,11 +108,11 b''
108 108 "sort": "updated_on_raw"}, title: "${_('Updated on')}", className: "td-time" }
109 109 ],
110 110 language: {
111 paginate: DEFAULT_GRID_PAGINATION
111 paginate: DEFAULT_GRID_PAGINATION,
112 emptyTable: _gettext("No pull requests available yet.")
112 113 },
113 114 "drawCallback": function( settings, json ) {
114 115 timeagoActivate();
115 tooltip_activate();
116 116 },
117 117 "createdRow": function ( row, data, index ) {
118 118 if (data['closed']) {
@@ -14,7 +14,7 b''
14 14 <div id="header-inner" class="title">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
17 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 %if c.rhodecode_name:
20 20 <div class="branding"> ${h.branding(c.rhodecode_name)}</div>
@@ -25,7 +25,7 b''
25 25
26 26 <div class="loginwrapper">
27 27 <div class="left-column">
28 <img class="sign-in-image" src="${h.url('/images/sign-in.png')}" alt="RhodeCode"/>
28 <img class="sign-in-image" src="${h.asset('images/sign-in.png')}" alt="RhodeCode"/>
29 29 </div>
30 30 <%block name="above_register_button" />
31 31 <div id="register" class="right-column">
@@ -50,8 +50,8 b''
50 50 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
51 51 </h3>
52 52 </div>
53 <div class="readme">
54 <div class="readme_box">
53 <div class="readme codeblock">
54 <div class="readme_box markdown-block">
55 55 ${c.readme_data|n}
56 56 </div>
57 57 </div>
@@ -68,11 +68,11 b''
68 68 "sort": "compare"}, title: "${_('Compare')}", className: "td-compare" }
69 69 ],
70 70 language: {
71 paginate: DEFAULT_GRID_PAGINATION
71 paginate: DEFAULT_GRID_PAGINATION,
72 emptyTable: _gettext("No tags available yet.")
72 73 },
73 74 "initComplete": function(settings, json) {
74 75 get_datatable_count();
75 tooltip_activate();
76 76 timeagoActivate();
77 77 compare_radio_buttons("${c.repo_name}", 'tag');
78 78 }
@@ -81,7 +81,6 b''
81 81 // update when things change
82 82 $('#obj_list_table').on('draw.dt', function() {
83 83 get_datatable_count();
84 tooltip_activate();
85 84 timeagoActivate();
86 85 });
87 86
@@ -25,68 +25,6 b' import pytest'
25 25 from rhodecode.config import environment
26 26
27 27
28 class TestUseDirectHookCalls(object):
29 @pytest.mark.parametrize('config', [
30 {
31 'vcs.hooks.direct_calls': 'true',
32 'base_path': 'fake_base_path'
33 }
34 ])
35 def test_returns_true_when_conditions_are_met(self, config):
36 result = environment._use_direct_hook_calls(config)
37 assert result is True
38
39 @pytest.mark.parametrize('config', [
40 {
41 'vcs.hooks.direct_calls': 'false',
42 'base_path': 'fake_base_path'
43 },
44 {
45 'base_path': 'fake_base_path'
46 }
47 ])
48 def test_returns_false_when_conditions_are_not_met(self, config):
49 result = environment._use_direct_hook_calls(config)
50 assert result is False
51
52
53 class TestGetVcsHooksProtocol(object):
54 def test_returns_pyro4_by_default(self):
55 config = {}
56 result = environment._get_vcs_hooks_protocol(config)
57 assert result == 'pyro4'
58
59 @pytest.mark.parametrize('protocol', ['PYRO4', 'HTTP', 'Pyro4', 'Http'])
60 def test_returns_lower_case_value(self, protocol):
61 config = {
62 'vcs.hooks.protocol': protocol
63 }
64 result = environment._get_vcs_hooks_protocol(config)
65 assert result == protocol.lower()
66
67
68 class TestLoadEnvironment(object):
69 def test_calls_use_direct_hook_calls(self, _external_calls_patcher):
70 global_conf = {
71 'here': '',
72 'vcs.connection_timeout': '0',
73 'vcs.server.enable': 'false'
74 }
75 app_conf = {
76 'cache_dir': '/tmp/',
77 '__file__': '/tmp/abcde.ini'
78 }
79 direct_calls_patcher = mock.patch.object(
80 environment, '_use_direct_hook_calls', return_value=True)
81 protocol_patcher = mock.patch.object(
82 environment, '_get_vcs_hooks_protocol', return_value='http')
83 with direct_calls_patcher as direct_calls_mock, \
84 protocol_patcher as protocol_mock:
85 environment.load_environment(global_conf, app_conf)
86 direct_calls_mock.call_count == 1
87 protocol_mock.call_count == 1
88
89
90 28 @pytest.fixture
91 29 def _external_calls_patcher(request):
92 30 # TODO: mikhail: This is a temporary solution. Ideally load_environment
@@ -47,10 +47,10 b' class TestAuthSettingsController(object)'
47 47 for plugin in plugins_list.split(','):
48 48 plugin_name = plugin.partition('#')[-1]
49 49 enabled_plugin = '%s_enabled' % plugin_name
50 cache_ttl = '%s_auth_cache_ttl' % plugin_name
50 cache_ttl = '%s_cache_ttl' % plugin_name
51 51
52 52 # default params that are needed for each plugin,
53 # `enabled` and `auth_cache_ttl`
53 # `enabled` and `cache_ttl`
54 54 params.update({
55 55 enabled_plugin: True,
56 56 cache_ttl: 0
@@ -67,7 +67,7 b' class TestAuthSettingsController(object)'
67 67 for _plugin in _enabled_plugins:
68 68 db_plugin = SettingsModel().get_setting_by_name(_plugin)
69 69 if db_plugin:
70 Session.delete(db_plugin)
70 Session().delete(db_plugin)
71 71 Session().commit()
72 72
73 73 response = self.app.post(url=test_url, params=params)
@@ -97,7 +97,7 b' class TestAuthSettingsController(object)'
97 97 'attr_firstname': 'ima',
98 98 'attr_lastname': 'tester',
99 99 'attr_email': 'test@example.com',
100 'auth_cache_ttl': '0',
100 'cache_ttl': '0',
101 101 })
102 102 if force:
103 103 params = {}
@@ -113,8 +113,7 b' class TestAuthSettingsController(object)'
113 113 response.mustcontain('Authentication Plugins')
114 114
115 115 @pytest.mark.parametrize("disable_plugin, needs_import", [
116 pytest.mark.xfail(('egg:rhodecode-enterprise-ce#container', None),
117 reason="Migration of container plugin pending."),
116 ('egg:rhodecode-enterprise-ce#headers', None),
118 117 ('egg:rhodecode-enterprise-ce#crowd', None),
119 118 ('egg:rhodecode-enterprise-ce#jasig_cas', None),
120 119 ('egg:rhodecode-enterprise-ce#ldap', None),
@@ -191,7 +190,7 b' class TestAuthSettingsController(object)'
191 190 'attr_firstname': 'ima',
192 191 'attr_lastname': 'tester',
193 192 'attr_email': 'test@example.com',
194 'auth_cache_ttl': '60',
193 'cache_ttl': '60',
195 194 'csrf_token': csrf_token,
196 195 }
197 196 )
@@ -129,40 +129,33 b' class TestGistsController(TestController'
129 129 for gist in GistModel.get_all():
130 130 response.mustcontain(no=['gist: %s' % gist.gist_access_id])
131 131
132 def test_create_missing_description(self):
132 def test_create(self):
133 133 self.log_user()
134 134 response = self.app.post(
135 135 url('gists'),
136 params={'lifetime': -1, 'csrf_token': self.csrf_token},
137 status=200)
138
139 response.mustcontain('Missing value')
140
141 def test_create(self):
142 self.log_user()
143 response = self.app.post(url('gists'),
144 params={'lifetime': -1,
145 'content': 'gist test',
146 'filename': 'foo',
147 'public': 'public',
148 'acl_level': Gist.ACL_LEVEL_PUBLIC,
149 'csrf_token': self.csrf_token},
150 status=302)
136 params={'lifetime': -1,
137 'content': 'gist test',
138 'filename': 'foo',
139 'public': 'public',
140 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
141 'csrf_token': self.csrf_token},
142 status=302)
151 143 response = response.follow()
152 144 response.mustcontain('added file: foo')
153 145 response.mustcontain('gist test')
154 146
155 147 def test_create_with_path_with_dirs(self):
156 148 self.log_user()
157 response = self.app.post(url('gists'),
158 params={'lifetime': -1,
159 'content': 'gist test',
160 'filename': '/home/foo',
161 'public': 'public',
162 'acl_level': Gist.ACL_LEVEL_PUBLIC,
163 'csrf_token': self.csrf_token},
164 status=200)
165 response.mustcontain('Filename cannot be inside a directory')
149 response = self.app.post(
150 url('gists'),
151 params={'lifetime': -1,
152 'content': 'gist test',
153 'filename': '/home/foo',
154 'public': 'public',
155 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
156 'csrf_token': self.csrf_token},
157 status=200)
158 response.mustcontain('Filename /home/foo cannot be inside a directory')
166 159
167 160 def test_access_expired_gist(self, create_gist):
168 161 self.log_user()
@@ -175,14 +168,15 b' class TestGistsController(TestController'
175 168
176 169 def test_create_private(self):
177 170 self.log_user()
178 response = self.app.post(url('gists'),
179 params={'lifetime': -1,
180 'content': 'private gist test',
181 'filename': 'private-foo',
182 'private': 'private',
183 'acl_level': Gist.ACL_LEVEL_PUBLIC,
184 'csrf_token': self.csrf_token},
185 status=302)
171 response = self.app.post(
172 url('gists'),
173 params={'lifetime': -1,
174 'content': 'private gist test',
175 'filename': 'private-foo',
176 'private': 'private',
177 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
178 'csrf_token': self.csrf_token},
179 status=302)
186 180 response = response.follow()
187 181 response.mustcontain('added file: private-foo<')
188 182 response.mustcontain('private gist test')
@@ -193,14 +187,15 b' class TestGistsController(TestController'
193 187
194 188 def test_create_private_acl_private(self):
195 189 self.log_user()
196 response = self.app.post(url('gists'),
197 params={'lifetime': -1,
198 'content': 'private gist test',
199 'filename': 'private-foo',
200 'private': 'private',
201 'acl_level': Gist.ACL_LEVEL_PRIVATE,
202 'csrf_token': self.csrf_token},
203 status=302)
190 response = self.app.post(
191 url('gists'),
192 params={'lifetime': -1,
193 'content': 'private gist test',
194 'filename': 'private-foo',
195 'private': 'private',
196 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE,
197 'csrf_token': self.csrf_token},
198 status=302)
204 199 response = response.follow()
205 200 response.mustcontain('added file: private-foo<')
206 201 response.mustcontain('private gist test')
@@ -211,15 +206,16 b' class TestGistsController(TestController'
211 206
212 207 def test_create_with_description(self):
213 208 self.log_user()
214 response = self.app.post(url('gists'),
215 params={'lifetime': -1,
216 'content': 'gist test',
217 'filename': 'foo-desc',
218 'description': 'gist-desc',
219 'public': 'public',
220 'acl_level': Gist.ACL_LEVEL_PUBLIC,
221 'csrf_token': self.csrf_token},
222 status=302)
209 response = self.app.post(
210 url('gists'),
211 params={'lifetime': -1,
212 'content': 'gist test',
213 'filename': 'foo-desc',
214 'description': 'gist-desc',
215 'public': 'public',
216 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
217 'csrf_token': self.csrf_token},
218 status=302)
223 219 response = response.follow()
224 220 response.mustcontain('added file: foo-desc')
225 221 response.mustcontain('gist test')
@@ -233,7 +229,7 b' class TestGistsController(TestController'
233 229 'filename': 'foo-desc',
234 230 'description': 'gist-desc',
235 231 'public': 'public',
236 'acl_level': Gist.ACL_LEVEL_PUBLIC,
232 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
237 233 'csrf_token': self.csrf_token
238 234 }
239 235 response = self.app.post(url('gists'), params=params, status=302)
@@ -18,9 +18,13 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import mock
21 22 import pytest
22 23
24 import rhodecode
25 from rhodecode.model.settings import SettingsModel
23 26 from rhodecode.tests import url, HG_REPO
27 from rhodecode.tests.utils import AssertResponse
24 28
25 29
26 30 @pytest.mark.usefixtures('autologin_user', 'app')
@@ -38,3 +42,77 b' class TestAdminRepoSettingsController:'
38 42 ])
39 43 def test_simple_get(self, urlname, app):
40 44 app.get(url(urlname, repo_name=HG_REPO))
45
46 @pytest.mark.parametrize('setting_name, setting_backends', [
47 ('hg_use_rebase_for_merging', ['hg']),
48 ])
49 def test_labs_settings_visible_if_enabled(
50 self, setting_name, setting_backends, backend):
51 if backend.alias not in setting_backends:
52 pytest.skip('Setting not available for backend {}'.format(backend))
53
54 vcs_settings_url = url(
55 'repo_vcs_settings', repo_name=backend.repo.repo_name)
56
57 with mock.patch.dict(
58 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
59 response = self.app.get(vcs_settings_url)
60
61 assertr = AssertResponse(response)
62 assertr.one_element_exists('#rhodecode_{}'.format(setting_name))
63
64 @pytest.mark.parametrize('setting_name, setting_backends', [
65 ('hg_use_rebase_for_merging', ['hg']),
66 ])
67 def test_labs_settings_not_visible_if_disabled(
68 self, setting_name, setting_backends, backend):
69 if backend.alias not in setting_backends:
70 pytest.skip('Setting not available for backend {}'.format(backend))
71
72 vcs_settings_url = url(
73 'repo_vcs_settings', repo_name=backend.repo.repo_name)
74
75 with mock.patch.dict(
76 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
77 response = self.app.get(vcs_settings_url)
78
79 assertr = AssertResponse(response)
80 assertr.no_element_exists('#rhodecode_{}'.format(setting_name))
81
82 @pytest.mark.parametrize('setting_name, setting_backends', [
83 ('hg_use_rebase_for_merging', ['hg']),
84 ])
85 def test_update_boolean_settings(
86 self, csrf_token, setting_name, setting_backends, backend):
87 if backend.alias not in setting_backends:
88 pytest.skip('Setting not available for backend {}'.format(backend))
89
90 repo = backend.create_repo()
91
92 settings_model = SettingsModel(repo=repo)
93 vcs_settings_url = url(
94 'repo_vcs_settings', repo_name=repo.repo_name)
95
96 self.app.post(
97 vcs_settings_url,
98 params={
99 'inherit_global_settings': False,
100 'new_svn_branch': 'dummy-value-for-testing',
101 'new_svn_tag': 'dummy-value-for-testing',
102 'rhodecode_{}'.format(setting_name): 'true',
103 'csrf_token': csrf_token,
104 })
105 setting = settings_model.get_setting_by_name(setting_name)
106 assert setting.app_settings_value
107
108 self.app.post(
109 vcs_settings_url,
110 params={
111 'inherit_global_settings': False,
112 'new_svn_branch': 'dummy-value-for-testing',
113 'new_svn_tag': 'dummy-value-for-testing',
114 'rhodecode_{}'.format(setting_name): 'false',
115 'csrf_token': csrf_token,
116 })
117 setting = settings_model.get_setting_by_name(setting_name)
118 assert not setting.app_settings_value
@@ -18,14 +18,12 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 import os
22 21 import urllib
23 22
24 23 import mock
25 24 import pytest
26 25
27 26 from rhodecode.lib import auth
28 from rhodecode.lib import vcs
29 27 from rhodecode.lib.utils2 import safe_str, str2bool
30 28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
31 29 from rhodecode.model.db import Repository, RepoGroup, UserRepoToPerm, User,\
@@ -38,9 +36,9 b' from rhodecode.model.user import UserMod'
38 36 from rhodecode.tests import (
39 37 login_user_session, url, assert_session_flash, TEST_USER_ADMIN_LOGIN,
40 38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, HG_REPO, GIT_REPO,
41 TESTS_TMP_PATH, logout_user_session)
39 logout_user_session)
42 40 from rhodecode.tests.fixture import Fixture, error_function
43 from rhodecode.tests.utils import AssertResponse
41 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
44 42
45 43 fixture = Fixture()
46 44
@@ -1243,14 +1241,6 b' class TestVcsSettings(object):'
1243 1241 assert repo_element.value == global_element.value
1244 1242
1245 1243
1246 def repo_on_filesystem(repo_name):
1247 try:
1248 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
1249 return True
1250 except Exception:
1251 return False
1252
1253
1254 1244 def _get_permission_for_user(user, repo):
1255 1245 perm = UserRepoToPerm.query()\
1256 1246 .filter(UserRepoToPerm.repository ==
@@ -343,6 +343,38 b' class TestAdminSettingsVcs:'
343 343 setting = SettingsModel().get_setting_by_name(setting_key)
344 344 assert setting.app_settings_value is new_value
345 345
346 def test_has_a_section_for_labs_settings_if_enabled(self, app):
347 with mock.patch.dict(
348 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
349 response = self.app.get(url('admin_settings_vcs'))
350 response.mustcontain('Labs settings:')
351
352 def test_has_not_a_section_for_labs_settings_if_disables(self, app):
353 with mock.patch.dict(
354 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
355 response = self.app.get(url('admin_settings_vcs'))
356 response.mustcontain(no='Labs settings:')
357
358 @pytest.mark.parametrize('new_value', [True, False])
359 def test_allows_to_change_hg_rebase_merge_strategy(
360 self, app, form_defaults, csrf_token, new_value):
361 setting_key = 'hg_use_rebase_for_merging'
362
363 form_defaults.update({
364 'csrf_token': csrf_token,
365 'rhodecode_' + setting_key: str(new_value),
366 })
367
368 with mock.patch.dict(
369 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
370 app.post(
371 url('admin_settings_vcs'),
372 params=form_defaults,
373 status=302)
374
375 setting = SettingsModel().get_setting_by_name(setting_key)
376 assert setting.app_settings_value is new_value
377
346 378 @pytest.fixture
347 379 def disable_sql_cache(self, request):
348 380 patcher = mock.patch(
@@ -414,7 +446,6 b' class TestLabsSettings(object):'
414 446
415 447 @pytest.mark.parametrize('setting_name', [
416 448 'proxy_subversion_http_requests',
417 'hg_use_rebase_for_merging',
418 449 ])
419 450 def test_update_boolean_settings(self, csrf_token, setting_name):
420 451 self.app.post(
@@ -25,10 +25,11 b' from rhodecode.tests import *'
25 25 from rhodecode.model.db import (
26 26 ChangesetComment, Notification, UserNotification)
27 27 from rhodecode.model.meta import Session
28 from rhodecode.lib import helpers as h
28 29
29 30
30 31 @pytest.mark.backends("git", "hg", "svn")
31 class TestChangeSetCommentsController(TestController):
32 class TestCommitCommentsController(TestController):
32 33
33 34 @pytest.fixture(autouse=True)
34 35 def prepare(self, request, pylonsapp):
@@ -53,7 +54,8 b' class TestChangeSetCommentsController(Te'
53 54
54 55 def test_create(self, backend):
55 56 self.log_user()
56 commit_id = backend.repo.get_commit('300').raw_id
57 commit = backend.repo.get_commit('300')
58 commit_id = commit.raw_id
57 59 text = u'CommentOnCommit'
58 60
59 61 params = {'text': text, 'csrf_token': self.csrf_token}
@@ -77,7 +79,8 b' class TestChangeSetCommentsController(Te'
77 79 comment_id = ChangesetComment.query().first().comment_id
78 80 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
79 81
80 sbj = 'commented on commit of {0}'.format(backend.repo_name)
82 sbj = 'commented on commit `{0}` in the {1} repository'.format(
83 h.show_id(commit), backend.repo_name)
81 84 assert sbj in notification.subject
82 85
83 86 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
@@ -86,7 +89,8 b' class TestChangeSetCommentsController(Te'
86 89
87 90 def test_create_inline(self, backend):
88 91 self.log_user()
89 commit_id = backend.repo.get_commit('300').raw_id
92 commit = backend.repo.get_commit('300')
93 commit_id = commit.raw_id
90 94 text = u'CommentOnCommit'
91 95 f_path = 'vcs/web/simplevcs/views/repository.py'
92 96 line = 'n1'
@@ -119,8 +123,10 b' class TestChangeSetCommentsController(Te'
119 123 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
120 124
121 125 assert comment.revision == commit_id
122
123 sbj = 'commented on commit of {0}'.format(backend.repo_name)
126 sbj = 'commented on commit `{commit}` ' \
127 '(file: `{f_path}`) in the {repo} repository'.format(
128 commit=h.show_id(commit),
129 f_path=f_path, line=line, repo=backend.repo_name)
124 130 assert sbj in notification.subject
125 131
126 132 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
@@ -154,6 +160,46 b' class TestChangeSetCommentsController(Te'
154 160 # test_regular gets notification by @mention
155 161 assert sorted(users) == [u'test_admin', u'test_regular']
156 162
163 def test_create_with_status_change(self, backend):
164 self.log_user()
165 commit = backend.repo.get_commit('300')
166 commit_id = commit.raw_id
167 text = u'CommentOnCommit'
168 f_path = 'vcs/web/simplevcs/views/repository.py'
169 line = 'n1'
170
171 params = {'text': text, 'changeset_status': 'approved',
172 'csrf_token': self.csrf_token}
173
174 self.app.post(
175 url(controller='changeset', action='comment',
176 repo_name=backend.repo_name, revision=commit_id), params=params)
177
178 response = self.app.get(
179 url(controller='changeset', action='index',
180 repo_name=backend.repo_name, revision=commit_id))
181
182 # test DB
183 assert ChangesetComment.query().count() == 1
184 assert_comment_links(response, ChangesetComment.query().count(), 0)
185
186 assert Notification.query().count() == 1
187 assert ChangesetComment.query().count() == 1
188
189 notification = Notification.query().all()[0]
190
191 comment_id = ChangesetComment.query().first().comment_id
192 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
193
194 sbj = 'commented on commit `{0}` (status: Approved) ' \
195 'in the {1} repository'.format(
196 h.show_id(commit), backend.repo_name)
197 assert sbj in notification.subject
198
199 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
200 backend.repo_name, commit_id, comment_id))
201 assert lnk in notification.body
202
157 203 def test_delete(self, backend):
158 204 self.log_user()
159 205 commit_id = backend.repo.get_commit('300').raw_id
@@ -28,9 +28,13 b' from rhodecode.lib import helpers as h'
28 28 from rhodecode.lib.compat import OrderedDict
29 29 from rhodecode.lib.ext_json import json
30 30 from rhodecode.lib.vcs import nodes
31 from rhodecode.lib.vcs.backends.base import EmptyCommit
31 32 from rhodecode.lib.vcs.conf import settings
33 from rhodecode.lib.vcs.nodes import FileNode
34 from rhodecode.model.db import Repository
35 from rhodecode.model.scm import ScmModel
32 36 from rhodecode.tests import (
33 url, assert_session_flash, assert_not_in_session_flash)
37 url, TEST_USER_ADMIN_LOGIN, assert_session_flash, assert_not_in_session_flash)
34 38 from rhodecode.tests.fixture import Fixture
35 39 from rhodecode.tests.utils import AssertResponse
36 40
@@ -43,6 +47,41 b' NODE_HISTORY = {'
43 47 }
44 48
45 49
50
51 def _commit_change(
52 repo, filename, content, message, vcs_type, parent=None,
53 newfile=False):
54 repo = Repository.get_by_repo_name(repo)
55 _commit = parent
56 if not parent:
57 _commit = EmptyCommit(alias=vcs_type)
58
59 if newfile:
60 nodes = {
61 filename: {
62 'content': content
63 }
64 }
65 commit = ScmModel().create_nodes(
66 user=TEST_USER_ADMIN_LOGIN, repo=repo,
67 message=message,
68 nodes=nodes,
69 parent_commit=_commit,
70 author=TEST_USER_ADMIN_LOGIN,
71 )
72 else:
73 commit = ScmModel().commit_change(
74 repo=repo.scm_instance(), repo_name=repo.repo_name,
75 commit=parent, user=TEST_USER_ADMIN_LOGIN,
76 author=TEST_USER_ADMIN_LOGIN,
77 message=message,
78 content=content,
79 f_path=filename
80 )
81 return commit
82
83
84
46 85 @pytest.mark.usefixtures("app")
47 86 class TestFilesController:
48 87
@@ -54,7 +93,7 b' class TestFilesController:'
54 93
55 94 params = {
56 95 'repo_name': backend.repo_name,
57 'revision': commit.raw_id,
96 'commit_id': commit.raw_id,
58 97 'date': commit.date
59 98 }
60 99 assert_dirs_in_response(response, ['docs', 'vcs'], params)
@@ -135,7 +174,7 b' class TestFilesController:'
135 174 files = ['README.rst']
136 175 params = {
137 176 'repo_name': backend.repo_name,
138 'revision': commit.raw_id,
177 'commit_id': commit.raw_id,
139 178 }
140 179 assert_dirs_in_response(response, dirs, params)
141 180 assert_files_in_response(response, files, params)
@@ -302,31 +341,34 b' class TestFilesController:'
302 341 url('files_nodelist_home', repo_name=backend.repo_name,
303 342 f_path='/', revision='tip'), status=400)
304 343
305 def test_tree_metadata_list_success(self, backend, xhr_header):
344 def test_nodetree_full_success(self, backend, xhr_header):
306 345 commit = backend.repo.get_commit(commit_idx=173)
307 346 response = self.app.get(
308 url('files_metadata_list_home', repo_name=backend.repo_name,
309 f_path='/', revision=commit.raw_id),
347 url('files_nodetree_full', repo_name=backend.repo_name,
348 f_path='/', commit_id=commit.raw_id),
310 349 extra_environ=xhr_header)
311 350
312 expected_keys = ['author', 'message', 'modified_at', 'modified_ts',
313 'name', 'revision', 'short_id', 'size']
314 for filename in response.json.get('metadata'):
315 for key in expected_keys:
316 assert key in filename
351 assert_response = AssertResponse(response)
317 352
318 def test_tree_metadata_list_if_file(self, backend, xhr_header):
353 for attr in ['data-commit-id', 'data-date', 'data-author']:
354 elements = assert_response.get_elements('[{}]'.format(attr))
355 assert len(elements) > 1
356
357 for element in elements:
358 assert element.get(attr)
359
360 def test_nodetree_full_if_file(self, backend, xhr_header):
319 361 commit = backend.repo.get_commit(commit_idx=173)
320 362 response = self.app.get(
321 url('files_metadata_list_home', repo_name=backend.repo_name,
322 f_path='README.rst', revision=commit.raw_id),
363 url('files_nodetree_full', repo_name=backend.repo_name,
364 f_path='README.rst', commit_id=commit.raw_id),
323 365 extra_environ=xhr_header)
324 assert response.json == {'metadata': []}
366 assert response.body == ''
325 367
326 368 def test_tree_metadata_list_missing_xhr(self, backend):
327 369 self.app.get(
328 url('files_metadata_list_home', repo_name=backend.repo_name,
329 f_path='/', revision='tip'), status=400)
370 url('files_nodetree_full', repo_name=backend.repo_name,
371 f_path='/', commit_id='tip'), status=400)
330 372
331 373 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
332 374 self, app, backend_stub, autologin_regular_user, user_regular,
@@ -917,13 +959,13 b' class TestChangingFiles:'
917 959
918 960 def assert_files_in_response(response, files, params):
919 961 template = (
920 "href='/%(repo_name)s/files/%(revision)s/%(name)s'")
962 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
921 963 _assert_items_in_response(response, files, template, params)
922 964
923 965
924 966 def assert_dirs_in_response(response, dirs, params):
925 967 template = (
926 "href='/%(repo_name)s/files/%(revision)s/%(name)s'")
968 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
927 969 _assert_items_in_response(response, dirs, template, params)
928 970
929 971
@@ -937,3 +979,100 b' def _assert_items_in_response(response, '
937 979 def assert_timeago_in_response(response, items, params):
938 980 for item in items:
939 981 response.mustcontain(h.age_component(params['date']))
982
983
984
985 @pytest.mark.usefixtures("autologin_user", "app")
986 class TestSideBySideDiff:
987
988 def test_diff2way(self, app, backend, backend_stub):
989 f_path = 'content'
990 commit1_content = 'content-25d7e49c18b159446c'
991 commit2_content = 'content-603d6c72c46d953420'
992 repo = backend.create_repo()
993
994 commit1 = _commit_change(
995 repo.repo_name, filename=f_path, content=commit1_content,
996 message='A', vcs_type=backend.alias, parent=None, newfile=True)
997
998 commit2 = _commit_change(
999 repo.repo_name, filename=f_path, content=commit2_content,
1000 message='B, child of A', vcs_type=backend.alias, parent=commit1)
1001
1002 response = self.app.get(url(
1003 controller='files', action='diff_2way',
1004 repo_name=repo.repo_name,
1005 diff1=commit1.raw_id,
1006 diff2=commit2.raw_id,
1007 f_path=f_path))
1008
1009 assert_response = AssertResponse(response)
1010 response.mustcontain(
1011 ('Side-by-side Diff r0:%s ... r1:%s') % ( commit1.short_id, commit2.short_id ))
1012 response.mustcontain('id="compare"')
1013 response.mustcontain((
1014 "var orig1_url = '/%s/raw/%s/%s';\n"
1015 "var orig2_url = '/%s/raw/%s/%s';") %
1016 ( repo.repo_name, commit1.raw_id, f_path,
1017 repo.repo_name, commit2.raw_id, f_path))
1018
1019
1020 def test_diff2way_with_empty_file(self, app, backend, backend_stub):
1021 commits = [
1022 {'message': 'First commit'},
1023 {'message': 'Commit with binary',
1024 'added': [nodes.FileNode('file.empty', content='')]},
1025 ]
1026 f_path='file.empty'
1027 repo = backend.create_repo(commits=commits)
1028 commit_id1 = repo.get_commit(commit_idx=0).raw_id
1029 commit_id2 = repo.get_commit(commit_idx=1).raw_id
1030
1031 response = self.app.get(url(
1032 controller='files', action='diff_2way',
1033 repo_name=repo.repo_name,
1034 diff1=commit_id1,
1035 diff2=commit_id2,
1036 f_path=f_path))
1037
1038 assert_response = AssertResponse(response)
1039 if backend.alias == 'svn':
1040 assert_session_flash( response,
1041 ('%(file_path)s has not changed') % { 'file_path': 'file.empty' })
1042 else:
1043 response.mustcontain(
1044 ('Side-by-side Diff r0:%s ... r1:%s') % ( repo.get_commit(commit_idx=0).short_id, repo.get_commit(commit_idx=1).short_id ))
1045 response.mustcontain('id="compare"')
1046 response.mustcontain((
1047 "var orig1_url = '/%s/raw/%s/%s';\n"
1048 "var orig2_url = '/%s/raw/%s/%s';") %
1049 ( repo.repo_name, commit_id1, f_path,
1050 repo.repo_name, commit_id2, f_path))
1051
1052
1053 def test_empty_diff_2way_redirect_to_summary_with_alert(self, app, backend):
1054 commit_id_range = {
1055 'hg': (
1056 '25d7e49c18b159446cadfa506a5cf8ad1cb04067',
1057 '603d6c72c46d953420c89d36372f08d9f305f5dd'),
1058 'git': (
1059 '6fc9270775aaf5544c1deb014f4ddd60c952fcbb',
1060 '03fa803d7e9fb14daa9a3089e0d1494eda75d986'),
1061 'svn': (
1062 '335',
1063 '337'),
1064 }
1065 f_path = 'setup.py'
1066
1067 commit_ids = commit_id_range[backend.alias]
1068
1069 response = self.app.get(url(
1070 controller='files', action='diff_2way',
1071 repo_name=backend.repo_name,
1072 diff2=commit_ids[0],
1073 diff1=commit_ids[1],
1074 f_path=f_path))
1075
1076 assert_response = AssertResponse(response)
1077 assert_session_flash( response,
1078 ('%(file_path)s has not changed') % { 'file_path': f_path })
@@ -22,6 +22,7 b' import json'
22 22
23 23 from mock import patch
24 24 import pytest
25 from pylons import tmpl_context as c
25 26
26 27 import rhodecode
27 28 from rhodecode.lib.utils import map_groups
@@ -49,6 +50,14 b' class TestHomeController(TestController)'
49 50 for repo in Repository.getAll():
50 51 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
51 52
53 def test_index_contains_statics_with_ver(self):
54 self.log_user()
55 response = self.app.get(url(controller='home', action='index'))
56
57 rhodecode_version_hash = c.rhodecode_version_hash
58 response.mustcontain('style.css?ver={0}'.format(rhodecode_version_hash))
59 response.mustcontain('scripts.js?ver={0}'.format(rhodecode_version_hash))
60
52 61 def test_index_contains_backend_specific_details(self, backend):
53 62 self.log_user()
54 63 response = self.app.get(url(controller='home', action='index'))
@@ -19,13 +19,11 b''
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import re
22 import os
23 22
24 23 import mock
25 24 import pytest
26 25
27 26 from rhodecode.controllers import summary
28 from rhodecode.lib import vcs
29 27 from rhodecode.lib import helpers as h
30 28 from rhodecode.lib.compat import OrderedDict
31 29 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
@@ -34,9 +32,9 b' from rhodecode.model.meta import Session'
34 32 from rhodecode.model.repo import RepoModel
35 33 from rhodecode.model.scm import ScmModel
36 34 from rhodecode.tests import (
37 TestController, url, HG_REPO, assert_session_flash, TESTS_TMP_PATH)
35 TestController, url, HG_REPO, assert_session_flash)
38 36 from rhodecode.tests.fixture import Fixture
39 from rhodecode.tests.utils import AssertResponse
37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
40 38
41 39
42 40 fixture = Fixture()
@@ -302,20 +300,23 b' class TestCreateReferenceData:'
302 300 repo = mock.Mock()
303 301 repo.name = 'test-repo'
304 302 repo.alias = 'git'
303 full_repo_name = 'pytest-repo-group/' + repo.name
305 304 controller = summary.SummaryController()
306 305
307 result = controller._create_reference_data(repo, example_refs)
306 result = controller._create_reference_data(
307 repo, full_repo_name, example_refs)
308 308
309 expected_files_url = '/{}/files/'.format(full_repo_name)
309 310 expected_result = [
310 311 {
311 312 'children': [
312 313 {
313 314 'id': 'a', 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
314 'files_url': '/test-repo/files/a/?at=a'
315 'files_url': expected_files_url + 'a/?at=a',
315 316 },
316 317 {
317 318 'id': 'b', 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
318 'files_url': '/test-repo/files/b/?at=b'
319 'files_url': expected_files_url + 'b/?at=b',
319 320 }
320 321 ],
321 322 'text': 'section_1'
@@ -324,7 +325,7 b' class TestCreateReferenceData:'
324 325 'children': [
325 326 {
326 327 'id': 'c', 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
327 'files_url': '/test-repo/files/c/?at=c'
328 'files_url': expected_files_url + 'c/?at=c',
328 329 }
329 330 ],
330 331 'text': 'section_2'
@@ -335,21 +336,24 b' class TestCreateReferenceData:'
335 336 repo = mock.Mock()
336 337 repo.name = 'test-repo'
337 338 repo.alias = 'svn'
339 full_repo_name = 'pytest-repo-group/' + repo.name
338 340 controller = summary.SummaryController()
339 result = controller._create_reference_data(repo, example_refs)
341 result = controller._create_reference_data(
342 repo, full_repo_name, example_refs)
340 343
344 expected_files_url = '/{}/files/'.format(full_repo_name)
341 345 expected_result = [
342 346 {
343 347 'children': [
344 348 {
345 349 'id': 'a@a_id', 'raw_id': 'a_id',
346 350 'text': 'a', 'type': 't1',
347 'files_url': '/test-repo/files/a_id/a?at=a'
351 'files_url': expected_files_url + 'a_id/a?at=a',
348 352 },
349 353 {
350 354 'id': 'b@b_id', 'raw_id': 'b_id',
351 355 'text': 'b', 'type': 't1',
352 'files_url': '/test-repo/files/b_id/b?at=b'
356 'files_url': expected_files_url + 'b_id/b?at=b',
353 357 }
354 358 ],
355 359 'text': 'section_1'
@@ -359,7 +363,7 b' class TestCreateReferenceData:'
359 363 {
360 364 'id': 'c@c_id', 'raw_id': 'c_id',
361 365 'text': 'c', 'type': 't2',
362 'files_url': '/test-repo/files/c_id/c?at=c'
366 'files_url': expected_files_url + 'c_id/c?at=c',
363 367 }
364 368 ],
365 369 'text': 'section_2'
@@ -394,44 +398,38 b' class TestRepoLocation:'
394 398 response, 'The repository at %s cannot be located.' % repo_name)
395 399
396 400
397 def repo_on_filesystem(repo_name):
398 try:
399 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
400 return True
401 except Exception:
402 return False
403
404
405 401 class TestCreateFilesUrl(object):
406 402 def test_creates_non_svn_url(self):
407 403 controller = summary.SummaryController()
408 404 repo = mock.Mock()
409 405 repo.name = 'abcde'
406 full_repo_name = 'test-repo-group/' + repo.name
410 407 ref_name = 'branch1'
411 408 raw_id = 'deadbeef0123456789'
412 409 is_svn = False
413 410
414 411 with mock.patch.object(summary.h, 'url') as url_mock:
415 412 result = controller._create_files_url(
416 repo, ref_name, raw_id, is_svn)
413 repo, full_repo_name, ref_name, raw_id, is_svn)
417 414 url_mock.assert_called_once_with(
418 'files_home', repo_name=repo.name, f_path='', revision=ref_name,
419 at=ref_name)
415 'files_home', repo_name=full_repo_name, f_path='',
416 revision=ref_name, at=ref_name)
420 417 assert result == url_mock.return_value
421 418
422 419 def test_creates_svn_url(self):
423 420 controller = summary.SummaryController()
424 421 repo = mock.Mock()
425 422 repo.name = 'abcde'
423 full_repo_name = 'test-repo-group/' + repo.name
426 424 ref_name = 'branch1'
427 425 raw_id = 'deadbeef0123456789'
428 426 is_svn = True
429 427
430 428 with mock.patch.object(summary.h, 'url') as url_mock:
431 429 result = controller._create_files_url(
432 repo, ref_name, raw_id, is_svn)
430 repo, full_repo_name, ref_name, raw_id, is_svn)
433 431 url_mock.assert_called_once_with(
434 'files_home', repo_name=repo.name, f_path=ref_name,
432 'files_home', repo_name=full_repo_name, f_path=ref_name,
435 433 revision=raw_id, at=ref_name)
436 434 assert result == url_mock.return_value
437 435
@@ -439,21 +437,24 b' class TestCreateFilesUrl(object):'
439 437 controller = summary.SummaryController()
440 438 repo = mock.Mock()
441 439 repo.name = 'abcde'
440 full_repo_name = 'test-repo-group/' + repo.name
442 441 ref_name = 'branch1/branch2'
443 442 raw_id = 'deadbeef0123456789'
444 443 is_svn = False
445 444
446 445 with mock.patch.object(summary.h, 'url') as url_mock:
447 446 result = controller._create_files_url(
448 repo, ref_name, raw_id, is_svn)
447 repo, full_repo_name, ref_name, raw_id, is_svn)
449 448 url_mock.assert_called_once_with(
450 'files_home', repo_name=repo.name, f_path='', revision=raw_id,
449 'files_home', repo_name=full_repo_name, f_path='', revision=raw_id,
451 450 at=ref_name)
452 451 assert result == url_mock.return_value
453 452
454 453
455 454 class TestReferenceItems(object):
456 455 repo = mock.Mock()
456 repo.name = 'pytest-repo'
457 repo_full_name = 'pytest-repo-group/' + repo.name
457 458 ref_type = 'branch'
458 459 fake_url = '/abcde/'
459 460
@@ -477,7 +478,8 b' class TestReferenceItems(object):'
477 478
478 479 with url_patcher as url_mock, svn_patcher:
479 480 result = controller._create_reference_items(
480 self.repo, refs, self.ref_type, self._format_function)
481 self.repo, self.repo_full_name, refs, self.ref_type,
482 self._format_function)
481 483 assert len(result) == amount
482 484 assert url_mock.call_count == amount
483 485
@@ -496,9 +498,11 b' class TestReferenceItems(object):'
496 498
497 499 with url_patcher as url_mock, svn_patcher:
498 500 result = controller._create_reference_items(
499 self.repo, refs, self.ref_type, self._format_function)
501 self.repo, self.repo_full_name, refs, self.ref_type,
502 self._format_function)
500 503
501 url_mock.assert_called_once_with(self.repo, ref_name, ref_id, False)
504 url_mock.assert_called_once_with(
505 self.repo, self.repo_full_name, ref_name, ref_id, False)
502 506 expected_result = [
503 507 {
504 508 'text': ref_name,
@@ -58,9 +58,10 b' def get_environ(url):'
58 58 # Edge case: not a smart protocol url
59 59 ('/foo/bar', 'pull'),
60 60 ])
61 def test_get_action(url, expected_action):
61 def test_get_action(url, expected_action, pylonsapp):
62 62 app = simplegit.SimpleGit(application=None,
63 config={'auth_ret_code': '', 'base_path': ''})
63 config={'auth_ret_code': '', 'base_path': ''},
64 registry=None)
64 65 assert expected_action == app._get_action(get_environ(url))
65 66
66 67
@@ -74,15 +75,17 b' def test_get_action(url, expected_action'
74 75 ('/foo/bar/git-upload-pack', 'foo/bar'),
75 76 ('/foo/bar/git-receive-pack', 'foo/bar'),
76 77 ])
77 def test_get_repository_name(url, expected_repo_name):
78 def test_get_repository_name(url, expected_repo_name, pylonsapp):
78 79 app = simplegit.SimpleGit(application=None,
79 config={'auth_ret_code': '', 'base_path': ''})
80 config={'auth_ret_code': '', 'base_path': ''},
81 registry=None)
80 82 assert expected_repo_name == app._get_repository_name(get_environ(url))
81 83
82 84
83 def test_get_config():
85 def test_get_config(pylonsapp):
84 86 app = simplegit.SimpleGit(application=None,
85 config={'auth_ret_code': '', 'base_path': ''})
87 config={'auth_ret_code': '', 'base_path': ''},
88 registry=None)
86 89 extras = {'foo': 'FOO', 'bar': 'BAR'}
87 90
88 91 # We copy the extras as the method below will change the contents.
@@ -95,13 +98,13 b' def test_get_config():'
95 98 assert config == expected_config
96 99
97 100
98 def test_create_wsgi_app_uses_scm_app_from_simplevcs():
101 def test_create_wsgi_app_uses_scm_app_from_simplevcs(pylonsapp):
99 102 config = {
100 103 'auth_ret_code': '',
101 104 'base_path': '',
102 105 'vcs.scm_app_implementation':
103 106 'rhodecode.tests.lib.middleware.mock_scm_app',
104 107 }
105 app = simplegit.SimpleGit(application=None, config=config)
108 app = simplegit.SimpleGit(application=None, config=config, registry=None)
106 109 wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {})
107 110 assert wsgi_app is mock_scm_app.mock_git_wsgi
@@ -55,7 +55,8 b' def get_environ(url):'
55 55 ])
56 56 def test_get_action(url, expected_action):
57 57 app = simplehg.SimpleHg(application=None,
58 config={'auth_ret_code': '', 'base_path': ''})
58 config={'auth_ret_code': '', 'base_path': ''},
59 registry=None)
59 60 assert expected_action == app._get_action(get_environ(url))
60 61
61 62
@@ -72,13 +73,15 b' def test_get_action(url, expected_action'
72 73 ])
73 74 def test_get_repository_name(url, expected_repo_name):
74 75 app = simplehg.SimpleHg(application=None,
75 config={'auth_ret_code': '', 'base_path': ''})
76 config={'auth_ret_code': '', 'base_path': ''},
77 registry=None)
76 78 assert expected_repo_name == app._get_repository_name(get_environ(url))
77 79
78 80
79 81 def test_get_config():
80 82 app = simplehg.SimpleHg(application=None,
81 config={'auth_ret_code': '', 'base_path': ''})
83 config={'auth_ret_code': '', 'base_path': ''},
84 registry=None)
82 85 extras = {'foo': 'FOO', 'bar': 'BAR'}
83 86
84 87 mock_config = Config()
@@ -108,6 +111,6 b' def test_create_wsgi_app_uses_scm_app_fr'
108 111 'vcs.scm_app_implementation':
109 112 'rhodecode.tests.lib.middleware.mock_scm_app',
110 113 }
111 app = simplehg.SimpleHg(application=None, config=config)
114 app = simplehg.SimpleHg(application=None, config=config, registry=None)
112 115 wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {})
113 116 assert wsgi_app is mock_scm_app.mock_hg_wsgi
@@ -33,7 +33,8 b' class TestSimpleSvn(object):'
33 33 self.app = SimpleSvn(
34 34 application='None',
35 35 config={'auth_ret_code': '',
36 'base_path': rhodecode.CONFIG['base_path']})
36 'base_path': rhodecode.CONFIG['base_path']},
37 registry=None)
37 38
38 39 def test_get_config(self):
39 40 extras = {'foo': 'FOO', 'bar': 'BAR'}
@@ -140,7 +141,8 b' class TestSimpleSvnApp(object):'
140 141 }
141 142 expected_headers = [
142 143 ('MS-Author-Via', 'DAV'),
143 ('SVN-Supported-Posts', 'create-txn-with-props')
144 ('SVN-Supported-Posts', 'create-txn-with-props'),
145 ('X-RhodeCode-Backend', 'svn'),
144 146 ]
145 147 response_headers = self.app._get_response_headers(headers)
146 148 assert sorted(response_headers) == sorted(expected_headers)
@@ -174,7 +176,8 b' class TestSimpleSvnApp(object):'
174 176 }
175 177 expected_response_headers = [
176 178 ('SVN-Supported-Posts', 'create-txn-with-props'),
177 ('MS-Author-Via', 'DAV')
179 ('MS-Author-Via', 'DAV'),
180 ('X-RhodeCode-Backend', 'svn'),
178 181 ]
179 182 request_mock.assert_called_once_with(
180 183 self.environment['REQUEST_METHOD'], expected_url,
@@ -25,9 +25,7 b' import pytest'
25 25 import webtest.app
26 26
27 27 from rhodecode.lib.caching_query import FromCache
28 from rhodecode.lib.hooks_daemon import (
29 Pyro4HooksCallbackDaemon, DummyHooksCallbackDaemon,
30 HttpHooksCallbackDaemon)
28 from rhodecode.lib.hooks_daemon import DummyHooksCallbackDaemon
31 29 from rhodecode.lib.middleware import simplevcs
32 30 from rhodecode.lib.middleware.https_fixup import HttpsFixup
33 31 from rhodecode.lib.middleware.utils import scm_app
@@ -66,7 +64,7 b' def vcscontroller(pylonsapp, config_stub'
66 64 config_stub.include('rhodecode.authentication')
67 65
68 66 set_anonymous_access(True)
69 controller = StubVCSController(pylonsapp, pylonsapp.config)
67 controller = StubVCSController(pylonsapp, pylonsapp.config, None)
70 68 app = HttpsFixup(controller, pylonsapp.config)
71 69 app = webtest.app.TestApp(app)
72 70
@@ -131,7 +129,7 b' class StubFailVCSController(simplevcs.Si'
131 129
132 130 @pytest.fixture(scope='module')
133 131 def fail_controller(pylonsapp):
134 controller = StubFailVCSController(pylonsapp, pylonsapp.config)
132 controller = StubFailVCSController(pylonsapp, pylonsapp.config, None)
135 133 controller = HttpsFixup(controller, pylonsapp.config)
136 134 controller = webtest.app.TestApp(controller)
137 135 return controller
@@ -148,7 +146,7 b' def test_provides_traceback_for_appenlig'
148 146
149 147
150 148 def test_provides_utils_scm_app_as_scm_app_by_default(pylonsapp):
151 controller = StubVCSController(pylonsapp, pylonsapp.config)
149 controller = StubVCSController(pylonsapp, pylonsapp.config, None)
152 150 assert controller.scm_app is scm_app
153 151
154 152
@@ -156,7 +154,7 b' def test_allows_to_override_scm_app_via_'
156 154 config = pylonsapp.config.copy()
157 155 config['vcs.scm_app_implementation'] = (
158 156 'rhodecode.tests.lib.middleware.mock_scm_app')
159 controller = StubVCSController(pylonsapp, config)
157 controller = StubVCSController(pylonsapp, config, None)
160 158 assert controller.scm_app is mock_scm_app
161 159
162 160
@@ -231,7 +229,12 b' class TestGenerateVcsResponse:'
231 229 assert prepare_mock.call_count == 1
232 230
233 231 def call_controller_with_response_body(self, response_body):
234 controller = StubVCSController(None, {'base_path': 'fake_base_path'})
232 settings = {
233 'base_path': 'fake_base_path',
234 'vcs.hooks.protocol': 'http',
235 'vcs.hooks.direct_calls': False,
236 }
237 controller = StubVCSController(None, settings, None)
235 238 controller._invalidate_cache = mock.Mock()
236 239 controller.stub_response_body = response_body
237 240 self.start_response = mock.Mock()
@@ -281,16 +284,11 b' class TestInitializeGenerator:'
281 284
282 285
283 286 class TestPrepareHooksDaemon(object):
284 def test_calls_imported_prepare_callback_daemon(self):
285 config = {
286 'base_path': 'fake_base_path',
287 'vcs.hooks.direct_calls': False,
288 'vcs.hooks.protocol': 'http'
289 }
287 def test_calls_imported_prepare_callback_daemon(self, app_settings):
290 288 expected_extras = {'extra1': 'value1'}
291 289 daemon = DummyHooksCallbackDaemon()
292 290
293 controller = StubVCSController(None, config)
291 controller = StubVCSController(None, app_settings, None)
294 292 prepare_patcher = mock.patch.object(
295 293 simplevcs, 'prepare_callback_daemon',
296 294 return_value=(daemon, expected_extras))
@@ -298,7 +296,9 b' class TestPrepareHooksDaemon(object):'
298 296 callback_daemon, extras = controller._prepare_callback_daemon(
299 297 expected_extras.copy())
300 298 prepare_mock.assert_called_once_with(
301 expected_extras, protocol='http', use_direct_calls=False)
299 expected_extras,
300 protocol=app_settings['vcs.hooks.protocol'],
301 use_direct_calls=app_settings['vcs.hooks.direct_calls'])
302 302
303 303 assert callback_daemon == daemon
304 304 assert extras == extras
@@ -26,7 +26,7 b' from rhodecode.lib.middleware import vcs'
26 26
27 27 def test_is_hg():
28 28 environ = {
29 'PATH_INFO': 'rhodecode-dev',
29 'PATH_INFO': '/rhodecode-dev',
30 30 'QUERY_STRING': 'cmd=changegroup',
31 31 'HTTP_ACCEPT': 'application/mercurial'
32 32 }
@@ -35,7 +35,7 b' def test_is_hg():'
35 35
36 36 def test_is_hg_no_cmd():
37 37 environ = {
38 'PATH_INFO': 'rhodecode-dev',
38 'PATH_INFO': '/rhodecode-dev',
39 39 'QUERY_STRING': '',
40 40 'HTTP_ACCEPT': 'application/mercurial'
41 41 }
@@ -44,7 +44,7 b' def test_is_hg_no_cmd():'
44 44
45 45 def test_is_hg_empty_cmd():
46 46 environ = {
47 'PATH_INFO': 'rhodecode-dev',
47 'PATH_INFO': '/rhodecode-dev',
48 48 'QUERY_STRING': 'cmd=',
49 49 'HTTP_ACCEPT': 'application/mercurial'
50 50 }
@@ -53,7 +53,7 b' def test_is_hg_empty_cmd():'
53 53
54 54 def test_is_svn_returns_true_if_subversion_is_in_a_dav_header():
55 55 environ = {
56 'PATH_INFO': 'rhodecode-dev',
56 'PATH_INFO': '/rhodecode-dev',
57 57 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log-revprops'
58 58 }
59 59 assert vcs.is_svn(environ) is True
@@ -61,7 +61,7 b' def test_is_svn_returns_true_if_subversi'
61 61
62 62 def test_is_svn_returns_false_if_subversion_is_not_in_a_dav_header():
63 63 environ = {
64 'PATH_INFO': 'rhodecode-dev',
64 'PATH_INFO': '/rhodecode-dev',
65 65 'HTTP_DAV': 'http://stuff.tigris.org/xmlns/dav/svn/log-revprops'
66 66 }
67 67 assert vcs.is_svn(environ) is False
@@ -69,11 +69,31 b' def test_is_svn_returns_false_if_subvers'
69 69
70 70 def test_is_svn_returns_false_if_no_dav_header():
71 71 environ = {
72 'PATH_INFO': 'rhodecode-dev',
72 'PATH_INFO': '/rhodecode-dev',
73 73 }
74 74 assert vcs.is_svn(environ) is False
75 75
76 76
77 def test_is_svn_returns_true_if_magic_path_segment():
78 environ = {
79 'PATH_INFO': '/stub-repository/!svn/rev/4',
80 }
81 assert vcs.is_svn(environ)
82
83
84 def test_is_svn_allows_to_configure_the_magic_path(monkeypatch):
85 """
86 This is intended as a fallback in case someone has configured his
87 Subversion server with a different magic path segment.
88 """
89 monkeypatch.setitem(
90 rhodecode.CONFIG, 'rhodecode_subversion_magic_path', '/!my-magic')
91 environ = {
92 'PATH_INFO': '/stub-repository/!my-magic/rev/4',
93 }
94 assert vcs.is_svn(environ)
95
96
77 97 class TestVCSMiddleware(object):
78 98 def test_get_handler_app_retuns_svn_app_when_proxy_enabled(self):
79 99 environ = {
@@ -82,8 +102,9 b' class TestVCSMiddleware(object):'
82 102 }
83 103 app = Mock()
84 104 config = Mock()
105 registry = Mock()
85 106 middleware = vcs.VCSMiddleware(
86 app, config=config, appenlight_client=None)
107 app, config=config, appenlight_client=None, registry=registry)
87 108 snv_patch = patch('rhodecode.lib.middleware.vcs.SimpleSvn')
88 109 settings_patch = patch.dict(
89 110 rhodecode.CONFIG,
@@ -92,7 +113,7 b' class TestVCSMiddleware(object):'
92 113 svn_mock.return_value = None
93 114 middleware._get_handler_app(environ)
94 115
95 svn_mock.assert_called_once_with(app, config)
116 svn_mock.assert_called_once_with(app, config, registry)
96 117
97 118 def test_get_handler_app_retuns_no_svn_app_when_proxy_disabled(self):
98 119 environ = {
@@ -101,8 +122,9 b' class TestVCSMiddleware(object):'
101 122 }
102 123 app = Mock()
103 124 config = Mock()
125 registry = Mock()
104 126 middleware = vcs.VCSMiddleware(
105 app, config=config, appenlight_client=None)
127 app, config=config, appenlight_client=None, registry=registry)
106 128 snv_patch = patch('rhodecode.lib.middleware.vcs.SimpleSvn')
107 129 settings_patch = patch.dict(
108 130 rhodecode.CONFIG,
@@ -33,7 +33,7 b' def vcs_http_app(vcsserver_http_echo_app'
33 33 """
34 34 git_url = vcsserver_http_echo_app.http_url + 'stream/git/'
35 35 vcs_http_proxy = scm_app_http.VcsHttpProxy(
36 git_url, 'stub_path', 'stub_name', None)
36 git_url, 'stub_path', 'stub_name', None, 'stub_backend')
37 37 app = webtest.TestApp(vcs_http_proxy)
38 38 return app
39 39
@@ -133,4 +133,4 b' def create_scm_app():'
133 133 """
134 134 echo_app_url = os.environ["RC_ECHO_URL"]
135 135 return scm_app_http.VcsHttpProxy(
136 echo_app_url, 'stub_path', 'stub_name', None)
136 echo_app_url, 'stub_path', 'stub_name', None, 'stub_backend')
@@ -86,8 +86,10 b' def test_diffprocessor_as_html_with_comm'
86 86 </tr>
87 87 <tr class="line unmod">
88 88 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
89 <td id="setuppy_o2" class="lineno old"><a href="#setuppy_o2">2</a></td>
90 <td id="setuppy_n2" class="lineno new"><a href="#setuppy_n2">2</a></td>
89 <td id="setuppy_o2" class="lineno old"><a href="#setuppy_o2" class="tooltip"
90 title="Click to select line">2</a></td>
91 <td id="setuppy_n2" class="lineno new"><a href="#setuppy_n2" class="tooltip"
92 title="Click to select line">2</a></td>
91 93 <td class="code">
92 94 <pre>#!/usr/bin/python
93 95 </pre>
@@ -95,8 +97,10 b' def test_diffprocessor_as_html_with_comm'
95 97 </tr>
96 98 <tr class="line unmod">
97 99 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
98 <td id="setuppy_o3" class="lineno old"><a href="#setuppy_o3">3</a></td>
99 <td id="setuppy_n3" class="lineno new"><a href="#setuppy_n3">3</a></td>
100 <td id="setuppy_o3" class="lineno old"><a href="#setuppy_o3" class="tooltip"
101 title="Click to select line">3</a></td>
102 <td id="setuppy_n3" class="lineno new"><a href="#setuppy_n3" class="tooltip"
103 title="Click to select line">3</a></td>
100 104 <td class="code">
101 105 <pre># Setup file for X
102 106 </pre>
@@ -104,8 +108,10 b' def test_diffprocessor_as_html_with_comm'
104 108 </tr>
105 109 <tr class="line unmod">
106 110 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
107 <td id="setuppy_o4" class="lineno old"><a href="#setuppy_o4">4</a></td>
108 <td id="setuppy_n4" class="lineno new"><a href="#setuppy_n4">4</a></td>
111 <td id="setuppy_o4" class="lineno old"><a href="#setuppy_o4" class="tooltip"
112 title="Click to select line">4</a></td>
113 <td id="setuppy_n4" class="lineno new"><a href="#setuppy_n4" class="tooltip"
114 title="Click to select line">4</a></td>
109 115 <td class="code">
110 116 <pre># Copyright (C) No one
111 117 </pre>
@@ -113,8 +119,10 b' def test_diffprocessor_as_html_with_comm'
113 119 </tr>
114 120 <tr class="line del">
115 121 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
116 <td id="setuppy_o5" class="lineno old"><a href="#setuppy_o5">5</a></td>
117 <td class="lineno new"><a href="#setuppy_n"></a></td>
122 <td id="setuppy_o5" class="lineno old"><a href="#setuppy_o5" class="tooltip"
123 title="Click to select line">5</a></td>
124 <td class="lineno new"><a href="#setuppy_n" class="tooltip"
125 title="Click to select line"></a></td>
118 126 <td class="code">
119 127 <pre>
120 128 </pre>
@@ -122,8 +130,10 b' def test_diffprocessor_as_html_with_comm'
122 130 </tr>
123 131 <tr class="line add">
124 132 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
125 <td class="lineno old"><a href="#setuppy_o"></a></td>
126 <td id="setuppy_n5" class="lineno new"><a href="#setuppy_n5">5</a></td>
133 <td class="lineno old"><a href="#setuppy_o" class="tooltip"
134 title="Click to select line"></a></td>
135 <td id="setuppy_n5" class="lineno new"><a href="#setuppy_n5" class="tooltip"
136 title="Click to select line">5</a></td>
127 137 <td class="code">
128 138 <pre><ins>x</ins>
129 139 </pre>
@@ -131,8 +141,10 b' def test_diffprocessor_as_html_with_comm'
131 141 </tr>
132 142 <tr class="line unmod">
133 143 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
134 <td id="setuppy_o6" class="lineno old"><a href="#setuppy_o6">6</a></td>
135 <td id="setuppy_n6" class="lineno new"><a href="#setuppy_n6">6</a></td>
144 <td id="setuppy_o6" class="lineno old"><a href="#setuppy_o6" class="tooltip"
145 title="Click to select line">6</a></td>
146 <td id="setuppy_n6" class="lineno new"><a href="#setuppy_n6" class="tooltip"
147 title="Click to select line">6</a></td>
136 148 <td class="code">
137 149 <pre>try:
138 150 </pre>
@@ -140,8 +152,10 b' def test_diffprocessor_as_html_with_comm'
140 152 </tr>
141 153 <tr class="line unmod">
142 154 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
143 <td id="setuppy_o7" class="lineno old"><a href="#setuppy_o7">7</a></td>
144 <td id="setuppy_n7" class="lineno new"><a href="#setuppy_n7">7</a></td>
155 <td id="setuppy_o7" class="lineno old"><a href="#setuppy_o7" class="tooltip"
156 title="Click to select line">7</a></td>
157 <td id="setuppy_n7" class="lineno new"><a href="#setuppy_n7" class="tooltip"
158 title="Click to select line">7</a></td>
145 159 <td class="code">
146 160 <pre> from setuptools import setup, Extension
147 161 </pre>
@@ -149,8 +163,10 b' def test_diffprocessor_as_html_with_comm'
149 163 </tr>
150 164 <tr class="line unmod">
151 165 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
152 <td id="setuppy_o8" class="lineno old"><a href="#setuppy_o8">8</a></td>
153 <td id="setuppy_n8" class="lineno new"><a href="#setuppy_n8">8</a></td>
166 <td id="setuppy_o8" class="lineno old"><a href="#setuppy_o8" class="tooltip"
167 title="Click to select line">8</a></td>
168 <td id="setuppy_n8" class="lineno new"><a href="#setuppy_n8" class="tooltip"
169 title="Click to select line">8</a></td>
154 170 <td class="code">
155 171 <pre>except ImportError:
156 172 </pre>
@@ -69,6 +69,40 b' def test_format_binary():'
69 69 assert helpers.format_byte_size_binary(298489462784) == '278.0 GiB'
70 70
71 71
72 @pytest.mark.parametrize('text_string, pattern, expected', [
73 ('No issue here', '(?:#)(?P<issue_id>\d+)', []),
74 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
75 [{'url': 'http://r.io/{repo}/i/42', 'id': '42'}]),
76 ('Fix #42, #53', '(?:#)(?P<issue_id>\d+)', [
77 {'url': 'http://r.io/{repo}/i/42', 'id': '42'},
78 {'url': 'http://r.io/{repo}/i/53', 'id': '53'}]),
79 ('Fix #42', '(?:#)?<issue_id>\d+)', []), # Broken regex
80 ])
81 def test_extract_issues(backend, text_string, pattern, expected):
82 repo = backend.create_repo()
83 config = {
84 '123': {
85 'uid': '123',
86 'pat': pattern,
87 'url': 'http://r.io/${repo}/i/${issue_id}',
88 'pref': '#',
89 }
90 }
91
92 def get_settings_mock(self, cache=True):
93 return config
94
95 with mock.patch.object(IssueTrackerSettingsModel,
96 'get_settings', get_settings_mock):
97 text, issues = helpers.process_patterns(text_string, repo.repo_name)
98
99 expected = copy.deepcopy(expected)
100 for item in expected:
101 item['url'] = item['url'].format(repo=repo.repo_name)
102
103 assert issues == expected
104
105
72 106 @pytest.mark.parametrize('text_string, pattern, expected_text', [
73 107 ('Fix #42', '(?:#)(?P<issue_id>\d+)',
74 108 'Fix <a class="issue-tracker-link" href="http://r.io/{repo}/i/42">#42</a>'
@@ -90,7 +124,7 b' def test_process_patterns_repo(backend, '
90 124
91 125 with mock.patch.object(IssueTrackerSettingsModel,
92 126 'get_settings', get_settings_mock):
93 processed_text = helpers.process_patterns(
127 processed_text, issues = helpers.process_patterns(
94 128 text_string, repo.repo_name, config)
95 129
96 130 assert processed_text == expected_text.format(repo=repo.repo_name)
@@ -116,7 +150,7 b' def test_process_patterns_no_repo(text_s'
116 150
117 151 with mock.patch.object(IssueTrackerSettingsModel,
118 152 'get_global_settings', get_settings_mock):
119 processed_text = helpers.process_patterns(
153 processed_text, issues = helpers.process_patterns(
120 154 text_string, '', config)
121 155
122 156 assert processed_text == expected_text
@@ -140,7 +174,7 b' def test_process_patterns_non_existent_r'
140 174
141 175 with mock.patch.object(IssueTrackerSettingsModel,
142 176 'get_global_settings', get_settings_mock):
143 processed_text = helpers.process_patterns(
177 processed_text, issues = helpers.process_patterns(
144 178 text_string, 'do-not-exist', config)
145 179
146 180 assert processed_text == expected_text
@@ -28,12 +28,12 b' from rhodecode.lib import hooks_base, ut'
28 28 action_logger=mock.Mock(),
29 29 post_push_extension=mock.Mock(),
30 30 Repository=mock.Mock())
31 def test_post_push_truncates_commits():
31 def test_post_push_truncates_commits(user_regular, repo_stub):
32 32 extras = {
33 33 'ip': '127.0.0.1',
34 'username': 'test',
34 'username': user_regular.username,
35 35 'action': 'push_local',
36 'repository': 'test',
36 'repository': repo_stub.repo_name,
37 37 'scm': 'git',
38 38 'config': '',
39 39 'server_url': 'http://example.com',
@@ -27,9 +27,9 b' def test_render_email(pylonsapp):'
27 27 assert body_plaintext == 'Email Plaintext Body'
28 28
29 29 # body
30 assert '<b>This is a notification ' \
31 'from RhodeCode. http://test.example.com:80/</b>' in body
32 assert '<b>Email Body' in body
30 assert 'This is a notification ' \
31 'from RhodeCode. http://test.example.com:80/' in body
32 assert 'Email Body' in body
33 33
34 34
35 35 def test_render_pr_email(pylonsapp, user_admin):
@@ -177,3 +177,37 b' Auto status change to |new_status|'
177 177 renderer = RstTemplateRenderer()
178 178 rendered = renderer.render('auto_status_change.mako', **params)
179 179 assert expected == rendered
180
181
182 @pytest.mark.parametrize(
183 "readmes, exts, order",
184 [
185 ([], [], []),
186
187 ([('readme1', 0), ('text1', 1)], [('.ext', 0), ('.txt', 1)],
188 ['readme1.ext', 'readme1.txt', 'text1.ext', 'text1.txt']),
189
190 ([('readme2', 0), ('text2', 1)], [('.ext', 2), ('.txt', 1)],
191 ['readme2.txt', 'readme2.ext', 'text2.txt', 'text2.ext']),
192
193 ([('readme3', 0), ('text3', 1)], [('.XXX', 1)],
194 ['readme3.XXX', 'text3.XXX']),
195 ])
196 def test_generate_readmes(readmes, exts, order):
197 assert order == MarkupRenderer.generate_readmes(readmes, exts)
198
199
200 @pytest.mark.parametrize(
201 "renderer, expected_order",
202 [
203 ('plain', ['readme', 'README', 'Readme']),
204 ('text', ['readme', 'README', 'Readme']),
205 ('markdown', MarkupRenderer.generate_readmes(
206 MarkupRenderer.ALL_READMES, MarkupRenderer.MARKDOWN_EXTS)),
207 ('rst', MarkupRenderer.generate_readmes(
208 MarkupRenderer.ALL_READMES, MarkupRenderer.RST_EXTS)),
209 ])
210 def test_order_of_readme_generation(renderer, expected_order):
211 mkd_renderer = MarkupRenderer()
212 assert expected_order == mkd_renderer.pick_readme_order(
213 renderer)[:len(expected_order)]
@@ -18,9 +18,41 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 from rhodecode.lib.utils2 import obfuscate_url_pw
21 import pytest
22
23 from rhodecode.lib.utils2 import (
24 obfuscate_url_pw, get_routes_generator_for_server_url)
22 25
23 26
24 27 def test_obfuscate_url_pw():
25 28 engine = u'/home/repos/malmö'
26 29 assert obfuscate_url_pw(engine)
30
31
32 @pytest.mark.parametrize('scheme', ['https', 'http'])
33 @pytest.mark.parametrize('domain', [
34 'www.test.com', 'test.com', 'test.co.uk', '192.168.1.3'])
35 @pytest.mark.parametrize('port', [None, '80', '443', '999'])
36 @pytest.mark.parametrize('script_path', [None, '/', '/prefix', '/prefix/more'])
37 def test_routes_generator(pylonsapp, scheme, domain, port, script_path):
38 server_url = '%s://%s' % (scheme, domain)
39 if port is not None:
40 server_url += ':' + port
41 if script_path:
42 server_url += script_path
43
44
45 expected_url = '%s://%s' % (scheme, domain)
46 if scheme == 'https':
47 if port not in (None, '443'):
48 expected_url += ':' + port
49 elif scheme == 'http':
50 if port not in ('80', None):
51 expected_url += ':' + port
52
53 if script_path:
54 expected_url = (expected_url + script_path).rstrip('/')
55
56 url_generator = get_routes_generator_for_server_url(server_url)
57 assert url_generator(
58 '/a_test_path', qualified=True) == expected_url + '/a_test_path'
@@ -21,7 +21,7 b''
21 21 import colander
22 22 import pytest
23 23
24 from rhodecode.model.validation_schema import GroupNameType
24 from rhodecode.model.validation_schema.types import GroupNameType
25 25
26 26
27 27 class TestGroupNameType(object):
@@ -39,7 +39,8 b' SVN_FORM_DATA = {'
39 39
40 40 GENERAL_FORM_DATA = {
41 41 'rhodecode_pr_merge_enabled': True,
42 'rhodecode_use_outdated_comments': True
42 'rhodecode_use_outdated_comments': True,
43 'rhodecode_hg_use_rebase_for_merging': True,
43 44 }
44 45
45 46
@@ -157,7 +157,8 b' class TestPullRequestModel:'
157 157 self.merge_mock.assert_called_once_with(
158 158 pull_request.target_ref_parts,
159 159 pull_request.source_repo.scm_instance(),
160 pull_request.source_ref_parts, self.workspace_id, dry_run=True)
160 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
161 use_rebase=False)
161 162
162 163 assert pull_request._last_merge_source_rev == self.source_commit
163 164 assert pull_request._last_merge_target_rev == self.target_commit
@@ -185,7 +186,8 b' class TestPullRequestModel:'
185 186 self.merge_mock.assert_called_once_with(
186 187 pull_request.target_ref_parts,
187 188 pull_request.source_repo.scm_instance(),
188 pull_request.source_ref_parts, self.workspace_id, dry_run=True)
189 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
190 use_rebase=False)
189 191
190 192 assert pull_request._last_merge_source_rev == self.source_commit
191 193 assert pull_request._last_merge_target_rev == self.target_commit
@@ -216,7 +218,8 b' class TestPullRequestModel:'
216 218 self.merge_mock.assert_called_once_with(
217 219 pull_request.target_ref_parts,
218 220 pull_request.source_repo.scm_instance(),
219 pull_request.source_ref_parts, self.workspace_id, dry_run=True)
221 pull_request.source_ref_parts, self.workspace_id, dry_run=True,
222 use_rebase=False)
220 223
221 224 assert pull_request._last_merge_source_rev is None
222 225 assert pull_request._last_merge_target_rev is None
@@ -271,6 +274,7 b' class TestPullRequestModel:'
271 274 '6126b7bfcc82ad2d3deaee22af926b082ce54cc6',
272 275 MergeFailureReason.NONE)
273 276
277 merge_extras['repository'] = pull_request.target_repo.repo_name
274 278 PullRequestModel().merge(
275 279 pull_request, pull_request.author, extras=merge_extras)
276 280
@@ -288,7 +292,8 b' class TestPullRequestModel:'
288 292 pull_request.target_ref_parts,
289 293 pull_request.source_repo.scm_instance(),
290 294 pull_request.source_ref_parts, self.workspace_id,
291 user_name=user.username, user_email=user.email, message=message
295 user_name=user.username, user_email=user.email, message=message,
296 use_rebase=False
292 297 )
293 298 self.invalidation_mock.assert_called_once_with(
294 299 pull_request.target_repo.repo_name)
@@ -308,6 +313,7 b' class TestPullRequestModel:'
308 313 '6126b7bfcc82ad2d3deaee22af926b082ce54cc6',
309 314 MergeFailureReason.MERGE_FAILED)
310 315
316 merge_extras['repository'] = pull_request.target_repo.repo_name
311 317 PullRequestModel().merge(
312 318 pull_request, pull_request.author, extras=merge_extras)
313 319
@@ -325,7 +331,8 b' class TestPullRequestModel:'
325 331 pull_request.target_ref_parts,
326 332 pull_request.source_repo.scm_instance(),
327 333 pull_request.source_ref_parts, self.workspace_id,
328 user_name=user.username, user_email=user.email, message=message
334 user_name=user.username, user_email=user.email, message=message,
335 use_rebase=False
329 336 )
330 337
331 338 pull_request = PullRequest.get(pull_request.pull_request_id)
@@ -364,6 +371,7 b' class TestIntegrationMerge(object):'
364 371 pull_request = pr_util.create_pull_request(
365 372 approved=True, mergeable=True)
366 373 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
374 merge_extras['repository'] = pull_request.target_repo.repo_name
367 375 Session().commit()
368 376
369 377 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
@@ -379,6 +387,7 b' class TestIntegrationMerge(object):'
379 387 pull_request = pr_util.create_pull_request(
380 388 approved=True, mergeable=True)
381 389 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
390 merge_extras['repository'] = pull_request.target_repo.repo_name
382 391 Session().commit()
383 392
384 393 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
@@ -400,6 +409,7 b' class TestIntegrationMerge(object):'
400 409 # all data is pre-computed, that's why just updating the DB is not
401 410 # enough.
402 411 merge_extras['locked_by'] = locked_by
412 merge_extras['repository'] = pull_request.target_repo.repo_name
403 413 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
404 414 Session().commit()
405 415 merge_status = PullRequestModel().merge(
@@ -160,7 +160,8 b' class TestRepoModel:'
160 160 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm:
161 161 scm_instance = mock.Mock()
162 162 scm_instance.get_commit.return_value = {
163 'raw_id': 40*'0'
163 'raw_id': 40*'0',
164 'revision': 1
164 165 }
165 166 scm.return_value = scm_instance
166 167 repo.update_commit_cache()
@@ -51,7 +51,7 b' def test_scm_instance_config(backend):'
51 51 def test__get_instance_config(backend):
52 52 repo = backend.create_repo()
53 53 vcs_class = Mock()
54 with patch.multiple('rhodecode.model.db',
54 with patch.multiple('rhodecode.lib.vcs.backends',
55 55 get_scm=DEFAULT,
56 56 get_backend=DEFAULT) as mocks:
57 57 mocks['get_scm'].return_value = backend.alias
@@ -59,13 +59,13 b' def test__get_instance_config(backend):'
59 59 with patch('rhodecode.model.db.Repository._config') as config_mock:
60 60 repo._get_instance()
61 61 vcs_class.assert_called_with(
62 repo.repo_full_path, config=config_mock, create=False,
63 with_wire={'cache': True})
62 repo_path=repo.repo_full_path, config=config_mock,
63 create=False, with_wire={'cache': True})
64 64
65 65 new_config = {'override': 'old_config'}
66 66 repo._get_instance(config=new_config)
67 67 vcs_class.assert_called_with(
68 repo.repo_full_path, config=new_config, create=False,
68 repo_path=repo.repo_full_path, config=new_config, create=False,
69 69 with_wire={'cache': True})
70 70
71 71
@@ -77,6 +77,19 b' def test_mark_for_invalidation_config(ba'
77 77 assert kwargs['config'].__dict__ == repo._config.__dict__
78 78
79 79
80 def test_mark_for_invalidation_with_delete_updates_last_commit(backend):
81 commits = [{'message': 'A'}, {'message': 'B'}]
82 repo = backend.create_repo(commits=commits)
83 scm.ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
84 assert repo.changeset_cache['revision'] == 1
85
86
87 def test_mark_for_invalidation_with_delete_updates_last_commit_empty(backend):
88 repo = backend.create_repo()
89 scm.ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
90 assert repo.changeset_cache['revision'] == -1
91
92
80 93 def test_strip_with_multiple_heads(backend_hg):
81 94 commits = [
82 95 {'message': 'A'},
@@ -135,6 +148,23 b' def test_get_nodes_returns_unicode_non_f'
135 148 assert_contains_only_unicode([f['name'] for f in files])
136 149
137 150
151 def test_get_nodes_max_file_bytes(backend_random):
152 repo = backend_random.repo
153 max_file_bytes = 10
154 directories, files = scm.ScmModel().get_nodes(
155 repo.repo_name, repo.get_commit(commit_idx=0).raw_id, content=True,
156 extended_info=True, flat=False)
157 assert any(file['content'] and len(file['content']) > max_file_bytes
158 for file in files)
159
160 directories, files = scm.ScmModel().get_nodes(
161 repo.repo_name, repo.get_commit(commit_idx=0).raw_id, content=True,
162 extended_info=True, flat=False, max_file_bytes=max_file_bytes)
163 assert all(
164 file['content'] is None if file['size'] > max_file_bytes else True
165 for file in files)
166
167
138 168 def assert_contains_only_unicode(structure):
139 169 assert structure
140 170 for value in structure:
@@ -87,18 +87,21 b' def _add_files_and_push(vcs, dest, clone'
87 87 added_file = jn(path, '%ssetup.py' % tempfile._RandomNameSequence().next())
88 88 Command(cwd).execute('touch %s' % added_file)
89 89 Command(cwd).execute('%s add %s' % (vcs, added_file))
90 author_str = 'Marcin Kuźminski <me@email.com>'
91
92 git_ident = "git config user.name {} && git config user.email {}".format(
93 'Marcin Kuźminski', 'me@email.com')
90 94
91 95 for i in xrange(kwargs.get('files_no', 3)):
92 96 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
93 97 Command(cwd).execute(cmd)
94 author_str = 'Marcin Kuźminski <me@email.com>'
95 98 if vcs == 'hg':
96 99 cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % (
97 100 i, author_str, added_file
98 101 )
99 102 elif vcs == 'git':
100 cmd = """EMAIL="me@email.com" git commit -m 'commited new %s' """\
101 """--author '%s' %s """ % (i, author_str, added_file)
103 cmd = """%s && git commit -m 'commited new %s' %s""" % (
104 git_ident, i, added_file)
102 105 Command(cwd).execute(cmd)
103 106
104 107 # PUSH it back
@@ -108,7 +111,8 b' def _add_files_and_push(vcs, dest, clone'
108 111 'hg push --verbose', clone_url)
109 112 elif vcs == 'git':
110 113 stdout, stderr = Command(cwd).execute(
111 'git push --verbose', clone_url + " master")
114 """%s && git push --verbose %s master""" % (
115 git_ident, clone_url))
112 116
113 117 return stdout, stderr
114 118
@@ -217,10 +217,10 b' def enable_auth_plugins(request, pylonsa'
217 217 plugin = rhodecode.authentication.base.loadplugin(module)
218 218 plugin_name = plugin.name
219 219 enabled_plugin = 'auth_%s_enabled' % plugin_name
220 cache_ttl = 'auth_%s_auth_cache_ttl' % plugin_name
220 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
221 221
222 222 # default params that are needed for each plugin,
223 # `enabled` and `auth_cache_ttl`
223 # `enabled` and `cache_ttl`
224 224 params.update({
225 225 enabled_plugin: True,
226 226 cache_ttl: 0
@@ -214,6 +214,22 b' def app(request, pylonsapp, http_environ'
214 214 return app
215 215
216 216
217 @pytest.fixture()
218 def app_settings(pylonsapp, pylons_config):
219 """
220 Settings dictionary used to create the app.
221
222 Parses the ini file and passes the result through the sanitize and apply
223 defaults mechanism in `rhodecode.config.middleware`.
224 """
225 from paste.deploy.loadwsgi import loadcontext, APP
226 from rhodecode.config.middleware import (
227 sanitize_settings_and_apply_defaults)
228 context = loadcontext(APP, 'config:' + pylons_config)
229 settings = sanitize_settings_and_apply_defaults(context.config())
230 return settings
231
232
217 233 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
218 234
219 235
@@ -285,6 +301,27 b' def tests_tmp_path(request):'
285 301 return TESTS_TMP_PATH
286 302
287 303
304 @pytest.fixture(scope='session', autouse=True)
305 def patch_pyro_request_scope_proxy_factory(request):
306 """
307 Patch the pyro proxy factory to always use the same dummy request object
308 when under test. This will return the same pyro proxy on every call.
309 """
310 dummy_request = pyramid.testing.DummyRequest()
311
312 def mocked_call(self, request=None):
313 return self.getProxy(request=dummy_request)
314
315 patcher = mock.patch(
316 'rhodecode.lib.vcs.client.RequestScopeProxyFactory.__call__',
317 new=mocked_call)
318 patcher.start()
319
320 @request.addfinalizer
321 def undo_patching():
322 patcher.stop()
323
324
288 325 @pytest.fixture
289 326 def test_repo_group(request):
290 327 """
@@ -1168,8 +1205,8 b' class UserUtility(object):'
1168 1205 def _inherit_default_user_permissions(self, user_name, value):
1169 1206 user = UserModel().get_by_username(user_name)
1170 1207 user.inherit_default_permissions = value
1171 Session.add(user)
1172 Session.commit()
1208 Session().add(user)
1209 Session().commit()
1173 1210
1174 1211 def cleanup(self):
1175 1212 self._cleanup_permissions()
@@ -171,6 +171,9 b' class AssertResponse(object):'
171 171 assert len(elements) == 1
172 172 return elements[0]
173 173
174 def get_elements(self, css_selector):
175 return self._get_elements(css_selector)
176
174 177 def _get_elements(self, css_selector):
175 178 doc = fromstring(self.response.body)
176 179 sel = CSSSelector(css_selector)
@@ -280,3 +283,11 b' def get_session_from_response(response):'
280 283 """
281 284 # TODO: Try to look up the session key also.
282 285 return response.request.environ['beaker.session']
286
287
288 def repo_on_filesystem(repo_name):
289 from rhodecode.lib import vcs
290 from rhodecode.tests import TESTS_TMP_PATH
291 repo = vcs.get_vcs_instance(
292 os.path.join(TESTS_TMP_PATH, repo_name), create=False)
293 return repo is not None
@@ -711,12 +711,10 b' TODO: To be written...'
711 711 source_ref = Reference('book', bookmark_name, source_commit.raw_id)
712 712 workspace = 'test-merge'
713 713
714 with mock.patch.object(rhodecode.lib.vcs.conf.settings,
715 'HG_USE_REBASE_FOR_MERGING', return_value=True):
716 merge_response = target_repo.merge(
717 target_ref, source_repo, source_ref, workspace,
718 'test user', 'test@rhodecode.com', 'merge message 1',
719 dry_run=False)
714 merge_response = target_repo.merge(
715 target_ref, source_repo, source_ref, workspace,
716 'test user', 'test@rhodecode.com', 'merge message 1',
717 dry_run=False, use_rebase=True)
720 718
721 719 expected_merge_response = MergeResponse(
722 720 True, True, merge_response.merge_commit_id,
@@ -38,7 +38,7 b' import os'
38 38 import pytest
39 39
40 40 from rhodecode.lib.vcs import create_vcsserver_proxy
41 from rhodecode.lib.vcs.backends import get_repo, get_backend
41 from rhodecode.lib.vcs.backends import get_backend, get_vcs_instance
42 42 from rhodecode.tests import TEST_HG_REPO, TEST_GIT_REPO
43 43
44 44
@@ -54,7 +54,7 b' def repo(request, pylonsapp):'
54 54 'hg': TEST_HG_REPO,
55 55 'git': TEST_GIT_REPO,
56 56 }
57 repo = get_repo(repos[request.param])
57 repo = get_vcs_instance(repos[request.param])
58 58 return repo
59 59
60 60
@@ -22,14 +22,16 b''
22 22 Tests for main module's methods.
23 23 """
24 24
25 import mock
25 26 import os
26 27 import shutil
28 import tempfile
27 29
28 30 import pytest
29 31
30 from rhodecode.lib.vcs import VCSError, get_repo, get_backend
32 from rhodecode.lib.vcs import VCSError, get_backend, get_vcs_instance
31 33 from rhodecode.lib.vcs.backends.hg import MercurialRepository
32 from rhodecode.tests import TEST_HG_REPO, TEST_GIT_REPO, TESTS_TMP_PATH
34 from rhodecode.tests import TEST_HG_REPO, TEST_GIT_REPO
33 35
34 36
35 37 pytestmark = pytest.mark.usefixtures("pylonsapp")
@@ -62,53 +64,81 b' def test_wrong_alias():'
62 64 get_backend(alias)
63 65
64 66
65 def test_get_repo():
66 alias = 'hg'
67 path = TEST_HG_REPO
68 backend = get_backend(alias)
69 repo = backend(path)
67 def test_get_vcs_instance_by_path(vcs_repo):
68 repo = get_vcs_instance(vcs_repo.path)
70 69
71 assert repo.__class__, get_repo(path == alias).__class__
72 assert repo.path, get_repo(path == alias).path
70 assert repo.__class__ == vcs_repo.__class__
71 assert repo.path == vcs_repo.path
72 assert repo.alias == vcs_repo.alias
73 assert repo.name == vcs_repo.name
73 74
74 75
75 def test_get_repo_autoalias_hg():
76 alias = 'hg'
77 path = TEST_HG_REPO
78 backend = get_backend(alias)
79 repo = backend(path)
76 @mock.patch('rhodecode.lib.vcs.backends.get_scm')
77 @mock.patch('rhodecode.lib.vcs.backends.get_backend')
78 def test_get_vcs_instance_by_path_args_passed(
79 get_backend_mock, get_scm_mock):
80 """
81 Test that the arguments passed to ``get_vcs_instance_by_path`` are
82 forewarded to the vcs backend class.
83 """
84 backend = mock.MagicMock()
85 get_backend_mock.return_value = backend
86 args = ['these-are-test-args', 0, True, None]
87 get_vcs_instance(TEST_HG_REPO, *args)
80 88
81 assert repo.__class__ == get_repo(path).__class__
82 assert repo.path == get_repo(path).path
89 backend.assert_called_with(*args, repo_path=TEST_HG_REPO)
83 90
84 91
85 def test_get_repo_autoalias_git():
86 alias = 'git'
87 path = TEST_GIT_REPO
88 backend = get_backend(alias)
89 repo = backend(path)
92 @mock.patch('rhodecode.lib.vcs.backends.get_scm')
93 @mock.patch('rhodecode.lib.vcs.backends.get_backend')
94 def test_get_vcs_instance_by_path_kwargs_passed(
95 get_backend_mock, get_scm_mock):
96 """
97 Test that the keyword arguments passed to ``get_vcs_instance_by_path`` are
98 forewarded to the vcs backend class.
99 """
100 backend = mock.MagicMock()
101 get_backend_mock.return_value = backend
102 kwargs = {
103 'foo': 'these-are-test-args',
104 'bar': 0,
105 'baz': True,
106 'foobar': None
107 }
108 get_vcs_instance(TEST_HG_REPO, **kwargs)
90 109
91 assert repo.__class__ == get_repo(path).__class__
92 assert repo.path == get_repo(path).path
110 backend.assert_called_with(repo_path=TEST_HG_REPO, **kwargs)
93 111
94 112
95 def test_get_repo_err():
96 blank_repo_path = os.path.join(TESTS_TMP_PATH, 'blank-error-repo')
97 if os.path.isdir(blank_repo_path):
98 shutil.rmtree(blank_repo_path)
113 def test_get_vcs_instance_by_path_err(request):
114 """
115 Test that ``get_vcs_instance_by_path`` returns None if a path is passed
116 to an empty directory.
117 """
118 empty_dir = tempfile.mkdtemp(prefix='pytest-empty-dir-')
99 119
100 os.mkdir(blank_repo_path)
101 pytest.raises(VCSError, get_repo, blank_repo_path)
102 pytest.raises(VCSError, get_repo, blank_repo_path + 'non_existing')
120 def fin():
121 shutil.rmtree(empty_dir)
122 request.addfinalizer(fin)
123
124 repo = get_vcs_instance(empty_dir)
125
126 assert repo is None
103 127
104 128
105 def test_get_repo_multialias():
106 multialias_repo_path = os.path.join(TESTS_TMP_PATH, 'hg-git-repo')
107 if os.path.isdir(multialias_repo_path):
108 shutil.rmtree(multialias_repo_path)
129 def test_get_vcs_instance_by_path_multiple_repos(request):
130 """
131 Test that ``get_vcs_instance_by_path`` returns None if a path is passed
132 to a directory with multiple repositories.
133 """
134 empty_dir = tempfile.mkdtemp(prefix='pytest-empty-dir-')
135 os.mkdir(os.path.join(empty_dir, '.git'))
136 os.mkdir(os.path.join(empty_dir, '.hg'))
109 137
110 os.mkdir(multialias_repo_path)
138 def fin():
139 shutil.rmtree(empty_dir)
140 request.addfinalizer(fin)
111 141
112 os.mkdir(os.path.join(multialias_repo_path, '.git'))
113 os.mkdir(os.path.join(multialias_repo_path, '.hg'))
114 pytest.raises(VCSError, get_repo, multialias_repo_path)
142 repo = get_vcs_instance(empty_dir)
143
144 assert repo is None
@@ -20,3 +20,16 b' from pyramid.i18n import TranslationStri'
20 20
21 21 # Create a translation string factory for the 'rhodecode' domain.
22 22 _ = TranslationStringFactory('rhodecode')
23
24 class LazyString(object):
25 def __init__(self, *args, **kw):
26 self.args = args
27 self.kw = kw
28
29 def __str__(self):
30 return _(*self.args, **self.kw)
31
32
33 def lazy_ugettext(*args, **kw):
34 """ Lazily evaluated version of _() """
35 return LazyString(*args, **kw)
@@ -67,7 +67,7 b' def pylons_compatibility_tween_factory(h'
67 67 # Setup the pylons context object ('c')
68 68 context = ContextObj()
69 69 context.rhodecode_user = auth_user
70 attach_context_attributes(context)
70 attach_context_attributes(context, request)
71 71 pylons.tmpl_context._push_object(context)
72 72 return handler(request)
73 73 finally:
@@ -24,16 +24,17 b' from rhodecode_api import RhodeCodeAPI'
24 24
25 25
26 26 def add_api_pr_args(parser):
27 parser.add_argument("--url", help="Rhodecode host url", required=True)
27 28 parser.add_argument("--token", help="Rhodecode user token", required=True)
28 parser.add_argument("--url", help="Rhodecode host url", required=True)
29 29 parser.add_argument("--repoid", help="Repo id", required=True)
30 parser.add_argument("--prid", help="Pull request id", required=True)
31 30 parser.add_argument("--message", help="Comment to add", required=True)
32
33 31 parser.add_argument("--status", help="Status to set (approved|rejected)")
34 32 parser.add_argument(
33 "--prid",
34 help="(Optional) Pull request id")
35 parser.add_argument(
35 36 "--commit-id",
36 help="Expected commit ID for source repo")
37 help="(Optional) Expected commit ID for source repo")
37 38
38 39
39 40 def get_pr_head(args):
@@ -59,18 +60,30 b' if __name__ == "__main__":'
59 60 args = parser.parse_args()
60 61 status = args.status
61 62
62 if args.commit_id and status:
63 if not pr_has_same_head(args):
64 # PR updated, don't set status
65 status = None
63 rc = RhodeCodeAPI(args.url, args.token)
64
65 if not (args.prid or args.commit_id):
66 raise AttributeError('You must specify --prid or --commit-id')
67
68 if args.prid:
69 if args.commit_id and status:
70 if not pr_has_same_head(args):
71 # PR updated, don't set status
72 status = None
66 73
67 rc = RhodeCodeAPI(args.url, args.token)
68 resp = rc.comment_pull_request(
69 repoid=args.repoid,
70 pullrequestid=int(args.prid),
71 message=args.message,
72 status=status
73 )
74 resp = rc.comment_pull_request(
75 repoid=args.repoid,
76 pullrequestid=int(args.prid),
77 message=args.message,
78 status=status
79 )
80 elif args.commit_id:
81 resp = rc.comment_commit(
82 repoid=args.repoid,
83 commit_id=args.commit_id,
84 message=args.message,
85 status=status,
86 )
74 87
75 88 if resp['error']:
76 89 raise Exception(resp['error'])
@@ -70,9 +70,12 b' requirements = ['
70 70 'authomatic',
71 71 'backport_ipaddress',
72 72 'celery',
73 'channelstream',
73 74 'colander',
74 75 'decorator',
76 'deform',
75 77 'docutils',
78 'gevent',
76 79 'gunicorn',
77 80 'infrae.cache',
78 81 'ipython',
@@ -81,6 +84,7 b' requirements = ['
81 84 'msgpack-python',
82 85 'packaging',
83 86 'psycopg2',
87 'py-gfm',
84 88 'pycrypto',
85 89 'pycurl',
86 90 'pyparsing',
@@ -99,6 +103,8 b' requirements = ['
99 103 'simplejson',
100 104 'waitress',
101 105 'zope.cachedescriptors',
106 'dogpile.cache',
107 'dogpile.core'
102 108 ]
103 109
104 110 if is_windows:
@@ -38,12 +38,14 b' in enterprise.override (attrs: {'
38 38 # make development a little bit more convenient.
39 39 src = null;
40 40
41 buildInputs = attrs.buildInputs ++
42 pkgs.lib.optionals (hasVcsserver) vcsserver.propagatedNativeBuildInputs ++ [
43 pythonPackages.bumpversion
44 pythonPackages.invoke
45 pythonPackages.ipdb
46 ];
41 buildInputs =
42 attrs.buildInputs ++
43 pkgs.lib.optionals (hasVcsserver) vcsserver.propagatedNativeBuildInputs ++
44 (with pythonPackages; [
45 bumpversion
46 invoke
47 ipdb
48 ]);
47 49
48 50 shellHook = attrs.shellHook +
49 51 pkgs.lib.strings.optionalString (hasVcsserver) ''
@@ -1,24 +1,36 b''
1 ################################################################################
1
2
2 3 ################################################################################
3 # RhodeCode Enterprise - configuration file #
4 # Built-in functions and variables #
4 ## RHODECODE ENTERPRISE CONFIGURATION ##
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 # #
7 6 ################################################################################
8 7
9 8 [DEFAULT]
10 9 debug = true
11 pdebug = false
10
12 11 ################################################################################
12 ## EMAIL CONFIGURATION ##
13 13 ## Uncomment and replace with the email address which should receive ##
14 14 ## any error reports after an application crash ##
15 15 ## Additionally these settings will be used by the RhodeCode mailing system ##
16 16 ################################################################################
17 #email_to = admin@localhost
18 #error_email_from = paste_error@localhost
17
18 ## prefix all emails subjects with given prefix, helps filtering out emails
19 #email_prefix = [RhodeCode]
20
21 ## email FROM address all mails will be sent
19 22 #app_email_from = rhodecode-noreply@localhost
23
24 ## Uncomment and replace with the address which should receive any error report
25 ## note: using appenlight for error handling doesn't need this to be uncommented
26 #email_to = admin@localhost
27
28 ## in case of Application errors, sent an error email form
29 #error_email_from = rhodecode_error@localhost
30
31 ## additional error message to be send in case of server crash
20 32 #error_message =
21 #email_prefix = [RhodeCode]
33
22 34
23 35 #smtp_server = mail.server.com
24 36 #smtp_username =
@@ -34,9 +46,11 b' pdebug = false'
34 46 host = 0.0.0.0
35 47 port = 5000
36 48
37 ##########################
38 ## WAITRESS WSGI SERVER ##
39 ##########################
49 ##################################
50 ## WAITRESS WSGI SERVER ##
51 ## Recommended for Development ##
52 ##################################
53
40 54 use = egg:waitress#main
41 55 ## number of worker threads
42 56 threads = 5
@@ -46,25 +60,18 b' max_request_body_size = 107374182400'
46 60 ## May not work on old windows systems.
47 61 asyncore_use_poll = true
48 62
49 ## PASTE HTTP ##
50 #use = egg:Paste#http
51 ## nr of worker threads to spawn
52 #threadpool_workers = 5
53 ## max request before thread respawn
54 #threadpool_max_requests = 10
55 ## option to use threads of process
56 #use_threadpool = true
57 63
58 64 ##########################
59 65 ## GUNICORN WSGI SERVER ##
60 66 ##########################
61 67 ## run with gunicorn --log-config <inifile.ini> --paste <inifile.ini>
68
62 69 #use = egg:gunicorn#main
63 70 ## Sets the number of process workers. You must set `instance_id = *`
64 71 ## when this option is set to more than one worker, recommended
65 72 ## value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers
66 73 ## The `instance_id = *` must be set in the [app:main] section below
67 #workers = 1
74 #workers = 2
68 75 ## number of threads for each of the worker, must be set to 1 for gevent
69 76 ## generally recommened to be at 1
70 77 #threads = 1
@@ -73,11 +80,13 b' asyncore_use_poll = true'
73 80 ## type of worker class, one of sync, gevent
74 81 ## recommended for bigger setup is using of of other than sync one
75 82 #worker_class = sync
83 ## The maximum number of simultaneous clients. Valid only for Gevent
84 #worker_connections = 10
76 85 ## max number of requests that worker will handle before being gracefully
77 86 ## restarted, could prevent memory leaks
78 87 #max_requests = 1000
79 88 #max_requests_jitter = 30
80 ## ammount of time a worker can spend with handling a request before it
89 ## amount of time a worker can spend with handling a request before it
81 90 ## gets killed and restarted. Set to 6hrs
82 91 #timeout = 21600
83 92
@@ -142,28 +151,45 b' asyncore_use_poll = true'
142 151 #cheaper-step = 1
143 152
144 153 ## prefix middleware for RhodeCode, disables force_https flag.
154 ## recommended when using proxy setup.
145 155 ## allows to set RhodeCode under a prefix in server.
146 156 ## eg https://server.com/<prefix>. Enable `filter-with =` option below as well.
147 #[filter:proxy-prefix]
148 #use = egg:PasteDeploy#prefix
149 #prefix = /<your-prefix>
157 ## optionally set prefix like: `prefix = /<your-prefix>`
158 [filter:proxy-prefix]
159 use = egg:PasteDeploy#prefix
160 prefix = /
150 161
151 162 [app:main]
152 163 is_test = True
153 164 use = egg:rhodecode-enterprise-ce
154 ## enable proxy prefix middleware, defined below
165
166 ## enable proxy prefix middleware, defined above
155 167 #filter-with = proxy-prefix
156 168
169
170 ## RHODECODE PLUGINS ##
157 171 rhodecode.includes = rhodecode.api
158 172
159 173 # api prefix url
160 174 rhodecode.api.url = /_admin/api
161 175
162 176
163 full_stack = true
177 ## END RHODECODE PLUGINS ##
178
179 ## encryption key used to encrypt social plugin tokens,
180 ## remote_urls with credentials etc, if not set it defaults to
181 ## `beaker.session.secret`
182 #rhodecode.encrypted_values.secret =
164 183
165 ## Serve static files via RhodeCode, disable to serve them via HTTP server
166 static_files = true
184 ## decryption strict mode (enabled by default). It controls if decryption raises
185 ## `SignatureVerificationError` in case of wrong key, or damaged encryption data.
186 #rhodecode.encrypted_values.strict = false
187
188 ## return gzipped responses from Rhodecode (static files/application)
189 gzip_responses = false
190
191 ## autogenerate javascript routes file on startup
192 generate_js_files = false
167 193
168 194 ## Optional Languages
169 195 ## en(default), be, de, es, fr, it, ja, pl, pt, ru, zh
@@ -245,6 +271,21 b' default_encoding = UTF-8'
245 271 ## all running rhodecode instances. Leave empty if you don't use it
246 272 instance_id =
247 273
274 ## Fallback authentication plugin. Set this to a plugin ID to force the usage
275 ## of an authentication plugin also if it is disabled by it's settings.
276 ## This could be useful if you are unable to log in to the system due to broken
277 ## authentication settings. Then you can enable e.g. the internal rhodecode auth
278 ## module to log in again and fix the settings.
279 ##
280 ## Available builtin plugin IDs (hash is part of the ID):
281 ## egg:rhodecode-enterprise-ce#rhodecode
282 ## egg:rhodecode-enterprise-ce#pam
283 ## egg:rhodecode-enterprise-ce#ldap
284 ## egg:rhodecode-enterprise-ce#jasig_cas
285 ## egg:rhodecode-enterprise-ce#headers
286 ## egg:rhodecode-enterprise-ce#crowd
287 #rhodecode.auth_plugin_fallback = egg:rhodecode-enterprise-ce#rhodecode
288
248 289 ## alternative return HTTP header for failed authentication. Default HTTP
249 290 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
250 291 ## handling that causing a series of failed authentication calls.
@@ -336,8 +377,8 b' beaker.cache.sql_cache_short.type = memo'
336 377 beaker.cache.sql_cache_short.expire = 1
337 378 beaker.cache.sql_cache_short.key_length = 256
338 379
339 # default is memory cache, configure only if required
340 # using multi-node or multi-worker setup
380 ## default is memory cache, configure only if required
381 ## using multi-node or multi-worker setup
341 382 #beaker.cache.auth_plugins.type = ext:database
342 383 #beaker.cache.auth_plugins.lock_dir = %(here)s/data/cache/auth_plugin_lock
343 384 #beaker.cache.auth_plugins.url = postgresql://postgres:secret@localhost/rhodecode
@@ -350,8 +391,8 b' beaker.cache.repo_cache_long.type = memo'
350 391 beaker.cache.repo_cache_long.max_items = 4096
351 392 beaker.cache.repo_cache_long.expire = 2592000
352 393
353 # default is memorylru_base cache, configure only if required
354 # using multi-node or multi-worker setup
394 ## default is memorylru_base cache, configure only if required
395 ## using multi-node or multi-worker setup
355 396 #beaker.cache.repo_cache_long.type = ext:memcached
356 397 #beaker.cache.repo_cache_long.url = localhost:11211
357 398 #beaker.cache.repo_cache_long.expire = 1209600
@@ -362,12 +403,12 b' beaker.cache.repo_cache_long.expire = 25'
362 403 ####################################
363 404
364 405 ## .session.type is type of storage options for the session, current allowed
365 ## types are file(default), ext:memcached, ext:database, and memory.
366 #beaker.session.type = file
406 ## types are file, ext:memcached, ext:database, and memory (default).
407 beaker.session.type = file
408 beaker.session.data_dir = %(here)s/rc/data/sessions/data
367 409
368 ## db based session, fast, and allows easy management over logged in users ##
410 ## db based session, fast, and allows easy management over logged in users
369 411 #beaker.session.type = ext:database
370 #beaker.session.lock_dir = %(here)s/data/cache/session_db_lock
371 412 #beaker.session.table_name = db_session
372 413 #beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
373 414 #beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
@@ -376,6 +417,7 b' beaker.cache.repo_cache_long.expire = 25'
376 417
377 418 beaker.session.key = rhodecode
378 419 beaker.session.secret = test-rc-uytcxaz
420 beaker.session.lock_dir = %(here)s/rc/data/sessions/lock
379 421
380 422 ## Secure encrypted cookie. Requires AES and AES python libraries
381 423 ## you must disable beaker.session.secret to use this
@@ -386,6 +428,7 b' beaker.session.secret = test-rc-uytcxaz'
386 428 ## accessed for given amount of time in seconds
387 429 beaker.session.timeout = 2592000
388 430 beaker.session.httponly = true
431 ## Path to use for the cookie.
389 432 #beaker.session.cookie_path = /<your-prefix>
390 433
391 434 ## uncomment for https secure cookie
@@ -401,12 +444,33 b' beaker.session.auto = false'
401 444 ###################################
402 445 ## SEARCH INDEXING CONFIGURATION ##
403 446 ###################################
447 ## Full text search indexer is available in rhodecode-tools under
448 ## `rhodecode-tools index` command
404 449
450 # WHOOSH Backend, doesn't require additional services to run
451 # it works good with few dozen repos
405 452 search.module = rhodecode.lib.index.whoosh
406 453 search.location = %(here)s/data/index
407 454
455 ########################################
456 ### CHANNELSTREAM CONFIG ####
457 ########################################
458 ## channelstream enables persistent connections and live notification
459 ## in the system. It's also used by the chat system
460
461 channelstream.enabled = false
462 # location of channelstream server on the backend
463 channelstream.server = 127.0.0.1:9800
464 ## location of the channelstream server from outside world
465 ## most likely this would be an http server special backend URL, that handles
466 ## websocket connections see nginx example for config
467 channelstream.ws_url = ws://rhodecode.yourserver.com/_channelstream
468 channelstream.secret = secret
469 channelstream.history.location = %(here)s/channelstream_history
470
471
408 472 ###################################
409 ## ERROR AND LOG HANDLING SYSTEM ##
473 ## APPENLIGHT CONFIG ##
410 474 ###################################
411 475
412 476 ## Appenlight is tailored to work with RhodeCode, see
@@ -417,7 +481,7 b' appenlight = false'
417 481
418 482 appenlight.server_url = https://api.appenlight.com
419 483 appenlight.api_key = YOUR_API_KEY
420 ;appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
484 #appenlight.transport_config = https://api.appenlight.com?threaded=1&timeout=5
421 485
422 486 # used for JS client
423 487 appenlight.api_public_key = YOUR_API_PUBLIC_KEY
@@ -480,9 +544,10 b' debug_style = false'
480 544 #########################################################
481 545 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
482 546 #########################################################
483 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db
547 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db
484 548 #sqlalchemy.db1.url = postgresql://postgres:qweqwe@localhost/rhodecode_test
485 549 #sqlalchemy.db1.url = mysql://root:qweqwe@localhost/rhodecode_test
550 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode_test.db
486 551
487 552 # see sqlalchemy docs for other advanced settings
488 553
@@ -507,12 +572,57 b' sqlalchemy.db1.convert_unicode = true'
507 572 ##################
508 573 vcs.server.enable = true
509 574 vcs.server = localhost:9901
510 # Available protocols: pyro4, http
575
576 ## Web server connectivity protocol, responsible for web based VCS operatations
577 ## Available protocols are:
578 ## `pyro4` - using pyro4 server
579 ## `http` - using http-rpc backend
511 580 vcs.server.protocol = pyro4
512 vcs.server.log_level = info
513 vcs.start_server = false
581
582 ## Push/Pull operations protocol, available options are:
583 ## `pyro4` - using pyro4 server
584 ## `rhodecode.lib.middleware.utils.scm_app_http` - Http based, recommended
585 ## `vcsserver.scm_app` - internal app (EE only)
586 vcs.scm_app_implementation = pyro4
587
588 ## Push/Pull operations hooks protocol, available options are:
589 ## `pyro4` - using pyro4 server
590 ## `http` - using http-rpc backend
591 vcs.hooks.protocol = pyro4
592
593 vcs.server.log_level = debug
594 ## Start VCSServer with this instance as a subprocess, usefull for development
595 vcs.start_server = true
596
597 ## List of enabled VCS backends, available options are:
598 ## `hg` - mercurial
599 ## `git` - git
600 ## `svn` - subversion
514 601 vcs.backends = hg, git, svn
602
515 603 vcs.connection_timeout = 3600
604 ## Compatibility version when creating SVN repositories. Defaults to newest version when commented out.
605 ## Available options are: pre-1.4-compatible, pre-1.5-compatible, pre-1.6-compatible, pre-1.8-compatible
606 #vcs.svn.compatible_version = pre-1.8-compatible
607
608
609 ############################################################
610 ### Subversion proxy support (mod_dav_svn) ###
611 ### Maps RhodeCode repo groups into SVN paths for Apache ###
612 ############################################################
613 ## Enable or disable the config file generation.
614 svn.proxy.generate_config = false
615 ## Generate config file with `SVNListParentPath` set to `On`.
616 svn.proxy.list_parent_path = true
617 ## Set location and file name of generated config file.
618 svn.proxy.config_file_path = %(here)s/mod_dav_svn.conf
619 ## File system path to the directory containing the repositories served by
620 ## RhodeCode.
621 svn.proxy.parent_path_root = /path/to/repo_store
622 ## Used as a prefix to the <Location> block in the generated config file. In
623 ## most cases it should be set to `/`.
624 svn.proxy.location_root = /
625
516 626
517 627 ################################
518 628 ### LOGGING CONFIGURATION ####
@@ -530,7 +640,7 b' keys = generic, color_formatter, color_f'
530 640 ## LOGGERS ##
531 641 #############
532 642 [logger_root]
533 level = DEBUG
643 level = NOTSET
534 644 handlers = console
535 645
536 646 [logger_routes]
@@ -577,7 +687,7 b' propagate = 0'
577 687 [handler_console]
578 688 class = StreamHandler
579 689 args = (sys.stderr,)
580 level = INFO
690 level = DEBUG
581 691 formatter = generic
582 692
583 693 [handler_console_sql]
@@ -1,47 +0,0 b''
1 README - Quickstart
2 ===================
3
4 This folder contains the functional tests and automation of specification
5 examples. Details about testing can be found in
6 `/docs-internal/testing/index.rst`.
7
8
9 Setting up your Rhodecode Enterprise instance
10 ---------------------------------------------
11
12 The tests will create users and repositories as needed, so you can start with a
13 new and empty instance.
14
15 Use the following example call for the database setup of Enterprise::
16
17 paster setup-rhodecode \
18 --user=admin \
19 --email=admin@example.com \
20 --password=secret \
21 --api-key=9999999999999999999999999999999999999999 \
22 your-enterprise-config.ini
23
24 This way the username, password, and auth token of the admin user will match the
25 defaults from the test run.
26
27
28 Usage
29 -----
30
31 1. Make sure your Rhodecode Enterprise instance is running at
32 http://localhost:5000.
33
34 2. Enter `nix-shell` from the acceptance_tests folder::
35
36 cd acceptance_tests
37 nix-shell
38
39 Make sure that `rcpkgs` and `rcnixpkgs` are available on the nix path.
40
41 3. Run the tests::
42
43 py.test -c example.ini -vs
44
45 The parameter ``-vs`` allows you to see debugging output during the test
46 run. Check ``py.test --help`` and the documentation at http://pytest.org to
47 learn all details about the test runner.
@@ -1,66 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import colander
22 from colander import Invalid # noqa
23
24
25 class GroupNameType(colander.String):
26 SEPARATOR = '/'
27
28 def deserialize(self, node, cstruct):
29 result = super(GroupNameType, self).deserialize(node, cstruct)
30 return self._replace_extra_slashes(result)
31
32 def _replace_extra_slashes(self, path):
33 path = path.split(self.SEPARATOR)
34 path = [item for item in path if item]
35 return self.SEPARATOR.join(path)
36
37
38 class RepoGroupSchema(colander.Schema):
39 group_name = colander.SchemaNode(GroupNameType())
40
41
42 class RepoSchema(colander.Schema):
43 repo_name = colander.SchemaNode(GroupNameType())
44
45
46 class SearchParamsSchema(colander.MappingSchema):
47 search_query = colander.SchemaNode(
48 colander.String(),
49 missing='')
50 search_type = colander.SchemaNode(
51 colander.String(),
52 missing='content',
53 validator=colander.OneOf(['content', 'path', 'commit', 'repository']))
54 search_sort = colander.SchemaNode(
55 colander.String(),
56 missing='newfirst',
57 validator=colander.OneOf(
58 ['oldfirst', 'newfirst']))
59 page_limit = colander.SchemaNode(
60 colander.Integer(),
61 missing=10,
62 validator=colander.Range(1, 500))
63 requested_page = colander.SchemaNode(
64 colander.Integer(),
65 missing=1)
66
@@ -1,17 +0,0 b''
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
3
4 (function() {
5 "use strict";
6
7 var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-gss");
8 function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "gss"); }
9
10 MT("atComponent",
11 "[def @component] {",
12 "[tag foo] {",
13 " [property color]: [keyword black];",
14 "}",
15 "}");
16
17 })();
@@ -1,54 +0,0 b''
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
3
4 (function() {
5 "use strict";
6
7 var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-less");
8 function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "less"); }
9
10 MT("variable",
11 "[variable-2 @base]: [atom #f04615];",
12 "[qualifier .class] {",
13 " [property width]: [variable percentage]([number 0.5]); [comment // returns `50%`]",
14 " [property color]: [variable saturate]([variable-2 @base], [number 5%]);",
15 "}");
16
17 MT("amp",
18 "[qualifier .child], [qualifier .sibling] {",
19 " [qualifier .parent] [atom &] {",
20 " [property color]: [keyword black];",
21 " }",
22 " [atom &] + [atom &] {",
23 " [property color]: [keyword red];",
24 " }",
25 "}");
26
27 MT("mixin",
28 "[qualifier .mixin] ([variable dark]; [variable-2 @color]) {",
29 " [property color]: [variable darken]([variable-2 @color], [number 10%]);",
30 "}",
31 "[qualifier .mixin] ([variable light]; [variable-2 @color]) {",
32 " [property color]: [variable lighten]([variable-2 @color], [number 10%]);",
33 "}",
34 "[qualifier .mixin] ([variable-2 @_]; [variable-2 @color]) {",
35 " [property display]: [atom block];",
36 "}",
37 "[variable-2 @switch]: [variable light];",
38 "[qualifier .class] {",
39 " [qualifier .mixin]([variable-2 @switch]; [atom #888]);",
40 "}");
41
42 MT("nest",
43 "[qualifier .one] {",
44 " [def @media] ([property width]: [number 400px]) {",
45 " [property font-size]: [number 1.2em];",
46 " [def @media] [attribute print] [keyword and] [property color] {",
47 " [property color]: [keyword blue];",
48 " }",
49 " }",
50 "}");
51
52
53 MT("interpolation", ".@{[variable foo]} { [property font-weight]: [atom bold]; }");
54 })();
@@ -1,110 +0,0 b''
1 // CodeMirror, copyright (c) by Marijn Haverbeke and others
2 // Distributed under an MIT license: http://codemirror.net/LICENSE
3
4 (function() {
5 var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-scss");
6 function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); }
7
8 MT('url_with_quotation',
9 "[tag foo] { [property background]:[atom url]([string test.jpg]) }");
10
11 MT('url_with_double_quotes',
12 "[tag foo] { [property background]:[atom url]([string \"test.jpg\"]) }");
13
14 MT('url_with_single_quotes',
15 "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) }");
16
17 MT('string',
18 "[def @import] [string \"compass/css3\"]");
19
20 MT('important_keyword',
21 "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) [keyword !important] }");
22
23 MT('variable',
24 "[variable-2 $blue]:[atom #333]");
25
26 MT('variable_as_attribute',
27 "[tag foo] { [property color]:[variable-2 $blue] }");
28
29 MT('numbers',
30 "[tag foo] { [property padding]:[number 10px] [number 10] [number 10em] [number 8in] }");
31
32 MT('number_percentage',
33 "[tag foo] { [property width]:[number 80%] }");
34
35 MT('selector',
36 "[builtin #hello][qualifier .world]{}");
37
38 MT('singleline_comment',
39 "[comment // this is a comment]");
40
41 MT('multiline_comment',
42 "[comment /*foobar*/]");
43
44 MT('attribute_with_hyphen',
45 "[tag foo] { [property font-size]:[number 10px] }");
46
47 MT('string_after_attribute',
48 "[tag foo] { [property content]:[string \"::\"] }");
49
50 MT('directives',
51 "[def @include] [qualifier .mixin]");
52
53 MT('basic_structure',
54 "[tag p] { [property background]:[keyword red]; }");
55
56 MT('nested_structure',
57 "[tag p] { [tag a] { [property color]:[keyword red]; } }");
58
59 MT('mixin',
60 "[def @mixin] [tag table-base] {}");
61
62 MT('number_without_semicolon',
63 "[tag p] {[property width]:[number 12]}",
64 "[tag a] {[property color]:[keyword red];}");
65
66 MT('atom_in_nested_block',
67 "[tag p] { [tag a] { [property color]:[atom #000]; } }");
68
69 MT('interpolation_in_property',
70 "[tag foo] { #{[variable-2 $hello]}:[number 2]; }");
71
72 MT('interpolation_in_selector',
73 "[tag foo]#{[variable-2 $hello]} { [property color]:[atom #000]; }");
74
75 MT('interpolation_error',
76 "[tag foo]#{[variable foo]} { [property color]:[atom #000]; }");
77
78 MT("divide_operator",
79 "[tag foo] { [property width]:[number 4] [operator /] [number 2] }");
80
81 MT('nested_structure_with_id_selector',
82 "[tag p] { [builtin #hello] { [property color]:[keyword red]; } }");
83
84 MT('indent_mixin',
85 "[def @mixin] [tag container] (",
86 " [variable-2 $a]: [number 10],",
87 " [variable-2 $b]: [number 10])",
88 "{}");
89
90 MT('indent_nested',
91 "[tag foo] {",
92 " [tag bar] {",
93 " }",
94 "}");
95
96 MT('indent_parentheses',
97 "[tag foo] {",
98 " [property color]: [variable darken]([variable-2 $blue],",
99 " [number 9%]);",
100 "}");
101
102 MT('indent_vardef',
103 "[variable-2 $name]:",
104 " [string 'val'];",
105 "[tag tag] {",
106 " [tag inner] {",
107 " [property margin]: [number 3px];",
108 " }",
109 "}");
110 })();
1 NO CONTENT: file was removed
@@ -1,389 +0,0 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 """
22 This is a locustio based test scenario simulating users on the web interface.
23
24 To run it::
25
26 locust -f rhodecode/tests/load/performance.py --no-web -c 5 -r 5
27
28 Discover details regarding the command with the ``--help`` parameter and the
29 documentation at http://docs.locust.io/en/latest/ .
30
31 With the environment variable `SEED` it is possible to create more or less
32 repeatable runs. They are not fully repeatable, since the response times from
33 the server vary and so two test runs with the same seed value will sooner or
34 later run out of sync.
35 """
36
37 import logging
38 import os
39 import random
40 import re
41
42 from locust import HttpLocust, TaskSet, task
43
44
45 log = logging.getLogger('performance')
46
47 # List of repositories which shall be used during the test run
48 REPOSITORIES = [
49 'vcs_test_git',
50 'vcs_test_hg',
51 'vcs_test_svn',
52 # 'linux',
53 # 'cpython',
54 # 'git',
55 ]
56
57 HOST = 'http://localhost:5000'
58
59
60 # List of user accounts to login to the test system.
61 # Format: tuple(username, password)
62 USER_ACCOUNTS = [
63 ('test_admin', 'test12'),
64 ]
65
66 # Toggle this to show and aggregate results on individual URLs
67 SHOW_DETAILS = False
68
69 # Used to initialize pseudo random generators which are used to select
70 # the next task, to take a decision etc.
71 _seed = os.environ.get('SEED', None)
72
73 try:
74 _seed = int(_seed)
75 except ValueError:
76 pass
77
78 if (isinstance(_seed, basestring) and
79 os.environ.get('PYTHONHASHSEED', None) == 'random'):
80 print("Setting a SEED does not work if PYTHONHASHSEED is set to random.")
81 print("Use a numeric value for SEED or unset PYTHONHASHSEED.")
82 exit(1)
83
84 log.info("Using SEED %s", _seed)
85
86 _seeder = random.Random(_seed)
87
88
89 class BaseTaskSet(TaskSet):
90
91 _decision = None
92 _random = None
93
94 def __init__(self, parent):
95 super(BaseTaskSet, self).__init__(parent)
96 log_name = "user.%s" % (id(self.locust), )
97 self.log = log.getChild(log_name)
98
99 @property
100 def decision(self):
101 if self._decision:
102 return self._decision
103 return self.parent.decision
104
105 @property
106 def random(self):
107 if self._random:
108 return self._random
109 return self.parent.random
110
111 def get_next_task(self):
112 task = self.random.choice(self.tasks)
113 self.log.debug("next task %s", task.__name__)
114 return task
115
116 def wait(self):
117 millis = self.random.randint(self.min_wait, self.max_wait)
118 seconds = millis / 1000.0
119 self.log.debug("sleeping %s", seconds)
120 self._sleep(seconds)
121
122
123 class BrowseLatestChanges(BaseTaskSet):
124 """
125 User browsing through the latest changes form the summary page
126 """
127 page_size = 10
128
129 def on_start(self):
130 self.repo = self.decision.choose_repository()
131 self.page = 1
132 self.response = self.client.get('/%s' % (self.repo, ))
133
134 @task(20)
135 def goto_next_page(self):
136 self.page += 1
137 self.get_changelog_summary()
138
139 @task(5)
140 def goto_prev_page(self):
141 self.page -= 1
142 if self.page < 0:
143 self.interrupt()
144 self.get_changelog_summary()
145
146 @task(20)
147 def open_commit(self):
148 commits = get_commit_links(self.response.content)
149 commit = self.decision.choose(commits, "commit")
150 if not commit:
151 return
152 self.client.get(
153 commit,
154 name=self._commit_name(commit))
155
156 def get_changelog_summary(self):
157 summary = '/%s?size=%s&page=%s' % (
158 self.repo, self.page_size, self.page)
159 self.response = self.client.get(
160 summary,
161 headers={'X-PARTIAL-XHR': 'true'},
162 name=self._changelog_summary_name(summary))
163
164 @task(1)
165 def stop(self):
166 self.interrupt()
167
168 def _commit_name(self, url):
169 if SHOW_DETAILS:
170 return url
171 return '/%s/changeset/{{commit_id}}' % (self.repo, )
172
173 def _changelog_summary_name(self, url):
174 if SHOW_DETAILS:
175 return url
176 return '/%s?size={{size}}&page={{page}}' % (self.repo, )
177
178
179 class BrowseChangelog(BaseTaskSet):
180
181 def on_start(self):
182 self.repo = self.decision.choose_repository()
183 self.page = 1
184 self.open_changelog_page()
185
186 @task(20)
187 def goto_next_page(self):
188 self.page += 1
189 self.open_changelog_page()
190
191 @task(5)
192 def goto_prev_page(self):
193 self.page -= 1
194 self.open_changelog_page()
195
196 @task(20)
197 def open_commit(self):
198 commits = get_commit_links(self.response.content)
199 commit = self.decision.choose(commits, "commit")
200 if not commit:
201 return
202 self.client.get(
203 commit,
204 name=self._commit_name(commit))
205
206 def open_changelog_page(self):
207 if self.page <= 0:
208 self.interrupt()
209 changelog = '/%s/changelog?page=%s' % (self.repo, self.page)
210 self.response = self.client.get(
211 changelog,
212 name=self._changelog_page_name(changelog))
213
214 @task(1)
215 def stop(self):
216 self.interrupt()
217
218 def _commit_name(self, url):
219 if SHOW_DETAILS:
220 return url
221 return '/%s/changeset/{{commit_id}}' % (self.repo, )
222
223 def _changelog_page_name(self, url):
224 if SHOW_DETAILS:
225 return url
226 return '/%s/changelog/?page={{page}}' % (self.repo, )
227
228
229 class BrowseFiles(BaseTaskSet):
230
231 def on_start(self):
232 self.stack = []
233 self.repo = self.decision.choose_repository()
234 self.fetch_directory()
235
236 def fetch_directory(self, url=None):
237 if not url:
238 url = '/%s/files/tip/' % (self.repo, )
239 response = self.client.get(url, name=self._url_name('dir', url=url))
240 self.stack.append({
241 'directories': get_directory_links(response.content, self.repo),
242 'files': get_file_links(response.content, self.repo, 'files'),
243 'files_raw': get_file_links(response.content, self.repo, 'raw'),
244 'files_annotate': get_file_links(response.content, self.repo, 'annotate'),
245 })
246
247 @task(10)
248 def browse_directory(self):
249 dirs = self.stack[-1]['directories']
250 if not dirs:
251 return
252 url = self.decision.choose(dirs, "directory")
253 self.fetch_directory(url=url)
254
255 @task(10)
256 def browse_file(self):
257 files = self.stack[-1]['files']
258 if not files:
259 return
260 file_url = self.decision.choose(files, "file")
261 self.client.get(file_url, name=self._url_name('file', file_url))
262
263 @task(10)
264 def browse_raw_file(self):
265 files = self.stack[-1]['files_raw']
266 if not files:
267 return
268 file_url = self.decision.choose(files, "file")
269 self.client.get(file_url, name=self._url_name('file_raw', file_url))
270
271 @task(10)
272 def browse_annotate_file(self):
273 files = self.stack[-1]['files_annotate']
274 if not files:
275 return
276 file_url = self.decision.choose(files, "file")
277 self.client.get(
278 file_url, name=self._url_name('file_annotate', file_url))
279
280 @task(5)
281 def back_to_parent_directory(self):
282 if not len(self.stack) > 1:
283 return
284 self.stack.pop()
285
286 @task(1)
287 def stop(self):
288 self.interrupt()
289
290 def _url_name(self, kind, url):
291 if SHOW_DETAILS:
292 return url
293 return '/%s/%s/{{commit_id}}/{{path}}' % (self.repo, kind)
294
295
296 class UserBehavior(BaseTaskSet):
297
298 tasks = [
299 (BrowseLatestChanges, 1),
300 (BrowseChangelog, 1),
301 (BrowseFiles, 2),
302 ]
303
304 def on_start(self):
305 seed = getattr(self.locust, "seed", None)
306 self._decision = ChoiceMaker(random.Random(seed), log=self.log)
307 self._random = random.Random(seed)
308 user_account = self.decision.choose_user()
309 data = {
310 'username': user_account[0],
311 'password': user_account[1],
312 }
313 response = self.client.post(
314 '/_admin/login?came_from=/', data=data, allow_redirects=False)
315
316 # Sanity check that the login worked out.
317 # Successful login means we are redirected to "came_from".
318 assert response.status_code == 302
319 assert response.headers['location'] == self.locust.host + '/'
320
321 @task(2)
322 def browse_index_page(self):
323 self.client.get('/')
324
325
326 class WebsiteUser(HttpLocust):
327 host = HOST
328 task_set = UserBehavior
329 min_wait = 1000
330 max_wait = 1000
331
332 def __init__(self):
333 super(WebsiteUser, self).__init__()
334 self.seed = _seeder.random()
335 log.info("WebsiteUser with seed %s", self.seed)
336
337
338 class ChoiceMaker(object):
339
340 def __init__(self, random, log=None):
341 self._random = random
342 self.log = log
343
344 def choose(self, choices, kind=None):
345 if len(choices) == 0:
346 self.log.debug("nothing to choose from")
347 return None
348
349 item = self._random.choice(choices)
350 self.log.debug("choosing %s %s", kind or "item", item)
351 return item
352
353 def choose_user(self):
354 return self.choose(USER_ACCOUNTS, "user")
355
356 def choose_repository(self):
357 return self.choose(REPOSITORIES, "repository")
358
359
360 def get_commit_links(content):
361 # TODO: find out a way to read the HTML and grab elements by selector
362 links = re.findall(
363 r'<a.*?class="message-link".*?href="(.*?)".*?/a>', content)
364 return links
365
366
367 def get_directory_links(content, repo_name):
368 return _get_links(content, 'browser-dir')
369
370
371 def get_file_links(content, repo_name, link_type):
372
373 def _process(lnk, repo_name, link_type):
374 if link_type == 'files':
375 return lnk
376 elif link_type == 'raw':
377 return re.sub(r'%s/files/' % repo_name, r'%s/raw/' % repo_name, lnk)
378 elif link_type == 'annotate':
379 return re.sub(r'%s/files/' % repo_name, r'%s/annotate/' % repo_name, lnk)
380 links = []
381 for lnk in _get_links(content, 'browser-file'):
382 links.append(_process(lnk, repo_name, link_type))
383 return links
384
385
386 def _get_links(content, class_name):
387 return re.findall(
388 r'<a.*?class="' + class_name + r'.*?href="(.*?)".*?/a>',
389 content)
This diff has been collapsed as it changes many lines, (1011 lines changed) Show them Hide them
@@ -1,1011 +0,0 b''
1 ===========
2 Performance
3 ===========
4
5 This is a record of the comparison of the RC codebase before and after VCS2 changes.
6
7
8 Performance Comparison
9 ----------------------
10
11 Based on RC from:
12 https://code.rhodecode.com/johbo/rhodecode-fork
13
14 Database was SQLite loaded with large repositories:
15 Git @6c4ab27f2378ce67940b4496365043119d7ffff2 (https://github.com/git/git.git)
16 CPython @92241 (http://hg.python.org/cpython)
17
18 Command to execute the performance comparison (toggle SHOW_DETAILS in script):
19 SEED=performance locust -f rhodecode/tests/load/performance.py --no-web -c 4 -r 4 -n 500
20
21
22 RC Branch@v.2.2.5
23 +++++++++++++++++
24
25 Name # reqs # fails Avg Min Max | Median req/s
26 --------------------------------------------------------------------------------------------------------------------------------------------
27 GET / 9 0(0.00%) 585 233 1009 | 560 0.00
28 POST /_admin/login?came_from=/ 2 0(0.00%) 652 619 685 | 620 0.00
29 GET /cpython 2 0(0.00%) 633 362 904 | 360 0.00
30 GET /cpython/annotate/{revision}/{path} [file_annotate] 12 0(0.00%) 1159 335 4160 | 870 0.00
31 GET /cpython/changelog/?page={page} 169 0(0.00%) 769 208 1678 | 730 0.20
32 GET /cpython/changelog_summary?page={page} 25 0(0.00%) 455 179 946 | 360 0.00
33 GET /cpython/changeset/{changeset} 166 0(0.00%) 755 221 2762 | 700 0.20
34 GET /cpython/file/{revision}/{path} [dir] 12 0(0.00%) 1749 245 2870 | 1400 0.00
35 GET /cpython/file/{revision}/{path} [file] 14 0(0.00%) 877 233 1541 | 880 0.00
36 GET /cpython/raw/{revision}/{path} [file_raw] 18 0(0.00%) 652 211 1315 | 650 0.00
37 GET /git 1 0(0.00%) 4833 4833 4833 | 4800 0.00
38 GET /git/annotate/{revision}/{path} [file_annotate] 8 0(0.00%) 2923 1026 9228 | 1700 0.00
39 GET /git/changelog/?page={page} 24 0(0.00%) 2894 1782 5362 | 2700 0.00
40 GET /git/changelog_summary?page={page} 4 0(0.00%) 1155 508 1737 | 770 0.00
41 GET /git/changeset/{changeset} 3 0(0.00%) 551 443 742 | 470 0.00
42 GET /git/file/{revision}/{path} [dir] 11 0(0.00%) 16383 1158 53965 | 4900 0.40
43 GET /git/file/{revision}/{path} [file] 11 0(0.00%) 1088 391 1836 | 1100 0.00
44 GET /git/raw/{revision}/{path} [file_raw] 9 0(0.00%) 729 244 1363 | 720 0.10
45 --------------------------------------------------------------------------------------------------------------------------------------------
46 Total 500 0(0.00%) 0.90
47
48
49 Name # reqs # fails Avg Min Max | Median req/s
50 --------------------------------------------------------------------------------------------------------------------------------------------
51 GET / 10 0(0.00%) 601 299 1015 | 560 0.20
52 POST /_admin/login?came_from=/ 2 0(0.00%) 644 575 713 | 580 0.00
53 GET /cpython 2 0(0.00%) 782 439 1126 | 440 0.00
54 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/.hgtags 2 0(0.00%) 1470 988 1953 | 990 0.00
55 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Grammar/Grammar 2 0(0.00%) 603 451 756 | 450 0.00
56 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Makefile.pre.in 1 0(0.00%) 4010 4010 4010 | 4000 0.00
57 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Misc/RPM/README 1 0(0.00%) 1373 1373 1373 | 1400 0.00
58 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Misc/valgrind-python.supp 1 0(0.00%) 985 985 985 | 990 0.00
59 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/README 1 0(0.00%) 506 506 506 | 510 0.00
60 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/_freeze_importlib.c 1 0(0.00%) 882 882 882 | 880 0.00
61 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/_testembed.c 2 0(0.00%) 579 318 840 | 320 0.00
62 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/python.c 1 0(0.00%) 1002 1002 1002 | 1000 0.00
63 GET /cpython/changelog?page=1 6 0(0.00%) 643 296 1471 | 440 0.00
64 GET /cpython/changelog?page=10 3 0(0.00%) 1046 987 1088 | 1100 0.00
65 GET /cpython/changelog?page=11 5 0(0.00%) 579 340 932 | 570 0.00
66 GET /cpython/changelog?page=12 7 0(0.00%) 677 296 1404 | 530 0.00
67 GET /cpython/changelog?page=13 6 0(0.00%) 555 350 940 | 430 0.00
68 GET /cpython/changelog?page=14 4 0(0.00%) 323 254 371 | 330 0.00
69 GET /cpython/changelog?page=15 3 0(0.00%) 571 250 1020 | 440 0.00
70 GET /cpython/changelog?page=16 2 0(0.00%) 611 568 655 | 570 0.00
71 GET /cpython/changelog?page=17 2 0(0.00%) 848 843 854 | 840 0.00
72 GET /cpython/changelog?page=18 2 0(0.00%) 1020 804 1236 | 800 0.00
73 GET /cpython/changelog?page=19 4 0(0.00%) 729 340 1104 | 690 0.00
74 GET /cpython/changelog?page=2 4 0(0.00%) 491 258 984 | 360 0.00
75 GET /cpython/changelog?page=20 5 0(0.00%) 847 469 1093 | 860 0.00
76 GET /cpython/changelog?page=21 3 0(0.00%) 1341 1127 1503 | 1400 0.00
77 GET /cpython/changelog?page=22 3 0(0.00%) 642 528 838 | 560 0.00
78 GET /cpython/changelog?page=23 3 0(0.00%) 489 361 684 | 420 0.00
79 GET /cpython/changelog?page=24 2 0(0.00%) 588 392 785 | 390 0.00
80 GET /cpython/changelog?page=25 2 0(0.00%) 872 726 1019 | 730 0.00
81 GET /cpython/changelog?page=26 9 0(0.00%) 923 414 1648 | 920 0.00
82 GET /cpython/changelog?page=27 10 0(0.00%) 974 592 1426 | 970 0.00
83 GET /cpython/changelog?page=28 5 0(0.00%) 1105 558 1389 | 1300 0.00
84 GET /cpython/changelog?page=29 4 0(0.00%) 1009 860 1135 | 920 0.00
85 GET /cpython/changelog?page=3 4 0(0.00%) 768 356 1211 | 520 0.00
86 GET /cpython/changelog?page=30 1 0(0.00%) 398 398 398 | 400 0.00
87 GET /cpython/changelog?page=31 1 0(0.00%) 1231 1231 1231 | 1200 0.00
88 GET /cpython/changelog?page=32 1 0(0.00%) 1231 1231 1231 | 1200 0.00
89 GET /cpython/changelog?page=33 3 0(0.00%) 765 266 1115 | 920 0.00
90 GET /cpython/changelog?page=34 4 0(0.00%) 756 695 922 | 700 0.00
91 GET /cpython/changelog?page=35 2 0(0.00%) 583 480 687 | 480 0.00
92 GET /cpython/changelog?page=36 1 0(0.00%) 511 511 511 | 510 0.00
93 GET /cpython/changelog?page=37 3 0(0.00%) 926 706 1054 | 1000 0.00
94 GET /cpython/changelog?page=38 3 0(0.00%) 851 595 1015 | 940 0.00
95 GET /cpython/changelog?page=39 1 0(0.00%) 689 689 689 | 690 0.00
96 GET /cpython/changelog?page=4 5 0(0.00%) 729 443 1459 | 450 0.00
97 GET /cpython/changelog?page=40 1 0(0.00%) 1115 1115 1115 | 1100 0.00
98 GET /cpython/changelog?page=41 1 0(0.00%) 943 943 943 | 940 0.00
99 GET /cpython/changelog?page=42 1 0(0.00%) 487 487 487 | 490 0.00
100 GET /cpython/changelog?page=43 2 0(0.00%) 714 439 990 | 440 0.00
101 GET /cpython/changelog?page=44 2 0(0.00%) 741 444 1038 | 440 0.00
102 GET /cpython/changelog?page=45 1 0(0.00%) 923 923 923 | 920 0.00
103 GET /cpython/changelog?page=46 1 0(0.00%) 1245 1245 1245 | 1200 0.00
104 GET /cpython/changelog?page=47 1 0(0.00%) 1068 1068 1068 | 1100 0.00
105 GET /cpython/changelog?page=48 1 0(0.00%) 1113 1113 1113 | 1100 0.00
106 GET /cpython/changelog?page=49 1 0(0.00%) 948 948 948 | 950 0.00
107 GET /cpython/changelog?page=5 5 0(0.00%) 567 329 846 | 450 0.00
108 GET /cpython/changelog?page=50 1 0(0.00%) 1230 1230 1230 | 1200 0.00
109 GET /cpython/changelog?page=51 2 0(0.00%) 899 855 944 | 860 0.00
110 GET /cpython/changelog?page=52 2 0(0.00%) 1113 872 1355 | 870 0.00
111 GET /cpython/changelog?page=53 1 0(0.00%) 1010 1010 1010 | 1000 0.00
112 GET /cpython/changelog?page=54 2 0(0.00%) 1022 872 1173 | 870 0.10
113 GET /cpython/changelog?page=55 2 0(0.00%) 875 803 947 | 800 0.20
114 GET /cpython/changelog?page=56 1 0(0.00%) 434 434 434 | 430 0.10
115 GET /cpython/changelog?page=57 1 0(0.00%) 791 791 791 | 790 0.10
116 GET /cpython/changelog?page=58 1 0(0.00%) 665 665 665 | 670 0.00
117 GET /cpython/changelog?page=6 4 0(0.00%) 519 280 709 | 450 0.00
118 GET /cpython/changelog?page=7 4 0(0.00%) 576 201 992 | 550 0.00
119 GET /cpython/changelog?page=8 3 0(0.00%) 732 331 1280 | 590 0.00
120 GET /cpython/changelog?page=9 3 0(0.00%) 921 420 1689 | 650 0.00
121 GET /cpython/changelog_summary?size=10&page=0 1 0(0.00%) 379 379 379 | 380 0.00
122 GET /cpython/changelog_summary?size=10&page=10 1 0(0.00%) 500 500 500 | 500 0.00
123 GET /cpython/changelog_summary?size=10&page=11 2 0(0.00%) 293 248 338 | 250 0.00
124 GET /cpython/changelog_summary?size=10&page=12 1 0(0.00%) 408 408 408 | 410 0.00
125 GET /cpython/changelog_summary?size=10&page=2 3 0(0.00%) 770 188 1079 | 1000 0.00
126 GET /cpython/changelog_summary?size=10&page=3 3 0(0.00%) 718 379 1243 | 530 0.00
127 GET /cpython/changelog_summary?size=10&page=4 1 0(0.00%) 512 512 512 | 510 0.00
128 GET /cpython/changelog_summary?size=10&page=5 4 0(0.00%) 540 348 673 | 550 0.00
129 GET /cpython/changelog_summary?size=10&page=6 4 0(0.00%) 531 362 632 | 550 0.00
130 GET /cpython/changelog_summary?size=10&page=7 1 0(0.00%) 989 989 989 | 990 0.00
131 GET /cpython/changelog_summary?size=10&page=8 2 0(0.00%) 1226 1093 1360 | 1100 0.00
132 GET /cpython/changelog_summary?size=10&page=9 2 0(0.00%) 549 395 703 | 400 0.00
133 GET /cpython/changeset/0045eec1e247c9b0855d5dd0ffa724068c43b8f7 1 0(0.00%) 570 570 570 | 570 0.00
134 GET /cpython/changeset/014060738f7f89044fbe3a231b491daa69f7e366 1 0(0.00%) 1037 1037 1037 | 1000 0.00
135 GET /cpython/changeset/02b25ec13c94e87e6e8ac2876455d776377a945a 1 0(0.00%) 1523 1523 1523 | 1500 0.00
136 GET /cpython/changeset/04a2f86539b56c2161069691b586981c7e8db4d6 1 0(0.00%) 1400 1400 1400 | 1400 0.00
137 GET /cpython/changeset/057a8bd043ea759ad7fbd25a90a942f8078efff4 1 0(0.00%) 1354 1354 1354 | 1400 0.00
138 GET /cpython/changeset/05e8f92b58ff41bb4d710d80a0793728900674eb 1 0(0.00%) 1115 1115 1115 | 1100 0.00
139 GET /cpython/changeset/0788d0d6bf85c32ea72a378a9aa9efa05430d089 1 0(0.00%) 284 284 284 | 280 0.00
140 GET /cpython/changeset/0a4f286105b476a31870e36b4a4704bc2e11e541 1 0(0.00%) 1121 1121 1121 | 1100 0.00
141 GET /cpython/changeset/0b477934e0a1a1f032010ee4f21261958bd65c14 1 0(0.00%) 247 247 247 | 250 0.00
142 GET /cpython/changeset/0b79c702abda873c6f45a720f028bffeb87bb8d7 1 0(0.00%) 1445 1445 1445 | 1400 0.00
143 GET /cpython/changeset/0c57aba6b1a320127340076300e23f90c0e21d44 2 0(0.00%) 698 450 947 | 450 0.00
144 GET /cpython/changeset/17e1af9ad66c9a31fd22de3c9cf17c1ffc765422 1 0(0.00%) 1216 1216 1216 | 1200 0.00
145 GET /cpython/changeset/1b3a5d63521cb9ecb045e78f79c443e8134d6038 1 0(0.00%) 833 833 833 | 830 0.00
146 GET /cpython/changeset/1c48171fc8da0225f22f498b699ecb7f3f6db41d 1 0(0.00%) 384 384 384 | 380 0.00
147 GET /cpython/changeset/263701e0b77e3160bc6a835087f838bd6b24092a 1 0(0.00%) 1407 1407 1407 | 1400 0.00
148 GET /cpython/changeset/275da9f9d7d7c4049409d26b02786326a23e870d 1 0(0.00%) 733 733 733 | 730 0.00
149 GET /cpython/changeset/27feb652d3add1391e9665b4362cae80c28dd508 1 0(0.00%) 704 704 704 | 700 0.00
150 GET /cpython/changeset/294b598afbb6878a65cf36ea2ca7065b4684a7e8 3 0(0.00%) 457 366 597 | 410 0.00
151 GET /cpython/changeset/29665ab293faf5d378445c0ed6889ee81702d0c9 2 0(0.00%) 764 493 1036 | 490 0.00
152 GET /cpython/changeset/2b7a4cbed2a0ec4f9ab7afd9d0e9b9b492d25718 1 0(0.00%) 940 940 940 | 940 0.00
153 GET /cpython/changeset/39f9cd7ad19abb1620268efff6d53e4d1521619d 1 0(0.00%) 973 973 973 | 970 0.00
154 GET /cpython/changeset/3b5279b5bfd17666d5fd3729782c538cadf5a264 1 0(0.00%) 831 831 831 | 830 0.00
155 GET /cpython/changeset/3b6b905ae229d2fa06268615c696a3a64aded264 2 0(0.00%) 1243 614 1873 | 610 0.00
156 GET /cpython/changeset/3d45155b7b9bcfd00758f5fc11380e60aaf71d83 1 0(0.00%) 536 536 536 | 540 0.00
157 GET /cpython/changeset/42ced0d023cd4e74f0317253491fc18f1a3c9d23 3 0(0.00%) 1080 840 1287 | 1100 0.00
158 GET /cpython/changeset/438da6ae38faa5d3b0dc998c72ac10aeb3c7f268 1 0(0.00%) 810 810 810 | 810 0.00
159 GET /cpython/changeset/452f9486a0114923caba8607673b931fab0f769d 1 0(0.00%) 752 752 752 | 750 0.00
160 GET /cpython/changeset/45b208ae9eabfc335a2f3e8fa26606195bc62ecb 1 0(0.00%) 641 641 641 | 640 0.00
161 GET /cpython/changeset/488daf4691f6171b61e8b7f7722fdfda742baa48 1 0(0.00%) 782 782 782 | 780 0.00
162 GET /cpython/changeset/49085b7460291e4ce7e2113d02e28ea41eec752d 4 0(0.00%) 811 396 1075 | 830 0.00
163 GET /cpython/changeset/4c0966b7dbe102f4ccf153e18bf669eab4f130cc 1 0(0.00%) 370 370 370 | 370 0.00
164 GET /cpython/changeset/4d4a9094bdb008baaa9ccacfd654c3b0b90c38a0 1 0(0.00%) 1811 1811 1811 | 1800 0.00
165 GET /cpython/changeset/4f359c631bb0f69c536cba1078421cf01c35b3ee 1 0(0.00%) 1125 1125 1125 | 1100 0.00
166 GET /cpython/changeset/4fe27263f9d494ac8ab3a14a489a00d7f1289c34 1 0(0.00%) 964 964 964 | 960 0.00
167 GET /cpython/changeset/5496bf8972b6f1f2b3d39baf6e6591363c4b79a7 1 0(0.00%) 590 590 590 | 590 0.00
168 GET /cpython/changeset/5875c50e93fee1d3832cfa19933e0e9c8788c49b 1 0(0.00%) 872 872 872 | 870 0.00
169 GET /cpython/changeset/58e0d2c3ead8ff3dfe0cb5a6f79c0f69dde281b7 1 0(0.00%) 991 991 991 | 990 0.00
170 GET /cpython/changeset/5a157e3b3c47ed38e1f1f4c466f68c3c35585667 1 0(0.00%) 738 738 738 | 740 0.00
171 GET /cpython/changeset/5a299c3ec12060de9d59dd0e0aff1ffb099115f6 1 0(0.00%) 762 762 762 | 760 0.00
172 GET /cpython/changeset/5a58f6e793ccf17393c725e01b741c0fec5450f2 2 0(0.00%) 679 456 902 | 460 0.00
173 GET /cpython/changeset/5ac811cbec879f87ca1c6f91f2bd9f3ead8f3a85 1 0(0.00%) 1034 1034 1034 | 1000 0.00
174 GET /cpython/changeset/5c0fa5fd0fb59a87d639cac44aba2abed0569d7f 1 0(0.00%) 1370 1370 1370 | 1400 0.00
175 GET /cpython/changeset/5c54f3818efd86d3269b71874e4bef76b352ee83 1 0(0.00%) 768 768 768 | 770 0.00
176 GET /cpython/changeset/5c60dd51818286ce20c242ef4f3600421c711955 1 0(0.00%) 1339 1339 1339 | 1300 0.00
177 GET /cpython/changeset/5c9b508e442f8e16307b62a1e9d1775a612f8172 1 0(0.00%) 446 446 446 | 450 0.00
178 GET /cpython/changeset/619feea86ce4b63f358e62b0b8e132b7c6b7f17b 1 0(0.00%) 299 299 299 | 300 0.00
179 GET /cpython/changeset/6298895a52de36edaf182316a1081ccdfebf134e 1 0(0.00%) 989 989 989 | 990 0.00
180 GET /cpython/changeset/668f6938fb1cd72b5f2d58626b02eb8a81204eb3 1 0(0.00%) 480 480 480 | 480 0.00
181 GET /cpython/changeset/691ca1694fe79f299478b0aa9acbe9a06254e65b 1 0(0.00%) 226 226 226 | 230 0.00
182 GET /cpython/changeset/69783040dd543d748f7197fdf7d9e147ba9cbc71 1 0(0.00%) 470 470 470 | 470 0.00
183 GET /cpython/changeset/6dd4c2d30b0e6c31506e5b11a9db081cd61fbdf7 1 0(0.00%) 1297 1297 1297 | 1300 0.00
184 GET /cpython/changeset/71567539303222f1e59e10291205401a3ffba6a0 1 0(0.00%) 844 844 844 | 840 0.00
185 GET /cpython/changeset/766570a5d6070488cbf5acf967c500088d1f4c17 1 0(0.00%) 440 440 440 | 440 0.00
186 GET /cpython/changeset/7749d30d9caa81113f5a84578e55e5336251eb66 1 0(0.00%) 783 783 783 | 780 0.00
187 GET /cpython/changeset/77fbbb5a73064ee17425b5fd8e6d68cf91ca33ab 1 0(0.00%) 1202 1202 1202 | 1200 0.00
188 GET /cpython/changeset/78fc2465f11473a29963400617aa0e633e3f2cbf 1 0(0.00%) 560 560 560 | 560 0.00
189 GET /cpython/changeset/78fff5310206ad496e79f936da351e52397c41af 1 0(0.00%) 852 852 852 | 850 0.00
190 GET /cpython/changeset/79562a31e5a621fde97ed0f1b8700b6dfc58452b 1 0(0.00%) 1106 1106 1106 | 1100 0.10
191 GET /cpython/changeset/7a1737033a23aae00da70dc8159322e46a7a6551 1 0(0.00%) 234 234 234 | 230 0.00
192 GET /cpython/changeset/7c19f1f792c6c8d5a7e4c3e8d424318c3fe0663b 1 0(0.00%) 880 880 880 | 880 0.00
193 GET /cpython/changeset/823f5507bd864cdb1899dbc05a42355bf7188728 1 0(0.00%) 699 699 699 | 700 0.00
194 GET /cpython/changeset/833325d45113ca81b22a6ef8e753b8e2fe26b04c 1 0(0.00%) 1156 1156 1156 | 1200 0.00
195 GET /cpython/changeset/897c9e6ddb1ac1d467d2bf700aecbc585c6e33ac 1 0(0.00%) 274 274 274 | 270 0.00
196 GET /cpython/changeset/8b8b79e1f8626a69c3f2abccb2147e148d52173e 1 0(0.00%) 1375 1375 1375 | 1400 0.00
197 GET /cpython/changeset/906df134924d7e71adcf65dea81ccaeb034cd530 3 0(0.00%) 1052 710 1236 | 1200 0.00
198 GET /cpython/changeset/90db391ce7a5c7fd36c157703a9e5fe46578225e 1 0(0.00%) 682 682 682 | 680 0.00
199 GET /cpython/changeset/90e3feac9a2dacd20a9bfb7bffb7b9147bf32ee7 3 0(0.00%) 660 513 752 | 720 0.00
200 GET /cpython/changeset/92d691c3ca009b890bd844708ef60335424295f6 1 0(0.00%) 1007 1007 1007 | 1000 0.00
201 GET /cpython/changeset/944fc499ccd30a5a4d868373b69d8585bd07cdf7 1 0(0.00%) 941 941 941 | 940 0.00
202 GET /cpython/changeset/958e8bebda6d3ec7fee21e0ede5a871b1c818948 1 0(0.00%) 735 735 735 | 740 0.00
203 GET /cpython/changeset/959fb56294ec20849884116b17b8ba102ef54bac 1 0(0.00%) 589 589 589 | 590 0.00
204 GET /cpython/changeset/98a2e215ff0177436421b65de6d0e8221ad1d91f 1 0(0.00%) 1273 1273 1273 | 1300 0.00
205 GET /cpython/changeset/9a84c335d8e11926ddd71366e6085cc7c168cc0c 1 0(0.00%) 410 410 410 | 410 0.00
206 GET /cpython/changeset/9b450b19aa118c4888b6acc6ba5e8f0fb00aaa87 1 0(0.00%) 862 862 862 | 860 0.00
207 GET /cpython/changeset/9cd3ab7c09d190c077c42dd9cbfe43feb697132b 1 0(0.00%) 1218 1218 1218 | 1200 0.00
208 GET /cpython/changeset/a0ae1ac69925b3ae866c7e089e95400aa3681a42 1 0(0.00%) 1178 1178 1178 | 1200 0.00
209 GET /cpython/changeset/a4095a895425ea48960b46d16aff2eed9d11943e 1 0(0.00%) 459 459 459 | 460 0.00
210 GET /cpython/changeset/a74c48aa43eb5109351b49d8b1e420612389a2e8 2 0(0.00%) 420 250 590 | 250 0.00
211 GET /cpython/changeset/a821cc06082b302451e829015e151151fd0964ac 1 0(0.00%) 695 695 695 | 700 0.00
212 GET /cpython/changeset/a8db78d7b6574b425d6b1a38f55068761f5da2e7 1 0(0.00%) 1134 1134 1134 | 1100 0.00
213 GET /cpython/changeset/a91f8766f75583d7dc0e6dfebff0fea056f15e56 1 0(0.00%) 668 668 668 | 670 0.00
214 GET /cpython/changeset/a9637b29954d952ffcdde6bd76cd2202282427a0 1 0(0.00%) 1380 1380 1380 | 1400 0.00
215 GET /cpython/changeset/ad7ac2a469e1e9858825a646ffcbce68aae8ed40 1 0(0.00%) 898 898 898 | 900 0.00
216 GET /cpython/changeset/b05d4f3ee19050f681db045c0b5d39ef59c4841b 1 0(0.00%) 776 776 776 | 780 0.00
217 GET /cpython/changeset/b1c82ef96862101642deb702e046763f09678c3f 1 0(0.00%) 371 371 371 | 370 0.00
218 GET /cpython/changeset/b3aa30f474c4c5dd8f12c92c1156d7aa5393d72b 1 0(0.00%) 955 955 955 | 960 0.00
219 GET /cpython/changeset/b57b4e3b83ffc69a0d364fd33f63c857425f3299 1 0(0.00%) 484 484 484 | 480 0.00
220 GET /cpython/changeset/b63b8c5628da906d780207e322083e3c3de3afec 1 0(0.00%) 862 862 862 | 860 0.00
221 GET /cpython/changeset/b7af119f78c3ff9dd53a90f91cdd4efcb8d8d49e 1 0(0.00%) 325 325 325 | 330 0.00
222 GET /cpython/changeset/b82ff2d7f5efec2e8a8ba7b2b8e4b0d68901d859 1 0(0.00%) 604 604 604 | 600 0.00
223 GET /cpython/changeset/b836a0cd68f7dc64d918c9d80e0e9793877f7dbe 2 0(0.00%) 573 254 893 | 250 0.00
224 GET /cpython/changeset/ba141f9e58b658b151cb6d40086821296109e37c 1 0(0.00%) 536 536 536 | 540 0.00
225 GET /cpython/changeset/bb28542af0601fd468ad60b584e4dd9de08faa4a 1 0(0.00%) 406 406 406 | 410 0.00
226 GET /cpython/changeset/bc991d4f9ce70bdcc3da7cd0b1fe916a17a0a805 2 0(0.00%) 1140 768 1512 | 770 0.00
227 GET /cpython/changeset/bda68f9732288de00e503979f2cae36e9a8096f1 1 0(0.00%) 1453 1453 1453 | 1500 0.00
228 GET /cpython/changeset/bf0221ca3477f065583039133806ef6887e7b291 2 0(0.00%) 370 319 421 | 320 0.00
229 GET /cpython/changeset/c48980af7df207e8c606e00c054eadb513f59d37 2 0(0.00%) 622 598 646 | 600 0.00
230 GET /cpython/changeset/c8ce5bca0fcda4307f7ac5d69103ce128a562705 1 0(0.00%) 1136 1136 1136 | 1100 0.00
231 GET /cpython/changeset/ca4a22256bf88452349c2ff54b90a4bd983cb72d 2 0(0.00%) 530 275 785 | 280 0.00
232 GET /cpython/changeset/cb8fc24e209f91cfde35c698897797b7c729ef7f 2 0(0.00%) 843 373 1314 | 370 0.00
233 GET /cpython/changeset/cdda2656ceae9d9807ff31ae796ac44aa652f6b0 1 0(0.00%) 860 860 860 | 860 0.00
234 GET /cpython/changeset/ce50e5f724e9c1dbe81ddf36cb2134fce81faaa8 2 0(0.00%) 1023 527 1520 | 530 0.00
235 GET /cpython/changeset/d002c4d60ed4cc6015662256d564070fa6981eb5 1 0(0.00%) 605 605 605 | 610 0.00
236 GET /cpython/changeset/d2041159e8edcb34ccac5978754e7b4b6806b663 2 0(0.00%) 1033 884 1182 | 880 0.00
237 GET /cpython/changeset/d4fed34877929b31a08e23ea7c686e6d5f9c56d5 2 0(0.00%) 583 320 846 | 320 0.00
238 GET /cpython/changeset/d85fcf23549e002626618196d930d3c807d2b20e 1 0(0.00%) 1119 1119 1119 | 1100 0.00
239 GET /cpython/changeset/d9da4b77624b522ce367a37d737acd208b3f793f 1 0(0.00%) 1059 1059 1059 | 1100 0.00
240 GET /cpython/changeset/dac25d8ac95a7841879d17ac99394d561deea88d 1 0(0.00%) 391 391 391 | 390 0.00
241 GET /cpython/changeset/db600c927b2b87537c5e71ce39d825675cfb5e63 1 0(0.00%) 575 575 575 | 580 0.00
242 GET /cpython/changeset/dd064d4b739eb626f407d074bda5f860c4533bfe 1 0(0.00%) 868 868 868 | 870 0.00
243 GET /cpython/changeset/dea18d1104b6f67a790e6ff62f483a27ca6e5123 2 0(0.00%) 1200 1110 1290 | 1100 0.00
244 GET /cpython/changeset/e0510a3bdf8fa1bfe061f856ec5e5877cb26d259 1 0(0.00%) 857 857 857 | 860 0.00
245 GET /cpython/changeset/e0561df131aaf6580523fbdb172239922e0026e9 1 0(0.00%) 838 838 838 | 840 0.00
246 GET /cpython/changeset/e0f681f4ade3af52915d5f32daac97ada580d71a 4 0(0.00%) 732 265 1453 | 300 0.00
247 GET /cpython/changeset/e205bce4cc0ac68e0255228e06d23a6e6632795d 1 0(0.00%) 1324 1324 1324 | 1300 0.00
248 GET /cpython/changeset/e22d0ff286f9fa8b41b72420f165bad369312521 1 0(0.00%) 225 225 225 | 230 0.00
249 GET /cpython/changeset/e6bb59b6b85c14f25582961abcdd6a2c7e16f487 1 0(0.00%) 579 579 579 | 580 0.00
250 GET /cpython/changeset/e6be001c070b07bdcfaa1bec64b822d32400f309 1 0(0.00%) 890 890 890 | 890 0.00
251 GET /cpython/changeset/eaddc617af1ea7f2137cd59255e9df366910be6e 1 0(0.00%) 1052 1052 1052 | 1100 0.00
252 GET /cpython/changeset/ec2efc094d6245dbd0ce2a2fca394a15ee627888 2 0(0.00%) 1128 1109 1147 | 1100 0.00
253 GET /cpython/changeset/eee636e3edbe117a552d637afc0bcaf88f5c42a9 1 0(0.00%) 994 994 994 | 990 0.00
254 GET /cpython/changeset/efb0a3eb4e06e649d61dc9f96d3a8962974cf04f 2 0(0.00%) 881 863 900 | 860 0.00
255 GET /cpython/changeset/f138a74ee6119403afdfb4b7f5e83d8c0f3c7f59 1 0(0.00%) 414 414 414 | 410 0.00
256 GET /cpython/changeset/f3b9814fb81a8435f5bfe343ba2863537a237703 2 0(0.00%) 965 905 1025 | 910 0.00
257 GET /cpython/changeset/f426bd85f8083504e09cb0dd670a8530f121a039 1 0(0.00%) 827 827 827 | 830 0.00
258 GET /cpython/changeset/f574c7b18279bbb69942f4149e4fa429d589fd1c 1 0(0.00%) 1428 1428 1428 | 1400 0.00
259 GET /cpython/changeset/fd5f7ce6b66ab58e0b8889f82a4845b3e0927bf5 2 0(0.00%) 524 280 769 | 280 0.00
260 GET /cpython/changeset/fdfa15a9243c93e20366fb7e40747c556c3c43ba 1 0(0.00%) 3237 3237 3237 | 3200 0.00
261 GET /cpython/changeset/ffbbd43d73426f19c1947d533fbeab805a85612a 1 0(0.00%) 1256 1256 1256 | 1300 0.00
262 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/ 2 0(0.00%) 1024 885 1164 | 890 0.00
263 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Grammar 3 0(0.00%) 596 204 992 | 590 0.00
264 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Grammar/Grammar 2 0(0.00%) 616 607 625 | 610 0.00
265 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Makefile.pre.in 1 0(0.00%) 1549 1549 1549 | 1500 0.00
266 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc 3 0(0.00%) 2755 2083 3259 | 2900 0.00
267 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/ACKS 1 0(0.00%) 951 951 951 | 950 0.00
268 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/RPM 1 0(0.00%) 1126 1126 1126 | 1100 0.00
269 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/coverity_model.c 1 0(0.00%) 1076 1076 1076 | 1100 0.00
270 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-config.in 1 0(0.00%) 1063 1063 1063 | 1100 0.00
271 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-wing4.wpr 1 0(0.00%) 368 368 368 | 370 0.00
272 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-wing5.wpr 1 0(0.00%) 1029 1029 1029 | 1000 0.00
273 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python.pc.in 1 0(0.00%) 733 733 733 | 730 0.00
274 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/PC 1 0(0.00%) 1452 1452 1452 | 1500 0.00
275 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Programs 1 0(0.00%) 1205 1205 1205 | 1200 0.00
276 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Programs/README 2 0(0.00%) 473 212 735 | 210 0.00
277 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Programs/python.c 1 0(0.00%) 430 430 430 | 430 0.00
278 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/README 1 0(0.00%) 1623 1623 1623 | 1600 0.00
279 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/install-sh 1 0(0.00%) 1072 1072 1072 | 1100 0.00
280 GET /cpython/files/tip/ 1 0(0.00%) 1676 1676 1676 | 1700 0.00
281 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/.hgtouch 1 0(0.00%) 309 309 309 | 310 0.00
282 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Grammar/Grammar 5 0(0.00%) 452 311 674 | 370 0.00
283 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Misc/NEWS 1 0(0.00%) 525 525 525 | 530 0.00
284 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Misc/coverity_model.c 1 0(0.00%) 618 618 618 | 620 0.00
285 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-wing4.wpr 1 0(0.00%) 610 610 610 | 610 0.00
286 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Programs/README 1 0(0.00%) 405 405 405 | 410 0.00
287 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Programs/_freeze_importlib.c 5 0(0.00%) 421 186 916 | 280 0.00
288 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Programs/_testembed.c 1 0(0.00%) 512 512 512 | 510 0.00
289 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/config.sub 2 0(0.00%) 786 381 1191 | 380 0.00
290 GET /git 2 0(0.00%) 2276 1432 3120 | 1400 0.10
291 GET /git/annotate/333b5fc1dbf59968868c108f0ef018f6a052f330/git-bisect.sh 1 0(0.00%) 3815 3815 3815 | 3800 0.00
292 GET /git/annotate/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/.gitignore 2 0(0.00%) 1158 959 1357 | 960 0.00
293 GET /git/annotate/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/Makefile 1 0(0.00%) 2278 2278 2278 | 2300 0.00
294 GET /git/annotate/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/gitk 1 0(0.00%) 1055 1055 1055 | 1100 0.00
295 GET /git/annotate/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/po/de.po 1 0(0.00%) 6939 6939 6939 | 6900 0.00
296 GET /git/annotate/333b5fc1dbf59968868c108f0ef018f6a052f330/templates/info--exclude 1 0(0.00%) 1611 1611 1611 | 1600 0.00
297 GET /git/annotate/333b5fc1dbf59968868c108f0ef018f6a052f330/walker.h 1 0(0.00%) 1733 1733 1733 | 1700 0.00
298 GET /git/changelog?page=1 1 0(0.00%) 1895 1895 1895 | 1900 0.00
299 GET /git/changelog?page=10 1 0(0.00%) 1625 1625 1625 | 1600 0.00
300 GET /git/changelog?page=11 1 0(0.00%) 1705 1705 1705 | 1700 0.00
301 GET /git/changelog?page=12 1 0(0.00%) 2978 2978 2978 | 3000 0.00
302 GET /git/changelog?page=13 1 0(0.00%) 3444 3444 3444 | 3400 0.00
303 GET /git/changelog?page=14 1 0(0.00%) 2669 2669 2669 | 2700 0.00
304 GET /git/changelog?page=15 1 0(0.00%) 2434 2434 2434 | 2400 0.00
305 GET /git/changelog?page=16 1 0(0.00%) 3245 3245 3245 | 3200 0.00
306 GET /git/changelog?page=17 1 0(0.00%) 1878 1878 1878 | 1900 0.00
307 GET /git/changelog?page=18 1 0(0.00%) 2589 2589 2589 | 2600 0.00
308 GET /git/changelog?page=2 1 0(0.00%) 2246 2246 2246 | 2200 0.00
309 GET /git/changelog?page=3 1 0(0.00%) 2801 2801 2801 | 2800 0.00
310 GET /git/changelog?page=4 1 0(0.00%) 3448 3448 3448 | 3400 0.00
311 GET /git/changelog?page=5 2 0(0.00%) 2505 2337 2673 | 2300 0.00
312 GET /git/changelog?page=6 3 0(0.00%) 3466 2849 3782 | 3800 0.00
313 GET /git/changelog?page=7 3 0(0.00%) 2506 2116 2987 | 2400 0.00
314 GET /git/changelog?page=8 2 0(0.00%) 2193 2175 2212 | 2200 0.00
315 GET /git/changelog?page=9 1 0(0.00%) 2201 2201 2201 | 2200 0.00
316 GET /git/changelog_summary?size=10&page=2 3 0(0.00%) 1033 655 1268 | 1200 0.10
317 GET /git/changelog_summary?size=10&page=3 3 0(0.00%) 1118 524 1534 | 1300 0.10
318 GET /git/changelog_summary?size=10&page=4 1 0(0.00%) 1108 1108 1108 | 1100 0.10
319 GET /git/changelog_summary?size=10&page=5 1 0(0.00%) 941 941 941 | 940 0.00
320 GET /git/changeset/252e072e33117ded3a758af0985ac53b14685923 2 0(0.00%) 1019 830 1209 | 830 0.00
321 GET /git/changeset/bfa668e72684c2661c0fde9f00946d5a9316c79e 1 0(0.00%) 1192 1192 1192 | 1200 0.00
322 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git 3 0(0.00%) 790 706 840 | 830 0.00
323 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/.gitignore 3 0(0.00%) 1155 794 1464 | 1200 0.00
324 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/Makefile 1 0(0.00%) 981 981 981 | 980 0.00
325 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/po 2 0(0.00%) 3812 3784 3841 | 3800 0.00
326 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/po/.gitignore 1 0(0.00%) 554 554 554 | 550 0.00
327 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/po/hu.po 1 0(0.00%) 1119 1119 1119 | 1100 0.00
328 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/po/sv.po 1 0(0.00%) 1064 1064 1064 | 1100 0.00
329 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/notes-utils.h 1 0(0.00%) 712 712 712 | 710 0.00
330 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/perl 1 0(0.00%) 2637 2637 2637 | 2600 0.00
331 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/perl/Git 1 0(0.00%) 1507 1507 1507 | 1500 0.00
332 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/quote.h 1 0(0.00%) 733 733 733 | 730 0.00
333 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/strbuf.h 1 0(0.00%) 1107 1107 1107 | 1100 0.00
334 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/templates 1 0(0.00%) 5632 5632 5632 | 5600 0.00
335 GET /git/files/333b5fc1dbf59968868c108f0ef018f6a052f330/templates/hooks--prepare-commit-msg.sample 1 0(0.00%) 613 613 613 | 610 0.00
336 GET /git/files/tip/ 3 0(0.00%) 49279 48580 49734 | 50000 0.00
337 GET /git/raw/333b5fc1dbf59968868c108f0ef018f6a052f330/git.rc 1 0(0.00%) 843 843 843 | 840 0.00
338 GET /git/raw/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/.gitignore 1 0(0.00%) 683 683 683 | 680 0.00
339 GET /git/raw/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/Makefile 1 0(0.00%) 631 631 631 | 630 0.00
340 GET /git/raw/333b5fc1dbf59968868c108f0ef018f6a052f330/gitk-git/po/it.po 1 0(0.00%) 870 870 870 | 870 0.00
341 GET /git/raw/333b5fc1dbf59968868c108f0ef018f6a052f330/list-objects.h 1 0(0.00%) 812 812 812 | 810 0.00
342 GET /git/raw/333b5fc1dbf59968868c108f0ef018f6a052f330/perl/Git/I18N.pm 2 0(0.00%) 1029 931 1128 | 930 0.00
343 GET /git/raw/333b5fc1dbf59968868c108f0ef018f6a052f330/templates/branches-- 1 0(0.00%) 368 368 368 | 370 0.00
344 GET /git/raw/333b5fc1dbf59968868c108f0ef018f6a052f330/templates/this--description 1 0(0.00%) 753 753 753 | 750 0.00
345 --------------------------------------------------------------------------------------------------------------------------------------------
346 Total 500 0(0.00%) 1.20
347
348
349 RC Branch @5801 (with VCS2)
350 +++++++++++++++++++++++++++
351
352 Name # reqs # fails Avg Min Max | Median req/s
353 --------------------------------------------------------------------------------------------------------------------------------------------
354 GET / 9 0(0.00%) 418 204 734 | 420 0.20
355 POST /_admin/login?came_from=/ 2 0(0.00%) 679 655 703 | 660 0.00
356 GET /cpython 2 0(0.00%) 1970 1927 2013 | 1900 0.00
357 GET /cpython/annotate/{revision}/{path} [file_annotate] 12 0(0.00%) 19591 1457 109765 | 10000 0.00
358 GET /cpython/changelog/?page={page} 144 0(0.00%) 4549 2478 6852 | 4700 0.30
359 GET /cpython/changelog_summary?page={page} 25 0(0.00%) 1911 1134 2619 | 1900 0.00
360 GET /cpython/changeset/{changeset} 155 0(0.00%) 1032 520 1922 | 1000 0.30
361 GET /cpython/file/{revision}/{path} [dir] 12 0(0.00%) 3099 708 7210 | 3100 0.00
362 GET /cpython/file/{revision}/{path} [file] 14 0(0.00%) 1191 730 1679 | 1100 0.00
363 GET /cpython/raw/{revision}/{path} [file_raw] 18 0(0.00%) 710 432 1239 | 650 0.00
364 GET /git 2 0(0.00%) 2262 906 3619 | 910 0.10
365 GET /git/annotate/{revision}/{path} [file_annotate] 17 0(0.00%) 3527 496 18817 | 1600 0.10
366 GET /git/changelog/?page={page} 24 0(0.00%) 1958 1138 2685 | 2000 0.00
367 GET /git/changelog_summary?page={page} 7 0(0.00%) 1161 926 1601 | 1100 0.20
368 GET /git/changeset/{changeset} 3 0(0.00%) 775 564 886 | 880 0.00
369 GET /git/file/{revision}/{path} [dir] 17 0(0.00%) 15398 773 47140 | 3300 0.00
370 GET /git/file/{revision}/{path} [file] 19 0(0.00%) 838 527 1354 | 790 0.00
371 GET /git/raw/{revision}/{path} [file_raw] 18 0(0.00%) 465 256 698 | 480 0.20
372 --------------------------------------------------------------------------------------------------------------------------------------------
373 Total 500 0(0.00%) 1.40
374
375
376 Name # reqs # fails Avg Min Max | Median req/s
377 --------------------------------------------------------------------------------------------------------------------------------------------
378 GET / 9 0(0.00%) 457 175 916 | 360 0.20
379 POST /_admin/login?came_from=/ 2 0(0.00%) 622 530 714 | 530 0.00
380 GET /cpython 2 0(0.00%) 2148 2029 2267 | 2000 0.00
381 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/.hgtags 2 0(0.00%) 10790 10557 11023 | 11000 0.00
382 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Grammar/Grammar 2 0(0.00%) 10247 9447 11047 | 9400 0.00
383 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Makefile.pre.in 1 0(0.00%) 108652 108652 108652 | 109000 0.00
384 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Misc/RPM/README 1 0(0.00%) 3652 3652 3652 | 3700 0.00
385 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Misc/valgrind-python.supp 1 0(0.00%) 37846 37846 37846 | 38000 0.00
386 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/README 1 0(0.00%) 1241 1241 1241 | 1200 0.00
387 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/_freeze_importlib.c 1 0(0.00%) 10121 10121 10121 | 10000 0.00
388 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/_testembed.c 2 0(0.00%) 10322 10274 10370 | 10000 0.00
389 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/python.c 1 0(0.00%) 7431 7431 7431 | 7400 0.00
390 GET /cpython/changelog?page=1 6 0(0.00%) 4629 3215 5562 | 5100 0.00
391 GET /cpython/changelog?page=10 3 0(0.00%) 3946 2958 5876 | 3000 0.00
392 GET /cpython/changelog?page=11 5 0(0.00%) 4140 3180 5288 | 3900 0.00
393 GET /cpython/changelog?page=12 7 0(0.00%) 3742 2651 5150 | 3800 0.00
394 GET /cpython/changelog?page=13 6 0(0.00%) 3773 2980 5547 | 3300 0.00
395 GET /cpython/changelog?page=14 4 0(0.00%) 5047 3554 6506 | 4600 0.00
396 GET /cpython/changelog?page=15 3 0(0.00%) 4083 3481 5273 | 3500 0.00
397 GET /cpython/changelog?page=16 2 0(0.00%) 4617 3348 5886 | 3300 0.00
398 GET /cpython/changelog?page=17 2 0(0.00%) 5610 5529 5692 | 5500 0.00
399 GET /cpython/changelog?page=18 2 0(0.00%) 4435 3253 5618 | 3300 0.00
400 GET /cpython/changelog?page=19 4 0(0.00%) 4645 3749 5533 | 4200 0.00
401 GET /cpython/changelog?page=2 4 0(0.00%) 3889 2837 5693 | 3200 0.00
402 GET /cpython/changelog?page=20 5 0(0.00%) 5010 4095 5874 | 4900 0.00
403 GET /cpython/changelog?page=21 3 0(0.00%) 4414 3458 5706 | 4100 0.00
404 GET /cpython/changelog?page=22 3 0(0.00%) 4975 4356 5331 | 5200 0.00
405 GET /cpython/changelog?page=23 3 0(0.00%) 4502 3962 5116 | 4400 0.00
406 GET /cpython/changelog?page=24 2 0(0.00%) 5052 4627 5478 | 4600 0.00
407 GET /cpython/changelog?page=25 2 0(0.00%) 5823 5346 6301 | 5300 0.00
408 GET /cpython/changelog?page=26 8 0(0.00%) 4373 2688 5867 | 4500 0.10
409 GET /cpython/changelog?page=27 9 0(0.00%) 4064 2703 5463 | 4100 0.10
410 GET /cpython/changelog?page=28 4 0(0.00%) 4446 3327 5999 | 3800 0.00
411 GET /cpython/changelog?page=29 3 0(0.00%) 4126 3591 4415 | 4400 0.00
412 GET /cpython/changelog?page=3 4 0(0.00%) 4299 3510 5200 | 4000 0.00
413 GET /cpython/changelog?page=30 1 0(0.00%) 4687 4687 4687 | 4700 0.00
414 GET /cpython/changelog?page=31 1 0(0.00%) 4753 4753 4753 | 4800 0.00
415 GET /cpython/changelog?page=32 1 0(0.00%) 3848 3848 3848 | 3800 0.00
416 GET /cpython/changelog?page=33 3 0(0.00%) 5279 4866 5849 | 5100 0.00
417 GET /cpython/changelog?page=34 4 0(0.00%) 4646 3155 5978 | 4400 0.00
418 GET /cpython/changelog?page=35 2 0(0.00%) 4810 4499 5121 | 4500 0.00
419 GET /cpython/changelog?page=36 1 0(0.00%) 3048 3048 3048 | 3000 0.00
420 GET /cpython/changelog?page=37 3 0(0.00%) 4551 3992 5221 | 4400 0.00
421 GET /cpython/changelog?page=38 3 0(0.00%) 4681 3539 6230 | 4300 0.00
422 GET /cpython/changelog?page=39 1 0(0.00%) 3552 3552 3552 | 3600 0.00
423 GET /cpython/changelog?page=4 5 0(0.00%) 4647 4098 5166 | 4700 0.00
424 GET /cpython/changelog?page=40 1 0(0.00%) 4581 4581 4581 | 4600 0.00
425 GET /cpython/changelog?page=41 1 0(0.00%) 4486 4486 4486 | 4500 0.00
426 GET /cpython/changelog?page=42 1 0(0.00%) 3651 3651 3651 | 3700 0.00
427 GET /cpython/changelog?page=43 2 0(0.00%) 3274 2789 3760 | 2800 0.10
428 GET /cpython/changelog?page=44 2 0(0.00%) 3262 3136 3388 | 3100 0.10
429 GET /cpython/changelog?page=5 5 0(0.00%) 4439 3113 5673 | 4600 0.00
430 GET /cpython/changelog?page=6 4 0(0.00%) 4864 3543 5763 | 4600 0.00
431 GET /cpython/changelog?page=7 4 0(0.00%) 4824 4526 5376 | 4600 0.00
432 GET /cpython/changelog?page=8 3 0(0.00%) 3840 2725 5503 | 3300 0.00
433 GET /cpython/changelog?page=9 3 0(0.00%) 4628 3879 5564 | 4400 0.00
434 GET /cpython/changelog_summary?size=10&page=0 1 0(0.00%) 2371 2371 2371 | 2400 0.00
435 GET /cpython/changelog_summary?size=10&page=10 1 0(0.00%) 1693 1693 1693 | 1700 0.00
436 GET /cpython/changelog_summary?size=10&page=11 2 0(0.00%) 2128 1947 2309 | 1900 0.00
437 GET /cpython/changelog_summary?size=10&page=12 1 0(0.00%) 1723 1723 1723 | 1700 0.00
438 GET /cpython/changelog_summary?size=10&page=2 3 0(0.00%) 1918 1597 2122 | 2000 0.00
439 GET /cpython/changelog_summary?size=10&page=3 3 0(0.00%) 1728 1096 2084 | 2000 0.00
440 GET /cpython/changelog_summary?size=10&page=4 1 0(0.00%) 2098 2098 2098 | 2100 0.00
441 GET /cpython/changelog_summary?size=10&page=5 4 0(0.00%) 1669 1108 2105 | 1500 0.00
442 GET /cpython/changelog_summary?size=10&page=6 4 0(0.00%) 1860 1700 2021 | 1900 0.00
443 GET /cpython/changelog_summary?size=10&page=7 1 0(0.00%) 1566 1566 1566 | 1600 0.00
444 GET /cpython/changelog_summary?size=10&page=8 2 0(0.00%) 1962 1871 2054 | 1900 0.00
445 GET /cpython/changelog_summary?size=10&page=9 2 0(0.00%) 1713 1589 1838 | 1600 0.00
446 GET /cpython/changeset/0039154be0c0298e68c80cc721e51b50ce238ec8 1 0(0.00%) 1306 1306 1306 | 1300 0.00
447 GET /cpython/changeset/0045eec1e247c9b0855d5dd0ffa724068c43b8f7 2 0(0.00%) 901 502 1301 | 500 0.00
448 GET /cpython/changeset/014060738f7f89044fbe3a231b491daa69f7e366 1 0(0.00%) 972 972 972 | 970 0.00
449 GET /cpython/changeset/01ec8bb7187f9d6849ce566062e47afe48fabef9 1 0(0.00%) 1098 1098 1098 | 1100 0.00
450 GET /cpython/changeset/02b25ec13c94e87e6e8ac2876455d776377a945a 1 0(0.00%) 933 933 933 | 930 0.00
451 GET /cpython/changeset/057a8bd043ea759ad7fbd25a90a942f8078efff4 2 0(0.00%) 1232 1180 1284 | 1200 0.00
452 GET /cpython/changeset/05e8f92b58ff41bb4d710d80a0793728900674eb 1 0(0.00%) 1275 1275 1275 | 1300 0.00
453 GET /cpython/changeset/0788d0d6bf85c32ea72a378a9aa9efa05430d089 1 0(0.00%) 1031 1031 1031 | 1000 0.00
454 GET /cpython/changeset/0b477934e0a1a1f032010ee4f21261958bd65c14 1 0(0.00%) 1009 1009 1009 | 1000 0.00
455 GET /cpython/changeset/0b79c702abda873c6f45a720f028bffeb87bb8d7 1 0(0.00%) 1197 1197 1197 | 1200 0.00
456 GET /cpython/changeset/0c2792a8f9db14ed6d5c67d557af3ed64aa83ba0 3 0(0.00%) 919 718 1031 | 1000 0.00
457 GET /cpython/changeset/0c57aba6b1a320127340076300e23f90c0e21d44 2 0(0.00%) 1156 1117 1195 | 1100 0.00
458 GET /cpython/changeset/0fef0afb9d198b747194d259b8d3dad2b8f4dfc4 1 0(0.00%) 1529 1529 1529 | 1500 0.00
459 GET /cpython/changeset/1c48171fc8da0225f22f498b699ecb7f3f6db41d 2 0(0.00%) 792 537 1047 | 540 0.00
460 GET /cpython/changeset/1c7567ec6292238ec80c1bca7b81d3088972cbfb 1 0(0.00%) 957 957 957 | 960 0.00
461 GET /cpython/changeset/1db5cde4958f5a80d8236a6244cf99fe8b4987e5 1 0(0.00%) 1008 1008 1008 | 1000 0.00
462 GET /cpython/changeset/221a1f9155e2a8b4d12015261b83a2ce3a0c62a2 2 0(0.00%) 1165 999 1331 | 1000 0.00
463 GET /cpython/changeset/263701e0b77e3160bc6a835087f838bd6b24092a 1 0(0.00%) 1051 1051 1051 | 1100 0.00
464 GET /cpython/changeset/26afcb8a87b95e9bba8348528d3876c4851b1184 1 0(0.00%) 1436 1436 1436 | 1400 0.00
465 GET /cpython/changeset/275d02865d11b517bb0c132bf4c345b858a3882c 1 0(0.00%) 1342 1342 1342 | 1300 0.00
466 GET /cpython/changeset/275da9f9d7d7c4049409d26b02786326a23e870d 1 0(0.00%) 402 402 402 | 400 0.00
467 GET /cpython/changeset/294b598afbb6878a65cf36ea2ca7065b4684a7e8 3 0(0.00%) 1096 955 1200 | 1100 0.00
468 GET /cpython/changeset/2ae5709692ef6afed4c3602a75f3a7bc578303cc 1 0(0.00%) 998 998 998 | 1000 0.00
469 GET /cpython/changeset/2e0a98178c07e4e5989c6e0f8270d98f4fd5579a 1 0(0.00%) 1381 1381 1381 | 1400 0.00
470 GET /cpython/changeset/2f2c1816d0c7a2897bc5d36ea5bdd415e730fdc1 2 0(0.00%) 986 854 1118 | 850 0.00
471 GET /cpython/changeset/3435c5865cfcb4d31a48c13097176f6abe4a4289 1 0(0.00%) 1202 1202 1202 | 1200 0.00
472 GET /cpython/changeset/345ce3d2986a5976016d57c97f67c253cdc2f616 1 0(0.00%) 1039 1039 1039 | 1000 0.00
473 GET /cpython/changeset/38688f0a0e047e6702048f59247cd4a85e8e350e 1 0(0.00%) 406 406 406 | 410 0.00
474 GET /cpython/changeset/3b6b905ae229d2fa06268615c696a3a64aded264 1 0(0.00%) 1319 1319 1319 | 1300 0.00
475 GET /cpython/changeset/404cf4c071c8f84e68857afaa7fa4cf6f55804e1 1 0(0.00%) 1234 1234 1234 | 1200 0.00
476 GET /cpython/changeset/410439e94a40316b56564ba4fd47f4d28c34f783 1 0(0.00%) 574 574 574 | 570 0.00
477 GET /cpython/changeset/438da6ae38faa5d3b0dc998c72ac10aeb3c7f268 1 0(0.00%) 820 820 820 | 820 0.00
478 GET /cpython/changeset/45dcdd8f32118a433e5e3dcc7b356006cd2a486f 1 0(0.00%) 1093 1093 1093 | 1100 0.00
479 GET /cpython/changeset/49085b7460291e4ce7e2113d02e28ea41eec752d 2 0(0.00%) 897 817 977 | 820 0.00
480 GET /cpython/changeset/49cdb04bfcc6f1245aee9254229d8e01d3ef4293 1 0(0.00%) 1359 1359 1359 | 1400 0.00
481 GET /cpython/changeset/4be39be5230c07ba57986df7e13690777905ad1b 2 0(0.00%) 1223 1185 1261 | 1200 0.00
482 GET /cpython/changeset/4d3f960c26f5eadb2346081aee8e87b07b2e30c5 1 0(0.00%) 414 414 414 | 410 0.00
483 GET /cpython/changeset/4ef517041573969a1f58d3ed512f3395673c1587 1 0(0.00%) 956 956 956 | 960 0.00
484 GET /cpython/changeset/4f359c631bb0f69c536cba1078421cf01c35b3ee 2 0(0.00%) 987 938 1037 | 940 0.00
485 GET /cpython/changeset/50527a1b769c2e4323511cbdcbdf4e66a779ac99 1 0(0.00%) 856 856 856 | 860 0.00
486 GET /cpython/changeset/50c995bdc00a3fb6941d6c946e4e16bc26fe93be 1 0(0.00%) 1038 1038 1038 | 1000 0.00
487 GET /cpython/changeset/5496bf8972b6f1f2b3d39baf6e6591363c4b79a7 1 0(0.00%) 1013 1013 1013 | 1000 0.00
488 GET /cpython/changeset/57740d19f5c2c063231d1d2898b940fd16f13355 1 0(0.00%) 752 752 752 | 750 0.00
489 GET /cpython/changeset/58e0d2c3ead8ff3dfe0cb5a6f79c0f69dde281b7 1 0(0.00%) 1106 1106 1106 | 1100 0.00
490 GET /cpython/changeset/5a157e3b3c47ed38e1f1f4c466f68c3c35585667 1 0(0.00%) 808 808 808 | 810 0.00
491 GET /cpython/changeset/5a58f6e793ccf17393c725e01b741c0fec5450f2 1 0(0.00%) 989 989 989 | 990 0.00
492 GET /cpython/changeset/5ac811cbec879f87ca1c6f91f2bd9f3ead8f3a85 1 0(0.00%) 988 988 988 | 990 0.00
493 GET /cpython/changeset/6219aa966a5fb7572502646377cc8d67c45697fb 1 0(0.00%) 821 821 821 | 820 0.00
494 GET /cpython/changeset/6298895a52de36edaf182316a1081ccdfebf134e 1 0(0.00%) 1159 1159 1159 | 1200 0.00
495 GET /cpython/changeset/65018d952340acda44c20baee75184a696dd1887 1 0(0.00%) 1219 1219 1219 | 1200 0.00
496 GET /cpython/changeset/66c7f30fe8c7943ff09500d9ccb4836a15dedec1 1 0(0.00%) 1238 1238 1238 | 1200 0.00
497 GET /cpython/changeset/69783040dd543d748f7197fdf7d9e147ba9cbc71 1 0(0.00%) 1272 1272 1272 | 1300 0.00
498 GET /cpython/changeset/6a71d3c796532cab3f2661e134bbf28ae9902435 2 0(0.00%) 900 554 1247 | 550 0.00
499 GET /cpython/changeset/6cbb97f039bdb1b63469c76e759d149d4e6841de 1 0(0.00%) 1059 1059 1059 | 1100 0.00
500 GET /cpython/changeset/6d41f139709b7c77293f9d71f2316b790ebecc21 1 0(0.00%) 990 990 990 | 990 0.00
501 GET /cpython/changeset/6e67a0394957f1756269defbaab505b45524b853 1 0(0.00%) 811 811 811 | 810 0.00
502 GET /cpython/changeset/734d5ab0ce6ac6333a35e98a014f49ea3b0205cb 1 0(0.00%) 870 870 870 | 870 0.00
503 GET /cpython/changeset/7640af73c19d5ebe409ad8aa41aa2eb5b8f6dac1 2 0(0.00%) 1114 1099 1129 | 1100 0.00
504 GET /cpython/changeset/766570a5d6070488cbf5acf967c500088d1f4c17 1 0(0.00%) 1145 1145 1145 | 1100 0.00
505 GET /cpython/changeset/7749d30d9caa81113f5a84578e55e5336251eb66 1 0(0.00%) 1169 1169 1169 | 1200 0.00
506 GET /cpython/changeset/77fbbb5a73064ee17425b5fd8e6d68cf91ca33ab 1 0(0.00%) 1051 1051 1051 | 1100 0.00
507 GET /cpython/changeset/7bc53cf8b2dfb0a9fe4dfb1bb08e26d8dce04eba 1 0(0.00%) 1336 1336 1336 | 1300 0.00
508 GET /cpython/changeset/7c19f1f792c6c8d5a7e4c3e8d424318c3fe0663b 1 0(0.00%) 776 776 776 | 780 0.00
509 GET /cpython/changeset/7ed237478fcc3695d2b95b861efcff0a43a023e5 3 0(0.00%) 856 772 992 | 810 0.00
510 GET /cpython/changeset/7f8843ec34ee4049493b58882cb7165d5e268f9b 1 0(0.00%) 987 987 987 | 990 0.00
511 GET /cpython/changeset/8039a20b18cc17fdbbc91758746e6344948022e4 1 0(0.00%) 689 689 689 | 690 0.00
512 GET /cpython/changeset/8106f91fccd66f1306426e190e684e395b9d3132 1 0(0.00%) 911 911 911 | 910 0.00
513 GET /cpython/changeset/823f5507bd864cdb1899dbc05a42355bf7188728 1 0(0.00%) 1114 1114 1114 | 1100 0.00
514 GET /cpython/changeset/855ff9182a0778ba1e20af6fc043d27591d66d80 2 0(0.00%) 1041 1001 1081 | 1000 0.10
515 GET /cpython/changeset/87f940e85cb0e48208ca25a87b5b58cb6cfb886e 1 0(0.00%) 667 667 667 | 670 0.00
516 GET /cpython/changeset/88a532a31eb383dd19a43d0a3c4c9841f84fd88f 2 0(0.00%) 1059 1047 1072 | 1000 0.00
517 GET /cpython/changeset/897c9e6ddb1ac1d467d2bf700aecbc585c6e33ac 1 0(0.00%) 904 904 904 | 900 0.00
518 GET /cpython/changeset/8b8b79e1f8626a69c3f2abccb2147e148d52173e 1 0(0.00%) 918 918 918 | 920 0.00
519 GET /cpython/changeset/92d691c3ca009b890bd844708ef60335424295f6 1 0(0.00%) 934 934 934 | 930 0.00
520 GET /cpython/changeset/944fc499ccd30a5a4d868373b69d8585bd07cdf7 1 0(0.00%) 905 905 905 | 910 0.00
521 GET /cpython/changeset/958e8bebda6d3ec7fee21e0ede5a871b1c818948 1 0(0.00%) 1034 1034 1034 | 1000 0.10
522 GET /cpython/changeset/962c1fefc34af9db3110c9c6538e5cdb39c170de 1 0(0.00%) 1289 1289 1289 | 1300 0.00
523 GET /cpython/changeset/97ed0b51bbc078cd5a836932422a7517af6fa8aa 1 0(0.00%) 999 999 999 | 1000 0.00
524 GET /cpython/changeset/9b450b19aa118c4888b6acc6ba5e8f0fb00aaa87 1 0(0.00%) 1002 1002 1002 | 1000 0.00
525 GET /cpython/changeset/9e3c367b45a189e941eb3a772964a54b5b7574d5 1 0(0.00%) 876 876 876 | 880 0.00
526 GET /cpython/changeset/a0ae1ac69925b3ae866c7e089e95400aa3681a42 1 0(0.00%) 993 993 993 | 990 0.00
527 GET /cpython/changeset/a62368778d6cd0c6473f149f8d850dcce7f31031 1 0(0.00%) 912 912 912 | 910 0.00
528 GET /cpython/changeset/a74c48aa43eb5109351b49d8b1e420612389a2e8 1 0(0.00%) 1005 1005 1005 | 1000 0.00
529 GET /cpython/changeset/a77dc87a34fffe2b6899fa6b5a8f5b226e9bf952 1 0(0.00%) 1188 1188 1188 | 1200 0.00
530 GET /cpython/changeset/a8db78d7b6574b425d6b1a38f55068761f5da2e7 1 0(0.00%) 1282 1282 1282 | 1300 0.00
531 GET /cpython/changeset/a9637b29954d952ffcdde6bd76cd2202282427a0 1 0(0.00%) 1212 1212 1212 | 1200 0.00
532 GET /cpython/changeset/aa059a8fb55a957b84bbe471bab74514adf23ee4 1 0(0.00%) 1189 1189 1189 | 1200 0.00
533 GET /cpython/changeset/ad7ac2a469e1e9858825a646ffcbce68aae8ed40 2 0(0.00%) 922 873 971 | 870 0.00
534 GET /cpython/changeset/afd64e6606288447e75ad8031cf72112f47b5dba 1 0(0.00%) 1230 1230 1230 | 1200 0.00
535 GET /cpython/changeset/b08921c7d1ecfa43aa47f3453a87f705e9b92af8 1 0(0.00%) 686 686 686 | 690 0.00
536 GET /cpython/changeset/b1c82ef96862101642deb702e046763f09678c3f 1 0(0.00%) 1073 1073 1073 | 1100 0.00
537 GET /cpython/changeset/b3aa30f474c4c5dd8f12c92c1156d7aa5393d72b 1 0(0.00%) 1067 1067 1067 | 1100 0.00
538 GET /cpython/changeset/b5114747d3edb248c91f0da90d10f0bcf9514630 1 0(0.00%) 1066 1066 1066 | 1100 0.00
539 GET /cpython/changeset/b94da2b69d13ecc9f8c13ad74c411409edd56ca5 2 0(0.00%) 976 849 1104 | 850 0.00
540 GET /cpython/changeset/b957f475e41eaae8678c092b326b42579a5d2de6 1 0(0.00%) 1015 1015 1015 | 1000 0.00
541 GET /cpython/changeset/bc991d4f9ce70bdcc3da7cd0b1fe916a17a0a805 2 0(0.00%) 804 650 958 | 650 0.00
542 GET /cpython/changeset/c173a34f20c02bb5a188fa80ccdcf1b5fc7e551e 1 0(0.00%) 1079 1079 1079 | 1100 0.00
543 GET /cpython/changeset/c1edc4e43eb103254c5d96f1708542623fe08f17 1 0(0.00%) 911 911 911 | 910 0.00
544 GET /cpython/changeset/c48980af7df207e8c606e00c054eadb513f59d37 2 0(0.00%) 724 687 762 | 690 0.00
545 GET /cpython/changeset/c4f053f1b47f9a63eb8bfb13bac2a5674e2ef999 1 0(0.00%) 1019 1019 1019 | 1000 0.00
546 GET /cpython/changeset/c755a3b58fa6d98b0c68e309d3299cdc52c34ba9 1 0(0.00%) 955 955 955 | 960 0.00
547 GET /cpython/changeset/c8ce5bca0fcda4307f7ac5d69103ce128a562705 1 0(0.00%) 1109 1109 1109 | 1100 0.00
548 GET /cpython/changeset/ca4a22256bf88452349c2ff54b90a4bd983cb72d 2 0(0.00%) 733 717 749 | 720 0.00
549 GET /cpython/changeset/d4b035c9fb7df8750d86f27702b41ee190ba4cb3 1 0(0.00%) 978 978 978 | 980 0.00
550 GET /cpython/changeset/d775fa9a97678fb93eef1396bf67832f872abbfd 1 0(0.00%) 427 427 427 | 430 0.00
551 GET /cpython/changeset/d85fcf23549e002626618196d930d3c807d2b20e 1 0(0.00%) 879 879 879 | 880 0.00
552 GET /cpython/changeset/d9607a71456e0ff635251fcb9ac09369cb0a4b89 1 0(0.00%) 956 956 956 | 960 0.00
553 GET /cpython/changeset/d99014320220951707f79d114ae33f8c5a372a87 1 0(0.00%) 943 943 943 | 940 0.00
554 GET /cpython/changeset/d9da4b77624b522ce367a37d737acd208b3f793f 1 0(0.00%) 1178 1178 1178 | 1200 0.00
555 GET /cpython/changeset/db600c927b2b87537c5e71ce39d825675cfb5e63 1 0(0.00%) 894 894 894 | 890 0.00
556 GET /cpython/changeset/dce9dbc8e8924582d3ddb3b18f40d712c30631ea 1 0(0.00%) 771 771 771 | 770 0.10
557 GET /cpython/changeset/e0561df131aaf6580523fbdb172239922e0026e9 1 0(0.00%) 1324 1324 1324 | 1300 0.00
558 GET /cpython/changeset/e0f681f4ade3af52915d5f32daac97ada580d71a 5 0(0.00%) 1081 629 1473 | 1100 0.00
559 GET /cpython/changeset/e405bcbf761cc79afddc60045d7e94af6d2e144f 1 0(0.00%) 906 906 906 | 910 0.00
560 GET /cpython/changeset/e6bb59b6b85c14f25582961abcdd6a2c7e16f487 1 0(0.00%) 837 837 837 | 840 0.00
561 GET /cpython/changeset/e6be82fbc02519c1b61399bfd204d61489163eeb 1 0(0.00%) 1285 1285 1285 | 1300 0.00
562 GET /cpython/changeset/e750d2b44c1d4692545661fd325960b17fbe6fe6 1 0(0.00%) 690 690 690 | 690 0.00
563 GET /cpython/changeset/eafe4007c999f277bc84038af21d65fb6ea1aec8 1 0(0.00%) 1207 1207 1207 | 1200 0.00
564 GET /cpython/changeset/ebd6f7f7859f05efb66ee99fd22516d61b720afe 1 0(0.00%) 1037 1037 1037 | 1000 0.00
565 GET /cpython/changeset/ec2efc094d6245dbd0ce2a2fca394a15ee627888 1 0(0.00%) 875 875 875 | 880 0.00
566 GET /cpython/changeset/eee636e3edbe117a552d637afc0bcaf88f5c42a9 1 0(0.00%) 874 874 874 | 870 0.00
567 GET /cpython/changeset/efb0a3eb4e06e649d61dc9f96d3a8962974cf04f 1 0(0.00%) 514 514 514 | 510 0.00
568 GET /cpython/changeset/f0a5be139717b166a72db1e713d97006b191c746 1 0(0.00%) 980 980 980 | 980 0.00
569 GET /cpython/changeset/f0ab6f9f06036dfacff09f22f86464840b50eb0a 3 0(0.00%) 881 562 1051 | 1000 0.00
570 GET /cpython/changeset/f5df571bfe1d66406c0171cd59cf0da9708d345c 1 0(0.00%) 982 982 982 | 980 0.00
571 GET /cpython/changeset/fc7f28b11d208f0e4716cbb0f7068901e7d65046 1 0(0.00%) 940 940 940 | 940 0.00
572 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/ 2 0(0.00%) 3681 3579 3783 | 3600 0.00
573 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Grammar 3 0(0.00%) 1130 1080 1212 | 1100 0.00
574 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Grammar/Grammar 2 0(0.00%) 1242 1189 1296 | 1200 0.00
575 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Makefile.pre.in 1 0(0.00%) 1216 1216 1216 | 1200 0.00
576 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc 3 0(0.00%) 4547 4029 4970 | 4600 0.00
577 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/ACKS 1 0(0.00%) 575 575 575 | 580 0.00
578 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/RPM 1 0(0.00%) 1437 1437 1437 | 1400 0.00
579 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/coverity_model.c 1 0(0.00%) 822 822 822 | 820 0.00
580 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-config.in 1 0(0.00%) 1125 1125 1125 | 1100 0.00
581 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-wing4.wpr 1 0(0.00%) 1157 1157 1157 | 1200 0.00
582 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-wing5.wpr 1 0(0.00%) 989 989 989 | 990 0.00
583 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python.pc.in 1 0(0.00%) 928 928 928 | 930 0.00
584 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/PC 1 0(0.00%) 7186 7186 7186 | 7200 0.00
585 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Programs 1 0(0.00%) 1520 1520 1520 | 1500 0.00
586 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Programs/README 2 0(0.00%) 1168 1082 1255 | 1100 0.00
587 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Programs/python.c 1 0(0.00%) 1258 1258 1258 | 1300 0.00
588 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/README 1 0(0.00%) 853 853 853 | 850 0.00
589 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/install-sh 1 0(0.00%) 1392 1392 1392 | 1400 0.00
590 GET /cpython/files/tip/ 1 0(0.00%) 3070 3070 3070 | 3100 0.00
591 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/.hgtouch 1 0(0.00%) 730 730 730 | 730 0.00
592 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Grammar/Grammar 5 0(0.00%) 776 584 1062 | 730 0.00
593 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Misc/NEWS 1 0(0.00%) 892 892 892 | 890 0.00
594 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Misc/coverity_model.c 1 0(0.00%) 728 728 728 | 730 0.00
595 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-wing4.wpr 1 0(0.00%) 528 528 528 | 530 0.00
596 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Programs/README 1 0(0.00%) 677 677 677 | 680 0.00
597 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Programs/_freeze_importlib.c 5 0(0.00%) 845 729 988 | 810 0.00
598 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Programs/_testembed.c 1 0(0.00%) 458 458 458 | 460 0.00
599 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/config.sub 2 0(0.00%) 679 462 897 | 460 0.00
600 GET /git 2 0(0.00%) 2232 792 3673 | 790 0.10
601 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/builtin/mktag.c 1 0(0.00%) 4663 4663 4663 | 4700 0.00
602 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/compat/strlcpy.c 1 0(0.00%) 1707 1707 1707 | 1700 0.00
603 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gettext.h 1 0(0.00%) 2689 2689 2689 | 2700 0.00
604 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/git-am.sh 1 0(0.00%) 14322 14322 14322 | 14000 0.00
605 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/.gitignore 2 0(0.00%) 1071 1021 1121 | 1000 0.00
606 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/Makefile 1 0(0.00%) 2254 2254 2254 | 2300 0.00
607 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/gitk 3 0(0.00%) 821 627 1098 | 740 0.10
608 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/de.po 1 0(0.00%) 18742 18742 18742 | 19000 0.00
609 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/patch-ids.c 1 0(0.00%) 2829 2829 2829 | 2800 0.10
610 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/pathspec.h 1 0(0.00%) 2283 2283 2283 | 2300 0.00
611 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/reflog-walk.h 1 0(0.00%) 2146 2146 2146 | 2100 0.00
612 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/templates/info--exclude 1 0(0.00%) 1319 1319 1319 | 1300 0.00
613 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/test-mktemp.c 1 0(0.00%) 1711 1711 1711 | 1700 0.00
614 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/wildmatch.c 1 0(0.00%) 5524 5524 5524 | 5500 0.00
615 GET /git/changelog?page=1 1 0(0.00%) 1913 1913 1913 | 1900 0.00
616 GET /git/changelog?page=10 1 0(0.00%) 1809 1809 1809 | 1800 0.00
617 GET /git/changelog?page=11 1 0(0.00%) 2183 2183 2183 | 2200 0.00
618 GET /git/changelog?page=12 1 0(0.00%) 1926 1926 1926 | 1900 0.00
619 GET /git/changelog?page=13 1 0(0.00%) 1582 1582 1582 | 1600 0.00
620 GET /git/changelog?page=14 1 0(0.00%) 1846 1846 1846 | 1800 0.00
621 GET /git/changelog?page=15 1 0(0.00%) 1555 1555 1555 | 1600 0.00
622 GET /git/changelog?page=16 1 0(0.00%) 1884 1884 1884 | 1900 0.00
623 GET /git/changelog?page=17 1 0(0.00%) 1921 1921 1921 | 1900 0.00
624 GET /git/changelog?page=18 1 0(0.00%) 1930 1930 1930 | 1900 0.00
625 GET /git/changelog?page=2 1 0(0.00%) 2007 2007 2007 | 2000 0.00
626 GET /git/changelog?page=3 1 0(0.00%) 1985 1985 1985 | 2000 0.00
627 GET /git/changelog?page=4 1 0(0.00%) 1756 1756 1756 | 1800 0.00
628 GET /git/changelog?page=5 2 0(0.00%) 2035 1836 2234 | 1800 0.00
629 GET /git/changelog?page=6 3 0(0.00%) 1728 1567 1877 | 1700 0.00
630 GET /git/changelog?page=7 3 0(0.00%) 1847 1674 1974 | 1900 0.00
631 GET /git/changelog?page=8 2 0(0.00%) 1814 1550 2079 | 1600 0.00
632 GET /git/changelog?page=9 1 0(0.00%) 1903 1903 1903 | 1900 0.00
633 GET /git/changelog_summary?size=10&page=2 3 0(0.00%) 925 798 1136 | 840 0.00
634 GET /git/changelog_summary?size=10&page=3 2 0(0.00%) 1285 970 1601 | 970 0.00
635 GET /git/changeset/d299e9e550c1bf8640907fdba1f03cc585ee71df 2 0(0.00%) 1041 936 1146 | 940 0.00
636 GET /git/changeset/d8b396e17ecfe28b39b5f4470f791c434cce40ec 1 0(0.00%) 830 830 830 | 830 0.00
637 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/ 2 0(0.00%) 44548 44276 44821 | 44000 0.00
638 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/builtin 1 0(0.00%) 11120 11120 11120 | 11000 0.00
639 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/builtin/read-tree.c 1 0(0.00%) 863 863 863 | 860 0.00
640 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/builtin/rerere.c 1 0(0.00%) 1076 1076 1076 | 1100 0.00
641 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/builtin/upload-archive.c 1 0(0.00%) 1256 1256 1256 | 1300 0.00
642 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/compat 1 0(0.00%) 6305 6305 6305 | 6300 0.00
643 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/compat/fopen.c 1 0(0.00%) 697 697 697 | 700 0.00
644 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/ctype.c 1 0(0.00%) 859 859 859 | 860 0.00
645 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git 4 0(0.00%) 950 745 1135 | 910 0.10
646 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/.gitignore 3 0(0.00%) 619 606 643 | 610 0.00
647 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/Makefile 1 0(0.00%) 895 895 895 | 900 0.00
648 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po 2 0(0.00%) 3015 2986 3044 | 3000 0.00
649 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/.gitignore 1 0(0.00%) 1126 1126 1126 | 1100 0.00
650 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/hu.po 1 0(0.00%) 1123 1123 1123 | 1100 0.00
651 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/sv.po 1 0(0.00%) 626 626 626 | 630 0.00
652 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/mailmap.c 1 0(0.00%) 1186 1186 1186 | 1200 0.00
653 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/notes-merge.c 1 0(0.00%) 766 766 766 | 770 0.00
654 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/perl 1 0(0.00%) 1076 1076 1076 | 1100 0.00
655 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/perl/Git 1 0(0.00%) 1085 1085 1085 | 1100 0.10
656 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/progress.h 1 0(0.00%) 794 794 794 | 790 0.00
657 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/sigchain.c 1 0(0.00%) 764 764 764 | 760 0.00
658 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/templates 1 0(0.00%) 4525 4525 4525 | 4500 0.00
659 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/templates/hooks--prepare-commit-msg.sample 1 0(0.00%) 900 900 900 | 900 0.00
660 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/test-hashmap.c 1 0(0.00%) 540 540 540 | 540 0.00
661 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/test-line-buffer.c 1 0(0.00%) 786 786 786 | 790 0.00
662 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/vcs-svn 1 0(0.00%) 3224 3224 3224 | 3200 0.00
663 GET /git/files/tip/ 3 0(0.00%) 44722 42670 47681 | 44000 0.00
664 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/builtin/apply.c 1 0(0.00%) 655 655 655 | 660 0.00
665 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/builtin/fmt-merge-msg.c 1 0(0.00%) 531 531 531 | 530 0.00
666 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/builtin/index-pack.c 1 0(0.00%) 655 655 655 | 660 0.00
667 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/combine-diff.c 1 0(0.00%) 352 352 352 | 350 0.00
668 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/git-compat-util.h 1 0(0.00%) 552 552 552 | 550 0.10
669 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/git-web--browse.sh 1 0(0.00%) 453 453 453 | 450 0.00
670 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/.gitignore 3 0(0.00%) 543 351 875 | 400 0.20
671 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/Makefile 1 0(0.00%) 562 562 562 | 560 0.00
672 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/it.po 1 0(0.00%) 319 319 319 | 320 0.00
673 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/line-range.c 1 0(0.00%) 324 324 324 | 320 0.00
674 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/pack-bitmap.h 1 0(0.00%) 410 410 410 | 410 0.00
675 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/perl/Git/I18N.pm 2 0(0.00%) 551 471 632 | 470 0.20
676 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/tag.c 1 0(0.00%) 616 616 616 | 620 0.00
677 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/templates/branches-- 1 0(0.00%) 490 490 490 | 490 0.00
678 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/templates/this--description 1 0(0.00%) 346 346 346 | 350 0.00
679 --------------------------------------------------------------------------------------------------------------------------------------------
680 Total 500 0(0.00%) 1.90
681
682
683 RC Branch @5886 (with VCS2 optimizations)
684 +++++++++++++++++++++++++++++++++++++++++
685
686 Name # reqs # fails Avg Min Max | Median req/s
687 --------------------------------------------------------------------------------------------------------------------------------------------
688 GET / 9 0(0.00%) 502 231 719 | 520 0.00
689 POST /_admin/login?came_from=/ 2 0(0.00%) 702 696 709 | 700 0.00
690 GET /cpython 2 0(0.00%) 766 732 800 | 730 0.00
691 GET /cpython/annotate/{revision}/{path} [file_annotate] 12 0(0.00%) 1125 305 5130 | 710 0.00
692 GET /cpython/changelog/?page={page} 153 0(0.00%) 1203 378 2391 | 1300 0.60
693 GET /cpython/changelog_summary?page={page} 25 0(0.00%) 685 249 1256 | 640 0.00
694 GET /cpython/changeset/{changeset} 158 0(0.00%) 726 254 1492 | 730 0.30
695 GET /cpython/file/{revision}/{path} [dir] 12 0(0.00%) 885 311 1660 | 780 0.00
696 GET /cpython/file/{revision}/{path} [file] 14 0(0.00%) 740 257 1228 | 700 0.00
697 GET /cpython/raw/{revision}/{path} [file_raw] 18 0(0.00%) 462 196 908 | 390 0.00
698 GET /git 2 0(0.00%) 2293 545 4041 | 550 0.00
699 GET /git/annotate/{revision}/{path} [file_annotate] 8 0(0.00%) 1345 811 2153 | 1100 0.00
700 GET /git/changelog/?page={page} 24 0(0.00%) 1400 672 1894 | 1400 0.00
701 GET /git/changelog_summary?page={page} 18 0(0.00%) 986 462 1475 | 970 0.40
702 GET /git/changeset/{changeset} 10 0(0.00%) 811 320 1258 | 760 0.20
703 GET /git/file/{revision}/{path} [dir] 12 0(0.00%) 12083 567 43469 | 2500 0.00
704 GET /git/file/{revision}/{path} [file] 12 0(0.00%) 761 430 1161 | 680 0.00
705 GET /git/raw/{revision}/{path} [file_raw] 9 0(0.00%) 504 208 912 | 400 0.00
706 --------------------------------------------------------------------------------------------------------------------------------------------
707 Total 500 0(0.00%) 1.50
708
709
710 Name # reqs # fails Avg Min Max | Median req/s
711 --------------------------------------------------------------------------------------------------------------------------------------------
712 GET / 9 0(0.00%) 428 133 797 | 390 0.00
713 POST /_admin/login?came_from=/ 2 0(0.00%) 640 572 709 | 570 0.00
714 GET /cpython 2 0(0.00%) 1005 640 1371 | 640 0.00
715 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/.hgtags 2 0(0.00%) 1305 899 1712 | 900 0.00
716 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Grammar/Grammar 2 0(0.00%) 791 491 1092 | 490 0.00
717 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Makefile.pre.in 1 0(0.00%) 5160 5160 5160 | 5200 0.00
718 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Misc/RPM/README 1 0(0.00%) 410 410 410 | 410 0.00
719 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Misc/valgrind-python.supp 1 0(0.00%) 1102 1102 1102 | 1100 0.00
720 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/README 1 0(0.00%) 1238 1238 1238 | 1200 0.00
721 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/_freeze_importlib.c 1 0(0.00%) 619 619 619 | 620 0.00
722 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/_testembed.c 2 0(0.00%) 941 705 1177 | 710 0.00
723 GET /cpython/annotate/74236c8bf064188516b32bf95016971227ec72a9/Programs/python.c 1 0(0.00%) 404 404 404 | 400 0.00
724 GET /cpython/changelog?page=1 6 0(0.00%) 1446 749 2104 | 1400 0.00
725 GET /cpython/changelog?page=10 3 0(0.00%) 1059 785 1418 | 970 0.00
726 GET /cpython/changelog?page=11 5 0(0.00%) 1020 777 1312 | 1000 0.00
727 GET /cpython/changelog?page=12 7 0(0.00%) 1047 494 1485 | 1100 0.00
728 GET /cpython/changelog?page=13 6 0(0.00%) 1119 587 1526 | 1200 0.00
729 GET /cpython/changelog?page=14 4 0(0.00%) 922 545 1399 | 790 0.00
730 GET /cpython/changelog?page=15 3 0(0.00%) 813 415 1384 | 640 0.00
731 GET /cpython/changelog?page=16 2 0(0.00%) 1341 1129 1553 | 1100 0.00
732 GET /cpython/changelog?page=17 2 0(0.00%) 1322 1313 1332 | 1300 0.00
733 GET /cpython/changelog?page=18 2 0(0.00%) 1032 698 1366 | 700 0.00
734 GET /cpython/changelog?page=19 4 0(0.00%) 990 437 1491 | 840 0.00
735 GET /cpython/changelog?page=2 4 0(0.00%) 953 626 1280 | 830 0.00
736 GET /cpython/changelog?page=20 5 0(0.00%) 1124 387 1392 | 1300 0.00
737 GET /cpython/changelog?page=21 3 0(0.00%) 908 388 1200 | 1100 0.00
738 GET /cpython/changelog?page=22 3 0(0.00%) 1668 1415 1797 | 1800 0.00
739 GET /cpython/changelog?page=23 3 0(0.00%) 1314 720 1748 | 1500 0.00
740 GET /cpython/changelog?page=24 2 0(0.00%) 1376 1278 1474 | 1300 0.00
741 GET /cpython/changelog?page=25 2 0(0.00%) 1424 1342 1507 | 1300 0.00
742 GET /cpython/changelog?page=26 9 0(0.00%) 1429 970 1904 | 1600 0.10
743 GET /cpython/changelog?page=27 10 0(0.00%) 1325 724 1674 | 1300 0.20
744 GET /cpython/changelog?page=28 5 0(0.00%) 1438 845 1815 | 1600 0.00
745 GET /cpython/changelog?page=29 3 0(0.00%) 1332 912 1644 | 1400 0.00
746 GET /cpython/changelog?page=3 4 0(0.00%) 1553 1205 1760 | 1600 0.00
747 GET /cpython/changelog?page=30 1 0(0.00%) 1144 1144 1144 | 1100 0.00
748 GET /cpython/changelog?page=31 1 0(0.00%) 1047 1047 1047 | 1000 0.00
749 GET /cpython/changelog?page=32 1 0(0.00%) 1038 1038 1038 | 1000 0.00
750 GET /cpython/changelog?page=33 3 0(0.00%) 917 395 1245 | 1100 0.00
751 GET /cpython/changelog?page=34 4 0(0.00%) 1185 971 1532 | 1100 0.00
752 GET /cpython/changelog?page=35 2 0(0.00%) 570 463 677 | 460 0.00
753 GET /cpython/changelog?page=36 1 0(0.00%) 941 941 941 | 940 0.00
754 GET /cpython/changelog?page=37 3 0(0.00%) 1669 1371 1842 | 1800 0.00
755 GET /cpython/changelog?page=38 3 0(0.00%) 1114 860 1573 | 910 0.00
756 GET /cpython/changelog?page=39 1 0(0.00%) 1506 1506 1506 | 1500 0.00
757 GET /cpython/changelog?page=4 5 0(0.00%) 1426 676 2673 | 1100 0.00
758 GET /cpython/changelog?page=40 1 0(0.00%) 1771 1771 1771 | 1800 0.00
759 GET /cpython/changelog?page=41 1 0(0.00%) 1732 1732 1732 | 1700 0.00
760 GET /cpython/changelog?page=42 1 0(0.00%) 1546 1546 1546 | 1500 0.00
761 GET /cpython/changelog?page=43 2 0(0.00%) 1539 1377 1701 | 1400 0.00
762 GET /cpython/changelog?page=44 2 0(0.00%) 1264 945 1583 | 950 0.00
763 GET /cpython/changelog?page=45 1 0(0.00%) 595 595 595 | 600 0.10
764 GET /cpython/changelog?page=46 1 0(0.00%) 1099 1099 1099 | 1100 0.10
765 GET /cpython/changelog?page=47 1 0(0.00%) 1672 1672 1672 | 1700 0.10
766 GET /cpython/changelog?page=48 1 0(0.00%) 1698 1698 1698 | 1700 0.00
767 GET /cpython/changelog?page=5 5 0(0.00%) 1051 432 1749 | 920 0.00
768 GET /cpython/changelog?page=6 4 0(0.00%) 1292 568 1980 | 1100 0.00
769 GET /cpython/changelog?page=7 4 0(0.00%) 1513 1136 1869 | 1300 0.00
770 GET /cpython/changelog?page=8 3 0(0.00%) 1521 1113 1811 | 1600 0.00
771 GET /cpython/changelog?page=9 3 0(0.00%) 1378 872 1798 | 1500 0.00
772 GET /cpython/changelog_summary?size=10&page=0 1 0(0.00%) 281 281 281 | 280 0.00
773 GET /cpython/changelog_summary?size=10&page=10 1 0(0.00%) 291 291 291 | 290 0.00
774 GET /cpython/changelog_summary?size=10&page=11 2 0(0.00%) 429 395 463 | 400 0.00
775 GET /cpython/changelog_summary?size=10&page=12 1 0(0.00%) 499 499 499 | 500 0.00
776 GET /cpython/changelog_summary?size=10&page=2 3 0(0.00%) 935 899 985 | 920 0.00
777 GET /cpython/changelog_summary?size=10&page=3 3 0(0.00%) 392 250 652 | 270 0.00
778 GET /cpython/changelog_summary?size=10&page=4 1 0(0.00%) 928 928 928 | 930 0.00
779 GET /cpython/changelog_summary?size=10&page=5 4 0(0.00%) 904 350 1280 | 820 0.00
780 GET /cpython/changelog_summary?size=10&page=6 4 0(0.00%) 570 254 1039 | 440 0.00
781 GET /cpython/changelog_summary?size=10&page=7 1 0(0.00%) 1208 1208 1208 | 1200 0.00
782 GET /cpython/changelog_summary?size=10&page=8 2 0(0.00%) 426 256 596 | 260 0.00
783 GET /cpython/changelog_summary?size=10&page=9 2 0(0.00%) 721 645 798 | 650 0.00
784 GET /cpython/changeset/0039154be0c0298e68c80cc721e51b50ce238ec8 1 0(0.00%) 977 977 977 | 980 0.00
785 GET /cpython/changeset/0045eec1e247c9b0855d5dd0ffa724068c43b8f7 2 0(0.00%) 830 648 1012 | 650 0.00
786 GET /cpython/changeset/014060738f7f89044fbe3a231b491daa69f7e366 1 0(0.00%) 1041 1041 1041 | 1000 0.00
787 GET /cpython/changeset/01ec8bb7187f9d6849ce566062e47afe48fabef9 1 0(0.00%) 432 432 432 | 430 0.00
788 GET /cpython/changeset/02b25ec13c94e87e6e8ac2876455d776377a945a 1 0(0.00%) 516 516 516 | 520 0.00
789 GET /cpython/changeset/057a8bd043ea759ad7fbd25a90a942f8078efff4 2 0(0.00%) 921 455 1388 | 460 0.00
790 GET /cpython/changeset/05e8f92b58ff41bb4d710d80a0793728900674eb 1 0(0.00%) 820 820 820 | 820 0.00
791 GET /cpython/changeset/0788d0d6bf85c32ea72a378a9aa9efa05430d089 1 0(0.00%) 307 307 307 | 310 0.00
792 GET /cpython/changeset/0b477934e0a1a1f032010ee4f21261958bd65c14 1 0(0.00%) 631 631 631 | 630 0.00
793 GET /cpython/changeset/0b79c702abda873c6f45a720f028bffeb87bb8d7 1 0(0.00%) 623 623 623 | 620 0.00
794 GET /cpython/changeset/0c2792a8f9db14ed6d5c67d557af3ed64aa83ba0 3 0(0.00%) 692 291 1410 | 380 0.00
795 GET /cpython/changeset/0c57aba6b1a320127340076300e23f90c0e21d44 2 0(0.00%) 1010 618 1403 | 620 0.00
796 GET /cpython/changeset/0fef0afb9d198b747194d259b8d3dad2b8f4dfc4 1 0(0.00%) 1241 1241 1241 | 1200 0.00
797 GET /cpython/changeset/1c48171fc8da0225f22f498b699ecb7f3f6db41d 2 0(0.00%) 991 655 1327 | 660 0.00
798 GET /cpython/changeset/1c7567ec6292238ec80c1bca7b81d3088972cbfb 1 0(0.00%) 631 631 631 | 630 0.00
799 GET /cpython/changeset/1db5cde4958f5a80d8236a6244cf99fe8b4987e5 1 0(0.00%) 776 776 776 | 780 0.00
800 GET /cpython/changeset/221a1f9155e2a8b4d12015261b83a2ce3a0c62a2 2 0(0.00%) 774 486 1062 | 490 0.00
801 GET /cpython/changeset/263701e0b77e3160bc6a835087f838bd6b24092a 1 0(0.00%) 1132 1132 1132 | 1100 0.00
802 GET /cpython/changeset/26afcb8a87b95e9bba8348528d3876c4851b1184 1 0(0.00%) 610 610 610 | 610 0.00
803 GET /cpython/changeset/275d02865d11b517bb0c132bf4c345b858a3882c 1 0(0.00%) 1101 1101 1101 | 1100 0.00
804 GET /cpython/changeset/275da9f9d7d7c4049409d26b02786326a23e870d 1 0(0.00%) 440 440 440 | 440 0.00
805 GET /cpython/changeset/294b598afbb6878a65cf36ea2ca7065b4684a7e8 3 0(0.00%) 698 380 869 | 850 0.00
806 GET /cpython/changeset/2ae5709692ef6afed4c3602a75f3a7bc578303cc 1 0(0.00%) 587 587 587 | 590 0.00
807 GET /cpython/changeset/2e0a98178c07e4e5989c6e0f8270d98f4fd5579a 1 0(0.00%) 1021 1021 1021 | 1000 0.00
808 GET /cpython/changeset/2f2c1816d0c7a2897bc5d36ea5bdd415e730fdc1 2 0(0.00%) 1068 773 1364 | 770 0.00
809 GET /cpython/changeset/3435c5865cfcb4d31a48c13097176f6abe4a4289 1 0(0.00%) 1243 1243 1243 | 1200 0.00
810 GET /cpython/changeset/345ce3d2986a5976016d57c97f67c253cdc2f616 1 0(0.00%) 815 815 815 | 820 0.00
811 GET /cpython/changeset/38688f0a0e047e6702048f59247cd4a85e8e350e 1 0(0.00%) 811 811 811 | 810 0.00
812 GET /cpython/changeset/3b6b905ae229d2fa06268615c696a3a64aded264 1 0(0.00%) 824 824 824 | 820 0.00
813 GET /cpython/changeset/404cf4c071c8f84e68857afaa7fa4cf6f55804e1 1 0(0.00%) 573 573 573 | 570 0.00
814 GET /cpython/changeset/410439e94a40316b56564ba4fd47f4d28c34f783 1 0(0.00%) 670 670 670 | 670 0.00
815 GET /cpython/changeset/438da6ae38faa5d3b0dc998c72ac10aeb3c7f268 1 0(0.00%) 1053 1053 1053 | 1100 0.00
816 GET /cpython/changeset/45dcdd8f32118a433e5e3dcc7b356006cd2a486f 1 0(0.00%) 1072 1072 1072 | 1100 0.00
817 GET /cpython/changeset/49085b7460291e4ce7e2113d02e28ea41eec752d 2 0(0.00%) 771 495 1047 | 500 0.00
818 GET /cpython/changeset/49cdb04bfcc6f1245aee9254229d8e01d3ef4293 1 0(0.00%) 907 907 907 | 910 0.00
819 GET /cpython/changeset/4be39be5230c07ba57986df7e13690777905ad1b 2 0(0.00%) 1033 734 1333 | 730 0.00
820 GET /cpython/changeset/4d3f960c26f5eadb2346081aee8e87b07b2e30c5 1 0(0.00%) 855 855 855 | 860 0.00
821 GET /cpython/changeset/4ef517041573969a1f58d3ed512f3395673c1587 1 0(0.00%) 816 816 816 | 820 0.00
822 GET /cpython/changeset/4f359c631bb0f69c536cba1078421cf01c35b3ee 2 0(0.00%) 617 311 923 | 310 0.00
823 GET /cpython/changeset/50527a1b769c2e4323511cbdcbdf4e66a779ac99 1 0(0.00%) 785 785 785 | 790 0.00
824 GET /cpython/changeset/50c995bdc00a3fb6941d6c946e4e16bc26fe93be 1 0(0.00%) 496 496 496 | 500 0.00
825 GET /cpython/changeset/5496bf8972b6f1f2b3d39baf6e6591363c4b79a7 1 0(0.00%) 616 616 616 | 620 0.00
826 GET /cpython/changeset/57740d19f5c2c063231d1d2898b940fd16f13355 1 0(0.00%) 408 408 408 | 410 0.00
827 GET /cpython/changeset/58e0d2c3ead8ff3dfe0cb5a6f79c0f69dde281b7 1 0(0.00%) 977 977 977 | 980 0.00
828 GET /cpython/changeset/5a157e3b3c47ed38e1f1f4c466f68c3c35585667 1 0(0.00%) 855 855 855 | 860 0.00
829 GET /cpython/changeset/5a58f6e793ccf17393c725e01b741c0fec5450f2 1 0(0.00%) 582 582 582 | 580 0.00
830 GET /cpython/changeset/5ac811cbec879f87ca1c6f91f2bd9f3ead8f3a85 1 0(0.00%) 903 903 903 | 900 0.00
831 GET /cpython/changeset/6219aa966a5fb7572502646377cc8d67c45697fb 1 0(0.00%) 1369 1369 1369 | 1400 0.00
832 GET /cpython/changeset/6298895a52de36edaf182316a1081ccdfebf134e 1 0(0.00%) 1125 1125 1125 | 1100 0.00
833 GET /cpython/changeset/65018d952340acda44c20baee75184a696dd1887 1 0(0.00%) 638 638 638 | 640 0.00
834 GET /cpython/changeset/66c7f30fe8c7943ff09500d9ccb4836a15dedec1 1 0(0.00%) 447 447 447 | 450 0.00
835 GET /cpython/changeset/69783040dd543d748f7197fdf7d9e147ba9cbc71 1 0(0.00%) 789 789 789 | 790 0.00
836 GET /cpython/changeset/6a71d3c796532cab3f2661e134bbf28ae9902435 2 0(0.00%) 729 276 1183 | 280 0.00
837 GET /cpython/changeset/6cbb97f039bdb1b63469c76e759d149d4e6841de 1 0(0.00%) 1097 1097 1097 | 1100 0.00
838 GET /cpython/changeset/6d41f139709b7c77293f9d71f2316b790ebecc21 1 0(0.00%) 1238 1238 1238 | 1200 0.00
839 GET /cpython/changeset/6e67a0394957f1756269defbaab505b45524b853 1 0(0.00%) 1467 1467 1467 | 1500 0.00
840 GET /cpython/changeset/734d5ab0ce6ac6333a35e98a014f49ea3b0205cb 1 0(0.00%) 687 687 687 | 690 0.00
841 GET /cpython/changeset/7640af73c19d5ebe409ad8aa41aa2eb5b8f6dac1 2 0(0.00%) 363 275 451 | 280 0.00
842 GET /cpython/changeset/766570a5d6070488cbf5acf967c500088d1f4c17 1 0(0.00%) 748 748 748 | 750 0.00
843 GET /cpython/changeset/7749d30d9caa81113f5a84578e55e5336251eb66 1 0(0.00%) 926 926 926 | 930 0.00
844 GET /cpython/changeset/77fbbb5a73064ee17425b5fd8e6d68cf91ca33ab 1 0(0.00%) 1261 1261 1261 | 1300 0.00
845 GET /cpython/changeset/7bc53cf8b2dfb0a9fe4dfb1bb08e26d8dce04eba 1 0(0.00%) 1096 1096 1096 | 1100 0.00
846 GET /cpython/changeset/7c19f1f792c6c8d5a7e4c3e8d424318c3fe0663b 1 0(0.00%) 293 293 293 | 290 0.00
847 GET /cpython/changeset/7ed237478fcc3695d2b95b861efcff0a43a023e5 3 0(0.00%) 537 256 959 | 400 0.00
848 GET /cpython/changeset/7f8843ec34ee4049493b58882cb7165d5e268f9b 1 0(0.00%) 1213 1213 1213 | 1200 0.00
849 GET /cpython/changeset/8039a20b18cc17fdbbc91758746e6344948022e4 1 0(0.00%) 1030 1030 1030 | 1000 0.00
850 GET /cpython/changeset/8106f91fccd66f1306426e190e684e395b9d3132 1 0(0.00%) 628 628 628 | 630 0.00
851 GET /cpython/changeset/823f5507bd864cdb1899dbc05a42355bf7188728 1 0(0.00%) 1031 1031 1031 | 1000 0.00
852 GET /cpython/changeset/855ff9182a0778ba1e20af6fc043d27591d66d80 2 0(0.00%) 877 864 891 | 860 0.10
853 GET /cpython/changeset/87f940e85cb0e48208ca25a87b5b58cb6cfb886e 1 0(0.00%) 304 304 304 | 300 0.00
854 GET /cpython/changeset/88a532a31eb383dd19a43d0a3c4c9841f84fd88f 2 0(0.00%) 834 664 1004 | 660 0.00
855 GET /cpython/changeset/897c9e6ddb1ac1d467d2bf700aecbc585c6e33ac 1 0(0.00%) 968 968 968 | 970 0.00
856 GET /cpython/changeset/8b8b79e1f8626a69c3f2abccb2147e148d52173e 1 0(0.00%) 352 352 352 | 350 0.00
857 GET /cpython/changeset/92d691c3ca009b890bd844708ef60335424295f6 1 0(0.00%) 1115 1115 1115 | 1100 0.00
858 GET /cpython/changeset/944fc499ccd30a5a4d868373b69d8585bd07cdf7 1 0(0.00%) 947 947 947 | 950 0.00
859 GET /cpython/changeset/958e8bebda6d3ec7fee21e0ede5a871b1c818948 1 0(0.00%) 545 545 545 | 550 0.00
860 GET /cpython/changeset/962c1fefc34af9db3110c9c6538e5cdb39c170de 1 0(0.00%) 694 694 694 | 690 0.00
861 GET /cpython/changeset/97ed0b51bbc078cd5a836932422a7517af6fa8aa 1 0(0.00%) 421 421 421 | 420 0.00
862 GET /cpython/changeset/9b450b19aa118c4888b6acc6ba5e8f0fb00aaa87 1 0(0.00%) 322 322 322 | 320 0.00
863 GET /cpython/changeset/9e3c367b45a189e941eb3a772964a54b5b7574d5 1 0(0.00%) 1069 1069 1069 | 1100 0.00
864 GET /cpython/changeset/a0ae1ac69925b3ae866c7e089e95400aa3681a42 1 0(0.00%) 277 277 277 | 280 0.00
865 GET /cpython/changeset/a62368778d6cd0c6473f149f8d850dcce7f31031 1 0(0.00%) 732 732 732 | 730 0.00
866 GET /cpython/changeset/a74c48aa43eb5109351b49d8b1e420612389a2e8 1 0(0.00%) 1344 1344 1344 | 1300 0.00
867 GET /cpython/changeset/a77dc87a34fffe2b6899fa6b5a8f5b226e9bf952 1 0(0.00%) 776 776 776 | 780 0.00
868 GET /cpython/changeset/a8db78d7b6574b425d6b1a38f55068761f5da2e7 1 0(0.00%) 1001 1001 1001 | 1000 0.00
869 GET /cpython/changeset/a9637b29954d952ffcdde6bd76cd2202282427a0 1 0(0.00%) 526 526 526 | 530 0.00
870 GET /cpython/changeset/aa059a8fb55a957b84bbe471bab74514adf23ee4 1 0(0.00%) 513 513 513 | 510 0.00
871 GET /cpython/changeset/ad7ac2a469e1e9858825a646ffcbce68aae8ed40 2 0(0.00%) 432 254 611 | 250 0.00
872 GET /cpython/changeset/afd64e6606288447e75ad8031cf72112f47b5dba 1 0(0.00%) 1036 1036 1036 | 1000 0.00
873 GET /cpython/changeset/b08921c7d1ecfa43aa47f3453a87f705e9b92af8 1 0(0.00%) 989 989 989 | 990 0.00
874 GET /cpython/changeset/b1c82ef96862101642deb702e046763f09678c3f 1 0(0.00%) 502 502 502 | 500 0.00
875 GET /cpython/changeset/b3aa30f474c4c5dd8f12c92c1156d7aa5393d72b 1 0(0.00%) 545 545 545 | 550 0.00
876 GET /cpython/changeset/b5114747d3edb248c91f0da90d10f0bcf9514630 1 0(0.00%) 771 771 771 | 770 0.00
877 GET /cpython/changeset/b94da2b69d13ecc9f8c13ad74c411409edd56ca5 2 0(0.00%) 547 489 606 | 490 0.00
878 GET /cpython/changeset/b957f475e41eaae8678c092b326b42579a5d2de6 1 0(0.00%) 292 292 292 | 290 0.00
879 GET /cpython/changeset/bc991d4f9ce70bdcc3da7cd0b1fe916a17a0a805 2 0(0.00%) 626 303 950 | 300 0.00
880 GET /cpython/changeset/c173a34f20c02bb5a188fa80ccdcf1b5fc7e551e 1 0(0.00%) 1085 1085 1085 | 1100 0.00
881 GET /cpython/changeset/c1edc4e43eb103254c5d96f1708542623fe08f17 1 0(0.00%) 291 291 291 | 290 0.00
882 GET /cpython/changeset/c238d2899d47c21cf34f6927146dce8a0126a6a0 1 0(0.00%) 426 426 426 | 430 0.00
883 GET /cpython/changeset/c48980af7df207e8c606e00c054eadb513f59d37 2 0(0.00%) 880 494 1267 | 490 0.00
884 GET /cpython/changeset/c4f053f1b47f9a63eb8bfb13bac2a5674e2ef999 1 0(0.00%) 604 604 604 | 600 0.00
885 GET /cpython/changeset/c755a3b58fa6d98b0c68e309d3299cdc52c34ba9 1 0(0.00%) 632 632 632 | 630 0.00
886 GET /cpython/changeset/c8ce5bca0fcda4307f7ac5d69103ce128a562705 1 0(0.00%) 1100 1100 1100 | 1100 0.00
887 GET /cpython/changeset/ca4a22256bf88452349c2ff54b90a4bd983cb72d 2 0(0.00%) 485 447 524 | 450 0.00
888 GET /cpython/changeset/d4b035c9fb7df8750d86f27702b41ee190ba4cb3 1 0(0.00%) 898 898 898 | 900 0.00
889 GET /cpython/changeset/d775fa9a97678fb93eef1396bf67832f872abbfd 1 0(0.00%) 815 815 815 | 820 0.00
890 GET /cpython/changeset/d85fcf23549e002626618196d930d3c807d2b20e 1 0(0.00%) 419 419 419 | 420 0.00
891 GET /cpython/changeset/d9607a71456e0ff635251fcb9ac09369cb0a4b89 1 0(0.00%) 444 444 444 | 440 0.00
892 GET /cpython/changeset/d99014320220951707f79d114ae33f8c5a372a87 1 0(0.00%) 1263 1263 1263 | 1300 0.00
893 GET /cpython/changeset/d9da4b77624b522ce367a37d737acd208b3f793f 1 0(0.00%) 1375 1375 1375 | 1400 0.00
894 GET /cpython/changeset/db600c927b2b87537c5e71ce39d825675cfb5e63 1 0(0.00%) 690 690 690 | 690 0.00
895 GET /cpython/changeset/dce9dbc8e8924582d3ddb3b18f40d712c30631ea 1 0(0.00%) 301 301 301 | 300 0.00
896 GET /cpython/changeset/e0561df131aaf6580523fbdb172239922e0026e9 1 0(0.00%) 571 571 571 | 570 0.00
897 GET /cpython/changeset/e0f681f4ade3af52915d5f32daac97ada580d71a 5 0(0.00%) 546 388 714 | 540 0.00
898 GET /cpython/changeset/e405bcbf761cc79afddc60045d7e94af6d2e144f 1 0(0.00%) 753 753 753 | 750 0.00
899 GET /cpython/changeset/e6bb59b6b85c14f25582961abcdd6a2c7e16f487 1 0(0.00%) 1046 1046 1046 | 1000 0.00
900 GET /cpython/changeset/e6be82fbc02519c1b61399bfd204d61489163eeb 1 0(0.00%) 1200 1200 1200 | 1200 0.00
901 GET /cpython/changeset/e750d2b44c1d4692545661fd325960b17fbe6fe6 1 0(0.00%) 636 636 636 | 640 0.00
902 GET /cpython/changeset/eafe4007c999f277bc84038af21d65fb6ea1aec8 1 0(0.00%) 1025 1025 1025 | 1000 0.00
903 GET /cpython/changeset/ebd6f7f7859f05efb66ee99fd22516d61b720afe 1 0(0.00%) 390 390 390 | 390 0.00
904 GET /cpython/changeset/ec2efc094d6245dbd0ce2a2fca394a15ee627888 1 0(0.00%) 967 967 967 | 970 0.00
905 GET /cpython/changeset/eee636e3edbe117a552d637afc0bcaf88f5c42a9 1 0(0.00%) 660 660 660 | 660 0.00
906 GET /cpython/changeset/efb0a3eb4e06e649d61dc9f96d3a8962974cf04f 1 0(0.00%) 878 878 878 | 880 0.00
907 GET /cpython/changeset/f02a563ad1bf5150dafe25080b249a955aa253f1 1 0(0.00%) 1233 1233 1233 | 1200 0.10
908 GET /cpython/changeset/f0a5be139717b166a72db1e713d97006b191c746 1 0(0.00%) 254 254 254 | 250 0.00
909 GET /cpython/changeset/f0ab6f9f06036dfacff09f22f86464840b50eb0a 3 0(0.00%) 804 538 987 | 890 0.00
910 GET /cpython/changeset/f5df571bfe1d66406c0171cd59cf0da9708d345c 1 0(0.00%) 387 387 387 | 390 0.00
911 GET /cpython/changeset/fc7f28b11d208f0e4716cbb0f7068901e7d65046 1 0(0.00%) 890 890 890 | 890 0.00
912 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/ 2 0(0.00%) 829 558 1101 | 560 0.00
913 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Grammar 3 0(0.00%) 882 576 1093 | 980 0.00
914 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Grammar/Grammar 2 0(0.00%) 590 414 766 | 410 0.00
915 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Makefile.pre.in 1 0(0.00%) 764 764 764 | 760 0.00
916 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc 3 0(0.00%) 1047 780 1284 | 1100 0.00
917 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/ACKS 1 0(0.00%) 1039 1039 1039 | 1000 0.00
918 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/RPM 1 0(0.00%) 210 210 210 | 210 0.00
919 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/coverity_model.c 1 0(0.00%) 446 446 446 | 450 0.00
920 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-config.in 1 0(0.00%) 776 776 776 | 780 0.00
921 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-wing4.wpr 1 0(0.00%) 769 769 769 | 770 0.00
922 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-wing5.wpr 1 0(0.00%) 883 883 883 | 880 0.00
923 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Misc/python.pc.in 1 0(0.00%) 382 382 382 | 380 0.00
924 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/PC 1 0(0.00%) 1841 1841 1841 | 1800 0.00
925 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Programs 1 0(0.00%) 751 751 751 | 750 0.00
926 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Programs/README 2 0(0.00%) 698 424 972 | 420 0.00
927 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/Programs/python.c 1 0(0.00%) 713 713 713 | 710 0.00
928 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/README 1 0(0.00%) 253 253 253 | 250 0.00
929 GET /cpython/files/74236c8bf064188516b32bf95016971227ec72a9/install-sh 1 0(0.00%) 474 474 474 | 470 0.00
930 GET /cpython/files/tip/ 1 0(0.00%) 1276 1276 1276 | 1300 0.00
931 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/.hgtouch 1 0(0.00%) 582 582 582 | 580 0.00
932 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Grammar/Grammar 5 0(0.00%) 486 209 784 | 480 0.00
933 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Misc/NEWS 1 0(0.00%) 518 518 518 | 520 0.00
934 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Misc/coverity_model.c 1 0(0.00%) 921 921 921 | 920 0.00
935 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Misc/python-wing4.wpr 1 0(0.00%) 413 413 413 | 410 0.00
936 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Programs/README 1 0(0.00%) 427 427 427 | 430 0.00
937 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Programs/_freeze_importlib.c 5 0(0.00%) 480 267 856 | 490 0.00
938 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/Programs/_testembed.c 1 0(0.00%) 531 531 531 | 530 0.00
939 GET /cpython/raw/74236c8bf064188516b32bf95016971227ec72a9/config.sub 2 0(0.00%) 630 496 765 | 500 0.00
940 GET /git 2 0(0.00%) 2382 1181 3584 | 1200 0.00
941 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/git-am.sh 1 0(0.00%) 2625 2625 2625 | 2600 0.00
942 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/.gitignore 2 0(0.00%) 1222 986 1458 | 990 0.00
943 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/Makefile 1 0(0.00%) 1497 1497 1497 | 1500 0.00
944 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/gitk 1 0(0.00%) 1263 1263 1263 | 1300 0.00
945 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/de.po 1 0(0.00%) 1625 1625 1625 | 1600 0.00
946 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/templates/info--exclude 1 0(0.00%) 1139 1139 1139 | 1100 0.00
947 GET /git/annotate/6c4ab27f2378ce67940b4496365043119d7ffff2/wildmatch.c 1 0(0.00%) 1340 1340 1340 | 1300 0.00
948 GET /git/changelog?page=1 1 0(0.00%) 1894 1894 1894 | 1900 0.00
949 GET /git/changelog?page=10 1 0(0.00%) 1618 1618 1618 | 1600 0.00
950 GET /git/changelog?page=11 1 0(0.00%) 910 910 910 | 910 0.00
951 GET /git/changelog?page=12 1 0(0.00%) 1512 1512 1512 | 1500 0.00
952 GET /git/changelog?page=13 1 0(0.00%) 1659 1659 1659 | 1700 0.00
953 GET /git/changelog?page=14 1 0(0.00%) 1599 1599 1599 | 1600 0.00
954 GET /git/changelog?page=15 1 0(0.00%) 1616 1616 1616 | 1600 0.00
955 GET /git/changelog?page=16 1 0(0.00%) 1647 1647 1647 | 1600 0.00
956 GET /git/changelog?page=17 1 0(0.00%) 1692 1692 1692 | 1700 0.00
957 GET /git/changelog?page=18 1 0(0.00%) 1636 1636 1636 | 1600 0.00
958 GET /git/changelog?page=2 1 0(0.00%) 1820 1820 1820 | 1800 0.00
959 GET /git/changelog?page=3 1 0(0.00%) 1702 1702 1702 | 1700 0.00
960 GET /git/changelog?page=4 1 0(0.00%) 1395 1395 1395 | 1400 0.00
961 GET /git/changelog?page=5 2 0(0.00%) 1699 1530 1868 | 1500 0.00
962 GET /git/changelog?page=6 3 0(0.00%) 1573 1361 1732 | 1600 0.00
963 GET /git/changelog?page=7 3 0(0.00%) 1372 1104 1718 | 1300 0.00
964 GET /git/changelog?page=8 2 0(0.00%) 1318 1075 1561 | 1100 0.00
965 GET /git/changelog?page=9 1 0(0.00%) 1522 1522 1522 | 1500 0.00
966 GET /git/changelog_summary?size=10&page=10 2 0(0.00%) 1350 1325 1376 | 1300 0.10
967 GET /git/changelog_summary?size=10&page=11 1 0(0.00%) 1381 1381 1381 | 1400 0.00
968 GET /git/changelog_summary?size=10&page=2 3 0(0.00%) 852 722 924 | 910 0.00
969 GET /git/changelog_summary?size=10&page=3 3 0(0.00%) 1054 728 1462 | 970 0.00
970 GET /git/changelog_summary?size=10&page=4 2 0(0.00%) 1362 1356 1368 | 1400 0.00
971 GET /git/changelog_summary?size=10&page=5 2 0(0.00%) 1119 923 1315 | 920 0.00
972 GET /git/changelog_summary?size=10&page=6 1 0(0.00%) 524 524 524 | 520 0.00
973 GET /git/changelog_summary?size=10&page=7 2 0(0.00%) 800 549 1051 | 550 0.10
974 GET /git/changelog_summary?size=10&page=8 2 0(0.00%) 602 472 732 | 470 0.10
975 GET /git/changelog_summary?size=10&page=9 1 0(0.00%) 864 864 864 | 860 0.10
976 GET /git/changeset/0d854fc1e35c145f81d97c4565bd6c07a7d47f19 1 0(0.00%) 964 964 964 | 960 0.00
977 GET /git/changeset/3a224ff2bbb8a3782d6db67426aa8bc09063ab08 1 0(0.00%) 469 469 469 | 470 0.00
978 GET /git/changeset/5796c5baa3ef0a7d179fdac6cb5815f75f65fd53 1 0(0.00%) 874 874 874 | 870 0.00
979 GET /git/changeset/63618af24a67e5fc959217201d9f220383a39fd0 1 0(0.00%) 715 715 715 | 720 0.00
980 GET /git/changeset/8c3f3f28cb8c946867365292abfc2c33a5707d3e 1 0(0.00%) 1028 1028 1028 | 1000 0.00
981 GET /git/changeset/af3e5d1b2af6322621f26791abf8d619771f04e2 1 0(0.00%) 905 905 905 | 910 0.10
982 GET /git/changeset/d299e9e550c1bf8640907fdba1f03cc585ee71df 2 0(0.00%) 539 307 772 | 310 0.00
983 GET /git/changeset/d8b396e17ecfe28b39b5f4470f791c434cce40ec 1 0(0.00%) 1169 1169 1169 | 1200 0.00
984 GET /git/changeset/fe24d396e1b8d2eedbc07f626af3dcd2b14e6012 1 0(0.00%) 1285 1285 1285 | 1300 0.00
985 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git 3 0(0.00%) 908 477 1271 | 980 0.00
986 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/.gitignore 3 0(0.00%) 784 423 1116 | 810 0.00
987 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/Makefile 1 0(0.00%) 1049 1049 1049 | 1000 0.00
988 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po 2 0(0.00%) 2989 2668 3311 | 2700 0.00
989 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/.gitignore 1 0(0.00%) 860 860 860 | 860 0.00
990 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/hu.po 1 0(0.00%) 640 640 640 | 640 0.00
991 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/sv.po 1 0(0.00%) 905 905 905 | 910 0.00
992 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/notes-merge.c 1 0(0.00%) 645 645 645 | 650 0.00
993 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/perl 1 0(0.00%) 1516 1516 1516 | 1500 0.00
994 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/perl/Git 1 0(0.00%) 1018 1018 1018 | 1000 0.00
995 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/progress.h 1 0(0.00%) 1179 1179 1179 | 1200 0.00
996 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/sigchain.c 1 0(0.00%) 927 927 927 | 930 0.00
997 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/templates 1 0(0.00%) 4762 4762 4762 | 4800 0.00
998 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/templates/hooks--prepare-commit-msg.sample 1 0(0.00%) 841 841 841 | 840 0.00
999 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/test-hashmap.c 1 0(0.00%) 864 864 864 | 860 0.00
1000 GET /git/files/6c4ab27f2378ce67940b4496365043119d7ffff2/vcs-svn 1 0(0.00%) 3691 3691 3691 | 3700 0.00
1001 GET /git/files/tip/ 3 0(0.00%) 42597 40984 43882 | 43000 0.00
1002 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/git-web--browse.sh 1 0(0.00%) 411 411 411 | 410 0.00
1003 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/.gitignore 1 0(0.00%) 509 509 509 | 510 0.00
1004 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/Makefile 1 0(0.00%) 863 863 863 | 860 0.00
1005 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/gitk-git/po/it.po 1 0(0.00%) 289 289 289 | 290 0.00
1006 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/line-range.c 1 0(0.00%) 182 182 182 | 180 0.00
1007 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/perl/Git/I18N.pm 2 0(0.00%) 496 379 614 | 380 0.00
1008 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/templates/branches-- 1 0(0.00%) 675 675 675 | 680 0.00
1009 GET /git/raw/6c4ab27f2378ce67940b4496365043119d7ffff2/templates/this--description 1 0(0.00%) 559 559 559 | 560 0.00
1010 --------------------------------------------------------------------------------------------------------------------------------------------
1011 Total 500 0(0.00%) 1.30
@@ -1,498 +0,0 b''
1 <html xmlns="http://www.w3.org/1999/xhtml">
2 <head>
3 <title>
4 Gist &middot; 22
5 </title>
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <meta name="robots" content="index, nofollow"/>
8 <link rel="icon" href="/images/favicon.ico?ver=b5cbcb03" sizes="16x16 32x32" type="image/png" />
9 <link rel="stylesheet" type="text/css" href="/css/style.css?ver=b5cbcb03" media="screen"/>
10 <!--[if lt IE 9]>
11 <link rel="stylesheet" type="text/css" href="/css/ie.css?ver=b5cbcb03" media="screen"/>
12 <![endif]-->
13 <script src="/js/rhodecode/i18n/en.js?ver=b5cbcb03"></script>
14 <script type="text/javascript">
15 // register templateContext to pass template variables to JS
16 var templateContext = {
17 repo_name: "",
18 repo_type: null,
19 repo_landing_commit: null,
20 rhodecode_user: {
21 username: "default",
22 email: "anonymous@rhodecode.org",
23 },
24 visual: {
25 default_renderer: "rst"
26 },
27 commit_data: {
28 commit_id: null,
29 },
30 pull_request_data: {
31 pull_request_id: null,
32 },
33 timeago: {
34 refresh_time: 120000,
35 cutoff_limit: 604800000
36 }
37 };
38 var REPO_NAME = "";
39 var REPO_LANDING_REV = '';
40 var REPO_TYPE = '';
41 var APPLICATION_URL = "";
42 var DEFAULT_RENDERER = "rst";
43 var CSRF_TOKEN = "0fd2775cf20f09b9942125e6ffb4c6da143d1b14";
44 var USER = {name:'default'};
45 var APPENLIGHT = {
46 enabled: false,
47 key: 'YOUR_API_PUBLIC_KEY',
48 serverUrl: 'https://api.appenlight.com',
49 requestInfo: {
50 ip: '0.0.0.0',
51 username: 'default'
52 }
53 };
54 </script>
55 <!--[if lt IE 9]>
56 <script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script>
57 <![endif]-->
58 <script language="javascript" type="text/javascript" src="/js/scripts.js?ver=b5cbcb03"></script>
59 <script>CodeMirror.modeURL = "/js/mode/%N/%N.js";</script>
60 <script type="text/javascript">
61 $(document).ready(function(){
62 tooltip_activate();
63 show_more_event();
64 show_changeset_tooltip();
65 timeagoActivate();
66 })
67 </script>
68 </head>
69 <body id="body">
70 <noscript>
71 <div class="noscript-error">
72 Please enable JavaScript to use RhodeCode Enterprise
73 </div>
74 </noscript>
75 <!--[if IE 7]>
76 <script>$(document.body).addClass('ie7')</script>
77 <![endif]-->
78 <!--[if IE 8]>
79 <script>$(document.body).addClass('ie8')</script>
80 <![endif]-->
81 <!--[if IE 9]>
82 <script>$(document.body).addClass('ie9')</script>
83 <![endif]-->
84 <div class="outerwrapper">
85 <!-- HEADER -->
86 <div class="header">
87 <div id="header-inner" class="wrapper">
88 <div id="logo">
89 <div class="logo-wrapper">
90 <a href="/"><img src="/images/rhodecode-logo-white-216x60.png" alt="RhodeCode"/></a>
91 </div>
92 </div>
93 <!-- MENU BAR NAV -->
94 <ul id="quick" class="main_nav navigation horizontal-list">
95 <!-- repo switcher -->
96 <li class=" repo_switcher_li has_select2">
97 <input id="repo_switcher" name="repo_switcher" type="hidden">
98 </li>
99 <li class="">
100 <a class="menulink" title="Show Public activity journal" href="/_admin/public_journal">
101 <div class="menulabel">Public journal</div>
102 </a>
103 </li>
104 <li class="active">
105 <a class="menulink childs" title="Show Gists" href="/_admin/gists">
106 <div class="menulabel">Gists</div>
107 </a>
108 </li>
109 <li class="">
110 <a class="menulink" title="Search in repositories you have access to" href="/_admin/search">
111 <div class="menulabel">Search</div>
112 </a>
113 </li>
114 <li id="quick_login_li">
115 <a id="quick_login_link" class="menulink childs">
116 <img class="gravatar gravatar-large" src="data:image/svg+xml;base64,CiAgICAgICAgPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgICAgICAg
117 dmVyc2lvbj0iMS4xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIgogICAg
118 ICAgIHZpZXdCb3g9Ii0xNSAtMTAgNDM5LjE2NSA0MjkuMTY0IgoKICAgICAgICB4bWw6c3BhY2U9
119 InByZXNlcnZlIgogICAgICAgIHN0eWxlPSJiYWNrZ3JvdW5kOiM5Nzk3OTc7IiA+CgogICAgICAg
120 IDxwYXRoIGQ9Ik0yMDQuNTgzLDIxNi42NzFjNTAuNjY0LDAsOTEuNzQtNDguMDc1LAogICAgICAg
121 ICAgICAgICAgIDkxLjc0LTEwNy4zNzhjMC04Mi4yMzctNDEuMDc0LTEwNy4zNzctOTEuNzQtMTA3
122 LjM3NwogICAgICAgICAgICAgICAgIGMtNTAuNjY4LDAtOTEuNzQsMjUuMTQtOTEuNzQsMTA3LjM3
123 N0MxMTIuODQ0LAogICAgICAgICAgICAgICAgIDE2OC41OTYsMTUzLjkxNiwyMTYuNjcxLAogICAg
124 ICAgICAgICAgICAgIDIwNC41ODMsMjE2LjY3MXoiIGZpbGw9IiNmZmYiLz4KICAgICAgICA8cGF0
125 aCBkPSJNNDA3LjE2NCwzNzQuNzE3TDM2MC44OCwKICAgICAgICAgICAgICAgICAyNzAuNDU0Yy0y
126 LjExNy00Ljc3MS01LjgzNi04LjcyOC0xMC40NjUtMTEuMTM4bC03MS44My0zNy4zOTIKICAgICAg
127 ICAgICAgICAgICBjLTEuNTg0LTAuODIzLTMuNTAyLTAuNjYzLTQuOTI2LDAuNDE1Yy0yMC4zMTYs
128 CiAgICAgICAgICAgICAgICAgMTUuMzY2LTQ0LjIwMywyMy40ODgtNjkuMDc2LDIzLjQ4OGMtMjQu
129 ODc3LAogICAgICAgICAgICAgICAgIDAtNDguNzYyLTguMTIyLTY5LjA3OC0yMy40ODgKICAgICAg
130 ICAgICAgICAgICBjLTEuNDI4LTEuMDc4LTMuMzQ2LTEuMjM4LTQuOTMtMC40MTVMNTguNzUsCiAg
131 ICAgICAgICAgICAgICAgMjU5LjMxNmMtNC42MzEsMi40MS04LjM0Niw2LjM2NS0xMC40NjUsMTEu
132 MTM4TDIuMDAxLDM3NC43MTcKICAgICAgICAgICAgICAgICBjLTMuMTkxLDcuMTg4LTIuNTM3LDE1
133 LjQxMiwxLjc1LDIyLjAwNWM0LjI4NSwKICAgICAgICAgICAgICAgICA2LjU5MiwxMS41MzcsMTAu
134 NTI2LDE5LjQsMTAuNTI2aDM2Mi44NjFjNy44NjMsMCwxNS4xMTctMy45MzYsCiAgICAgICAgICAg
135 ICAgICAgMTkuNDAyLTEwLjUyNyBDNDA5LjY5OSwzOTAuMTI5LAogICAgICAgICAgICAgICAgIDQx
136 MC4zNTUsMzgxLjkwMiw0MDcuMTY0LDM3NC43MTd6IiBmaWxsPSIjZmZmIi8+CiAgICAgICAgPC9z
137 dmc+
138 " height="20" width="20">
139 <span class="user">
140 <span>Sign in</span>
141 </span>
142 </a>
143 <div class="user-menu submenu">
144 <div id="quick_login">
145 <h4>Sign in to your account</h4>
146 <form action="/_admin/login?came_from=%2F_admin%2Fgists%2F22" method="post">
147 <div class="form form-vertical">
148 <div class="fields">
149 <div class="field">
150 <div class="label">
151 <label for="username">Username:</label>
152 </div>
153 <div class="input">
154 <input class="focus" id="username" name="username" tabindex="1" type="text" />
155 </div>
156 </div>
157 <div class="field">
158 <div class="label">
159 <label for="password">Password:</label>
160 <span class="forgot_password"><a href="/_admin/password_reset">(Forgot password?)</a></span>
161 </div>
162 <div class="input">
163 <input class="focus" id="password" name="password" tabindex="2" type="password" />
164 </div>
165 </div>
166 <div class="buttons">
167 <div class="register">
168 <a href="/_admin/register">Don&#39;t have an account ?</a>
169 </div>
170 <div class="submit">
171 <input class="btn btn-small" id="sign_in" name="sign_in" tabindex="3" type="submit" value="Sign In" />
172 </div>
173 </div>
174 </div>
175 </div>
176 </form>
177 </div>
178 </div>
179 </li>
180 </ul>
181 <script type="text/javascript">
182 var visual_show_public_icon = "True" == "True";
183 /*format the look of items in the list*/
184 var format = function(state, escapeMarkup){
185 if (!state.id){
186 return state.text; // optgroup
187 }
188 var obj_dict = state.obj;
189 var tmpl = '';
190 if(obj_dict && state.type == 'repo'){
191 if(obj_dict['repo_type'] === 'hg'){
192 tmpl += '<i class="icon-hg"></i> ';
193 }
194 else if(obj_dict['repo_type'] === 'git'){
195 tmpl += '<i class="icon-git"></i> ';
196 }
197 else if(obj_dict['repo_type'] === 'svn'){
198 tmpl += '<i class="icon-svn"></i> ';
199 }
200 if(obj_dict['private']){
201 tmpl += '<i class="icon-lock" ></i> ';
202 }
203 else if(visual_show_public_icon){
204 tmpl += '<i class="icon-unlock-alt"></i> ';
205 }
206 }
207 if(obj_dict && state.type == 'group'){
208 tmpl += '<i class="icon-folder-close"></i> ';
209 }
210 tmpl += escapeMarkup(state.text);
211 return tmpl;
212 };
213 var formatResult = function(result, container, query, escapeMarkup) {
214 return format(result, escapeMarkup);
215 };
216 var formatSelection = function(data, container, escapeMarkup) {
217 return format(data, escapeMarkup);
218 };
219 $("#repo_switcher").select2({
220 cachedDataSource: {},
221 minimumInputLength: 2,
222 placeholder: '<div class="menulabel">Go to <div class="show_more"></div></div>',
223 dropdownAutoWidth: true,
224 formatResult: formatResult,
225 formatSelection: formatSelection,
226 containerCssClass: "repo-switcher",
227 dropdownCssClass: "repo-switcher-dropdown",
228 escapeMarkup: function(m){
229 // don't escape our custom placeholder
230 if(m.substr(0,23) == '<div class="menulabel">'){
231 return m;
232 }
233 return Select2.util.escapeMarkup(m);
234 },
235 query: $.debounce(250, function(query){
236 self = this;
237 var cacheKey = query.term;
238 var cachedData = self.cachedDataSource[cacheKey];
239 if (cachedData) {
240 query.callback({results: cachedData.results});
241 } else {
242 $.ajax({
243 url: "/_repos_and_groups",
244 data: {'query': query.term},
245 dataType: 'json',
246 type: 'GET',
247 success: function(data) {
248 self.cachedDataSource[cacheKey] = data;
249 query.callback({results: data.results});
250 },
251 error: function(data, textStatus, errorThrown) {
252 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
253 }
254 })
255 }
256 })
257 });
258 $("#repo_switcher").on('select2-selecting', function(e){
259 e.preventDefault();
260 window.location = pyroutes.url('summary_home', {'repo_name': e.val});
261 });
262 // general help "?"
263 Mousetrap.bind(['?'], function(e) {
264 $('#help_kb').modal({})
265 });
266 // / open the quick filter
267 Mousetrap.bind(['/'], function(e) {
268 $("#repo_switcher").select2("open");
269 // return false to prevent default browser behavior
270 // and stop event from bubbling
271 return false;
272 });
273 // general nav g + action
274 Mousetrap.bind(['g h'], function(e) {
275 window.location = pyroutes.url('home');
276 });
277 Mousetrap.bind(['g g'], function(e) {
278 window.location = pyroutes.url('gists', {'private':1});
279 });
280 Mousetrap.bind(['g G'], function(e) {
281 window.location = pyroutes.url('gists', {'public':1});
282 });
283 Mousetrap.bind(['n g'], function(e) {
284 window.location = pyroutes.url('new_gist');
285 });
286 Mousetrap.bind(['n r'], function(e) {
287 window.location = pyroutes.url('new_repo');
288 });
289 </script>
290 <script src="/js/rhodecode/base/keyboard-bindings.js?ver=b5cbcb03"></script>
291 <!-- END MENU BAR NAV -->
292 </div>
293 </div>
294 <!-- END HEADER -->
295 <!-- CONTENT -->
296 <div id="content" class="wrapper">
297 <div class="flash_msg">
298 <script>
299 if (typeof jQuery != 'undefined') {
300 $(".alert").alert();
301 }
302 </script>
303 </div>
304 <div class="main">
305 <div class="box">
306 <!-- box / title -->
307 <div class="title">
308 <div class="breadcrumbs">
309 Gist &middot; 22
310 / URL: http://test.example.com:80/_admin/gists/22
311 </div>
312 </div>
313 <div class="table">
314 <div id="files_data">
315 <div id="codeblock" class="codeblock">
316 <div class="code-header">
317 <div class="stats">
318 <div class="buttons">
319 <a class="btn btn-mini" href="/_admin/gists/22/tip/raw">Show as Raw</a>
320 </div>
321 <div class="left" >
322 <span> gist-desc</span>
323 <span>Expires:
324 never
325 </span>
326 </div>
327 </div>
328 <div class="author">
329 <div title="RhodeCode Admin &lt;test_admin@mail.com&gt;">
330 <div class="rc-user tooltip" title="RhodeCode Admin &lt;test_admin@mail.com&gt;">
331 <img class="gravatar" src="data:image/svg+xml;base64,CiAgICAgICAgPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHBvaW50ZXIt
332 ZXZlbnRzPSJub25lIgogICAgICAgICAgICAgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIgogICAgICAg
333 ICAgICAgc3R5bGU9IndpZHRoOiAxMDAlOyBoZWlnaHQ6IDEwMCU7IGJhY2tncm91bmQtY29sb3I6
334 ICM1OWIzODkiCiAgICAgICAgICAgICB2aWV3Qm94PSIwIDAgMzIgMzIiPgogICAgICAgICAgICA8
335 dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB5PSI1MCUiIHg9IjUwJSIgZHk9IjAuMzVlbSIKICAg
336 ICAgICAgICAgICAgICAgcG9pbnRlci1ldmVudHM9ImF1dG8iIGZpbGw9IiNmZmYiCiAgICAgICAg
337 ICAgICAgICAgIGZvbnQtZmFtaWx5PSJwcm94aW1hbm92YXJlZ3VsYXIsUHJveGltYSBOb3ZhIFJl
338 Z3VsYXIsUHJveGltYSBOb3ZhLEFyaWFsLEx1Y2lkYSBHcmFuZGUsc2Fucy1zZXJpZiIKICAgICAg
339 ICAgICAgICAgICAgc3R5bGU9ImZvbnQtd2VpZ2h0OiA0MDA7IGZvbnQtc2l6ZTogMTcuMjk3Mjk3
340 Mjk3M3B4OyI+VE0KICAgICAgICAgICAgPC90ZXh0PgogICAgICAgIDwvc3ZnPg==
341 " height="16" width="16">
342 <span class="user"> <a href="/_profiles/test_admin">test_admin</a></span>
343 </div>
344 - created <time class="timeago tooltip" title="Mon, 23 May 2016 19:54:34" datetime="2016-05-23 19:54:34+00:00">Mon, 23 May 2016 19:54:34</time>
345 </div>
346 </div>
347 <div class="commit">added file: gist-show-me</div>
348 </div>
349 <!-- <div id="c-G-eff3be0ea272" class="stats" >
350 <a href="http://test.example.com:80/_admin/gists/22">¶</a>
351 <b >gist-show-me</b>
352 <div>
353 <a class="btn btn-mini" href="/_admin/gists/22/3f6f18ddcd5669697303ab3cd5a2eabb1c558c6f/raw/gist-show-me">Show as raw</a>
354 </div>
355 </div> -->
356 <div class="code-body textarea text-area editor">
357 <table class="code-highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><a href="#L1">1</a></pre></div></td><td id="hlcode" class="code"><div class="code-highlight"><pre><div id="L1"><a name="L-1"></a>some gist
358 </div></pre></div>
359 </td></tr></table>
360 </div>
361 </div>
362 </div>
363 </div>
364 </div>
365 </div>
366 </div>
367 <!-- END CONTENT -->
368 </div>
369 <!-- FOOTER -->
370 <div id="footer">
371 <div id="footer-inner" class="title wrapper">
372 <div>
373 <p class="footer-link-right">
374 RhodeCode Enterprise 4.0.0 Community Edition
375 &copy; 2010-2016, <a href="https://rhodecode.com" target="_blank">RhodeCode GmbH</a>. All rights reserved.
376 <a href="https://rhodecode.com/help/" target="_blank">Support</a>
377 </p>
378 <p class="server-instance" style="display:none">
379 RhodeCode instance id: vps1.local-105134
380 </p>
381 </div>
382 </div>
383 </div>
384 <!-- END FOOTER -->
385 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
386 <div class="modal-dialog">
387 <div class="modal-content">
388 <div class="modal-header">
389 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
390 <h4 class="modal-title" id="myModalLabel">Keyboard shortcuts</h4>
391 </div>
392 <div class="modal-body">
393 <div class="block-left">
394 <table class="keyboard-mappings">
395 <tbody>
396 <tr>
397 <th></th>
398 <th>Site-wide shortcuts</th>
399 </tr>
400 <tr>
401 <td class="keys">
402 <span class="key tag">/</span>
403 </td>
404 <td>Open quick search box</td>
405 </tr>
406 <tr>
407 <td class="keys">
408 <span class="key tag">g h</span>
409 </td>
410 <td>Goto home page</td>
411 </tr>
412 <tr>
413 <td class="keys">
414 <span class="key tag">g g</span>
415 </td>
416 <td>Goto my private gists page</td>
417 </tr>
418 <tr>
419 <td class="keys">
420 <span class="key tag">g G</span>
421 </td>
422 <td>Goto my public gists page</td>
423 </tr>
424 <tr>
425 <td class="keys">
426 <span class="key tag">n r</span>
427 </td>
428 <td>New repository page</td>
429 </tr>
430 <tr>
431 <td class="keys">
432 <span class="key tag">n g</span>
433 </td>
434 <td>New gist page</td>
435 </tr>
436 </tbody>
437 </table>
438 </div>
439 <div class="block-left">
440 <table class="keyboard-mappings">
441 <tbody>
442 <tr>
443 <th></th>
444 <th>Repositories</th>
445 </tr>
446 <tr>
447 <td class="keys">
448 <span class="key tag">g s</span>
449 </td>
450 <td>Goto summary page</td>
451 </tr>
452 <tr>
453 <td class="keys">
454 <span class="key tag">g c</span>
455 </td>
456 <td>Goto changelog page</td>
457 </tr>
458 <tr>
459 <td class="keys">
460 <span class="key tag">g f</span>
461 </td>
462 <td>Goto files page</td>
463 </tr>
464 <tr>
465 <td class="keys">
466 <span class="key tag">g F</span>
467 </td>
468 <td>Goto files page with file search activated</td>
469 </tr>
470 <tr>
471 <td class="keys">
472 <span class="key tag">g p</span>
473 </td>
474 <td>Goto pull requests page</td>
475 </tr>
476 <tr>
477 <td class="keys">
478 <span class="key tag">g o</span>
479 </td>
480 <td>Goto repository settings</td>
481 </tr>
482 <tr>
483 <td class="keys">
484 <span class="key tag">g O</span>
485 </td>
486 <td>Goto repository permissions settings</td>
487 </tr>
488 </tbody>
489 </table>
490 </div>
491 </div>
492 <div class="modal-footer">
493 </div>
494 </div><!-- /.modal-content -->
495 </div><!-- /.modal-dialog -->
496 </div><!-- /.modal -->
497 </body>
498 </html> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now