##// END OF EJS Templates
merge with beta
marcink -
r2908:3148c08c merge rhodecode-0.0.1.4.4 default
parent child Browse files
Show More
@@ -1,30 +1,31 b''
1 List of contributors to RhodeCode project:
1 List of contributors to RhodeCode project:
2 Marcin KuΕΊmiΕ„ski <marcin@python-works.com>
2 Marcin KuΕΊmiΕ„ski <marcin@python-works.com>
3 Lukasz Balcerzak <lukaszbalcerzak@gmail.com>
3 Lukasz Balcerzak <lukaszbalcerzak@gmail.com>
4 Jason Harris <jason@jasonfharris.com>
4 Jason Harris <jason@jasonfharris.com>
5 Thayne Harbaugh <thayne@fusionio.com>
5 Thayne Harbaugh <thayne@fusionio.com>
6 cejones <>
6 cejones <>
7 Thomas Waldmann <tw-public@gmx.de>
7 Thomas Waldmann <tw-public@gmx.de>
8 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
8 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
9 Dmitri Kuznetsov <>
9 Dmitri Kuznetsov <>
10 Jared Bunting <jared.bunting@peachjean.com>
10 Jared Bunting <jared.bunting@peachjean.com>
11 Steve Romanow <slestak989@gmail.com>
11 Steve Romanow <slestak989@gmail.com>
12 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
12 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
13 Ankit Solanki <ankit.solanki@gmail.com>
13 Ankit Solanki <ankit.solanki@gmail.com>
14 Liad Shani <liadff@gmail.com>
14 Liad Shani <liadff@gmail.com>
15 Les Peabody <lpeabody@gmail.com>
15 Les Peabody <lpeabody@gmail.com>
16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
17 Matt Zuba <matt.zuba@goodwillaz.org>
17 Matt Zuba <matt.zuba@goodwillaz.org>
18 Aras Pranckevicius <aras@unity3d.com>
18 Aras Pranckevicius <aras@unity3d.com>
19 Tony Bussieres <t.bussieres@gmail.com>
19 Tony Bussieres <t.bussieres@gmail.com>
20 Erwin Kroon <e.kroon@smartmetersolutions.nl>
20 Erwin Kroon <e.kroon@smartmetersolutions.nl>
21 nansenat16 <nansenat16@null.tw>
21 nansenat16 <nansenat16@null.tw>
22 Vincent Duvert <vincent@duvert.net>
22 Vincent Duvert <vincent@duvert.net>
23 Takumi IINO <trot.thunder@gmail.com>
23 Takumi IINO <trot.thunder@gmail.com>
24 Indra Talip <indra.talip@gmail.com>
24 Indra Talip <indra.talip@gmail.com>
25 James Rhodes <jrhodes@redpointsoftware.com.au>
25 James Rhodes <jrhodes@redpointsoftware.com.au>
26 Dominik Ruf <dominikruf@gmail.com>
26 Dominik Ruf <dominikruf@gmail.com>
27 xpol <xpolife@gmail.com>
27 xpol <xpolife@gmail.com>
28 Vincent Caron <vcaron@bearstech.com>
28 Vincent Caron <vcaron@bearstech.com>
29 Zachary Auclair <zach101@gmail.com>
29 Zachary Auclair <zach101@gmail.com>
30 Stefan Engel <mail@engel-stefan.de>
30 Stefan Engel <mail@engel-stefan.de>
31 Andrew Shadura <bugzilla@tut.by> No newline at end of file
@@ -1,876 +1,846 b''
1 .. _api:
1 .. _api:
2
2
3 ===
3 ===
4 API
4 API
5 ===
5 ===
6
6
7
7
8 Starting from RhodeCode version 1.2 a simple API was implemented.
8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 There's a single schema for calling all api methods. API is implemented
9 There's a single schema for calling all api methods. API is implemented
10 with JSON protocol both ways. An url to send API request to RhodeCode is
10 with JSON protocol both ways. An url to send API request to RhodeCode is
11 <your_server>/_admin/api
11 <your_server>/_admin/api
12
12
13 API ACCESS FOR WEB VIEWS
13 API ACCESS FOR WEB VIEWS
14 ++++++++++++++++++++++++
14 ++++++++++++++++++++++++
15
15
16 API access can also be turned on for each web view in RhodeCode that is
16 API access can also be turned on for each web view in RhodeCode that is
17 decorated with `@LoginRequired` decorator. To enable API access simple change
17 decorated with `@LoginRequired` decorator. To enable API access simple change
18 the standard login decorator to `@LoginRequired(api_access=True)`.
18 the standard login decorator to `@LoginRequired(api_access=True)`.
19 After this change, a rhodecode view can be accessed without login by adding a
19 After this change, a rhodecode view can be accessed without login by adding a
20 GET parameter `?api_key=<api_key>` to url. By default this is only
20 GET parameter `?api_key=<api_key>` to url. By default this is only
21 enabled on RSS/ATOM feed views.
21 enabled on RSS/ATOM feed views.
22
22
23
23
24 API ACCESS
24 API ACCESS
25 ++++++++++
25 ++++++++++
26
26
27 All clients are required to send JSON-RPC spec JSON data::
27 All clients are required to send JSON-RPC spec JSON data::
28
28
29 {
29 {
30 "id:"<id>",
30 "id:"<id>",
31 "api_key":"<api_key>",
31 "api_key":"<api_key>",
32 "method":"<method_name>",
32 "method":"<method_name>",
33 "args":{"<arg_key>":"<arg_val>"}
33 "args":{"<arg_key>":"<arg_val>"}
34 }
34 }
35
35
36 Example call for autopulling remotes repos using curl::
36 Example call for autopulling remotes repos using curl::
37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
38
38
39 Simply provide
39 Simply provide
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
41 - *api_key* for access and permission validation.
41 - *api_key* for access and permission validation.
42 - *method* is name of method to call
42 - *method* is name of method to call
43 - *args* is an key:value list of arguments to pass to method
43 - *args* is an key:value list of arguments to pass to method
44
44
45 .. note::
45 .. note::
46
46
47 api_key can be found in your user account page
47 api_key can be found in your user account page
48
48
49
49
50 RhodeCode API will return always a JSON-RPC response::
50 RhodeCode API will return always a JSON-RPC response::
51
51
52 {
52 {
53 "id":<id>, # matching id sent by request
53 "id":<id>, # matching id sent by request
54 "result": "<result>"|null, # JSON formatted result, null if any errors
54 "result": "<result>"|null, # JSON formatted result, null if any errors
55 "error": "null"|<error_message> # JSON formatted error (if any)
55 "error": "null"|<error_message> # JSON formatted error (if any)
56 }
56 }
57
57
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
59 calling api *error* key from response will contain failure description
59 calling api *error* key from response will contain failure description
60 and result will be null.
60 and result will be null.
61
61
62
62
63 API CLIENT
63 API CLIENT
64 ++++++++++
64 ++++++++++
65
65
66 From version 1.4 RhodeCode adds a script that allows to easily
66 From version 1.4 RhodeCode adds a script that allows to easily
67 communicate with API. After installing RhodeCode a `rhodecode-api` script
67 communicate with API. After installing RhodeCode a `rhodecode-api` script
68 will be available.
68 will be available.
69
69
70 To get started quickly simply run::
70 To get started quickly simply run::
71
71
72 rhodecode-api _create_config --apikey=<youapikey> --apihost=<rhodecode host>
72 rhodecode-api _create_config --apikey=<youapikey> --apihost=<rhodecode host>
73
73
74 This will create a file named .config in the directory you executed it storing
74 This will create a file named .config in the directory you executed it storing
75 json config file with credentials. You can skip this step and always provide
75 json config file with credentials. You can skip this step and always provide
76 both of the arguments to be able to communicate with server
76 both of the arguments to be able to communicate with server
77
77
78
78
79 after that simply run any api command for example get_repo::
79 after that simply run any api command for example get_repo::
80
80
81 rhodecode-api get_repo
81 rhodecode-api get_repo
82
82
83 calling {"api_key": "<apikey>", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000
83 calling {"api_key": "<apikey>", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000
84 rhodecode said:
84 rhodecode said:
85 {'error': 'Missing non optional `repoid` arg in JSON DATA',
85 {'error': 'Missing non optional `repoid` arg in JSON DATA',
86 'id': 75,
86 'id': 75,
87 'result': None}
87 'result': None}
88
88
89 Ups looks like we forgot to add an argument
89 Ups looks like we forgot to add an argument
90
90
91 Let's try again now giving the repoid as parameters::
91 Let's try again now giving the repoid as parameters::
92
92
93 rhodecode-api get_repo repoid:rhodecode
93 rhodecode-api get_repo repoid:rhodecode
94
94
95 calling {"api_key": "<apikey>", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000
95 calling {"api_key": "<apikey>", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000
96 rhodecode said:
96 rhodecode said:
97 {'error': None,
97 {'error': None,
98 'id': 39,
98 'id': 39,
99 'result': <json data...>}
99 'result': <json data...>}
100
100
101
101
102
102
103 API METHODS
103 API METHODS
104 +++++++++++
104 +++++++++++
105
105
106
106
107 pull
107 pull
108 ----
108 ----
109
109
110 Pulls given repo from remote location. Can be used to automatically keep
110 Pulls given repo from remote location. Can be used to automatically keep
111 remote repos up to date. This command can be executed only using api_key
111 remote repos up to date. This command can be executed only using api_key
112 belonging to user with admin rights
112 belonging to user with admin rights
113
113
114 INPUT::
114 INPUT::
115
115
116 id : <id_for_response>
116 id : <id_for_response>
117 api_key : "<api_key>"
117 api_key : "<api_key>"
118 method : "pull"
118 method : "pull"
119 args : {
119 args : {
120 "repoid" : "<reponame or repo_id>"
120 "repoid" : "<reponame or repo_id>"
121 }
121 }
122
122
123 OUTPUT::
123 OUTPUT::
124
124
125 id : <id_given_in_input>
125 id : <id_given_in_input>
126 result : "Pulled from `<reponame>`"
126 result : "Pulled from `<reponame>`"
127 error : null
127 error : null
128
128
129
129
130 rescan_repos
130 rescan_repos
131 ------------
131 ------------
132
132
133 Dispatch rescan repositories action. If remove_obsolete is set
133 Dispatch rescan repositories action. If remove_obsolete is set
134 RhodeCode will delete repos that are in database but not in the filesystem.
134 RhodeCode will delete repos that are in database but not in the filesystem.
135 This command can be executed only using api_key belonging to user with admin
135 This command can be executed only using api_key belonging to user with admin
136 rights.
136 rights.
137
137
138 INPUT::
138 INPUT::
139
139
140 id : <id_for_response>
140 id : <id_for_response>
141 api_key : "<api_key>"
141 api_key : "<api_key>"
142 method : "rescan_repos"
142 method : "rescan_repos"
143 args : {
143 args : {
144 "remove_obsolete" : "<boolean = Optional(False)>"
144 "remove_obsolete" : "<boolean = Optional(False)>"
145 }
145 }
146
146
147 OUTPUT::
147 OUTPUT::
148
148
149 id : <id_given_in_input>
149 id : <id_given_in_input>
150 result : "{'added': [<list of names of added repos>],
150 result : "{'added': [<list of names of added repos>],
151 'removed': [<list of names of removed repos>]}"
151 'removed': [<list of names of removed repos>]}"
152 error : null
152 error : null
153
153
154
154
155 lock
155 lock
156 ----
156 ----
157
157
158 Set locking state on given repository by given user.
158 Set locking state on given repository by given user.
159 This command can be executed only using api_key belonging to user with admin
159 This command can be executed only using api_key belonging to user with admin
160 rights.
160 rights.
161
161
162 INPUT::
162 INPUT::
163
163
164 id : <id_for_response>
164 id : <id_for_response>
165 api_key : "<api_key>"
165 api_key : "<api_key>"
166 method : "lock"
166 method : "lock"
167 args : {
167 args : {
168 "repoid" : "<reponame or repo_id>"
168 "repoid" : "<reponame or repo_id>"
169 "userid" : "<user_id or username>",
169 "userid" : "<user_id or username>",
170 "locked" : "<bool true|false>"
170 "locked" : "<bool true|false>"
171
171
172 }
172 }
173
173
174 OUTPUT::
174 OUTPUT::
175
175
176 id : <id_given_in_input>
176 id : <id_given_in_input>
177 result : "User `<username>` set lock state for repo `<reponame>` to `true|false`"
177 result : "User `<username>` set lock state for repo `<reponame>` to `true|false`"
178 error : null
178 error : null
179
179
180
180
181 get_user
181 get_user
182 --------
182 --------
183
183
184 Get's an user by username or user_id, Returns empty result if user is not found.
184 Get's an user by username or user_id, Returns empty result if user is not found.
185 This command can be executed only using api_key belonging to user with admin
185 This command can be executed only using api_key belonging to user with admin
186 rights.
186 rights.
187
187
188
188
189 INPUT::
189 INPUT::
190
190
191 id : <id_for_response>
191 id : <id_for_response>
192 api_key : "<api_key>"
192 api_key : "<api_key>"
193 method : "get_user"
193 method : "get_user"
194 args : {
194 args : {
195 "userid" : "<username or user_id>"
195 "userid" : "<username or user_id>"
196 }
196 }
197
197
198 OUTPUT::
198 OUTPUT::
199
199
200 id : <id_given_in_input>
200 id : <id_given_in_input>
201 result: None if user does not exist or
201 result: None if user does not exist or
202 {
202 {
203 "user_id" : "<user_id>",
203 "user_id" : "<user_id>",
204 "username" : "<username>",
204 "username" : "<username>",
205 "firstname": "<firstname>",
205 "firstname": "<firstname>",
206 "lastname" : "<lastname>",
206 "lastname" : "<lastname>",
207 "email" : "<email>",
207 "email" : "<email>",
208 "emails": "<list_of_all_additional_emails>",
208 "emails": "<list_of_all_additional_emails>",
209 "active" : "<bool>",
209 "active" : "<bool>",
210 "admin" :Β  "<bool>",
210 "admin" :Β  "<bool>",
211 "ldap_dn" : "<ldap_dn>",
211 "ldap_dn" : "<ldap_dn>",
212 "last_login": "<last_login>",
212 "last_login": "<last_login>",
213 "permissions": {
213 "permissions": {
214 "global": ["hg.create.repository",
214 "global": ["hg.create.repository",
215 "repository.read",
215 "repository.read",
216 "hg.register.manual_activate"],
216 "hg.register.manual_activate"],
217 "repositories": {"repo1": "repository.none"},
217 "repositories": {"repo1": "repository.none"},
218 "repositories_groups": {"Group1": "group.read"}
218 "repositories_groups": {"Group1": "group.read"}
219 },
219 },
220 }
220 }
221
221
222 error: null
222 error: null
223
223
224
224
225 get_users
225 get_users
226 ---------
226 ---------
227
227
228 Lists all existing users. This command can be executed only using api_key
228 Lists all existing users. This command can be executed only using api_key
229 belonging to user with admin rights.
229 belonging to user with admin rights.
230
230
231
231
232 INPUT::
232 INPUT::
233
233
234 id : <id_for_response>
234 id : <id_for_response>
235 api_key : "<api_key>"
235 api_key : "<api_key>"
236 method : "get_users"
236 method : "get_users"
237 args : { }
237 args : { }
238
238
239 OUTPUT::
239 OUTPUT::
240
240
241 id : <id_given_in_input>
241 id : <id_given_in_input>
242 result: [
242 result: [
243 {
243 {
244 "user_id" : "<user_id>",
244 "user_id" : "<user_id>",
245 "username" : "<username>",
245 "username" : "<username>",
246 "firstname": "<firstname>",
246 "firstname": "<firstname>",
247 "lastname" : "<lastname>",
247 "lastname" : "<lastname>",
248 "email" : "<email>",
248 "email" : "<email>",
249 "emails": "<list_of_all_additional_emails>",
249 "emails": "<list_of_all_additional_emails>",
250 "active" : "<bool>",
250 "active" : "<bool>",
251 "admin" :Β  "<bool>",
251 "admin" :Β  "<bool>",
252 "ldap_dn" : "<ldap_dn>",
252 "ldap_dn" : "<ldap_dn>",
253 "last_login": "<last_login>",
253 "last_login": "<last_login>",
254 },
254 },
255 …
255 …
256 ]
256 ]
257 error: null
257 error: null
258
258
259
259
260 create_user
260 create_user
261 -----------
261 -----------
262
262
263 Creates new user. This command can
263 Creates new user. This command can
264 be executed only using api_key belonging to user with admin rights.
264 be executed only using api_key belonging to user with admin rights.
265
265
266
266
267 INPUT::
267 INPUT::
268
268
269 id : <id_for_response>
269 id : <id_for_response>
270 api_key : "<api_key>"
270 api_key : "<api_key>"
271 method : "create_user"
271 method : "create_user"
272 args : {
272 args : {
273 "username" : "<username>",
273 "username" : "<username>",
274 "email" : "<useremail>",
274 "email" : "<useremail>",
275 "password" : "<password>",
275 "password" : "<password>",
276 "firstname" : "<firstname> = Optional(None)",
276 "firstname" : "<firstname> = Optional(None)",
277 "lastname" : "<lastname> = Optional(None)",
277 "lastname" : "<lastname> = Optional(None)",
278 "active" : "<bool> = Optional(True)",
278 "active" : "<bool> = Optional(True)",
279 "admin" : "<bool> = Optional(False)",
279 "admin" : "<bool> = Optional(False)",
280 "ldap_dn" : "<ldap_dn> = Optional(None)"
280 "ldap_dn" : "<ldap_dn> = Optional(None)"
281 }
281 }
282
282
283 OUTPUT::
283 OUTPUT::
284
284
285 id : <id_given_in_input>
285 id : <id_given_in_input>
286 result: {
286 result: {
287 "msg" : "created new user `<username>`",
287 "msg" : "created new user `<username>`",
288 "user": {
288 "user": {
289 "user_id" : "<user_id>",
289 "user_id" : "<user_id>",
290 "username" : "<username>",
290 "username" : "<username>",
291 "firstname": "<firstname>",
291 "firstname": "<firstname>",
292 "lastname" : "<lastname>",
292 "lastname" : "<lastname>",
293 "email" : "<email>",
293 "email" : "<email>",
294 "emails": "<list_of_all_additional_emails>",
294 "emails": "<list_of_all_additional_emails>",
295 "active" : "<bool>",
295 "active" : "<bool>",
296 "admin" :Β  "<bool>",
296 "admin" :Β  "<bool>",
297 "ldap_dn" : "<ldap_dn>",
297 "ldap_dn" : "<ldap_dn>",
298 "last_login": "<last_login>",
298 "last_login": "<last_login>",
299 },
299 },
300 }
300 }
301 error: null
301 error: null
302
302
303
303
304 update_user
304 update_user
305 -----------
305 -----------
306
306
307 updates given user if such user exists. This command can
307 updates given user if such user exists. This command can
308 be executed only using api_key belonging to user with admin rights.
308 be executed only using api_key belonging to user with admin rights.
309
309
310
310
311 INPUT::
311 INPUT::
312
312
313 id : <id_for_response>
313 id : <id_for_response>
314 api_key : "<api_key>"
314 api_key : "<api_key>"
315 method : "update_user"
315 method : "update_user"
316 args : {
316 args : {
317 "userid" : "<user_id or username>",
317 "userid" : "<user_id or username>",
318 "username" : "<username> = Optional",
318 "username" : "<username> = Optional",
319 "email" : "<useremail> = Optional",
319 "email" : "<useremail> = Optional",
320 "password" : "<password> = Optional",
320 "password" : "<password> = Optional",
321 "firstname" : "<firstname> = Optional",
321 "firstname" : "<firstname> = Optional",
322 "lastname" : "<lastname> = Optional",
322 "lastname" : "<lastname> = Optional",
323 "active" : "<bool> = Optional",
323 "active" : "<bool> = Optional",
324 "admin" : "<bool> = Optional",
324 "admin" : "<bool> = Optional",
325 "ldap_dn" : "<ldap_dn> = Optional"
325 "ldap_dn" : "<ldap_dn> = Optional"
326 }
326 }
327
327
328 OUTPUT::
328 OUTPUT::
329
329
330 id : <id_given_in_input>
330 id : <id_given_in_input>
331 result: {
331 result: {
332 "msg" : "updated user ID:<userid> <username>",
332 "msg" : "updated user ID:<userid> <username>",
333 "user": {
333 "user": {
334 "user_id" : "<user_id>",
334 "user_id" : "<user_id>",
335 "username" : "<username>",
335 "username" : "<username>",
336 "firstname": "<firstname>",
336 "firstname": "<firstname>",
337 "lastname" : "<lastname>",
337 "lastname" : "<lastname>",
338 "email" : "<email>",
338 "email" : "<email>",
339 "emails": "<list_of_all_additional_emails>",
339 "emails": "<list_of_all_additional_emails>",
340 "active" : "<bool>",
340 "active" : "<bool>",
341 "admin" :Β  "<bool>",
341 "admin" :Β  "<bool>",
342 "ldap_dn" : "<ldap_dn>",
342 "ldap_dn" : "<ldap_dn>",
343 "last_login": "<last_login>",
343 "last_login": "<last_login>",
344 },
344 },
345 }
345 }
346 error: null
346 error: null
347
347
348
348
349 delete_user
349 delete_user
350 -----------
350 -----------
351
351
352
352
353 deletes givenuser if such user exists. This command can
353 deletes givenuser if such user exists. This command can
354 be executed only using api_key belonging to user with admin rights.
354 be executed only using api_key belonging to user with admin rights.
355
355
356
356
357 INPUT::
357 INPUT::
358
358
359 id : <id_for_response>
359 id : <id_for_response>
360 api_key : "<api_key>"
360 api_key : "<api_key>"
361 method : "delete_user"
361 method : "delete_user"
362 args : {
362 args : {
363 "userid" : "<user_id or username>",
363 "userid" : "<user_id or username>",
364 }
364 }
365
365
366 OUTPUT::
366 OUTPUT::
367
367
368 id : <id_given_in_input>
368 id : <id_given_in_input>
369 result: {
369 result: {
370 "msg" : "deleted user ID:<userid> <username>",
370 "msg" : "deleted user ID:<userid> <username>",
371 "user": null
371 "user": null
372 }
372 }
373 error: null
373 error: null
374
374
375
375
376 get_users_group
376 get_users_group
377 ---------------
377 ---------------
378
378
379 Gets an existing users group. This command can be executed only using api_key
379 Gets an existing users group. This command can be executed only using api_key
380 belonging to user with admin rights.
380 belonging to user with admin rights.
381
381
382
382
383 INPUT::
383 INPUT::
384
384
385 id : <id_for_response>
385 id : <id_for_response>
386 api_key : "<api_key>"
386 api_key : "<api_key>"
387 method : "get_users_group"
387 method : "get_users_group"
388 args : {
388 args : {
389 "usersgroupid" : "<users group id or name>"
389 "usersgroupid" : "<users group id or name>"
390 }
390 }
391
391
392 OUTPUT::
392 OUTPUT::
393
393
394 id : <id_given_in_input>
394 id : <id_given_in_input>
395 result : None if group not exist
395 result : None if group not exist
396 {
396 {
397 "users_group_id" : "<id>",
397 "users_group_id" : "<id>",
398 "group_name" : "<groupname>",
398 "group_name" : "<groupname>",
399 "active": "<bool>",
399 "active": "<bool>",
400 "members" : [
400 "members" : [
401 {
401 {
402 "user_id" : "<user_id>",
402 "user_id" : "<user_id>",
403 "username" : "<username>",
403 "username" : "<username>",
404 "firstname": "<firstname>",
404 "firstname": "<firstname>",
405 "lastname" : "<lastname>",
405 "lastname" : "<lastname>",
406 "email" : "<email>",
406 "email" : "<email>",
407 "emails": "<list_of_all_additional_emails>",
407 "emails": "<list_of_all_additional_emails>",
408 "active" : "<bool>",
408 "active" : "<bool>",
409 "admin" :Β  "<bool>",
409 "admin" :Β  "<bool>",
410 "ldap_dn" : "<ldap_dn>",
410 "ldap_dn" : "<ldap_dn>",
411 "last_login": "<last_login>",
411 "last_login": "<last_login>",
412 },
412 },
413 …
413 …
414 ]
414 ]
415 }
415 }
416 error : null
416 error : null
417
417
418
418
419 get_users_groups
419 get_users_groups
420 ----------------
420 ----------------
421
421
422 Lists all existing users groups. This command can be executed only using
422 Lists all existing users groups. This command can be executed only using
423 api_key belonging to user with admin rights.
423 api_key belonging to user with admin rights.
424
424
425
425
426 INPUT::
426 INPUT::
427
427
428 id : <id_for_response>
428 id : <id_for_response>
429 api_key : "<api_key>"
429 api_key : "<api_key>"
430 method : "get_users_groups"
430 method : "get_users_groups"
431 args : { }
431 args : { }
432
432
433 OUTPUT::
433 OUTPUT::
434
434
435 id : <id_given_in_input>
435 id : <id_given_in_input>
436 result : [
436 result : [
437 {
437 {
438 "users_group_id" : "<id>",
438 "users_group_id" : "<id>",
439 "group_name" : "<groupname>",
439 "group_name" : "<groupname>",
440 "active": "<bool>",
440 "active": "<bool>",
441 "members" : [
442 {
443 "user_id" : "<user_id>",
444 "username" : "<username>",
445 "firstname": "<firstname>",
446 "lastname" : "<lastname>",
447 "email" : "<email>",
448 "emails": "<list_of_all_additional_emails>",
449 "active" : "<bool>",
450 "admin" :Β  "<bool>",
451 "ldap_dn" : "<ldap_dn>",
452 "last_login": "<last_login>",
453 },
454 …
455 ]
456 },
441 },
457 …
442 …
458 ]
443 ]
459 error : null
444 error : null
460
445
461
446
462 create_users_group
447 create_users_group
463 ------------------
448 ------------------
464
449
465 Creates new users group. This command can be executed only using api_key
450 Creates new users group. This command can be executed only using api_key
466 belonging to user with admin rights
451 belonging to user with admin rights
467
452
468
453
469 INPUT::
454 INPUT::
470
455
471 id : <id_for_response>
456 id : <id_for_response>
472 api_key : "<api_key>"
457 api_key : "<api_key>"
473 method : "create_users_group"
458 method : "create_users_group"
474 args: {
459 args: {
475 "group_name": "<groupname>",
460 "group_name": "<groupname>",
476 "active":"<bool> = Optional(True)"
461 "active":"<bool> = Optional(True)"
477 }
462 }
478
463
479 OUTPUT::
464 OUTPUT::
480
465
481 id : <id_given_in_input>
466 id : <id_given_in_input>
482 result: {
467 result: {
483 "msg": "created new users group `<groupname>`",
468 "msg": "created new users group `<groupname>`",
484 "users_group": {
469 "users_group": {
485 "users_group_id" : "<id>",
470 "users_group_id" : "<id>",
486 "group_name" : "<groupname>",
471 "group_name" : "<groupname>",
487 "active": "<bool>",
472 "active": "<bool>",
488 "members" : [
489 {
490 "user_id" : "<user_id>",
491 "username" : "<username>",
492 "firstname": "<firstname>",
493 "lastname" : "<lastname>",
494 "email" : "<email>",
495 "emails": "<list_of_all_additional_emails>",
496 "active" : "<bool>",
497 "admin" :Β  "<bool>",
498 "ldap_dn" : "<ldap_dn>",
499 "last_login": "<last_login>",
500 },
501 …
502 ]
503 },
473 },
504 }
474 }
505 error: null
475 error: null
506
476
507
477
508 add_user_to_users_group
478 add_user_to_users_group
509 -----------------------
479 -----------------------
510
480
511 Adds a user to a users group. If user exists in that group success will be
481 Adds a user to a users group. If user exists in that group success will be
512 `false`. This command can be executed only using api_key
482 `false`. This command can be executed only using api_key
513 belonging to user with admin rights
483 belonging to user with admin rights
514
484
515
485
516 INPUT::
486 INPUT::
517
487
518 id : <id_for_response>
488 id : <id_for_response>
519 api_key : "<api_key>"
489 api_key : "<api_key>"
520 method : "add_user_users_group"
490 method : "add_user_users_group"
521 args: {
491 args: {
522 "usersgroupid" : "<users group id or name>",
492 "usersgroupid" : "<users group id or name>",
523 "userid" : "<user_id or username>",
493 "userid" : "<user_id or username>",
524 }
494 }
525
495
526 OUTPUT::
496 OUTPUT::
527
497
528 id : <id_given_in_input>
498 id : <id_given_in_input>
529 result: {
499 result: {
530 "success": True|False # depends on if member is in group
500 "success": True|False # depends on if member is in group
531 "msg": "added member `<username>` to users group `<groupname>` |
501 "msg": "added member `<username>` to users group `<groupname>` |
532 User is already in that group"
502 User is already in that group"
533 }
503 }
534 error: null
504 error: null
535
505
536
506
537 remove_user_from_users_group
507 remove_user_from_users_group
538 ----------------------------
508 ----------------------------
539
509
540 Removes a user from a users group. If user is not in given group success will
510 Removes a user from a users group. If user is not in given group success will
541 be `false`. This command can be executed only
511 be `false`. This command can be executed only
542 using api_key belonging to user with admin rights
512 using api_key belonging to user with admin rights
543
513
544
514
545 INPUT::
515 INPUT::
546
516
547 id : <id_for_response>
517 id : <id_for_response>
548 api_key : "<api_key>"
518 api_key : "<api_key>"
549 method : "remove_user_from_users_group"
519 method : "remove_user_from_users_group"
550 args: {
520 args: {
551 "usersgroupid" : "<users group id or name>",
521 "usersgroupid" : "<users group id or name>",
552 "userid" : "<user_id or username>",
522 "userid" : "<user_id or username>",
553 }
523 }
554
524
555 OUTPUT::
525 OUTPUT::
556
526
557 id : <id_given_in_input>
527 id : <id_given_in_input>
558 result: {
528 result: {
559 "success": True|False, # depends on if member is in group
529 "success": True|False, # depends on if member is in group
560 "msg": "removed member <username> from users group <groupname> |
530 "msg": "removed member <username> from users group <groupname> |
561 User wasn't in group"
531 User wasn't in group"
562 }
532 }
563 error: null
533 error: null
564
534
565
535
566 get_repo
536 get_repo
567 --------
537 --------
568
538
569 Gets an existing repository by it's name or repository_id. Members will return
539 Gets an existing repository by it's name or repository_id. Members will return
570 either users_group or user associated to that repository. This command can
540 either users_group or user associated to that repository. This command can
571 be executed only using api_key belonging to user with admin rights.
541 be executed only using api_key belonging to user with admin rights.
572
542
573
543
574 INPUT::
544 INPUT::
575
545
576 id : <id_for_response>
546 id : <id_for_response>
577 api_key : "<api_key>"
547 api_key : "<api_key>"
578 method : "get_repo"
548 method : "get_repo"
579 args: {
549 args: {
580 "repoid" : "<reponame or repo_id>"
550 "repoid" : "<reponame or repo_id>"
581 }
551 }
582
552
583 OUTPUT::
553 OUTPUT::
584
554
585 id : <id_given_in_input>
555 id : <id_given_in_input>
586 result: None if repository does not exist or
556 result: None if repository does not exist or
587 {
557 {
588 "repo_id" : "<repo_id>",
558 "repo_id" : "<repo_id>",
589 "repo_name" : "<reponame>"
559 "repo_name" : "<reponame>"
590 "repo_type" : "<repo_type>",
560 "repo_type" : "<repo_type>",
591 "clone_uri" : "<clone_uri>",
561 "clone_uri" : "<clone_uri>",
592 "private": : "<bool>",
562 "private": : "<bool>",
593 "created_on" : "<datetimecreated>",
563 "created_on" : "<datetimecreated>",
594 "description" : "<description>",
564 "description" : "<description>",
595 "landing_rev": "<landing_rev>",
565 "landing_rev": "<landing_rev>",
596 "owner": "<repo_owner>",
566 "owner": "<repo_owner>",
597 "fork_of": "<name_of_fork_parent>",
567 "fork_of": "<name_of_fork_parent>",
598 "members" : [
568 "members" : [
599 {
569 {
600 "type": "user",
570 "type": "user",
601 "user_id" : "<user_id>",
571 "user_id" : "<user_id>",
602 "username" : "<username>",
572 "username" : "<username>",
603 "firstname": "<firstname>",
573 "firstname": "<firstname>",
604 "lastname" : "<lastname>",
574 "lastname" : "<lastname>",
605 "email" : "<email>",
575 "email" : "<email>",
606 "emails": "<list_of_all_additional_emails>",
576 "emails": "<list_of_all_additional_emails>",
607 "active" : "<bool>",
577 "active" : "<bool>",
608 "admin" :Β  "<bool>",
578 "admin" :Β  "<bool>",
609 "ldap_dn" : "<ldap_dn>",
579 "ldap_dn" : "<ldap_dn>",
610 "last_login": "<last_login>",
580 "last_login": "<last_login>",
611 "permission" : "repository.(read|write|admin)"
581 "permission" : "repository.(read|write|admin)"
612 },
582 },
613 …
583 …
614 {
584 {
615 "type": "users_group",
585 "type": "users_group",
616 "id" : "<usersgroupid>",
586 "id" : "<usersgroupid>",
617 "name" : "<usersgroupname>",
587 "name" : "<usersgroupname>",
618 "active": "<bool>",
588 "active": "<bool>",
619 "permission" : "repository.(read|write|admin)"
589 "permission" : "repository.(read|write|admin)"
620 },
590 },
621 …
591 …
622 ]
592 ]
623 }
593 }
624 error: null
594 error: null
625
595
626
596
627 get_repos
597 get_repos
628 ---------
598 ---------
629
599
630 Lists all existing repositories. This command can be executed only using api_key
600 Lists all existing repositories. This command can be executed only using api_key
631 belonging to user with admin rights
601 belonging to user with admin rights
632
602
633
603
634 INPUT::
604 INPUT::
635
605
636 id : <id_for_response>
606 id : <id_for_response>
637 api_key : "<api_key>"
607 api_key : "<api_key>"
638 method : "get_repos"
608 method : "get_repos"
639 args: { }
609 args: { }
640
610
641 OUTPUT::
611 OUTPUT::
642
612
643 id : <id_given_in_input>
613 id : <id_given_in_input>
644 result: [
614 result: [
645 {
615 {
646 "repo_id" : "<repo_id>",
616 "repo_id" : "<repo_id>",
647 "repo_name" : "<reponame>"
617 "repo_name" : "<reponame>"
648 "repo_type" : "<repo_type>",
618 "repo_type" : "<repo_type>",
649 "clone_uri" : "<clone_uri>",
619 "clone_uri" : "<clone_uri>",
650 "private": : "<bool>",
620 "private": : "<bool>",
651 "created_on" : "<datetimecreated>",
621 "created_on" : "<datetimecreated>",
652 "description" : "<description>",
622 "description" : "<description>",
653 "landing_rev": "<landing_rev>",
623 "landing_rev": "<landing_rev>",
654 "owner": "<repo_owner>",
624 "owner": "<repo_owner>",
655 "fork_of": "<name_of_fork_parent>",
625 "fork_of": "<name_of_fork_parent>",
656 },
626 },
657 …
627 …
658 ]
628 ]
659 error: null
629 error: null
660
630
661
631
662 get_repo_nodes
632 get_repo_nodes
663 --------------
633 --------------
664
634
665 returns a list of nodes and it's children in a flat list for a given path
635 returns a list of nodes and it's children in a flat list for a given path
666 at given revision. It's possible to specify ret_type to show only `files` or
636 at given revision. It's possible to specify ret_type to show only `files` or
667 `dirs`. This command can be executed only using api_key belonging to user
637 `dirs`. This command can be executed only using api_key belonging to user
668 with admin rights
638 with admin rights
669
639
670
640
671 INPUT::
641 INPUT::
672
642
673 id : <id_for_response>
643 id : <id_for_response>
674 api_key : "<api_key>"
644 api_key : "<api_key>"
675 method : "get_repo_nodes"
645 method : "get_repo_nodes"
676 args: {
646 args: {
677 "repoid" : "<reponame or repo_id>"
647 "repoid" : "<reponame or repo_id>"
678 "revision" : "<revision>",
648 "revision" : "<revision>",
679 "root_path" : "<root_path>",
649 "root_path" : "<root_path>",
680 "ret_type" : "<ret_type> = Optional('all')"
650 "ret_type" : "<ret_type> = Optional('all')"
681 }
651 }
682
652
683 OUTPUT::
653 OUTPUT::
684
654
685 id : <id_given_in_input>
655 id : <id_given_in_input>
686 result: [
656 result: [
687 {
657 {
688 "name" : "<name>"
658 "name" : "<name>"
689 "type" : "<type>",
659 "type" : "<type>",
690 },
660 },
691 …
661 …
692 ]
662 ]
693 error: null
663 error: null
694
664
695
665
696 create_repo
666 create_repo
697 -----------
667 -----------
698
668
699 Creates a repository. This command can be executed only using api_key
669 Creates a repository. This command can be executed only using api_key
700 belonging to user with admin rights.
670 belonging to user with admin rights.
701 If repository name contains "/", all needed repository groups will be created.
671 If repository name contains "/", all needed repository groups will be created.
702 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
672 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
703 and create "baz" repository with "bar" as group.
673 and create "baz" repository with "bar" as group.
704
674
705
675
706 INPUT::
676 INPUT::
707
677
708 id : <id_for_response>
678 id : <id_for_response>
709 api_key : "<api_key>"
679 api_key : "<api_key>"
710 method : "create_repo"
680 method : "create_repo"
711 args: {
681 args: {
712 "repo_name" : "<reponame>",
682 "repo_name" : "<reponame>",
713 "owner" : "<onwer_name_or_id>",
683 "owner" : "<onwer_name_or_id>",
714 "repo_type" : "<repo_type>",
684 "repo_type" : "<repo_type>",
715 "description" : "<description> = Optional('')",
685 "description" : "<description> = Optional('')",
716 "private" : "<bool> = Optional(False)",
686 "private" : "<bool> = Optional(False)",
717 "clone_uri" : "<clone_uri> = Optional(None)",
687 "clone_uri" : "<clone_uri> = Optional(None)",
718 "landing_rev" : "<landing_rev> = Optional('tip')",
688 "landing_rev" : "<landing_rev> = Optional('tip')",
719 }
689 }
720
690
721 OUTPUT::
691 OUTPUT::
722
692
723 id : <id_given_in_input>
693 id : <id_given_in_input>
724 result: {
694 result: {
725 "msg": "Created new repository `<reponame>`",
695 "msg": "Created new repository `<reponame>`",
726 "repo": {
696 "repo": {
727 "repo_id" : "<repo_id>",
697 "repo_id" : "<repo_id>",
728 "repo_name" : "<reponame>"
698 "repo_name" : "<reponame>"
729 "repo_type" : "<repo_type>",
699 "repo_type" : "<repo_type>",
730 "clone_uri" : "<clone_uri>",
700 "clone_uri" : "<clone_uri>",
731 "private": : "<bool>",
701 "private": : "<bool>",
732 "created_on" : "<datetimecreated>",
702 "created_on" : "<datetimecreated>",
733 "description" : "<description>",
703 "description" : "<description>",
734 "landing_rev": "<landing_rev>",
704 "landing_rev": "<landing_rev>",
735 "owner": "<repo_owner>",
705 "owner": "<repo_owner>",
736 "fork_of": "<name_of_fork_parent>",
706 "fork_of": "<name_of_fork_parent>",
737 },
707 },
738 }
708 }
739 error: null
709 error: null
740
710
741
711
742 delete_repo
712 delete_repo
743 -----------
713 -----------
744
714
745 Deletes a repository. This command can be executed only using api_key
715 Deletes a repository. This command can be executed only using api_key
746 belonging to user with admin rights.
716 belonging to user with admin rights.
747
717
748
718
749 INPUT::
719 INPUT::
750
720
751 id : <id_for_response>
721 id : <id_for_response>
752 api_key : "<api_key>"
722 api_key : "<api_key>"
753 method : "delete_repo"
723 method : "delete_repo"
754 args: {
724 args: {
755 "repoid" : "<reponame or repo_id>"
725 "repoid" : "<reponame or repo_id>"
756 }
726 }
757
727
758 OUTPUT::
728 OUTPUT::
759
729
760 id : <id_given_in_input>
730 id : <id_given_in_input>
761 result: {
731 result: {
762 "msg": "Deleted repository `<reponame>`",
732 "msg": "Deleted repository `<reponame>`",
763 "success": true
733 "success": true
764 }
734 }
765 error: null
735 error: null
766
736
767
737
768 grant_user_permission
738 grant_user_permission
769 ---------------------
739 ---------------------
770
740
771 Grant permission for user on given repository, or update existing one
741 Grant permission for user on given repository, or update existing one
772 if found. This command can be executed only using api_key belonging to user
742 if found. This command can be executed only using api_key belonging to user
773 with admin rights.
743 with admin rights.
774
744
775
745
776 INPUT::
746 INPUT::
777
747
778 id : <id_for_response>
748 id : <id_for_response>
779 api_key : "<api_key>"
749 api_key : "<api_key>"
780 method : "grant_user_permission"
750 method : "grant_user_permission"
781 args: {
751 args: {
782 "repoid" : "<reponame or repo_id>"
752 "repoid" : "<reponame or repo_id>"
783 "userid" : "<username or user_id>"
753 "userid" : "<username or user_id>"
784 "perm" : "(repository.(none|read|write|admin))",
754 "perm" : "(repository.(none|read|write|admin))",
785 }
755 }
786
756
787 OUTPUT::
757 OUTPUT::
788
758
789 id : <id_given_in_input>
759 id : <id_given_in_input>
790 result: {
760 result: {
791 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
761 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
792 "success": true
762 "success": true
793 }
763 }
794 error: null
764 error: null
795
765
796
766
797 revoke_user_permission
767 revoke_user_permission
798 ----------------------
768 ----------------------
799
769
800 Revoke permission for user on given repository. This command can be executed
770 Revoke permission for user on given repository. This command can be executed
801 only using api_key belonging to user with admin rights.
771 only using api_key belonging to user with admin rights.
802
772
803
773
804 INPUT::
774 INPUT::
805
775
806 id : <id_for_response>
776 id : <id_for_response>
807 api_key : "<api_key>"
777 api_key : "<api_key>"
808 method : "revoke_user_permission"
778 method : "revoke_user_permission"
809 args: {
779 args: {
810 "repoid" : "<reponame or repo_id>"
780 "repoid" : "<reponame or repo_id>"
811 "userid" : "<username or user_id>"
781 "userid" : "<username or user_id>"
812 }
782 }
813
783
814 OUTPUT::
784 OUTPUT::
815
785
816 id : <id_given_in_input>
786 id : <id_given_in_input>
817 result: {
787 result: {
818 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
788 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
819 "success": true
789 "success": true
820 }
790 }
821 error: null
791 error: null
822
792
823
793
824 grant_users_group_permission
794 grant_users_group_permission
825 ----------------------------
795 ----------------------------
826
796
827 Grant permission for users group on given repository, or update
797 Grant permission for users group on given repository, or update
828 existing one if found. This command can be executed only using
798 existing one if found. This command can be executed only using
829 api_key belonging to user with admin rights.
799 api_key belonging to user with admin rights.
830
800
831
801
832 INPUT::
802 INPUT::
833
803
834 id : <id_for_response>
804 id : <id_for_response>
835 api_key : "<api_key>"
805 api_key : "<api_key>"
836 method : "grant_users_group_permission"
806 method : "grant_users_group_permission"
837 args: {
807 args: {
838 "repoid" : "<reponame or repo_id>"
808 "repoid" : "<reponame or repo_id>"
839 "usersgroupid" : "<users group id or name>"
809 "usersgroupid" : "<users group id or name>"
840 "perm" : "(repository.(none|read|write|admin))",
810 "perm" : "(repository.(none|read|write|admin))",
841 }
811 }
842
812
843 OUTPUT::
813 OUTPUT::
844
814
845 id : <id_given_in_input>
815 id : <id_given_in_input>
846 result: {
816 result: {
847 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
817 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
848 "success": true
818 "success": true
849 }
819 }
850 error: null
820 error: null
851
821
852
822
853 revoke_users_group_permission
823 revoke_users_group_permission
854 -----------------------------
824 -----------------------------
855
825
856 Revoke permission for users group on given repository.This command can be
826 Revoke permission for users group on given repository.This command can be
857 executed only using api_key belonging to user with admin rights.
827 executed only using api_key belonging to user with admin rights.
858
828
859 INPUT::
829 INPUT::
860
830
861 id : <id_for_response>
831 id : <id_for_response>
862 api_key : "<api_key>"
832 api_key : "<api_key>"
863 method : "revoke_users_group_permission"
833 method : "revoke_users_group_permission"
864 args: {
834 args: {
865 "repoid" : "<reponame or repo_id>"
835 "repoid" : "<reponame or repo_id>"
866 "usersgroupid" : "<users group id or name>"
836 "usersgroupid" : "<users group id or name>"
867 }
837 }
868
838
869 OUTPUT::
839 OUTPUT::
870
840
871 id : <id_given_in_input>
841 id : <id_given_in_input>
872 result: {
842 result: {
873 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
843 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
874 "success": true
844 "success": true
875 }
845 }
876 error: null No newline at end of file
846 error: null
@@ -1,817 +1,820 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7 1.4.4 (**2012-10-08**)
7 1.4.4 (**2012-10-08**)
8 ----------------------
8 ----------------------
9
9
10 news
10 news
11 ++++
11 ++++
12
12
13 - obfuscate db password in logs for engine connection string
13 - obfuscate db password in logs for engine connection string
14 - #574 Show pull request status also in shortlog (if any)
14 - #574 Show pull request status also in shortlog (if any)
15 - remember selected tab in my account page
15 - remember selected tab in my account page
16 - Bumped mercurial version to 2.3.2
16 - Bumped mercurial version to 2.3.2
17 - #595 rcextension hook for repository delete
17
18
18 fixes
19 fixes
19 +++++
20 +++++
20
21
21 - Add git version detection to warn users that Git used in system is to
22 - Add git version detection to warn users that Git used in system is to
22 old. Ref #588 - also show git version in system details in settings page
23 old. Ref #588 - also show git version in system details in settings page
23 - fixed files quick filter links
24 - fixed files quick filter links
24 - #590 Add GET flag that controls the way the diff are generated, for pull
25 - #590 Add GET flag that controls the way the diff are generated, for pull
25 requests we want to use non-bundle based diffs, That are far better for
26 requests we want to use non-bundle based diffs, That are far better for
26 doing code reviews. The /compare url still uses bundle compare for full
27 doing code reviews. The /compare url still uses bundle compare for full
27 comparison including the incoming changesets
28 comparison including the incoming changesets
28 - Fixed #585, checks for status of revision where to strict, and made
29 - Fixed #585, checks for status of revision where to strict, and made
29 opening pull request with those revision impossible due to previously set
30 opening pull request with those revision impossible due to previously set
30 status. Checks now are made also for the repository.
31 status. Checks now are made also for the repository.
31 - fixes #591 git backend was causing encoding errors when handling binary
32 - fixes #591 git backend was causing encoding errors when handling binary
32 files - added a test case for VCS lib tests
33 files - added a test case for VCS lib tests
34 - fixed #597 commits in future get negative age.
35 - fixed #598 API docs methods had wrong members parameter as returned data
33
36
34 1.4.3 (**2012-09-28**)
37 1.4.3 (**2012-09-28**)
35 ----------------------
38 ----------------------
36
39
37 news
40 news
38 ++++
41 ++++
39
42
40 - #558 Added config file to hooks extra data
43 - #558 Added config file to hooks extra data
41 - bumped mercurial version to 2.3.1
44 - bumped mercurial version to 2.3.1
42 - #518 added possibility of specifying multiple patterns for issues
45 - #518 added possibility of specifying multiple patterns for issues
43 - update codemirror to latest version
46 - update codemirror to latest version
44
47
45 fixes
48 fixes
46 +++++
49 +++++
47
50
48 - fixed #570 explicit users group permissions can overwrite owner permissions
51 - fixed #570 explicit users group permissions can overwrite owner permissions
49 - fixed #578 set proper PATH with current Python for Git
52 - fixed #578 set proper PATH with current Python for Git
50 hooks to execute within same Python as RhodeCode
53 hooks to execute within same Python as RhodeCode
51 - fixed issue with Git bare repos that ends with .git in name
54 - fixed issue with Git bare repos that ends with .git in name
52
55
53 1.4.2 (**2012-09-12**)
56 1.4.2 (**2012-09-12**)
54 ----------------------
57 ----------------------
55
58
56 news
59 news
57 ++++
60 ++++
58
61
59 - added option to menu to quick lock/unlock repository for users that have
62 - added option to menu to quick lock/unlock repository for users that have
60 write access to
63 write access to
61 - Implemented permissions for writing to repo
64 - Implemented permissions for writing to repo
62 groups. Now only write access to group allows to create a repostiory
65 groups. Now only write access to group allows to create a repostiory
63 within that group
66 within that group
64 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
67 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
65 - updated translation for zh_CN
68 - updated translation for zh_CN
66
69
67 fixes
70 fixes
68 +++++
71 +++++
69
72
70 - fixed visual permissions check on repos groups inside groups
73 - fixed visual permissions check on repos groups inside groups
71 - fixed issues with non-ascii search terms in search, and indexers
74 - fixed issues with non-ascii search terms in search, and indexers
72 - fixed parsing of page number in GET parameters
75 - fixed parsing of page number in GET parameters
73 - fixed issues with generating pull-request overview for repos with
76 - fixed issues with generating pull-request overview for repos with
74 bookmarks and tags, also preview doesn't loose chosen revision from
77 bookmarks and tags, also preview doesn't loose chosen revision from
75 select dropdown
78 select dropdown
76
79
77 1.4.1 (**2012-09-07**)
80 1.4.1 (**2012-09-07**)
78 ----------------------
81 ----------------------
79
82
80 news
83 news
81 ++++
84 ++++
82
85
83 - always put a comment about code-review status change even if user send
86 - always put a comment about code-review status change even if user send
84 empty data
87 empty data
85 - modified_on column saves repository update and it's going to be used
88 - modified_on column saves repository update and it's going to be used
86 later for light version of main page ref #500
89 later for light version of main page ref #500
87 - pull request notifications send much nicer emails with details about pull
90 - pull request notifications send much nicer emails with details about pull
88 request
91 request
89 - #551 show breadcrumbs in summary view for repositories inside a group
92 - #551 show breadcrumbs in summary view for repositories inside a group
90
93
91 fixes
94 fixes
92 +++++
95 +++++
93
96
94 - fixed migrations of permissions that can lead to inconsistency.
97 - fixed migrations of permissions that can lead to inconsistency.
95 Some users sent feedback that after upgrading from older versions issues
98 Some users sent feedback that after upgrading from older versions issues
96 with updating default permissions occurred. RhodeCode detects that now and
99 with updating default permissions occurred. RhodeCode detects that now and
97 resets default user permission to initial state if there is a need for that.
100 resets default user permission to initial state if there is a need for that.
98 Also forces users to set the default value for new forking permission.
101 Also forces users to set the default value for new forking permission.
99 - #535 improved apache wsgi example configuration in docs
102 - #535 improved apache wsgi example configuration in docs
100 - fixes #550 mercurial repositories comparision failed when origin repo had
103 - fixes #550 mercurial repositories comparision failed when origin repo had
101 additional not-common changesets
104 additional not-common changesets
102 - fixed status of code-review in preview windows of pull request
105 - fixed status of code-review in preview windows of pull request
103 - git forks were not initialized at bare repos
106 - git forks were not initialized at bare repos
104 - fixes #555 fixes issues with comparing non-related repositories
107 - fixes #555 fixes issues with comparing non-related repositories
105 - fixes #557 follower counter always counts up
108 - fixes #557 follower counter always counts up
106 - fixed issue #560 require push ssl checkbox wasn't shown when option was
109 - fixed issue #560 require push ssl checkbox wasn't shown when option was
107 enabled
110 enabled
108 - fixed #559
111 - fixed #559
109 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
112 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
110 if it was a request to url by repository ID
113 if it was a request to url by repository ID
111
114
112 1.4.0 (**2012-09-03**)
115 1.4.0 (**2012-09-03**)
113 ----------------------
116 ----------------------
114
117
115 news
118 news
116 ++++
119 ++++
117
120
118 - new codereview system
121 - new codereview system
119 - email map, allowing users to have multiple email addresses mapped into
122 - email map, allowing users to have multiple email addresses mapped into
120 their accounts
123 their accounts
121 - improved git-hook system. Now all actions for git are logged into journal
124 - improved git-hook system. Now all actions for git are logged into journal
122 including pushed revisions, user and IP address
125 including pushed revisions, user and IP address
123 - changed setup-app into setup-rhodecode and added default options to it.
126 - changed setup-app into setup-rhodecode and added default options to it.
124 - new git repos are created as bare now by default
127 - new git repos are created as bare now by default
125 - #464 added links to groups in permission box
128 - #464 added links to groups in permission box
126 - #465 mentions autocomplete inside comments boxes
129 - #465 mentions autocomplete inside comments boxes
127 - #469 added --update-only option to whoosh to re-index only given list
130 - #469 added --update-only option to whoosh to re-index only given list
128 of repos in index
131 of repos in index
129 - rhodecode-api CLI client
132 - rhodecode-api CLI client
130 - new git http protocol replaced buggy dulwich implementation.
133 - new git http protocol replaced buggy dulwich implementation.
131 Now based on pygrack & gitweb
134 Now based on pygrack & gitweb
132 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
135 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
133 reformated based on user suggestions. Additional rss/atom feeds for user
136 reformated based on user suggestions. Additional rss/atom feeds for user
134 journal
137 journal
135 - various i18n improvements
138 - various i18n improvements
136 - #478 permissions overview for admin in user edit view
139 - #478 permissions overview for admin in user edit view
137 - File view now displays small gravatars off all authors of given file
140 - File view now displays small gravatars off all authors of given file
138 - Implemented landing revisions. Each repository will get landing_rev attribute
141 - Implemented landing revisions. Each repository will get landing_rev attribute
139 that defines 'default' revision/branch for generating readme files
142 that defines 'default' revision/branch for generating readme files
140 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
143 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
141 earliest possible call.
144 earliest possible call.
142 - Import remote svn repositories to mercurial using hgsubversion.
145 - Import remote svn repositories to mercurial using hgsubversion.
143 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
146 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
144 - RhodeCode can use alternative server for generating avatar icons
147 - RhodeCode can use alternative server for generating avatar icons
145 - implemented repositories locking. Pull locks, push unlocks. Also can be done
148 - implemented repositories locking. Pull locks, push unlocks. Also can be done
146 via API calls
149 via API calls
147 - #538 form for permissions can handle multiple users at once
150 - #538 form for permissions can handle multiple users at once
148
151
149 fixes
152 fixes
150 +++++
153 +++++
151
154
152 - improved translations
155 - improved translations
153 - fixes issue #455 Creating an archive generates an exception on Windows
156 - fixes issue #455 Creating an archive generates an exception on Windows
154 - fixes #448 Download ZIP archive keeps file in /tmp open and results
157 - fixes #448 Download ZIP archive keeps file in /tmp open and results
155 in out of disk space
158 in out of disk space
156 - fixes issue #454 Search results under Windows include proceeding
159 - fixes issue #454 Search results under Windows include proceeding
157 backslash
160 backslash
158 - fixed issue #450. Rhodecode no longer will crash when bad revision is
161 - fixed issue #450. Rhodecode no longer will crash when bad revision is
159 present in journal data.
162 present in journal data.
160 - fix for issue #417, git execution was broken on windows for certain
163 - fix for issue #417, git execution was broken on windows for certain
161 commands.
164 commands.
162 - fixed #413. Don't disable .git directory for bare repos on deleting
165 - fixed #413. Don't disable .git directory for bare repos on deleting
163 - fixed issue #459. Changed the way of obtaining logger in reindex task.
166 - fixed issue #459. Changed the way of obtaining logger in reindex task.
164 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
167 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
165 reindexing modified files
168 reindexing modified files
166 - fixed #481 rhodecode emails are sent without Date header
169 - fixed #481 rhodecode emails are sent without Date header
167 - fixed #458 wrong count when no repos are present
170 - fixed #458 wrong count when no repos are present
168 - fixed issue #492 missing `\ No newline at end of file` test at the end of
171 - fixed issue #492 missing `\ No newline at end of file` test at the end of
169 new chunk in html diff
172 new chunk in html diff
170 - full text search now works also for commit messages
173 - full text search now works also for commit messages
171
174
172 1.3.6 (**2012-05-17**)
175 1.3.6 (**2012-05-17**)
173 ----------------------
176 ----------------------
174
177
175 news
178 news
176 ++++
179 ++++
177
180
178 - chinese traditional translation
181 - chinese traditional translation
179 - changed setup-app into setup-rhodecode and added arguments for auto-setup
182 - changed setup-app into setup-rhodecode and added arguments for auto-setup
180 mode that doesn't need user interaction
183 mode that doesn't need user interaction
181
184
182 fixes
185 fixes
183 +++++
186 +++++
184
187
185 - fixed no scm found warning
188 - fixed no scm found warning
186 - fixed __future__ import error on rcextensions
189 - fixed __future__ import error on rcextensions
187 - made simplejson required lib for speedup on JSON encoding
190 - made simplejson required lib for speedup on JSON encoding
188 - fixes #449 bad regex could get more than revisions from parsing history
191 - fixes #449 bad regex could get more than revisions from parsing history
189 - don't clear DB session when CELERY_EAGER is turned ON
192 - don't clear DB session when CELERY_EAGER is turned ON
190
193
191 1.3.5 (**2012-05-10**)
194 1.3.5 (**2012-05-10**)
192 ----------------------
195 ----------------------
193
196
194 news
197 news
195 ++++
198 ++++
196
199
197 - use ext_json for json module
200 - use ext_json for json module
198 - unified annotation view with file source view
201 - unified annotation view with file source view
199 - notification improvements, better inbox + css
202 - notification improvements, better inbox + css
200 - #419 don't strip passwords for login forms, make rhodecode
203 - #419 don't strip passwords for login forms, make rhodecode
201 more compatible with LDAP servers
204 more compatible with LDAP servers
202 - Added HTTP_X_FORWARDED_FOR as another method of extracting
205 - Added HTTP_X_FORWARDED_FOR as another method of extracting
203 IP for pull/push logs. - moved all to base controller
206 IP for pull/push logs. - moved all to base controller
204 - #415: Adding comment to changeset causes reload.
207 - #415: Adding comment to changeset causes reload.
205 Comments are now added via ajax and doesn't reload the page
208 Comments are now added via ajax and doesn't reload the page
206 - #374 LDAP config is discarded when LDAP can't be activated
209 - #374 LDAP config is discarded when LDAP can't be activated
207 - limited push/pull operations are now logged for git in the journal
210 - limited push/pull operations are now logged for git in the journal
208 - bumped mercurial to 2.2.X series
211 - bumped mercurial to 2.2.X series
209 - added support for displaying submodules in file-browser
212 - added support for displaying submodules in file-browser
210 - #421 added bookmarks in changelog view
213 - #421 added bookmarks in changelog view
211
214
212 fixes
215 fixes
213 +++++
216 +++++
214
217
215 - fixed dev-version marker for stable when served from source codes
218 - fixed dev-version marker for stable when served from source codes
216 - fixed missing permission checks on show forks page
219 - fixed missing permission checks on show forks page
217 - #418 cast to unicode fixes in notification objects
220 - #418 cast to unicode fixes in notification objects
218 - #426 fixed mention extracting regex
221 - #426 fixed mention extracting regex
219 - fixed remote-pulling for git remotes remopositories
222 - fixed remote-pulling for git remotes remopositories
220 - fixed #434: Error when accessing files or changesets of a git repository
223 - fixed #434: Error when accessing files or changesets of a git repository
221 with submodules
224 with submodules
222 - fixed issue with empty APIKEYS for users after registration ref. #438
225 - fixed issue with empty APIKEYS for users after registration ref. #438
223 - fixed issue with getting README files from git repositories
226 - fixed issue with getting README files from git repositories
224
227
225 1.3.4 (**2012-03-28**)
228 1.3.4 (**2012-03-28**)
226 ----------------------
229 ----------------------
227
230
228 news
231 news
229 ++++
232 ++++
230
233
231 - Whoosh logging is now controlled by the .ini files logging setup
234 - Whoosh logging is now controlled by the .ini files logging setup
232 - added clone-url into edit form on /settings page
235 - added clone-url into edit form on /settings page
233 - added help text into repo add/edit forms
236 - added help text into repo add/edit forms
234 - created rcextensions module with additional mappings (ref #322) and
237 - created rcextensions module with additional mappings (ref #322) and
235 post push/pull/create repo hooks callbacks
238 post push/pull/create repo hooks callbacks
236 - implemented #377 Users view for his own permissions on account page
239 - implemented #377 Users view for his own permissions on account page
237 - #399 added inheritance of permissions for users group on repos groups
240 - #399 added inheritance of permissions for users group on repos groups
238 - #401 repository group is automatically pre-selected when adding repos
241 - #401 repository group is automatically pre-selected when adding repos
239 inside a repository group
242 inside a repository group
240 - added alternative HTTP 403 response when client failed to authenticate. Helps
243 - added alternative HTTP 403 response when client failed to authenticate. Helps
241 solving issues with Mercurial and LDAP
244 solving issues with Mercurial and LDAP
242 - #402 removed group prefix from repository name when listing repositories
245 - #402 removed group prefix from repository name when listing repositories
243 inside a group
246 inside a group
244 - added gravatars into permission view and permissions autocomplete
247 - added gravatars into permission view and permissions autocomplete
245 - #347 when running multiple RhodeCode instances, properly invalidates cache
248 - #347 when running multiple RhodeCode instances, properly invalidates cache
246 for all registered servers
249 for all registered servers
247
250
248 fixes
251 fixes
249 +++++
252 +++++
250
253
251 - fixed #390 cache invalidation problems on repos inside group
254 - fixed #390 cache invalidation problems on repos inside group
252 - fixed #385 clone by ID url was loosing proxy prefix in URL
255 - fixed #385 clone by ID url was loosing proxy prefix in URL
253 - fixed some unicode problems with waitress
256 - fixed some unicode problems with waitress
254 - fixed issue with escaping < and > in changeset commits
257 - fixed issue with escaping < and > in changeset commits
255 - fixed error occurring during recursive group creation in API
258 - fixed error occurring during recursive group creation in API
256 create_repo function
259 create_repo function
257 - fixed #393 py2.5 fixes for routes url generator
260 - fixed #393 py2.5 fixes for routes url generator
258 - fixed #397 Private repository groups shows up before login
261 - fixed #397 Private repository groups shows up before login
259 - fixed #396 fixed problems with revoking users in nested groups
262 - fixed #396 fixed problems with revoking users in nested groups
260 - fixed mysql unicode issues + specified InnoDB as default engine with
263 - fixed mysql unicode issues + specified InnoDB as default engine with
261 utf8 charset
264 utf8 charset
262 - #406 trim long branch/tag names in changelog to not break UI
265 - #406 trim long branch/tag names in changelog to not break UI
263
266
264 1.3.3 (**2012-03-02**)
267 1.3.3 (**2012-03-02**)
265 ----------------------
268 ----------------------
266
269
267 news
270 news
268 ++++
271 ++++
269
272
270
273
271 fixes
274 fixes
272 +++++
275 +++++
273
276
274 - fixed some python2.5 compatibility issues
277 - fixed some python2.5 compatibility issues
275 - fixed issues with removed repos was accidentally added as groups, after
278 - fixed issues with removed repos was accidentally added as groups, after
276 full rescan of paths
279 full rescan of paths
277 - fixes #376 Cannot edit user (using container auth)
280 - fixes #376 Cannot edit user (using container auth)
278 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
281 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
279 configuration
282 configuration
280 - fixed initial sorting of repos inside repo group
283 - fixed initial sorting of repos inside repo group
281 - fixes issue when user tried to resubmit same permission into user/user_groups
284 - fixes issue when user tried to resubmit same permission into user/user_groups
282 - bumped beaker version that fixes #375 leap error bug
285 - bumped beaker version that fixes #375 leap error bug
283 - fixed raw_changeset for git. It was generated with hg patch headers
286 - fixed raw_changeset for git. It was generated with hg patch headers
284 - fixed vcs issue with last_changeset for filenodes
287 - fixed vcs issue with last_changeset for filenodes
285 - fixed missing commit after hook delete
288 - fixed missing commit after hook delete
286 - fixed #372 issues with git operation detection that caused a security issue
289 - fixed #372 issues with git operation detection that caused a security issue
287 for git repos
290 for git repos
288
291
289 1.3.2 (**2012-02-28**)
292 1.3.2 (**2012-02-28**)
290 ----------------------
293 ----------------------
291
294
292 news
295 news
293 ++++
296 ++++
294
297
295
298
296 fixes
299 fixes
297 +++++
300 +++++
298
301
299 - fixed git protocol issues with repos-groups
302 - fixed git protocol issues with repos-groups
300 - fixed git remote repos validator that prevented from cloning remote git repos
303 - fixed git remote repos validator that prevented from cloning remote git repos
301 - fixes #370 ending slashes fixes for repo and groups
304 - fixes #370 ending slashes fixes for repo and groups
302 - fixes #368 improved git-protocol detection to handle other clients
305 - fixes #368 improved git-protocol detection to handle other clients
303 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
306 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
304 Moved To Root
307 Moved To Root
305 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
308 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
306 - fixed #373 missing cascade drop on user_group_to_perm table
309 - fixed #373 missing cascade drop on user_group_to_perm table
307
310
308 1.3.1 (**2012-02-27**)
311 1.3.1 (**2012-02-27**)
309 ----------------------
312 ----------------------
310
313
311 news
314 news
312 ++++
315 ++++
313
316
314
317
315 fixes
318 fixes
316 +++++
319 +++++
317
320
318 - redirection loop occurs when remember-me wasn't checked during login
321 - redirection loop occurs when remember-me wasn't checked during login
319 - fixes issues with git blob history generation
322 - fixes issues with git blob history generation
320 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
323 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
321
324
322 1.3.0 (**2012-02-26**)
325 1.3.0 (**2012-02-26**)
323 ----------------------
326 ----------------------
324
327
325 news
328 news
326 ++++
329 ++++
327
330
328 - code review, inspired by github code-comments
331 - code review, inspired by github code-comments
329 - #215 rst and markdown README files support
332 - #215 rst and markdown README files support
330 - #252 Container-based and proxy pass-through authentication support
333 - #252 Container-based and proxy pass-through authentication support
331 - #44 branch browser. Filtering of changelog by branches
334 - #44 branch browser. Filtering of changelog by branches
332 - mercurial bookmarks support
335 - mercurial bookmarks support
333 - new hover top menu, optimized to add maximum size for important views
336 - new hover top menu, optimized to add maximum size for important views
334 - configurable clone url template with possibility to specify protocol like
337 - configurable clone url template with possibility to specify protocol like
335 ssh:// or http:// and also manually alter other parts of clone_url.
338 ssh:// or http:// and also manually alter other parts of clone_url.
336 - enabled largefiles extension by default
339 - enabled largefiles extension by default
337 - optimized summary file pages and saved a lot of unused space in them
340 - optimized summary file pages and saved a lot of unused space in them
338 - #239 option to manually mark repository as fork
341 - #239 option to manually mark repository as fork
339 - #320 mapping of commit authors to RhodeCode users
342 - #320 mapping of commit authors to RhodeCode users
340 - #304 hashes are displayed using monospace font
343 - #304 hashes are displayed using monospace font
341 - diff configuration, toggle white lines and context lines
344 - diff configuration, toggle white lines and context lines
342 - #307 configurable diffs, whitespace toggle, increasing context lines
345 - #307 configurable diffs, whitespace toggle, increasing context lines
343 - sorting on branches, tags and bookmarks using YUI datatable
346 - sorting on branches, tags and bookmarks using YUI datatable
344 - improved file filter on files page
347 - improved file filter on files page
345 - implements #330 api method for listing nodes ar particular revision
348 - implements #330 api method for listing nodes ar particular revision
346 - #73 added linking issues in commit messages to chosen issue tracker url
349 - #73 added linking issues in commit messages to chosen issue tracker url
347 based on user defined regular expression
350 based on user defined regular expression
348 - added linking of changesets in commit messages
351 - added linking of changesets in commit messages
349 - new compact changelog with expandable commit messages
352 - new compact changelog with expandable commit messages
350 - firstname and lastname are optional in user creation
353 - firstname and lastname are optional in user creation
351 - #348 added post-create repository hook
354 - #348 added post-create repository hook
352 - #212 global encoding settings is now configurable from .ini files
355 - #212 global encoding settings is now configurable from .ini files
353 - #227 added repository groups permissions
356 - #227 added repository groups permissions
354 - markdown gets codehilite extensions
357 - markdown gets codehilite extensions
355 - new API methods, delete_repositories, grante/revoke permissions for groups
358 - new API methods, delete_repositories, grante/revoke permissions for groups
356 and repos
359 and repos
357
360
358
361
359 fixes
362 fixes
360 +++++
363 +++++
361
364
362 - rewrote dbsession management for atomic operations, and better error handling
365 - rewrote dbsession management for atomic operations, and better error handling
363 - fixed sorting of repo tables
366 - fixed sorting of repo tables
364 - #326 escape of special html entities in diffs
367 - #326 escape of special html entities in diffs
365 - normalized user_name => username in api attributes
368 - normalized user_name => username in api attributes
366 - fixes #298 ldap created users with mixed case emails created conflicts
369 - fixes #298 ldap created users with mixed case emails created conflicts
367 on saving a form
370 on saving a form
368 - fixes issue when owner of a repo couldn't revoke permissions for users
371 - fixes issue when owner of a repo couldn't revoke permissions for users
369 and groups
372 and groups
370 - fixes #271 rare JSON serialization problem with statistics
373 - fixes #271 rare JSON serialization problem with statistics
371 - fixes #337 missing validation check for conflicting names of a group with a
374 - fixes #337 missing validation check for conflicting names of a group with a
372 repositories group
375 repositories group
373 - #340 fixed session problem for mysql and celery tasks
376 - #340 fixed session problem for mysql and celery tasks
374 - fixed #331 RhodeCode mangles repository names if the a repository group
377 - fixed #331 RhodeCode mangles repository names if the a repository group
375 contains the "full path" to the repositories
378 contains the "full path" to the repositories
376 - #355 RhodeCode doesn't store encrypted LDAP passwords
379 - #355 RhodeCode doesn't store encrypted LDAP passwords
377
380
378 1.2.5 (**2012-01-28**)
381 1.2.5 (**2012-01-28**)
379 ----------------------
382 ----------------------
380
383
381 news
384 news
382 ++++
385 ++++
383
386
384 fixes
387 fixes
385 +++++
388 +++++
386
389
387 - #340 Celery complains about MySQL server gone away, added session cleanup
390 - #340 Celery complains about MySQL server gone away, added session cleanup
388 for celery tasks
391 for celery tasks
389 - #341 "scanning for repositories in None" log message during Rescan was missing
392 - #341 "scanning for repositories in None" log message during Rescan was missing
390 a parameter
393 a parameter
391 - fixed creating archives with subrepos. Some hooks were triggered during that
394 - fixed creating archives with subrepos. Some hooks were triggered during that
392 operation leading to crash.
395 operation leading to crash.
393 - fixed missing email in account page.
396 - fixed missing email in account page.
394 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
397 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
395 forking on windows impossible
398 forking on windows impossible
396
399
397 1.2.4 (**2012-01-19**)
400 1.2.4 (**2012-01-19**)
398 ----------------------
401 ----------------------
399
402
400 news
403 news
401 ++++
404 ++++
402
405
403 - RhodeCode is bundled with mercurial series 2.0.X by default, with
406 - RhodeCode is bundled with mercurial series 2.0.X by default, with
404 full support to largefiles extension. Enabled by default in new installations
407 full support to largefiles extension. Enabled by default in new installations
405 - #329 Ability to Add/Remove Groups to/from a Repository via AP
408 - #329 Ability to Add/Remove Groups to/from a Repository via AP
406 - added requires.txt file with requirements
409 - added requires.txt file with requirements
407
410
408 fixes
411 fixes
409 +++++
412 +++++
410
413
411 - fixes db session issues with celery when emailing admins
414 - fixes db session issues with celery when emailing admins
412 - #331 RhodeCode mangles repository names if the a repository group
415 - #331 RhodeCode mangles repository names if the a repository group
413 contains the "full path" to the repositories
416 contains the "full path" to the repositories
414 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
417 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
415 - DB session cleanup after hg protocol operations, fixes issues with
418 - DB session cleanup after hg protocol operations, fixes issues with
416 `mysql has gone away` errors
419 `mysql has gone away` errors
417 - #333 doc fixes for get_repo api function
420 - #333 doc fixes for get_repo api function
418 - #271 rare JSON serialization problem with statistics enabled
421 - #271 rare JSON serialization problem with statistics enabled
419 - #337 Fixes issues with validation of repository name conflicting with
422 - #337 Fixes issues with validation of repository name conflicting with
420 a group name. A proper message is now displayed.
423 a group name. A proper message is now displayed.
421 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
424 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
422 doesn't work
425 doesn't work
423 - #316 fixes issues with web description in hgrc files
426 - #316 fixes issues with web description in hgrc files
424
427
425 1.2.3 (**2011-11-02**)
428 1.2.3 (**2011-11-02**)
426 ----------------------
429 ----------------------
427
430
428 news
431 news
429 ++++
432 ++++
430
433
431 - added option to manage repos group for non admin users
434 - added option to manage repos group for non admin users
432 - added following API methods for get_users, create_user, get_users_groups,
435 - added following API methods for get_users, create_user, get_users_groups,
433 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
436 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
434 get_repo, create_repo, add_user_to_repo
437 get_repo, create_repo, add_user_to_repo
435 - implements #237 added password confirmation for my account
438 - implements #237 added password confirmation for my account
436 and admin edit user.
439 and admin edit user.
437 - implements #291 email notification for global events are now sent to all
440 - implements #291 email notification for global events are now sent to all
438 administrator users, and global config email.
441 administrator users, and global config email.
439
442
440 fixes
443 fixes
441 +++++
444 +++++
442
445
443 - added option for passing auth method for smtp mailer
446 - added option for passing auth method for smtp mailer
444 - #276 issue with adding a single user with id>10 to usergroups
447 - #276 issue with adding a single user with id>10 to usergroups
445 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
448 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
446 - #288 fixes managing of repos in a group for non admin user
449 - #288 fixes managing of repos in a group for non admin user
447
450
448 1.2.2 (**2011-10-17**)
451 1.2.2 (**2011-10-17**)
449 ----------------------
452 ----------------------
450
453
451 news
454 news
452 ++++
455 ++++
453
456
454 - #226 repo groups are available by path instead of numerical id
457 - #226 repo groups are available by path instead of numerical id
455
458
456 fixes
459 fixes
457 +++++
460 +++++
458
461
459 - #259 Groups with the same name but with different parent group
462 - #259 Groups with the same name but with different parent group
460 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
463 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
461 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
464 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
462 - #265 ldap save fails sometimes on converting attributes to booleans,
465 - #265 ldap save fails sometimes on converting attributes to booleans,
463 added getter and setter into model that will prevent from this on db model level
466 added getter and setter into model that will prevent from this on db model level
464 - fixed problems with timestamps issues #251 and #213
467 - fixed problems with timestamps issues #251 and #213
465 - fixes #266 RhodeCode allows to create repo with the same name and in
468 - fixes #266 RhodeCode allows to create repo with the same name and in
466 the same parent as group
469 the same parent as group
467 - fixes #245 Rescan of the repositories on Windows
470 - fixes #245 Rescan of the repositories on Windows
468 - fixes #248 cannot edit repos inside a group on windows
471 - fixes #248 cannot edit repos inside a group on windows
469 - fixes #219 forking problems on windows
472 - fixes #219 forking problems on windows
470
473
471 1.2.1 (**2011-10-08**)
474 1.2.1 (**2011-10-08**)
472 ----------------------
475 ----------------------
473
476
474 news
477 news
475 ++++
478 ++++
476
479
477
480
478 fixes
481 fixes
479 +++++
482 +++++
480
483
481 - fixed problems with basic auth and push problems
484 - fixed problems with basic auth and push problems
482 - gui fixes
485 - gui fixes
483 - fixed logger
486 - fixed logger
484
487
485 1.2.0 (**2011-10-07**)
488 1.2.0 (**2011-10-07**)
486 ----------------------
489 ----------------------
487
490
488 news
491 news
489 ++++
492 ++++
490
493
491 - implemented #47 repository groups
494 - implemented #47 repository groups
492 - implemented #89 Can setup google analytics code from settings menu
495 - implemented #89 Can setup google analytics code from settings menu
493 - implemented #91 added nicer looking archive urls with more download options
496 - implemented #91 added nicer looking archive urls with more download options
494 like tags, branches
497 like tags, branches
495 - implemented #44 into file browsing, and added follow branch option
498 - implemented #44 into file browsing, and added follow branch option
496 - implemented #84 downloads can be enabled/disabled for each repository
499 - implemented #84 downloads can be enabled/disabled for each repository
497 - anonymous repository can be cloned without having to pass default:default
500 - anonymous repository can be cloned without having to pass default:default
498 into clone url
501 into clone url
499 - fixed #90 whoosh indexer can index chooses repositories passed in command
502 - fixed #90 whoosh indexer can index chooses repositories passed in command
500 line
503 line
501 - extended journal with day aggregates and paging
504 - extended journal with day aggregates and paging
502 - implemented #107 source code lines highlight ranges
505 - implemented #107 source code lines highlight ranges
503 - implemented #93 customizable changelog on combined revision ranges -
506 - implemented #93 customizable changelog on combined revision ranges -
504 equivalent of githubs compare view
507 equivalent of githubs compare view
505 - implemented #108 extended and more powerful LDAP configuration
508 - implemented #108 extended and more powerful LDAP configuration
506 - implemented #56 users groups
509 - implemented #56 users groups
507 - major code rewrites optimized codes for speed and memory usage
510 - major code rewrites optimized codes for speed and memory usage
508 - raw and diff downloads are now in git format
511 - raw and diff downloads are now in git format
509 - setup command checks for write access to given path
512 - setup command checks for write access to given path
510 - fixed many issues with international characters and unicode. It uses utf8
513 - fixed many issues with international characters and unicode. It uses utf8
511 decode with replace to provide less errors even with non utf8 encoded strings
514 decode with replace to provide less errors even with non utf8 encoded strings
512 - #125 added API KEY access to feeds
515 - #125 added API KEY access to feeds
513 - #109 Repository can be created from external Mercurial link (aka. remote
516 - #109 Repository can be created from external Mercurial link (aka. remote
514 repository, and manually updated (via pull) from admin panel
517 repository, and manually updated (via pull) from admin panel
515 - beta git support - push/pull server + basic view for git repos
518 - beta git support - push/pull server + basic view for git repos
516 - added followers page and forks page
519 - added followers page and forks page
517 - server side file creation (with binary file upload interface)
520 - server side file creation (with binary file upload interface)
518 and edition with commits powered by codemirror
521 and edition with commits powered by codemirror
519 - #111 file browser file finder, quick lookup files on whole file tree
522 - #111 file browser file finder, quick lookup files on whole file tree
520 - added quick login sliding menu into main page
523 - added quick login sliding menu into main page
521 - changelog uses lazy loading of affected files details, in some scenarios
524 - changelog uses lazy loading of affected files details, in some scenarios
522 this can improve speed of changelog page dramatically especially for
525 this can improve speed of changelog page dramatically especially for
523 larger repositories.
526 larger repositories.
524 - implements #214 added support for downloading subrepos in download menu.
527 - implements #214 added support for downloading subrepos in download menu.
525 - Added basic API for direct operations on rhodecode via JSON
528 - Added basic API for direct operations on rhodecode via JSON
526 - Implemented advanced hook management
529 - Implemented advanced hook management
527
530
528 fixes
531 fixes
529 +++++
532 +++++
530
533
531 - fixed file browser bug, when switching into given form revision the url was
534 - fixed file browser bug, when switching into given form revision the url was
532 not changing
535 not changing
533 - fixed propagation to error controller on simplehg and simplegit middlewares
536 - fixed propagation to error controller on simplehg and simplegit middlewares
534 - fixed error when trying to make a download on empty repository
537 - fixed error when trying to make a download on empty repository
535 - fixed problem with '[' chars in commit messages in journal
538 - fixed problem with '[' chars in commit messages in journal
536 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
539 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
537 - journal fork fixes
540 - journal fork fixes
538 - removed issue with space inside renamed repository after deletion
541 - removed issue with space inside renamed repository after deletion
539 - fixed strange issue on formencode imports
542 - fixed strange issue on formencode imports
540 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
543 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
541 - #150 fixes for errors on repositories mapped in db but corrupted in
544 - #150 fixes for errors on repositories mapped in db but corrupted in
542 filesystem
545 filesystem
543 - fixed problem with ascendant characters in realm #181
546 - fixed problem with ascendant characters in realm #181
544 - fixed problem with sqlite file based database connection pool
547 - fixed problem with sqlite file based database connection pool
545 - whoosh indexer and code stats share the same dynamic extensions map
548 - whoosh indexer and code stats share the same dynamic extensions map
546 - fixes #188 - relationship delete of repo_to_perm entry on user removal
549 - fixes #188 - relationship delete of repo_to_perm entry on user removal
547 - fixes issue #189 Trending source files shows "show more" when no more exist
550 - fixes issue #189 Trending source files shows "show more" when no more exist
548 - fixes issue #197 Relative paths for pidlocks
551 - fixes issue #197 Relative paths for pidlocks
549 - fixes issue #198 password will require only 3 chars now for login form
552 - fixes issue #198 password will require only 3 chars now for login form
550 - fixes issue #199 wrong redirection for non admin users after creating a repository
553 - fixes issue #199 wrong redirection for non admin users after creating a repository
551 - fixes issues #202, bad db constraint made impossible to attach same group
554 - fixes issues #202, bad db constraint made impossible to attach same group
552 more than one time. Affects only mysql/postgres
555 more than one time. Affects only mysql/postgres
553 - fixes #218 os.kill patch for windows was missing sig param
556 - fixes #218 os.kill patch for windows was missing sig param
554 - improved rendering of dag (they are not trimmed anymore when number of
557 - improved rendering of dag (they are not trimmed anymore when number of
555 heads exceeds 5)
558 heads exceeds 5)
556
559
557 1.1.8 (**2011-04-12**)
560 1.1.8 (**2011-04-12**)
558 ----------------------
561 ----------------------
559
562
560 news
563 news
561 ++++
564 ++++
562
565
563 - improved windows support
566 - improved windows support
564
567
565 fixes
568 fixes
566 +++++
569 +++++
567
570
568 - fixed #140 freeze of python dateutil library, since new version is python2.x
571 - fixed #140 freeze of python dateutil library, since new version is python2.x
569 incompatible
572 incompatible
570 - setup-app will check for write permission in given path
573 - setup-app will check for write permission in given path
571 - cleaned up license info issue #149
574 - cleaned up license info issue #149
572 - fixes for issues #137,#116 and problems with unicode and accented characters.
575 - fixes for issues #137,#116 and problems with unicode and accented characters.
573 - fixes crashes on gravatar, when passed in email as unicode
576 - fixes crashes on gravatar, when passed in email as unicode
574 - fixed tooltip flickering problems
577 - fixed tooltip flickering problems
575 - fixed came_from redirection on windows
578 - fixed came_from redirection on windows
576 - fixed logging modules, and sql formatters
579 - fixed logging modules, and sql formatters
577 - windows fixes for os.kill issue #133
580 - windows fixes for os.kill issue #133
578 - fixes path splitting for windows issues #148
581 - fixes path splitting for windows issues #148
579 - fixed issue #143 wrong import on migration to 1.1.X
582 - fixed issue #143 wrong import on migration to 1.1.X
580 - fixed problems with displaying binary files, thanks to Thomas Waldmann
583 - fixed problems with displaying binary files, thanks to Thomas Waldmann
581 - removed name from archive files since it's breaking ui for long repo names
584 - removed name from archive files since it's breaking ui for long repo names
582 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
585 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
583 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
586 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
584 Thomas Waldmann
587 Thomas Waldmann
585 - fixed issue #166 summary pager was skipping 10 revisions on second page
588 - fixed issue #166 summary pager was skipping 10 revisions on second page
586
589
587
590
588 1.1.7 (**2011-03-23**)
591 1.1.7 (**2011-03-23**)
589 ----------------------
592 ----------------------
590
593
591 news
594 news
592 ++++
595 ++++
593
596
594 fixes
597 fixes
595 +++++
598 +++++
596
599
597 - fixed (again) #136 installation support for FreeBSD
600 - fixed (again) #136 installation support for FreeBSD
598
601
599
602
600 1.1.6 (**2011-03-21**)
603 1.1.6 (**2011-03-21**)
601 ----------------------
604 ----------------------
602
605
603 news
606 news
604 ++++
607 ++++
605
608
606 fixes
609 fixes
607 +++++
610 +++++
608
611
609 - fixed #136 installation support for FreeBSD
612 - fixed #136 installation support for FreeBSD
610 - RhodeCode will check for python version during installation
613 - RhodeCode will check for python version during installation
611
614
612 1.1.5 (**2011-03-17**)
615 1.1.5 (**2011-03-17**)
613 ----------------------
616 ----------------------
614
617
615 news
618 news
616 ++++
619 ++++
617
620
618 - basic windows support, by exchanging pybcrypt into sha256 for windows only
621 - basic windows support, by exchanging pybcrypt into sha256 for windows only
619 highly inspired by idea of mantis406
622 highly inspired by idea of mantis406
620
623
621 fixes
624 fixes
622 +++++
625 +++++
623
626
624 - fixed sorting by author in main page
627 - fixed sorting by author in main page
625 - fixed crashes with diffs on binary files
628 - fixed crashes with diffs on binary files
626 - fixed #131 problem with boolean values for LDAP
629 - fixed #131 problem with boolean values for LDAP
627 - fixed #122 mysql problems thanks to striker69
630 - fixed #122 mysql problems thanks to striker69
628 - fixed problem with errors on calling raw/raw_files/annotate functions
631 - fixed problem with errors on calling raw/raw_files/annotate functions
629 with unknown revisions
632 with unknown revisions
630 - fixed returned rawfiles attachment names with international character
633 - fixed returned rawfiles attachment names with international character
631 - cleaned out docs, big thanks to Jason Harris
634 - cleaned out docs, big thanks to Jason Harris
632
635
633 1.1.4 (**2011-02-19**)
636 1.1.4 (**2011-02-19**)
634 ----------------------
637 ----------------------
635
638
636 news
639 news
637 ++++
640 ++++
638
641
639 fixes
642 fixes
640 +++++
643 +++++
641
644
642 - fixed formencode import problem on settings page, that caused server crash
645 - fixed formencode import problem on settings page, that caused server crash
643 when that page was accessed as first after server start
646 when that page was accessed as first after server start
644 - journal fixes
647 - journal fixes
645 - fixed option to access repository just by entering http://server/<repo_name>
648 - fixed option to access repository just by entering http://server/<repo_name>
646
649
647 1.1.3 (**2011-02-16**)
650 1.1.3 (**2011-02-16**)
648 ----------------------
651 ----------------------
649
652
650 news
653 news
651 ++++
654 ++++
652
655
653 - implemented #102 allowing the '.' character in username
656 - implemented #102 allowing the '.' character in username
654 - added option to access repository just by entering http://server/<repo_name>
657 - added option to access repository just by entering http://server/<repo_name>
655 - celery task ignores result for better performance
658 - celery task ignores result for better performance
656
659
657 fixes
660 fixes
658 +++++
661 +++++
659
662
660 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
663 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
661 apollo13 and Johan Walles
664 apollo13 and Johan Walles
662 - small fixes in journal
665 - small fixes in journal
663 - fixed problems with getting setting for celery from .ini files
666 - fixed problems with getting setting for celery from .ini files
664 - registration, password reset and login boxes share the same title as main
667 - registration, password reset and login boxes share the same title as main
665 application now
668 application now
666 - fixed #113: to high permissions to fork repository
669 - fixed #113: to high permissions to fork repository
667 - fixed problem with '[' chars in commit messages in journal
670 - fixed problem with '[' chars in commit messages in journal
668 - removed issue with space inside renamed repository after deletion
671 - removed issue with space inside renamed repository after deletion
669 - db transaction fixes when filesystem repository creation failed
672 - db transaction fixes when filesystem repository creation failed
670 - fixed #106 relation issues on databases different than sqlite
673 - fixed #106 relation issues on databases different than sqlite
671 - fixed static files paths links to use of url() method
674 - fixed static files paths links to use of url() method
672
675
673 1.1.2 (**2011-01-12**)
676 1.1.2 (**2011-01-12**)
674 ----------------------
677 ----------------------
675
678
676 news
679 news
677 ++++
680 ++++
678
681
679
682
680 fixes
683 fixes
681 +++++
684 +++++
682
685
683 - fixes #98 protection against float division of percentage stats
686 - fixes #98 protection against float division of percentage stats
684 - fixed graph bug
687 - fixed graph bug
685 - forced webhelpers version since it was making troubles during installation
688 - forced webhelpers version since it was making troubles during installation
686
689
687 1.1.1 (**2011-01-06**)
690 1.1.1 (**2011-01-06**)
688 ----------------------
691 ----------------------
689
692
690 news
693 news
691 ++++
694 ++++
692
695
693 - added force https option into ini files for easier https usage (no need to
696 - added force https option into ini files for easier https usage (no need to
694 set server headers with this options)
697 set server headers with this options)
695 - small css updates
698 - small css updates
696
699
697 fixes
700 fixes
698 +++++
701 +++++
699
702
700 - fixed #96 redirect loop on files view on repositories without changesets
703 - fixed #96 redirect loop on files view on repositories without changesets
701 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
704 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
702 and server crashed with errors
705 and server crashed with errors
703 - fixed large tooltips problems on main page
706 - fixed large tooltips problems on main page
704 - fixed #92 whoosh indexer is more error proof
707 - fixed #92 whoosh indexer is more error proof
705
708
706 1.1.0 (**2010-12-18**)
709 1.1.0 (**2010-12-18**)
707 ----------------------
710 ----------------------
708
711
709 news
712 news
710 ++++
713 ++++
711
714
712 - rewrite of internals for vcs >=0.1.10
715 - rewrite of internals for vcs >=0.1.10
713 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
716 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
714 with older clients
717 with older clients
715 - anonymous access, authentication via ldap
718 - anonymous access, authentication via ldap
716 - performance upgrade for cached repos list - each repository has its own
719 - performance upgrade for cached repos list - each repository has its own
717 cache that's invalidated when needed.
720 cache that's invalidated when needed.
718 - performance upgrades on repositories with large amount of commits (20K+)
721 - performance upgrades on repositories with large amount of commits (20K+)
719 - main page quick filter for filtering repositories
722 - main page quick filter for filtering repositories
720 - user dashboards with ability to follow chosen repositories actions
723 - user dashboards with ability to follow chosen repositories actions
721 - sends email to admin on new user registration
724 - sends email to admin on new user registration
722 - added cache/statistics reset options into repository settings
725 - added cache/statistics reset options into repository settings
723 - more detailed action logger (based on hooks) with pushed changesets lists
726 - more detailed action logger (based on hooks) with pushed changesets lists
724 and options to disable those hooks from admin panel
727 and options to disable those hooks from admin panel
725 - introduced new enhanced changelog for merges that shows more accurate results
728 - introduced new enhanced changelog for merges that shows more accurate results
726 - new improved and faster code stats (based on pygments lexers mapping tables,
729 - new improved and faster code stats (based on pygments lexers mapping tables,
727 showing up to 10 trending sources for each repository. Additionally stats
730 showing up to 10 trending sources for each repository. Additionally stats
728 can be disabled in repository settings.
731 can be disabled in repository settings.
729 - gui optimizations, fixed application width to 1024px
732 - gui optimizations, fixed application width to 1024px
730 - added cut off (for large files/changesets) limit into config files
733 - added cut off (for large files/changesets) limit into config files
731 - whoosh, celeryd, upgrade moved to paster command
734 - whoosh, celeryd, upgrade moved to paster command
732 - other than sqlite database backends can be used
735 - other than sqlite database backends can be used
733
736
734 fixes
737 fixes
735 +++++
738 +++++
736
739
737 - fixes #61 forked repo was showing only after cache expired
740 - fixes #61 forked repo was showing only after cache expired
738 - fixes #76 no confirmation on user deletes
741 - fixes #76 no confirmation on user deletes
739 - fixes #66 Name field misspelled
742 - fixes #66 Name field misspelled
740 - fixes #72 block user removal when he owns repositories
743 - fixes #72 block user removal when he owns repositories
741 - fixes #69 added password confirmation fields
744 - fixes #69 added password confirmation fields
742 - fixes #87 RhodeCode crashes occasionally on updating repository owner
745 - fixes #87 RhodeCode crashes occasionally on updating repository owner
743 - fixes #82 broken annotations on files with more than 1 blank line at the end
746 - fixes #82 broken annotations on files with more than 1 blank line at the end
744 - a lot of fixes and tweaks for file browser
747 - a lot of fixes and tweaks for file browser
745 - fixed detached session issues
748 - fixed detached session issues
746 - fixed when user had no repos he would see all repos listed in my account
749 - fixed when user had no repos he would see all repos listed in my account
747 - fixed ui() instance bug when global hgrc settings was loaded for server
750 - fixed ui() instance bug when global hgrc settings was loaded for server
748 instance and all hgrc options were merged with our db ui() object
751 instance and all hgrc options were merged with our db ui() object
749 - numerous small bugfixes
752 - numerous small bugfixes
750
753
751 (special thanks for TkSoh for detailed feedback)
754 (special thanks for TkSoh for detailed feedback)
752
755
753
756
754 1.0.2 (**2010-11-12**)
757 1.0.2 (**2010-11-12**)
755 ----------------------
758 ----------------------
756
759
757 news
760 news
758 ++++
761 ++++
759
762
760 - tested under python2.7
763 - tested under python2.7
761 - bumped sqlalchemy and celery versions
764 - bumped sqlalchemy and celery versions
762
765
763 fixes
766 fixes
764 +++++
767 +++++
765
768
766 - fixed #59 missing graph.js
769 - fixed #59 missing graph.js
767 - fixed repo_size crash when repository had broken symlinks
770 - fixed repo_size crash when repository had broken symlinks
768 - fixed python2.5 crashes.
771 - fixed python2.5 crashes.
769
772
770
773
771 1.0.1 (**2010-11-10**)
774 1.0.1 (**2010-11-10**)
772 ----------------------
775 ----------------------
773
776
774 news
777 news
775 ++++
778 ++++
776
779
777 - small css updated
780 - small css updated
778
781
779 fixes
782 fixes
780 +++++
783 +++++
781
784
782 - fixed #53 python2.5 incompatible enumerate calls
785 - fixed #53 python2.5 incompatible enumerate calls
783 - fixed #52 disable mercurial extension for web
786 - fixed #52 disable mercurial extension for web
784 - fixed #51 deleting repositories don't delete it's dependent objects
787 - fixed #51 deleting repositories don't delete it's dependent objects
785
788
786
789
787 1.0.0 (**2010-11-02**)
790 1.0.0 (**2010-11-02**)
788 ----------------------
791 ----------------------
789
792
790 - security bugfix simplehg wasn't checking for permissions on commands
793 - security bugfix simplehg wasn't checking for permissions on commands
791 other than pull or push.
794 other than pull or push.
792 - fixed doubled messages after push or pull in admin journal
795 - fixed doubled messages after push or pull in admin journal
793 - templating and css corrections, fixed repo switcher on chrome, updated titles
796 - templating and css corrections, fixed repo switcher on chrome, updated titles
794 - admin menu accessible from options menu on repository view
797 - admin menu accessible from options menu on repository view
795 - permissions cached queries
798 - permissions cached queries
796
799
797 1.0.0rc4 (**2010-10-12**)
800 1.0.0rc4 (**2010-10-12**)
798 --------------------------
801 --------------------------
799
802
800 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
803 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
801 - removed cache_manager settings from sqlalchemy meta
804 - removed cache_manager settings from sqlalchemy meta
802 - added sqlalchemy cache settings to ini files
805 - added sqlalchemy cache settings to ini files
803 - validated password length and added second try of failure on paster setup-app
806 - validated password length and added second try of failure on paster setup-app
804 - fixed setup database destroy prompt even when there was no db
807 - fixed setup database destroy prompt even when there was no db
805
808
806
809
807 1.0.0rc3 (**2010-10-11**)
810 1.0.0rc3 (**2010-10-11**)
808 -------------------------
811 -------------------------
809
812
810 - fixed i18n during installation.
813 - fixed i18n during installation.
811
814
812 1.0.0rc2 (**2010-10-11**)
815 1.0.0rc2 (**2010-10-11**)
813 -------------------------
816 -------------------------
814
817
815 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
818 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
816 occure. After vcs is fixed it'll be put back again.
819 occure. After vcs is fixed it'll be put back again.
817 - templating/css rewrites, optimized css. No newline at end of file
820 - templating/css rewrites, optimized css.
@@ -1,724 +1,724 b''
1 .. _setup:
1 .. _setup:
2
2
3 =====
3 =====
4 Setup
4 Setup
5 =====
5 =====
6
6
7
7
8 Setting up RhodeCode
8 Setting up RhodeCode
9 --------------------
9 --------------------
10
10
11 First, you will need to create a RhodeCode configuration file. Run the
11 First, you will need to create a RhodeCode configuration file. Run the
12 following command to do this::
12 following command to do this::
13
13
14 paster make-config RhodeCode production.ini
14 paster make-config RhodeCode production.ini
15
15
16 - This will create the file `production.ini` in the current directory. This
16 - This will create the file `production.ini` in the current directory. This
17 configuration file contains the various settings for RhodeCode, e.g proxy
17 configuration file contains the various settings for RhodeCode, e.g proxy
18 port, email settings, usage of static files, cache, celery settings and
18 port, email settings, usage of static files, cache, celery settings and
19 logging.
19 logging.
20
20
21
21
22 Next, you need to create the databases used by RhodeCode. I recommend that you
22 Next, you need to create the databases used by RhodeCode. I recommend that you
23 use postgresql or sqlite (default). If you choose a database other than the
23 use postgresql or sqlite (default). If you choose a database other than the
24 default ensure you properly adjust the db url in your production.ini
24 default ensure you properly adjust the db url in your production.ini
25 configuration file to use this other database. RhodeCode currently supports
25 configuration file to use this other database. RhodeCode currently supports
26 postgresql, sqlite and mysql databases. Create the database by running
26 postgresql, sqlite and mysql databases. Create the database by running
27 the following command::
27 the following command::
28
28
29 paster setup-rhodecode production.ini
29 paster setup-rhodecode production.ini
30
30
31 This will prompt you for a "root" path. This "root" path is the location where
31 This will prompt you for a "root" path. This "root" path is the location where
32 RhodeCode will store all of its repositories on the current machine. After
32 RhodeCode will store all of its repositories on the current machine. After
33 entering this "root" path ``setup-rhodecode`` will also prompt you for a username
33 entering this "root" path ``setup-rhodecode`` will also prompt you for a username
34 and password for the initial admin account which ``setup-rhodecode`` sets
34 and password for the initial admin account which ``setup-rhodecode`` sets
35 up for you.
35 up for you.
36
36
37 setup process can be fully automated, example for lazy::
37 setup process can be fully automated, example for lazy::
38
38
39 paster setup-rhodecode production.ini --user=marcink --password=secret --email=marcin@rhodecode.org --repos=/home/marcink/my_repos
39 paster setup-rhodecode production.ini --user=marcink --password=secret --email=marcin@rhodecode.org --repos=/home/marcink/my_repos
40
40
41
41
42 - The ``setup-rhodecode`` command will create all of the needed tables and an
42 - The ``setup-rhodecode`` command will create all of the needed tables and an
43 admin account. When choosing a root path you can either use a new empty
43 admin account. When choosing a root path you can either use a new empty
44 location, or a location which already contains existing repositories. If you
44 location, or a location which already contains existing repositories. If you
45 choose a location which contains existing repositories RhodeCode will simply
45 choose a location which contains existing repositories RhodeCode will simply
46 add all of the repositories at the chosen location to it's database.
46 add all of the repositories at the chosen location to it's database.
47 (Note: make sure you specify the correct path to the root).
47 (Note: make sure you specify the correct path to the root).
48 - Note: the given path for mercurial_ repositories **must** be write accessible
48 - Note: the given path for mercurial_ repositories **must** be write accessible
49 for the application. It's very important since the RhodeCode web interface
49 for the application. It's very important since the RhodeCode web interface
50 will work without write access, but when trying to do a push it will
50 will work without write access, but when trying to do a push it will
51 eventually fail with permission denied errors unless it has write access.
51 eventually fail with permission denied errors unless it has write access.
52
52
53 You are now ready to use RhodeCode, to run it simply execute::
53 You are now ready to use RhodeCode, to run it simply execute::
54
54
55 paster serve production.ini
55 paster serve production.ini
56
56
57 - This command runs the RhodeCode server. The web app should be available at the
57 - This command runs the RhodeCode server. The web app should be available at the
58 127.0.0.1:5000. This ip and port is configurable via the production.ini
58 127.0.0.1:5000. This ip and port is configurable via the production.ini
59 file created in previous step
59 file created in previous step
60 - Use the admin account you created above when running ``setup-rhodecode``
60 - Use the admin account you created above when running ``setup-rhodecode``
61 to login to the web app.
61 to login to the web app.
62 - The default permissions on each repository is read, and the owner is admin.
62 - The default permissions on each repository is read, and the owner is admin.
63 Remember to update these if needed.
63 Remember to update these if needed.
64 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
64 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
65 well as edit more advanced options on users and repositories
65 well as edit more advanced options on users and repositories
66
66
67 Optionally users can create `rcextensions` package that extends RhodeCode
67 Optionally users can create `rcextensions` package that extends RhodeCode
68 functionality. To do this simply execute::
68 functionality. To do this simply execute::
69
69
70 paster make-rcext production.ini
70 paster make-rcext production.ini
71
71
72 This will create `rcextensions` package in the same place that your `ini` file
72 This will create `rcextensions` package in the same place that your `ini` file
73 lives. With `rcextensions` it's possible to add additional mapping for whoosh,
73 lives. With `rcextensions` it's possible to add additional mapping for whoosh,
74 stats and add additional code into the push/pull/create repo hooks. For example
74 stats and add additional code into the push/pull/create/delete repo hooks.
75 for sending signals to build-bots such as jenkins.
75 For example for sending signals to build-bots such as jenkins.
76 Please see the `__init__.py` file inside `rcextensions` package
76 Please see the `__init__.py` file inside `rcextensions` package
77 for more details.
77 for more details.
78
78
79
79
80 Using RhodeCode with SSH
80 Using RhodeCode with SSH
81 ------------------------
81 ------------------------
82
82
83 RhodeCode currently only hosts repositories using http and https. (The addition
83 RhodeCode currently only hosts repositories using http and https. (The addition
84 of ssh hosting is a planned future feature.) However you can easily use ssh in
84 of ssh hosting is a planned future feature.) However you can easily use ssh in
85 parallel with RhodeCode. (Repository access via ssh is a standard "out of
85 parallel with RhodeCode. (Repository access via ssh is a standard "out of
86 the box" feature of mercurial_ and you can use this to access any of the
86 the box" feature of mercurial_ and you can use this to access any of the
87 repositories that RhodeCode is hosting. See PublishingRepositories_)
87 repositories that RhodeCode is hosting. See PublishingRepositories_)
88
88
89 RhodeCode repository structures are kept in directories with the same name
89 RhodeCode repository structures are kept in directories with the same name
90 as the project. When using repository groups, each group is a subdirectory.
90 as the project. When using repository groups, each group is a subdirectory.
91 This allows you to easily use ssh for accessing repositories.
91 This allows you to easily use ssh for accessing repositories.
92
92
93 In order to use ssh you need to make sure that your web-server and the users
93 In order to use ssh you need to make sure that your web-server and the users
94 login accounts have the correct permissions set on the appropriate directories.
94 login accounts have the correct permissions set on the appropriate directories.
95 (Note that these permissions are independent of any permissions you have set up
95 (Note that these permissions are independent of any permissions you have set up
96 using the RhodeCode web interface.)
96 using the RhodeCode web interface.)
97
97
98 If your main directory (the same as set in RhodeCode settings) is for example
98 If your main directory (the same as set in RhodeCode settings) is for example
99 set to **/home/hg** and the repository you are using is named `rhodecode`, then
99 set to **/home/hg** and the repository you are using is named `rhodecode`, then
100 to clone via ssh you should run::
100 to clone via ssh you should run::
101
101
102 hg clone ssh://user@server.com/home/hg/rhodecode
102 hg clone ssh://user@server.com/home/hg/rhodecode
103
103
104 Using other external tools such as mercurial-server_ or using ssh key based
104 Using other external tools such as mercurial-server_ or using ssh key based
105 authentication is fully supported.
105 authentication is fully supported.
106
106
107 Note: In an advanced setup, in order for your ssh access to use the same
107 Note: In an advanced setup, in order for your ssh access to use the same
108 permissions as set up via the RhodeCode web interface, you can create an
108 permissions as set up via the RhodeCode web interface, you can create an
109 authentication hook to connect to the rhodecode db and runs check functions for
109 authentication hook to connect to the rhodecode db and runs check functions for
110 permissions against that.
110 permissions against that.
111
111
112 Setting up Whoosh full text search
112 Setting up Whoosh full text search
113 ----------------------------------
113 ----------------------------------
114
114
115 Starting from version 1.1 the whoosh index can be build by using the paster
115 Starting from version 1.1 the whoosh index can be build by using the paster
116 command ``make-index``. To use ``make-index`` you must specify the configuration
116 command ``make-index``. To use ``make-index`` you must specify the configuration
117 file that stores the location of the index. You may specify the location of the
117 file that stores the location of the index. You may specify the location of the
118 repositories (`--repo-location`). If not specified, this value is retrieved
118 repositories (`--repo-location`). If not specified, this value is retrieved
119 from the RhodeCode database. This was required prior to 1.2. Starting from
119 from the RhodeCode database. This was required prior to 1.2. Starting from
120 version 1.2 it is also possible to specify a comma separated list of
120 version 1.2 it is also possible to specify a comma separated list of
121 repositories (`--index-only`) to build index only on chooses repositories
121 repositories (`--index-only`) to build index only on chooses repositories
122 skipping any other found in repos location
122 skipping any other found in repos location
123
123
124 You may optionally pass the option `-f` to enable a full index rebuild. Without
124 You may optionally pass the option `-f` to enable a full index rebuild. Without
125 the `-f` option, indexing will run always in "incremental" mode.
125 the `-f` option, indexing will run always in "incremental" mode.
126
126
127 For an incremental index build use::
127 For an incremental index build use::
128
128
129 paster make-index production.ini
129 paster make-index production.ini
130
130
131 For a full index rebuild use::
131 For a full index rebuild use::
132
132
133 paster make-index production.ini -f
133 paster make-index production.ini -f
134
134
135
135
136 building index just for chosen repositories is possible with such command::
136 building index just for chosen repositories is possible with such command::
137
137
138 paster make-index production.ini --index-only=vcs,rhodecode
138 paster make-index production.ini --index-only=vcs,rhodecode
139
139
140
140
141 In order to do periodical index builds and keep your index always up to date.
141 In order to do periodical index builds and keep your index always up to date.
142 It's recommended to do a crontab entry for incremental indexing.
142 It's recommended to do a crontab entry for incremental indexing.
143 An example entry might look like this::
143 An example entry might look like this::
144
144
145 /path/to/python/bin/paster make-index /path/to/rhodecode/production.ini
145 /path/to/python/bin/paster make-index /path/to/rhodecode/production.ini
146
146
147 When using incremental mode (the default) whoosh will check the last
147 When using incremental mode (the default) whoosh will check the last
148 modification date of each file and add it to be reindexed if a newer file is
148 modification date of each file and add it to be reindexed if a newer file is
149 available. The indexing daemon checks for any removed files and removes them
149 available. The indexing daemon checks for any removed files and removes them
150 from index.
150 from index.
151
151
152 If you want to rebuild index from scratch, you can use the `-f` flag as above,
152 If you want to rebuild index from scratch, you can use the `-f` flag as above,
153 or in the admin panel you can check `build from scratch` flag.
153 or in the admin panel you can check `build from scratch` flag.
154
154
155
155
156 Setting up LDAP support
156 Setting up LDAP support
157 -----------------------
157 -----------------------
158
158
159 RhodeCode starting from version 1.1 supports ldap authentication. In order
159 RhodeCode starting from version 1.1 supports ldap authentication. In order
160 to use LDAP, you have to install the python-ldap_ package. This package is
160 to use LDAP, you have to install the python-ldap_ package. This package is
161 available via pypi, so you can install it by running
161 available via pypi, so you can install it by running
162
162
163 using easy_install::
163 using easy_install::
164
164
165 easy_install python-ldap
165 easy_install python-ldap
166
166
167 using pip::
167 using pip::
168
168
169 pip install python-ldap
169 pip install python-ldap
170
170
171 .. note::
171 .. note::
172 python-ldap requires some certain libs on your system, so before installing
172 python-ldap requires some certain libs on your system, so before installing
173 it check that you have at least `openldap`, and `sasl` libraries.
173 it check that you have at least `openldap`, and `sasl` libraries.
174
174
175 LDAP settings are located in admin->ldap section,
175 LDAP settings are located in admin->ldap section,
176
176
177 Here's a typical ldap setup::
177 Here's a typical ldap setup::
178
178
179 Connection settings
179 Connection settings
180 Enable LDAP = checked
180 Enable LDAP = checked
181 Host = host.example.org
181 Host = host.example.org
182 Port = 389
182 Port = 389
183 Account = <account>
183 Account = <account>
184 Password = <password>
184 Password = <password>
185 Connection Security = LDAPS connection
185 Connection Security = LDAPS connection
186 Certificate Checks = DEMAND
186 Certificate Checks = DEMAND
187
187
188 Search settings
188 Search settings
189 Base DN = CN=users,DC=host,DC=example,DC=org
189 Base DN = CN=users,DC=host,DC=example,DC=org
190 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
190 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
191 LDAP Search Scope = SUBTREE
191 LDAP Search Scope = SUBTREE
192
192
193 Attribute mappings
193 Attribute mappings
194 Login Attribute = uid
194 Login Attribute = uid
195 First Name Attribute = firstName
195 First Name Attribute = firstName
196 Last Name Attribute = lastName
196 Last Name Attribute = lastName
197 E-mail Attribute = mail
197 E-mail Attribute = mail
198
198
199 .. _enable_ldap:
199 .. _enable_ldap:
200
200
201 Enable LDAP : required
201 Enable LDAP : required
202 Whether to use LDAP for authenticating users.
202 Whether to use LDAP for authenticating users.
203
203
204 .. _ldap_host:
204 .. _ldap_host:
205
205
206 Host : required
206 Host : required
207 LDAP server hostname or IP address.
207 LDAP server hostname or IP address.
208
208
209 .. _Port:
209 .. _Port:
210
210
211 Port : required
211 Port : required
212 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
212 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
213
213
214 .. _ldap_account:
214 .. _ldap_account:
215
215
216 Account : optional
216 Account : optional
217 Only required if the LDAP server does not allow anonymous browsing of
217 Only required if the LDAP server does not allow anonymous browsing of
218 records. This should be a special account for record browsing. This
218 records. This should be a special account for record browsing. This
219 will require `LDAP Password`_ below.
219 will require `LDAP Password`_ below.
220
220
221 .. _LDAP Password:
221 .. _LDAP Password:
222
222
223 Password : optional
223 Password : optional
224 Only required if the LDAP server does not allow anonymous browsing of
224 Only required if the LDAP server does not allow anonymous browsing of
225 records.
225 records.
226
226
227 .. _Enable LDAPS:
227 .. _Enable LDAPS:
228
228
229 Connection Security : required
229 Connection Security : required
230 Defines the connection to LDAP server
230 Defines the connection to LDAP server
231
231
232 No encryption
232 No encryption
233 Plain non encrypted connection
233 Plain non encrypted connection
234
234
235 LDAPS connection
235 LDAPS connection
236 Enable ldaps connection. It will likely require `Port`_ to be set to
236 Enable ldaps connection. It will likely require `Port`_ to be set to
237 a different value (standard LDAPS port is 636). When LDAPS is enabled
237 a different value (standard LDAPS port is 636). When LDAPS is enabled
238 then `Certificate Checks`_ is required.
238 then `Certificate Checks`_ is required.
239
239
240 START_TLS on LDAP connection
240 START_TLS on LDAP connection
241 START TLS connection
241 START TLS connection
242
242
243 .. _Certificate Checks:
243 .. _Certificate Checks:
244
244
245 Certificate Checks : optional
245 Certificate Checks : optional
246 How SSL certificates verification is handled - this is only useful when
246 How SSL certificates verification is handled - this is only useful when
247 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
247 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
248 while the other options are susceptible to man-in-the-middle attacks. SSL
248 while the other options are susceptible to man-in-the-middle attacks. SSL
249 certificates can be installed to /etc/openldap/cacerts so that the
249 certificates can be installed to /etc/openldap/cacerts so that the
250 DEMAND or HARD options can be used with self-signed certificates or
250 DEMAND or HARD options can be used with self-signed certificates or
251 certificates that do not have traceable certificates of authority.
251 certificates that do not have traceable certificates of authority.
252
252
253 NEVER
253 NEVER
254 A serve certificate will never be requested or checked.
254 A serve certificate will never be requested or checked.
255
255
256 ALLOW
256 ALLOW
257 A server certificate is requested. Failure to provide a
257 A server certificate is requested. Failure to provide a
258 certificate or providing a bad certificate will not terminate the
258 certificate or providing a bad certificate will not terminate the
259 session.
259 session.
260
260
261 TRY
261 TRY
262 A server certificate is requested. Failure to provide a
262 A server certificate is requested. Failure to provide a
263 certificate does not halt the session; providing a bad certificate
263 certificate does not halt the session; providing a bad certificate
264 halts the session.
264 halts the session.
265
265
266 DEMAND
266 DEMAND
267 A server certificate is requested and must be provided and
267 A server certificate is requested and must be provided and
268 authenticated for the session to proceed.
268 authenticated for the session to proceed.
269
269
270 HARD
270 HARD
271 The same as DEMAND.
271 The same as DEMAND.
272
272
273 .. _Base DN:
273 .. _Base DN:
274
274
275 Base DN : required
275 Base DN : required
276 The Distinguished Name (DN) where searches for users will be performed.
276 The Distinguished Name (DN) where searches for users will be performed.
277 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
277 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
278
278
279 .. _LDAP Filter:
279 .. _LDAP Filter:
280
280
281 LDAP Filter : optional
281 LDAP Filter : optional
282 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
282 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
283 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
283 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
284 which LDAP objects are identified as representing Users for
284 which LDAP objects are identified as representing Users for
285 authentication. The filter is augmented by `Login Attribute`_ below.
285 authentication. The filter is augmented by `Login Attribute`_ below.
286 This can commonly be left blank.
286 This can commonly be left blank.
287
287
288 .. _LDAP Search Scope:
288 .. _LDAP Search Scope:
289
289
290 LDAP Search Scope : required
290 LDAP Search Scope : required
291 This limits how far LDAP will search for a matching object.
291 This limits how far LDAP will search for a matching object.
292
292
293 BASE
293 BASE
294 Only allows searching of `Base DN`_ and is usually not what you
294 Only allows searching of `Base DN`_ and is usually not what you
295 want.
295 want.
296
296
297 ONELEVEL
297 ONELEVEL
298 Searches all entries under `Base DN`_, but not Base DN itself.
298 Searches all entries under `Base DN`_, but not Base DN itself.
299
299
300 SUBTREE
300 SUBTREE
301 Searches all entries below `Base DN`_, but not Base DN itself.
301 Searches all entries below `Base DN`_, but not Base DN itself.
302 When using SUBTREE `LDAP Filter`_ is useful to limit object
302 When using SUBTREE `LDAP Filter`_ is useful to limit object
303 location.
303 location.
304
304
305 .. _Login Attribute:
305 .. _Login Attribute:
306
306
307 Login Attribute : required
307 Login Attribute : required
308 The LDAP record attribute that will be matched as the USERNAME or
308 The LDAP record attribute that will be matched as the USERNAME or
309 ACCOUNT used to connect to RhodeCode. This will be added to `LDAP
309 ACCOUNT used to connect to RhodeCode. This will be added to `LDAP
310 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
310 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
311 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
311 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
312 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
312 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
313 ::
313 ::
314
314
315 (&(LDAPFILTER)(uid=jsmith))
315 (&(LDAPFILTER)(uid=jsmith))
316
316
317 .. _ldap_attr_firstname:
317 .. _ldap_attr_firstname:
318
318
319 First Name Attribute : required
319 First Name Attribute : required
320 The LDAP record attribute which represents the user's first name.
320 The LDAP record attribute which represents the user's first name.
321
321
322 .. _ldap_attr_lastname:
322 .. _ldap_attr_lastname:
323
323
324 Last Name Attribute : required
324 Last Name Attribute : required
325 The LDAP record attribute which represents the user's last name.
325 The LDAP record attribute which represents the user's last name.
326
326
327 .. _ldap_attr_email:
327 .. _ldap_attr_email:
328
328
329 Email Attribute : required
329 Email Attribute : required
330 The LDAP record attribute which represents the user's email address.
330 The LDAP record attribute which represents the user's email address.
331
331
332 If all data are entered correctly, and python-ldap_ is properly installed
332 If all data are entered correctly, and python-ldap_ is properly installed
333 users should be granted access to RhodeCode with ldap accounts. At this
333 users should be granted access to RhodeCode with ldap accounts. At this
334 time user information is copied from LDAP into the RhodeCode user database.
334 time user information is copied from LDAP into the RhodeCode user database.
335 This means that updates of an LDAP user object may not be reflected as a
335 This means that updates of an LDAP user object may not be reflected as a
336 user update in RhodeCode.
336 user update in RhodeCode.
337
337
338 If You have problems with LDAP access and believe You entered correct
338 If You have problems with LDAP access and believe You entered correct
339 information check out the RhodeCode logs, any error messages sent from LDAP
339 information check out the RhodeCode logs, any error messages sent from LDAP
340 will be saved there.
340 will be saved there.
341
341
342 Active Directory
342 Active Directory
343 ''''''''''''''''
343 ''''''''''''''''
344
344
345 RhodeCode can use Microsoft Active Directory for user authentication. This
345 RhodeCode can use Microsoft Active Directory for user authentication. This
346 is done through an LDAP or LDAPS connection to Active Directory. The
346 is done through an LDAP or LDAPS connection to Active Directory. The
347 following LDAP configuration settings are typical for using Active
347 following LDAP configuration settings are typical for using Active
348 Directory ::
348 Directory ::
349
349
350 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
350 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
351 Login Attribute = sAMAccountName
351 Login Attribute = sAMAccountName
352 First Name Attribute = givenName
352 First Name Attribute = givenName
353 Last Name Attribute = sn
353 Last Name Attribute = sn
354 E-mail Attribute = mail
354 E-mail Attribute = mail
355
355
356 All other LDAP settings will likely be site-specific and should be
356 All other LDAP settings will likely be site-specific and should be
357 appropriately configured.
357 appropriately configured.
358
358
359
359
360 Authentication by container or reverse-proxy
360 Authentication by container or reverse-proxy
361 --------------------------------------------
361 --------------------------------------------
362
362
363 Starting with version 1.3, RhodeCode supports delegating the authentication
363 Starting with version 1.3, RhodeCode supports delegating the authentication
364 of users to its WSGI container, or to a reverse-proxy server through which all
364 of users to its WSGI container, or to a reverse-proxy server through which all
365 clients access the application.
365 clients access the application.
366
366
367 When these authentication methods are enabled in RhodeCode, it uses the
367 When these authentication methods are enabled in RhodeCode, it uses the
368 username that the container/proxy (Apache/Nginx/etc) authenticated and doesn't
368 username that the container/proxy (Apache/Nginx/etc) authenticated and doesn't
369 perform the authentication itself. The authorization, however, is still done by
369 perform the authentication itself. The authorization, however, is still done by
370 RhodeCode according to its settings.
370 RhodeCode according to its settings.
371
371
372 When a user logs in for the first time using these authentication methods,
372 When a user logs in for the first time using these authentication methods,
373 a matching user account is created in RhodeCode with default permissions. An
373 a matching user account is created in RhodeCode with default permissions. An
374 administrator can then modify it using RhodeCode's admin interface.
374 administrator can then modify it using RhodeCode's admin interface.
375 It's also possible for an administrator to create accounts and configure their
375 It's also possible for an administrator to create accounts and configure their
376 permissions before the user logs in for the first time.
376 permissions before the user logs in for the first time.
377
377
378 Container-based authentication
378 Container-based authentication
379 ''''''''''''''''''''''''''''''
379 ''''''''''''''''''''''''''''''
380
380
381 In a container-based authentication setup, RhodeCode reads the user name from
381 In a container-based authentication setup, RhodeCode reads the user name from
382 the ``REMOTE_USER`` server variable provided by the WSGI container.
382 the ``REMOTE_USER`` server variable provided by the WSGI container.
383
383
384 After setting up your container (see `Apache's WSGI config`_), you'd need
384 After setting up your container (see `Apache's WSGI config`_), you'd need
385 to configure it to require authentication on the location configured for
385 to configure it to require authentication on the location configured for
386 RhodeCode.
386 RhodeCode.
387
387
388 In order for RhodeCode to start using the provided username, you should set the
388 In order for RhodeCode to start using the provided username, you should set the
389 following in the [app:main] section of your .ini file::
389 following in the [app:main] section of your .ini file::
390
390
391 container_auth_enabled = true
391 container_auth_enabled = true
392
392
393
393
394 Proxy pass-through authentication
394 Proxy pass-through authentication
395 '''''''''''''''''''''''''''''''''
395 '''''''''''''''''''''''''''''''''
396
396
397 In a proxy pass-through authentication setup, RhodeCode reads the user name
397 In a proxy pass-through authentication setup, RhodeCode reads the user name
398 from the ``X-Forwarded-User`` request header, which should be configured to be
398 from the ``X-Forwarded-User`` request header, which should be configured to be
399 sent by the reverse-proxy server.
399 sent by the reverse-proxy server.
400
400
401 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
401 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
402 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'd need to
402 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'd need to
403 configure the authentication and add the username in a request header named
403 configure the authentication and add the username in a request header named
404 ``X-Forwarded-User``.
404 ``X-Forwarded-User``.
405
405
406 For example, the following config section for Apache sets a subdirectory in a
406 For example, the following config section for Apache sets a subdirectory in a
407 reverse-proxy setup with basic auth::
407 reverse-proxy setup with basic auth::
408
408
409 <Location /<someprefix> >
409 <Location /<someprefix> >
410 ProxyPass http://127.0.0.1:5000/<someprefix>
410 ProxyPass http://127.0.0.1:5000/<someprefix>
411 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
411 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
412 SetEnvIf X-Url-Scheme https HTTPS=1
412 SetEnvIf X-Url-Scheme https HTTPS=1
413
413
414 AuthType Basic
414 AuthType Basic
415 AuthName "RhodeCode authentication"
415 AuthName "RhodeCode authentication"
416 AuthUserFile /home/web/rhodecode/.htpasswd
416 AuthUserFile /home/web/rhodecode/.htpasswd
417 require valid-user
417 require valid-user
418
418
419 RequestHeader unset X-Forwarded-User
419 RequestHeader unset X-Forwarded-User
420
420
421 RewriteEngine On
421 RewriteEngine On
422 RewriteCond %{LA-U:REMOTE_USER} (.+)
422 RewriteCond %{LA-U:REMOTE_USER} (.+)
423 RewriteRule .* - [E=RU:%1]
423 RewriteRule .* - [E=RU:%1]
424 RequestHeader set X-Forwarded-User %{RU}e
424 RequestHeader set X-Forwarded-User %{RU}e
425 </Location>
425 </Location>
426
426
427 In order for RhodeCode to start using the forwarded username, you should set
427 In order for RhodeCode to start using the forwarded username, you should set
428 the following in the [app:main] section of your .ini file::
428 the following in the [app:main] section of your .ini file::
429
429
430 proxypass_auth_enabled = true
430 proxypass_auth_enabled = true
431
431
432 .. note::
432 .. note::
433 If you enable proxy pass-through authentication, make sure your server is
433 If you enable proxy pass-through authentication, make sure your server is
434 only accessible through the proxy. Otherwise, any client would be able to
434 only accessible through the proxy. Otherwise, any client would be able to
435 forge the authentication header and could effectively become authenticated
435 forge the authentication header and could effectively become authenticated
436 using any account of their liking.
436 using any account of their liking.
437
437
438 Integration with Issue trackers
438 Integration with Issue trackers
439 -------------------------------
439 -------------------------------
440
440
441 RhodeCode provides a simple integration with issue trackers. It's possible
441 RhodeCode provides a simple integration with issue trackers. It's possible
442 to define a regular expression that will fetch issue id stored in commit
442 to define a regular expression that will fetch issue id stored in commit
443 messages and replace that with an url to this issue. To enable this simply
443 messages and replace that with an url to this issue. To enable this simply
444 uncomment following variables in the ini file::
444 uncomment following variables in the ini file::
445
445
446 url_pat = (?:^#|\s#)(\w+)
446 url_pat = (?:^#|\s#)(\w+)
447 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
447 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
448 issue_prefix = #
448 issue_prefix = #
449
449
450 `url_pat` is the regular expression that will fetch issues from commit messages.
450 `url_pat` is the regular expression that will fetch issues from commit messages.
451 Default regex will match issues in format of #<number> eg. #300.
451 Default regex will match issues in format of #<number> eg. #300.
452
452
453 Matched issues will be replace with the link specified as `issue_server_link`
453 Matched issues will be replace with the link specified as `issue_server_link`
454 {id} will be replaced with issue id, and {repo} with repository name.
454 {id} will be replaced with issue id, and {repo} with repository name.
455 Since the # is striped `issue_prefix` is added as a prefix to url.
455 Since the # is striped `issue_prefix` is added as a prefix to url.
456 `issue_prefix` can be something different than # if you pass
456 `issue_prefix` can be something different than # if you pass
457 ISSUE- as issue prefix this will generate an url in format::
457 ISSUE- as issue prefix this will generate an url in format::
458
458
459 <a href="https://myissueserver.com/example_repo/issue/300">ISSUE-300</a>
459 <a href="https://myissueserver.com/example_repo/issue/300">ISSUE-300</a>
460
460
461 Hook management
461 Hook management
462 ---------------
462 ---------------
463
463
464 Hooks can be managed in similar way to this used in .hgrc files.
464 Hooks can be managed in similar way to this used in .hgrc files.
465 To access hooks setting click `advanced setup` on Hooks section of Mercurial
465 To access hooks setting click `advanced setup` on Hooks section of Mercurial
466 Settings in Admin.
466 Settings in Admin.
467
467
468 There are 4 built in hooks that cannot be changed (only enable/disable by
468 There are 4 built in hooks that cannot be changed (only enable/disable by
469 checkboxes on previos section).
469 checkboxes on previos section).
470 To add another custom hook simply fill in first section with
470 To add another custom hook simply fill in first section with
471 <name>.<hook_type> and the second one with hook path. Example hooks
471 <name>.<hook_type> and the second one with hook path. Example hooks
472 can be found at *rhodecode.lib.hooks*.
472 can be found at *rhodecode.lib.hooks*.
473
473
474
474
475 Changing default encoding
475 Changing default encoding
476 -------------------------
476 -------------------------
477
477
478 By default RhodeCode uses utf8 encoding, starting from 1.3 series this
478 By default RhodeCode uses utf8 encoding, starting from 1.3 series this
479 can be changed, simply edit default_encoding in .ini file to desired one.
479 can be changed, simply edit default_encoding in .ini file to desired one.
480 This affects many parts in rhodecode including commiters names, filenames,
480 This affects many parts in rhodecode including commiters names, filenames,
481 encoding of commit messages. In addition RhodeCode can detect if `chardet`
481 encoding of commit messages. In addition RhodeCode can detect if `chardet`
482 library is installed. If `chardet` is detected RhodeCode will fallback to it
482 library is installed. If `chardet` is detected RhodeCode will fallback to it
483 when there are encode/decode errors.
483 when there are encode/decode errors.
484
484
485
485
486 Setting Up Celery
486 Setting Up Celery
487 -----------------
487 -----------------
488
488
489 Since version 1.1 celery is configured by the rhodecode ini configuration files.
489 Since version 1.1 celery is configured by the rhodecode ini configuration files.
490 Simply set use_celery=true in the ini file then add / change the configuration
490 Simply set use_celery=true in the ini file then add / change the configuration
491 variables inside the ini file.
491 variables inside the ini file.
492
492
493 Remember that the ini files use the format with '.' not with '_' like celery.
493 Remember that the ini files use the format with '.' not with '_' like celery.
494 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
494 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
495 the config file.
495 the config file.
496
496
497 In order to start using celery run::
497 In order to start using celery run::
498
498
499 paster celeryd <configfile.ini>
499 paster celeryd <configfile.ini>
500
500
501
501
502 .. note::
502 .. note::
503 Make sure you run this command from the same virtualenv, and with the same
503 Make sure you run this command from the same virtualenv, and with the same
504 user that rhodecode runs.
504 user that rhodecode runs.
505
505
506 HTTPS support
506 HTTPS support
507 -------------
507 -------------
508
508
509 There are two ways to enable https:
509 There are two ways to enable https:
510
510
511 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
511 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
512 recognize this headers and make proper https redirections
512 recognize this headers and make proper https redirections
513 - Alternatively, change the `force_https = true` flag in the ini configuration
513 - Alternatively, change the `force_https = true` flag in the ini configuration
514 to force using https, no headers are needed than to enable https
514 to force using https, no headers are needed than to enable https
515
515
516
516
517 Nginx virtual host example
517 Nginx virtual host example
518 --------------------------
518 --------------------------
519
519
520 Sample config for nginx using proxy::
520 Sample config for nginx using proxy::
521
521
522 upstream rc {
522 upstream rc {
523 server 127.0.0.1:5000;
523 server 127.0.0.1:5000;
524 # add more instances for load balancing
524 # add more instances for load balancing
525 #server 127.0.0.1:5001;
525 #server 127.0.0.1:5001;
526 #server 127.0.0.1:5002;
526 #server 127.0.0.1:5002;
527 }
527 }
528
528
529 server {
529 server {
530 listen 80;
530 listen 80;
531 server_name hg.myserver.com;
531 server_name hg.myserver.com;
532 access_log /var/log/nginx/rhodecode.access.log;
532 access_log /var/log/nginx/rhodecode.access.log;
533 error_log /var/log/nginx/rhodecode.error.log;
533 error_log /var/log/nginx/rhodecode.error.log;
534
534
535 # uncomment if you have nginx with chunking module compiled
535 # uncomment if you have nginx with chunking module compiled
536 # fixes the issues of having to put postBuffer data for large git
536 # fixes the issues of having to put postBuffer data for large git
537 # pushes
537 # pushes
538 #chunkin on;
538 #chunkin on;
539 #error_page 411 = @my_411_error;
539 #error_page 411 = @my_411_error;
540 #location @my_411_error {
540 #location @my_411_error {
541 # chunkin_resume;
541 # chunkin_resume;
542 #}
542 #}
543
543
544 # uncomment if you want to serve static files by nginx
544 # uncomment if you want to serve static files by nginx
545 #root /path/to/installation/rhodecode/public;
545 #root /path/to/installation/rhodecode/public;
546
546
547 location / {
547 location / {
548 try_files $uri @rhode;
548 try_files $uri @rhode;
549 }
549 }
550
550
551 location @rhode {
551 location @rhode {
552 proxy_pass http://rc;
552 proxy_pass http://rc;
553 include /etc/nginx/proxy.conf;
553 include /etc/nginx/proxy.conf;
554 }
554 }
555
555
556 }
556 }
557
557
558 Here's the proxy.conf. It's tuned so it will not timeout on long
558 Here's the proxy.conf. It's tuned so it will not timeout on long
559 pushes or large pushes::
559 pushes or large pushes::
560
560
561 proxy_redirect off;
561 proxy_redirect off;
562 proxy_set_header Host $host;
562 proxy_set_header Host $host;
563 proxy_set_header X-Url-Scheme $scheme;
563 proxy_set_header X-Url-Scheme $scheme;
564 proxy_set_header X-Host $http_host;
564 proxy_set_header X-Host $http_host;
565 proxy_set_header X-Real-IP $remote_addr;
565 proxy_set_header X-Real-IP $remote_addr;
566 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
566 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
567 proxy_set_header Proxy-host $proxy_host;
567 proxy_set_header Proxy-host $proxy_host;
568 client_max_body_size 400m;
568 client_max_body_size 400m;
569 client_body_buffer_size 128k;
569 client_body_buffer_size 128k;
570 proxy_buffering off;
570 proxy_buffering off;
571 proxy_connect_timeout 7200;
571 proxy_connect_timeout 7200;
572 proxy_send_timeout 7200;
572 proxy_send_timeout 7200;
573 proxy_read_timeout 7200;
573 proxy_read_timeout 7200;
574 proxy_buffers 8 32k;
574 proxy_buffers 8 32k;
575
575
576 Also, when using root path with nginx you might set the static files to false
576 Also, when using root path with nginx you might set the static files to false
577 in the production.ini file::
577 in the production.ini file::
578
578
579 [app:main]
579 [app:main]
580 use = egg:rhodecode
580 use = egg:rhodecode
581 full_stack = true
581 full_stack = true
582 static_files = false
582 static_files = false
583 lang=en
583 lang=en
584 cache_dir = %(here)s/data
584 cache_dir = %(here)s/data
585
585
586 In order to not have the statics served by the application. This improves speed.
586 In order to not have the statics served by the application. This improves speed.
587
587
588
588
589 Apache virtual host reverse proxy example
589 Apache virtual host reverse proxy example
590 -----------------------------------------
590 -----------------------------------------
591
591
592 Here is a sample configuration file for apache using proxy::
592 Here is a sample configuration file for apache using proxy::
593
593
594 <VirtualHost *:80>
594 <VirtualHost *:80>
595 ServerName hg.myserver.com
595 ServerName hg.myserver.com
596 ServerAlias hg.myserver.com
596 ServerAlias hg.myserver.com
597
597
598 <Proxy *>
598 <Proxy *>
599 Order allow,deny
599 Order allow,deny
600 Allow from all
600 Allow from all
601 </Proxy>
601 </Proxy>
602
602
603 #important !
603 #important !
604 #Directive to properly generate url (clone url) for pylons
604 #Directive to properly generate url (clone url) for pylons
605 ProxyPreserveHost On
605 ProxyPreserveHost On
606
606
607 #rhodecode instance
607 #rhodecode instance
608 ProxyPass / http://127.0.0.1:5000/
608 ProxyPass / http://127.0.0.1:5000/
609 ProxyPassReverse / http://127.0.0.1:5000/
609 ProxyPassReverse / http://127.0.0.1:5000/
610
610
611 #to enable https use line below
611 #to enable https use line below
612 #SetEnvIf X-Url-Scheme https HTTPS=1
612 #SetEnvIf X-Url-Scheme https HTTPS=1
613
613
614 </VirtualHost>
614 </VirtualHost>
615
615
616
616
617 Additional tutorial
617 Additional tutorial
618 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
618 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
619
619
620
620
621 Apache as subdirectory
621 Apache as subdirectory
622 ----------------------
622 ----------------------
623
623
624 Apache subdirectory part::
624 Apache subdirectory part::
625
625
626 <Location /<someprefix> >
626 <Location /<someprefix> >
627 ProxyPass http://127.0.0.1:5000/<someprefix>
627 ProxyPass http://127.0.0.1:5000/<someprefix>
628 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
628 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
629 SetEnvIf X-Url-Scheme https HTTPS=1
629 SetEnvIf X-Url-Scheme https HTTPS=1
630 </Location>
630 </Location>
631
631
632 Besides the regular apache setup you will need to add the following line
632 Besides the regular apache setup you will need to add the following line
633 into [app:main] section of your .ini file::
633 into [app:main] section of your .ini file::
634
634
635 filter-with = proxy-prefix
635 filter-with = proxy-prefix
636
636
637 Add the following at the end of the .ini file::
637 Add the following at the end of the .ini file::
638
638
639 [filter:proxy-prefix]
639 [filter:proxy-prefix]
640 use = egg:PasteDeploy#prefix
640 use = egg:PasteDeploy#prefix
641 prefix = /<someprefix>
641 prefix = /<someprefix>
642
642
643
643
644 then change <someprefix> into your choosen prefix
644 then change <someprefix> into your choosen prefix
645
645
646 Apache's WSGI config
646 Apache's WSGI config
647 --------------------
647 --------------------
648
648
649 Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For
649 Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For
650 that, you'll need to:
650 that, you'll need to:
651
651
652 - Install mod_wsgi. If using a Debian-based distro, you can install
652 - Install mod_wsgi. If using a Debian-based distro, you can install
653 the package libapache2-mod-wsgi::
653 the package libapache2-mod-wsgi::
654
654
655 aptitude install libapache2-mod-wsgi
655 aptitude install libapache2-mod-wsgi
656
656
657 - Enable mod_wsgi::
657 - Enable mod_wsgi::
658
658
659 a2enmod wsgi
659 a2enmod wsgi
660
660
661 - Create a wsgi dispatch script, like the one below. Make sure you
661 - Create a wsgi dispatch script, like the one below. Make sure you
662 check the paths correctly point to where you installed RhodeCode
662 check the paths correctly point to where you installed RhodeCode
663 and its Python Virtual Environment.
663 and its Python Virtual Environment.
664 - Enable the WSGIScriptAlias directive for the wsgi dispatch script,
664 - Enable the WSGIScriptAlias directive for the wsgi dispatch script,
665 as in the following example. Once again, check the paths are
665 as in the following example. Once again, check the paths are
666 correctly specified.
666 correctly specified.
667
667
668 Here is a sample excerpt from an Apache Virtual Host configuration file::
668 Here is a sample excerpt from an Apache Virtual Host configuration file::
669
669
670 WSGIDaemonProcess pylons \
670 WSGIDaemonProcess pylons \
671 threads=4 \
671 threads=4 \
672 python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
672 python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
673 WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
673 WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
674 WSGIPassAuthorization On
674 WSGIPassAuthorization On
675
675
676 .. note::
676 .. note::
677 when running apache as root please add: `user=www-data group=www-data`
677 when running apache as root please add: `user=www-data group=www-data`
678 into above configuration
678 into above configuration
679
679
680 .. note::
680 .. note::
681 RhodeCode cannot be runned in multiprocess mode in apache, make sure
681 RhodeCode cannot be runned in multiprocess mode in apache, make sure
682 you don't specify `processes=num` directive in the config
682 you don't specify `processes=num` directive in the config
683
683
684
684
685 Example wsgi dispatch script::
685 Example wsgi dispatch script::
686
686
687 import os
687 import os
688 os.environ["HGENCODING"] = "UTF-8"
688 os.environ["HGENCODING"] = "UTF-8"
689 os.environ['PYTHON_EGG_CACHE'] = '/home/web/rhodecode/.egg-cache'
689 os.environ['PYTHON_EGG_CACHE'] = '/home/web/rhodecode/.egg-cache'
690
690
691 # sometimes it's needed to set the curent dir
691 # sometimes it's needed to set the curent dir
692 os.chdir('/home/web/rhodecode/')
692 os.chdir('/home/web/rhodecode/')
693
693
694 import site
694 import site
695 site.addsitedir("/home/web/rhodecode/pyenv/lib/python2.6/site-packages")
695 site.addsitedir("/home/web/rhodecode/pyenv/lib/python2.6/site-packages")
696
696
697 from paste.deploy import loadapp
697 from paste.deploy import loadapp
698 from paste.script.util.logging_config import fileConfig
698 from paste.script.util.logging_config import fileConfig
699
699
700 fileConfig('/home/web/rhodecode/production.ini')
700 fileConfig('/home/web/rhodecode/production.ini')
701 application = loadapp('config:/home/web/rhodecode/production.ini')
701 application = loadapp('config:/home/web/rhodecode/production.ini')
702
702
703 Note: when using mod_wsgi you'll need to install the same version of
703 Note: when using mod_wsgi you'll need to install the same version of
704 Mercurial that's inside RhodeCode's virtualenv also on the system's Python
704 Mercurial that's inside RhodeCode's virtualenv also on the system's Python
705 environment.
705 environment.
706
706
707
707
708 Other configuration files
708 Other configuration files
709 -------------------------
709 -------------------------
710
710
711 Some example init.d scripts can be found in init.d directory::
711 Some example init.d scripts can be found in init.d directory::
712
712
713 https://secure.rhodecode.org/rhodecode/files/beta/init.d
713 https://secure.rhodecode.org/rhodecode/files/beta/init.d
714
714
715 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
715 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
716 .. _python: http://www.python.org/
716 .. _python: http://www.python.org/
717 .. _mercurial: http://mercurial.selenic.com/
717 .. _mercurial: http://mercurial.selenic.com/
718 .. _celery: http://celeryproject.org/
718 .. _celery: http://celeryproject.org/
719 .. _rabbitmq: http://www.rabbitmq.com/
719 .. _rabbitmq: http://www.rabbitmq.com/
720 .. _python-ldap: http://www.python-ldap.org/
720 .. _python-ldap: http://www.python-ldap.org/
721 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
721 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
722 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
722 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
723 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
723 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
724 .. _google group rhodecode: http://groups.google.com/group/rhodecode No newline at end of file
724 .. _google group rhodecode: http://groups.google.com/group/rhodecode
@@ -1,86 +1,112 b''
1 # Additional mappings that are not present in the pygments lexers
1 # Additional mappings that are not present in the pygments lexers
2 # used for building stats
2 # used for building stats
3 # format is {'ext':['Names']} eg. {'py':['Python']} note: there can be
3 # format is {'ext':['Names']} eg. {'py':['Python']} note: there can be
4 # more than one name for extension
4 # more than one name for extension
5 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
5 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
6 # build by pygments
6 # build by pygments
7 EXTRA_MAPPINGS = {}
7 EXTRA_MAPPINGS = {}
8
8
9 #==============================================================================
9 #==============================================================================
10 # WHOOSH INDEX EXTENSIONS
10 # WHOOSH INDEX EXTENSIONS
11 #==============================================================================
11 #==============================================================================
12 # if INDEX_EXTENSIONS is [] it'll use pygments lexers extensions by default.
12 # if INDEX_EXTENSIONS is [] it'll use pygments lexers extensions by default.
13 # To set your own just add to this list extensions to index with content
13 # To set your own just add to this list extensions to index with content
14 INDEX_EXTENSIONS = []
14 INDEX_EXTENSIONS = []
15
15
16 # additional extensions for indexing besides the default from pygments
16 # additional extensions for indexing besides the default from pygments
17 # those get's added to INDEX_EXTENSIONS
17 # those get's added to INDEX_EXTENSIONS
18 EXTRA_INDEX_EXTENSIONS = []
18 EXTRA_INDEX_EXTENSIONS = []
19
19
20
20
21 #==============================================================================
21 #==============================================================================
22 # POST CREATE REPOSITORY HOOK
22 # POST CREATE REPOSITORY HOOK
23 #==============================================================================
23 #==============================================================================
24 # this function will be executed after each repository is created
24 # this function will be executed after each repository is created
25 def _crhook(*args, **kwargs):
25 def _crhook(*args, **kwargs):
26 """
26 """
27 Post create repository HOOK
27 Post create repository HOOK
28 kwargs available:
28 kwargs available:
29 :param repo_name:
29 :param repo_name:
30 :param repo_type:
30 :param repo_type:
31 :param description:
31 :param description:
32 :param private:
32 :param private:
33 :param created_on:
33 :param created_on:
34 :param enable_downloads:
34 :param enable_downloads:
35 :param repo_id:
35 :param repo_id:
36 :param user_id:
36 :param user_id:
37 :param enable_statistics:
37 :param enable_statistics:
38 :param clone_uri:
38 :param clone_uri:
39 :param fork_id:
39 :param fork_id:
40 :param group_id:
40 :param group_id:
41 :param created_by:
41 :param created_by:
42 """
42 """
43
44 return 0
43 return 0
45 CREATE_REPO_HOOK = _crhook
44 CREATE_REPO_HOOK = _crhook
46
45
47
46
48 #==============================================================================
47 #==============================================================================
48 # POST DELETE REPOSITORY HOOK
49 #==============================================================================
50 # this function will be executed after each repository deletion
51 def _dlhook(*args, **kwargs):
52 """
53 Post create repository HOOK
54 kwargs available:
55 :param repo_name:
56 :param repo_type:
57 :param description:
58 :param private:
59 :param created_on:
60 :param enable_downloads:
61 :param repo_id:
62 :param user_id:
63 :param enable_statistics:
64 :param clone_uri:
65 :param fork_id:
66 :param group_id:
67 :param deleted_by:
68 :param deleted_on:
69 """
70 return 0
71 DELETE_REPO_HOOK = _dlhook
72
73
74 #==============================================================================
49 # POST PUSH HOOK
75 # POST PUSH HOOK
50 #==============================================================================
76 #==============================================================================
51
77
52 # this function will be executed after each push it's runned after the build-in
78 # this function will be executed after each push it's runned after the build-in
53 # hook that rhodecode uses for logging pushes
79 # hook that rhodecode uses for logging pushes
54 def _pushhook(*args, **kwargs):
80 def _pushhook(*args, **kwargs):
55 """
81 """
56 Post push hook
82 Post push hook
57 kwargs available:
83 kwargs available:
58
84
59 :param username: name of user who pushed
85 :param username: name of user who pushed
60 :param ip: ip of who pushed
86 :param ip: ip of who pushed
61 :param action: pull
87 :param action: pull
62 :param repository: repository name
88 :param repository: repository name
63 :param pushed_revs: generator of pushed revisions
89 :param pushed_revs: generator of pushed revisions
64 """
90 """
65 return 0
91 return 0
66 PUSH_HOOK = _pushhook
92 PUSH_HOOK = _pushhook
67
93
68
94
69 #==============================================================================
95 #==============================================================================
70 # POST PULL HOOK
96 # POST PULL HOOK
71 #==============================================================================
97 #==============================================================================
72
98
73 # this function will be executed after each push it's runned after the build-in
99 # this function will be executed after each push it's runned after the build-in
74 # hook that rhodecode uses for logging pushes
100 # hook that rhodecode uses for logging pushes
75 def _pullhook(*args, **kwargs):
101 def _pullhook(*args, **kwargs):
76 """
102 """
77 Post pull hook
103 Post pull hook
78 kwargs available::
104 kwargs available::
79
105
80 :param username: name of user who pulled
106 :param username: name of user who pulled
81 :param ip: ip of who pushed
107 :param ip: ip of who pushed
82 :param action: pull
108 :param action: pull
83 :param repository: repository name
109 :param repository: repository name
84 """
110 """
85 return 0
111 return 0
86 PULL_HOOK = _pullhook
112 PULL_HOOK = _pullhook
@@ -1,191 +1,190 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.settings
3 rhodecode.controllers.settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Settings controller for rhodecode
6 Settings controller for rhodecode
7
7
8 :created_on: Jun 30, 2010
8 :created_on: Jun 30, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29
29
30 from formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from pylons import tmpl_context as c, request, url
32 from pylons import tmpl_context as c, request, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37
37
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator,\
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator,\
39 HasRepoPermissionAnyDecorator
39 HasRepoPermissionAnyDecorator
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.utils import invalidate_cache, action_logger
41 from rhodecode.lib.utils import invalidate_cache, action_logger
42
42
43 from rhodecode.model.forms import RepoSettingsForm
43 from rhodecode.model.forms import RepoSettingsForm
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.db import RepoGroup, Repository
45 from rhodecode.model.db import RepoGroup, Repository
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class SettingsController(BaseRepoController):
52 class SettingsController(BaseRepoController):
53
53
54 @LoginRequired()
54 @LoginRequired()
55 def __before__(self):
55 def __before__(self):
56 super(SettingsController, self).__before__()
56 super(SettingsController, self).__before__()
57
57
58 def __load_defaults(self):
58 def __load_defaults(self):
59 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
59 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
61
61
62 repo_model = RepoModel()
62 repo_model = RepoModel()
63 c.users_array = repo_model.get_users_js()
63 c.users_array = repo_model.get_users_js()
64 c.users_groups_array = repo_model.get_users_groups_js()
64 c.users_groups_array = repo_model.get_users_groups_js()
65 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
65 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
66 c.landing_revs_choices = choices
66 c.landing_revs_choices = choices
67
67
68 @HasRepoPermissionAllDecorator('repository.admin')
68 @HasRepoPermissionAllDecorator('repository.admin')
69 def index(self, repo_name):
69 def index(self, repo_name):
70 repo_model = RepoModel()
70 repo_model = RepoModel()
71 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
71 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
72 if not repo:
72 if not repo:
73 h.flash(_('%s repository is not mapped to db perhaps'
73 h.flash(_('%s repository is not mapped to db perhaps'
74 ' it was created or renamed from the file system'
74 ' it was created or renamed from the file system'
75 ' please run the application again'
75 ' please run the application again'
76 ' in order to rescan repositories') % repo_name,
76 ' in order to rescan repositories') % repo_name,
77 category='error')
77 category='error')
78
78
79 return redirect(url('home'))
79 return redirect(url('home'))
80
80
81 self.__load_defaults()
81 self.__load_defaults()
82
82
83 defaults = RepoModel()._get_defaults(repo_name)
83 defaults = RepoModel()._get_defaults(repo_name)
84
84
85 return htmlfill.render(
85 return htmlfill.render(
86 render('settings/repo_settings.html'),
86 render('settings/repo_settings.html'),
87 defaults=defaults,
87 defaults=defaults,
88 encoding="UTF-8",
88 encoding="UTF-8",
89 force_defaults=False
89 force_defaults=False
90 )
90 )
91
91
92 @HasRepoPermissionAllDecorator('repository.admin')
92 @HasRepoPermissionAllDecorator('repository.admin')
93 def update(self, repo_name):
93 def update(self, repo_name):
94 repo_model = RepoModel()
94 repo_model = RepoModel()
95 changed_name = repo_name
95 changed_name = repo_name
96
96
97 self.__load_defaults()
97 self.__load_defaults()
98
98
99 _form = RepoSettingsForm(edit=True,
99 _form = RepoSettingsForm(edit=True,
100 old_data={'repo_name': repo_name},
100 old_data={'repo_name': repo_name},
101 repo_groups=c.repo_groups_choices,
101 repo_groups=c.repo_groups_choices,
102 landing_revs=c.landing_revs_choices)()
102 landing_revs=c.landing_revs_choices)()
103 try:
103 try:
104 form_result = _form.to_python(dict(request.POST))
104 form_result = _form.to_python(dict(request.POST))
105
105
106 repo_model.update(repo_name, form_result)
106 repo_model.update(repo_name, form_result)
107 invalidate_cache('get_repo_cached_%s' % repo_name)
107 invalidate_cache('get_repo_cached_%s' % repo_name)
108 h.flash(_('Repository %s updated successfully') % repo_name,
108 h.flash(_('Repository %s updated successfully') % repo_name,
109 category='success')
109 category='success')
110 changed_name = form_result['repo_name_full']
110 changed_name = form_result['repo_name_full']
111 action_logger(self.rhodecode_user, 'user_updated_repo',
111 action_logger(self.rhodecode_user, 'user_updated_repo',
112 changed_name, self.ip_addr, self.sa)
112 changed_name, self.ip_addr, self.sa)
113 Session().commit()
113 Session().commit()
114 except formencode.Invalid, errors:
114 except formencode.Invalid, errors:
115 c.repo_info = repo_model.get_by_repo_name(repo_name)
115 c.repo_info = repo_model.get_by_repo_name(repo_name)
116 c.users_array = repo_model.get_users_js()
116 c.users_array = repo_model.get_users_js()
117 errors.value.update({'user': c.repo_info.user.username})
117 errors.value.update({'user': c.repo_info.user.username})
118 return htmlfill.render(
118 return htmlfill.render(
119 render('settings/repo_settings.html'),
119 render('settings/repo_settings.html'),
120 defaults=errors.value,
120 defaults=errors.value,
121 errors=errors.error_dict or {},
121 errors=errors.error_dict or {},
122 prefix_error=False,
122 prefix_error=False,
123 encoding="UTF-8")
123 encoding="UTF-8")
124 except Exception:
124 except Exception:
125 log.error(traceback.format_exc())
125 log.error(traceback.format_exc())
126 h.flash(_('error occurred during update of repository %s') \
126 h.flash(_('error occurred during update of repository %s') \
127 % repo_name, category='error')
127 % repo_name, category='error')
128
128
129 return redirect(url('repo_settings_home', repo_name=changed_name))
129 return redirect(url('repo_settings_home', repo_name=changed_name))
130
130
131 @HasRepoPermissionAllDecorator('repository.admin')
131 @HasRepoPermissionAllDecorator('repository.admin')
132 def delete(self, repo_name):
132 def delete(self, repo_name):
133 """DELETE /repos/repo_name: Delete an existing item"""
133 """DELETE /repos/repo_name: Delete an existing item"""
134 # Forms posted to this method should contain a hidden field:
134 # Forms posted to this method should contain a hidden field:
135 # <input type="hidden" name="_method" value="DELETE" />
135 # <input type="hidden" name="_method" value="DELETE" />
136 # Or using helpers:
136 # Or using helpers:
137 # h.form(url('repo_settings_delete', repo_name=ID),
137 # h.form(url('repo_settings_delete', repo_name=ID),
138 # method='delete')
138 # method='delete')
139 # url('repo_settings_delete', repo_name=ID)
139 # url('repo_settings_delete', repo_name=ID)
140
140
141 repo_model = RepoModel()
141 repo_model = RepoModel()
142 repo = repo_model.get_by_repo_name(repo_name)
142 repo = repo_model.get_by_repo_name(repo_name)
143 if not repo:
143 if not repo:
144 h.flash(_('%s repository is not mapped to db perhaps'
144 h.flash(_('%s repository is not mapped to db perhaps'
145 ' it was moved or renamed from the filesystem'
145 ' it was moved or renamed from the filesystem'
146 ' please run the application again'
146 ' please run the application again'
147 ' in order to rescan repositories') % repo_name,
147 ' in order to rescan repositories') % repo_name,
148 category='error')
148 category='error')
149
149
150 return redirect(url('home'))
150 return redirect(url('home'))
151 try:
151 try:
152 action_logger(self.rhodecode_user, 'user_deleted_repo',
152 action_logger(self.rhodecode_user, 'user_deleted_repo',
153 repo_name, self.ip_addr, self.sa)
153 repo_name, self.ip_addr, self.sa)
154 repo_model.delete(repo)
154 repo_model.delete(repo)
155 invalidate_cache('get_repo_cached_%s' % repo_name)
155 invalidate_cache('get_repo_cached_%s' % repo_name)
156 h.flash(_('deleted repository %s') % repo_name, category='success')
156 h.flash(_('deleted repository %s') % repo_name, category='success')
157 Session().commit()
157 Session().commit()
158 except Exception:
158 except Exception:
159 log.error(traceback.format_exc())
159 log.error(traceback.format_exc())
160 h.flash(_('An error occurred during deletion of %s') % repo_name,
160 h.flash(_('An error occurred during deletion of %s') % repo_name,
161 category='error')
161 category='error')
162
162
163 return redirect(url('home'))
163 return redirect(url('home'))
164
164
165 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
165 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
166 def toggle_locking(self, repo_name):
166 def toggle_locking(self, repo_name):
167 """
167 """
168 Toggle locking of repository by simple GET call to url
168 Toggle locking of repository by simple GET call to url
169
169
170 :param repo_name:
170 :param repo_name:
171 """
171 """
172
172
173 try:
173 try:
174 repo = Repository.get_by_repo_name(repo_name)
174 repo = Repository.get_by_repo_name(repo_name)
175
175
176 if repo.enable_locking:
176 if repo.enable_locking:
177 if repo.locked[0]:
177 if repo.locked[0]:
178 Repository.unlock(repo)
178 Repository.unlock(repo)
179 action = _('unlocked')
179 action = _('unlocked')
180 else:
180 else:
181 Repository.lock(repo, c.rhodecode_user.user_id)
181 Repository.lock(repo, c.rhodecode_user.user_id)
182 action = _('locked')
182 action = _('locked')
183
183
184 h.flash(_('Repository has been %s') % action,
184 h.flash(_('Repository has been %s') % action,
185 category='success')
185 category='success')
186 except Exception, e:
186 except Exception, e:
187 log.error(traceback.format_exc())
187 log.error(traceback.format_exc())
188 h.flash(_('An error occurred during unlocking'),
188 h.flash(_('An error occurred during unlocking'),
189 category='error')
189 category='error')
190 return redirect(url('summary_home', repo_name=repo_name))
190 return redirect(url('summary_home', repo_name=repo_name))
191
@@ -1,670 +1,670 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.db_manage
3 rhodecode.lib.db_manage
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database creation, and setup module for RhodeCode. Used for creation
6 Database creation, and setup module for RhodeCode. Used for creation
7 of database as well as for migration operations
7 of database as well as for migration operations
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import uuid
29 import uuid
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from rhodecode import __dbversion__, __py_version__
33 from rhodecode import __dbversion__, __py_version__
34
34
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36 from rhodecode.lib.utils import ask_ok
36 from rhodecode.lib.utils import ask_ok
37 from rhodecode.model import init_model
37 from rhodecode.model import init_model
38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
40 UserRepoGroupToPerm
40 UserRepoGroupToPerm
41
41
42 from sqlalchemy.engine import create_engine
42 from sqlalchemy.engine import create_engine
43 from rhodecode.model.repos_group import ReposGroupModel
43 from rhodecode.model.repos_group import ReposGroupModel
44 #from rhodecode.model import meta
44 #from rhodecode.model import meta
45 from rhodecode.model.meta import Session, Base
45 from rhodecode.model.meta import Session, Base
46
46
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 def notify(msg):
51 def notify(msg):
52 """
52 """
53 Notification for migrations messages
53 Notification for migrations messages
54 """
54 """
55 ml = len(msg) + (4 * 2)
55 ml = len(msg) + (4 * 2)
56 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
56 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
57
57
58
58
59 class DbManage(object):
59 class DbManage(object):
60 def __init__(self, log_sql, dbconf, root, tests=False):
60 def __init__(self, log_sql, dbconf, root, tests=False):
61 self.dbname = dbconf.split('/')[-1]
61 self.dbname = dbconf.split('/')[-1]
62 self.tests = tests
62 self.tests = tests
63 self.root = root
63 self.root = root
64 self.dburi = dbconf
64 self.dburi = dbconf
65 self.log_sql = log_sql
65 self.log_sql = log_sql
66 self.db_exists = False
66 self.db_exists = False
67 self.init_db()
67 self.init_db()
68
68
69 def init_db(self):
69 def init_db(self):
70 engine = create_engine(self.dburi, echo=self.log_sql)
70 engine = create_engine(self.dburi, echo=self.log_sql)
71 init_model(engine)
71 init_model(engine)
72 self.sa = Session()
72 self.sa = Session()
73
73
74 def create_tables(self, override=False, defaults={}):
74 def create_tables(self, override=False, defaults={}):
75 """
75 """
76 Create a auth database
76 Create a auth database
77 """
77 """
78 quiet = defaults.get('quiet')
78 quiet = defaults.get('quiet')
79 log.info("Any existing database is going to be destroyed")
79 log.info("Any existing database is going to be destroyed")
80 if self.tests or quiet:
80 if self.tests or quiet:
81 destroy = True
81 destroy = True
82 else:
82 else:
83 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
83 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
84 if not destroy:
84 if not destroy:
85 sys.exit()
85 sys.exit()
86 if destroy:
86 if destroy:
87 Base.metadata.drop_all()
87 Base.metadata.drop_all()
88
88
89 checkfirst = not override
89 checkfirst = not override
90 Base.metadata.create_all(checkfirst=checkfirst)
90 Base.metadata.create_all(checkfirst=checkfirst)
91 log.info('Created tables for %s' % self.dbname)
91 log.info('Created tables for %s' % self.dbname)
92
92
93 def set_db_version(self):
93 def set_db_version(self):
94 ver = DbMigrateVersion()
94 ver = DbMigrateVersion()
95 ver.version = __dbversion__
95 ver.version = __dbversion__
96 ver.repository_id = 'rhodecode_db_migrations'
96 ver.repository_id = 'rhodecode_db_migrations'
97 ver.repository_path = 'versions'
97 ver.repository_path = 'versions'
98 self.sa.add(ver)
98 self.sa.add(ver)
99 log.info('db version set to: %s' % __dbversion__)
99 log.info('db version set to: %s' % __dbversion__)
100
100
101 def upgrade(self):
101 def upgrade(self):
102 """
102 """
103 Upgrades given database schema to given revision following
103 Upgrades given database schema to given revision following
104 all needed steps, to perform the upgrade
104 all needed steps, to perform the upgrade
105
105
106 """
106 """
107
107
108 from rhodecode.lib.dbmigrate.migrate.versioning import api
108 from rhodecode.lib.dbmigrate.migrate.versioning import api
109 from rhodecode.lib.dbmigrate.migrate.exceptions import \
109 from rhodecode.lib.dbmigrate.migrate.exceptions import \
110 DatabaseNotControlledError
110 DatabaseNotControlledError
111
111
112 if 'sqlite' in self.dburi:
112 if 'sqlite' in self.dburi:
113 print (
113 print (
114 '********************** WARNING **********************\n'
114 '********************** WARNING **********************\n'
115 'Make sure your version of sqlite is at least 3.7.X. \n'
115 'Make sure your version of sqlite is at least 3.7.X. \n'
116 'Earlier versions are known to fail on some migrations\n'
116 'Earlier versions are known to fail on some migrations\n'
117 '*****************************************************\n'
117 '*****************************************************\n'
118 )
118 )
119 upgrade = ask_ok('You are about to perform database upgrade, make '
119 upgrade = ask_ok('You are about to perform database upgrade, make '
120 'sure You backed up your database before. '
120 'sure You backed up your database before. '
121 'Continue ? [y/n]')
121 'Continue ? [y/n]')
122 if not upgrade:
122 if not upgrade:
123 sys.exit('Nothing done')
123 sys.exit('Nothing done')
124
124
125 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
125 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
126 'rhodecode/lib/dbmigrate')
126 'rhodecode/lib/dbmigrate')
127 db_uri = self.dburi
127 db_uri = self.dburi
128
128
129 try:
129 try:
130 curr_version = api.db_version(db_uri, repository_path)
130 curr_version = api.db_version(db_uri, repository_path)
131 msg = ('Found current database under version'
131 msg = ('Found current database under version'
132 ' control with version %s' % curr_version)
132 ' control with version %s' % curr_version)
133
133
134 except (RuntimeError, DatabaseNotControlledError):
134 except (RuntimeError, DatabaseNotControlledError):
135 curr_version = 1
135 curr_version = 1
136 msg = ('Current database is not under version control. Setting'
136 msg = ('Current database is not under version control. Setting'
137 ' as version %s' % curr_version)
137 ' as version %s' % curr_version)
138 api.version_control(db_uri, repository_path, curr_version)
138 api.version_control(db_uri, repository_path, curr_version)
139
139
140 notify(msg)
140 notify(msg)
141
141
142 if curr_version == __dbversion__:
142 if curr_version == __dbversion__:
143 sys.exit('This database is already at the newest version')
143 sys.exit('This database is already at the newest version')
144
144
145 #======================================================================
145 #======================================================================
146 # UPGRADE STEPS
146 # UPGRADE STEPS
147 #======================================================================
147 #======================================================================
148
148
149 class UpgradeSteps(object):
149 class UpgradeSteps(object):
150 """
150 """
151 Those steps follow schema versions so for example schema
151 Those steps follow schema versions so for example schema
152 for example schema with seq 002 == step_2 and so on.
152 for example schema with seq 002 == step_2 and so on.
153 """
153 """
154
154
155 def __init__(self, klass):
155 def __init__(self, klass):
156 self.klass = klass
156 self.klass = klass
157
157
158 def step_0(self):
158 def step_0(self):
159 # step 0 is the schema upgrade, and than follow proper upgrades
159 # step 0 is the schema upgrade, and than follow proper upgrades
160 notify('attempting to do database upgrade to version %s' \
160 notify('attempting to do database upgrade to version %s' \
161 % __dbversion__)
161 % __dbversion__)
162 api.upgrade(db_uri, repository_path, __dbversion__)
162 api.upgrade(db_uri, repository_path, __dbversion__)
163 notify('Schema upgrade completed')
163 notify('Schema upgrade completed')
164
164
165 def step_1(self):
165 def step_1(self):
166 pass
166 pass
167
167
168 def step_2(self):
168 def step_2(self):
169 notify('Patching repo paths for newer version of RhodeCode')
169 notify('Patching repo paths for newer version of RhodeCode')
170 self.klass.fix_repo_paths()
170 self.klass.fix_repo_paths()
171
171
172 notify('Patching default user of RhodeCode')
172 notify('Patching default user of RhodeCode')
173 self.klass.fix_default_user()
173 self.klass.fix_default_user()
174
174
175 log.info('Changing ui settings')
175 log.info('Changing ui settings')
176 self.klass.create_ui_settings()
176 self.klass.create_ui_settings()
177
177
178 def step_3(self):
178 def step_3(self):
179 notify('Adding additional settings into RhodeCode db')
179 notify('Adding additional settings into RhodeCode db')
180 self.klass.fix_settings()
180 self.klass.fix_settings()
181 notify('Adding ldap defaults')
181 notify('Adding ldap defaults')
182 self.klass.create_ldap_options(skip_existing=True)
182 self.klass.create_ldap_options(skip_existing=True)
183
183
184 def step_4(self):
184 def step_4(self):
185 notify('create permissions and fix groups')
185 notify('create permissions and fix groups')
186 self.klass.create_permissions()
186 self.klass.create_permissions()
187 self.klass.fixup_groups()
187 self.klass.fixup_groups()
188
188
189 def step_5(self):
189 def step_5(self):
190 pass
190 pass
191
191
192 def step_6(self):
192 def step_6(self):
193
193
194 notify('re-checking permissions')
194 notify('re-checking permissions')
195 self.klass.create_permissions()
195 self.klass.create_permissions()
196
196
197 notify('installing new UI options')
197 notify('installing new UI options')
198 sett4 = RhodeCodeSetting('show_public_icon', True)
198 sett4 = RhodeCodeSetting('show_public_icon', True)
199 Session().add(sett4)
199 Session().add(sett4)
200 sett5 = RhodeCodeSetting('show_private_icon', True)
200 sett5 = RhodeCodeSetting('show_private_icon', True)
201 Session().add(sett5)
201 Session().add(sett5)
202 sett6 = RhodeCodeSetting('stylify_metatags', False)
202 sett6 = RhodeCodeSetting('stylify_metatags', False)
203 Session().add(sett6)
203 Session().add(sett6)
204
204
205 notify('fixing old PULL hook')
205 notify('fixing old PULL hook')
206 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
206 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
207 if _pull:
207 if _pull:
208 _pull.ui_key = RhodeCodeUi.HOOK_PULL
208 _pull.ui_key = RhodeCodeUi.HOOK_PULL
209 Session().add(_pull)
209 Session().add(_pull)
210
210
211 notify('fixing old PUSH hook')
211 notify('fixing old PUSH hook')
212 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
212 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
213 if _push:
213 if _push:
214 _push.ui_key = RhodeCodeUi.HOOK_PUSH
214 _push.ui_key = RhodeCodeUi.HOOK_PUSH
215 Session().add(_push)
215 Session().add(_push)
216
216
217 notify('installing new pre-push hook')
217 notify('installing new pre-push hook')
218 hooks4 = RhodeCodeUi()
218 hooks4 = RhodeCodeUi()
219 hooks4.ui_section = 'hooks'
219 hooks4.ui_section = 'hooks'
220 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
220 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
221 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
221 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
222 Session().add(hooks4)
222 Session().add(hooks4)
223
223
224 notify('installing new pre-pull hook')
224 notify('installing new pre-pull hook')
225 hooks6 = RhodeCodeUi()
225 hooks6 = RhodeCodeUi()
226 hooks6.ui_section = 'hooks'
226 hooks6.ui_section = 'hooks'
227 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
227 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
228 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
228 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
229 Session().add(hooks6)
229 Session().add(hooks6)
230
230
231 notify('installing hgsubversion option')
231 notify('installing hgsubversion option')
232 # enable hgsubversion disabled by default
232 # enable hgsubversion disabled by default
233 hgsubversion = RhodeCodeUi()
233 hgsubversion = RhodeCodeUi()
234 hgsubversion.ui_section = 'extensions'
234 hgsubversion.ui_section = 'extensions'
235 hgsubversion.ui_key = 'hgsubversion'
235 hgsubversion.ui_key = 'hgsubversion'
236 hgsubversion.ui_value = ''
236 hgsubversion.ui_value = ''
237 hgsubversion.ui_active = False
237 hgsubversion.ui_active = False
238 Session().add(hgsubversion)
238 Session().add(hgsubversion)
239
239
240 notify('installing hg git option')
240 notify('installing hg git option')
241 # enable hggit disabled by default
241 # enable hggit disabled by default
242 hggit = RhodeCodeUi()
242 hggit = RhodeCodeUi()
243 hggit.ui_section = 'extensions'
243 hggit.ui_section = 'extensions'
244 hggit.ui_key = 'hggit'
244 hggit.ui_key = 'hggit'
245 hggit.ui_value = ''
245 hggit.ui_value = ''
246 hggit.ui_active = False
246 hggit.ui_active = False
247 Session().add(hggit)
247 Session().add(hggit)
248
248
249 notify('re-check default permissions')
249 notify('re-check default permissions')
250 default_user = User.get_by_username(User.DEFAULT_USER)
250 default_user = User.get_by_username(User.DEFAULT_USER)
251 perm = Permission.get_by_key('hg.fork.repository')
251 perm = Permission.get_by_key('hg.fork.repository')
252 reg_perm = UserToPerm()
252 reg_perm = UserToPerm()
253 reg_perm.user = default_user
253 reg_perm.user = default_user
254 reg_perm.permission = perm
254 reg_perm.permission = perm
255 Session().add(reg_perm)
255 Session().add(reg_perm)
256
256
257 def step_7(self):
257 def step_7(self):
258 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
258 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
259 Session().commit()
259 Session().commit()
260 if perm_fixes:
260 if perm_fixes:
261 notify('There was an inconsistent state of permissions '
261 notify('There was an inconsistent state of permissions '
262 'detected for default user. Permissions are now '
262 'detected for default user. Permissions are now '
263 'reset to the default value for default user. '
263 'reset to the default value for default user. '
264 'Please validate and check default permissions '
264 'Please validate and check default permissions '
265 'in admin panel')
265 'in admin panel')
266
266
267 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
267 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
268
268
269 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
269 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
270 _step = None
270 _step = None
271 for step in upgrade_steps:
271 for step in upgrade_steps:
272 notify('performing upgrade step %s' % step)
272 notify('performing upgrade step %s' % step)
273 getattr(UpgradeSteps(self), 'step_%s' % step)()
273 getattr(UpgradeSteps(self), 'step_%s' % step)()
274 self.sa.commit()
274 self.sa.commit()
275 _step = step
275 _step = step
276
276
277 notify('upgrade to version %s successful' % _step)
277 notify('upgrade to version %s successful' % _step)
278
278
279 def fix_repo_paths(self):
279 def fix_repo_paths(self):
280 """
280 """
281 Fixes a old rhodecode version path into new one without a '*'
281 Fixes a old rhodecode version path into new one without a '*'
282 """
282 """
283
283
284 paths = self.sa.query(RhodeCodeUi)\
284 paths = self.sa.query(RhodeCodeUi)\
285 .filter(RhodeCodeUi.ui_key == '/')\
285 .filter(RhodeCodeUi.ui_key == '/')\
286 .scalar()
286 .scalar()
287
287
288 paths.ui_value = paths.ui_value.replace('*', '')
288 paths.ui_value = paths.ui_value.replace('*', '')
289
289
290 try:
290 try:
291 self.sa.add(paths)
291 self.sa.add(paths)
292 self.sa.commit()
292 self.sa.commit()
293 except:
293 except:
294 self.sa.rollback()
294 self.sa.rollback()
295 raise
295 raise
296
296
297 def fix_default_user(self):
297 def fix_default_user(self):
298 """
298 """
299 Fixes a old default user with some 'nicer' default values,
299 Fixes a old default user with some 'nicer' default values,
300 used mostly for anonymous access
300 used mostly for anonymous access
301 """
301 """
302 def_user = self.sa.query(User)\
302 def_user = self.sa.query(User)\
303 .filter(User.username == 'default')\
303 .filter(User.username == 'default')\
304 .one()
304 .one()
305
305
306 def_user.name = 'Anonymous'
306 def_user.name = 'Anonymous'
307 def_user.lastname = 'User'
307 def_user.lastname = 'User'
308 def_user.email = 'anonymous@rhodecode.org'
308 def_user.email = 'anonymous@rhodecode.org'
309
309
310 try:
310 try:
311 self.sa.add(def_user)
311 self.sa.add(def_user)
312 self.sa.commit()
312 self.sa.commit()
313 except:
313 except:
314 self.sa.rollback()
314 self.sa.rollback()
315 raise
315 raise
316
316
317 def fix_settings(self):
317 def fix_settings(self):
318 """
318 """
319 Fixes rhodecode settings adds ga_code key for google analytics
319 Fixes rhodecode settings adds ga_code key for google analytics
320 """
320 """
321
321
322 hgsettings3 = RhodeCodeSetting('ga_code', '')
322 hgsettings3 = RhodeCodeSetting('ga_code', '')
323
323
324 try:
324 try:
325 self.sa.add(hgsettings3)
325 self.sa.add(hgsettings3)
326 self.sa.commit()
326 self.sa.commit()
327 except:
327 except:
328 self.sa.rollback()
328 self.sa.rollback()
329 raise
329 raise
330
330
331 def admin_prompt(self, second=False, defaults={}):
331 def admin_prompt(self, second=False, defaults={}):
332 if not self.tests:
332 if not self.tests:
333 import getpass
333 import getpass
334
334
335 # defaults
335 # defaults
336 username = defaults.get('username')
336 username = defaults.get('username')
337 password = defaults.get('password')
337 password = defaults.get('password')
338 email = defaults.get('email')
338 email = defaults.get('email')
339
339
340 def get_password():
340 def get_password():
341 password = getpass.getpass('Specify admin password '
341 password = getpass.getpass('Specify admin password '
342 '(min 6 chars):')
342 '(min 6 chars):')
343 confirm = getpass.getpass('Confirm password:')
343 confirm = getpass.getpass('Confirm password:')
344
344
345 if password != confirm:
345 if password != confirm:
346 log.error('passwords mismatch')
346 log.error('passwords mismatch')
347 return False
347 return False
348 if len(password) < 6:
348 if len(password) < 6:
349 log.error('password is to short use at least 6 characters')
349 log.error('password is to short use at least 6 characters')
350 return False
350 return False
351
351
352 return password
352 return password
353 if username is None:
353 if username is None:
354 username = raw_input('Specify admin username:')
354 username = raw_input('Specify admin username:')
355 if password is None:
355 if password is None:
356 password = get_password()
356 password = get_password()
357 if not password:
357 if not password:
358 #second try
358 #second try
359 password = get_password()
359 password = get_password()
360 if not password:
360 if not password:
361 sys.exit()
361 sys.exit()
362 if email is None:
362 if email is None:
363 email = raw_input('Specify admin email:')
363 email = raw_input('Specify admin email:')
364 self.create_user(username, password, email, True)
364 self.create_user(username, password, email, True)
365 else:
365 else:
366 log.info('creating admin and regular test users')
366 log.info('creating admin and regular test users')
367 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
367 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
368 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
368 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
369 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
369 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
370 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
370 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
371 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
371 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
372
372
373 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
373 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
374 TEST_USER_ADMIN_EMAIL, True)
374 TEST_USER_ADMIN_EMAIL, True)
375
375
376 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
376 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
377 TEST_USER_REGULAR_EMAIL, False)
377 TEST_USER_REGULAR_EMAIL, False)
378
378
379 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
379 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
380 TEST_USER_REGULAR2_EMAIL, False)
380 TEST_USER_REGULAR2_EMAIL, False)
381
381
382 def create_ui_settings(self):
382 def create_ui_settings(self):
383 """
383 """
384 Creates ui settings, fills out hooks
384 Creates ui settings, fills out hooks
385 and disables dotencode
385 and disables dotencode
386 """
386 """
387
387
388 #HOOKS
388 #HOOKS
389 hooks1_key = RhodeCodeUi.HOOK_UPDATE
389 hooks1_key = RhodeCodeUi.HOOK_UPDATE
390 hooks1_ = self.sa.query(RhodeCodeUi)\
390 hooks1_ = self.sa.query(RhodeCodeUi)\
391 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
391 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
392
392
393 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
393 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
394 hooks1.ui_section = 'hooks'
394 hooks1.ui_section = 'hooks'
395 hooks1.ui_key = hooks1_key
395 hooks1.ui_key = hooks1_key
396 hooks1.ui_value = 'hg update >&2'
396 hooks1.ui_value = 'hg update >&2'
397 hooks1.ui_active = False
397 hooks1.ui_active = False
398 self.sa.add(hooks1)
398 self.sa.add(hooks1)
399
399
400 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
400 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
401 hooks2_ = self.sa.query(RhodeCodeUi)\
401 hooks2_ = self.sa.query(RhodeCodeUi)\
402 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
402 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
403 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
403 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
404 hooks2.ui_section = 'hooks'
404 hooks2.ui_section = 'hooks'
405 hooks2.ui_key = hooks2_key
405 hooks2.ui_key = hooks2_key
406 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
406 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
407 self.sa.add(hooks2)
407 self.sa.add(hooks2)
408
408
409 hooks3 = RhodeCodeUi()
409 hooks3 = RhodeCodeUi()
410 hooks3.ui_section = 'hooks'
410 hooks3.ui_section = 'hooks'
411 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
411 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
412 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
412 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
413 self.sa.add(hooks3)
413 self.sa.add(hooks3)
414
414
415 hooks4 = RhodeCodeUi()
415 hooks4 = RhodeCodeUi()
416 hooks4.ui_section = 'hooks'
416 hooks4.ui_section = 'hooks'
417 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
417 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
418 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
418 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
419 self.sa.add(hooks4)
419 self.sa.add(hooks4)
420
420
421 hooks5 = RhodeCodeUi()
421 hooks5 = RhodeCodeUi()
422 hooks5.ui_section = 'hooks'
422 hooks5.ui_section = 'hooks'
423 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
423 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
424 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
424 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
425 self.sa.add(hooks5)
425 self.sa.add(hooks5)
426
426
427 hooks6 = RhodeCodeUi()
427 hooks6 = RhodeCodeUi()
428 hooks6.ui_section = 'hooks'
428 hooks6.ui_section = 'hooks'
429 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
429 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
430 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
430 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
431 self.sa.add(hooks6)
431 self.sa.add(hooks6)
432
432
433 # enable largefiles
433 # enable largefiles
434 largefiles = RhodeCodeUi()
434 largefiles = RhodeCodeUi()
435 largefiles.ui_section = 'extensions'
435 largefiles.ui_section = 'extensions'
436 largefiles.ui_key = 'largefiles'
436 largefiles.ui_key = 'largefiles'
437 largefiles.ui_value = ''
437 largefiles.ui_value = ''
438 self.sa.add(largefiles)
438 self.sa.add(largefiles)
439
439
440 # enable hgsubversion disabled by default
440 # enable hgsubversion disabled by default
441 hgsubversion = RhodeCodeUi()
441 hgsubversion = RhodeCodeUi()
442 hgsubversion.ui_section = 'extensions'
442 hgsubversion.ui_section = 'extensions'
443 hgsubversion.ui_key = 'hgsubversion'
443 hgsubversion.ui_key = 'hgsubversion'
444 hgsubversion.ui_value = ''
444 hgsubversion.ui_value = ''
445 hgsubversion.ui_active = False
445 hgsubversion.ui_active = False
446 self.sa.add(hgsubversion)
446 self.sa.add(hgsubversion)
447
447
448 # enable hggit disabled by default
448 # enable hggit disabled by default
449 hggit = RhodeCodeUi()
449 hggit = RhodeCodeUi()
450 hggit.ui_section = 'extensions'
450 hggit.ui_section = 'extensions'
451 hggit.ui_key = 'hggit'
451 hggit.ui_key = 'hggit'
452 hggit.ui_value = ''
452 hggit.ui_value = ''
453 hggit.ui_active = False
453 hggit.ui_active = False
454 self.sa.add(hggit)
454 self.sa.add(hggit)
455
455
456 def create_ldap_options(self, skip_existing=False):
456 def create_ldap_options(self, skip_existing=False):
457 """Creates ldap settings"""
457 """Creates ldap settings"""
458
458
459 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
459 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
460 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
460 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
461 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
461 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
462 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
462 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
463 ('ldap_filter', ''), ('ldap_search_scope', ''),
463 ('ldap_filter', ''), ('ldap_search_scope', ''),
464 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
464 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
465 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
465 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
466
466
467 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
467 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
468 log.debug('Skipping option %s' % k)
468 log.debug('Skipping option %s' % k)
469 continue
469 continue
470 setting = RhodeCodeSetting(k, v)
470 setting = RhodeCodeSetting(k, v)
471 self.sa.add(setting)
471 self.sa.add(setting)
472
472
473 def fixup_groups(self):
473 def fixup_groups(self):
474 def_usr = User.get_by_username('default')
474 def_usr = User.get_by_username('default')
475 for g in RepoGroup.query().all():
475 for g in RepoGroup.query().all():
476 g.group_name = g.get_new_name(g.name)
476 g.group_name = g.get_new_name(g.name)
477 self.sa.add(g)
477 self.sa.add(g)
478 # get default perm
478 # get default perm
479 default = UserRepoGroupToPerm.query()\
479 default = UserRepoGroupToPerm.query()\
480 .filter(UserRepoGroupToPerm.group == g)\
480 .filter(UserRepoGroupToPerm.group == g)\
481 .filter(UserRepoGroupToPerm.user == def_usr)\
481 .filter(UserRepoGroupToPerm.user == def_usr)\
482 .scalar()
482 .scalar()
483
483
484 if default is None:
484 if default is None:
485 log.debug('missing default permission for group %s adding' % g)
485 log.debug('missing default permission for group %s adding' % g)
486 ReposGroupModel()._create_default_perms(g)
486 ReposGroupModel()._create_default_perms(g)
487
487
488 def reset_permissions(self, username):
488 def reset_permissions(self, username):
489 """
489 """
490 Resets permissions to default state, usefull when old systems had
490 Resets permissions to default state, usefull when old systems had
491 bad permissions, we must clean them up
491 bad permissions, we must clean them up
492
492
493 :param username:
493 :param username:
494 :type username:
494 :type username:
495 """
495 """
496 default_user = User.get_by_username(username)
496 default_user = User.get_by_username(username)
497 if not default_user:
497 if not default_user:
498 return
498 return
499
499
500 u2p = UserToPerm.query()\
500 u2p = UserToPerm.query()\
501 .filter(UserToPerm.user == default_user).all()
501 .filter(UserToPerm.user == default_user).all()
502 fixed = False
502 fixed = False
503 if len(u2p) != len(User.DEFAULT_PERMISSIONS):
503 if len(u2p) != len(User.DEFAULT_PERMISSIONS):
504 for p in u2p:
504 for p in u2p:
505 Session().delete(p)
505 Session().delete(p)
506 fixed = True
506 fixed = True
507 self.populate_default_permissions()
507 self.populate_default_permissions()
508 return fixed
508 return fixed
509
509
510 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
510 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
511 _path = defaults.get('repos_location')
511 _path = defaults.get('repos_location')
512 if retries == 3:
512 if retries == 3:
513 log.info('Setting up repositories config')
513 log.info('Setting up repositories config')
514
514
515 if _path is not None:
515 if _path is not None:
516 path = _path
516 path = _path
517 elif not self.tests and not test_repo_path:
517 elif not self.tests and not test_repo_path:
518 path = raw_input(
518 path = raw_input(
519 'Enter a valid absolute path to store repositories. '
519 'Enter a valid absolute path to store repositories. '
520 'All repositories in that path will be added automatically:'
520 'All repositories in that path will be added automatically:'
521 )
521 )
522 else:
522 else:
523 path = test_repo_path
523 path = test_repo_path
524 path_ok = True
524 path_ok = True
525
525
526 # check proper dir
526 # check proper dir
527 if not os.path.isdir(path):
527 if not os.path.isdir(path):
528 path_ok = False
528 path_ok = False
529 log.error('Given path %s is not a valid directory' % path)
529 log.error('Given path %s is not a valid directory' % path)
530
530
531 elif not os.path.isabs(path):
531 elif not os.path.isabs(path):
532 path_ok = False
532 path_ok = False
533 log.error('Given path %s is not an absolute path' % path)
533 log.error('Given path %s is not an absolute path' % path)
534
534
535 # check write access
535 # check write access
536 elif not os.access(path, os.W_OK) and path_ok:
536 elif not os.access(path, os.W_OK) and path_ok:
537 path_ok = False
537 path_ok = False
538 log.error('No write permission to given path %s' % path)
538 log.error('No write permission to given path %s' % path)
539
539
540 if retries == 0:
540 if retries == 0:
541 sys.exit('max retries reached')
541 sys.exit('max retries reached')
542 if path_ok is False:
542 if path_ok is False:
543 retries -= 1
543 retries -= 1
544 return self.config_prompt(test_repo_path, retries)
544 return self.config_prompt(test_repo_path, retries)
545
545
546 real_path = os.path.realpath(path)
546 real_path = os.path.realpath(path)
547
547
548 if real_path != path:
548 if real_path != path:
549 if not ask_ok(('Path looks like a symlink, Rhodecode will store '
549 if not ask_ok(('Path looks like a symlink, Rhodecode will store '
550 'given path as %s ? [y/n]') % (real_path)):
550 'given path as %s ? [y/n]') % (real_path)):
551 log.error('Canceled by user')
551 log.error('Canceled by user')
552 sys.exit(-1)
552 sys.exit(-1)
553
553
554 return real_path
554 return real_path
555
555
556 def create_settings(self, path):
556 def create_settings(self, path):
557
557
558 self.create_ui_settings()
558 self.create_ui_settings()
559
559
560 #HG UI OPTIONS
560 #HG UI OPTIONS
561 web1 = RhodeCodeUi()
561 web1 = RhodeCodeUi()
562 web1.ui_section = 'web'
562 web1.ui_section = 'web'
563 web1.ui_key = 'push_ssl'
563 web1.ui_key = 'push_ssl'
564 web1.ui_value = 'false'
564 web1.ui_value = 'false'
565
565
566 web2 = RhodeCodeUi()
566 web2 = RhodeCodeUi()
567 web2.ui_section = 'web'
567 web2.ui_section = 'web'
568 web2.ui_key = 'allow_archive'
568 web2.ui_key = 'allow_archive'
569 web2.ui_value = 'gz zip bz2'
569 web2.ui_value = 'gz zip bz2'
570
570
571 web3 = RhodeCodeUi()
571 web3 = RhodeCodeUi()
572 web3.ui_section = 'web'
572 web3.ui_section = 'web'
573 web3.ui_key = 'allow_push'
573 web3.ui_key = 'allow_push'
574 web3.ui_value = '*'
574 web3.ui_value = '*'
575
575
576 web4 = RhodeCodeUi()
576 web4 = RhodeCodeUi()
577 web4.ui_section = 'web'
577 web4.ui_section = 'web'
578 web4.ui_key = 'baseurl'
578 web4.ui_key = 'baseurl'
579 web4.ui_value = '/'
579 web4.ui_value = '/'
580
580
581 paths = RhodeCodeUi()
581 paths = RhodeCodeUi()
582 paths.ui_section = 'paths'
582 paths.ui_section = 'paths'
583 paths.ui_key = '/'
583 paths.ui_key = '/'
584 paths.ui_value = path
584 paths.ui_value = path
585
585
586 phases = RhodeCodeUi()
586 phases = RhodeCodeUi()
587 phases.ui_section = 'phases'
587 phases.ui_section = 'phases'
588 phases.ui_key = 'publish'
588 phases.ui_key = 'publish'
589 phases.ui_value = False
589 phases.ui_value = False
590
590
591 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
591 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
592 sett2 = RhodeCodeSetting('title', 'RhodeCode')
592 sett2 = RhodeCodeSetting('title', 'RhodeCode')
593 sett3 = RhodeCodeSetting('ga_code', '')
593 sett3 = RhodeCodeSetting('ga_code', '')
594
594
595 sett4 = RhodeCodeSetting('show_public_icon', True)
595 sett4 = RhodeCodeSetting('show_public_icon', True)
596 sett5 = RhodeCodeSetting('show_private_icon', True)
596 sett5 = RhodeCodeSetting('show_private_icon', True)
597 sett6 = RhodeCodeSetting('stylify_metatags', False)
597 sett6 = RhodeCodeSetting('stylify_metatags', False)
598
598
599 self.sa.add(web1)
599 self.sa.add(web1)
600 self.sa.add(web2)
600 self.sa.add(web2)
601 self.sa.add(web3)
601 self.sa.add(web3)
602 self.sa.add(web4)
602 self.sa.add(web4)
603 self.sa.add(paths)
603 self.sa.add(paths)
604 self.sa.add(sett1)
604 self.sa.add(sett1)
605 self.sa.add(sett2)
605 self.sa.add(sett2)
606 self.sa.add(sett3)
606 self.sa.add(sett3)
607 self.sa.add(sett4)
607 self.sa.add(sett4)
608 self.sa.add(sett5)
608 self.sa.add(sett5)
609 self.sa.add(sett6)
609 self.sa.add(sett6)
610
610
611 self.create_ldap_options()
611 self.create_ldap_options()
612
612
613 log.info('created ui config')
613 log.info('created ui config')
614
614
615 def create_user(self, username, password, email='', admin=False):
615 def create_user(self, username, password, email='', admin=False):
616 log.info('creating user %s' % username)
616 log.info('creating user %s' % username)
617 UserModel().create_or_update(username, password, email,
617 UserModel().create_or_update(username, password, email,
618 firstname='RhodeCode', lastname='Admin',
618 firstname='RhodeCode', lastname='Admin',
619 active=True, admin=admin)
619 active=True, admin=admin)
620
620
621 def create_default_user(self):
621 def create_default_user(self):
622 log.info('creating default user')
622 log.info('creating default user')
623 # create default user for handling default permissions.
623 # create default user for handling default permissions.
624 UserModel().create_or_update(username='default',
624 UserModel().create_or_update(username='default',
625 password=str(uuid.uuid1())[:8],
625 password=str(uuid.uuid1())[:8],
626 email='anonymous@rhodecode.org',
626 email='anonymous@rhodecode.org',
627 firstname='Anonymous', lastname='User')
627 firstname='Anonymous', lastname='User')
628
628
629 def create_permissions(self):
629 def create_permissions(self):
630 # module.(access|create|change|delete)_[name]
630 # module.(access|create|change|delete)_[name]
631 # module.(none|read|write|admin)
631 # module.(none|read|write|admin)
632
632
633 for p in Permission.PERMS:
633 for p in Permission.PERMS:
634 if not Permission.get_by_key(p[0]):
634 if not Permission.get_by_key(p[0]):
635 new_perm = Permission()
635 new_perm = Permission()
636 new_perm.permission_name = p[0]
636 new_perm.permission_name = p[0]
637 new_perm.permission_longname = p[0]
637 new_perm.permission_longname = p[0]
638 self.sa.add(new_perm)
638 self.sa.add(new_perm)
639
639
640 def populate_default_permissions(self):
640 def populate_default_permissions(self):
641 log.info('creating default user permissions')
641 log.info('creating default user permissions')
642
642
643 default_user = User.get_by_username('default')
643 default_user = User.get_by_username('default')
644
644
645 for def_perm in User.DEFAULT_PERMISSIONS:
645 for def_perm in User.DEFAULT_PERMISSIONS:
646
646
647 perm = self.sa.query(Permission)\
647 perm = self.sa.query(Permission)\
648 .filter(Permission.permission_name == def_perm)\
648 .filter(Permission.permission_name == def_perm)\
649 .scalar()
649 .scalar()
650 if not perm:
650 if not perm:
651 raise Exception(
651 raise Exception(
652 'CRITICAL: permission %s not found inside database !!'
652 'CRITICAL: permission %s not found inside database !!'
653 % def_perm
653 % def_perm
654 )
654 )
655 if not UserToPerm.query()\
655 if not UserToPerm.query()\
656 .filter(UserToPerm.permission == perm)\
656 .filter(UserToPerm.permission == perm)\
657 .filter(UserToPerm.user == default_user).scalar():
657 .filter(UserToPerm.user == default_user).scalar():
658 reg_perm = UserToPerm()
658 reg_perm = UserToPerm()
659 reg_perm.user = default_user
659 reg_perm.user = default_user
660 reg_perm.permission = perm
660 reg_perm.permission = perm
661 self.sa.add(reg_perm)
661 self.sa.add(reg_perm)
662
662
663 def finish(self):
663 def finish(self):
664 """
664 """
665 Function executed at the end of setup
665 Function executed at the end of setup
666 """
666 """
667 if not __py_version__ >= (2, 6):
667 if not __py_version__ >= (2, 6):
668 notify('Python2.5 detected, please switch '
668 notify('Python2.5 detected, please switch '
669 'egg:waitress#main -> egg:Paste#http '
669 'egg:waitress#main -> egg:Paste#http '
670 'in your .ini file') No newline at end of file
670 'in your .ini file')
@@ -1,382 +1,422 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.hooks
3 rhodecode.lib.hooks
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Hooks runned by rhodecode
6 Hooks runned by rhodecode
7
7
8 :created_on: Aug 6, 2010
8 :created_on: Aug 6, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import sys
26 import sys
27 import time
27 import binascii
28 import binascii
28 from inspect import isfunction
29 from inspect import isfunction
29
30
30 from mercurial.scmutil import revrange
31 from mercurial.scmutil import revrange
31 from mercurial.node import nullrev
32 from mercurial.node import nullrev
32
33
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib.utils import action_logger
35 from rhodecode.lib.utils import action_logger
35 from rhodecode.lib.vcs.backends.base import EmptyChangeset
36 from rhodecode.lib.vcs.backends.base import EmptyChangeset
36 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.exceptions import HTTPLockedRC
38 from rhodecode.lib.exceptions import HTTPLockedRC
38 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.model.db import Repository, User
40 from rhodecode.model.db import Repository, User
40
41
42
41 def _get_scm_size(alias, root_path):
43 def _get_scm_size(alias, root_path):
42
44
43 if not alias.startswith('.'):
45 if not alias.startswith('.'):
44 alias += '.'
46 alias += '.'
45
47
46 size_scm, size_root = 0, 0
48 size_scm, size_root = 0, 0
47 for path, dirs, files in os.walk(root_path):
49 for path, dirs, files in os.walk(root_path):
48 if path.find(alias) != -1:
50 if path.find(alias) != -1:
49 for f in files:
51 for f in files:
50 try:
52 try:
51 size_scm += os.path.getsize(os.path.join(path, f))
53 size_scm += os.path.getsize(os.path.join(path, f))
52 except OSError:
54 except OSError:
53 pass
55 pass
54 else:
56 else:
55 for f in files:
57 for f in files:
56 try:
58 try:
57 size_root += os.path.getsize(os.path.join(path, f))
59 size_root += os.path.getsize(os.path.join(path, f))
58 except OSError:
60 except OSError:
59 pass
61 pass
60
62
61 size_scm_f = h.format_byte_size(size_scm)
63 size_scm_f = h.format_byte_size(size_scm)
62 size_root_f = h.format_byte_size(size_root)
64 size_root_f = h.format_byte_size(size_root)
63 size_total_f = h.format_byte_size(size_root + size_scm)
65 size_total_f = h.format_byte_size(size_root + size_scm)
64
66
65 return size_scm_f, size_root_f, size_total_f
67 return size_scm_f, size_root_f, size_total_f
66
68
67
69
68 def repo_size(ui, repo, hooktype=None, **kwargs):
70 def repo_size(ui, repo, hooktype=None, **kwargs):
69 """
71 """
70 Presents size of repository after push
72 Presents size of repository after push
71
73
72 :param ui:
74 :param ui:
73 :param repo:
75 :param repo:
74 :param hooktype:
76 :param hooktype:
75 """
77 """
76
78
77 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
79 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
78
80
79 last_cs = repo[len(repo) - 1]
81 last_cs = repo[len(repo) - 1]
80
82
81 msg = ('Repository size .hg:%s repo:%s total:%s\n'
83 msg = ('Repository size .hg:%s repo:%s total:%s\n'
82 'Last revision is now r%s:%s\n') % (
84 'Last revision is now r%s:%s\n') % (
83 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
85 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
84 )
86 )
85
87
86 sys.stdout.write(msg)
88 sys.stdout.write(msg)
87
89
88
90
89 def pre_push(ui, repo, **kwargs):
91 def pre_push(ui, repo, **kwargs):
90 # pre push function, currently used to ban pushing when
92 # pre push function, currently used to ban pushing when
91 # repository is locked
93 # repository is locked
92 try:
94 try:
93 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
95 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
94 except:
96 except:
95 rc_extras = {}
97 rc_extras = {}
96 extras = dict(repo.ui.configitems('rhodecode_extras'))
98 extras = dict(repo.ui.configitems('rhodecode_extras'))
97
99
98 if 'username' in extras:
100 if 'username' in extras:
99 username = extras['username']
101 username = extras['username']
100 repository = extras['repository']
102 repository = extras['repository']
101 scm = extras['scm']
103 scm = extras['scm']
102 locked_by = extras['locked_by']
104 locked_by = extras['locked_by']
103 elif 'username' in rc_extras:
105 elif 'username' in rc_extras:
104 username = rc_extras['username']
106 username = rc_extras['username']
105 repository = rc_extras['repository']
107 repository = rc_extras['repository']
106 scm = rc_extras['scm']
108 scm = rc_extras['scm']
107 locked_by = rc_extras['locked_by']
109 locked_by = rc_extras['locked_by']
108 else:
110 else:
109 raise Exception('Missing data in repo.ui and os.environ')
111 raise Exception('Missing data in repo.ui and os.environ')
110
112
111 usr = User.get_by_username(username)
113 usr = User.get_by_username(username)
112 if locked_by[0] and usr.user_id != int(locked_by[0]):
114 if locked_by[0] and usr.user_id != int(locked_by[0]):
113 locked_by = User.get(locked_by[0]).username
115 locked_by = User.get(locked_by[0]).username
114 raise HTTPLockedRC(repository, locked_by)
116 raise HTTPLockedRC(repository, locked_by)
115
117
116
118
117 def pre_pull(ui, repo, **kwargs):
119 def pre_pull(ui, repo, **kwargs):
118 # pre push function, currently used to ban pushing when
120 # pre push function, currently used to ban pushing when
119 # repository is locked
121 # repository is locked
120 try:
122 try:
121 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
123 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
122 except:
124 except:
123 rc_extras = {}
125 rc_extras = {}
124 extras = dict(repo.ui.configitems('rhodecode_extras'))
126 extras = dict(repo.ui.configitems('rhodecode_extras'))
125 if 'username' in extras:
127 if 'username' in extras:
126 username = extras['username']
128 username = extras['username']
127 repository = extras['repository']
129 repository = extras['repository']
128 scm = extras['scm']
130 scm = extras['scm']
129 locked_by = extras['locked_by']
131 locked_by = extras['locked_by']
130 elif 'username' in rc_extras:
132 elif 'username' in rc_extras:
131 username = rc_extras['username']
133 username = rc_extras['username']
132 repository = rc_extras['repository']
134 repository = rc_extras['repository']
133 scm = rc_extras['scm']
135 scm = rc_extras['scm']
134 locked_by = rc_extras['locked_by']
136 locked_by = rc_extras['locked_by']
135 else:
137 else:
136 raise Exception('Missing data in repo.ui and os.environ')
138 raise Exception('Missing data in repo.ui and os.environ')
137
139
138 if locked_by[0]:
140 if locked_by[0]:
139 locked_by = User.get(locked_by[0]).username
141 locked_by = User.get(locked_by[0]).username
140 raise HTTPLockedRC(repository, locked_by)
142 raise HTTPLockedRC(repository, locked_by)
141
143
142
144
143 def log_pull_action(ui, repo, **kwargs):
145 def log_pull_action(ui, repo, **kwargs):
144 """
146 """
145 Logs user last pull action
147 Logs user last pull action
146
148
147 :param ui:
149 :param ui:
148 :param repo:
150 :param repo:
149 """
151 """
150 try:
152 try:
151 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
153 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
152 except:
154 except:
153 rc_extras = {}
155 rc_extras = {}
154 extras = dict(repo.ui.configitems('rhodecode_extras'))
156 extras = dict(repo.ui.configitems('rhodecode_extras'))
155 if 'username' in extras:
157 if 'username' in extras:
156 username = extras['username']
158 username = extras['username']
157 repository = extras['repository']
159 repository = extras['repository']
158 scm = extras['scm']
160 scm = extras['scm']
159 make_lock = extras['make_lock']
161 make_lock = extras['make_lock']
160 elif 'username' in rc_extras:
162 elif 'username' in rc_extras:
161 username = rc_extras['username']
163 username = rc_extras['username']
162 repository = rc_extras['repository']
164 repository = rc_extras['repository']
163 scm = rc_extras['scm']
165 scm = rc_extras['scm']
164 make_lock = rc_extras['make_lock']
166 make_lock = rc_extras['make_lock']
165 else:
167 else:
166 raise Exception('Missing data in repo.ui and os.environ')
168 raise Exception('Missing data in repo.ui and os.environ')
167 user = User.get_by_username(username)
169 user = User.get_by_username(username)
168 action = 'pull'
170 action = 'pull'
169 action_logger(user, action, repository, extras['ip'], commit=True)
171 action_logger(user, action, repository, extras['ip'], commit=True)
170 # extension hook call
172 # extension hook call
171 from rhodecode import EXTENSIONS
173 from rhodecode import EXTENSIONS
172 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
174 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
173
175
174 if isfunction(callback):
176 if isfunction(callback):
175 kw = {}
177 kw = {}
176 kw.update(extras)
178 kw.update(extras)
177 callback(**kw)
179 callback(**kw)
178
180
179 if make_lock is True:
181 if make_lock is True:
180 Repository.lock(Repository.get_by_repo_name(repository), user.user_id)
182 Repository.lock(Repository.get_by_repo_name(repository), user.user_id)
181 #msg = 'Made lock on repo `%s`' % repository
183 #msg = 'Made lock on repo `%s`' % repository
182 #sys.stdout.write(msg)
184 #sys.stdout.write(msg)
183
185
184 return 0
186 return 0
185
187
186
188
187 def log_push_action(ui, repo, **kwargs):
189 def log_push_action(ui, repo, **kwargs):
188 """
190 """
189 Maps user last push action to new changeset id, from mercurial
191 Maps user last push action to new changeset id, from mercurial
190
192
191 :param ui:
193 :param ui:
192 :param repo: repo object containing the `ui` object
194 :param repo: repo object containing the `ui` object
193 """
195 """
194
196
195 try:
197 try:
196 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
198 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
197 except:
199 except:
198 rc_extras = {}
200 rc_extras = {}
199
201
200 extras = dict(repo.ui.configitems('rhodecode_extras'))
202 extras = dict(repo.ui.configitems('rhodecode_extras'))
201 if 'username' in extras:
203 if 'username' in extras:
202 username = extras['username']
204 username = extras['username']
203 repository = extras['repository']
205 repository = extras['repository']
204 scm = extras['scm']
206 scm = extras['scm']
205 make_lock = extras['make_lock']
207 make_lock = extras['make_lock']
206 elif 'username' in rc_extras:
208 elif 'username' in rc_extras:
207 username = rc_extras['username']
209 username = rc_extras['username']
208 repository = rc_extras['repository']
210 repository = rc_extras['repository']
209 scm = rc_extras['scm']
211 scm = rc_extras['scm']
210 make_lock = rc_extras['make_lock']
212 make_lock = rc_extras['make_lock']
211 else:
213 else:
212 raise Exception('Missing data in repo.ui and os.environ')
214 raise Exception('Missing data in repo.ui and os.environ')
213
215
214 action = 'push' + ':%s'
216 action = 'push' + ':%s'
215
217
216 if scm == 'hg':
218 if scm == 'hg':
217 node = kwargs['node']
219 node = kwargs['node']
218
220
219 def get_revs(repo, rev_opt):
221 def get_revs(repo, rev_opt):
220 if rev_opt:
222 if rev_opt:
221 revs = revrange(repo, rev_opt)
223 revs = revrange(repo, rev_opt)
222
224
223 if len(revs) == 0:
225 if len(revs) == 0:
224 return (nullrev, nullrev)
226 return (nullrev, nullrev)
225 return (max(revs), min(revs))
227 return (max(revs), min(revs))
226 else:
228 else:
227 return (len(repo) - 1, 0)
229 return (len(repo) - 1, 0)
228
230
229 stop, start = get_revs(repo, [node + ':'])
231 stop, start = get_revs(repo, [node + ':'])
230 h = binascii.hexlify
232 h = binascii.hexlify
231 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
233 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
232 elif scm == 'git':
234 elif scm == 'git':
233 revs = kwargs.get('_git_revs', [])
235 revs = kwargs.get('_git_revs', [])
234 if '_git_revs' in kwargs:
236 if '_git_revs' in kwargs:
235 kwargs.pop('_git_revs')
237 kwargs.pop('_git_revs')
236
238
237 action = action % ','.join(revs)
239 action = action % ','.join(revs)
238
240
239 action_logger(username, action, repository, extras['ip'], commit=True)
241 action_logger(username, action, repository, extras['ip'], commit=True)
240
242
241 # extension hook call
243 # extension hook call
242 from rhodecode import EXTENSIONS
244 from rhodecode import EXTENSIONS
243 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
245 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
244 if isfunction(callback):
246 if isfunction(callback):
245 kw = {'pushed_revs': revs}
247 kw = {'pushed_revs': revs}
246 kw.update(extras)
248 kw.update(extras)
247 callback(**kw)
249 callback(**kw)
248
250
249 if make_lock is False:
251 if make_lock is False:
250 Repository.unlock(Repository.get_by_repo_name(repository))
252 Repository.unlock(Repository.get_by_repo_name(repository))
251 msg = 'Released lock on repo `%s`\n' % repository
253 msg = 'Released lock on repo `%s`\n' % repository
252 sys.stdout.write(msg)
254 sys.stdout.write(msg)
253
255
254 return 0
256 return 0
255
257
256
258
257 def log_create_repository(repository_dict, created_by, **kwargs):
259 def log_create_repository(repository_dict, created_by, **kwargs):
258 """
260 """
259 Post create repository Hook. This is a dummy function for admins to re-use
261 Post create repository Hook. This is a dummy function for admins to re-use
260 if needed. It's taken from rhodecode-extensions module and executed
262 if needed. It's taken from rhodecode-extensions module and executed
261 if present
263 if present
262
264
263 :param repository: dict dump of repository object
265 :param repository: dict dump of repository object
264 :param created_by: username who created repository
266 :param created_by: username who created repository
265 :param created_date: date of creation
266
267
267 available keys of repository_dict:
268 available keys of repository_dict:
268
269
269 'repo_type',
270 'repo_type',
270 'description',
271 'description',
271 'private',
272 'private',
272 'created_on',
273 'created_on',
273 'enable_downloads',
274 'enable_downloads',
274 'repo_id',
275 'repo_id',
275 'user_id',
276 'user_id',
276 'enable_statistics',
277 'enable_statistics',
277 'clone_uri',
278 'clone_uri',
278 'fork_id',
279 'fork_id',
279 'group_id',
280 'group_id',
280 'repo_name'
281 'repo_name'
281
282
282 """
283 """
283 from rhodecode import EXTENSIONS
284 from rhodecode import EXTENSIONS
284 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
285 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
285 if isfunction(callback):
286 if isfunction(callback):
286 kw = {}
287 kw = {}
287 kw.update(repository_dict)
288 kw.update(repository_dict)
288 kw.update({'created_by': created_by})
289 kw.update({'created_by': created_by})
289 kw.update(kwargs)
290 kw.update(kwargs)
290 return callback(**kw)
291 return callback(**kw)
291
292
292 return 0
293 return 0
293
294
295
296 def log_delete_repository(repository_dict, deleted_by, **kwargs):
297 """
298 Post delete repository Hook. This is a dummy function for admins to re-use
299 if needed. It's taken from rhodecode-extensions module and executed
300 if present
301
302 :param repository: dict dump of repository object
303 :param deleted_by: username who deleted the repository
304
305 available keys of repository_dict:
306
307 'repo_type',
308 'description',
309 'private',
310 'created_on',
311 'enable_downloads',
312 'repo_id',
313 'user_id',
314 'enable_statistics',
315 'clone_uri',
316 'fork_id',
317 'group_id',
318 'repo_name'
319
320 """
321 from rhodecode import EXTENSIONS
322 callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
323 if isfunction(callback):
324 kw = {}
325 kw.update(repository_dict)
326 kw.update({'deleted_by': deleted_by,
327 'deleted_on': time.time()})
328 kw.update(kwargs)
329 return callback(**kw)
330
331 return 0
332
333
294 handle_git_pre_receive = (lambda repo_path, revs, env:
334 handle_git_pre_receive = (lambda repo_path, revs, env:
295 handle_git_receive(repo_path, revs, env, hook_type='pre'))
335 handle_git_receive(repo_path, revs, env, hook_type='pre'))
296 handle_git_post_receive = (lambda repo_path, revs, env:
336 handle_git_post_receive = (lambda repo_path, revs, env:
297 handle_git_receive(repo_path, revs, env, hook_type='post'))
337 handle_git_receive(repo_path, revs, env, hook_type='post'))
298
338
299
339
300 def handle_git_receive(repo_path, revs, env, hook_type='post'):
340 def handle_git_receive(repo_path, revs, env, hook_type='post'):
301 """
341 """
302 A really hacky method that is runned by git post-receive hook and logs
342 A really hacky method that is runned by git post-receive hook and logs
303 an push action together with pushed revisions. It's executed by subprocess
343 an push action together with pushed revisions. It's executed by subprocess
304 thus needs all info to be able to create a on the fly pylons enviroment,
344 thus needs all info to be able to create a on the fly pylons enviroment,
305 connect to database and run the logging code. Hacky as sh*t but works.
345 connect to database and run the logging code. Hacky as sh*t but works.
306
346
307 :param repo_path:
347 :param repo_path:
308 :type repo_path:
348 :type repo_path:
309 :param revs:
349 :param revs:
310 :type revs:
350 :type revs:
311 :param env:
351 :param env:
312 :type env:
352 :type env:
313 """
353 """
314 from paste.deploy import appconfig
354 from paste.deploy import appconfig
315 from sqlalchemy import engine_from_config
355 from sqlalchemy import engine_from_config
316 from rhodecode.config.environment import load_environment
356 from rhodecode.config.environment import load_environment
317 from rhodecode.model import init_model
357 from rhodecode.model import init_model
318 from rhodecode.model.db import RhodeCodeUi
358 from rhodecode.model.db import RhodeCodeUi
319 from rhodecode.lib.utils import make_ui
359 from rhodecode.lib.utils import make_ui
320 extras = json.loads(env['RHODECODE_EXTRAS'])
360 extras = json.loads(env['RHODECODE_EXTRAS'])
321
361
322 path, ini_name = os.path.split(extras['config'])
362 path, ini_name = os.path.split(extras['config'])
323 conf = appconfig('config:%s' % ini_name, relative_to=path)
363 conf = appconfig('config:%s' % ini_name, relative_to=path)
324 load_environment(conf.global_conf, conf.local_conf)
364 load_environment(conf.global_conf, conf.local_conf)
325
365
326 engine = engine_from_config(conf, 'sqlalchemy.db1.')
366 engine = engine_from_config(conf, 'sqlalchemy.db1.')
327 init_model(engine)
367 init_model(engine)
328
368
329 baseui = make_ui('db')
369 baseui = make_ui('db')
330 # fix if it's not a bare repo
370 # fix if it's not a bare repo
331 if repo_path.endswith(os.sep + '.git'):
371 if repo_path.endswith(os.sep + '.git'):
332 repo_path = repo_path[:-5]
372 repo_path = repo_path[:-5]
333
373
334 repo = Repository.get_by_full_path(repo_path)
374 repo = Repository.get_by_full_path(repo_path)
335 if not repo:
375 if not repo:
336 raise OSError('Repository %s not found in database'
376 raise OSError('Repository %s not found in database'
337 % (safe_str(repo_path)))
377 % (safe_str(repo_path)))
338
378
339 _hooks = dict(baseui.configitems('hooks')) or {}
379 _hooks = dict(baseui.configitems('hooks')) or {}
340
380
341 for k, v in extras.items():
381 for k, v in extras.items():
342 baseui.setconfig('rhodecode_extras', k, v)
382 baseui.setconfig('rhodecode_extras', k, v)
343 repo = repo.scm_instance
383 repo = repo.scm_instance
344 repo.ui = baseui
384 repo.ui = baseui
345
385
346 if hook_type == 'pre':
386 if hook_type == 'pre':
347 pre_push(baseui, repo)
387 pre_push(baseui, repo)
348
388
349 # if push hook is enabled via web interface
389 # if push hook is enabled via web interface
350 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
390 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
351
391
352 rev_data = []
392 rev_data = []
353 for l in revs:
393 for l in revs:
354 old_rev, new_rev, ref = l.split(' ')
394 old_rev, new_rev, ref = l.split(' ')
355 _ref_data = ref.split('/')
395 _ref_data = ref.split('/')
356 if _ref_data[1] in ['tags', 'heads']:
396 if _ref_data[1] in ['tags', 'heads']:
357 rev_data.append({'old_rev': old_rev,
397 rev_data.append({'old_rev': old_rev,
358 'new_rev': new_rev,
398 'new_rev': new_rev,
359 'ref': ref,
399 'ref': ref,
360 'type': _ref_data[1],
400 'type': _ref_data[1],
361 'name': _ref_data[2].strip()})
401 'name': _ref_data[2].strip()})
362
402
363 git_revs = []
403 git_revs = []
364 for push_ref in rev_data:
404 for push_ref in rev_data:
365 _type = push_ref['type']
405 _type = push_ref['type']
366 if _type == 'heads':
406 if _type == 'heads':
367 if push_ref['old_rev'] == EmptyChangeset().raw_id:
407 if push_ref['old_rev'] == EmptyChangeset().raw_id:
368 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
408 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
369 heads = repo.run_git_command(cmd)[0]
409 heads = repo.run_git_command(cmd)[0]
370 heads = heads.replace(push_ref['ref'], '')
410 heads = heads.replace(push_ref['ref'], '')
371 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
411 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
372 heads.splitlines()))
412 heads.splitlines()))
373 cmd = (('log %(new_rev)s' % push_ref) +
413 cmd = (('log %(new_rev)s' % push_ref) +
374 ' --reverse --pretty=format:"%H" --not ' + heads)
414 ' --reverse --pretty=format:"%H" --not ' + heads)
375 else:
415 else:
376 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
416 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
377 ' --reverse --pretty=format:"%H"')
417 ' --reverse --pretty=format:"%H"')
378 git_revs += repo.run_git_command(cmd)[0].splitlines()
418 git_revs += repo.run_git_command(cmd)[0].splitlines()
379 elif _type == 'tags':
419 elif _type == 'tags':
380 git_revs += [push_ref['name']]
420 git_revs += [push_ref['name']]
381
421
382 log_push_action(baseui, repo, _git_revs=git_revs)
422 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,709 +1,709 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 from os.path import abspath
35 from os.path import abspath
36 from os.path import dirname as dn, join as jn
36 from os.path import dirname as dn, join as jn
37
37
38 from paste.script.command import Command, BadCommand
38 from paste.script.command import Command, BadCommand
39
39
40 from mercurial import ui, config
40 from mercurial import ui, config
41
41
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43
43
44 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs import get_backend
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.utils.helpers import get_scm
47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.exceptions import VCSError
49
49
50 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
51
51
52 from rhodecode.model import meta
52 from rhodecode.model import meta
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
55 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
56 from rhodecode.model.repos_group import ReposGroupModel
56 from rhodecode.model.repos_group import ReposGroupModel
57 from rhodecode.lib.utils2 import safe_str, safe_unicode
57 from rhodecode.lib.utils2 import safe_str, safe_unicode
58 from rhodecode.lib.vcs.utils.fakemod import create_module
58 from rhodecode.lib.vcs.utils.fakemod import create_module
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
63
63
64
64
65 def recursive_replace(str_, replace=' '):
65 def recursive_replace(str_, replace=' '):
66 """
66 """
67 Recursive replace of given sign to just one instance
67 Recursive replace of given sign to just one instance
68
68
69 :param str_: given string
69 :param str_: given string
70 :param replace: char to find and replace multiple instances
70 :param replace: char to find and replace multiple instances
71
71
72 Examples::
72 Examples::
73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
74 'Mighty-Mighty-Bo-sstones'
74 'Mighty-Mighty-Bo-sstones'
75 """
75 """
76
76
77 if str_.find(replace * 2) == -1:
77 if str_.find(replace * 2) == -1:
78 return str_
78 return str_
79 else:
79 else:
80 str_ = str_.replace(replace * 2, replace)
80 str_ = str_.replace(replace * 2, replace)
81 return recursive_replace(str_, replace)
81 return recursive_replace(str_, replace)
82
82
83
83
84 def repo_name_slug(value):
84 def repo_name_slug(value):
85 """
85 """
86 Return slug of name of repository
86 Return slug of name of repository
87 This function is called on each creation/modification
87 This function is called on each creation/modification
88 of repository to prevent bad names in repo
88 of repository to prevent bad names in repo
89 """
89 """
90
90
91 slug = remove_formatting(value)
91 slug = remove_formatting(value)
92 slug = strip_tags(slug)
92 slug = strip_tags(slug)
93
93
94 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
94 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
95 slug = slug.replace(c, '-')
95 slug = slug.replace(c, '-')
96 slug = recursive_replace(slug, '-')
96 slug = recursive_replace(slug, '-')
97 slug = collapse(slug, '-')
97 slug = collapse(slug, '-')
98 return slug
98 return slug
99
99
100
100
101 def get_repo_slug(request):
101 def get_repo_slug(request):
102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
103 if _repo:
103 if _repo:
104 _repo = _repo.rstrip('/')
104 _repo = _repo.rstrip('/')
105 return _repo
105 return _repo
106
106
107
107
108 def get_repos_group_slug(request):
108 def get_repos_group_slug(request):
109 _group = request.environ['pylons.routes_dict'].get('group_name')
109 _group = request.environ['pylons.routes_dict'].get('group_name')
110 if _group:
110 if _group:
111 _group = _group.rstrip('/')
111 _group = _group.rstrip('/')
112 return _group
112 return _group
113
113
114
114
115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
116 """
116 """
117 Action logger for various actions made by users
117 Action logger for various actions made by users
118
118
119 :param user: user that made this action, can be a unique username string or
119 :param user: user that made this action, can be a unique username string or
120 object containing user_id attribute
120 object containing user_id attribute
121 :param action: action to log, should be on of predefined unique actions for
121 :param action: action to log, should be on of predefined unique actions for
122 easy translations
122 easy translations
123 :param repo: string name of repository or object containing repo_id,
123 :param repo: string name of repository or object containing repo_id,
124 that action was made on
124 that action was made on
125 :param ipaddr: optional ip address from what the action was made
125 :param ipaddr: optional ip address from what the action was made
126 :param sa: optional sqlalchemy session
126 :param sa: optional sqlalchemy session
127
127
128 """
128 """
129
129
130 if not sa:
130 if not sa:
131 sa = meta.Session()
131 sa = meta.Session()
132
132
133 try:
133 try:
134 if hasattr(user, 'user_id'):
134 if hasattr(user, 'user_id'):
135 user_obj = user
135 user_obj = user
136 elif isinstance(user, basestring):
136 elif isinstance(user, basestring):
137 user_obj = User.get_by_username(user)
137 user_obj = User.get_by_username(user)
138 else:
138 else:
139 raise Exception('You have to provide user object or username')
139 raise Exception('You have to provide a user object or a username')
140
140
141 if hasattr(repo, 'repo_id'):
141 if hasattr(repo, 'repo_id'):
142 repo_obj = Repository.get(repo.repo_id)
142 repo_obj = Repository.get(repo.repo_id)
143 repo_name = repo_obj.repo_name
143 repo_name = repo_obj.repo_name
144 elif isinstance(repo, basestring):
144 elif isinstance(repo, basestring):
145 repo_name = repo.lstrip('/')
145 repo_name = repo.lstrip('/')
146 repo_obj = Repository.get_by_repo_name(repo_name)
146 repo_obj = Repository.get_by_repo_name(repo_name)
147 else:
147 else:
148 repo_obj = None
148 repo_obj = None
149 repo_name = ''
149 repo_name = ''
150
150
151 user_log = UserLog()
151 user_log = UserLog()
152 user_log.user_id = user_obj.user_id
152 user_log.user_id = user_obj.user_id
153 user_log.action = safe_unicode(action)
153 user_log.action = safe_unicode(action)
154
154
155 user_log.repository = repo_obj
155 user_log.repository = repo_obj
156 user_log.repository_name = repo_name
156 user_log.repository_name = repo_name
157
157
158 user_log.action_date = datetime.datetime.now()
158 user_log.action_date = datetime.datetime.now()
159 user_log.user_ip = ipaddr
159 user_log.user_ip = ipaddr
160 sa.add(user_log)
160 sa.add(user_log)
161
161
162 log.info(
162 log.info(
163 'Adding user %s, action %s on %s' % (user_obj, action,
163 'Adding user %s, action %s on %s' % (user_obj, action,
164 safe_unicode(repo))
164 safe_unicode(repo))
165 )
165 )
166 if commit:
166 if commit:
167 sa.commit()
167 sa.commit()
168 except:
168 except:
169 log.error(traceback.format_exc())
169 log.error(traceback.format_exc())
170 raise
170 raise
171
171
172
172
173 def get_repos(path, recursive=False):
173 def get_repos(path, recursive=False):
174 """
174 """
175 Scans given path for repos and return (name,(type,path)) tuple
175 Scans given path for repos and return (name,(type,path)) tuple
176
176
177 :param path: path to scan for repositories
177 :param path: path to scan for repositories
178 :param recursive: recursive search and return names with subdirs in front
178 :param recursive: recursive search and return names with subdirs in front
179 """
179 """
180
180
181 # remove ending slash for better results
181 # remove ending slash for better results
182 path = path.rstrip(os.sep)
182 path = path.rstrip(os.sep)
183
183
184 def _get_repos(p):
184 def _get_repos(p):
185 if not os.access(p, os.W_OK):
185 if not os.access(p, os.W_OK):
186 return
186 return
187 for dirpath in os.listdir(p):
187 for dirpath in os.listdir(p):
188 if os.path.isfile(os.path.join(p, dirpath)):
188 if os.path.isfile(os.path.join(p, dirpath)):
189 continue
189 continue
190 cur_path = os.path.join(p, dirpath)
190 cur_path = os.path.join(p, dirpath)
191 try:
191 try:
192 scm_info = get_scm(cur_path)
192 scm_info = get_scm(cur_path)
193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 except VCSError:
194 except VCSError:
195 if not recursive:
195 if not recursive:
196 continue
196 continue
197 #check if this dir containts other repos for recursive scan
197 #check if this dir containts other repos for recursive scan
198 rec_path = os.path.join(p, dirpath)
198 rec_path = os.path.join(p, dirpath)
199 if os.path.isdir(rec_path):
199 if os.path.isdir(rec_path):
200 for inner_scm in _get_repos(rec_path):
200 for inner_scm in _get_repos(rec_path):
201 yield inner_scm
201 yield inner_scm
202
202
203 return _get_repos(path)
203 return _get_repos(path)
204
204
205
205
206 def is_valid_repo(repo_name, base_path, scm=None):
206 def is_valid_repo(repo_name, base_path, scm=None):
207 """
207 """
208 Returns True if given path is a valid repository False otherwise.
208 Returns True if given path is a valid repository False otherwise.
209 If scm param is given also compare if given scm is the same as expected
209 If scm param is given also compare if given scm is the same as expected
210 from scm parameter
210 from scm parameter
211
211
212 :param repo_name:
212 :param repo_name:
213 :param base_path:
213 :param base_path:
214 :param scm:
214 :param scm:
215
215
216 :return True: if given path is a valid repository
216 :return True: if given path is a valid repository
217 """
217 """
218 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
218 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
219
219
220 try:
220 try:
221 scm_ = get_scm(full_path)
221 scm_ = get_scm(full_path)
222 if scm:
222 if scm:
223 return scm_[0] == scm
223 return scm_[0] == scm
224 return True
224 return True
225 except VCSError:
225 except VCSError:
226 return False
226 return False
227
227
228
228
229 def is_valid_repos_group(repos_group_name, base_path):
229 def is_valid_repos_group(repos_group_name, base_path):
230 """
230 """
231 Returns True if given path is a repos group False otherwise
231 Returns True if given path is a repos group False otherwise
232
232
233 :param repo_name:
233 :param repo_name:
234 :param base_path:
234 :param base_path:
235 """
235 """
236 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
236 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
237
237
238 # check if it's not a repo
238 # check if it's not a repo
239 if is_valid_repo(repos_group_name, base_path):
239 if is_valid_repo(repos_group_name, base_path):
240 return False
240 return False
241
241
242 try:
242 try:
243 # we need to check bare git repos at higher level
243 # we need to check bare git repos at higher level
244 # since we might match branches/hooks/info/objects or possible
244 # since we might match branches/hooks/info/objects or possible
245 # other things inside bare git repo
245 # other things inside bare git repo
246 get_scm(os.path.dirname(full_path))
246 get_scm(os.path.dirname(full_path))
247 return False
247 return False
248 except VCSError:
248 except VCSError:
249 pass
249 pass
250
250
251 # check if it's a valid path
251 # check if it's a valid path
252 if os.path.isdir(full_path):
252 if os.path.isdir(full_path):
253 return True
253 return True
254
254
255 return False
255 return False
256
256
257
257
258 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
258 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
259 while True:
259 while True:
260 ok = raw_input(prompt)
260 ok = raw_input(prompt)
261 if ok in ('y', 'ye', 'yes'):
261 if ok in ('y', 'ye', 'yes'):
262 return True
262 return True
263 if ok in ('n', 'no', 'nop', 'nope'):
263 if ok in ('n', 'no', 'nop', 'nope'):
264 return False
264 return False
265 retries = retries - 1
265 retries = retries - 1
266 if retries < 0:
266 if retries < 0:
267 raise IOError
267 raise IOError
268 print complaint
268 print complaint
269
269
270 #propagated from mercurial documentation
270 #propagated from mercurial documentation
271 ui_sections = ['alias', 'auth',
271 ui_sections = ['alias', 'auth',
272 'decode/encode', 'defaults',
272 'decode/encode', 'defaults',
273 'diff', 'email',
273 'diff', 'email',
274 'extensions', 'format',
274 'extensions', 'format',
275 'merge-patterns', 'merge-tools',
275 'merge-patterns', 'merge-tools',
276 'hooks', 'http_proxy',
276 'hooks', 'http_proxy',
277 'smtp', 'patch',
277 'smtp', 'patch',
278 'paths', 'profiling',
278 'paths', 'profiling',
279 'server', 'trusted',
279 'server', 'trusted',
280 'ui', 'web', ]
280 'ui', 'web', ]
281
281
282
282
283 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
283 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
284 """
284 """
285 A function that will read python rc files or database
285 A function that will read python rc files or database
286 and make an mercurial ui object from read options
286 and make an mercurial ui object from read options
287
287
288 :param path: path to mercurial config file
288 :param path: path to mercurial config file
289 :param checkpaths: check the path
289 :param checkpaths: check the path
290 :param read_from: read from 'file' or 'db'
290 :param read_from: read from 'file' or 'db'
291 """
291 """
292
292
293 baseui = ui.ui()
293 baseui = ui.ui()
294
294
295 # clean the baseui object
295 # clean the baseui object
296 baseui._ocfg = config.config()
296 baseui._ocfg = config.config()
297 baseui._ucfg = config.config()
297 baseui._ucfg = config.config()
298 baseui._tcfg = config.config()
298 baseui._tcfg = config.config()
299
299
300 if read_from == 'file':
300 if read_from == 'file':
301 if not os.path.isfile(path):
301 if not os.path.isfile(path):
302 log.debug('hgrc file is not present at %s skipping...' % path)
302 log.debug('hgrc file is not present at %s, skipping...' % path)
303 return False
303 return False
304 log.debug('reading hgrc from %s' % path)
304 log.debug('reading hgrc from %s' % path)
305 cfg = config.config()
305 cfg = config.config()
306 cfg.read(path)
306 cfg.read(path)
307 for section in ui_sections:
307 for section in ui_sections:
308 for k, v in cfg.items(section):
308 for k, v in cfg.items(section):
309 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
309 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
310 baseui.setconfig(section, k, v)
310 baseui.setconfig(section, k, v)
311
311
312 elif read_from == 'db':
312 elif read_from == 'db':
313 sa = meta.Session()
313 sa = meta.Session()
314 ret = sa.query(RhodeCodeUi)\
314 ret = sa.query(RhodeCodeUi)\
315 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
315 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
316 .all()
316 .all()
317
317
318 hg_ui = ret
318 hg_ui = ret
319 for ui_ in hg_ui:
319 for ui_ in hg_ui:
320 if ui_.ui_active:
320 if ui_.ui_active:
321 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
321 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
322 ui_.ui_key, ui_.ui_value)
322 ui_.ui_key, ui_.ui_value)
323 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
323 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
324 if ui_.ui_key == 'push_ssl':
324 if ui_.ui_key == 'push_ssl':
325 # force set push_ssl requirement to False, rhodecode
325 # force set push_ssl requirement to False, rhodecode
326 # handles that
326 # handles that
327 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
327 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
328 if clear_session:
328 if clear_session:
329 meta.Session.remove()
329 meta.Session.remove()
330 return baseui
330 return baseui
331
331
332
332
333 def set_rhodecode_config(config):
333 def set_rhodecode_config(config):
334 """
334 """
335 Updates pylons config with new settings from database
335 Updates pylons config with new settings from database
336
336
337 :param config:
337 :param config:
338 """
338 """
339 hgsettings = RhodeCodeSetting.get_app_settings()
339 hgsettings = RhodeCodeSetting.get_app_settings()
340
340
341 for k, v in hgsettings.items():
341 for k, v in hgsettings.items():
342 config[k] = v
342 config[k] = v
343
343
344
344
345 def invalidate_cache(cache_key, *args):
345 def invalidate_cache(cache_key, *args):
346 """
346 """
347 Puts cache invalidation task into db for
347 Puts cache invalidation task into db for
348 further global cache invalidation
348 further global cache invalidation
349 """
349 """
350
350
351 from rhodecode.model.scm import ScmModel
351 from rhodecode.model.scm import ScmModel
352
352
353 if cache_key.startswith('get_repo_cached_'):
353 if cache_key.startswith('get_repo_cached_'):
354 name = cache_key.split('get_repo_cached_')[-1]
354 name = cache_key.split('get_repo_cached_')[-1]
355 ScmModel().mark_for_invalidation(name)
355 ScmModel().mark_for_invalidation(name)
356
356
357
357
358 def map_groups(path):
358 def map_groups(path):
359 """
359 """
360 Given a full path to a repository, create all nested groups that this
360 Given a full path to a repository, create all nested groups that this
361 repo is inside. This function creates parent-child relationships between
361 repo is inside. This function creates parent-child relationships between
362 groups and creates default perms for all new groups.
362 groups and creates default perms for all new groups.
363
363
364 :param paths: full path to repository
364 :param paths: full path to repository
365 """
365 """
366 sa = meta.Session()
366 sa = meta.Session()
367 groups = path.split(Repository.url_sep())
367 groups = path.split(Repository.url_sep())
368 parent = None
368 parent = None
369 group = None
369 group = None
370
370
371 # last element is repo in nested groups structure
371 # last element is repo in nested groups structure
372 groups = groups[:-1]
372 groups = groups[:-1]
373 rgm = ReposGroupModel(sa)
373 rgm = ReposGroupModel(sa)
374 for lvl, group_name in enumerate(groups):
374 for lvl, group_name in enumerate(groups):
375 group_name = '/'.join(groups[:lvl] + [group_name])
375 group_name = '/'.join(groups[:lvl] + [group_name])
376 group = RepoGroup.get_by_group_name(group_name)
376 group = RepoGroup.get_by_group_name(group_name)
377 desc = '%s group' % group_name
377 desc = '%s group' % group_name
378
378
379 # skip folders that are now removed repos
379 # skip folders that are now removed repos
380 if REMOVED_REPO_PAT.match(group_name):
380 if REMOVED_REPO_PAT.match(group_name):
381 break
381 break
382
382
383 if group is None:
383 if group is None:
384 log.debug('creating group level: %s group_name: %s' % (lvl,
384 log.debug('creating group level: %s group_name: %s' % (lvl,
385 group_name))
385 group_name))
386 group = RepoGroup(group_name, parent)
386 group = RepoGroup(group_name, parent)
387 group.group_description = desc
387 group.group_description = desc
388 sa.add(group)
388 sa.add(group)
389 rgm._create_default_perms(group)
389 rgm._create_default_perms(group)
390 sa.flush()
390 sa.flush()
391 parent = group
391 parent = group
392 return group
392 return group
393
393
394
394
395 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
395 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
396 install_git_hook=False):
396 install_git_hook=False):
397 """
397 """
398 maps all repos given in initial_repo_list, non existing repositories
398 maps all repos given in initial_repo_list, non existing repositories
399 are created, if remove_obsolete is True it also check for db entries
399 are created, if remove_obsolete is True it also check for db entries
400 that are not in initial_repo_list and removes them.
400 that are not in initial_repo_list and removes them.
401
401
402 :param initial_repo_list: list of repositories found by scanning methods
402 :param initial_repo_list: list of repositories found by scanning methods
403 :param remove_obsolete: check for obsolete entries in database
403 :param remove_obsolete: check for obsolete entries in database
404 :param install_git_hook: if this is True, also check and install githook
404 :param install_git_hook: if this is True, also check and install githook
405 for a repo if missing
405 for a repo if missing
406 """
406 """
407 from rhodecode.model.repo import RepoModel
407 from rhodecode.model.repo import RepoModel
408 from rhodecode.model.scm import ScmModel
408 from rhodecode.model.scm import ScmModel
409 sa = meta.Session()
409 sa = meta.Session()
410 rm = RepoModel()
410 rm = RepoModel()
411 user = sa.query(User).filter(User.admin == True).first()
411 user = sa.query(User).filter(User.admin == True).first()
412 if user is None:
412 if user is None:
413 raise Exception('Missing administrative account !')
413 raise Exception('Missing administrative account!')
414 added = []
414 added = []
415
415
416 # # clear cache keys
416 # # clear cache keys
417 # log.debug("Clearing cache keys now...")
417 # log.debug("Clearing cache keys now...")
418 # CacheInvalidation.clear_cache()
418 # CacheInvalidation.clear_cache()
419 # sa.commit()
419 # sa.commit()
420
420
421 for name, repo in initial_repo_list.items():
421 for name, repo in initial_repo_list.items():
422 group = map_groups(name)
422 group = map_groups(name)
423 db_repo = rm.get_by_repo_name(name)
423 db_repo = rm.get_by_repo_name(name)
424 # found repo that is on filesystem not in RhodeCode database
424 # found repo that is on filesystem not in RhodeCode database
425 if not db_repo:
425 if not db_repo:
426 log.info('repository %s not found creating now' % name)
426 log.info('repository %s not found, creating now' % name)
427 added.append(name)
427 added.append(name)
428 desc = (repo.description
428 desc = (repo.description
429 if repo.description != 'unknown'
429 if repo.description != 'unknown'
430 else '%s repository' % name)
430 else '%s repository' % name)
431 new_repo = rm.create_repo(
431 new_repo = rm.create_repo(
432 repo_name=name,
432 repo_name=name,
433 repo_type=repo.alias,
433 repo_type=repo.alias,
434 description=desc,
434 description=desc,
435 repos_group=getattr(group, 'group_id', None),
435 repos_group=getattr(group, 'group_id', None),
436 owner=user,
436 owner=user,
437 just_db=True
437 just_db=True
438 )
438 )
439 # we added that repo just now, and make sure it has githook
439 # we added that repo just now, and make sure it has githook
440 # installed
440 # installed
441 if new_repo.repo_type == 'git':
441 if new_repo.repo_type == 'git':
442 ScmModel().install_git_hook(new_repo.scm_instance)
442 ScmModel().install_git_hook(new_repo.scm_instance)
443 elif install_git_hook:
443 elif install_git_hook:
444 if db_repo.repo_type == 'git':
444 if db_repo.repo_type == 'git':
445 ScmModel().install_git_hook(db_repo.scm_instance)
445 ScmModel().install_git_hook(db_repo.scm_instance)
446 # during starting install all cache keys for all repositories in the
446 # during starting install all cache keys for all repositories in the
447 # system, this will register all repos and multiple instances
447 # system, this will register all repos and multiple instances
448 key, _prefix, _org_key = CacheInvalidation._get_key(name)
448 key, _prefix, _org_key = CacheInvalidation._get_key(name)
449 log.debug("Creating cache key for %s instance_id:`%s`" % (name, _prefix))
449 log.debug("Creating a cache key for %s instance_id:`%s`" % (name, _prefix))
450 CacheInvalidation._get_or_create_key(key, _prefix, _org_key, commit=False)
450 CacheInvalidation._get_or_create_key(key, _prefix, _org_key, commit=False)
451 sa.commit()
451 sa.commit()
452 removed = []
452 removed = []
453 if remove_obsolete:
453 if remove_obsolete:
454 # remove from database those repositories that are not in the filesystem
454 # remove from database those repositories that are not in the filesystem
455 for repo in sa.query(Repository).all():
455 for repo in sa.query(Repository).all():
456 if repo.repo_name not in initial_repo_list.keys():
456 if repo.repo_name not in initial_repo_list.keys():
457 log.debug("Removing non existing repository found in db `%s`" %
457 log.debug("Removing non-existing repository found in db `%s`" %
458 repo.repo_name)
458 repo.repo_name)
459 try:
459 try:
460 sa.delete(repo)
460 sa.delete(repo)
461 sa.commit()
461 sa.commit()
462 removed.append(repo.repo_name)
462 removed.append(repo.repo_name)
463 except:
463 except:
464 #don't hold further removals on error
464 #don't hold further removals on error
465 log.error(traceback.format_exc())
465 log.error(traceback.format_exc())
466 sa.rollback()
466 sa.rollback()
467
467
468 return added, removed
468 return added, removed
469
469
470
470
471 # set cache regions for beaker so celery can utilise it
471 # set cache regions for beaker so celery can utilise it
472 def add_cache(settings):
472 def add_cache(settings):
473 cache_settings = {'regions': None}
473 cache_settings = {'regions': None}
474 for key in settings.keys():
474 for key in settings.keys():
475 for prefix in ['beaker.cache.', 'cache.']:
475 for prefix in ['beaker.cache.', 'cache.']:
476 if key.startswith(prefix):
476 if key.startswith(prefix):
477 name = key.split(prefix)[1].strip()
477 name = key.split(prefix)[1].strip()
478 cache_settings[name] = settings[key].strip()
478 cache_settings[name] = settings[key].strip()
479 if cache_settings['regions']:
479 if cache_settings['regions']:
480 for region in cache_settings['regions'].split(','):
480 for region in cache_settings['regions'].split(','):
481 region = region.strip()
481 region = region.strip()
482 region_settings = {}
482 region_settings = {}
483 for key, value in cache_settings.items():
483 for key, value in cache_settings.items():
484 if key.startswith(region):
484 if key.startswith(region):
485 region_settings[key.split('.')[1]] = value
485 region_settings[key.split('.')[1]] = value
486 region_settings['expire'] = int(region_settings.get('expire',
486 region_settings['expire'] = int(region_settings.get('expire',
487 60))
487 60))
488 region_settings.setdefault('lock_dir',
488 region_settings.setdefault('lock_dir',
489 cache_settings.get('lock_dir'))
489 cache_settings.get('lock_dir'))
490 region_settings.setdefault('data_dir',
490 region_settings.setdefault('data_dir',
491 cache_settings.get('data_dir'))
491 cache_settings.get('data_dir'))
492
492
493 if 'type' not in region_settings:
493 if 'type' not in region_settings:
494 region_settings['type'] = cache_settings.get('type',
494 region_settings['type'] = cache_settings.get('type',
495 'memory')
495 'memory')
496 beaker.cache.cache_regions[region] = region_settings
496 beaker.cache.cache_regions[region] = region_settings
497
497
498
498
499 def load_rcextensions(root_path):
499 def load_rcextensions(root_path):
500 import rhodecode
500 import rhodecode
501 from rhodecode.config import conf
501 from rhodecode.config import conf
502
502
503 path = os.path.join(root_path, 'rcextensions', '__init__.py')
503 path = os.path.join(root_path, 'rcextensions', '__init__.py')
504 if os.path.isfile(path):
504 if os.path.isfile(path):
505 rcext = create_module('rc', path)
505 rcext = create_module('rc', path)
506 EXT = rhodecode.EXTENSIONS = rcext
506 EXT = rhodecode.EXTENSIONS = rcext
507 log.debug('Found rcextensions now loading %s...' % rcext)
507 log.debug('Found rcextensions now loading %s...' % rcext)
508
508
509 # Additional mappings that are not present in the pygments lexers
509 # Additional mappings that are not present in the pygments lexers
510 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
510 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
511
511
512 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
512 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
513
513
514 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
514 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
515 log.debug('settings custom INDEX_EXTENSIONS')
515 log.debug('settings custom INDEX_EXTENSIONS')
516 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
516 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
517
517
518 #ADDITIONAL MAPPINGS
518 #ADDITIONAL MAPPINGS
519 log.debug('adding extra into INDEX_EXTENSIONS')
519 log.debug('adding extra into INDEX_EXTENSIONS')
520 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
520 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
521
521
522
522
523 #==============================================================================
523 #==============================================================================
524 # TEST FUNCTIONS AND CREATORS
524 # TEST FUNCTIONS AND CREATORS
525 #==============================================================================
525 #==============================================================================
526 def create_test_index(repo_location, config, full_index):
526 def create_test_index(repo_location, config, full_index):
527 """
527 """
528 Makes default test index
528 Makes default test index
529
529
530 :param config: test config
530 :param config: test config
531 :param full_index:
531 :param full_index:
532 """
532 """
533
533
534 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
534 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
535 from rhodecode.lib.pidlock import DaemonLock, LockHeld
535 from rhodecode.lib.pidlock import DaemonLock, LockHeld
536
536
537 repo_location = repo_location
537 repo_location = repo_location
538
538
539 index_location = os.path.join(config['app_conf']['index_dir'])
539 index_location = os.path.join(config['app_conf']['index_dir'])
540 if not os.path.exists(index_location):
540 if not os.path.exists(index_location):
541 os.makedirs(index_location)
541 os.makedirs(index_location)
542
542
543 try:
543 try:
544 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
544 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
545 WhooshIndexingDaemon(index_location=index_location,
545 WhooshIndexingDaemon(index_location=index_location,
546 repo_location=repo_location)\
546 repo_location=repo_location)\
547 .run(full_index=full_index)
547 .run(full_index=full_index)
548 l.release()
548 l.release()
549 except LockHeld:
549 except LockHeld:
550 pass
550 pass
551
551
552
552
553 def create_test_env(repos_test_path, config):
553 def create_test_env(repos_test_path, config):
554 """
554 """
555 Makes a fresh database and
555 Makes a fresh database and
556 install test repository into tmp dir
556 install test repository into tmp dir
557 """
557 """
558 from rhodecode.lib.db_manage import DbManage
558 from rhodecode.lib.db_manage import DbManage
559 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
559 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
560
560
561 # PART ONE create db
561 # PART ONE create db
562 dbconf = config['sqlalchemy.db1.url']
562 dbconf = config['sqlalchemy.db1.url']
563 log.debug('making test db %s' % dbconf)
563 log.debug('making test db %s' % dbconf)
564
564
565 # create test dir if it doesn't exist
565 # create test dir if it doesn't exist
566 if not os.path.isdir(repos_test_path):
566 if not os.path.isdir(repos_test_path):
567 log.debug('Creating testdir %s' % repos_test_path)
567 log.debug('Creating testdir %s' % repos_test_path)
568 os.makedirs(repos_test_path)
568 os.makedirs(repos_test_path)
569
569
570 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
570 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
571 tests=True)
571 tests=True)
572 dbmanage.create_tables(override=True)
572 dbmanage.create_tables(override=True)
573 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
573 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
574 dbmanage.create_default_user()
574 dbmanage.create_default_user()
575 dbmanage.admin_prompt()
575 dbmanage.admin_prompt()
576 dbmanage.create_permissions()
576 dbmanage.create_permissions()
577 dbmanage.populate_default_permissions()
577 dbmanage.populate_default_permissions()
578 Session().commit()
578 Session().commit()
579 # PART TWO make test repo
579 # PART TWO make test repo
580 log.debug('making test vcs repositories')
580 log.debug('making test vcs repositories')
581
581
582 idx_path = config['app_conf']['index_dir']
582 idx_path = config['app_conf']['index_dir']
583 data_path = config['app_conf']['cache_dir']
583 data_path = config['app_conf']['cache_dir']
584
584
585 #clean index and data
585 #clean index and data
586 if idx_path and os.path.exists(idx_path):
586 if idx_path and os.path.exists(idx_path):
587 log.debug('remove %s' % idx_path)
587 log.debug('remove %s' % idx_path)
588 shutil.rmtree(idx_path)
588 shutil.rmtree(idx_path)
589
589
590 if data_path and os.path.exists(data_path):
590 if data_path and os.path.exists(data_path):
591 log.debug('remove %s' % data_path)
591 log.debug('remove %s' % data_path)
592 shutil.rmtree(data_path)
592 shutil.rmtree(data_path)
593
593
594 #CREATE DEFAULT TEST REPOS
594 #CREATE DEFAULT TEST REPOS
595 cur_dir = dn(dn(abspath(__file__)))
595 cur_dir = dn(dn(abspath(__file__)))
596 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
596 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
597 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
597 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
598 tar.close()
598 tar.close()
599
599
600 cur_dir = dn(dn(abspath(__file__)))
600 cur_dir = dn(dn(abspath(__file__)))
601 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
601 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
602 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
602 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
603 tar.close()
603 tar.close()
604
604
605 #LOAD VCS test stuff
605 #LOAD VCS test stuff
606 from rhodecode.tests.vcs import setup_package
606 from rhodecode.tests.vcs import setup_package
607 setup_package()
607 setup_package()
608
608
609
609
610 #==============================================================================
610 #==============================================================================
611 # PASTER COMMANDS
611 # PASTER COMMANDS
612 #==============================================================================
612 #==============================================================================
613 class BasePasterCommand(Command):
613 class BasePasterCommand(Command):
614 """
614 """
615 Abstract Base Class for paster commands.
615 Abstract Base Class for paster commands.
616
616
617 The celery commands are somewhat aggressive about loading
617 The celery commands are somewhat aggressive about loading
618 celery.conf, and since our module sets the `CELERY_LOADER`
618 celery.conf, and since our module sets the `CELERY_LOADER`
619 environment variable to our loader, we have to bootstrap a bit and
619 environment variable to our loader, we have to bootstrap a bit and
620 make sure we've had a chance to load the pylons config off of the
620 make sure we've had a chance to load the pylons config off of the
621 command line, otherwise everything fails.
621 command line, otherwise everything fails.
622 """
622 """
623 min_args = 1
623 min_args = 1
624 min_args_error = "Please provide a paster config file as an argument."
624 min_args_error = "Please provide a paster config file as an argument."
625 takes_config_file = 1
625 takes_config_file = 1
626 requires_config_file = True
626 requires_config_file = True
627
627
628 def notify_msg(self, msg, log=False):
628 def notify_msg(self, msg, log=False):
629 """Make a notification to user, additionally if logger is passed
629 """Make a notification to user, additionally if logger is passed
630 it logs this action using given logger
630 it logs this action using given logger
631
631
632 :param msg: message that will be printed to user
632 :param msg: message that will be printed to user
633 :param log: logging instance, to use to additionally log this message
633 :param log: logging instance, to use to additionally log this message
634
634
635 """
635 """
636 if log and isinstance(log, logging):
636 if log and isinstance(log, logging):
637 log(msg)
637 log(msg)
638
638
639 def run(self, args):
639 def run(self, args):
640 """
640 """
641 Overrides Command.run
641 Overrides Command.run
642
642
643 Checks for a config file argument and loads it.
643 Checks for a config file argument and loads it.
644 """
644 """
645 if len(args) < self.min_args:
645 if len(args) < self.min_args:
646 raise BadCommand(
646 raise BadCommand(
647 self.min_args_error % {'min_args': self.min_args,
647 self.min_args_error % {'min_args': self.min_args,
648 'actual_args': len(args)})
648 'actual_args': len(args)})
649
649
650 # Decrement because we're going to lob off the first argument.
650 # Decrement because we're going to lob off the first argument.
651 # @@ This is hacky
651 # @@ This is hacky
652 self.min_args -= 1
652 self.min_args -= 1
653 self.bootstrap_config(args[0])
653 self.bootstrap_config(args[0])
654 self.update_parser()
654 self.update_parser()
655 return super(BasePasterCommand, self).run(args[1:])
655 return super(BasePasterCommand, self).run(args[1:])
656
656
657 def update_parser(self):
657 def update_parser(self):
658 """
658 """
659 Abstract method. Allows for the class's parser to be updated
659 Abstract method. Allows for the class's parser to be updated
660 before the superclass's `run` method is called. Necessary to
660 before the superclass's `run` method is called. Necessary to
661 allow options/arguments to be passed through to the underlying
661 allow options/arguments to be passed through to the underlying
662 celery command.
662 celery command.
663 """
663 """
664 raise NotImplementedError("Abstract Method.")
664 raise NotImplementedError("Abstract Method.")
665
665
666 def bootstrap_config(self, conf):
666 def bootstrap_config(self, conf):
667 """
667 """
668 Loads the pylons configuration.
668 Loads the pylons configuration.
669 """
669 """
670 from pylons import config as pylonsconfig
670 from pylons import config as pylonsconfig
671
671
672 self.path_to_ini_file = os.path.realpath(conf)
672 self.path_to_ini_file = os.path.realpath(conf)
673 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
673 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
674 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
674 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
675
675
676
676
677 def check_git_version():
677 def check_git_version():
678 """
678 """
679 Checks what version of git is installed in system, and issues a warning
679 Checks what version of git is installed in system, and issues a warning
680 if it's to old for RhodeCode to properly work.
680 if it's too old for RhodeCode to properly work.
681 """
681 """
682 import subprocess
682 import subprocess
683 from distutils.version import StrictVersion
683 from distutils.version import StrictVersion
684 from rhodecode import BACKENDS
684 from rhodecode import BACKENDS
685
685
686 p = subprocess.Popen('git --version', shell=True,
686 p = subprocess.Popen('git --version', shell=True,
687 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
687 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
688 stdout, stderr = p.communicate()
688 stdout, stderr = p.communicate()
689 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
689 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
690 try:
690 try:
691 _ver = StrictVersion(ver)
691 _ver = StrictVersion(ver)
692 except:
692 except:
693 _ver = StrictVersion('0.0.0')
693 _ver = StrictVersion('0.0.0')
694 stderr = traceback.format_exc()
694 stderr = traceback.format_exc()
695
695
696 req_ver = '1.7.4'
696 req_ver = '1.7.4'
697 to_old_git = False
697 to_old_git = False
698 if _ver <= StrictVersion(req_ver):
698 if _ver <= StrictVersion(req_ver):
699 to_old_git = True
699 to_old_git = True
700
700
701 if 'git' in BACKENDS:
701 if 'git' in BACKENDS:
702 log.debug('GIT version detected: %s' % stdout)
702 log.debug('GIT version detected: %s' % stdout)
703 if stderr:
703 if stderr:
704 log.warning('Unable to detect git version org error was:%r' % stderr)
704 log.warning('Unable to detect git version org error was:%r' % stderr)
705 elif to_old_git:
705 elif to_old_git:
706 log.warning('RhodeCode detected git version %s, which is to old '
706 log.warning('RhodeCode detected git version %s, which is too old '
707 'for the system to function properly make sure '
707 'for the system to function properly. Make sure '
708 'it is at least in version %s' % (ver, req_ver))
708 'its version is at least %s' % (ver, req_ver))
709 return _ver No newline at end of file
709 return _ver
@@ -1,507 +1,518 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import re
26 import re
27 import time
27 import time
28 import datetime
28 import datetime
29 from pylons.i18n.translation import _, ungettext
29 from pylons.i18n.translation import _, ungettext
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31
31
32
32
33 def __get_lem():
33 def __get_lem():
34 """
34 """
35 Get language extension map based on what's inside pygments lexers
35 Get language extension map based on what's inside pygments lexers
36 """
36 """
37 from pygments import lexers
37 from pygments import lexers
38 from string import lower
38 from string import lower
39 from collections import defaultdict
39 from collections import defaultdict
40
40
41 d = defaultdict(lambda: [])
41 d = defaultdict(lambda: [])
42
42
43 def __clean(s):
43 def __clean(s):
44 s = s.lstrip('*')
44 s = s.lstrip('*')
45 s = s.lstrip('.')
45 s = s.lstrip('.')
46
46
47 if s.find('[') != -1:
47 if s.find('[') != -1:
48 exts = []
48 exts = []
49 start, stop = s.find('['), s.find(']')
49 start, stop = s.find('['), s.find(']')
50
50
51 for suffix in s[start + 1:stop]:
51 for suffix in s[start + 1:stop]:
52 exts.append(s[:s.find('[')] + suffix)
52 exts.append(s[:s.find('[')] + suffix)
53 return map(lower, exts)
53 return map(lower, exts)
54 else:
54 else:
55 return map(lower, [s])
55 return map(lower, [s])
56
56
57 for lx, t in sorted(lexers.LEXERS.items()):
57 for lx, t in sorted(lexers.LEXERS.items()):
58 m = map(__clean, t[-2])
58 m = map(__clean, t[-2])
59 if m:
59 if m:
60 m = reduce(lambda x, y: x + y, m)
60 m = reduce(lambda x, y: x + y, m)
61 for ext in m:
61 for ext in m:
62 desc = lx.replace('Lexer', '')
62 desc = lx.replace('Lexer', '')
63 d[ext].append(desc)
63 d[ext].append(desc)
64
64
65 return dict(d)
65 return dict(d)
66
66
67 def str2bool(_str):
67 def str2bool(_str):
68 """
68 """
69 returs True/False value from given string, it tries to translate the
69 returs True/False value from given string, it tries to translate the
70 string into boolean
70 string into boolean
71
71
72 :param _str: string value to translate into boolean
72 :param _str: string value to translate into boolean
73 :rtype: boolean
73 :rtype: boolean
74 :returns: boolean from given string
74 :returns: boolean from given string
75 """
75 """
76 if _str is None:
76 if _str is None:
77 return False
77 return False
78 if _str in (True, False):
78 if _str in (True, False):
79 return _str
79 return _str
80 _str = str(_str).strip().lower()
80 _str = str(_str).strip().lower()
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
82
82
83
83
84 def convert_line_endings(line, mode):
84 def convert_line_endings(line, mode):
85 """
85 """
86 Converts a given line "line end" accordingly to given mode
86 Converts a given line "line end" accordingly to given mode
87
87
88 Available modes are::
88 Available modes are::
89 0 - Unix
89 0 - Unix
90 1 - Mac
90 1 - Mac
91 2 - DOS
91 2 - DOS
92
92
93 :param line: given line to convert
93 :param line: given line to convert
94 :param mode: mode to convert to
94 :param mode: mode to convert to
95 :rtype: str
95 :rtype: str
96 :return: converted line according to mode
96 :return: converted line according to mode
97 """
97 """
98 from string import replace
98 from string import replace
99
99
100 if mode == 0:
100 if mode == 0:
101 line = replace(line, '\r\n', '\n')
101 line = replace(line, '\r\n', '\n')
102 line = replace(line, '\r', '\n')
102 line = replace(line, '\r', '\n')
103 elif mode == 1:
103 elif mode == 1:
104 line = replace(line, '\r\n', '\r')
104 line = replace(line, '\r\n', '\r')
105 line = replace(line, '\n', '\r')
105 line = replace(line, '\n', '\r')
106 elif mode == 2:
106 elif mode == 2:
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
108 return line
108 return line
109
109
110
110
111 def detect_mode(line, default):
111 def detect_mode(line, default):
112 """
112 """
113 Detects line break for given line, if line break couldn't be found
113 Detects line break for given line, if line break couldn't be found
114 given default value is returned
114 given default value is returned
115
115
116 :param line: str line
116 :param line: str line
117 :param default: default
117 :param default: default
118 :rtype: int
118 :rtype: int
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
120 """
120 """
121 if line.endswith('\r\n'):
121 if line.endswith('\r\n'):
122 return 2
122 return 2
123 elif line.endswith('\n'):
123 elif line.endswith('\n'):
124 return 0
124 return 0
125 elif line.endswith('\r'):
125 elif line.endswith('\r'):
126 return 1
126 return 1
127 else:
127 else:
128 return default
128 return default
129
129
130
130
131 def generate_api_key(username, salt=None):
131 def generate_api_key(username, salt=None):
132 """
132 """
133 Generates unique API key for given username, if salt is not given
133 Generates unique API key for given username, if salt is not given
134 it'll be generated from some random string
134 it'll be generated from some random string
135
135
136 :param username: username as string
136 :param username: username as string
137 :param salt: salt to hash generate KEY
137 :param salt: salt to hash generate KEY
138 :rtype: str
138 :rtype: str
139 :returns: sha1 hash from username+salt
139 :returns: sha1 hash from username+salt
140 """
140 """
141 from tempfile import _RandomNameSequence
141 from tempfile import _RandomNameSequence
142 import hashlib
142 import hashlib
143
143
144 if salt is None:
144 if salt is None:
145 salt = _RandomNameSequence().next()
145 salt = _RandomNameSequence().next()
146
146
147 return hashlib.sha1(username + salt).hexdigest()
147 return hashlib.sha1(username + salt).hexdigest()
148
148
149
149
150 def safe_int(val, default=None):
150 def safe_int(val, default=None):
151 """
151 """
152 Returns int() of val if val is not convertable to int use default
152 Returns int() of val if val is not convertable to int use default
153 instead
153 instead
154
154
155 :param val:
155 :param val:
156 :param default:
156 :param default:
157 """
157 """
158
158
159 try:
159 try:
160 val = int(val)
160 val = int(val)
161 except ValueError:
161 except ValueError:
162 val = default
162 val = default
163
163
164 return val
164 return val
165
165
166
166
167 def safe_unicode(str_, from_encoding=None):
167 def safe_unicode(str_, from_encoding=None):
168 """
168 """
169 safe unicode function. Does few trick to turn str_ into unicode
169 safe unicode function. Does few trick to turn str_ into unicode
170
170
171 In case of UnicodeDecode error we try to return it with encoding detected
171 In case of UnicodeDecode error we try to return it with encoding detected
172 by chardet library if it fails fallback to unicode with errors replaced
172 by chardet library if it fails fallback to unicode with errors replaced
173
173
174 :param str_: string to decode
174 :param str_: string to decode
175 :rtype: unicode
175 :rtype: unicode
176 :returns: unicode object
176 :returns: unicode object
177 """
177 """
178 if isinstance(str_, unicode):
178 if isinstance(str_, unicode):
179 return str_
179 return str_
180
180
181 if not from_encoding:
181 if not from_encoding:
182 import rhodecode
182 import rhodecode
183 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
183 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
184 from_encoding = DEFAULT_ENCODING
184 from_encoding = DEFAULT_ENCODING
185
185
186 try:
186 try:
187 return unicode(str_)
187 return unicode(str_)
188 except UnicodeDecodeError:
188 except UnicodeDecodeError:
189 pass
189 pass
190
190
191 try:
191 try:
192 return unicode(str_, from_encoding)
192 return unicode(str_, from_encoding)
193 except UnicodeDecodeError:
193 except UnicodeDecodeError:
194 pass
194 pass
195
195
196 try:
196 try:
197 import chardet
197 import chardet
198 encoding = chardet.detect(str_)['encoding']
198 encoding = chardet.detect(str_)['encoding']
199 if encoding is None:
199 if encoding is None:
200 raise Exception()
200 raise Exception()
201 return str_.decode(encoding)
201 return str_.decode(encoding)
202 except (ImportError, UnicodeDecodeError, Exception):
202 except (ImportError, UnicodeDecodeError, Exception):
203 return unicode(str_, from_encoding, 'replace')
203 return unicode(str_, from_encoding, 'replace')
204
204
205
205
206 def safe_str(unicode_, to_encoding=None):
206 def safe_str(unicode_, to_encoding=None):
207 """
207 """
208 safe str function. Does few trick to turn unicode_ into string
208 safe str function. Does few trick to turn unicode_ into string
209
209
210 In case of UnicodeEncodeError we try to return it with encoding detected
210 In case of UnicodeEncodeError we try to return it with encoding detected
211 by chardet library if it fails fallback to string with errors replaced
211 by chardet library if it fails fallback to string with errors replaced
212
212
213 :param unicode_: unicode to encode
213 :param unicode_: unicode to encode
214 :rtype: str
214 :rtype: str
215 :returns: str object
215 :returns: str object
216 """
216 """
217
217
218 # if it's not basestr cast to str
218 # if it's not basestr cast to str
219 if not isinstance(unicode_, basestring):
219 if not isinstance(unicode_, basestring):
220 return str(unicode_)
220 return str(unicode_)
221
221
222 if isinstance(unicode_, str):
222 if isinstance(unicode_, str):
223 return unicode_
223 return unicode_
224
224
225 if not to_encoding:
225 if not to_encoding:
226 import rhodecode
226 import rhodecode
227 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
227 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
228 to_encoding = DEFAULT_ENCODING
228 to_encoding = DEFAULT_ENCODING
229
229
230 try:
230 try:
231 return unicode_.encode(to_encoding)
231 return unicode_.encode(to_encoding)
232 except UnicodeEncodeError:
232 except UnicodeEncodeError:
233 pass
233 pass
234
234
235 try:
235 try:
236 import chardet
236 import chardet
237 encoding = chardet.detect(unicode_)['encoding']
237 encoding = chardet.detect(unicode_)['encoding']
238 if encoding is None:
238 if encoding is None:
239 raise UnicodeEncodeError()
239 raise UnicodeEncodeError()
240
240
241 return unicode_.encode(encoding)
241 return unicode_.encode(encoding)
242 except (ImportError, UnicodeEncodeError):
242 except (ImportError, UnicodeEncodeError):
243 return unicode_.encode(to_encoding, 'replace')
243 return unicode_.encode(to_encoding, 'replace')
244
244
245 return safe_str
245 return safe_str
246
246
247
247
248 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
248 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
249 """
249 """
250 Custom engine_from_config functions that makes sure we use NullPool for
250 Custom engine_from_config functions that makes sure we use NullPool for
251 file based sqlite databases. This prevents errors on sqlite. This only
251 file based sqlite databases. This prevents errors on sqlite. This only
252 applies to sqlalchemy versions < 0.7.0
252 applies to sqlalchemy versions < 0.7.0
253
253
254 """
254 """
255 import sqlalchemy
255 import sqlalchemy
256 from sqlalchemy import engine_from_config as efc
256 from sqlalchemy import engine_from_config as efc
257 import logging
257 import logging
258
258
259 if int(sqlalchemy.__version__.split('.')[1]) < 7:
259 if int(sqlalchemy.__version__.split('.')[1]) < 7:
260
260
261 # This solution should work for sqlalchemy < 0.7.0, and should use
261 # This solution should work for sqlalchemy < 0.7.0, and should use
262 # proxy=TimerProxy() for execution time profiling
262 # proxy=TimerProxy() for execution time profiling
263
263
264 from sqlalchemy.pool import NullPool
264 from sqlalchemy.pool import NullPool
265 url = configuration[prefix + 'url']
265 url = configuration[prefix + 'url']
266
266
267 if url.startswith('sqlite'):
267 if url.startswith('sqlite'):
268 kwargs.update({'poolclass': NullPool})
268 kwargs.update({'poolclass': NullPool})
269 return efc(configuration, prefix, **kwargs)
269 return efc(configuration, prefix, **kwargs)
270 else:
270 else:
271 import time
271 import time
272 from sqlalchemy import event
272 from sqlalchemy import event
273 from sqlalchemy.engine import Engine
273 from sqlalchemy.engine import Engine
274
274
275 log = logging.getLogger('sqlalchemy.engine')
275 log = logging.getLogger('sqlalchemy.engine')
276 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
276 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
277 engine = efc(configuration, prefix, **kwargs)
277 engine = efc(configuration, prefix, **kwargs)
278
278
279 def color_sql(sql):
279 def color_sql(sql):
280 COLOR_SEQ = "\033[1;%dm"
280 COLOR_SEQ = "\033[1;%dm"
281 COLOR_SQL = YELLOW
281 COLOR_SQL = YELLOW
282 normal = '\x1b[0m'
282 normal = '\x1b[0m'
283 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
283 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
284
284
285 if configuration['debug']:
285 if configuration['debug']:
286 #attach events only for debug configuration
286 #attach events only for debug configuration
287
287
288 def before_cursor_execute(conn, cursor, statement,
288 def before_cursor_execute(conn, cursor, statement,
289 parameters, context, executemany):
289 parameters, context, executemany):
290 context._query_start_time = time.time()
290 context._query_start_time = time.time()
291 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
291 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
292
292
293 def after_cursor_execute(conn, cursor, statement,
293 def after_cursor_execute(conn, cursor, statement,
294 parameters, context, executemany):
294 parameters, context, executemany):
295 total = time.time() - context._query_start_time
295 total = time.time() - context._query_start_time
296 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
296 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
297
297
298 event.listen(engine, "before_cursor_execute",
298 event.listen(engine, "before_cursor_execute",
299 before_cursor_execute)
299 before_cursor_execute)
300 event.listen(engine, "after_cursor_execute",
300 event.listen(engine, "after_cursor_execute",
301 after_cursor_execute)
301 after_cursor_execute)
302
302
303 return engine
303 return engine
304
304
305
305
306 def age(prevdate):
306 def age(prevdate):
307 """
307 """
308 turns a datetime into an age string.
308 turns a datetime into an age string.
309
309
310 :param prevdate: datetime object
310 :param prevdate: datetime object
311 :rtype: unicode
311 :rtype: unicode
312 :returns: unicode words describing age
312 :returns: unicode words describing age
313 """
313 """
314
314
315 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
315 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
316 deltas = {}
316 deltas = {}
317 future = False
317
318
318 # Get date parts deltas
319 # Get date parts deltas
319 now = datetime.datetime.now()
320 now = datetime.datetime.now()
321 if prevdate > now:
322 now, prevdate = prevdate, now
323 future = True
324
320 for part in order:
325 for part in order:
321 deltas[part] = getattr(now, part) - getattr(prevdate, part)
326 deltas[part] = getattr(now, part) - getattr(prevdate, part)
322
327
323 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
328 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
324 # not 1 hour, -59 minutes and -59 seconds)
329 # not 1 hour, -59 minutes and -59 seconds)
325
330
326 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
331 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
327 part = order[num]
332 part = order[num]
328 carry_part = order[num - 1]
333 carry_part = order[num - 1]
329
334
330 if deltas[part] < 0:
335 if deltas[part] < 0:
331 deltas[part] += length
336 deltas[part] += length
332 deltas[carry_part] -= 1
337 deltas[carry_part] -= 1
333
338
334 # Same thing for days except that the increment depends on the (variable)
339 # Same thing for days except that the increment depends on the (variable)
335 # number of days in the month
340 # number of days in the month
336 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
341 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
337 if deltas['day'] < 0:
342 if deltas['day'] < 0:
338 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
343 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
339 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
344 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
340 deltas['day'] += 29
345 deltas['day'] += 29
341 else:
346 else:
342 deltas['day'] += month_lengths[prevdate.month - 1]
347 deltas['day'] += month_lengths[prevdate.month - 1]
343
348
344 deltas['month'] -= 1
349 deltas['month'] -= 1
345
350
346 if deltas['month'] < 0:
351 if deltas['month'] < 0:
347 deltas['month'] += 12
352 deltas['month'] += 12
348 deltas['year'] -= 1
353 deltas['year'] -= 1
349
354
350 # Format the result
355 # Format the result
351 fmt_funcs = {
356 fmt_funcs = {
352 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
357 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
353 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
358 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
354 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
359 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
355 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
360 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
356 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
361 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
357 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
362 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
358 }
363 }
359
364
360 for i, part in enumerate(order):
365 for i, part in enumerate(order):
361 value = deltas[part]
366 value = deltas[part]
362 if value == 0:
367 if value == 0:
363 continue
368 continue
364
369
365 if i < 5:
370 if i < 5:
366 sub_part = order[i + 1]
371 sub_part = order[i + 1]
367 sub_value = deltas[sub_part]
372 sub_value = deltas[sub_part]
368 else:
373 else:
369 sub_value = 0
374 sub_value = 0
370
375
371 if sub_value == 0:
376 if sub_value == 0:
372 return _(u'%s ago') % fmt_funcs[part](value)
377 if future:
373
378 return _(u'in %s') % fmt_funcs[part](value)
374 return _(u'%s and %s ago') % (fmt_funcs[part](value),
379 else:
375 fmt_funcs[sub_part](sub_value))
380 return _(u'%s ago') % fmt_funcs[part](value)
381 if future:
382 return _(u'in %s and %s') % (fmt_funcs[part](value),
383 fmt_funcs[sub_part](sub_value))
384 else:
385 return _(u'%s and %s ago') % (fmt_funcs[part](value),
386 fmt_funcs[sub_part](sub_value))
376
387
377 return _(u'just now')
388 return _(u'just now')
378
389
379
390
380 def uri_filter(uri):
391 def uri_filter(uri):
381 """
392 """
382 Removes user:password from given url string
393 Removes user:password from given url string
383
394
384 :param uri:
395 :param uri:
385 :rtype: unicode
396 :rtype: unicode
386 :returns: filtered list of strings
397 :returns: filtered list of strings
387 """
398 """
388 if not uri:
399 if not uri:
389 return ''
400 return ''
390
401
391 proto = ''
402 proto = ''
392
403
393 for pat in ('https://', 'http://'):
404 for pat in ('https://', 'http://'):
394 if uri.startswith(pat):
405 if uri.startswith(pat):
395 uri = uri[len(pat):]
406 uri = uri[len(pat):]
396 proto = pat
407 proto = pat
397 break
408 break
398
409
399 # remove passwords and username
410 # remove passwords and username
400 uri = uri[uri.find('@') + 1:]
411 uri = uri[uri.find('@') + 1:]
401
412
402 # get the port
413 # get the port
403 cred_pos = uri.find(':')
414 cred_pos = uri.find(':')
404 if cred_pos == -1:
415 if cred_pos == -1:
405 host, port = uri, None
416 host, port = uri, None
406 else:
417 else:
407 host, port = uri[:cred_pos], uri[cred_pos + 1:]
418 host, port = uri[:cred_pos], uri[cred_pos + 1:]
408
419
409 return filter(None, [proto, host, port])
420 return filter(None, [proto, host, port])
410
421
411
422
412 def credentials_filter(uri):
423 def credentials_filter(uri):
413 """
424 """
414 Returns a url with removed credentials
425 Returns a url with removed credentials
415
426
416 :param uri:
427 :param uri:
417 """
428 """
418
429
419 uri = uri_filter(uri)
430 uri = uri_filter(uri)
420 #check if we have port
431 #check if we have port
421 if len(uri) > 2 and uri[2]:
432 if len(uri) > 2 and uri[2]:
422 uri[2] = ':' + uri[2]
433 uri[2] = ':' + uri[2]
423
434
424 return ''.join(uri)
435 return ''.join(uri)
425
436
426
437
427 def get_changeset_safe(repo, rev):
438 def get_changeset_safe(repo, rev):
428 """
439 """
429 Safe version of get_changeset if this changeset doesn't exists for a
440 Safe version of get_changeset if this changeset doesn't exists for a
430 repo it returns a Dummy one instead
441 repo it returns a Dummy one instead
431
442
432 :param repo:
443 :param repo:
433 :param rev:
444 :param rev:
434 """
445 """
435 from rhodecode.lib.vcs.backends.base import BaseRepository
446 from rhodecode.lib.vcs.backends.base import BaseRepository
436 from rhodecode.lib.vcs.exceptions import RepositoryError
447 from rhodecode.lib.vcs.exceptions import RepositoryError
437 from rhodecode.lib.vcs.backends.base import EmptyChangeset
448 from rhodecode.lib.vcs.backends.base import EmptyChangeset
438 if not isinstance(repo, BaseRepository):
449 if not isinstance(repo, BaseRepository):
439 raise Exception('You must pass an Repository '
450 raise Exception('You must pass an Repository '
440 'object as first argument got %s', type(repo))
451 'object as first argument got %s', type(repo))
441
452
442 try:
453 try:
443 cs = repo.get_changeset(rev)
454 cs = repo.get_changeset(rev)
444 except RepositoryError:
455 except RepositoryError:
445 cs = EmptyChangeset(requested_revision=rev)
456 cs = EmptyChangeset(requested_revision=rev)
446 return cs
457 return cs
447
458
448
459
449 def datetime_to_time(dt):
460 def datetime_to_time(dt):
450 if dt:
461 if dt:
451 return time.mktime(dt.timetuple())
462 return time.mktime(dt.timetuple())
452
463
453
464
454 def time_to_datetime(tm):
465 def time_to_datetime(tm):
455 if tm:
466 if tm:
456 if isinstance(tm, basestring):
467 if isinstance(tm, basestring):
457 try:
468 try:
458 tm = float(tm)
469 tm = float(tm)
459 except ValueError:
470 except ValueError:
460 return
471 return
461 return datetime.datetime.fromtimestamp(tm)
472 return datetime.datetime.fromtimestamp(tm)
462
473
463 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
474 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
464
475
465
476
466 def extract_mentioned_users(s):
477 def extract_mentioned_users(s):
467 """
478 """
468 Returns unique usernames from given string s that have @mention
479 Returns unique usernames from given string s that have @mention
469
480
470 :param s: string to get mentions
481 :param s: string to get mentions
471 """
482 """
472 usrs = set()
483 usrs = set()
473 for username in re.findall(MENTIONS_REGEX, s):
484 for username in re.findall(MENTIONS_REGEX, s):
474 usrs.add(username)
485 usrs.add(username)
475
486
476 return sorted(list(usrs), key=lambda k: k.lower())
487 return sorted(list(usrs), key=lambda k: k.lower())
477
488
478
489
479 class AttributeDict(dict):
490 class AttributeDict(dict):
480 def __getattr__(self, attr):
491 def __getattr__(self, attr):
481 return self.get(attr, None)
492 return self.get(attr, None)
482 __setattr__ = dict.__setitem__
493 __setattr__ = dict.__setitem__
483 __delattr__ = dict.__delitem__
494 __delattr__ = dict.__delitem__
484
495
485
496
486 def fix_PATH(os_=None):
497 def fix_PATH(os_=None):
487 """
498 """
488 Get current active python path, and append it to PATH variable to fix issues
499 Get current active python path, and append it to PATH variable to fix issues
489 of subprocess calls and different python versions
500 of subprocess calls and different python versions
490 """
501 """
491 import sys
502 import sys
492 if os_ is None:
503 if os_ is None:
493 import os
504 import os
494 else:
505 else:
495 os = os_
506 os = os_
496
507
497 cur_path = os.path.split(sys.executable)[0]
508 cur_path = os.path.split(sys.executable)[0]
498 if not os.environ['PATH'].startswith(cur_path):
509 if not os.environ['PATH'].startswith(cur_path):
499 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
510 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
500
511
501
512
502 def obfuscate_url_pw(engine):
513 def obfuscate_url_pw(engine):
503 from sqlalchemy.engine import url
514 from sqlalchemy.engine import url
504 url = url.make_url(engine)
515 url = url.make_url(engine)
505 if url.password:
516 if url.password:
506 url.password = 'XXXXX'
517 url.password = 'XXXXX'
507 return str(url) No newline at end of file
518 return str(url)
@@ -1,544 +1,548 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import shutil
27 import shutil
28 import logging
28 import logging
29 import traceback
29 import traceback
30 from datetime import datetime
30 from datetime import datetime
31
31
32 from rhodecode.lib.vcs.backends import get_backend
32 from rhodecode.lib.vcs.backends import get_backend
33 from rhodecode.lib.compat import json
33 from rhodecode.lib.compat import json
34 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode
34 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode
35 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.caching_query import FromCache
36 from rhodecode.lib.hooks import log_create_repository
36 from rhodecode.lib.hooks import log_create_repository, log_delete_repository
37
37
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
39 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
40 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
40 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42
42
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class RepoModel(BaseModel):
47 class RepoModel(BaseModel):
48
48
49 cls = Repository
49 cls = Repository
50 URL_SEPARATOR = Repository.url_sep()
50 URL_SEPARATOR = Repository.url_sep()
51
51
52 def __get_users_group(self, users_group):
52 def __get_users_group(self, users_group):
53 return self._get_instance(UsersGroup, users_group,
53 return self._get_instance(UsersGroup, users_group,
54 callback=UsersGroup.get_by_group_name)
54 callback=UsersGroup.get_by_group_name)
55
55
56 def _get_repos_group(self, repos_group):
56 def _get_repos_group(self, repos_group):
57 return self._get_instance(RepoGroup, repos_group,
57 return self._get_instance(RepoGroup, repos_group,
58 callback=RepoGroup.get_by_group_name)
58 callback=RepoGroup.get_by_group_name)
59
59
60 @LazyProperty
60 @LazyProperty
61 def repos_path(self):
61 def repos_path(self):
62 """
62 """
63 Get's the repositories root path from database
63 Get's the repositories root path from database
64 """
64 """
65
65
66 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
66 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
67 return q.ui_value
67 return q.ui_value
68
68
69 def get(self, repo_id, cache=False):
69 def get(self, repo_id, cache=False):
70 repo = self.sa.query(Repository)\
70 repo = self.sa.query(Repository)\
71 .filter(Repository.repo_id == repo_id)
71 .filter(Repository.repo_id == repo_id)
72
72
73 if cache:
73 if cache:
74 repo = repo.options(FromCache("sql_cache_short",
74 repo = repo.options(FromCache("sql_cache_short",
75 "get_repo_%s" % repo_id))
75 "get_repo_%s" % repo_id))
76 return repo.scalar()
76 return repo.scalar()
77
77
78 def get_repo(self, repository):
78 def get_repo(self, repository):
79 return self._get_repo(repository)
79 return self._get_repo(repository)
80
80
81 def get_by_repo_name(self, repo_name, cache=False):
81 def get_by_repo_name(self, repo_name, cache=False):
82 repo = self.sa.query(Repository)\
82 repo = self.sa.query(Repository)\
83 .filter(Repository.repo_name == repo_name)
83 .filter(Repository.repo_name == repo_name)
84
84
85 if cache:
85 if cache:
86 repo = repo.options(FromCache("sql_cache_short",
86 repo = repo.options(FromCache("sql_cache_short",
87 "get_repo_%s" % repo_name))
87 "get_repo_%s" % repo_name))
88 return repo.scalar()
88 return repo.scalar()
89
89
90 def get_users_js(self):
90 def get_users_js(self):
91 users = self.sa.query(User).filter(User.active == True).all()
91 users = self.sa.query(User).filter(User.active == True).all()
92 return json.dumps([
92 return json.dumps([
93 {
93 {
94 'id': u.user_id,
94 'id': u.user_id,
95 'fname': u.name,
95 'fname': u.name,
96 'lname': u.lastname,
96 'lname': u.lastname,
97 'nname': u.username,
97 'nname': u.username,
98 'gravatar_lnk': h.gravatar_url(u.email, 14)
98 'gravatar_lnk': h.gravatar_url(u.email, 14)
99 } for u in users]
99 } for u in users]
100 )
100 )
101
101
102 def get_users_groups_js(self):
102 def get_users_groups_js(self):
103 users_groups = self.sa.query(UsersGroup)\
103 users_groups = self.sa.query(UsersGroup)\
104 .filter(UsersGroup.users_group_active == True).all()
104 .filter(UsersGroup.users_group_active == True).all()
105
105
106 return json.dumps([
106 return json.dumps([
107 {
107 {
108 'id': gr.users_group_id,
108 'id': gr.users_group_id,
109 'grname': gr.users_group_name,
109 'grname': gr.users_group_name,
110 'grmembers': len(gr.members),
110 'grmembers': len(gr.members),
111 } for gr in users_groups]
111 } for gr in users_groups]
112 )
112 )
113
113
114 def _get_defaults(self, repo_name):
114 def _get_defaults(self, repo_name):
115 """
115 """
116 Get's information about repository, and returns a dict for
116 Get's information about repository, and returns a dict for
117 usage in forms
117 usage in forms
118
118
119 :param repo_name:
119 :param repo_name:
120 """
120 """
121
121
122 repo_info = Repository.get_by_repo_name(repo_name)
122 repo_info = Repository.get_by_repo_name(repo_name)
123
123
124 if repo_info is None:
124 if repo_info is None:
125 return None
125 return None
126
126
127 defaults = repo_info.get_dict()
127 defaults = repo_info.get_dict()
128 group, repo_name = repo_info.groups_and_repo
128 group, repo_name = repo_info.groups_and_repo
129 defaults['repo_name'] = repo_name
129 defaults['repo_name'] = repo_name
130 defaults['repo_group'] = getattr(group[-1] if group else None,
130 defaults['repo_group'] = getattr(group[-1] if group else None,
131 'group_id', None)
131 'group_id', None)
132
132
133 # fill owner
133 # fill owner
134 if repo_info.user:
134 if repo_info.user:
135 defaults.update({'user': repo_info.user.username})
135 defaults.update({'user': repo_info.user.username})
136 else:
136 else:
137 replacement_user = User.query().filter(User.admin ==
137 replacement_user = User.query().filter(User.admin ==
138 True).first().username
138 True).first().username
139 defaults.update({'user': replacement_user})
139 defaults.update({'user': replacement_user})
140
140
141 # fill repository users
141 # fill repository users
142 for p in repo_info.repo_to_perm:
142 for p in repo_info.repo_to_perm:
143 defaults.update({'u_perm_%s' % p.user.username:
143 defaults.update({'u_perm_%s' % p.user.username:
144 p.permission.permission_name})
144 p.permission.permission_name})
145
145
146 # fill repository groups
146 # fill repository groups
147 for p in repo_info.users_group_to_perm:
147 for p in repo_info.users_group_to_perm:
148 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
148 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
149 p.permission.permission_name})
149 p.permission.permission_name})
150
150
151 return defaults
151 return defaults
152
152
153 def update(self, repo_name, form_data):
153 def update(self, repo_name, form_data):
154 try:
154 try:
155 cur_repo = self.get_by_repo_name(repo_name, cache=False)
155 cur_repo = self.get_by_repo_name(repo_name, cache=False)
156
156
157 # update permissions
157 # update permissions
158 for member, perm, member_type in form_data['perms_updates']:
158 for member, perm, member_type in form_data['perms_updates']:
159 if member_type == 'user':
159 if member_type == 'user':
160 # this updates existing one
160 # this updates existing one
161 RepoModel().grant_user_permission(
161 RepoModel().grant_user_permission(
162 repo=cur_repo, user=member, perm=perm
162 repo=cur_repo, user=member, perm=perm
163 )
163 )
164 else:
164 else:
165 RepoModel().grant_users_group_permission(
165 RepoModel().grant_users_group_permission(
166 repo=cur_repo, group_name=member, perm=perm
166 repo=cur_repo, group_name=member, perm=perm
167 )
167 )
168 # set new permissions
168 # set new permissions
169 for member, perm, member_type in form_data['perms_new']:
169 for member, perm, member_type in form_data['perms_new']:
170 if member_type == 'user':
170 if member_type == 'user':
171 RepoModel().grant_user_permission(
171 RepoModel().grant_user_permission(
172 repo=cur_repo, user=member, perm=perm
172 repo=cur_repo, user=member, perm=perm
173 )
173 )
174 else:
174 else:
175 RepoModel().grant_users_group_permission(
175 RepoModel().grant_users_group_permission(
176 repo=cur_repo, group_name=member, perm=perm
176 repo=cur_repo, group_name=member, perm=perm
177 )
177 )
178
178
179 # update current repo
179 # update current repo
180 for k, v in form_data.items():
180 for k, v in form_data.items():
181 if k == 'user':
181 if k == 'user':
182 cur_repo.user = User.get_by_username(v)
182 cur_repo.user = User.get_by_username(v)
183 elif k == 'repo_name':
183 elif k == 'repo_name':
184 pass
184 pass
185 elif k == 'repo_group':
185 elif k == 'repo_group':
186 cur_repo.group = RepoGroup.get(v)
186 cur_repo.group = RepoGroup.get(v)
187
187
188 else:
188 else:
189 setattr(cur_repo, k, v)
189 setattr(cur_repo, k, v)
190
190
191 new_name = cur_repo.get_new_name(form_data['repo_name'])
191 new_name = cur_repo.get_new_name(form_data['repo_name'])
192 cur_repo.repo_name = new_name
192 cur_repo.repo_name = new_name
193
193
194 self.sa.add(cur_repo)
194 self.sa.add(cur_repo)
195
195
196 if repo_name != new_name:
196 if repo_name != new_name:
197 # rename repository
197 # rename repository
198 self.__rename_repo(old=repo_name, new=new_name)
198 self.__rename_repo(old=repo_name, new=new_name)
199
199
200 return cur_repo
200 return cur_repo
201 except:
201 except:
202 log.error(traceback.format_exc())
202 log.error(traceback.format_exc())
203 raise
203 raise
204
204
205 def create_repo(self, repo_name, repo_type, description, owner,
205 def create_repo(self, repo_name, repo_type, description, owner,
206 private=False, clone_uri=None, repos_group=None,
206 private=False, clone_uri=None, repos_group=None,
207 landing_rev='tip', just_db=False, fork_of=None,
207 landing_rev='tip', just_db=False, fork_of=None,
208 copy_fork_permissions=False):
208 copy_fork_permissions=False):
209 """
209 """
210 Create repository
210 Create repository
211
211
212 """
212 """
213 from rhodecode.model.scm import ScmModel
213 from rhodecode.model.scm import ScmModel
214
214
215 owner = self._get_user(owner)
215 owner = self._get_user(owner)
216 fork_of = self._get_repo(fork_of)
216 fork_of = self._get_repo(fork_of)
217 repos_group = self._get_repos_group(repos_group)
217 repos_group = self._get_repos_group(repos_group)
218 try:
218 try:
219
219
220 # repo name is just a name of repository
220 # repo name is just a name of repository
221 # while repo_name_full is a full qualified name that is combined
221 # while repo_name_full is a full qualified name that is combined
222 # with name and path of group
222 # with name and path of group
223 repo_name_full = repo_name
223 repo_name_full = repo_name
224 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
224 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
225
225
226 new_repo = Repository()
226 new_repo = Repository()
227 new_repo.enable_statistics = False
227 new_repo.enable_statistics = False
228 new_repo.repo_name = repo_name_full
228 new_repo.repo_name = repo_name_full
229 new_repo.repo_type = repo_type
229 new_repo.repo_type = repo_type
230 new_repo.user = owner
230 new_repo.user = owner
231 new_repo.group = repos_group
231 new_repo.group = repos_group
232 new_repo.description = description or repo_name
232 new_repo.description = description or repo_name
233 new_repo.private = private
233 new_repo.private = private
234 new_repo.clone_uri = clone_uri
234 new_repo.clone_uri = clone_uri
235 new_repo.landing_rev = landing_rev
235 new_repo.landing_rev = landing_rev
236
236
237 if repos_group:
237 if repos_group:
238 new_repo.enable_locking = repos_group.enable_locking
238 new_repo.enable_locking = repos_group.enable_locking
239
239
240 if fork_of:
240 if fork_of:
241 parent_repo = fork_of
241 parent_repo = fork_of
242 new_repo.fork = parent_repo
242 new_repo.fork = parent_repo
243
243
244 self.sa.add(new_repo)
244 self.sa.add(new_repo)
245
245
246 def _create_default_perms():
246 def _create_default_perms():
247 # create default permission
247 # create default permission
248 repo_to_perm = UserRepoToPerm()
248 repo_to_perm = UserRepoToPerm()
249 default = 'repository.read'
249 default = 'repository.read'
250 for p in User.get_by_username('default').user_perms:
250 for p in User.get_by_username('default').user_perms:
251 if p.permission.permission_name.startswith('repository.'):
251 if p.permission.permission_name.startswith('repository.'):
252 default = p.permission.permission_name
252 default = p.permission.permission_name
253 break
253 break
254
254
255 default_perm = 'repository.none' if private else default
255 default_perm = 'repository.none' if private else default
256
256
257 repo_to_perm.permission_id = self.sa.query(Permission)\
257 repo_to_perm.permission_id = self.sa.query(Permission)\
258 .filter(Permission.permission_name == default_perm)\
258 .filter(Permission.permission_name == default_perm)\
259 .one().permission_id
259 .one().permission_id
260
260
261 repo_to_perm.repository = new_repo
261 repo_to_perm.repository = new_repo
262 repo_to_perm.user_id = User.get_by_username('default').user_id
262 repo_to_perm.user_id = User.get_by_username('default').user_id
263
263
264 self.sa.add(repo_to_perm)
264 self.sa.add(repo_to_perm)
265
265
266 if fork_of:
266 if fork_of:
267 if copy_fork_permissions:
267 if copy_fork_permissions:
268 repo = fork_of
268 repo = fork_of
269 user_perms = UserRepoToPerm.query()\
269 user_perms = UserRepoToPerm.query()\
270 .filter(UserRepoToPerm.repository == repo).all()
270 .filter(UserRepoToPerm.repository == repo).all()
271 group_perms = UsersGroupRepoToPerm.query()\
271 group_perms = UsersGroupRepoToPerm.query()\
272 .filter(UsersGroupRepoToPerm.repository == repo).all()
272 .filter(UsersGroupRepoToPerm.repository == repo).all()
273
273
274 for perm in user_perms:
274 for perm in user_perms:
275 UserRepoToPerm.create(perm.user, new_repo,
275 UserRepoToPerm.create(perm.user, new_repo,
276 perm.permission)
276 perm.permission)
277
277
278 for perm in group_perms:
278 for perm in group_perms:
279 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
279 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
280 perm.permission)
280 perm.permission)
281 else:
281 else:
282 _create_default_perms()
282 _create_default_perms()
283 else:
283 else:
284 _create_default_perms()
284 _create_default_perms()
285
285
286 if not just_db:
286 if not just_db:
287 self.__create_repo(repo_name, repo_type,
287 self.__create_repo(repo_name, repo_type,
288 repos_group,
288 repos_group,
289 clone_uri)
289 clone_uri)
290 log_create_repository(new_repo.get_dict(),
290 log_create_repository(new_repo.get_dict(),
291 created_by=owner.username)
291 created_by=owner.username)
292
292
293 # now automatically start following this repository as owner
293 # now automatically start following this repository as owner
294 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
294 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
295 owner.user_id)
295 owner.user_id)
296 return new_repo
296 return new_repo
297 except:
297 except:
298 log.error(traceback.format_exc())
298 log.error(traceback.format_exc())
299 raise
299 raise
300
300
301 def create(self, form_data, cur_user, just_db=False, fork=None):
301 def create(self, form_data, cur_user, just_db=False, fork=None):
302 """
302 """
303 Backward compatibility function, just a wrapper on top of create_repo
303 Backward compatibility function, just a wrapper on top of create_repo
304
304
305 :param form_data:
305 :param form_data:
306 :param cur_user:
306 :param cur_user:
307 :param just_db:
307 :param just_db:
308 :param fork:
308 :param fork:
309 """
309 """
310
310
311 repo_name = form_data['repo_name_full']
311 repo_name = form_data['repo_name_full']
312 repo_type = form_data['repo_type']
312 repo_type = form_data['repo_type']
313 description = form_data['description']
313 description = form_data['description']
314 owner = cur_user
314 owner = cur_user
315 private = form_data['private']
315 private = form_data['private']
316 clone_uri = form_data.get('clone_uri')
316 clone_uri = form_data.get('clone_uri')
317 repos_group = form_data['repo_group']
317 repos_group = form_data['repo_group']
318 landing_rev = form_data['landing_rev']
318 landing_rev = form_data['landing_rev']
319 copy_fork_permissions = form_data.get('copy_permissions')
319 copy_fork_permissions = form_data.get('copy_permissions')
320 fork_of = form_data.get('fork_parent_id')
320 fork_of = form_data.get('fork_parent_id')
321 return self.create_repo(
321 return self.create_repo(
322 repo_name, repo_type, description, owner, private, clone_uri,
322 repo_name, repo_type, description, owner, private, clone_uri,
323 repos_group, landing_rev, just_db, fork_of, copy_fork_permissions
323 repos_group, landing_rev, just_db, fork_of, copy_fork_permissions
324 )
324 )
325
325
326 def create_fork(self, form_data, cur_user):
326 def create_fork(self, form_data, cur_user):
327 """
327 """
328 Simple wrapper into executing celery task for fork creation
328 Simple wrapper into executing celery task for fork creation
329
329
330 :param form_data:
330 :param form_data:
331 :param cur_user:
331 :param cur_user:
332 """
332 """
333 from rhodecode.lib.celerylib import tasks, run_task
333 from rhodecode.lib.celerylib import tasks, run_task
334 run_task(tasks.create_repo_fork, form_data, cur_user)
334 run_task(tasks.create_repo_fork, form_data, cur_user)
335
335
336 def delete(self, repo):
336 def delete(self, repo):
337 repo = self._get_repo(repo)
337 repo = self._get_repo(repo)
338 if repo:
338 if repo:
339 old_repo_dict = repo.get_dict()
340 owner = repo.user
339 try:
341 try:
340 self.sa.delete(repo)
342 self.sa.delete(repo)
341 self.__delete_repo(repo)
343 self.__delete_repo(repo)
344 log_delete_repository(old_repo_dict,
345 deleted_by=owner.username)
342 except:
346 except:
343 log.error(traceback.format_exc())
347 log.error(traceback.format_exc())
344 raise
348 raise
345
349
346 def grant_user_permission(self, repo, user, perm):
350 def grant_user_permission(self, repo, user, perm):
347 """
351 """
348 Grant permission for user on given repository, or update existing one
352 Grant permission for user on given repository, or update existing one
349 if found
353 if found
350
354
351 :param repo: Instance of Repository, repository_id, or repository name
355 :param repo: Instance of Repository, repository_id, or repository name
352 :param user: Instance of User, user_id or username
356 :param user: Instance of User, user_id or username
353 :param perm: Instance of Permission, or permission_name
357 :param perm: Instance of Permission, or permission_name
354 """
358 """
355 user = self._get_user(user)
359 user = self._get_user(user)
356 repo = self._get_repo(repo)
360 repo = self._get_repo(repo)
357 permission = self._get_perm(perm)
361 permission = self._get_perm(perm)
358
362
359 # check if we have that permission already
363 # check if we have that permission already
360 obj = self.sa.query(UserRepoToPerm)\
364 obj = self.sa.query(UserRepoToPerm)\
361 .filter(UserRepoToPerm.user == user)\
365 .filter(UserRepoToPerm.user == user)\
362 .filter(UserRepoToPerm.repository == repo)\
366 .filter(UserRepoToPerm.repository == repo)\
363 .scalar()
367 .scalar()
364 if obj is None:
368 if obj is None:
365 # create new !
369 # create new !
366 obj = UserRepoToPerm()
370 obj = UserRepoToPerm()
367 obj.repository = repo
371 obj.repository = repo
368 obj.user = user
372 obj.user = user
369 obj.permission = permission
373 obj.permission = permission
370 self.sa.add(obj)
374 self.sa.add(obj)
371 log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
375 log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
372
376
373 def revoke_user_permission(self, repo, user):
377 def revoke_user_permission(self, repo, user):
374 """
378 """
375 Revoke permission for user on given repository
379 Revoke permission for user on given repository
376
380
377 :param repo: Instance of Repository, repository_id, or repository name
381 :param repo: Instance of Repository, repository_id, or repository name
378 :param user: Instance of User, user_id or username
382 :param user: Instance of User, user_id or username
379 """
383 """
380
384
381 user = self._get_user(user)
385 user = self._get_user(user)
382 repo = self._get_repo(repo)
386 repo = self._get_repo(repo)
383
387
384 obj = self.sa.query(UserRepoToPerm)\
388 obj = self.sa.query(UserRepoToPerm)\
385 .filter(UserRepoToPerm.repository == repo)\
389 .filter(UserRepoToPerm.repository == repo)\
386 .filter(UserRepoToPerm.user == user)\
390 .filter(UserRepoToPerm.user == user)\
387 .scalar()
391 .scalar()
388 if obj:
392 if obj:
389 self.sa.delete(obj)
393 self.sa.delete(obj)
390 log.debug('Revoked perm on %s on %s' % (repo, user))
394 log.debug('Revoked perm on %s on %s' % (repo, user))
391
395
392 def grant_users_group_permission(self, repo, group_name, perm):
396 def grant_users_group_permission(self, repo, group_name, perm):
393 """
397 """
394 Grant permission for users group on given repository, or update
398 Grant permission for users group on given repository, or update
395 existing one if found
399 existing one if found
396
400
397 :param repo: Instance of Repository, repository_id, or repository name
401 :param repo: Instance of Repository, repository_id, or repository name
398 :param group_name: Instance of UserGroup, users_group_id,
402 :param group_name: Instance of UserGroup, users_group_id,
399 or users group name
403 or users group name
400 :param perm: Instance of Permission, or permission_name
404 :param perm: Instance of Permission, or permission_name
401 """
405 """
402 repo = self._get_repo(repo)
406 repo = self._get_repo(repo)
403 group_name = self.__get_users_group(group_name)
407 group_name = self.__get_users_group(group_name)
404 permission = self._get_perm(perm)
408 permission = self._get_perm(perm)
405
409
406 # check if we have that permission already
410 # check if we have that permission already
407 obj = self.sa.query(UsersGroupRepoToPerm)\
411 obj = self.sa.query(UsersGroupRepoToPerm)\
408 .filter(UsersGroupRepoToPerm.users_group == group_name)\
412 .filter(UsersGroupRepoToPerm.users_group == group_name)\
409 .filter(UsersGroupRepoToPerm.repository == repo)\
413 .filter(UsersGroupRepoToPerm.repository == repo)\
410 .scalar()
414 .scalar()
411
415
412 if obj is None:
416 if obj is None:
413 # create new
417 # create new
414 obj = UsersGroupRepoToPerm()
418 obj = UsersGroupRepoToPerm()
415
419
416 obj.repository = repo
420 obj.repository = repo
417 obj.users_group = group_name
421 obj.users_group = group_name
418 obj.permission = permission
422 obj.permission = permission
419 self.sa.add(obj)
423 self.sa.add(obj)
420 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
424 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
421
425
422 def revoke_users_group_permission(self, repo, group_name):
426 def revoke_users_group_permission(self, repo, group_name):
423 """
427 """
424 Revoke permission for users group on given repository
428 Revoke permission for users group on given repository
425
429
426 :param repo: Instance of Repository, repository_id, or repository name
430 :param repo: Instance of Repository, repository_id, or repository name
427 :param group_name: Instance of UserGroup, users_group_id,
431 :param group_name: Instance of UserGroup, users_group_id,
428 or users group name
432 or users group name
429 """
433 """
430 repo = self._get_repo(repo)
434 repo = self._get_repo(repo)
431 group_name = self.__get_users_group(group_name)
435 group_name = self.__get_users_group(group_name)
432
436
433 obj = self.sa.query(UsersGroupRepoToPerm)\
437 obj = self.sa.query(UsersGroupRepoToPerm)\
434 .filter(UsersGroupRepoToPerm.repository == repo)\
438 .filter(UsersGroupRepoToPerm.repository == repo)\
435 .filter(UsersGroupRepoToPerm.users_group == group_name)\
439 .filter(UsersGroupRepoToPerm.users_group == group_name)\
436 .scalar()
440 .scalar()
437 if obj:
441 if obj:
438 self.sa.delete(obj)
442 self.sa.delete(obj)
439 log.debug('Revoked perm to %s on %s' % (repo, group_name))
443 log.debug('Revoked perm to %s on %s' % (repo, group_name))
440
444
441 def delete_stats(self, repo_name):
445 def delete_stats(self, repo_name):
442 """
446 """
443 removes stats for given repo
447 removes stats for given repo
444
448
445 :param repo_name:
449 :param repo_name:
446 """
450 """
447 try:
451 try:
448 obj = self.sa.query(Statistics)\
452 obj = self.sa.query(Statistics)\
449 .filter(Statistics.repository ==
453 .filter(Statistics.repository ==
450 self.get_by_repo_name(repo_name))\
454 self.get_by_repo_name(repo_name))\
451 .one()
455 .one()
452 self.sa.delete(obj)
456 self.sa.delete(obj)
453 except:
457 except:
454 log.error(traceback.format_exc())
458 log.error(traceback.format_exc())
455 raise
459 raise
456
460
457 def __create_repo(self, repo_name, alias, parent, clone_uri=False):
461 def __create_repo(self, repo_name, alias, parent, clone_uri=False):
458 """
462 """
459 makes repository on filesystem. It's group aware means it'll create
463 makes repository on filesystem. It's group aware means it'll create
460 a repository within a group, and alter the paths accordingly of
464 a repository within a group, and alter the paths accordingly of
461 group location
465 group location
462
466
463 :param repo_name:
467 :param repo_name:
464 :param alias:
468 :param alias:
465 :param parent_id:
469 :param parent_id:
466 :param clone_uri:
470 :param clone_uri:
467 """
471 """
468 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
472 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
469 from rhodecode.model.scm import ScmModel
473 from rhodecode.model.scm import ScmModel
470
474
471 if parent:
475 if parent:
472 new_parent_path = os.sep.join(parent.full_path_splitted)
476 new_parent_path = os.sep.join(parent.full_path_splitted)
473 else:
477 else:
474 new_parent_path = ''
478 new_parent_path = ''
475
479
476 # we need to make it str for mercurial
480 # we need to make it str for mercurial
477 repo_path = os.path.join(*map(lambda x: safe_str(x),
481 repo_path = os.path.join(*map(lambda x: safe_str(x),
478 [self.repos_path, new_parent_path, repo_name]))
482 [self.repos_path, new_parent_path, repo_name]))
479
483
480 # check if this path is not a repository
484 # check if this path is not a repository
481 if is_valid_repo(repo_path, self.repos_path):
485 if is_valid_repo(repo_path, self.repos_path):
482 raise Exception('This path %s is a valid repository' % repo_path)
486 raise Exception('This path %s is a valid repository' % repo_path)
483
487
484 # check if this path is a group
488 # check if this path is a group
485 if is_valid_repos_group(repo_path, self.repos_path):
489 if is_valid_repos_group(repo_path, self.repos_path):
486 raise Exception('This path %s is a valid group' % repo_path)
490 raise Exception('This path %s is a valid group' % repo_path)
487
491
488 log.info('creating repo %s in %s @ %s' % (
492 log.info('creating repo %s in %s @ %s' % (
489 repo_name, safe_unicode(repo_path), clone_uri
493 repo_name, safe_unicode(repo_path), clone_uri
490 )
494 )
491 )
495 )
492 backend = get_backend(alias)
496 backend = get_backend(alias)
493 if alias == 'hg':
497 if alias == 'hg':
494 backend(repo_path, create=True, src_url=clone_uri)
498 backend(repo_path, create=True, src_url=clone_uri)
495 elif alias == 'git':
499 elif alias == 'git':
496 r = backend(repo_path, create=True, src_url=clone_uri, bare=True)
500 r = backend(repo_path, create=True, src_url=clone_uri, bare=True)
497 # add rhodecode hook into this repo
501 # add rhodecode hook into this repo
498 ScmModel().install_git_hook(repo=r)
502 ScmModel().install_git_hook(repo=r)
499 else:
503 else:
500 raise Exception('Undefined alias %s' % alias)
504 raise Exception('Undefined alias %s' % alias)
501
505
502 def __rename_repo(self, old, new):
506 def __rename_repo(self, old, new):
503 """
507 """
504 renames repository on filesystem
508 renames repository on filesystem
505
509
506 :param old: old name
510 :param old: old name
507 :param new: new name
511 :param new: new name
508 """
512 """
509 log.info('renaming repo from %s to %s' % (old, new))
513 log.info('renaming repo from %s to %s' % (old, new))
510
514
511 old_path = os.path.join(self.repos_path, old)
515 old_path = os.path.join(self.repos_path, old)
512 new_path = os.path.join(self.repos_path, new)
516 new_path = os.path.join(self.repos_path, new)
513 if os.path.isdir(new_path):
517 if os.path.isdir(new_path):
514 raise Exception(
518 raise Exception(
515 'Was trying to rename to already existing dir %s' % new_path
519 'Was trying to rename to already existing dir %s' % new_path
516 )
520 )
517 shutil.move(old_path, new_path)
521 shutil.move(old_path, new_path)
518
522
519 def __delete_repo(self, repo):
523 def __delete_repo(self, repo):
520 """
524 """
521 removes repo from filesystem, the removal is acctually made by
525 removes repo from filesystem, the removal is acctually made by
522 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
526 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
523 repository is no longer valid for rhodecode, can be undeleted later on
527 repository is no longer valid for rhodecode, can be undeleted later on
524 by reverting the renames on this repository
528 by reverting the renames on this repository
525
529
526 :param repo: repo object
530 :param repo: repo object
527 """
531 """
528 rm_path = os.path.join(self.repos_path, repo.repo_name)
532 rm_path = os.path.join(self.repos_path, repo.repo_name)
529 log.info("Removing %s" % (rm_path))
533 log.info("Removing %s" % (rm_path))
530 # disable hg/git internal that it doesn't get detected as repo
534 # disable hg/git internal that it doesn't get detected as repo
531 alias = repo.repo_type
535 alias = repo.repo_type
532
536
533 bare = getattr(repo.scm_instance, 'bare', False)
537 bare = getattr(repo.scm_instance, 'bare', False)
534
538
535 if not bare:
539 if not bare:
536 # skip this for bare git repos
540 # skip this for bare git repos
537 shutil.move(os.path.join(rm_path, '.%s' % alias),
541 shutil.move(os.path.join(rm_path, '.%s' % alias),
538 os.path.join(rm_path, 'rm__.%s' % alias))
542 os.path.join(rm_path, 'rm__.%s' % alias))
539 # disable repo
543 # disable repo
540 _now = datetime.now()
544 _now = datetime.now()
541 _ms = str(_now.microsecond).rjust(6, '0')
545 _ms = str(_now.microsecond).rjust(6, '0')
542 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
546 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
543 repo.repo_name)
547 repo.repo_name)
544 shutil.move(rm_path, os.path.join(self.repos_path, _d))
548 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,246 +1,246 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${_('My Account')}
9 ${_('My Account')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17
17
18 <div class="box box-left">
18 <div class="box box-left">
19 <!-- box / title -->
19 <!-- box / title -->
20 <div class="title">
20 <div class="title">
21 ${self.breadcrumbs()}
21 ${self.breadcrumbs()}
22 </div>
22 </div>
23 <!-- end box / title -->
23 <!-- end box / title -->
24 ${c.form|n}
24 ${c.form|n}
25 </div>
25 </div>
26
26
27 <div class="box box-right">
27 <div class="box box-right">
28 <!-- box / title -->
28 <!-- box / title -->
29 <div class="title">
29 <div class="title">
30 <h5>
30 <h5>
31 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}" style="display: none"/>
31 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}" style="display: none"/>
32 </h5>
32 </h5>
33 <ul class="links" style="color:#DADADA">
33 <ul class="links" style="color:#DADADA">
34 <li>
34 <li>
35 <span><a id="show_perms" class="link-white current" href="#perms">${_('My permissions')}</a> </span>
35 <span><a id="show_perms" class="link-white current" href="#perms">${_('My permissions')}</a> </span>
36 </li>
36 </li>
37 <li>
37 <li>
38 <span><a id="show_my" class="link-white" href="#my">${_('My repos')}</a> </span>
38 <span><a id="show_my" class="link-white" href="#my">${_('My repos')}</a> </span>
39 </li>
39 </li>
40 <li>
40 <li>
41 <span><a id="show_pullrequests" class="link-white" href="#pullrequests">${_('My pull requests')}</a> </span>
41 <span><a id="show_pullrequests" class="link-white" href="#pullrequests">${_('My pull requests')}</a> </span>
42 </li>
42 </li>
43 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
43 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
44 <li>
44 <li>
45 <span>${h.link_to(_('Add repo'),h.url('admin_settings_create_repository'))}</span>
45 <span>${h.link_to(_('Add repo'),h.url('admin_settings_create_repository'))}</span>
46 </li>
46 </li>
47 %endif
47 %endif
48 </ul>
48 </ul>
49 </div>
49 </div>
50 <!-- end box / title -->
50 <!-- end box / title -->
51 <div id="perms" class="table">
51 <div id="perms" class="table">
52 %for section in sorted(c.rhodecode_user.permissions.keys()):
52 %for section in sorted(c.rhodecode_user.permissions.keys()):
53 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
53 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
54
54
55 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
55 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
56 <table id="tbl_list_${section}">
56 <table id="tbl_list_${section}">
57 <thead>
57 <thead>
58 <tr>
58 <tr>
59 <th class="left">${_('Name')}</th>
59 <th class="left">${_('Name')}</th>
60 <th class="left">${_('Permission')}</th>
60 <th class="left">${_('Permission')}</th>
61 </thead>
61 </thead>
62 <tbody>
62 <tbody>
63 %for k in c.rhodecode_user.permissions[section]:
63 %for k in c.rhodecode_user.permissions[section]:
64 <%
64 <%
65 if section != 'global':
65 if section != 'global':
66 section_perm = c.rhodecode_user.permissions[section].get(k)
66 section_perm = c.rhodecode_user.permissions[section].get(k)
67 _perm = section_perm.split('.')[-1]
67 _perm = section_perm.split('.')[-1]
68 else:
68 else:
69 _perm = section_perm = None
69 _perm = section_perm = None
70 %>
70 %>
71 %if _perm not in ['none']:
71 %if _perm not in ['none']:
72 <tr>
72 <tr>
73 <td>
73 <td>
74 %if section == 'repositories':
74 %if section == 'repositories':
75 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
75 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
76 %elif section == 'repositories_groups':
76 %elif section == 'repositories_groups':
77 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
77 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
78 %else:
78 %else:
79 ${k}
79 ${k}
80 %endif
80 %endif
81 </td>
81 </td>
82 <td>
82 <td>
83 %if section == 'global':
83 %if section == 'global':
84 ${h.bool2icon(True)}
84 ${h.bool2icon(True)}
85 %else:
85 %else:
86 <span class="perm_tag ${_perm}">${section_perm}</span>
86 <span class="perm_tag ${_perm}">${section_perm}</span>
87 %endif
87 %endif
88 </td>
88 </td>
89 </tr>
89 </tr>
90 %endif
90 %endif
91 %endfor
91 %endfor
92 </tbody>
92 </tbody>
93 </table>
93 </table>
94 </div>
94 </div>
95 %endfor
95 %endfor
96 </div>
96 </div>
97 <div id="my" class="table" style="display:none">
97 <div id="my" class="table" style="display:none">
98 </div>
98 </div>
99 <div id="pullrequests" class="table" style="display:none"></div>
99 <div id="pullrequests" class="table" style="display:none"></div>
100 </div>
100 </div>
101
101
102
102
103
103
104 <script type="text/javascript">
104 <script type="text/javascript">
105 var filter_activate = function(){
105 var filter_activate = function(){
106 var nodes = YUQ('#my tr td a.repo_name');
106 var nodes = YUQ('#my tr td a.repo_name');
107 var func = function(node){
107 var func = function(node){
108 return node.parentNode.parentNode.parentNode.parentNode;
108 return node.parentNode.parentNode.parentNode.parentNode;
109 }
109 }
110 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
110 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
111 }
111 }
112
112
113 var show_perms = function(e){
113 var show_perms = function(e){
114 YUD.addClass('show_perms', 'current');
114 YUD.addClass('show_perms', 'current');
115 YUD.removeClass('show_my','current');
115 YUD.removeClass('show_my','current');
116 YUD.removeClass('show_pullrequests','current');
116 YUD.removeClass('show_pullrequests','current');
117
117
118 YUD.setStyle('my','display','none');
118 YUD.setStyle('my','display','none');
119 YUD.setStyle('pullrequests','display','none');
119 YUD.setStyle('pullrequests','display','none');
120 YUD.setStyle('perms','display','');
120 YUD.setStyle('perms','display','');
121 YUD.setStyle('q_filter','display','none');
121 YUD.setStyle('q_filter','display','none');
122 }
122 }
123 YUE.on('show_perms','click',function(e){
123 YUE.on('show_perms','click',function(e){
124 show_perms();
124 show_perms();
125 })
125 })
126
126
127 var show_my = function(e){
127 var show_my = function(e){
128 YUD.addClass('show_my', 'current');
128 YUD.addClass('show_my', 'current');
129 YUD.removeClass('show_perms','current');
129 YUD.removeClass('show_perms','current');
130 YUD.removeClass('show_pullrequests','current');
130 YUD.removeClass('show_pullrequests','current');
131
131
132 YUD.setStyle('perms','display','none');
132 YUD.setStyle('perms','display','none');
133 YUD.setStyle('pullrequests','display','none');
133 YUD.setStyle('pullrequests','display','none');
134 YUD.setStyle('my','display','');
134 YUD.setStyle('my','display','');
135 YUD.setStyle('q_filter','display','');
135 YUD.setStyle('q_filter','display','');
136
136
137
137
138 var url = "${h.url('admin_settings_my_repos')}";
138 var url = "${h.url('admin_settings_my_repos')}";
139 ypjax(url, 'my', function(){
139 ypjax(url, 'my', function(){
140 table_sort();
140 table_sort();
141 filter_activate();
141 filter_activate();
142 });
142 });
143 }
143 }
144 YUE.on('show_my','click',function(e){
144 YUE.on('show_my','click',function(e){
145 show_my(e);
145 show_my(e);
146 })
146 })
147
147
148 var show_pullrequests = function(e){
148 var show_pullrequests = function(e){
149 YUD.addClass('show_pullrequests', 'current');
149 YUD.addClass('show_pullrequests', 'current');
150 YUD.removeClass('show_my','current');
150 YUD.removeClass('show_my','current');
151 YUD.removeClass('show_perms','current');
151 YUD.removeClass('show_perms','current');
152
152
153 YUD.setStyle('my','display','none');
153 YUD.setStyle('my','display','none');
154 YUD.setStyle('perms','display','none');
154 YUD.setStyle('perms','display','none');
155 YUD.setStyle('pullrequests','display','');
155 YUD.setStyle('pullrequests','display','');
156 YUD.setStyle('q_filter','display','none');
156 YUD.setStyle('q_filter','display','none');
157
157
158 var url = "${h.url('admin_settings_my_pullrequests')}";
158 var url = "${h.url('admin_settings_my_pullrequests')}";
159 ypjax(url, 'pullrequests');
159 ypjax(url, 'pullrequests');
160 }
160 }
161 YUE.on('show_pullrequests','click',function(e){
161 YUE.on('show_pullrequests','click',function(e){
162 show_pullrequests(e)
162 show_pullrequests(e)
163 })
163 })
164
164
165 var tabs = {
165 var tabs = {
166 'perms': show_perms,
166 'perms': show_perms,
167 'my': show_my,
167 'my': show_my,
168 'pullrequests': show_pullrequests
168 'pullrequests': show_pullrequests
169 }
169 }
170 var url = location.href.split('#');
170 var url = location.href.split('#');
171 if (url[1]) {
171 if (url[1]) {
172 //We have a hash
172 //We have a hash
173 var tabHash = url[1];
173 var tabHash = url[1];
174 console.log(tabs, tabHash)
174 console.log(tabs, tabHash)
175 tabs[tabHash]();
175 tabs[tabHash]();
176 }
176 }
177
177
178 // main table sorting
178 // main table sorting
179 var myColumnDefs = [
179 var myColumnDefs = [
180 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
180 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
181 {key:"name",label:"${_('Name')}",sortable:true,
181 {key:"name",label:"${_('Name')}",sortable:true,
182 sortOptions: { sortFunction: nameSort }},
182 sortOptions: { sortFunction: nameSort }},
183 {key:"tip",label:"${_('Tip')}",sortable:true,
183 {key:"tip",label:"${_('Tip')}",sortable:true,
184 sortOptions: { sortFunction: revisionSort }},
184 sortOptions: { sortFunction: revisionSort }},
185 {key:"action1",label:"",sortable:false},
185 {key:"action1",label:"",sortable:false},
186 {key:"action2",label:"",sortable:false},
186 {key:"action2",label:"",sortable:false},
187 ];
187 ];
188
188
189 function table_sort(){
189 function table_sort(){
190 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
190 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
191 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
191 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
192 myDataSource.responseSchema = {
192 myDataSource.responseSchema = {
193 fields: [
193 fields: [
194 {key:"menu"},
194 {key:"menu"},
195 {key:"name"},
195 {key:"name"},
196 {key:"tip"},
196 {key:"tip"},
197 {key:"action1"},
197 {key:"action1"},
198 {key:"action2"},
198 {key:"action2"},
199 ]
199 ]
200 };
200 };
201 var trans_defs = {
201 var trans_defs = {
202 sortedBy:{key:"name",dir:"asc"},
202 sortedBy:{key:"name",dir:"asc"},
203 MSG_SORTASC:"${_('Click to sort ascending')}",
203 MSG_SORTASC:"${_('Click to sort ascending')}",
204 MSG_SORTDESC:"${_('Click to sort descending')}",
204 MSG_SORTDESC:"${_('Click to sort descending')}",
205 MSG_EMPTY:"${_('No records found.')}",
205 MSG_EMPTY:"${_('No records found.')}",
206 MSG_ERROR:"${_('Data error.')}",
206 MSG_ERROR:"${_('Data error.')}",
207 MSG_LOADING:"${_('Loading...')}",
207 MSG_LOADING:"${_('Loading...')}",
208 }
208 }
209 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,trans_defs);
209 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,trans_defs);
210 myDataTable.subscribe('postRenderEvent',function(oArgs) {
210 myDataTable.subscribe('postRenderEvent',function(oArgs) {
211 tooltip_activate();
211 tooltip_activate();
212 quick_repo_menu();
212 quick_repo_menu();
213 filter_activate();
213 filter_activate();
214 });
214 });
215
215
216 var permsColumnDefs = [
216 var permsColumnDefs = [
217 {key:"name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: permNameSort }},
217 {key:"name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: permNameSort }},
218 {key:"perm",label:"${_('Permission')}",sortable:false,},
218 {key:"perm",label:"${_('Permission')}",sortable:false,},
219 ];
219 ];
220
220
221 // perms repos table
221 // perms repos table
222 var myDataSource2 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories"));
222 var myDataSource2 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories"));
223 myDataSource2.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
223 myDataSource2.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
224 myDataSource2.responseSchema = {
224 myDataSource2.responseSchema = {
225 fields: [
225 fields: [
226 {key:"name"},
226 {key:"name"},
227 {key:"perm"},
227 {key:"perm"},
228 ]
228 ]
229 };
229 };
230
230
231 new YAHOO.widget.DataTable("tbl_list_wrap_repositories", permsColumnDefs, myDataSource2, trans_defs);
231 new YAHOO.widget.DataTable("tbl_list_wrap_repositories", permsColumnDefs, myDataSource2, trans_defs);
232
232
233 //perms groups table
233 //perms groups table
234 var myDataSource3 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories_groups"));
234 var myDataSource3 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories_groups"));
235 myDataSource3.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
235 myDataSource3.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
236 myDataSource3.responseSchema = {
236 myDataSource3.responseSchema = {
237 fields: [
237 fields: [
238 {key:"name"},
238 {key:"name"},
239 {key:"perm"},
239 {key:"perm"},
240 ]
240 ]
241 };
241 };
242
242
243 new YAHOO.widget.DataTable("tbl_list_wrap_repositories_groups", permsColumnDefs, myDataSource3, trans_defs);
243 new YAHOO.widget.DataTable("tbl_list_wrap_repositories_groups", permsColumnDefs, myDataSource3, trans_defs);
244 }
244 }
245 </script>
245 </script>
246 </%def>
246 </%def>
@@ -1,200 +1,200 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_(u'Home'),h.url('/'))}
8 ${h.link_to(_(u'Home'),h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('New pull request')}
12 ${_('New pull request')}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
23 <div style="float:left;padding:0px 30px 30px 30px">
23 <div style="float:left;padding:0px 30px 30px 30px">
24
24
25 ##ORG
25 ##ORG
26 <div style="float:left">
26 <div style="float:left">
27 <div class="fork_user">
27 <div class="fork_user">
28 <div class="gravatar">
28 <div class="gravatar">
29 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
29 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
30 </div>
30 </div>
31 <span style="font-size: 20px">
31 <span style="font-size: 20px">
32 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')}
32 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')}
33 </span>
33 </span>
34 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
34 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
35 </div>
35 </div>
36 <div style="clear:both;padding-top: 10px"></div>
36 <div style="clear:both;padding-top: 10px"></div>
37 </div>
37 </div>
38 <div style="float:left;font-size:24px;padding:0px 20px">
38 <div style="float:left;font-size:24px;padding:0px 20px">
39 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
39 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
40 </div>
40 </div>
41
41
42 ##OTHER, most Probably the PARENT OF THIS FORK
42 ##OTHER, most Probably the PARENT OF THIS FORK
43 <div style="float:left">
43 <div style="float:left">
44 <div class="fork_user">
44 <div class="fork_user">
45 <div class="gravatar">
45 <div class="gravatar">
46 <img id="other_repo_gravatar" alt="gravatar" src=""/>
46 <img id="other_repo_gravatar" alt="gravatar" src=""/>
47 </div>
47 </div>
48 <span style="font-size: 20px">
48 <span style="font-size: 20px">
49 ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_pull_request_rev,c.default_revs,class_='refs')}
49 ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref',c.default_pull_request_rev,c.default_revs,class_='refs')}
50 </span>
50 </span>
51 <span style="padding:3px">
51 <span style="padding:3px">
52 <a id="refresh" href="#" class="tooltip" title="${h.tooltip(_('refresh overview'))}">
52 <a id="refresh" href="#" class="tooltip" title="${h.tooltip(_('refresh overview'))}">
53 <img style="margin:3px" class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
53 <img style="margin:3px" class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
54 </a>
54 </a>
55 </span>
55 </span>
56 <div id="other_repo_desc" style="padding:5px 3px 3px 42px;"></div>
56 <div id="other_repo_desc" style="padding:5px 3px 3px 42px;"></div>
57 </div>
57 </div>
58 <div style="clear:both;padding-top: 10px"></div>
58 <div style="clear:both;padding-top: 10px"></div>
59 </div>
59 </div>
60 <div style="clear:both;padding-top: 10px"></div>
60 <div style="clear:both;padding-top: 10px"></div>
61 ## overview pulled by ajax
61 ## overview pulled by ajax
62 <div style="float:left" id="pull_request_overview"></div>
62 <div style="float:left" id="pull_request_overview"></div>
63 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
63 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
64 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
64 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
65 </div>
65 </div>
66 </div>
66 </div>
67 <div style="float:left; border-left:1px dashed #eee">
67 <div style="float:left; border-left:1px dashed #eee">
68 <h4>${_('Pull request reviewers')}</h4>
68 <h4>${_('Pull request reviewers')}</h4>
69 <div id="reviewers" style="padding:0px 0px 0px 15px">
69 <div id="reviewers" style="padding:0px 0px 0px 15px">
70 ## members goes here !
70 ## members goes here !
71 <div class="group_members_wrap">
71 <div class="group_members_wrap">
72 <ul id="review_members" class="group_members">
72 <ul id="review_members" class="group_members">
73 %for member in c.review_members:
73 %for member in c.review_members:
74 <li id="reviewer_${member.user_id}">
74 <li id="reviewer_${member.user_id}">
75 <div class="reviewers_member">
75 <div class="reviewers_member">
76 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
76 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
77 <div style="float:left">${member.full_name} (${_('owner')})</div>
77 <div style="float:left">${member.full_name} (${_('owner')})</div>
78 <input type="hidden" value="${member.user_id}" name="review_members" />
78 <input type="hidden" value="${member.user_id}" name="review_members" />
79 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
79 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
80 </div>
80 </div>
81 </li>
81 </li>
82 %endfor
82 %endfor
83 </ul>
83 </ul>
84 </div>
84 </div>
85
85
86 <div class='ac'>
86 <div class='ac'>
87 <div class="reviewer_ac">
87 <div class="reviewer_ac">
88 ${h.text('user', class_='yui-ac-input')}
88 ${h.text('user', class_='yui-ac-input')}
89 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
89 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
90 <div id="reviewers_container"></div>
90 <div id="reviewers_container"></div>
91 </div>
91 </div>
92 </div>
92 </div>
93 </div>
93 </div>
94 </div>
94 </div>
95 <h3>${_('Create new pull request')}</h3>
95 <h3>${_('Create new pull request')}</h3>
96
96
97 <div class="form">
97 <div class="form">
98 <!-- fields -->
98 <!-- fields -->
99
99
100 <div class="fields">
100 <div class="fields">
101
101
102 <div class="field">
102 <div class="field">
103 <div class="label">
103 <div class="label">
104 <label for="pullrequest_title">${_('Title')}:</label>
104 <label for="pullrequest_title">${_('Title')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 ${h.text('pullrequest_title',size=30)}
107 ${h.text('pullrequest_title',size=30)}
108 </div>
108 </div>
109 </div>
109 </div>
110
110
111 <div class="field">
111 <div class="field">
112 <div class="label label-textarea">
112 <div class="label label-textarea">
113 <label for="pullrequest_desc">${_('description')}:</label>
113 <label for="pullrequest_desc">${_('description')}:</label>
114 </div>
114 </div>
115 <div class="textarea text-area editor">
115 <div class="textarea text-area editor">
116 ${h.textarea('pullrequest_desc',size=30)}
116 ${h.textarea('pullrequest_desc',size=30)}
117 </div>
117 </div>
118 </div>
118 </div>
119
119
120 <div class="buttons">
120 <div class="buttons">
121 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
121 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
122 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
122 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
123 </div>
123 </div>
124 </div>
124 </div>
125 </div>
125 </div>
126 ${h.end_form()}
126 ${h.end_form()}
127
127
128 </div>
128 </div>
129
129
130 <script type="text/javascript">
130 <script type="text/javascript">
131 var _USERS_AC_DATA = ${c.users_array|n};
131 var _USERS_AC_DATA = ${c.users_array|n};
132 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
132 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
133 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
133 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
134
134
135 var other_repos_info = ${c.other_repos_info|n};
135 var other_repos_info = ${c.other_repos_info|n};
136
136
137 var loadPreview = function(){
137 var loadPreview = function(){
138 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
138 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
139 var url = "${h.url('compare_url',
139 var url = "${h.url('compare_url',
140 repo_name='org_repo',
140 repo_name='org_repo',
141 org_ref_type='org_ref_type', org_ref='org_ref',
141 org_ref_type='org_ref_type', org_ref='org_ref',
142 other_ref_type='other_ref_type', other_ref='other_ref',
142 other_ref_type='other_ref_type', other_ref='other_ref',
143 repo='other_repo',
143 repo='other_repo',
144 as_form=True, bundle=False)}";
144 as_form=True, bundle=False)}";
145
145
146 var select_refs = YUQ('#pull_request_form select.refs')
146 var select_refs = YUQ('#pull_request_form select.refs')
147 var rev_data = {}; // gather the org/other ref and repo here
147 var rev_data = {}; // gather the org/other ref and repo here
148 for(var i=0;i<select_refs.length;i++){
148 for(var i=0;i<select_refs.length;i++){
149 var select_ref = select_refs[i];
149 var select_ref = select_refs[i];
150 var select_ref_data = select_ref.value.split(':');
150 var select_ref_data = select_ref.value.split(':');
151 var key = null;
151 var key = null;
152 var val = null;
152 var val = null;
153
153
154 if(select_ref_data.length>1){
154 if(select_ref_data.length>1){
155 key = select_ref.name+"_type";
155 key = select_ref.name+"_type";
156 val = select_ref_data[0];
156 val = select_ref_data[0];
157 url = url.replace(key,val);
157 url = url.replace(key,val);
158 rev_data[key] = val;
158 rev_data[key] = val;
159
159
160 key = select_ref.name;
160 key = select_ref.name;
161 val = select_ref_data[1];
161 val = select_ref_data[1];
162 url = url.replace(key,val);
162 url = url.replace(key,val);
163 rev_data[key] = val;
163 rev_data[key] = val;
164
164
165 }else{
165 }else{
166 key = select_ref.name;
166 key = select_ref.name;
167 val = select_ref.value;
167 val = select_ref.value;
168 url = url.replace(key,val);
168 url = url.replace(key,val);
169 rev_data[key] = val;
169 rev_data[key] = val;
170 }
170 }
171 }
171 }
172
172
173 YUE.on('other_repo', 'change', function(e){
173 YUE.on('other_repo', 'change', function(e){
174 var repo_name = e.currentTarget.value;
174 var repo_name = e.currentTarget.value;
175 // replace the <select> of changed repo
175 // replace the <select> of changed repo
176 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
176 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
177 });
177 });
178
178
179 ypjax(url,'pull_request_overview', function(data){
179 ypjax(url,'pull_request_overview', function(data){
180 var sel_box = YUQ('#pull_request_form #other_repo')[0];
180 var sel_box = YUQ('#pull_request_form #other_repo')[0];
181 var repo_name = sel_box.options[sel_box.selectedIndex].value;
181 var repo_name = sel_box.options[sel_box.selectedIndex].value;
182 YUD.get('pull_request_overview_url').href = url;
182 YUD.get('pull_request_overview_url').href = url;
183 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
183 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
184 YUD.get('other_repo_gravatar').src = other_repos_info[repo_name]['gravatar'];
184 YUD.get('other_repo_gravatar').src = other_repos_info[repo_name]['gravatar'];
185 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
185 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
186 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
186 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
187 // select back the revision that was just compared
187 // select back the revision that was just compared
188 setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
188 setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
189 })
189 })
190 }
190 }
191 YUE.on('refresh','click',function(e){
191 YUE.on('refresh','click',function(e){
192 loadPreview()
192 loadPreview()
193 })
193 })
194
194
195 //lazy load overview after 0.5s
195 //lazy load overview after 0.5s
196 setTimeout(loadPreview, 500)
196 setTimeout(loadPreview, 500)
197
197
198 </script>
198 </script>
199
199
200 </%def>
200 </%def>
@@ -1,199 +1,199 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_(u'Home'),h.url('/'))}
8 ${h.link_to(_(u'Home'),h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 %if c.pull_request.is_closed():
22 %if c.pull_request.is_closed():
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div>
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))} ${_('with status %s') % h.changeset_status_lbl(c.current_changeset_status)}</div>
24 %endif
24 %endif
25 <h3>${_('Title')}: ${c.pull_request.title}</h3>
25 <h3>${_('Title')}: ${c.pull_request.title}</h3>
26
26
27 <div class="form">
27 <div class="form">
28 <div id="summary" class="fields">
28 <div id="summary" class="fields">
29 <div class="field">
29 <div class="field">
30 <div class="label-summary">
30 <div class="label-summary">
31 <label>${_('Status')}:</label>
31 <label>${_('Status')}:</label>
32 </div>
32 </div>
33 <div class="input">
33 <div class="input">
34 <div class="changeset-status-container" style="float:none;clear:both">
34 <div class="changeset-status-container" style="float:none;clear:both">
35 %if c.current_changeset_status:
35 %if c.current_changeset_status:
36 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
36 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
37 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
37 <div class="changeset-status-ico" style="padding:1px 4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
38 %endif
38 %endif
39 </div>
39 </div>
40 </div>
40 </div>
41 </div>
41 </div>
42 <div class="field">
42 <div class="field">
43 <div class="label-summary">
43 <div class="label-summary">
44 <label>${_('Still not reviewed by')}:</label>
44 <label>${_('Still not reviewed by')}:</label>
45 </div>
45 </div>
46 <div class="input">
46 <div class="input">
47 % if len(c.pull_request_pending_reviewers) > 0:
47 % if len(c.pull_request_pending_reviewers) > 0:
48 <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
48 <div class="tooltip" title="${h.tooltip(','.join([x.username for x in c.pull_request_pending_reviewers]))}">${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}</div>
49 %else:
49 %else:
50 <div>${_('pull request was reviewed by all reviewers')}</div>
50 <div>${_('pull request was reviewed by all reviewers')}</div>
51 %endif
51 %endif
52 </div>
52 </div>
53 </div>
53 </div>
54 </div>
54 </div>
55 </div>
55 </div>
56 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
56 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
57 <div style="padding:4px 4px 10px 20px">
57 <div style="padding:4px 4px 10px 20px">
58 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
58 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
59 </div>
59 </div>
60
60
61 <div style="min-height:160px">
61 <div style="min-height:160px">
62 ##DIFF
62 ##DIFF
63 <div class="table" style="float:left;clear:none">
63 <div class="table" style="float:left;clear:none">
64 <div id="body" class="diffblock">
64 <div id="body" class="diffblock">
65 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
65 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
66 </div>
66 </div>
67 <div id="changeset_compare_view_content">
67 <div id="changeset_compare_view_content">
68 ##CS
68 ##CS
69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
70 <%include file="/compare/compare_cs.html" />
70 <%include file="/compare/compare_cs.html" />
71
71
72 ## FILES
72 ## FILES
73 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
73 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
74 <div class="cs_files">
74 <div class="cs_files">
75 %for fid, change, f, stat in c.files:
75 %for fid, change, f, stat in c.files:
76 <div class="cs_${change}">
76 <div class="cs_${change}">
77 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
77 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
78 <div class="changes">${h.fancy_file_stats(stat)}</div>
78 <div class="changes">${h.fancy_file_stats(stat)}</div>
79 </div>
79 </div>
80 %endfor
80 %endfor
81 </div>
81 </div>
82 </div>
82 </div>
83 </div>
83 </div>
84 ## REVIEWERS
84 ## REVIEWERS
85 <div style="float:left; border-left:1px dashed #eee">
85 <div style="float:left; border-left:1px dashed #eee">
86 <h4>${_('Pull request reviewers')}</h4>
86 <h4>${_('Pull request reviewers')}</h4>
87 <div id="reviewers" style="padding:0px 0px 0px 15px">
87 <div id="reviewers" style="padding:0px 0px 0px 15px">
88 ## members goes here !
88 ## members goes here !
89 <div class="group_members_wrap">
89 <div class="group_members_wrap">
90 <ul id="review_members" class="group_members">
90 <ul id="review_members" class="group_members">
91 %for member,status in c.pull_request_reviewers:
91 %for member,status in c.pull_request_reviewers:
92 <li id="reviewer_${member.user_id}">
92 <li id="reviewer_${member.user_id}">
93 <div class="reviewers_member">
93 <div class="reviewers_member">
94 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
94 <div style="float:left;padding:0px 3px 0px 0px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
95 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
95 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
96 </div>
96 </div>
97 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
97 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
98 <div style="float:left">${member.full_name} (${_('owner')})</div>
98 <div style="float:left">${member.full_name} (${_('owner')})</div>
99 <input type="hidden" value="${member.user_id}" name="review_members" />
99 <input type="hidden" value="${member.user_id}" name="review_members" />
100 %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id):
100 %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id):
101 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
101 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
102 %endif
102 %endif
103 </div>
103 </div>
104 </li>
104 </li>
105 %endfor
105 %endfor
106 </ul>
106 </ul>
107 </div>
107 </div>
108 %if not c.pull_request.is_closed():
108 %if not c.pull_request.is_closed():
109 <div class='ac'>
109 <div class='ac'>
110 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
110 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
111 <div class="reviewer_ac">
111 <div class="reviewer_ac">
112 ${h.text('user', class_='yui-ac-input')}
112 ${h.text('user', class_='yui-ac-input')}
113 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
113 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
114 <div id="reviewers_container"></div>
114 <div id="reviewers_container"></div>
115 </div>
115 </div>
116 <div style="padding:0px 10px">
116 <div style="padding:0px 10px">
117 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
117 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
118 </div>
118 </div>
119 %endif
119 %endif
120 </div>
120 </div>
121 %endif
121 %endif
122 </div>
122 </div>
123 </div>
123 </div>
124 </div>
124 </div>
125 <script>
125 <script>
126 var _USERS_AC_DATA = ${c.users_array|n};
126 var _USERS_AC_DATA = ${c.users_array|n};
127 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
127 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
128 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
128 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
129 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
129 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
130 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
130 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
131 </script>
131 </script>
132
132
133 ## diff block
133 ## diff block
134 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
134 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
135 %for fid, change, f, stat in c.files:
135 %for fid, change, f, stat in c.files:
136 ${diff_block.diff_block_simple([c.changes[fid]])}
136 ${diff_block.diff_block_simple([c.changes[fid]])}
137 %endfor
137 %endfor
138
138
139 ## template for inline comment form
139 ## template for inline comment form
140 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
140 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
141 ${comment.comment_inline_form()}
141 ${comment.comment_inline_form()}
142
142
143 ## render comments and inlines
143 ## render comments and inlines
144 ${comment.generate_comments()}
144 ${comment.generate_comments()}
145
145
146 % if not c.pull_request.is_closed():
146 % if not c.pull_request.is_closed():
147 ## main comment form and it status
147 ## main comment form and it status
148 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
148 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
149 pull_request_id=c.pull_request.pull_request_id),
149 pull_request_id=c.pull_request.pull_request_id),
150 c.current_changeset_status,
150 c.current_changeset_status,
151 close_btn=True)}
151 close_btn=True)}
152 %endif
152 %endif
153
153
154 <script type="text/javascript">
154 <script type="text/javascript">
155 YUE.onDOMReady(function(){
155 YUE.onDOMReady(function(){
156 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
156 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
157
157
158 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
158 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
159 var show = 'none';
159 var show = 'none';
160 var target = e.currentTarget;
160 var target = e.currentTarget;
161 if(target.checked){
161 if(target.checked){
162 var show = ''
162 var show = ''
163 }
163 }
164 var boxid = YUD.getAttribute(target,'id_for');
164 var boxid = YUD.getAttribute(target,'id_for');
165 var comments = YUQ('#{0} .inline-comments'.format(boxid));
165 var comments = YUQ('#{0} .inline-comments'.format(boxid));
166 for(c in comments){
166 for(c in comments){
167 YUD.setStyle(comments[c],'display',show);
167 YUD.setStyle(comments[c],'display',show);
168 }
168 }
169 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
169 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
170 for(c in btns){
170 for(c in btns){
171 YUD.setStyle(btns[c],'display',show);
171 YUD.setStyle(btns[c],'display',show);
172 }
172 }
173 })
173 })
174
174
175 YUE.on(YUQ('.line'),'click',function(e){
175 YUE.on(YUQ('.line'),'click',function(e){
176 var tr = e.currentTarget;
176 var tr = e.currentTarget;
177 injectInlineForm(tr);
177 injectInlineForm(tr);
178 });
178 });
179
179
180 // inject comments into they proper positions
180 // inject comments into they proper positions
181 var file_comments = YUQ('.inline-comment-placeholder');
181 var file_comments = YUQ('.inline-comment-placeholder');
182 renderInlineComments(file_comments);
182 renderInlineComments(file_comments);
183
183
184 YUE.on(YUD.get('update_pull_request'),'click',function(e){
184 YUE.on(YUD.get('update_pull_request'),'click',function(e){
185
185
186 var reviewers_ids = [];
186 var reviewers_ids = [];
187 var ids = YUQ('#review_members input');
187 var ids = YUQ('#review_members input');
188 for(var i=0; i<ids.length;i++){
188 for(var i=0; i<ids.length;i++){
189 var id = ids[i].value
189 var id = ids[i].value
190 reviewers_ids.push(id);
190 reviewers_ids.push(id);
191 }
191 }
192 updateReviewers(reviewers_ids);
192 updateReviewers(reviewers_ids);
193 })
193 })
194 })
194 })
195 </script>
195 </script>
196
196
197 </div>
197 </div>
198
198
199 </%def>
199 </%def>
@@ -1,103 +1,103 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 %if c.repo_changesets:
2 %if c.repo_changesets:
3 <table class="table_disp">
3 <table class="table_disp">
4 <tr>
4 <tr>
5 <th class="left">${_('revision')}</th>
5 <th class="left">${_('revision')}</th>
6 <th class="left">${_('commit message')}</th>
6 <th class="left">${_('commit message')}</th>
7 <th class="left">${_('age')}</th>
7 <th class="left">${_('age')}</th>
8 <th class="left">${_('author')}</th>
8 <th class="left">${_('author')}</th>
9 <th class="left">${_('branch')}</th>
9 <th class="left">${_('branch')}</th>
10 <th class="left">${_('tags')}</th>
10 <th class="left">${_('tags')}</th>
11 </tr>
11 </tr>
12 %for cnt,cs in enumerate(c.repo_changesets):
12 %for cnt,cs in enumerate(c.repo_changesets):
13 <tr class="parity${cnt%2}">
13 <tr class="parity${cnt%2}">
14 <td>
14 <td>
15 <div>
15 <div>
16 <div class="changeset-status-container">
16 <div class="changeset-status-container">
17 %if c.statuses.get(cs.raw_id):
17 %if c.statuses.get(cs.raw_id):
18 <div class="changeset-status-ico">
18 <div class="changeset-status-ico">
19 %if c.statuses.get(cs.raw_id)[2]:
19 %if c.statuses.get(cs.raw_id)[2]:
20 <a class="tooltip" title="${_('Click to open associated pull request')}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
20 <a class="tooltip" title="${_('Click to open associated pull request')}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
21 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
21 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
22 </a>
22 </a>
23 %else:
23 %else:
24 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
24 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
25 %endif
25 %endif
26 </div>
26 </div>
27 %endif
27 %endif
28 </div>
28 </div>
29 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}">r${cs.revision}:${h.short_id(cs.raw_id)}</a></pre>
29 <pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}">r${cs.revision}:${h.short_id(cs.raw_id)}</a></pre>
30 </div>
30 </div>
31 </td>
31 </td>
32 <td>
32 <td>
33 ${h.link_to(h.truncate(cs.message,50) or _('No commit message'),
33 ${h.link_to(h.truncate(cs.message,50) or _('No commit message'),
34 h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
34 h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
35 title=cs.message)}
35 title=cs.message)}
36 </td>
36 </td>
37 <td><span class="tooltip" title="${h.tooltip(h.fmt_date(cs.date))}">
37 <td><span class="tooltip" title="${h.tooltip(h.fmt_date(cs.date))}">
38 ${h.age(cs.date)}</span>
38 ${h.age(cs.date)}</span>
39 </td>
39 </td>
40 <td title="${cs.author}">${h.person(cs.author)}</td>
40 <td title="${cs.author}">${h.person(cs.author)}</td>
41 <td>
41 <td>
42 <span class="logtags">
42 <span class="logtags">
43 %if cs.branch:
43 %if cs.branch:
44 <span class="branchtag">
44 <span class="branchtag">
45 ${cs.branch}
45 ${cs.branch}
46 </span>
46 </span>
47 %endif
47 %endif
48 </span>
48 </span>
49 </td>
49 </td>
50 <td>
50 <td>
51 <span class="logtags">
51 <span class="logtags">
52 %for tag in cs.tags:
52 %for tag in cs.tags:
53 <span class="tagtag">${tag}</span>
53 <span class="tagtag">${tag}</span>
54 %endfor
54 %endfor
55 </span>
55 </span>
56 </td>
56 </td>
57 </tr>
57 </tr>
58 %endfor
58 %endfor
59
59
60 </table>
60 </table>
61
61
62 <script type="text/javascript">
62 <script type="text/javascript">
63 YUE.onDOMReady(function(){
63 YUE.onDOMReady(function(){
64 YUE.delegate("shortlog_data","click",function(e, matchedEl, container){
64 YUE.delegate("shortlog_data","click",function(e, matchedEl, container){
65 ypjax(e.target.href,"shortlog_data",function(){tooltip_activate();});
65 ypjax(e.target.href,"shortlog_data",function(){tooltip_activate();});
66 YUE.preventDefault(e);
66 YUE.preventDefault(e);
67 },'.pager_link');
67 },'.pager_link');
68 });
68 });
69 </script>
69 </script>
70
70
71 <div class="pagination-wh pagination-left">
71 <div class="pagination-wh pagination-left">
72 ${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
72 ${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
73 </div>
73 </div>
74 %else:
74 %else:
75
75
76 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
76 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
77 <h4>${_('Add or upload files directly via RhodeCode')}</h4>
77 <h4>${_('Add or upload files directly via RhodeCode')}</h4>
78 <div style="margin: 20px 30px;">
78 <div style="margin: 20px 30px;">
79 <div id="add_node_id" class="add_node">
79 <div id="add_node_id" class="add_node">
80 <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='')}">${_('add new file')}</a>
80 <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='')}">${_('add new file')}</a>
81 </div>
81 </div>
82 </div>
82 </div>
83 %endif
83 %endif
84
84
85
85
86 <h4>${_('Push new repo')}</h4>
86 <h4>${_('Push new repo')}</h4>
87 <pre>
87 <pre>
88 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
88 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
89 ${c.rhodecode_repo.alias} add README # add first file
89 ${c.rhodecode_repo.alias} add README # add first file
90 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
90 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
91 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
91 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
92 </pre>
92 </pre>
93
93
94 <h4>${_('Existing repository?')}</h4>
94 <h4>${_('Existing repository?')}</h4>
95 <pre>
95 <pre>
96 %if h.is_git(c.rhodecode_repo):
96 %if h.is_git(c.rhodecode_repo):
97 git remote add origin ${c.clone_repo_url}
97 git remote add origin ${c.clone_repo_url}
98 git push -u origin master
98 git push -u origin master
99 %else:
99 %else:
100 hg push ${c.clone_repo_url}
100 hg push ${c.clone_repo_url}
101 %endif
101 %endif
102 </pre>
102 </pre>
103 %endif
103 %endif
@@ -1,403 +1,403 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2 from rhodecode.model.repo import RepoModel
2 from rhodecode.model.repo import RepoModel
3 from rhodecode.model.meta import Session
3 from rhodecode.model.meta import Session
4 from rhodecode.model.db import Repository
4 from rhodecode.model.db import Repository
5 from rhodecode.model.scm import ScmModel
5 from rhodecode.model.scm import ScmModel
6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
7
7
8
8
9 class TestCompareController(TestController):
9 class TestCompareController(TestController):
10
10
11 def test_index_tag(self):
11 def test_index_tag(self):
12 self.log_user()
12 self.log_user()
13 tag1 = '0.1.3'
13 tag1 = '0.1.3'
14 tag2 = '0.1.2'
14 tag2 = '0.1.2'
15 response = self.app.get(url(controller='compare', action='index',
15 response = self.app.get(url(controller='compare', action='index',
16 repo_name=HG_REPO,
16 repo_name=HG_REPO,
17 org_ref_type="tag",
17 org_ref_type="tag",
18 org_ref=tag1,
18 org_ref=tag1,
19 other_ref_type="tag",
19 other_ref_type="tag",
20 other_ref=tag2,
20 other_ref=tag2,
21 ))
21 ))
22 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
22 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
23 ## outgoing changesets between tags
23 ## outgoing changesets between tags
24 response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
24 response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
25 response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
25 response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
26 response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
26 response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
27 response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
27 response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
28 response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
28 response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
29 response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
29 response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
30 response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
30 response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
31
31
32 ## files diff
32 ## files diff
33 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
33 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
34 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
34 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
37 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
37 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
38 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
38 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
39 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
39 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
40 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
40 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
41 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
41 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
42 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
42 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
43 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
43 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
44
44
45 def test_index_branch(self):
45 def test_index_branch(self):
46 self.log_user()
46 self.log_user()
47 response = self.app.get(url(controller='compare', action='index',
47 response = self.app.get(url(controller='compare', action='index',
48 repo_name=HG_REPO,
48 repo_name=HG_REPO,
49 org_ref_type="branch",
49 org_ref_type="branch",
50 org_ref='default',
50 org_ref='default',
51 other_ref_type="branch",
51 other_ref_type="branch",
52 other_ref='default',
52 other_ref='default',
53 ))
53 ))
54
54
55 response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO))
55 response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO))
56 # branch are equal
56 # branch are equal
57 response.mustcontain('<tr><td>No changesets</td></tr>')
57 response.mustcontain('<tr><td>No changesets</td></tr>')
58
58
59 def test_compare_revisions(self):
59 def test_compare_revisions(self):
60 self.log_user()
60 self.log_user()
61 rev1 = '3d8f361e72ab'
61 rev1 = '3d8f361e72ab'
62 rev2 = 'b986218ba1c9'
62 rev2 = 'b986218ba1c9'
63 response = self.app.get(url(controller='compare', action='index',
63 response = self.app.get(url(controller='compare', action='index',
64 repo_name=HG_REPO,
64 repo_name=HG_REPO,
65 org_ref_type="rev",
65 org_ref_type="rev",
66 org_ref=rev1,
66 org_ref=rev1,
67 other_ref_type="rev",
67 other_ref_type="rev",
68 other_ref=rev2,
68 other_ref=rev2,
69 ))
69 ))
70 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
70 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
71 ## outgoing changesets between those revisions
71 ## outgoing changesets between those revisions
72 response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev1))
72 response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev1))
73
73
74 ## files
74 ## files
75 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (HG_REPO, rev1, rev2))
75 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (HG_REPO, rev1, rev2))
76
76
77 def test_compare_remote_repos(self):
77 def test_compare_remote_repos(self):
78 self.log_user()
78 self.log_user()
79
79
80 form_data = dict(
80 form_data = dict(
81 repo_name=HG_FORK,
81 repo_name=HG_FORK,
82 repo_name_full=HG_FORK,
82 repo_name_full=HG_FORK,
83 repo_group=None,
83 repo_group=None,
84 repo_type='hg',
84 repo_type='hg',
85 description='',
85 description='',
86 private=False,
86 private=False,
87 copy_permissions=False,
87 copy_permissions=False,
88 landing_rev='tip',
88 landing_rev='tip',
89 update_after_clone=False,
89 update_after_clone=False,
90 fork_parent_id=Repository.get_by_repo_name(HG_REPO),
90 fork_parent_id=Repository.get_by_repo_name(HG_REPO),
91 )
91 )
92 RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN)
92 RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN)
93
93
94 Session().commit()
94 Session().commit()
95
95
96 rev1 = '7d4bc8ec6be5'
96 rev1 = '7d4bc8ec6be5'
97 rev2 = '56349e29c2af'
97 rev2 = '56349e29c2af'
98
98
99 response = self.app.get(url(controller='compare', action='index',
99 response = self.app.get(url(controller='compare', action='index',
100 repo_name=HG_REPO,
100 repo_name=HG_REPO,
101 org_ref_type="rev",
101 org_ref_type="rev",
102 org_ref=rev1,
102 org_ref=rev1,
103 other_ref_type="rev",
103 other_ref_type="rev",
104 other_ref=rev2,
104 other_ref=rev2,
105 repo=HG_FORK
105 repo=HG_FORK
106 ))
106 ))
107
107
108 try:
108 try:
109 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
109 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
110 ## outgoing changesets between those revisions
110 ## outgoing changesets between those revisions
111
111
112 response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_REPO, rev1))
112 response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_REPO, rev1))
113 response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_REPO))
113 response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_REPO))
114 response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO))
114 response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO))
115
115
116 ## files
116 ## files
117 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2))
117 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2))
118 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2))
118 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2))
119 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2))
119 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2))
120 finally:
120 finally:
121 RepoModel().delete(HG_FORK)
121 RepoModel().delete(HG_FORK)
122
122
123 def test_compare_extra_commits(self):
123 def test_compare_extra_commits(self):
124 self.log_user()
124 self.log_user()
125
125
126 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
126 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
127 description='diff-test',
127 description='diff-test',
128 owner=TEST_USER_ADMIN_LOGIN)
128 owner=TEST_USER_ADMIN_LOGIN)
129
129
130 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
130 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
131 description='diff-test',
131 description='diff-test',
132 owner=TEST_USER_ADMIN_LOGIN)
132 owner=TEST_USER_ADMIN_LOGIN)
133
133
134 Session().commit()
134 Session().commit()
135 r1_id = repo1.repo_id
135 r1_id = repo1.repo_id
136 r1_name = repo1.repo_name
136 r1_name = repo1.repo_name
137 r2_id = repo2.repo_id
137 r2_id = repo2.repo_id
138 r2_name = repo2.repo_name
138 r2_name = repo2.repo_name
139
139
140 #commit something !
140 #commit something !
141 cs0 = ScmModel().create_node(
141 cs0 = ScmModel().create_node(
142 repo=repo1.scm_instance, repo_name=r1_name,
142 repo=repo1.scm_instance, repo_name=r1_name,
143 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
143 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
144 author=TEST_USER_ADMIN_LOGIN,
144 author=TEST_USER_ADMIN_LOGIN,
145 message='commit1',
145 message='commit1',
146 content='line1',
146 content='line1',
147 f_path='file1'
147 f_path='file1'
148 )
148 )
149
149
150 cs0_prim = ScmModel().create_node(
150 cs0_prim = ScmModel().create_node(
151 repo=repo2.scm_instance, repo_name=r2_name,
151 repo=repo2.scm_instance, repo_name=r2_name,
152 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
152 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
153 author=TEST_USER_ADMIN_LOGIN,
153 author=TEST_USER_ADMIN_LOGIN,
154 message='commit1',
154 message='commit1',
155 content='line1',
155 content='line1',
156 f_path='file1'
156 f_path='file1'
157 )
157 )
158
158
159 cs1 = ScmModel().commit_change(
159 cs1 = ScmModel().commit_change(
160 repo=repo2.scm_instance, repo_name=r2_name,
160 repo=repo2.scm_instance, repo_name=r2_name,
161 cs=cs0_prim, user=TEST_USER_ADMIN_LOGIN, author=TEST_USER_ADMIN_LOGIN,
161 cs=cs0_prim, user=TEST_USER_ADMIN_LOGIN, author=TEST_USER_ADMIN_LOGIN,
162 message='commit2',
162 message='commit2',
163 content='line1\nline2',
163 content='line1\nline2',
164 f_path='file1'
164 f_path='file1'
165 )
165 )
166
166
167 rev1 = 'default'
167 rev1 = 'default'
168 rev2 = 'default'
168 rev2 = 'default'
169 response = self.app.get(url(controller='compare', action='index',
169 response = self.app.get(url(controller='compare', action='index',
170 repo_name=r2_name,
170 repo_name=r2_name,
171 org_ref_type="branch",
171 org_ref_type="branch",
172 org_ref=rev1,
172 org_ref=rev1,
173 other_ref_type="branch",
173 other_ref_type="branch",
174 other_ref=rev2,
174 other_ref=rev2,
175 repo=r1_name
175 repo=r1_name
176 ))
176 ))
177
177
178 try:
178 try:
179 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
179 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
180
180
181 response.mustcontain("""<div class="message">commit2</div>""")
181 response.mustcontain("""<div class="message">commit2</div>""")
182 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (r2_name, cs1.raw_id, cs1.short_id))
182 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (r2_name, cs1.raw_id, cs1.short_id))
183 ## files
183 ## files
184 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s#C--826e8142e6ba">file1</a>""" % (r2_name, rev1, rev2))
184 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s#C--826e8142e6ba">file1</a>""" % (r2_name, rev1, rev2))
185
185
186 finally:
186 finally:
187 RepoModel().delete(r1_id)
187 RepoModel().delete(r1_id)
188 RepoModel().delete(r2_id)
188 RepoModel().delete(r2_id)
189
189
190 def test_org_repo_new_commits_after_forking(self):
190 def test_org_repo_new_commits_after_forking(self):
191 self.log_user()
191 self.log_user()
192
192
193 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
193 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
194 description='diff-test',
194 description='diff-test',
195 owner=TEST_USER_ADMIN_LOGIN)
195 owner=TEST_USER_ADMIN_LOGIN)
196
196
197 Session().commit()
197 Session().commit()
198 r1_id = repo1.repo_id
198 r1_id = repo1.repo_id
199 r1_name = repo1.repo_name
199 r1_name = repo1.repo_name
200
200
201 #commit something initially !
201 #commit something initially !
202 cs0 = ScmModel().create_node(
202 cs0 = ScmModel().create_node(
203 repo=repo1.scm_instance, repo_name=r1_name,
203 repo=repo1.scm_instance, repo_name=r1_name,
204 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
204 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
205 author=TEST_USER_ADMIN_LOGIN,
205 author=TEST_USER_ADMIN_LOGIN,
206 message='commit1',
206 message='commit1',
207 content='line1',
207 content='line1',
208 f_path='file1'
208 f_path='file1'
209 )
209 )
210 Session().commit()
210 Session().commit()
211 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
211 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
212 #fork the repo1
212 #fork the repo1
213 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
213 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
214 description='compare-test',
214 description='compare-test',
215 clone_uri=repo1.repo_full_path,
215 clone_uri=repo1.repo_full_path,
216 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
216 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
217 Session().commit()
217 Session().commit()
218 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
218 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
219 r2_id = repo2.repo_id
219 r2_id = repo2.repo_id
220 r2_name = repo2.repo_name
220 r2_name = repo2.repo_name
221
221
222 #make 3 new commits in fork
222 #make 3 new commits in fork
223 cs1 = ScmModel().create_node(
223 cs1 = ScmModel().create_node(
224 repo=repo2.scm_instance, repo_name=r2_name,
224 repo=repo2.scm_instance, repo_name=r2_name,
225 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
225 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
226 author=TEST_USER_ADMIN_LOGIN,
226 author=TEST_USER_ADMIN_LOGIN,
227 message='commit1-fork',
227 message='commit1-fork',
228 content='file1-line1-from-fork',
228 content='file1-line1-from-fork',
229 f_path='file1-fork'
229 f_path='file1-fork'
230 )
230 )
231 cs2 = ScmModel().create_node(
231 cs2 = ScmModel().create_node(
232 repo=repo2.scm_instance, repo_name=r2_name,
232 repo=repo2.scm_instance, repo_name=r2_name,
233 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
233 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
234 author=TEST_USER_ADMIN_LOGIN,
234 author=TEST_USER_ADMIN_LOGIN,
235 message='commit2-fork',
235 message='commit2-fork',
236 content='file2-line1-from-fork',
236 content='file2-line1-from-fork',
237 f_path='file2-fork'
237 f_path='file2-fork'
238 )
238 )
239 cs3 = ScmModel().create_node(
239 cs3 = ScmModel().create_node(
240 repo=repo2.scm_instance, repo_name=r2_name,
240 repo=repo2.scm_instance, repo_name=r2_name,
241 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
241 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
242 author=TEST_USER_ADMIN_LOGIN,
242 author=TEST_USER_ADMIN_LOGIN,
243 message='commit3-fork',
243 message='commit3-fork',
244 content='file3-line1-from-fork',
244 content='file3-line1-from-fork',
245 f_path='file3-fork'
245 f_path='file3-fork'
246 )
246 )
247
247
248 #compare !
248 #compare !
249 rev1 = 'default'
249 rev1 = 'default'
250 rev2 = 'default'
250 rev2 = 'default'
251 response = self.app.get(url(controller='compare', action='index',
251 response = self.app.get(url(controller='compare', action='index',
252 repo_name=r2_name,
252 repo_name=r2_name,
253 org_ref_type="branch",
253 org_ref_type="branch",
254 org_ref=rev1,
254 org_ref=rev1,
255 other_ref_type="branch",
255 other_ref_type="branch",
256 other_ref=rev2,
256 other_ref=rev2,
257 repo=r1_name,
257 repo=r1_name,
258 bundle=True,
258 bundle=True,
259 ))
259 ))
260
260
261 try:
261 try:
262 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
262 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
263 response.mustcontain("""file1-line1-from-fork""")
263 response.mustcontain("""file1-line1-from-fork""")
264 response.mustcontain("""file2-line1-from-fork""")
264 response.mustcontain("""file2-line1-from-fork""")
265 response.mustcontain("""file3-line1-from-fork""")
265 response.mustcontain("""file3-line1-from-fork""")
266
266
267 #add new commit into parent !
267 #add new commit into parent !
268 cs0 = ScmModel().create_node(
268 cs0 = ScmModel().create_node(
269 repo=repo1.scm_instance, repo_name=r1_name,
269 repo=repo1.scm_instance, repo_name=r1_name,
270 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
270 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
271 author=TEST_USER_ADMIN_LOGIN,
271 author=TEST_USER_ADMIN_LOGIN,
272 message='commit2',
272 message='commit2',
273 content='line1-from-new-parent',
273 content='line1-from-new-parent',
274 f_path='file2'
274 f_path='file2'
275 )
275 )
276 #compare !
276 #compare !
277 rev1 = 'default'
277 rev1 = 'default'
278 rev2 = 'default'
278 rev2 = 'default'
279 response = self.app.get(url(controller='compare', action='index',
279 response = self.app.get(url(controller='compare', action='index',
280 repo_name=r2_name,
280 repo_name=r2_name,
281 org_ref_type="branch",
281 org_ref_type="branch",
282 org_ref=rev1,
282 org_ref=rev1,
283 other_ref_type="branch",
283 other_ref_type="branch",
284 other_ref=rev2,
284 other_ref=rev2,
285 repo=r1_name,
285 repo=r1_name,
286 bundle=True,
286 bundle=True,
287 ))
287 ))
288
288
289 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
289 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
290 response.mustcontain("""<a href="#">file2</a>""") # new commit from parent
290 response.mustcontain("""<a href="#">file2</a>""") # new commit from parent
291 response.mustcontain("""line1-from-new-parent""")
291 response.mustcontain("""line1-from-new-parent""")
292 response.mustcontain("""file1-line1-from-fork""")
292 response.mustcontain("""file1-line1-from-fork""")
293 response.mustcontain("""file2-line1-from-fork""")
293 response.mustcontain("""file2-line1-from-fork""")
294 response.mustcontain("""file3-line1-from-fork""")
294 response.mustcontain("""file3-line1-from-fork""")
295 finally:
295 finally:
296 RepoModel().delete(r2_id)
296 RepoModel().delete(r2_id)
297 RepoModel().delete(r1_id)
297 RepoModel().delete(r1_id)
298
298
299 def test_org_repo_new_commits_after_forking_simple_diff(self):
299 def test_org_repo_new_commits_after_forking_simple_diff(self):
300 self.log_user()
300 self.log_user()
301
301
302 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
302 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
303 description='diff-test',
303 description='diff-test',
304 owner=TEST_USER_ADMIN_LOGIN)
304 owner=TEST_USER_ADMIN_LOGIN)
305
305
306 Session().commit()
306 Session().commit()
307 r1_id = repo1.repo_id
307 r1_id = repo1.repo_id
308 r1_name = repo1.repo_name
308 r1_name = repo1.repo_name
309
309
310 #commit something initially !
310 #commit something initially !
311 cs0 = ScmModel().create_node(
311 cs0 = ScmModel().create_node(
312 repo=repo1.scm_instance, repo_name=r1_name,
312 repo=repo1.scm_instance, repo_name=r1_name,
313 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
313 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
314 author=TEST_USER_ADMIN_LOGIN,
314 author=TEST_USER_ADMIN_LOGIN,
315 message='commit1',
315 message='commit1',
316 content='line1',
316 content='line1',
317 f_path='file1'
317 f_path='file1'
318 )
318 )
319 Session().commit()
319 Session().commit()
320 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
320 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
321 #fork the repo1
321 #fork the repo1
322 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
322 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
323 description='compare-test',
323 description='compare-test',
324 clone_uri=repo1.repo_full_path,
324 clone_uri=repo1.repo_full_path,
325 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
325 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
326 Session().commit()
326 Session().commit()
327 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
327 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
328 r2_id = repo2.repo_id
328 r2_id = repo2.repo_id
329 r2_name = repo2.repo_name
329 r2_name = repo2.repo_name
330
330
331 #make 3 new commits in fork
331 #make 3 new commits in fork
332 cs1 = ScmModel().create_node(
332 cs1 = ScmModel().create_node(
333 repo=repo2.scm_instance, repo_name=r2_name,
333 repo=repo2.scm_instance, repo_name=r2_name,
334 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
334 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
335 author=TEST_USER_ADMIN_LOGIN,
335 author=TEST_USER_ADMIN_LOGIN,
336 message='commit1-fork',
336 message='commit1-fork',
337 content='file1-line1-from-fork',
337 content='file1-line1-from-fork',
338 f_path='file1-fork'
338 f_path='file1-fork'
339 )
339 )
340 cs2 = ScmModel().create_node(
340 cs2 = ScmModel().create_node(
341 repo=repo2.scm_instance, repo_name=r2_name,
341 repo=repo2.scm_instance, repo_name=r2_name,
342 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
342 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
343 author=TEST_USER_ADMIN_LOGIN,
343 author=TEST_USER_ADMIN_LOGIN,
344 message='commit2-fork',
344 message='commit2-fork',
345 content='file2-line1-from-fork',
345 content='file2-line1-from-fork',
346 f_path='file2-fork'
346 f_path='file2-fork'
347 )
347 )
348 cs3 = ScmModel().create_node(
348 cs3 = ScmModel().create_node(
349 repo=repo2.scm_instance, repo_name=r2_name,
349 repo=repo2.scm_instance, repo_name=r2_name,
350 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
350 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
351 author=TEST_USER_ADMIN_LOGIN,
351 author=TEST_USER_ADMIN_LOGIN,
352 message='commit3-fork',
352 message='commit3-fork',
353 content='file3-line1-from-fork',
353 content='file3-line1-from-fork',
354 f_path='file3-fork'
354 f_path='file3-fork'
355 )
355 )
356
356
357 #compare !
357 #compare !
358 rev1 = 'default'
358 rev1 = 'default'
359 rev2 = 'default'
359 rev2 = 'default'
360 response = self.app.get(url(controller='compare', action='index',
360 response = self.app.get(url(controller='compare', action='index',
361 repo_name=r2_name,
361 repo_name=r2_name,
362 org_ref_type="branch",
362 org_ref_type="branch",
363 org_ref=rev1,
363 org_ref=rev1,
364 other_ref_type="branch",
364 other_ref_type="branch",
365 other_ref=rev2,
365 other_ref=rev2,
366 repo=r1_name,
366 repo=r1_name,
367 bundle=False,
367 bundle=False,
368 ))
368 ))
369
369
370 try:
370 try:
371 #response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
371 #response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
372
372
373 #add new commit into parent !
373 #add new commit into parent !
374 cs0 = ScmModel().create_node(
374 cs0 = ScmModel().create_node(
375 repo=repo1.scm_instance, repo_name=r1_name,
375 repo=repo1.scm_instance, repo_name=r1_name,
376 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
376 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
377 author=TEST_USER_ADMIN_LOGIN,
377 author=TEST_USER_ADMIN_LOGIN,
378 message='commit2',
378 message='commit2',
379 content='line1',
379 content='line1',
380 f_path='file2'
380 f_path='file2'
381 )
381 )
382 #compare !
382 #compare !
383 rev1 = 'default'
383 rev1 = 'default'
384 rev2 = 'default'
384 rev2 = 'default'
385 response = self.app.get(url(controller='compare', action='index',
385 response = self.app.get(url(controller='compare', action='index',
386 repo_name=r2_name,
386 repo_name=r2_name,
387 org_ref_type="branch",
387 org_ref_type="branch",
388 org_ref=rev1,
388 org_ref=rev1,
389 other_ref_type="branch",
389 other_ref_type="branch",
390 other_ref=rev2,
390 other_ref=rev2,
391 repo=r1_name,
391 repo=r1_name,
392 bundle=False
392 bundle=False
393 ))
393 ))
394 rev2 = cs0.parents[0].raw_id
394 rev2 = cs0.parents[0].raw_id
395 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
395 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
396 response.mustcontain("""file1-line1-from-fork""")
396 response.mustcontain("""file1-line1-from-fork""")
397 response.mustcontain("""file2-line1-from-fork""")
397 response.mustcontain("""file2-line1-from-fork""")
398 response.mustcontain("""file3-line1-from-fork""")
398 response.mustcontain("""file3-line1-from-fork""")
399 self.assertFalse("""<a href="#">file2</a>""" in response.body) # new commit from parent
399 self.assertFalse("""<a href="#">file2</a>""" in response.body) # new commit from parent
400 self.assertFalse("""line1-from-new-parent""" in response.body)
400 self.assertFalse("""line1-from-new-parent""" in response.body)
401 finally:
401 finally:
402 RepoModel().delete(r2_id)
402 RepoModel().delete(r2_id)
403 RepoModel().delete(r1_id) No newline at end of file
403 RepoModel().delete(r1_id)
@@ -1,198 +1,212 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.tests.test_libs
3 rhodecode.tests.test_libs
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6
6
7 Package for testing various lib/helper functions in rhodecode
7 Package for testing various lib/helper functions in rhodecode
8
8
9 :created_on: Jun 9, 2011
9 :created_on: Jun 9, 2011
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import unittest
26 import unittest
27 import datetime
27 import datetime
28 import hashlib
28 import hashlib
29 import mock
29 import mock
30 from rhodecode.tests import *
30 from rhodecode.tests import *
31
31
32 proto = 'http'
32 proto = 'http'
33 TEST_URLS = [
33 TEST_URLS = [
34 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
34 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
35 '%s://127.0.0.1' % proto),
35 '%s://127.0.0.1' % proto),
36 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
36 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
37 '%s://127.0.0.1' % proto),
37 '%s://127.0.0.1' % proto),
38 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
38 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
39 '%s://127.0.0.1' % proto),
39 '%s://127.0.0.1' % proto),
40 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
40 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
41 '%s://127.0.0.1:8080' % proto),
41 '%s://127.0.0.1:8080' % proto),
42 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
42 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
43 '%s://domain.org' % proto),
43 '%s://domain.org' % proto),
44 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
44 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
45 '8080'],
45 '8080'],
46 '%s://domain.org:8080' % proto),
46 '%s://domain.org:8080' % proto),
47 ]
47 ]
48
48
49 proto = 'https'
49 proto = 'https'
50 TEST_URLS += [
50 TEST_URLS += [
51 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
51 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
52 '%s://127.0.0.1' % proto),
52 '%s://127.0.0.1' % proto),
53 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
53 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
54 '%s://127.0.0.1' % proto),
54 '%s://127.0.0.1' % proto),
55 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
55 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
56 '%s://127.0.0.1' % proto),
56 '%s://127.0.0.1' % proto),
57 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
57 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
58 '%s://127.0.0.1:8080' % proto),
58 '%s://127.0.0.1:8080' % proto),
59 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
59 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
60 '%s://domain.org' % proto),
60 '%s://domain.org' % proto),
61 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
61 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
62 '8080'],
62 '8080'],
63 '%s://domain.org:8080' % proto),
63 '%s://domain.org:8080' % proto),
64 ]
64 ]
65
65
66
66
67 class TestLibs(unittest.TestCase):
67 class TestLibs(unittest.TestCase):
68
68
69 def test_uri_filter(self):
69 def test_uri_filter(self):
70 from rhodecode.lib.utils2 import uri_filter
70 from rhodecode.lib.utils2 import uri_filter
71
71
72 for url in TEST_URLS:
72 for url in TEST_URLS:
73 self.assertEqual(uri_filter(url[0]), url[1])
73 self.assertEqual(uri_filter(url[0]), url[1])
74
74
75 def test_credentials_filter(self):
75 def test_credentials_filter(self):
76 from rhodecode.lib.utils2 import credentials_filter
76 from rhodecode.lib.utils2 import credentials_filter
77
77
78 for url in TEST_URLS:
78 for url in TEST_URLS:
79 self.assertEqual(credentials_filter(url[0]), url[2])
79 self.assertEqual(credentials_filter(url[0]), url[2])
80
80
81 def test_str2bool(self):
81 def test_str2bool(self):
82 from rhodecode.lib.utils2 import str2bool
82 from rhodecode.lib.utils2 import str2bool
83 test_cases = [
83 test_cases = [
84 ('t', True),
84 ('t', True),
85 ('true', True),
85 ('true', True),
86 ('y', True),
86 ('y', True),
87 ('yes', True),
87 ('yes', True),
88 ('on', True),
88 ('on', True),
89 ('1', True),
89 ('1', True),
90 ('Y', True),
90 ('Y', True),
91 ('yeS', True),
91 ('yeS', True),
92 ('Y', True),
92 ('Y', True),
93 ('TRUE', True),
93 ('TRUE', True),
94 ('T', True),
94 ('T', True),
95 ('False', False),
95 ('False', False),
96 ('F', False),
96 ('F', False),
97 ('FALSE', False),
97 ('FALSE', False),
98 ('0', False),
98 ('0', False),
99 ('-1', False),
99 ('-1', False),
100 ('', False), ]
100 ('', False), ]
101
101
102 for case in test_cases:
102 for case in test_cases:
103 self.assertEqual(str2bool(case[0]), case[1])
103 self.assertEqual(str2bool(case[0]), case[1])
104
104
105 def test_mention_extractor(self):
105 def test_mention_extractor(self):
106 from rhodecode.lib.utils2 import extract_mentioned_users
106 from rhodecode.lib.utils2 import extract_mentioned_users
107 sample = (
107 sample = (
108 "@first hi there @marcink here's my email marcin@email.com "
108 "@first hi there @marcink here's my email marcin@email.com "
109 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
109 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
110 "@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl "
110 "@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl "
111 "@marian.user just do it @marco-polo and next extract @marco_polo "
111 "@marian.user just do it @marco-polo and next extract @marco_polo "
112 "user.dot hej ! not-needed maril@domain.org"
112 "user.dot hej ! not-needed maril@domain.org"
113 )
113 )
114
114
115 s = sorted([
115 s = sorted([
116 'first', 'marcink', 'lukaszb', 'one_more22', 'MARCIN', 'maRCiN', 'john',
116 'first', 'marcink', 'lukaszb', 'one_more22', 'MARCIN', 'maRCiN', 'john',
117 'marian.user', 'marco-polo', 'marco_polo'
117 'marian.user', 'marco-polo', 'marco_polo'
118 ], key=lambda k: k.lower())
118 ], key=lambda k: k.lower())
119 self.assertEqual(s, extract_mentioned_users(sample))
119 self.assertEqual(s, extract_mentioned_users(sample))
120
120
121 def test_age(self):
121 def test_age(self):
122 import calendar
122 import calendar
123 from rhodecode.lib.utils2 import age
123 from rhodecode.lib.utils2 import age
124 n = datetime.datetime.now()
124 n = datetime.datetime.now()
125 delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs)
125 delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs)
126 self.assertEqual(age(n), u'just now')
126 self.assertEqual(age(n), u'just now')
127 self.assertEqual(age(n - delt(seconds=1)), u'1 second ago')
127 self.assertEqual(age(n - delt(seconds=1)), u'1 second ago')
128 self.assertEqual(age(n - delt(seconds=60 * 2)), u'2 minutes ago')
128 self.assertEqual(age(n - delt(seconds=60 * 2)), u'2 minutes ago')
129 self.assertEqual(age(n - delt(hours=1)), u'1 hour ago')
129 self.assertEqual(age(n - delt(hours=1)), u'1 hour ago')
130 self.assertEqual(age(n - delt(hours=24)), u'1 day ago')
130 self.assertEqual(age(n - delt(hours=24)), u'1 day ago')
131 self.assertEqual(age(n - delt(hours=24 * 5)), u'5 days ago')
131 self.assertEqual(age(n - delt(hours=24 * 5)), u'5 days ago')
132 self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[n.month-1] + 2))),
132 self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[n.month - 1] + 2))),
133 u'1 month and 2 days ago')
133 u'1 month and 2 days ago')
134 self.assertEqual(age(n - delt(hours=24 * 400)), u'1 year and 1 month ago')
134 self.assertEqual(age(n - delt(hours=24 * 400)), u'1 year and 1 month ago')
135
135
136 def test_age_in_future(self):
137 import calendar
138 from rhodecode.lib.utils2 import age
139 n = datetime.datetime.now()
140 delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs)
141 self.assertEqual(age(n), u'just now')
142 self.assertEqual(age(n + delt(seconds=1)), u'in 1 second')
143 self.assertEqual(age(n + delt(seconds=60 * 2)), u'in 2 minutes')
144 self.assertEqual(age(n + delt(hours=1)), u'in 1 hour')
145 self.assertEqual(age(n + delt(hours=24)), u'in 1 day')
146 self.assertEqual(age(n + delt(hours=24 * 5)), u'in 5 days')
147 self.assertEqual(age(n + delt(hours=24 * (calendar.mdays[n.month - 1] + 2))),
148 u'in 1 month and 1 day')
149 self.assertEqual(age(n + delt(hours=24 * 400)), u'in 1 year and 1 month')
150
136 def test_tag_exctrator(self):
151 def test_tag_exctrator(self):
137 sample = (
152 sample = (
138 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
153 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
139 "[requires] [stale] [see<>=>] [see => http://url.com]"
154 "[requires] [stale] [see<>=>] [see => http://url.com]"
140 "[requires => url] [lang => python] [just a tag]"
155 "[requires => url] [lang => python] [just a tag]"
141 "[,d] [ => ULR ] [obsolete] [desc]]"
156 "[,d] [ => ULR ] [obsolete] [desc]]"
142 )
157 )
143 from rhodecode.lib.helpers import desc_stylize
158 from rhodecode.lib.helpers import desc_stylize
144 res = desc_stylize(sample)
159 res = desc_stylize(sample)
145 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
160 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
146 self.assertTrue('<div class="metatag" tag="obsolete">obsolete</div>' in res)
161 self.assertTrue('<div class="metatag" tag="obsolete">obsolete</div>' in res)
147 self.assertTrue('<div class="metatag" tag="stale">stale</div>' in res)
162 self.assertTrue('<div class="metatag" tag="stale">stale</div>' in res)
148 self.assertTrue('<div class="metatag" tag="lang">python</div>' in res)
163 self.assertTrue('<div class="metatag" tag="lang">python</div>' in res)
149 self.assertTrue('<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res)
164 self.assertTrue('<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res)
150 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
165 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
151
166
152 def test_alternative_gravatar(self):
167 def test_alternative_gravatar(self):
153 from rhodecode.lib.helpers import gravatar_url
168 from rhodecode.lib.helpers import gravatar_url
154 _md5 = lambda s: hashlib.md5(s).hexdigest()
169 _md5 = lambda s: hashlib.md5(s).hexdigest()
155
170
156 def fake_conf(**kwargs):
171 def fake_conf(**kwargs):
157 from pylons import config
172 from pylons import config
158 config['app_conf'] = {}
173 config['app_conf'] = {}
159 config['app_conf']['use_gravatar'] = True
174 config['app_conf']['use_gravatar'] = True
160 config['app_conf'].update(kwargs)
175 config['app_conf'].update(kwargs)
161 return config
176 return config
162
177
163 class fake_url():
178 class fake_url():
164 @classmethod
179 @classmethod
165 def current(cls, *args, **kwargs):
180 def current(cls, *args, **kwargs):
166 return 'https://server.com'
181 return 'https://server.com'
167
182
168 with mock.patch('pylons.url', fake_url):
183 with mock.patch('pylons.url', fake_url):
169 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
184 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
170 with mock.patch('pylons.config', fake):
185 with mock.patch('pylons.config', fake):
171 from pylons import url
186 from pylons import url
172 assert url.current() == 'https://server.com'
187 assert url.current() == 'https://server.com'
173 grav = gravatar_url(email_address='test@foo.com', size=24)
188 grav = gravatar_url(email_address='test@foo.com', size=24)
174 assert grav == 'http://test.com/test@foo.com'
189 assert grav == 'http://test.com/test@foo.com'
175
190
176 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
191 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
177 with mock.patch('pylons.config', fake):
192 with mock.patch('pylons.config', fake):
178 grav = gravatar_url(email_address='test@foo.com', size=24)
193 grav = gravatar_url(email_address='test@foo.com', size=24)
179 assert grav == 'http://test.com/test@foo.com'
194 assert grav == 'http://test.com/test@foo.com'
180
195
181 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}')
196 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}')
182 with mock.patch('pylons.config', fake):
197 with mock.patch('pylons.config', fake):
183 em = 'test@foo.com'
198 em = 'test@foo.com'
184 grav = gravatar_url(email_address=em, size=24)
199 grav = gravatar_url(email_address=em, size=24)
185 assert grav == 'http://test.com/%s' % (_md5(em))
200 assert grav == 'http://test.com/%s' % (_md5(em))
186
201
187 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}/{size}')
202 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}/{size}')
188 with mock.patch('pylons.config', fake):
203 with mock.patch('pylons.config', fake):
189 em = 'test@foo.com'
204 em = 'test@foo.com'
190 grav = gravatar_url(email_address=em, size=24)
205 grav = gravatar_url(email_address=em, size=24)
191 assert grav == 'http://test.com/%s/%s' % (_md5(em), 24)
206 assert grav == 'http://test.com/%s/%s' % (_md5(em), 24)
192
207
193 fake = fake_conf(alternative_gravatar_url='{scheme}://{netloc}/{md5email}/{size}')
208 fake = fake_conf(alternative_gravatar_url='{scheme}://{netloc}/{md5email}/{size}')
194 with mock.patch('pylons.config', fake):
209 with mock.patch('pylons.config', fake):
195 em = 'test@foo.com'
210 em = 'test@foo.com'
196 grav = gravatar_url(email_address=em, size=24)
211 grav = gravatar_url(email_address=em, size=24)
197 assert grav == 'https://server.com/%s/%s' % (_md5(em), 24)
212 assert grav == 'https://server.com/%s/%s' % (_md5(em), 24)
198
@@ -1,246 +1,247 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 import unittest
2 import unittest
3 import formencode
3 import formencode
4
4
5 from rhodecode.tests import *
5 from rhodecode.tests import *
6
6
7 from rhodecode.model import validators as v
7 from rhodecode.model import validators as v
8 from rhodecode.model.users_group import UsersGroupModel
8 from rhodecode.model.users_group import UsersGroupModel
9
9
10 from rhodecode.model.meta import Session
10 from rhodecode.model.meta import Session
11 from rhodecode.model.repos_group import ReposGroupModel
11 from rhodecode.model.repos_group import ReposGroupModel
12 from rhodecode.config.routing import ADMIN_PREFIX
12 from rhodecode.config.routing import ADMIN_PREFIX
13 from rhodecode.model.db import ChangesetStatus
13 from rhodecode.model.db import ChangesetStatus, Repository
14 from rhodecode.model.changeset_status import ChangesetStatusModel
14 from rhodecode.model.changeset_status import ChangesetStatusModel
15 from rhodecode.model.comment import ChangesetCommentsModel
15 from rhodecode.model.comment import ChangesetCommentsModel
16
16
17
17
18 class TestReposGroups(unittest.TestCase):
18 class TestReposGroups(unittest.TestCase):
19
19
20 def setUp(self):
20 def setUp(self):
21 pass
21 pass
22
22
23 def tearDown(self):
23 def tearDown(self):
24 pass
24 pass
25
25
26 def test_Message_extractor(self):
26 def test_Message_extractor(self):
27 validator = v.ValidUsername()
27 validator = v.ValidUsername()
28 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
28 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
29
29
30 class StateObj(object):
30 class StateObj(object):
31 pass
31 pass
32
32
33 self.assertRaises(formencode.Invalid,
33 self.assertRaises(formencode.Invalid,
34 validator.to_python, 'default', StateObj)
34 validator.to_python, 'default', StateObj)
35
35
36 def test_ValidUsername(self):
36 def test_ValidUsername(self):
37 validator = v.ValidUsername()
37 validator = v.ValidUsername()
38
38
39 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
39 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
40 self.assertRaises(formencode.Invalid, validator.to_python, 'new_user')
40 self.assertRaises(formencode.Invalid, validator.to_python, 'new_user')
41 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
41 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
42 self.assertRaises(formencode.Invalid, validator.to_python,
42 self.assertRaises(formencode.Invalid, validator.to_python,
43 TEST_USER_ADMIN_LOGIN)
43 TEST_USER_ADMIN_LOGIN)
44 self.assertEqual('test', validator.to_python('test'))
44 self.assertEqual('test', validator.to_python('test'))
45
45
46 validator = v.ValidUsername(edit=True, old_data={'user_id': 1})
46 validator = v.ValidUsername(edit=True, old_data={'user_id': 1})
47
47
48 def test_ValidRepoUser(self):
48 def test_ValidRepoUser(self):
49 validator = v.ValidRepoUser()
49 validator = v.ValidRepoUser()
50 self.assertRaises(formencode.Invalid, validator.to_python, 'nouser')
50 self.assertRaises(formencode.Invalid, validator.to_python, 'nouser')
51 self.assertEqual(TEST_USER_ADMIN_LOGIN,
51 self.assertEqual(TEST_USER_ADMIN_LOGIN,
52 validator.to_python(TEST_USER_ADMIN_LOGIN))
52 validator.to_python(TEST_USER_ADMIN_LOGIN))
53
53
54 def test_ValidUsersGroup(self):
54 def test_ValidUsersGroup(self):
55 validator = v.ValidUsersGroup()
55 validator = v.ValidUsersGroup()
56 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
56 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
57 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
57 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
58
58
59 gr = UsersGroupModel().create('test')
59 gr = UsersGroupModel().create('test')
60 gr2 = UsersGroupModel().create('tes2')
60 gr2 = UsersGroupModel().create('tes2')
61 Session.commit()
61 Session.commit()
62 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
62 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
63 assert gr.users_group_id != None
63 assert gr.users_group_id != None
64 validator = v.ValidUsersGroup(edit=True,
64 validator = v.ValidUsersGroup(edit=True,
65 old_data={'users_group_id':
65 old_data={'users_group_id':
66 gr2.users_group_id})
66 gr2.users_group_id})
67
67
68 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
68 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
69 self.assertRaises(formencode.Invalid, validator.to_python, 'TesT')
69 self.assertRaises(formencode.Invalid, validator.to_python, 'TesT')
70 self.assertRaises(formencode.Invalid, validator.to_python, 'TEST')
70 self.assertRaises(formencode.Invalid, validator.to_python, 'TEST')
71 UsersGroupModel().delete(gr)
71 UsersGroupModel().delete(gr)
72 UsersGroupModel().delete(gr2)
72 UsersGroupModel().delete(gr2)
73 Session.commit()
73 Session.commit()
74
74
75 def test_ValidReposGroup(self):
75 def test_ValidReposGroup(self):
76 validator = v.ValidReposGroup()
76 validator = v.ValidReposGroup()
77 model = ReposGroupModel()
77 model = ReposGroupModel()
78 self.assertRaises(formencode.Invalid, validator.to_python,
78 self.assertRaises(formencode.Invalid, validator.to_python,
79 {'group_name': HG_REPO, })
79 {'group_name': HG_REPO, })
80 gr = model.create(group_name='test_gr', group_description='desc',
80 gr = model.create(group_name='test_gr', group_description='desc',
81 parent=None,
81 parent=None,
82 just_db=True)
82 just_db=True)
83 self.assertRaises(formencode.Invalid,
83 self.assertRaises(formencode.Invalid,
84 validator.to_python, {'group_name': gr.group_name, })
84 validator.to_python, {'group_name': gr.group_name, })
85
85
86 validator = v.ValidReposGroup(edit=True,
86 validator = v.ValidReposGroup(edit=True,
87 old_data={'group_id': gr.group_id})
87 old_data={'group_id': gr.group_id})
88 self.assertRaises(formencode.Invalid,
88 self.assertRaises(formencode.Invalid,
89 validator.to_python, {
89 validator.to_python, {
90 'group_name': gr.group_name + 'n',
90 'group_name': gr.group_name + 'n',
91 'group_parent_id': gr.group_id
91 'group_parent_id': gr.group_id
92 })
92 })
93 model.delete(gr)
93 model.delete(gr)
94
94
95 def test_ValidPassword(self):
95 def test_ValidPassword(self):
96 validator = v.ValidPassword()
96 validator = v.ValidPassword()
97 self.assertEqual('lol', validator.to_python('lol'))
97 self.assertEqual('lol', validator.to_python('lol'))
98 self.assertEqual(None, validator.to_python(None))
98 self.assertEqual(None, validator.to_python(None))
99 self.assertRaises(formencode.Invalid, validator.to_python, 'Δ…Δ‡ΕΌΕΊ')
99 self.assertRaises(formencode.Invalid, validator.to_python, 'Δ…Δ‡ΕΌΕΊ')
100
100
101 def test_ValidPasswordsMatch(self):
101 def test_ValidPasswordsMatch(self):
102 validator = v.ValidPasswordsMatch()
102 validator = v.ValidPasswordsMatch()
103 self.assertRaises(formencode.Invalid,
103 self.assertRaises(formencode.Invalid,
104 validator.to_python, {'password': 'pass',
104 validator.to_python, {'password': 'pass',
105 'password_confirmation': 'pass2'})
105 'password_confirmation': 'pass2'})
106
106
107 self.assertRaises(formencode.Invalid,
107 self.assertRaises(formencode.Invalid,
108 validator.to_python, {'new_password': 'pass',
108 validator.to_python, {'new_password': 'pass',
109 'password_confirmation': 'pass2'})
109 'password_confirmation': 'pass2'})
110
110
111 self.assertEqual({'new_password': 'pass',
111 self.assertEqual({'new_password': 'pass',
112 'password_confirmation': 'pass'},
112 'password_confirmation': 'pass'},
113 validator.to_python({'new_password': 'pass',
113 validator.to_python({'new_password': 'pass',
114 'password_confirmation': 'pass'}))
114 'password_confirmation': 'pass'}))
115
115
116 self.assertEqual({'password': 'pass',
116 self.assertEqual({'password': 'pass',
117 'password_confirmation': 'pass'},
117 'password_confirmation': 'pass'},
118 validator.to_python({'password': 'pass',
118 validator.to_python({'password': 'pass',
119 'password_confirmation': 'pass'}))
119 'password_confirmation': 'pass'}))
120
120
121 def test_ValidAuth(self):
121 def test_ValidAuth(self):
122 validator = v.ValidAuth()
122 validator = v.ValidAuth()
123 valid_creds = {
123 valid_creds = {
124 'username': TEST_USER_REGULAR2_LOGIN,
124 'username': TEST_USER_REGULAR2_LOGIN,
125 'password': TEST_USER_REGULAR2_PASS,
125 'password': TEST_USER_REGULAR2_PASS,
126 }
126 }
127 invalid_creds = {
127 invalid_creds = {
128 'username': 'err',
128 'username': 'err',
129 'password': 'err',
129 'password': 'err',
130 }
130 }
131 self.assertEqual(valid_creds, validator.to_python(valid_creds))
131 self.assertEqual(valid_creds, validator.to_python(valid_creds))
132 self.assertRaises(formencode.Invalid,
132 self.assertRaises(formencode.Invalid,
133 validator.to_python, invalid_creds)
133 validator.to_python, invalid_creds)
134
134
135 def test_ValidAuthToken(self):
135 def test_ValidAuthToken(self):
136 validator = v.ValidAuthToken()
136 validator = v.ValidAuthToken()
137 # this is untestable without a threadlocal
137 # this is untestable without a threadlocal
138 # self.assertRaises(formencode.Invalid,
138 # self.assertRaises(formencode.Invalid,
139 # validator.to_python, 'BadToken')
139 # validator.to_python, 'BadToken')
140 validator
140 validator
141
141
142 def test_ValidRepoName(self):
142 def test_ValidRepoName(self):
143 validator = v.ValidRepoName()
143 validator = v.ValidRepoName()
144
144
145 self.assertRaises(formencode.Invalid,
145 self.assertRaises(formencode.Invalid,
146 validator.to_python, {'repo_name': ''})
146 validator.to_python, {'repo_name': ''})
147
147
148 self.assertRaises(formencode.Invalid,
148 self.assertRaises(formencode.Invalid,
149 validator.to_python, {'repo_name': HG_REPO})
149 validator.to_python, {'repo_name': HG_REPO})
150
150
151 gr = ReposGroupModel().create(group_name='group_test',
151 gr = ReposGroupModel().create(group_name='group_test',
152 group_description='desc',
152 group_description='desc',
153 parent=None,)
153 parent=None,)
154 self.assertRaises(formencode.Invalid,
154 self.assertRaises(formencode.Invalid,
155 validator.to_python, {'repo_name': gr.group_name})
155 validator.to_python, {'repo_name': gr.group_name})
156
156
157 #TODO: write an error case for that ie. create a repo withinh a group
157 #TODO: write an error case for that ie. create a repo withinh a group
158 # self.assertRaises(formencode.Invalid,
158 # self.assertRaises(formencode.Invalid,
159 # validator.to_python, {'repo_name': 'some',
159 # validator.to_python, {'repo_name': 'some',
160 # 'repo_group': gr.group_id})
160 # 'repo_group': gr.group_id})
161
161
162 def test_ValidForkName(self):
162 def test_ValidForkName(self):
163 # this uses ValidRepoName validator
163 # this uses ValidRepoName validator
164 assert True
164 assert True
165
165
166 @parameterized.expand([
166 @parameterized.expand([
167 ('test', 'test'), ('lolz!', 'lolz'), (' aavv', 'aavv'),
167 ('test', 'test'), ('lolz!', 'lolz'), (' aavv', 'aavv'),
168 ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'),
168 ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'),
169 ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'),
169 ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'),
170 ('/]re po', 're-po')])
170 ('/]re po', 're-po')])
171 def test_SlugifyName(self, name, expected):
171 def test_SlugifyName(self, name, expected):
172 validator = v.SlugifyName()
172 validator = v.SlugifyName()
173 self.assertEqual(expected, validator.to_python(name))
173 self.assertEqual(expected, validator.to_python(name))
174
174
175 def test_ValidCloneUri(self):
175 def test_ValidCloneUri(self):
176 #TODO: write this one
176 #TODO: write this one
177 pass
177 pass
178
178
179 def test_ValidForkType(self):
179 def test_ValidForkType(self):
180 validator = v.ValidForkType(old_data={'repo_type': 'hg'})
180 validator = v.ValidForkType(old_data={'repo_type': 'hg'})
181 self.assertEqual('hg', validator.to_python('hg'))
181 self.assertEqual('hg', validator.to_python('hg'))
182 self.assertRaises(formencode.Invalid, validator.to_python, 'git')
182 self.assertRaises(formencode.Invalid, validator.to_python, 'git')
183
183
184 def test_ValidPerms(self):
184 def test_ValidPerms(self):
185 #TODO: write this one
185 #TODO: write this one
186 pass
186 pass
187
187
188 def test_ValidSettings(self):
188 def test_ValidSettings(self):
189 validator = v.ValidSettings()
189 validator = v.ValidSettings()
190 self.assertEqual({'pass': 'pass'},
190 self.assertEqual({'pass': 'pass'},
191 validator.to_python(value={'user': 'test',
191 validator.to_python(value={'user': 'test',
192 'pass': 'pass'}))
192 'pass': 'pass'}))
193
193
194 self.assertEqual({'user2': 'test', 'pass': 'pass'},
194 self.assertEqual({'user2': 'test', 'pass': 'pass'},
195 validator.to_python(value={'user2': 'test',
195 validator.to_python(value={'user2': 'test',
196 'pass': 'pass'}))
196 'pass': 'pass'}))
197
197
198 def test_ValidPath(self):
198 def test_ValidPath(self):
199 validator = v.ValidPath()
199 validator = v.ValidPath()
200 self.assertEqual(TESTS_TMP_PATH,
200 self.assertEqual(TESTS_TMP_PATH,
201 validator.to_python(TESTS_TMP_PATH))
201 validator.to_python(TESTS_TMP_PATH))
202 self.assertRaises(formencode.Invalid, validator.to_python,
202 self.assertRaises(formencode.Invalid, validator.to_python,
203 '/no_such_dir')
203 '/no_such_dir')
204
204
205 def test_UniqSystemEmail(self):
205 def test_UniqSystemEmail(self):
206 validator = v.UniqSystemEmail(old_data={})
206 validator = v.UniqSystemEmail(old_data={})
207
207
208 self.assertEqual('mail@python.org',
208 self.assertEqual('mail@python.org',
209 validator.to_python('MaiL@Python.org'))
209 validator.to_python('MaiL@Python.org'))
210
210
211 email = TEST_USER_REGULAR2_EMAIL
211 email = TEST_USER_REGULAR2_EMAIL
212 self.assertRaises(formencode.Invalid, validator.to_python, email)
212 self.assertRaises(formencode.Invalid, validator.to_python, email)
213
213
214 def test_ValidSystemEmail(self):
214 def test_ValidSystemEmail(self):
215 validator = v.ValidSystemEmail()
215 validator = v.ValidSystemEmail()
216 email = TEST_USER_REGULAR2_EMAIL
216 email = TEST_USER_REGULAR2_EMAIL
217
217
218 self.assertEqual(email, validator.to_python(email))
218 self.assertEqual(email, validator.to_python(email))
219 self.assertRaises(formencode.Invalid, validator.to_python, 'err')
219 self.assertRaises(formencode.Invalid, validator.to_python, 'err')
220
220
221 def test_LdapLibValidator(self):
221 def test_LdapLibValidator(self):
222 validator = v.LdapLibValidator()
222 validator = v.LdapLibValidator()
223 self.assertRaises(v.LdapImportError, validator.to_python, 'err')
223 self.assertRaises(v.LdapImportError, validator.to_python, 'err')
224
224
225 def test_AttrLoginValidator(self):
225 def test_AttrLoginValidator(self):
226 validator = v.AttrLoginValidator()
226 validator = v.AttrLoginValidator()
227 self.assertRaises(formencode.Invalid, validator.to_python, 123)
227 self.assertRaises(formencode.Invalid, validator.to_python, 123)
228
228
229 def test_NotReviewedRevisions(self):
229 def test_NotReviewedRevisions(self):
230 validator = v.NotReviewedRevisions()
230 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
231 validator = v.NotReviewedRevisions(repo_id)
231 rev = '0' * 40
232 rev = '0' * 40
232 # add status for a rev, that should throw an error because it is already
233 # add status for a rev, that should throw an error because it is already
233 # reviewed
234 # reviewed
234 new_status = ChangesetStatus()
235 new_status = ChangesetStatus()
235 new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
236 new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
236 new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO)
237 new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO)
237 new_status.status = ChangesetStatus.STATUS_APPROVED
238 new_status.status = ChangesetStatus.STATUS_APPROVED
238 new_status.comment = None
239 new_status.comment = None
239 new_status.revision = rev
240 new_status.revision = rev
240 Session().add(new_status)
241 Session().add(new_status)
241 Session().commit()
242 Session().commit()
242 try:
243 try:
243 self.assertRaises(formencode.Invalid, validator.to_python, [rev])
244 self.assertRaises(formencode.Invalid, validator.to_python, [rev])
244 finally:
245 finally:
245 Session().delete(new_status)
246 Session().delete(new_status)
246 Session().commit()
247 Session().commit()
General Comments 0
You need to be logged in to leave comments. Login now