##// 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 1 List of contributors to RhodeCode project:
2 2 Marcin KuΕΊmiΕ„ski <marcin@python-works.com>
3 3 Lukasz Balcerzak <lukaszbalcerzak@gmail.com>
4 4 Jason Harris <jason@jasonfharris.com>
5 5 Thayne Harbaugh <thayne@fusionio.com>
6 6 cejones <>
7 7 Thomas Waldmann <tw-public@gmx.de>
8 8 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
9 9 Dmitri Kuznetsov <>
10 10 Jared Bunting <jared.bunting@peachjean.com>
11 11 Steve Romanow <slestak989@gmail.com>
12 12 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
13 13 Ankit Solanki <ankit.solanki@gmail.com>
14 14 Liad Shani <liadff@gmail.com>
15 15 Les Peabody <lpeabody@gmail.com>
16 16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
17 17 Matt Zuba <matt.zuba@goodwillaz.org>
18 18 Aras Pranckevicius <aras@unity3d.com>
19 19 Tony Bussieres <t.bussieres@gmail.com>
20 20 Erwin Kroon <e.kroon@smartmetersolutions.nl>
21 21 nansenat16 <nansenat16@null.tw>
22 22 Vincent Duvert <vincent@duvert.net>
23 23 Takumi IINO <trot.thunder@gmail.com>
24 24 Indra Talip <indra.talip@gmail.com>
25 25 James Rhodes <jrhodes@redpointsoftware.com.au>
26 26 Dominik Ruf <dominikruf@gmail.com>
27 27 xpol <xpolife@gmail.com>
28 28 Vincent Caron <vcaron@bearstech.com>
29 29 Zachary Auclair <zach101@gmail.com>
30 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 1 .. _api:
2 2
3 3 ===
4 4 API
5 5 ===
6 6
7 7
8 8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 9 There's a single schema for calling all api methods. API is implemented
10 10 with JSON protocol both ways. An url to send API request to RhodeCode is
11 11 <your_server>/_admin/api
12 12
13 13 API ACCESS FOR WEB VIEWS
14 14 ++++++++++++++++++++++++
15 15
16 16 API access can also be turned on for each web view in RhodeCode that is
17 17 decorated with `@LoginRequired` decorator. To enable API access simple change
18 18 the standard login decorator to `@LoginRequired(api_access=True)`.
19 19 After this change, a rhodecode view can be accessed without login by adding a
20 20 GET parameter `?api_key=<api_key>` to url. By default this is only
21 21 enabled on RSS/ATOM feed views.
22 22
23 23
24 24 API ACCESS
25 25 ++++++++++
26 26
27 27 All clients are required to send JSON-RPC spec JSON data::
28 28
29 29 {
30 30 "id:"<id>",
31 31 "api_key":"<api_key>",
32 32 "method":"<method_name>",
33 33 "args":{"<arg_key>":"<arg_val>"}
34 34 }
35 35
36 36 Example call for autopulling remotes repos using curl::
37 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 39 Simply provide
40 40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
41 41 - *api_key* for access and permission validation.
42 42 - *method* is name of method to call
43 43 - *args* is an key:value list of arguments to pass to method
44 44
45 45 .. note::
46 46
47 47 api_key can be found in your user account page
48 48
49 49
50 50 RhodeCode API will return always a JSON-RPC response::
51 51
52 52 {
53 53 "id":<id>, # matching id sent by request
54 54 "result": "<result>"|null, # JSON formatted result, null if any errors
55 55 "error": "null"|<error_message> # JSON formatted error (if any)
56 56 }
57 57
58 58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
59 59 calling api *error* key from response will contain failure description
60 60 and result will be null.
61 61
62 62
63 63 API CLIENT
64 64 ++++++++++
65 65
66 66 From version 1.4 RhodeCode adds a script that allows to easily
67 67 communicate with API. After installing RhodeCode a `rhodecode-api` script
68 68 will be available.
69 69
70 70 To get started quickly simply run::
71 71
72 72 rhodecode-api _create_config --apikey=<youapikey> --apihost=<rhodecode host>
73 73
74 74 This will create a file named .config in the directory you executed it storing
75 75 json config file with credentials. You can skip this step and always provide
76 76 both of the arguments to be able to communicate with server
77 77
78 78
79 79 after that simply run any api command for example get_repo::
80 80
81 81 rhodecode-api get_repo
82 82
83 83 calling {"api_key": "<apikey>", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000
84 84 rhodecode said:
85 85 {'error': 'Missing non optional `repoid` arg in JSON DATA',
86 86 'id': 75,
87 87 'result': None}
88 88
89 89 Ups looks like we forgot to add an argument
90 90
91 91 Let's try again now giving the repoid as parameters::
92 92
93 93 rhodecode-api get_repo repoid:rhodecode
94 94
95 95 calling {"api_key": "<apikey>", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000
96 96 rhodecode said:
97 97 {'error': None,
98 98 'id': 39,
99 99 'result': <json data...>}
100 100
101 101
102 102
103 103 API METHODS
104 104 +++++++++++
105 105
106 106
107 107 pull
108 108 ----
109 109
110 110 Pulls given repo from remote location. Can be used to automatically keep
111 111 remote repos up to date. This command can be executed only using api_key
112 112 belonging to user with admin rights
113 113
114 114 INPUT::
115 115
116 116 id : <id_for_response>
117 117 api_key : "<api_key>"
118 118 method : "pull"
119 119 args : {
120 120 "repoid" : "<reponame or repo_id>"
121 121 }
122 122
123 123 OUTPUT::
124 124
125 125 id : <id_given_in_input>
126 126 result : "Pulled from `<reponame>`"
127 127 error : null
128 128
129 129
130 130 rescan_repos
131 131 ------------
132 132
133 133 Dispatch rescan repositories action. If remove_obsolete is set
134 134 RhodeCode will delete repos that are in database but not in the filesystem.
135 135 This command can be executed only using api_key belonging to user with admin
136 136 rights.
137 137
138 138 INPUT::
139 139
140 140 id : <id_for_response>
141 141 api_key : "<api_key>"
142 142 method : "rescan_repos"
143 143 args : {
144 144 "remove_obsolete" : "<boolean = Optional(False)>"
145 145 }
146 146
147 147 OUTPUT::
148 148
149 149 id : <id_given_in_input>
150 150 result : "{'added': [<list of names of added repos>],
151 151 'removed': [<list of names of removed repos>]}"
152 152 error : null
153 153
154 154
155 155 lock
156 156 ----
157 157
158 158 Set locking state on given repository by given user.
159 159 This command can be executed only using api_key belonging to user with admin
160 160 rights.
161 161
162 162 INPUT::
163 163
164 164 id : <id_for_response>
165 165 api_key : "<api_key>"
166 166 method : "lock"
167 167 args : {
168 168 "repoid" : "<reponame or repo_id>"
169 169 "userid" : "<user_id or username>",
170 170 "locked" : "<bool true|false>"
171 171
172 172 }
173 173
174 174 OUTPUT::
175 175
176 176 id : <id_given_in_input>
177 177 result : "User `<username>` set lock state for repo `<reponame>` to `true|false`"
178 178 error : null
179 179
180 180
181 181 get_user
182 182 --------
183 183
184 184 Get's an user by username or user_id, Returns empty result if user is not found.
185 185 This command can be executed only using api_key belonging to user with admin
186 186 rights.
187 187
188 188
189 189 INPUT::
190 190
191 191 id : <id_for_response>
192 192 api_key : "<api_key>"
193 193 method : "get_user"
194 194 args : {
195 195 "userid" : "<username or user_id>"
196 196 }
197 197
198 198 OUTPUT::
199 199
200 200 id : <id_given_in_input>
201 201 result: None if user does not exist or
202 202 {
203 203 "user_id" : "<user_id>",
204 204 "username" : "<username>",
205 205 "firstname": "<firstname>",
206 206 "lastname" : "<lastname>",
207 207 "email" : "<email>",
208 208 "emails": "<list_of_all_additional_emails>",
209 209 "active" : "<bool>",
210 210 "admin" :Β  "<bool>",
211 211 "ldap_dn" : "<ldap_dn>",
212 212 "last_login": "<last_login>",
213 213 "permissions": {
214 214 "global": ["hg.create.repository",
215 215 "repository.read",
216 216 "hg.register.manual_activate"],
217 217 "repositories": {"repo1": "repository.none"},
218 218 "repositories_groups": {"Group1": "group.read"}
219 219 },
220 220 }
221 221
222 222 error: null
223 223
224 224
225 225 get_users
226 226 ---------
227 227
228 228 Lists all existing users. This command can be executed only using api_key
229 229 belonging to user with admin rights.
230 230
231 231
232 232 INPUT::
233 233
234 234 id : <id_for_response>
235 235 api_key : "<api_key>"
236 236 method : "get_users"
237 237 args : { }
238 238
239 239 OUTPUT::
240 240
241 241 id : <id_given_in_input>
242 242 result: [
243 243 {
244 244 "user_id" : "<user_id>",
245 245 "username" : "<username>",
246 246 "firstname": "<firstname>",
247 247 "lastname" : "<lastname>",
248 248 "email" : "<email>",
249 249 "emails": "<list_of_all_additional_emails>",
250 250 "active" : "<bool>",
251 251 "admin" :Β  "<bool>",
252 252 "ldap_dn" : "<ldap_dn>",
253 253 "last_login": "<last_login>",
254 254 },
255 255 …
256 256 ]
257 257 error: null
258 258
259 259
260 260 create_user
261 261 -----------
262 262
263 263 Creates new user. This command can
264 264 be executed only using api_key belonging to user with admin rights.
265 265
266 266
267 267 INPUT::
268 268
269 269 id : <id_for_response>
270 270 api_key : "<api_key>"
271 271 method : "create_user"
272 272 args : {
273 273 "username" : "<username>",
274 274 "email" : "<useremail>",
275 275 "password" : "<password>",
276 276 "firstname" : "<firstname> = Optional(None)",
277 277 "lastname" : "<lastname> = Optional(None)",
278 278 "active" : "<bool> = Optional(True)",
279 279 "admin" : "<bool> = Optional(False)",
280 280 "ldap_dn" : "<ldap_dn> = Optional(None)"
281 281 }
282 282
283 283 OUTPUT::
284 284
285 285 id : <id_given_in_input>
286 286 result: {
287 287 "msg" : "created new user `<username>`",
288 288 "user": {
289 289 "user_id" : "<user_id>",
290 290 "username" : "<username>",
291 291 "firstname": "<firstname>",
292 292 "lastname" : "<lastname>",
293 293 "email" : "<email>",
294 294 "emails": "<list_of_all_additional_emails>",
295 295 "active" : "<bool>",
296 296 "admin" :Β  "<bool>",
297 297 "ldap_dn" : "<ldap_dn>",
298 298 "last_login": "<last_login>",
299 299 },
300 300 }
301 301 error: null
302 302
303 303
304 304 update_user
305 305 -----------
306 306
307 307 updates given user if such user exists. This command can
308 308 be executed only using api_key belonging to user with admin rights.
309 309
310 310
311 311 INPUT::
312 312
313 313 id : <id_for_response>
314 314 api_key : "<api_key>"
315 315 method : "update_user"
316 316 args : {
317 317 "userid" : "<user_id or username>",
318 318 "username" : "<username> = Optional",
319 319 "email" : "<useremail> = Optional",
320 320 "password" : "<password> = Optional",
321 321 "firstname" : "<firstname> = Optional",
322 322 "lastname" : "<lastname> = Optional",
323 323 "active" : "<bool> = Optional",
324 324 "admin" : "<bool> = Optional",
325 325 "ldap_dn" : "<ldap_dn> = Optional"
326 326 }
327 327
328 328 OUTPUT::
329 329
330 330 id : <id_given_in_input>
331 331 result: {
332 332 "msg" : "updated user ID:<userid> <username>",
333 333 "user": {
334 334 "user_id" : "<user_id>",
335 335 "username" : "<username>",
336 336 "firstname": "<firstname>",
337 337 "lastname" : "<lastname>",
338 338 "email" : "<email>",
339 339 "emails": "<list_of_all_additional_emails>",
340 340 "active" : "<bool>",
341 341 "admin" :Β  "<bool>",
342 342 "ldap_dn" : "<ldap_dn>",
343 343 "last_login": "<last_login>",
344 344 },
345 345 }
346 346 error: null
347 347
348 348
349 349 delete_user
350 350 -----------
351 351
352 352
353 353 deletes givenuser if such user exists. This command can
354 354 be executed only using api_key belonging to user with admin rights.
355 355
356 356
357 357 INPUT::
358 358
359 359 id : <id_for_response>
360 360 api_key : "<api_key>"
361 361 method : "delete_user"
362 362 args : {
363 363 "userid" : "<user_id or username>",
364 364 }
365 365
366 366 OUTPUT::
367 367
368 368 id : <id_given_in_input>
369 369 result: {
370 370 "msg" : "deleted user ID:<userid> <username>",
371 371 "user": null
372 372 }
373 373 error: null
374 374
375 375
376 376 get_users_group
377 377 ---------------
378 378
379 379 Gets an existing users group. This command can be executed only using api_key
380 380 belonging to user with admin rights.
381 381
382 382
383 383 INPUT::
384 384
385 385 id : <id_for_response>
386 386 api_key : "<api_key>"
387 387 method : "get_users_group"
388 388 args : {
389 389 "usersgroupid" : "<users group id or name>"
390 390 }
391 391
392 392 OUTPUT::
393 393
394 394 id : <id_given_in_input>
395 395 result : None if group not exist
396 396 {
397 397 "users_group_id" : "<id>",
398 398 "group_name" : "<groupname>",
399 399 "active": "<bool>",
400 400 "members" : [
401 401 {
402 402 "user_id" : "<user_id>",
403 403 "username" : "<username>",
404 404 "firstname": "<firstname>",
405 405 "lastname" : "<lastname>",
406 406 "email" : "<email>",
407 407 "emails": "<list_of_all_additional_emails>",
408 408 "active" : "<bool>",
409 409 "admin" :Β  "<bool>",
410 410 "ldap_dn" : "<ldap_dn>",
411 411 "last_login": "<last_login>",
412 412 },
413 413 …
414 414 ]
415 415 }
416 416 error : null
417 417
418 418
419 419 get_users_groups
420 420 ----------------
421 421
422 422 Lists all existing users groups. This command can be executed only using
423 423 api_key belonging to user with admin rights.
424 424
425 425
426 426 INPUT::
427 427
428 428 id : <id_for_response>
429 429 api_key : "<api_key>"
430 430 method : "get_users_groups"
431 431 args : { }
432 432
433 433 OUTPUT::
434 434
435 435 id : <id_given_in_input>
436 436 result : [
437 437 {
438 438 "users_group_id" : "<id>",
439 439 "group_name" : "<groupname>",
440 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 444 error : null
460 445
461 446
462 447 create_users_group
463 448 ------------------
464 449
465 450 Creates new users group. This command can be executed only using api_key
466 451 belonging to user with admin rights
467 452
468 453
469 454 INPUT::
470 455
471 456 id : <id_for_response>
472 457 api_key : "<api_key>"
473 458 method : "create_users_group"
474 459 args: {
475 460 "group_name": "<groupname>",
476 461 "active":"<bool> = Optional(True)"
477 462 }
478 463
479 464 OUTPUT::
480 465
481 466 id : <id_given_in_input>
482 467 result: {
483 468 "msg": "created new users group `<groupname>`",
484 469 "users_group": {
485 470 "users_group_id" : "<id>",
486 471 "group_name" : "<groupname>",
487 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 475 error: null
506 476
507 477
508 478 add_user_to_users_group
509 479 -----------------------
510 480
511 481 Adds a user to a users group. If user exists in that group success will be
512 482 `false`. This command can be executed only using api_key
513 483 belonging to user with admin rights
514 484
515 485
516 486 INPUT::
517 487
518 488 id : <id_for_response>
519 489 api_key : "<api_key>"
520 490 method : "add_user_users_group"
521 491 args: {
522 492 "usersgroupid" : "<users group id or name>",
523 493 "userid" : "<user_id or username>",
524 494 }
525 495
526 496 OUTPUT::
527 497
528 498 id : <id_given_in_input>
529 499 result: {
530 500 "success": True|False # depends on if member is in group
531 501 "msg": "added member `<username>` to users group `<groupname>` |
532 502 User is already in that group"
533 503 }
534 504 error: null
535 505
536 506
537 507 remove_user_from_users_group
538 508 ----------------------------
539 509
540 510 Removes a user from a users group. If user is not in given group success will
541 511 be `false`. This command can be executed only
542 512 using api_key belonging to user with admin rights
543 513
544 514
545 515 INPUT::
546 516
547 517 id : <id_for_response>
548 518 api_key : "<api_key>"
549 519 method : "remove_user_from_users_group"
550 520 args: {
551 521 "usersgroupid" : "<users group id or name>",
552 522 "userid" : "<user_id or username>",
553 523 }
554 524
555 525 OUTPUT::
556 526
557 527 id : <id_given_in_input>
558 528 result: {
559 529 "success": True|False, # depends on if member is in group
560 530 "msg": "removed member <username> from users group <groupname> |
561 531 User wasn't in group"
562 532 }
563 533 error: null
564 534
565 535
566 536 get_repo
567 537 --------
568 538
569 539 Gets an existing repository by it's name or repository_id. Members will return
570 540 either users_group or user associated to that repository. This command can
571 541 be executed only using api_key belonging to user with admin rights.
572 542
573 543
574 544 INPUT::
575 545
576 546 id : <id_for_response>
577 547 api_key : "<api_key>"
578 548 method : "get_repo"
579 549 args: {
580 550 "repoid" : "<reponame or repo_id>"
581 551 }
582 552
583 553 OUTPUT::
584 554
585 555 id : <id_given_in_input>
586 556 result: None if repository does not exist or
587 557 {
588 558 "repo_id" : "<repo_id>",
589 559 "repo_name" : "<reponame>"
590 560 "repo_type" : "<repo_type>",
591 561 "clone_uri" : "<clone_uri>",
592 562 "private": : "<bool>",
593 563 "created_on" : "<datetimecreated>",
594 564 "description" : "<description>",
595 565 "landing_rev": "<landing_rev>",
596 566 "owner": "<repo_owner>",
597 567 "fork_of": "<name_of_fork_parent>",
598 568 "members" : [
599 569 {
600 570 "type": "user",
601 571 "user_id" : "<user_id>",
602 572 "username" : "<username>",
603 573 "firstname": "<firstname>",
604 574 "lastname" : "<lastname>",
605 575 "email" : "<email>",
606 576 "emails": "<list_of_all_additional_emails>",
607 577 "active" : "<bool>",
608 578 "admin" :Β  "<bool>",
609 579 "ldap_dn" : "<ldap_dn>",
610 580 "last_login": "<last_login>",
611 581 "permission" : "repository.(read|write|admin)"
612 582 },
613 583 …
614 584 {
615 585 "type": "users_group",
616 586 "id" : "<usersgroupid>",
617 587 "name" : "<usersgroupname>",
618 588 "active": "<bool>",
619 589 "permission" : "repository.(read|write|admin)"
620 590 },
621 591 …
622 592 ]
623 593 }
624 594 error: null
625 595
626 596
627 597 get_repos
628 598 ---------
629 599
630 600 Lists all existing repositories. This command can be executed only using api_key
631 601 belonging to user with admin rights
632 602
633 603
634 604 INPUT::
635 605
636 606 id : <id_for_response>
637 607 api_key : "<api_key>"
638 608 method : "get_repos"
639 609 args: { }
640 610
641 611 OUTPUT::
642 612
643 613 id : <id_given_in_input>
644 614 result: [
645 615 {
646 616 "repo_id" : "<repo_id>",
647 617 "repo_name" : "<reponame>"
648 618 "repo_type" : "<repo_type>",
649 619 "clone_uri" : "<clone_uri>",
650 620 "private": : "<bool>",
651 621 "created_on" : "<datetimecreated>",
652 622 "description" : "<description>",
653 623 "landing_rev": "<landing_rev>",
654 624 "owner": "<repo_owner>",
655 625 "fork_of": "<name_of_fork_parent>",
656 626 },
657 627 …
658 628 ]
659 629 error: null
660 630
661 631
662 632 get_repo_nodes
663 633 --------------
664 634
665 635 returns a list of nodes and it's children in a flat list for a given path
666 636 at given revision. It's possible to specify ret_type to show only `files` or
667 637 `dirs`. This command can be executed only using api_key belonging to user
668 638 with admin rights
669 639
670 640
671 641 INPUT::
672 642
673 643 id : <id_for_response>
674 644 api_key : "<api_key>"
675 645 method : "get_repo_nodes"
676 646 args: {
677 647 "repoid" : "<reponame or repo_id>"
678 648 "revision" : "<revision>",
679 649 "root_path" : "<root_path>",
680 650 "ret_type" : "<ret_type> = Optional('all')"
681 651 }
682 652
683 653 OUTPUT::
684 654
685 655 id : <id_given_in_input>
686 656 result: [
687 657 {
688 658 "name" : "<name>"
689 659 "type" : "<type>",
690 660 },
691 661 …
692 662 ]
693 663 error: null
694 664
695 665
696 666 create_repo
697 667 -----------
698 668
699 669 Creates a repository. This command can be executed only using api_key
700 670 belonging to user with admin rights.
701 671 If repository name contains "/", all needed repository groups will be created.
702 672 For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
703 673 and create "baz" repository with "bar" as group.
704 674
705 675
706 676 INPUT::
707 677
708 678 id : <id_for_response>
709 679 api_key : "<api_key>"
710 680 method : "create_repo"
711 681 args: {
712 682 "repo_name" : "<reponame>",
713 683 "owner" : "<onwer_name_or_id>",
714 684 "repo_type" : "<repo_type>",
715 685 "description" : "<description> = Optional('')",
716 686 "private" : "<bool> = Optional(False)",
717 687 "clone_uri" : "<clone_uri> = Optional(None)",
718 688 "landing_rev" : "<landing_rev> = Optional('tip')",
719 689 }
720 690
721 691 OUTPUT::
722 692
723 693 id : <id_given_in_input>
724 694 result: {
725 695 "msg": "Created new repository `<reponame>`",
726 696 "repo": {
727 697 "repo_id" : "<repo_id>",
728 698 "repo_name" : "<reponame>"
729 699 "repo_type" : "<repo_type>",
730 700 "clone_uri" : "<clone_uri>",
731 701 "private": : "<bool>",
732 702 "created_on" : "<datetimecreated>",
733 703 "description" : "<description>",
734 704 "landing_rev": "<landing_rev>",
735 705 "owner": "<repo_owner>",
736 706 "fork_of": "<name_of_fork_parent>",
737 707 },
738 708 }
739 709 error: null
740 710
741 711
742 712 delete_repo
743 713 -----------
744 714
745 715 Deletes a repository. This command can be executed only using api_key
746 716 belonging to user with admin rights.
747 717
748 718
749 719 INPUT::
750 720
751 721 id : <id_for_response>
752 722 api_key : "<api_key>"
753 723 method : "delete_repo"
754 724 args: {
755 725 "repoid" : "<reponame or repo_id>"
756 726 }
757 727
758 728 OUTPUT::
759 729
760 730 id : <id_given_in_input>
761 731 result: {
762 732 "msg": "Deleted repository `<reponame>`",
763 733 "success": true
764 734 }
765 735 error: null
766 736
767 737
768 738 grant_user_permission
769 739 ---------------------
770 740
771 741 Grant permission for user on given repository, or update existing one
772 742 if found. This command can be executed only using api_key belonging to user
773 743 with admin rights.
774 744
775 745
776 746 INPUT::
777 747
778 748 id : <id_for_response>
779 749 api_key : "<api_key>"
780 750 method : "grant_user_permission"
781 751 args: {
782 752 "repoid" : "<reponame or repo_id>"
783 753 "userid" : "<username or user_id>"
784 754 "perm" : "(repository.(none|read|write|admin))",
785 755 }
786 756
787 757 OUTPUT::
788 758
789 759 id : <id_given_in_input>
790 760 result: {
791 761 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
792 762 "success": true
793 763 }
794 764 error: null
795 765
796 766
797 767 revoke_user_permission
798 768 ----------------------
799 769
800 770 Revoke permission for user on given repository. This command can be executed
801 771 only using api_key belonging to user with admin rights.
802 772
803 773
804 774 INPUT::
805 775
806 776 id : <id_for_response>
807 777 api_key : "<api_key>"
808 778 method : "revoke_user_permission"
809 779 args: {
810 780 "repoid" : "<reponame or repo_id>"
811 781 "userid" : "<username or user_id>"
812 782 }
813 783
814 784 OUTPUT::
815 785
816 786 id : <id_given_in_input>
817 787 result: {
818 788 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
819 789 "success": true
820 790 }
821 791 error: null
822 792
823 793
824 794 grant_users_group_permission
825 795 ----------------------------
826 796
827 797 Grant permission for users group on given repository, or update
828 798 existing one if found. This command can be executed only using
829 799 api_key belonging to user with admin rights.
830 800
831 801
832 802 INPUT::
833 803
834 804 id : <id_for_response>
835 805 api_key : "<api_key>"
836 806 method : "grant_users_group_permission"
837 807 args: {
838 808 "repoid" : "<reponame or repo_id>"
839 809 "usersgroupid" : "<users group id or name>"
840 810 "perm" : "(repository.(none|read|write|admin))",
841 811 }
842 812
843 813 OUTPUT::
844 814
845 815 id : <id_given_in_input>
846 816 result: {
847 817 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
848 818 "success": true
849 819 }
850 820 error: null
851 821
852 822
853 823 revoke_users_group_permission
854 824 -----------------------------
855 825
856 826 Revoke permission for users group on given repository.This command can be
857 827 executed only using api_key belonging to user with admin rights.
858 828
859 829 INPUT::
860 830
861 831 id : <id_for_response>
862 832 api_key : "<api_key>"
863 833 method : "revoke_users_group_permission"
864 834 args: {
865 835 "repoid" : "<reponame or repo_id>"
866 836 "usersgroupid" : "<users group id or name>"
867 837 }
868 838
869 839 OUTPUT::
870 840
871 841 id : <id_given_in_input>
872 842 result: {
873 843 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
874 844 "success": true
875 845 }
876 846 error: null No newline at end of file
@@ -1,817 +1,820 b''
1 1 .. _changelog:
2 2
3 3 =========
4 4 Changelog
5 5 =========
6 6
7 7 1.4.4 (**2012-10-08**)
8 8 ----------------------
9 9
10 10 news
11 11 ++++
12 12
13 13 - obfuscate db password in logs for engine connection string
14 14 - #574 Show pull request status also in shortlog (if any)
15 15 - remember selected tab in my account page
16 16 - Bumped mercurial version to 2.3.2
17 - #595 rcextension hook for repository delete
17 18
18 19 fixes
19 20 +++++
20 21
21 22 - Add git version detection to warn users that Git used in system is to
22 23 old. Ref #588 - also show git version in system details in settings page
23 24 - fixed files quick filter links
24 25 - #590 Add GET flag that controls the way the diff are generated, for pull
25 26 requests we want to use non-bundle based diffs, That are far better for
26 27 doing code reviews. The /compare url still uses bundle compare for full
27 28 comparison including the incoming changesets
28 29 - Fixed #585, checks for status of revision where to strict, and made
29 30 opening pull request with those revision impossible due to previously set
30 31 status. Checks now are made also for the repository.
31 32 - fixes #591 git backend was causing encoding errors when handling binary
32 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 37 1.4.3 (**2012-09-28**)
35 38 ----------------------
36 39
37 40 news
38 41 ++++
39 42
40 43 - #558 Added config file to hooks extra data
41 44 - bumped mercurial version to 2.3.1
42 45 - #518 added possibility of specifying multiple patterns for issues
43 46 - update codemirror to latest version
44 47
45 48 fixes
46 49 +++++
47 50
48 51 - fixed #570 explicit users group permissions can overwrite owner permissions
49 52 - fixed #578 set proper PATH with current Python for Git
50 53 hooks to execute within same Python as RhodeCode
51 54 - fixed issue with Git bare repos that ends with .git in name
52 55
53 56 1.4.2 (**2012-09-12**)
54 57 ----------------------
55 58
56 59 news
57 60 ++++
58 61
59 62 - added option to menu to quick lock/unlock repository for users that have
60 63 write access to
61 64 - Implemented permissions for writing to repo
62 65 groups. Now only write access to group allows to create a repostiory
63 66 within that group
64 67 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
65 68 - updated translation for zh_CN
66 69
67 70 fixes
68 71 +++++
69 72
70 73 - fixed visual permissions check on repos groups inside groups
71 74 - fixed issues with non-ascii search terms in search, and indexers
72 75 - fixed parsing of page number in GET parameters
73 76 - fixed issues with generating pull-request overview for repos with
74 77 bookmarks and tags, also preview doesn't loose chosen revision from
75 78 select dropdown
76 79
77 80 1.4.1 (**2012-09-07**)
78 81 ----------------------
79 82
80 83 news
81 84 ++++
82 85
83 86 - always put a comment about code-review status change even if user send
84 87 empty data
85 88 - modified_on column saves repository update and it's going to be used
86 89 later for light version of main page ref #500
87 90 - pull request notifications send much nicer emails with details about pull
88 91 request
89 92 - #551 show breadcrumbs in summary view for repositories inside a group
90 93
91 94 fixes
92 95 +++++
93 96
94 97 - fixed migrations of permissions that can lead to inconsistency.
95 98 Some users sent feedback that after upgrading from older versions issues
96 99 with updating default permissions occurred. RhodeCode detects that now and
97 100 resets default user permission to initial state if there is a need for that.
98 101 Also forces users to set the default value for new forking permission.
99 102 - #535 improved apache wsgi example configuration in docs
100 103 - fixes #550 mercurial repositories comparision failed when origin repo had
101 104 additional not-common changesets
102 105 - fixed status of code-review in preview windows of pull request
103 106 - git forks were not initialized at bare repos
104 107 - fixes #555 fixes issues with comparing non-related repositories
105 108 - fixes #557 follower counter always counts up
106 109 - fixed issue #560 require push ssl checkbox wasn't shown when option was
107 110 enabled
108 111 - fixed #559
109 112 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
110 113 if it was a request to url by repository ID
111 114
112 115 1.4.0 (**2012-09-03**)
113 116 ----------------------
114 117
115 118 news
116 119 ++++
117 120
118 121 - new codereview system
119 122 - email map, allowing users to have multiple email addresses mapped into
120 123 their accounts
121 124 - improved git-hook system. Now all actions for git are logged into journal
122 125 including pushed revisions, user and IP address
123 126 - changed setup-app into setup-rhodecode and added default options to it.
124 127 - new git repos are created as bare now by default
125 128 - #464 added links to groups in permission box
126 129 - #465 mentions autocomplete inside comments boxes
127 130 - #469 added --update-only option to whoosh to re-index only given list
128 131 of repos in index
129 132 - rhodecode-api CLI client
130 133 - new git http protocol replaced buggy dulwich implementation.
131 134 Now based on pygrack & gitweb
132 135 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
133 136 reformated based on user suggestions. Additional rss/atom feeds for user
134 137 journal
135 138 - various i18n improvements
136 139 - #478 permissions overview for admin in user edit view
137 140 - File view now displays small gravatars off all authors of given file
138 141 - Implemented landing revisions. Each repository will get landing_rev attribute
139 142 that defines 'default' revision/branch for generating readme files
140 143 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
141 144 earliest possible call.
142 145 - Import remote svn repositories to mercurial using hgsubversion.
143 146 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
144 147 - RhodeCode can use alternative server for generating avatar icons
145 148 - implemented repositories locking. Pull locks, push unlocks. Also can be done
146 149 via API calls
147 150 - #538 form for permissions can handle multiple users at once
148 151
149 152 fixes
150 153 +++++
151 154
152 155 - improved translations
153 156 - fixes issue #455 Creating an archive generates an exception on Windows
154 157 - fixes #448 Download ZIP archive keeps file in /tmp open and results
155 158 in out of disk space
156 159 - fixes issue #454 Search results under Windows include proceeding
157 160 backslash
158 161 - fixed issue #450. Rhodecode no longer will crash when bad revision is
159 162 present in journal data.
160 163 - fix for issue #417, git execution was broken on windows for certain
161 164 commands.
162 165 - fixed #413. Don't disable .git directory for bare repos on deleting
163 166 - fixed issue #459. Changed the way of obtaining logger in reindex task.
164 167 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
165 168 reindexing modified files
166 169 - fixed #481 rhodecode emails are sent without Date header
167 170 - fixed #458 wrong count when no repos are present
168 171 - fixed issue #492 missing `\ No newline at end of file` test at the end of
169 172 new chunk in html diff
170 173 - full text search now works also for commit messages
171 174
172 175 1.3.6 (**2012-05-17**)
173 176 ----------------------
174 177
175 178 news
176 179 ++++
177 180
178 181 - chinese traditional translation
179 182 - changed setup-app into setup-rhodecode and added arguments for auto-setup
180 183 mode that doesn't need user interaction
181 184
182 185 fixes
183 186 +++++
184 187
185 188 - fixed no scm found warning
186 189 - fixed __future__ import error on rcextensions
187 190 - made simplejson required lib for speedup on JSON encoding
188 191 - fixes #449 bad regex could get more than revisions from parsing history
189 192 - don't clear DB session when CELERY_EAGER is turned ON
190 193
191 194 1.3.5 (**2012-05-10**)
192 195 ----------------------
193 196
194 197 news
195 198 ++++
196 199
197 200 - use ext_json for json module
198 201 - unified annotation view with file source view
199 202 - notification improvements, better inbox + css
200 203 - #419 don't strip passwords for login forms, make rhodecode
201 204 more compatible with LDAP servers
202 205 - Added HTTP_X_FORWARDED_FOR as another method of extracting
203 206 IP for pull/push logs. - moved all to base controller
204 207 - #415: Adding comment to changeset causes reload.
205 208 Comments are now added via ajax and doesn't reload the page
206 209 - #374 LDAP config is discarded when LDAP can't be activated
207 210 - limited push/pull operations are now logged for git in the journal
208 211 - bumped mercurial to 2.2.X series
209 212 - added support for displaying submodules in file-browser
210 213 - #421 added bookmarks in changelog view
211 214
212 215 fixes
213 216 +++++
214 217
215 218 - fixed dev-version marker for stable when served from source codes
216 219 - fixed missing permission checks on show forks page
217 220 - #418 cast to unicode fixes in notification objects
218 221 - #426 fixed mention extracting regex
219 222 - fixed remote-pulling for git remotes remopositories
220 223 - fixed #434: Error when accessing files or changesets of a git repository
221 224 with submodules
222 225 - fixed issue with empty APIKEYS for users after registration ref. #438
223 226 - fixed issue with getting README files from git repositories
224 227
225 228 1.3.4 (**2012-03-28**)
226 229 ----------------------
227 230
228 231 news
229 232 ++++
230 233
231 234 - Whoosh logging is now controlled by the .ini files logging setup
232 235 - added clone-url into edit form on /settings page
233 236 - added help text into repo add/edit forms
234 237 - created rcextensions module with additional mappings (ref #322) and
235 238 post push/pull/create repo hooks callbacks
236 239 - implemented #377 Users view for his own permissions on account page
237 240 - #399 added inheritance of permissions for users group on repos groups
238 241 - #401 repository group is automatically pre-selected when adding repos
239 242 inside a repository group
240 243 - added alternative HTTP 403 response when client failed to authenticate. Helps
241 244 solving issues with Mercurial and LDAP
242 245 - #402 removed group prefix from repository name when listing repositories
243 246 inside a group
244 247 - added gravatars into permission view and permissions autocomplete
245 248 - #347 when running multiple RhodeCode instances, properly invalidates cache
246 249 for all registered servers
247 250
248 251 fixes
249 252 +++++
250 253
251 254 - fixed #390 cache invalidation problems on repos inside group
252 255 - fixed #385 clone by ID url was loosing proxy prefix in URL
253 256 - fixed some unicode problems with waitress
254 257 - fixed issue with escaping < and > in changeset commits
255 258 - fixed error occurring during recursive group creation in API
256 259 create_repo function
257 260 - fixed #393 py2.5 fixes for routes url generator
258 261 - fixed #397 Private repository groups shows up before login
259 262 - fixed #396 fixed problems with revoking users in nested groups
260 263 - fixed mysql unicode issues + specified InnoDB as default engine with
261 264 utf8 charset
262 265 - #406 trim long branch/tag names in changelog to not break UI
263 266
264 267 1.3.3 (**2012-03-02**)
265 268 ----------------------
266 269
267 270 news
268 271 ++++
269 272
270 273
271 274 fixes
272 275 +++++
273 276
274 277 - fixed some python2.5 compatibility issues
275 278 - fixed issues with removed repos was accidentally added as groups, after
276 279 full rescan of paths
277 280 - fixes #376 Cannot edit user (using container auth)
278 281 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
279 282 configuration
280 283 - fixed initial sorting of repos inside repo group
281 284 - fixes issue when user tried to resubmit same permission into user/user_groups
282 285 - bumped beaker version that fixes #375 leap error bug
283 286 - fixed raw_changeset for git. It was generated with hg patch headers
284 287 - fixed vcs issue with last_changeset for filenodes
285 288 - fixed missing commit after hook delete
286 289 - fixed #372 issues with git operation detection that caused a security issue
287 290 for git repos
288 291
289 292 1.3.2 (**2012-02-28**)
290 293 ----------------------
291 294
292 295 news
293 296 ++++
294 297
295 298
296 299 fixes
297 300 +++++
298 301
299 302 - fixed git protocol issues with repos-groups
300 303 - fixed git remote repos validator that prevented from cloning remote git repos
301 304 - fixes #370 ending slashes fixes for repo and groups
302 305 - fixes #368 improved git-protocol detection to handle other clients
303 306 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
304 307 Moved To Root
305 308 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
306 309 - fixed #373 missing cascade drop on user_group_to_perm table
307 310
308 311 1.3.1 (**2012-02-27**)
309 312 ----------------------
310 313
311 314 news
312 315 ++++
313 316
314 317
315 318 fixes
316 319 +++++
317 320
318 321 - redirection loop occurs when remember-me wasn't checked during login
319 322 - fixes issues with git blob history generation
320 323 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
321 324
322 325 1.3.0 (**2012-02-26**)
323 326 ----------------------
324 327
325 328 news
326 329 ++++
327 330
328 331 - code review, inspired by github code-comments
329 332 - #215 rst and markdown README files support
330 333 - #252 Container-based and proxy pass-through authentication support
331 334 - #44 branch browser. Filtering of changelog by branches
332 335 - mercurial bookmarks support
333 336 - new hover top menu, optimized to add maximum size for important views
334 337 - configurable clone url template with possibility to specify protocol like
335 338 ssh:// or http:// and also manually alter other parts of clone_url.
336 339 - enabled largefiles extension by default
337 340 - optimized summary file pages and saved a lot of unused space in them
338 341 - #239 option to manually mark repository as fork
339 342 - #320 mapping of commit authors to RhodeCode users
340 343 - #304 hashes are displayed using monospace font
341 344 - diff configuration, toggle white lines and context lines
342 345 - #307 configurable diffs, whitespace toggle, increasing context lines
343 346 - sorting on branches, tags and bookmarks using YUI datatable
344 347 - improved file filter on files page
345 348 - implements #330 api method for listing nodes ar particular revision
346 349 - #73 added linking issues in commit messages to chosen issue tracker url
347 350 based on user defined regular expression
348 351 - added linking of changesets in commit messages
349 352 - new compact changelog with expandable commit messages
350 353 - firstname and lastname are optional in user creation
351 354 - #348 added post-create repository hook
352 355 - #212 global encoding settings is now configurable from .ini files
353 356 - #227 added repository groups permissions
354 357 - markdown gets codehilite extensions
355 358 - new API methods, delete_repositories, grante/revoke permissions for groups
356 359 and repos
357 360
358 361
359 362 fixes
360 363 +++++
361 364
362 365 - rewrote dbsession management for atomic operations, and better error handling
363 366 - fixed sorting of repo tables
364 367 - #326 escape of special html entities in diffs
365 368 - normalized user_name => username in api attributes
366 369 - fixes #298 ldap created users with mixed case emails created conflicts
367 370 on saving a form
368 371 - fixes issue when owner of a repo couldn't revoke permissions for users
369 372 and groups
370 373 - fixes #271 rare JSON serialization problem with statistics
371 374 - fixes #337 missing validation check for conflicting names of a group with a
372 375 repositories group
373 376 - #340 fixed session problem for mysql and celery tasks
374 377 - fixed #331 RhodeCode mangles repository names if the a repository group
375 378 contains the "full path" to the repositories
376 379 - #355 RhodeCode doesn't store encrypted LDAP passwords
377 380
378 381 1.2.5 (**2012-01-28**)
379 382 ----------------------
380 383
381 384 news
382 385 ++++
383 386
384 387 fixes
385 388 +++++
386 389
387 390 - #340 Celery complains about MySQL server gone away, added session cleanup
388 391 for celery tasks
389 392 - #341 "scanning for repositories in None" log message during Rescan was missing
390 393 a parameter
391 394 - fixed creating archives with subrepos. Some hooks were triggered during that
392 395 operation leading to crash.
393 396 - fixed missing email in account page.
394 397 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
395 398 forking on windows impossible
396 399
397 400 1.2.4 (**2012-01-19**)
398 401 ----------------------
399 402
400 403 news
401 404 ++++
402 405
403 406 - RhodeCode is bundled with mercurial series 2.0.X by default, with
404 407 full support to largefiles extension. Enabled by default in new installations
405 408 - #329 Ability to Add/Remove Groups to/from a Repository via AP
406 409 - added requires.txt file with requirements
407 410
408 411 fixes
409 412 +++++
410 413
411 414 - fixes db session issues with celery when emailing admins
412 415 - #331 RhodeCode mangles repository names if the a repository group
413 416 contains the "full path" to the repositories
414 417 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
415 418 - DB session cleanup after hg protocol operations, fixes issues with
416 419 `mysql has gone away` errors
417 420 - #333 doc fixes for get_repo api function
418 421 - #271 rare JSON serialization problem with statistics enabled
419 422 - #337 Fixes issues with validation of repository name conflicting with
420 423 a group name. A proper message is now displayed.
421 424 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
422 425 doesn't work
423 426 - #316 fixes issues with web description in hgrc files
424 427
425 428 1.2.3 (**2011-11-02**)
426 429 ----------------------
427 430
428 431 news
429 432 ++++
430 433
431 434 - added option to manage repos group for non admin users
432 435 - added following API methods for get_users, create_user, get_users_groups,
433 436 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
434 437 get_repo, create_repo, add_user_to_repo
435 438 - implements #237 added password confirmation for my account
436 439 and admin edit user.
437 440 - implements #291 email notification for global events are now sent to all
438 441 administrator users, and global config email.
439 442
440 443 fixes
441 444 +++++
442 445
443 446 - added option for passing auth method for smtp mailer
444 447 - #276 issue with adding a single user with id>10 to usergroups
445 448 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
446 449 - #288 fixes managing of repos in a group for non admin user
447 450
448 451 1.2.2 (**2011-10-17**)
449 452 ----------------------
450 453
451 454 news
452 455 ++++
453 456
454 457 - #226 repo groups are available by path instead of numerical id
455 458
456 459 fixes
457 460 +++++
458 461
459 462 - #259 Groups with the same name but with different parent group
460 463 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
461 464 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
462 465 - #265 ldap save fails sometimes on converting attributes to booleans,
463 466 added getter and setter into model that will prevent from this on db model level
464 467 - fixed problems with timestamps issues #251 and #213
465 468 - fixes #266 RhodeCode allows to create repo with the same name and in
466 469 the same parent as group
467 470 - fixes #245 Rescan of the repositories on Windows
468 471 - fixes #248 cannot edit repos inside a group on windows
469 472 - fixes #219 forking problems on windows
470 473
471 474 1.2.1 (**2011-10-08**)
472 475 ----------------------
473 476
474 477 news
475 478 ++++
476 479
477 480
478 481 fixes
479 482 +++++
480 483
481 484 - fixed problems with basic auth and push problems
482 485 - gui fixes
483 486 - fixed logger
484 487
485 488 1.2.0 (**2011-10-07**)
486 489 ----------------------
487 490
488 491 news
489 492 ++++
490 493
491 494 - implemented #47 repository groups
492 495 - implemented #89 Can setup google analytics code from settings menu
493 496 - implemented #91 added nicer looking archive urls with more download options
494 497 like tags, branches
495 498 - implemented #44 into file browsing, and added follow branch option
496 499 - implemented #84 downloads can be enabled/disabled for each repository
497 500 - anonymous repository can be cloned without having to pass default:default
498 501 into clone url
499 502 - fixed #90 whoosh indexer can index chooses repositories passed in command
500 503 line
501 504 - extended journal with day aggregates and paging
502 505 - implemented #107 source code lines highlight ranges
503 506 - implemented #93 customizable changelog on combined revision ranges -
504 507 equivalent of githubs compare view
505 508 - implemented #108 extended and more powerful LDAP configuration
506 509 - implemented #56 users groups
507 510 - major code rewrites optimized codes for speed and memory usage
508 511 - raw and diff downloads are now in git format
509 512 - setup command checks for write access to given path
510 513 - fixed many issues with international characters and unicode. It uses utf8
511 514 decode with replace to provide less errors even with non utf8 encoded strings
512 515 - #125 added API KEY access to feeds
513 516 - #109 Repository can be created from external Mercurial link (aka. remote
514 517 repository, and manually updated (via pull) from admin panel
515 518 - beta git support - push/pull server + basic view for git repos
516 519 - added followers page and forks page
517 520 - server side file creation (with binary file upload interface)
518 521 and edition with commits powered by codemirror
519 522 - #111 file browser file finder, quick lookup files on whole file tree
520 523 - added quick login sliding menu into main page
521 524 - changelog uses lazy loading of affected files details, in some scenarios
522 525 this can improve speed of changelog page dramatically especially for
523 526 larger repositories.
524 527 - implements #214 added support for downloading subrepos in download menu.
525 528 - Added basic API for direct operations on rhodecode via JSON
526 529 - Implemented advanced hook management
527 530
528 531 fixes
529 532 +++++
530 533
531 534 - fixed file browser bug, when switching into given form revision the url was
532 535 not changing
533 536 - fixed propagation to error controller on simplehg and simplegit middlewares
534 537 - fixed error when trying to make a download on empty repository
535 538 - fixed problem with '[' chars in commit messages in journal
536 539 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
537 540 - journal fork fixes
538 541 - removed issue with space inside renamed repository after deletion
539 542 - fixed strange issue on formencode imports
540 543 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
541 544 - #150 fixes for errors on repositories mapped in db but corrupted in
542 545 filesystem
543 546 - fixed problem with ascendant characters in realm #181
544 547 - fixed problem with sqlite file based database connection pool
545 548 - whoosh indexer and code stats share the same dynamic extensions map
546 549 - fixes #188 - relationship delete of repo_to_perm entry on user removal
547 550 - fixes issue #189 Trending source files shows "show more" when no more exist
548 551 - fixes issue #197 Relative paths for pidlocks
549 552 - fixes issue #198 password will require only 3 chars now for login form
550 553 - fixes issue #199 wrong redirection for non admin users after creating a repository
551 554 - fixes issues #202, bad db constraint made impossible to attach same group
552 555 more than one time. Affects only mysql/postgres
553 556 - fixes #218 os.kill patch for windows was missing sig param
554 557 - improved rendering of dag (they are not trimmed anymore when number of
555 558 heads exceeds 5)
556 559
557 560 1.1.8 (**2011-04-12**)
558 561 ----------------------
559 562
560 563 news
561 564 ++++
562 565
563 566 - improved windows support
564 567
565 568 fixes
566 569 +++++
567 570
568 571 - fixed #140 freeze of python dateutil library, since new version is python2.x
569 572 incompatible
570 573 - setup-app will check for write permission in given path
571 574 - cleaned up license info issue #149
572 575 - fixes for issues #137,#116 and problems with unicode and accented characters.
573 576 - fixes crashes on gravatar, when passed in email as unicode
574 577 - fixed tooltip flickering problems
575 578 - fixed came_from redirection on windows
576 579 - fixed logging modules, and sql formatters
577 580 - windows fixes for os.kill issue #133
578 581 - fixes path splitting for windows issues #148
579 582 - fixed issue #143 wrong import on migration to 1.1.X
580 583 - fixed problems with displaying binary files, thanks to Thomas Waldmann
581 584 - removed name from archive files since it's breaking ui for long repo names
582 585 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
583 586 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
584 587 Thomas Waldmann
585 588 - fixed issue #166 summary pager was skipping 10 revisions on second page
586 589
587 590
588 591 1.1.7 (**2011-03-23**)
589 592 ----------------------
590 593
591 594 news
592 595 ++++
593 596
594 597 fixes
595 598 +++++
596 599
597 600 - fixed (again) #136 installation support for FreeBSD
598 601
599 602
600 603 1.1.6 (**2011-03-21**)
601 604 ----------------------
602 605
603 606 news
604 607 ++++
605 608
606 609 fixes
607 610 +++++
608 611
609 612 - fixed #136 installation support for FreeBSD
610 613 - RhodeCode will check for python version during installation
611 614
612 615 1.1.5 (**2011-03-17**)
613 616 ----------------------
614 617
615 618 news
616 619 ++++
617 620
618 621 - basic windows support, by exchanging pybcrypt into sha256 for windows only
619 622 highly inspired by idea of mantis406
620 623
621 624 fixes
622 625 +++++
623 626
624 627 - fixed sorting by author in main page
625 628 - fixed crashes with diffs on binary files
626 629 - fixed #131 problem with boolean values for LDAP
627 630 - fixed #122 mysql problems thanks to striker69
628 631 - fixed problem with errors on calling raw/raw_files/annotate functions
629 632 with unknown revisions
630 633 - fixed returned rawfiles attachment names with international character
631 634 - cleaned out docs, big thanks to Jason Harris
632 635
633 636 1.1.4 (**2011-02-19**)
634 637 ----------------------
635 638
636 639 news
637 640 ++++
638 641
639 642 fixes
640 643 +++++
641 644
642 645 - fixed formencode import problem on settings page, that caused server crash
643 646 when that page was accessed as first after server start
644 647 - journal fixes
645 648 - fixed option to access repository just by entering http://server/<repo_name>
646 649
647 650 1.1.3 (**2011-02-16**)
648 651 ----------------------
649 652
650 653 news
651 654 ++++
652 655
653 656 - implemented #102 allowing the '.' character in username
654 657 - added option to access repository just by entering http://server/<repo_name>
655 658 - celery task ignores result for better performance
656 659
657 660 fixes
658 661 +++++
659 662
660 663 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
661 664 apollo13 and Johan Walles
662 665 - small fixes in journal
663 666 - fixed problems with getting setting for celery from .ini files
664 667 - registration, password reset and login boxes share the same title as main
665 668 application now
666 669 - fixed #113: to high permissions to fork repository
667 670 - fixed problem with '[' chars in commit messages in journal
668 671 - removed issue with space inside renamed repository after deletion
669 672 - db transaction fixes when filesystem repository creation failed
670 673 - fixed #106 relation issues on databases different than sqlite
671 674 - fixed static files paths links to use of url() method
672 675
673 676 1.1.2 (**2011-01-12**)
674 677 ----------------------
675 678
676 679 news
677 680 ++++
678 681
679 682
680 683 fixes
681 684 +++++
682 685
683 686 - fixes #98 protection against float division of percentage stats
684 687 - fixed graph bug
685 688 - forced webhelpers version since it was making troubles during installation
686 689
687 690 1.1.1 (**2011-01-06**)
688 691 ----------------------
689 692
690 693 news
691 694 ++++
692 695
693 696 - added force https option into ini files for easier https usage (no need to
694 697 set server headers with this options)
695 698 - small css updates
696 699
697 700 fixes
698 701 +++++
699 702
700 703 - fixed #96 redirect loop on files view on repositories without changesets
701 704 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
702 705 and server crashed with errors
703 706 - fixed large tooltips problems on main page
704 707 - fixed #92 whoosh indexer is more error proof
705 708
706 709 1.1.0 (**2010-12-18**)
707 710 ----------------------
708 711
709 712 news
710 713 ++++
711 714
712 715 - rewrite of internals for vcs >=0.1.10
713 716 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
714 717 with older clients
715 718 - anonymous access, authentication via ldap
716 719 - performance upgrade for cached repos list - each repository has its own
717 720 cache that's invalidated when needed.
718 721 - performance upgrades on repositories with large amount of commits (20K+)
719 722 - main page quick filter for filtering repositories
720 723 - user dashboards with ability to follow chosen repositories actions
721 724 - sends email to admin on new user registration
722 725 - added cache/statistics reset options into repository settings
723 726 - more detailed action logger (based on hooks) with pushed changesets lists
724 727 and options to disable those hooks from admin panel
725 728 - introduced new enhanced changelog for merges that shows more accurate results
726 729 - new improved and faster code stats (based on pygments lexers mapping tables,
727 730 showing up to 10 trending sources for each repository. Additionally stats
728 731 can be disabled in repository settings.
729 732 - gui optimizations, fixed application width to 1024px
730 733 - added cut off (for large files/changesets) limit into config files
731 734 - whoosh, celeryd, upgrade moved to paster command
732 735 - other than sqlite database backends can be used
733 736
734 737 fixes
735 738 +++++
736 739
737 740 - fixes #61 forked repo was showing only after cache expired
738 741 - fixes #76 no confirmation on user deletes
739 742 - fixes #66 Name field misspelled
740 743 - fixes #72 block user removal when he owns repositories
741 744 - fixes #69 added password confirmation fields
742 745 - fixes #87 RhodeCode crashes occasionally on updating repository owner
743 746 - fixes #82 broken annotations on files with more than 1 blank line at the end
744 747 - a lot of fixes and tweaks for file browser
745 748 - fixed detached session issues
746 749 - fixed when user had no repos he would see all repos listed in my account
747 750 - fixed ui() instance bug when global hgrc settings was loaded for server
748 751 instance and all hgrc options were merged with our db ui() object
749 752 - numerous small bugfixes
750 753
751 754 (special thanks for TkSoh for detailed feedback)
752 755
753 756
754 757 1.0.2 (**2010-11-12**)
755 758 ----------------------
756 759
757 760 news
758 761 ++++
759 762
760 763 - tested under python2.7
761 764 - bumped sqlalchemy and celery versions
762 765
763 766 fixes
764 767 +++++
765 768
766 769 - fixed #59 missing graph.js
767 770 - fixed repo_size crash when repository had broken symlinks
768 771 - fixed python2.5 crashes.
769 772
770 773
771 774 1.0.1 (**2010-11-10**)
772 775 ----------------------
773 776
774 777 news
775 778 ++++
776 779
777 780 - small css updated
778 781
779 782 fixes
780 783 +++++
781 784
782 785 - fixed #53 python2.5 incompatible enumerate calls
783 786 - fixed #52 disable mercurial extension for web
784 787 - fixed #51 deleting repositories don't delete it's dependent objects
785 788
786 789
787 790 1.0.0 (**2010-11-02**)
788 791 ----------------------
789 792
790 793 - security bugfix simplehg wasn't checking for permissions on commands
791 794 other than pull or push.
792 795 - fixed doubled messages after push or pull in admin journal
793 796 - templating and css corrections, fixed repo switcher on chrome, updated titles
794 797 - admin menu accessible from options menu on repository view
795 798 - permissions cached queries
796 799
797 800 1.0.0rc4 (**2010-10-12**)
798 801 --------------------------
799 802
800 803 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
801 804 - removed cache_manager settings from sqlalchemy meta
802 805 - added sqlalchemy cache settings to ini files
803 806 - validated password length and added second try of failure on paster setup-app
804 807 - fixed setup database destroy prompt even when there was no db
805 808
806 809
807 810 1.0.0rc3 (**2010-10-11**)
808 811 -------------------------
809 812
810 813 - fixed i18n during installation.
811 814
812 815 1.0.0rc2 (**2010-10-11**)
813 816 -------------------------
814 817
815 818 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
816 819 occure. After vcs is fixed it'll be put back again.
817 820 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,724 +1,724 b''
1 1 .. _setup:
2 2
3 3 =====
4 4 Setup
5 5 =====
6 6
7 7
8 8 Setting up RhodeCode
9 9 --------------------
10 10
11 11 First, you will need to create a RhodeCode configuration file. Run the
12 12 following command to do this::
13 13
14 14 paster make-config RhodeCode production.ini
15 15
16 16 - This will create the file `production.ini` in the current directory. This
17 17 configuration file contains the various settings for RhodeCode, e.g proxy
18 18 port, email settings, usage of static files, cache, celery settings and
19 19 logging.
20 20
21 21
22 22 Next, you need to create the databases used by RhodeCode. I recommend that you
23 23 use postgresql or sqlite (default). If you choose a database other than the
24 24 default ensure you properly adjust the db url in your production.ini
25 25 configuration file to use this other database. RhodeCode currently supports
26 26 postgresql, sqlite and mysql databases. Create the database by running
27 27 the following command::
28 28
29 29 paster setup-rhodecode production.ini
30 30
31 31 This will prompt you for a "root" path. This "root" path is the location where
32 32 RhodeCode will store all of its repositories on the current machine. After
33 33 entering this "root" path ``setup-rhodecode`` will also prompt you for a username
34 34 and password for the initial admin account which ``setup-rhodecode`` sets
35 35 up for you.
36 36
37 37 setup process can be fully automated, example for lazy::
38 38
39 39 paster setup-rhodecode production.ini --user=marcink --password=secret --email=marcin@rhodecode.org --repos=/home/marcink/my_repos
40 40
41 41
42 42 - The ``setup-rhodecode`` command will create all of the needed tables and an
43 43 admin account. When choosing a root path you can either use a new empty
44 44 location, or a location which already contains existing repositories. If you
45 45 choose a location which contains existing repositories RhodeCode will simply
46 46 add all of the repositories at the chosen location to it's database.
47 47 (Note: make sure you specify the correct path to the root).
48 48 - Note: the given path for mercurial_ repositories **must** be write accessible
49 49 for the application. It's very important since the RhodeCode web interface
50 50 will work without write access, but when trying to do a push it will
51 51 eventually fail with permission denied errors unless it has write access.
52 52
53 53 You are now ready to use RhodeCode, to run it simply execute::
54 54
55 55 paster serve production.ini
56 56
57 57 - This command runs the RhodeCode server. The web app should be available at the
58 58 127.0.0.1:5000. This ip and port is configurable via the production.ini
59 59 file created in previous step
60 60 - Use the admin account you created above when running ``setup-rhodecode``
61 61 to login to the web app.
62 62 - The default permissions on each repository is read, and the owner is admin.
63 63 Remember to update these if needed.
64 64 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
65 65 well as edit more advanced options on users and repositories
66 66
67 67 Optionally users can create `rcextensions` package that extends RhodeCode
68 68 functionality. To do this simply execute::
69 69
70 70 paster make-rcext production.ini
71 71
72 72 This will create `rcextensions` package in the same place that your `ini` file
73 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
75 for sending signals to build-bots such as jenkins.
74 stats and add additional code into the push/pull/create/delete repo hooks.
75 For example for sending signals to build-bots such as jenkins.
76 76 Please see the `__init__.py` file inside `rcextensions` package
77 77 for more details.
78 78
79 79
80 80 Using RhodeCode with SSH
81 81 ------------------------
82 82
83 83 RhodeCode currently only hosts repositories using http and https. (The addition
84 84 of ssh hosting is a planned future feature.) However you can easily use ssh in
85 85 parallel with RhodeCode. (Repository access via ssh is a standard "out of
86 86 the box" feature of mercurial_ and you can use this to access any of the
87 87 repositories that RhodeCode is hosting. See PublishingRepositories_)
88 88
89 89 RhodeCode repository structures are kept in directories with the same name
90 90 as the project. When using repository groups, each group is a subdirectory.
91 91 This allows you to easily use ssh for accessing repositories.
92 92
93 93 In order to use ssh you need to make sure that your web-server and the users
94 94 login accounts have the correct permissions set on the appropriate directories.
95 95 (Note that these permissions are independent of any permissions you have set up
96 96 using the RhodeCode web interface.)
97 97
98 98 If your main directory (the same as set in RhodeCode settings) is for example
99 99 set to **/home/hg** and the repository you are using is named `rhodecode`, then
100 100 to clone via ssh you should run::
101 101
102 102 hg clone ssh://user@server.com/home/hg/rhodecode
103 103
104 104 Using other external tools such as mercurial-server_ or using ssh key based
105 105 authentication is fully supported.
106 106
107 107 Note: In an advanced setup, in order for your ssh access to use the same
108 108 permissions as set up via the RhodeCode web interface, you can create an
109 109 authentication hook to connect to the rhodecode db and runs check functions for
110 110 permissions against that.
111 111
112 112 Setting up Whoosh full text search
113 113 ----------------------------------
114 114
115 115 Starting from version 1.1 the whoosh index can be build by using the paster
116 116 command ``make-index``. To use ``make-index`` you must specify the configuration
117 117 file that stores the location of the index. You may specify the location of the
118 118 repositories (`--repo-location`). If not specified, this value is retrieved
119 119 from the RhodeCode database. This was required prior to 1.2. Starting from
120 120 version 1.2 it is also possible to specify a comma separated list of
121 121 repositories (`--index-only`) to build index only on chooses repositories
122 122 skipping any other found in repos location
123 123
124 124 You may optionally pass the option `-f` to enable a full index rebuild. Without
125 125 the `-f` option, indexing will run always in "incremental" mode.
126 126
127 127 For an incremental index build use::
128 128
129 129 paster make-index production.ini
130 130
131 131 For a full index rebuild use::
132 132
133 133 paster make-index production.ini -f
134 134
135 135
136 136 building index just for chosen repositories is possible with such command::
137 137
138 138 paster make-index production.ini --index-only=vcs,rhodecode
139 139
140 140
141 141 In order to do periodical index builds and keep your index always up to date.
142 142 It's recommended to do a crontab entry for incremental indexing.
143 143 An example entry might look like this::
144 144
145 145 /path/to/python/bin/paster make-index /path/to/rhodecode/production.ini
146 146
147 147 When using incremental mode (the default) whoosh will check the last
148 148 modification date of each file and add it to be reindexed if a newer file is
149 149 available. The indexing daemon checks for any removed files and removes them
150 150 from index.
151 151
152 152 If you want to rebuild index from scratch, you can use the `-f` flag as above,
153 153 or in the admin panel you can check `build from scratch` flag.
154 154
155 155
156 156 Setting up LDAP support
157 157 -----------------------
158 158
159 159 RhodeCode starting from version 1.1 supports ldap authentication. In order
160 160 to use LDAP, you have to install the python-ldap_ package. This package is
161 161 available via pypi, so you can install it by running
162 162
163 163 using easy_install::
164 164
165 165 easy_install python-ldap
166 166
167 167 using pip::
168 168
169 169 pip install python-ldap
170 170
171 171 .. note::
172 172 python-ldap requires some certain libs on your system, so before installing
173 173 it check that you have at least `openldap`, and `sasl` libraries.
174 174
175 175 LDAP settings are located in admin->ldap section,
176 176
177 177 Here's a typical ldap setup::
178 178
179 179 Connection settings
180 180 Enable LDAP = checked
181 181 Host = host.example.org
182 182 Port = 389
183 183 Account = <account>
184 184 Password = <password>
185 185 Connection Security = LDAPS connection
186 186 Certificate Checks = DEMAND
187 187
188 188 Search settings
189 189 Base DN = CN=users,DC=host,DC=example,DC=org
190 190 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
191 191 LDAP Search Scope = SUBTREE
192 192
193 193 Attribute mappings
194 194 Login Attribute = uid
195 195 First Name Attribute = firstName
196 196 Last Name Attribute = lastName
197 197 E-mail Attribute = mail
198 198
199 199 .. _enable_ldap:
200 200
201 201 Enable LDAP : required
202 202 Whether to use LDAP for authenticating users.
203 203
204 204 .. _ldap_host:
205 205
206 206 Host : required
207 207 LDAP server hostname or IP address.
208 208
209 209 .. _Port:
210 210
211 211 Port : required
212 212 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
213 213
214 214 .. _ldap_account:
215 215
216 216 Account : optional
217 217 Only required if the LDAP server does not allow anonymous browsing of
218 218 records. This should be a special account for record browsing. This
219 219 will require `LDAP Password`_ below.
220 220
221 221 .. _LDAP Password:
222 222
223 223 Password : optional
224 224 Only required if the LDAP server does not allow anonymous browsing of
225 225 records.
226 226
227 227 .. _Enable LDAPS:
228 228
229 229 Connection Security : required
230 230 Defines the connection to LDAP server
231 231
232 232 No encryption
233 233 Plain non encrypted connection
234 234
235 235 LDAPS connection
236 236 Enable ldaps connection. It will likely require `Port`_ to be set to
237 237 a different value (standard LDAPS port is 636). When LDAPS is enabled
238 238 then `Certificate Checks`_ is required.
239 239
240 240 START_TLS on LDAP connection
241 241 START TLS connection
242 242
243 243 .. _Certificate Checks:
244 244
245 245 Certificate Checks : optional
246 246 How SSL certificates verification is handled - this is only useful when
247 247 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
248 248 while the other options are susceptible to man-in-the-middle attacks. SSL
249 249 certificates can be installed to /etc/openldap/cacerts so that the
250 250 DEMAND or HARD options can be used with self-signed certificates or
251 251 certificates that do not have traceable certificates of authority.
252 252
253 253 NEVER
254 254 A serve certificate will never be requested or checked.
255 255
256 256 ALLOW
257 257 A server certificate is requested. Failure to provide a
258 258 certificate or providing a bad certificate will not terminate the
259 259 session.
260 260
261 261 TRY
262 262 A server certificate is requested. Failure to provide a
263 263 certificate does not halt the session; providing a bad certificate
264 264 halts the session.
265 265
266 266 DEMAND
267 267 A server certificate is requested and must be provided and
268 268 authenticated for the session to proceed.
269 269
270 270 HARD
271 271 The same as DEMAND.
272 272
273 273 .. _Base DN:
274 274
275 275 Base DN : required
276 276 The Distinguished Name (DN) where searches for users will be performed.
277 277 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
278 278
279 279 .. _LDAP Filter:
280 280
281 281 LDAP Filter : optional
282 282 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
283 283 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
284 284 which LDAP objects are identified as representing Users for
285 285 authentication. The filter is augmented by `Login Attribute`_ below.
286 286 This can commonly be left blank.
287 287
288 288 .. _LDAP Search Scope:
289 289
290 290 LDAP Search Scope : required
291 291 This limits how far LDAP will search for a matching object.
292 292
293 293 BASE
294 294 Only allows searching of `Base DN`_ and is usually not what you
295 295 want.
296 296
297 297 ONELEVEL
298 298 Searches all entries under `Base DN`_, but not Base DN itself.
299 299
300 300 SUBTREE
301 301 Searches all entries below `Base DN`_, but not Base DN itself.
302 302 When using SUBTREE `LDAP Filter`_ is useful to limit object
303 303 location.
304 304
305 305 .. _Login Attribute:
306 306
307 307 Login Attribute : required
308 308 The LDAP record attribute that will be matched as the USERNAME or
309 309 ACCOUNT used to connect to RhodeCode. This will be added to `LDAP
310 310 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
311 311 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
312 312 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
313 313 ::
314 314
315 315 (&(LDAPFILTER)(uid=jsmith))
316 316
317 317 .. _ldap_attr_firstname:
318 318
319 319 First Name Attribute : required
320 320 The LDAP record attribute which represents the user's first name.
321 321
322 322 .. _ldap_attr_lastname:
323 323
324 324 Last Name Attribute : required
325 325 The LDAP record attribute which represents the user's last name.
326 326
327 327 .. _ldap_attr_email:
328 328
329 329 Email Attribute : required
330 330 The LDAP record attribute which represents the user's email address.
331 331
332 332 If all data are entered correctly, and python-ldap_ is properly installed
333 333 users should be granted access to RhodeCode with ldap accounts. At this
334 334 time user information is copied from LDAP into the RhodeCode user database.
335 335 This means that updates of an LDAP user object may not be reflected as a
336 336 user update in RhodeCode.
337 337
338 338 If You have problems with LDAP access and believe You entered correct
339 339 information check out the RhodeCode logs, any error messages sent from LDAP
340 340 will be saved there.
341 341
342 342 Active Directory
343 343 ''''''''''''''''
344 344
345 345 RhodeCode can use Microsoft Active Directory for user authentication. This
346 346 is done through an LDAP or LDAPS connection to Active Directory. The
347 347 following LDAP configuration settings are typical for using Active
348 348 Directory ::
349 349
350 350 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
351 351 Login Attribute = sAMAccountName
352 352 First Name Attribute = givenName
353 353 Last Name Attribute = sn
354 354 E-mail Attribute = mail
355 355
356 356 All other LDAP settings will likely be site-specific and should be
357 357 appropriately configured.
358 358
359 359
360 360 Authentication by container or reverse-proxy
361 361 --------------------------------------------
362 362
363 363 Starting with version 1.3, RhodeCode supports delegating the authentication
364 364 of users to its WSGI container, or to a reverse-proxy server through which all
365 365 clients access the application.
366 366
367 367 When these authentication methods are enabled in RhodeCode, it uses the
368 368 username that the container/proxy (Apache/Nginx/etc) authenticated and doesn't
369 369 perform the authentication itself. The authorization, however, is still done by
370 370 RhodeCode according to its settings.
371 371
372 372 When a user logs in for the first time using these authentication methods,
373 373 a matching user account is created in RhodeCode with default permissions. An
374 374 administrator can then modify it using RhodeCode's admin interface.
375 375 It's also possible for an administrator to create accounts and configure their
376 376 permissions before the user logs in for the first time.
377 377
378 378 Container-based authentication
379 379 ''''''''''''''''''''''''''''''
380 380
381 381 In a container-based authentication setup, RhodeCode reads the user name from
382 382 the ``REMOTE_USER`` server variable provided by the WSGI container.
383 383
384 384 After setting up your container (see `Apache's WSGI config`_), you'd need
385 385 to configure it to require authentication on the location configured for
386 386 RhodeCode.
387 387
388 388 In order for RhodeCode to start using the provided username, you should set the
389 389 following in the [app:main] section of your .ini file::
390 390
391 391 container_auth_enabled = true
392 392
393 393
394 394 Proxy pass-through authentication
395 395 '''''''''''''''''''''''''''''''''
396 396
397 397 In a proxy pass-through authentication setup, RhodeCode reads the user name
398 398 from the ``X-Forwarded-User`` request header, which should be configured to be
399 399 sent by the reverse-proxy server.
400 400
401 401 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
402 402 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'd need to
403 403 configure the authentication and add the username in a request header named
404 404 ``X-Forwarded-User``.
405 405
406 406 For example, the following config section for Apache sets a subdirectory in a
407 407 reverse-proxy setup with basic auth::
408 408
409 409 <Location /<someprefix> >
410 410 ProxyPass http://127.0.0.1:5000/<someprefix>
411 411 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
412 412 SetEnvIf X-Url-Scheme https HTTPS=1
413 413
414 414 AuthType Basic
415 415 AuthName "RhodeCode authentication"
416 416 AuthUserFile /home/web/rhodecode/.htpasswd
417 417 require valid-user
418 418
419 419 RequestHeader unset X-Forwarded-User
420 420
421 421 RewriteEngine On
422 422 RewriteCond %{LA-U:REMOTE_USER} (.+)
423 423 RewriteRule .* - [E=RU:%1]
424 424 RequestHeader set X-Forwarded-User %{RU}e
425 425 </Location>
426 426
427 427 In order for RhodeCode to start using the forwarded username, you should set
428 428 the following in the [app:main] section of your .ini file::
429 429
430 430 proxypass_auth_enabled = true
431 431
432 432 .. note::
433 433 If you enable proxy pass-through authentication, make sure your server is
434 434 only accessible through the proxy. Otherwise, any client would be able to
435 435 forge the authentication header and could effectively become authenticated
436 436 using any account of their liking.
437 437
438 438 Integration with Issue trackers
439 439 -------------------------------
440 440
441 441 RhodeCode provides a simple integration with issue trackers. It's possible
442 442 to define a regular expression that will fetch issue id stored in commit
443 443 messages and replace that with an url to this issue. To enable this simply
444 444 uncomment following variables in the ini file::
445 445
446 446 url_pat = (?:^#|\s#)(\w+)
447 447 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
448 448 issue_prefix = #
449 449
450 450 `url_pat` is the regular expression that will fetch issues from commit messages.
451 451 Default regex will match issues in format of #<number> eg. #300.
452 452
453 453 Matched issues will be replace with the link specified as `issue_server_link`
454 454 {id} will be replaced with issue id, and {repo} with repository name.
455 455 Since the # is striped `issue_prefix` is added as a prefix to url.
456 456 `issue_prefix` can be something different than # if you pass
457 457 ISSUE- as issue prefix this will generate an url in format::
458 458
459 459 <a href="https://myissueserver.com/example_repo/issue/300">ISSUE-300</a>
460 460
461 461 Hook management
462 462 ---------------
463 463
464 464 Hooks can be managed in similar way to this used in .hgrc files.
465 465 To access hooks setting click `advanced setup` on Hooks section of Mercurial
466 466 Settings in Admin.
467 467
468 468 There are 4 built in hooks that cannot be changed (only enable/disable by
469 469 checkboxes on previos section).
470 470 To add another custom hook simply fill in first section with
471 471 <name>.<hook_type> and the second one with hook path. Example hooks
472 472 can be found at *rhodecode.lib.hooks*.
473 473
474 474
475 475 Changing default encoding
476 476 -------------------------
477 477
478 478 By default RhodeCode uses utf8 encoding, starting from 1.3 series this
479 479 can be changed, simply edit default_encoding in .ini file to desired one.
480 480 This affects many parts in rhodecode including commiters names, filenames,
481 481 encoding of commit messages. In addition RhodeCode can detect if `chardet`
482 482 library is installed. If `chardet` is detected RhodeCode will fallback to it
483 483 when there are encode/decode errors.
484 484
485 485
486 486 Setting Up Celery
487 487 -----------------
488 488
489 489 Since version 1.1 celery is configured by the rhodecode ini configuration files.
490 490 Simply set use_celery=true in the ini file then add / change the configuration
491 491 variables inside the ini file.
492 492
493 493 Remember that the ini files use the format with '.' not with '_' like celery.
494 494 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
495 495 the config file.
496 496
497 497 In order to start using celery run::
498 498
499 499 paster celeryd <configfile.ini>
500 500
501 501
502 502 .. note::
503 503 Make sure you run this command from the same virtualenv, and with the same
504 504 user that rhodecode runs.
505 505
506 506 HTTPS support
507 507 -------------
508 508
509 509 There are two ways to enable https:
510 510
511 511 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
512 512 recognize this headers and make proper https redirections
513 513 - Alternatively, change the `force_https = true` flag in the ini configuration
514 514 to force using https, no headers are needed than to enable https
515 515
516 516
517 517 Nginx virtual host example
518 518 --------------------------
519 519
520 520 Sample config for nginx using proxy::
521 521
522 522 upstream rc {
523 523 server 127.0.0.1:5000;
524 524 # add more instances for load balancing
525 525 #server 127.0.0.1:5001;
526 526 #server 127.0.0.1:5002;
527 527 }
528 528
529 529 server {
530 530 listen 80;
531 531 server_name hg.myserver.com;
532 532 access_log /var/log/nginx/rhodecode.access.log;
533 533 error_log /var/log/nginx/rhodecode.error.log;
534 534
535 535 # uncomment if you have nginx with chunking module compiled
536 536 # fixes the issues of having to put postBuffer data for large git
537 537 # pushes
538 538 #chunkin on;
539 539 #error_page 411 = @my_411_error;
540 540 #location @my_411_error {
541 541 # chunkin_resume;
542 542 #}
543 543
544 544 # uncomment if you want to serve static files by nginx
545 545 #root /path/to/installation/rhodecode/public;
546 546
547 547 location / {
548 548 try_files $uri @rhode;
549 549 }
550 550
551 551 location @rhode {
552 552 proxy_pass http://rc;
553 553 include /etc/nginx/proxy.conf;
554 554 }
555 555
556 556 }
557 557
558 558 Here's the proxy.conf. It's tuned so it will not timeout on long
559 559 pushes or large pushes::
560 560
561 561 proxy_redirect off;
562 562 proxy_set_header Host $host;
563 563 proxy_set_header X-Url-Scheme $scheme;
564 564 proxy_set_header X-Host $http_host;
565 565 proxy_set_header X-Real-IP $remote_addr;
566 566 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
567 567 proxy_set_header Proxy-host $proxy_host;
568 568 client_max_body_size 400m;
569 569 client_body_buffer_size 128k;
570 570 proxy_buffering off;
571 571 proxy_connect_timeout 7200;
572 572 proxy_send_timeout 7200;
573 573 proxy_read_timeout 7200;
574 574 proxy_buffers 8 32k;
575 575
576 576 Also, when using root path with nginx you might set the static files to false
577 577 in the production.ini file::
578 578
579 579 [app:main]
580 580 use = egg:rhodecode
581 581 full_stack = true
582 582 static_files = false
583 583 lang=en
584 584 cache_dir = %(here)s/data
585 585
586 586 In order to not have the statics served by the application. This improves speed.
587 587
588 588
589 589 Apache virtual host reverse proxy example
590 590 -----------------------------------------
591 591
592 592 Here is a sample configuration file for apache using proxy::
593 593
594 594 <VirtualHost *:80>
595 595 ServerName hg.myserver.com
596 596 ServerAlias hg.myserver.com
597 597
598 598 <Proxy *>
599 599 Order allow,deny
600 600 Allow from all
601 601 </Proxy>
602 602
603 603 #important !
604 604 #Directive to properly generate url (clone url) for pylons
605 605 ProxyPreserveHost On
606 606
607 607 #rhodecode instance
608 608 ProxyPass / http://127.0.0.1:5000/
609 609 ProxyPassReverse / http://127.0.0.1:5000/
610 610
611 611 #to enable https use line below
612 612 #SetEnvIf X-Url-Scheme https HTTPS=1
613 613
614 614 </VirtualHost>
615 615
616 616
617 617 Additional tutorial
618 618 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
619 619
620 620
621 621 Apache as subdirectory
622 622 ----------------------
623 623
624 624 Apache subdirectory part::
625 625
626 626 <Location /<someprefix> >
627 627 ProxyPass http://127.0.0.1:5000/<someprefix>
628 628 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
629 629 SetEnvIf X-Url-Scheme https HTTPS=1
630 630 </Location>
631 631
632 632 Besides the regular apache setup you will need to add the following line
633 633 into [app:main] section of your .ini file::
634 634
635 635 filter-with = proxy-prefix
636 636
637 637 Add the following at the end of the .ini file::
638 638
639 639 [filter:proxy-prefix]
640 640 use = egg:PasteDeploy#prefix
641 641 prefix = /<someprefix>
642 642
643 643
644 644 then change <someprefix> into your choosen prefix
645 645
646 646 Apache's WSGI config
647 647 --------------------
648 648
649 649 Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For
650 650 that, you'll need to:
651 651
652 652 - Install mod_wsgi. If using a Debian-based distro, you can install
653 653 the package libapache2-mod-wsgi::
654 654
655 655 aptitude install libapache2-mod-wsgi
656 656
657 657 - Enable mod_wsgi::
658 658
659 659 a2enmod wsgi
660 660
661 661 - Create a wsgi dispatch script, like the one below. Make sure you
662 662 check the paths correctly point to where you installed RhodeCode
663 663 and its Python Virtual Environment.
664 664 - Enable the WSGIScriptAlias directive for the wsgi dispatch script,
665 665 as in the following example. Once again, check the paths are
666 666 correctly specified.
667 667
668 668 Here is a sample excerpt from an Apache Virtual Host configuration file::
669 669
670 670 WSGIDaemonProcess pylons \
671 671 threads=4 \
672 672 python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
673 673 WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
674 674 WSGIPassAuthorization On
675 675
676 676 .. note::
677 677 when running apache as root please add: `user=www-data group=www-data`
678 678 into above configuration
679 679
680 680 .. note::
681 681 RhodeCode cannot be runned in multiprocess mode in apache, make sure
682 682 you don't specify `processes=num` directive in the config
683 683
684 684
685 685 Example wsgi dispatch script::
686 686
687 687 import os
688 688 os.environ["HGENCODING"] = "UTF-8"
689 689 os.environ['PYTHON_EGG_CACHE'] = '/home/web/rhodecode/.egg-cache'
690 690
691 691 # sometimes it's needed to set the curent dir
692 692 os.chdir('/home/web/rhodecode/')
693 693
694 694 import site
695 695 site.addsitedir("/home/web/rhodecode/pyenv/lib/python2.6/site-packages")
696 696
697 697 from paste.deploy import loadapp
698 698 from paste.script.util.logging_config import fileConfig
699 699
700 700 fileConfig('/home/web/rhodecode/production.ini')
701 701 application = loadapp('config:/home/web/rhodecode/production.ini')
702 702
703 703 Note: when using mod_wsgi you'll need to install the same version of
704 704 Mercurial that's inside RhodeCode's virtualenv also on the system's Python
705 705 environment.
706 706
707 707
708 708 Other configuration files
709 709 -------------------------
710 710
711 711 Some example init.d scripts can be found in init.d directory::
712 712
713 713 https://secure.rhodecode.org/rhodecode/files/beta/init.d
714 714
715 715 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
716 716 .. _python: http://www.python.org/
717 717 .. _mercurial: http://mercurial.selenic.com/
718 718 .. _celery: http://celeryproject.org/
719 719 .. _rabbitmq: http://www.rabbitmq.com/
720 720 .. _python-ldap: http://www.python-ldap.org/
721 721 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
722 722 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
723 723 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
724 724 .. _google group rhodecode: http://groups.google.com/group/rhodecode No newline at end of file
@@ -1,86 +1,112 b''
1 1 # Additional mappings that are not present in the pygments lexers
2 2 # used for building stats
3 3 # format is {'ext':['Names']} eg. {'py':['Python']} note: there can be
4 4 # more than one name for extension
5 5 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
6 6 # build by pygments
7 7 EXTRA_MAPPINGS = {}
8 8
9 9 #==============================================================================
10 10 # WHOOSH INDEX EXTENSIONS
11 11 #==============================================================================
12 12 # if INDEX_EXTENSIONS is [] it'll use pygments lexers extensions by default.
13 13 # To set your own just add to this list extensions to index with content
14 14 INDEX_EXTENSIONS = []
15 15
16 16 # additional extensions for indexing besides the default from pygments
17 17 # those get's added to INDEX_EXTENSIONS
18 18 EXTRA_INDEX_EXTENSIONS = []
19 19
20 20
21 21 #==============================================================================
22 22 # POST CREATE REPOSITORY HOOK
23 23 #==============================================================================
24 24 # this function will be executed after each repository is created
25 25 def _crhook(*args, **kwargs):
26 26 """
27 27 Post create repository HOOK
28 28 kwargs available:
29 29 :param repo_name:
30 30 :param repo_type:
31 31 :param description:
32 32 :param private:
33 33 :param created_on:
34 34 :param enable_downloads:
35 35 :param repo_id:
36 36 :param user_id:
37 37 :param enable_statistics:
38 38 :param clone_uri:
39 39 :param fork_id:
40 40 :param group_id:
41 41 :param created_by:
42 42 """
43
44 43 return 0
45 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 75 # POST PUSH HOOK
50 76 #==============================================================================
51 77
52 78 # this function will be executed after each push it's runned after the build-in
53 79 # hook that rhodecode uses for logging pushes
54 80 def _pushhook(*args, **kwargs):
55 81 """
56 82 Post push hook
57 83 kwargs available:
58 84
59 85 :param username: name of user who pushed
60 86 :param ip: ip of who pushed
61 87 :param action: pull
62 88 :param repository: repository name
63 89 :param pushed_revs: generator of pushed revisions
64 90 """
65 91 return 0
66 92 PUSH_HOOK = _pushhook
67 93
68 94
69 95 #==============================================================================
70 96 # POST PULL HOOK
71 97 #==============================================================================
72 98
73 99 # this function will be executed after each push it's runned after the build-in
74 100 # hook that rhodecode uses for logging pushes
75 101 def _pullhook(*args, **kwargs):
76 102 """
77 103 Post pull hook
78 104 kwargs available::
79 105
80 106 :param username: name of user who pulled
81 107 :param ip: ip of who pushed
82 108 :param action: pull
83 109 :param repository: repository name
84 110 """
85 111 return 0
86 112 PULL_HOOK = _pullhook
@@ -1,191 +1,190 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Settings controller for rhodecode
7 7
8 8 :created_on: Jun 30, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31
32 32 from pylons import tmpl_context as c, request, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 import rhodecode.lib.helpers as h
37 37
38 38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator,\
39 39 HasRepoPermissionAnyDecorator
40 40 from rhodecode.lib.base import BaseRepoController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger
42 42
43 43 from rhodecode.model.forms import RepoSettingsForm
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.db import RepoGroup, Repository
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.scm import ScmModel
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class SettingsController(BaseRepoController):
53 53
54 54 @LoginRequired()
55 55 def __before__(self):
56 56 super(SettingsController, self).__before__()
57 57
58 58 def __load_defaults(self):
59 59 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
60 60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
61 61
62 62 repo_model = RepoModel()
63 63 c.users_array = repo_model.get_users_js()
64 64 c.users_groups_array = repo_model.get_users_groups_js()
65 65 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
66 66 c.landing_revs_choices = choices
67 67
68 68 @HasRepoPermissionAllDecorator('repository.admin')
69 69 def index(self, repo_name):
70 70 repo_model = RepoModel()
71 71 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
72 72 if not repo:
73 73 h.flash(_('%s repository is not mapped to db perhaps'
74 74 ' it was created or renamed from the file system'
75 75 ' please run the application again'
76 76 ' in order to rescan repositories') % repo_name,
77 77 category='error')
78 78
79 79 return redirect(url('home'))
80 80
81 81 self.__load_defaults()
82 82
83 83 defaults = RepoModel()._get_defaults(repo_name)
84 84
85 85 return htmlfill.render(
86 86 render('settings/repo_settings.html'),
87 87 defaults=defaults,
88 88 encoding="UTF-8",
89 89 force_defaults=False
90 90 )
91 91
92 92 @HasRepoPermissionAllDecorator('repository.admin')
93 93 def update(self, repo_name):
94 94 repo_model = RepoModel()
95 95 changed_name = repo_name
96 96
97 97 self.__load_defaults()
98 98
99 99 _form = RepoSettingsForm(edit=True,
100 100 old_data={'repo_name': repo_name},
101 101 repo_groups=c.repo_groups_choices,
102 102 landing_revs=c.landing_revs_choices)()
103 103 try:
104 104 form_result = _form.to_python(dict(request.POST))
105 105
106 106 repo_model.update(repo_name, form_result)
107 107 invalidate_cache('get_repo_cached_%s' % repo_name)
108 108 h.flash(_('Repository %s updated successfully') % repo_name,
109 109 category='success')
110 110 changed_name = form_result['repo_name_full']
111 111 action_logger(self.rhodecode_user, 'user_updated_repo',
112 112 changed_name, self.ip_addr, self.sa)
113 113 Session().commit()
114 114 except formencode.Invalid, errors:
115 115 c.repo_info = repo_model.get_by_repo_name(repo_name)
116 116 c.users_array = repo_model.get_users_js()
117 117 errors.value.update({'user': c.repo_info.user.username})
118 118 return htmlfill.render(
119 119 render('settings/repo_settings.html'),
120 120 defaults=errors.value,
121 121 errors=errors.error_dict or {},
122 122 prefix_error=False,
123 123 encoding="UTF-8")
124 124 except Exception:
125 125 log.error(traceback.format_exc())
126 126 h.flash(_('error occurred during update of repository %s') \
127 127 % repo_name, category='error')
128 128
129 129 return redirect(url('repo_settings_home', repo_name=changed_name))
130 130
131 131 @HasRepoPermissionAllDecorator('repository.admin')
132 132 def delete(self, repo_name):
133 133 """DELETE /repos/repo_name: Delete an existing item"""
134 134 # Forms posted to this method should contain a hidden field:
135 135 # <input type="hidden" name="_method" value="DELETE" />
136 136 # Or using helpers:
137 137 # h.form(url('repo_settings_delete', repo_name=ID),
138 138 # method='delete')
139 139 # url('repo_settings_delete', repo_name=ID)
140 140
141 141 repo_model = RepoModel()
142 142 repo = repo_model.get_by_repo_name(repo_name)
143 143 if not repo:
144 144 h.flash(_('%s repository is not mapped to db perhaps'
145 145 ' it was moved or renamed from the filesystem'
146 146 ' please run the application again'
147 147 ' in order to rescan repositories') % repo_name,
148 148 category='error')
149 149
150 150 return redirect(url('home'))
151 151 try:
152 152 action_logger(self.rhodecode_user, 'user_deleted_repo',
153 153 repo_name, self.ip_addr, self.sa)
154 154 repo_model.delete(repo)
155 155 invalidate_cache('get_repo_cached_%s' % repo_name)
156 156 h.flash(_('deleted repository %s') % repo_name, category='success')
157 157 Session().commit()
158 158 except Exception:
159 159 log.error(traceback.format_exc())
160 160 h.flash(_('An error occurred during deletion of %s') % repo_name,
161 161 category='error')
162 162
163 163 return redirect(url('home'))
164 164
165 165 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
166 166 def toggle_locking(self, repo_name):
167 167 """
168 168 Toggle locking of repository by simple GET call to url
169 169
170 170 :param repo_name:
171 171 """
172 172
173 173 try:
174 174 repo = Repository.get_by_repo_name(repo_name)
175 175
176 176 if repo.enable_locking:
177 177 if repo.locked[0]:
178 178 Repository.unlock(repo)
179 179 action = _('unlocked')
180 180 else:
181 181 Repository.lock(repo, c.rhodecode_user.user_id)
182 182 action = _('locked')
183 183
184 184 h.flash(_('Repository has been %s') % action,
185 185 category='success')
186 186 except Exception, e:
187 187 log.error(traceback.format_exc())
188 188 h.flash(_('An error occurred during unlocking'),
189 189 category='error')
190 190 return redirect(url('summary_home', repo_name=repo_name))
191
@@ -1,670 +1,670 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import sys
29 29 import uuid
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from rhodecode import __dbversion__, __py_version__
34 34
35 35 from rhodecode.model.user import UserModel
36 36 from rhodecode.lib.utils import ask_ok
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
40 40 UserRepoGroupToPerm
41 41
42 42 from sqlalchemy.engine import create_engine
43 43 from rhodecode.model.repos_group import ReposGroupModel
44 44 #from rhodecode.model import meta
45 45 from rhodecode.model.meta import Session, Base
46 46
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 def notify(msg):
52 52 """
53 53 Notification for migrations messages
54 54 """
55 55 ml = len(msg) + (4 * 2)
56 56 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
57 57
58 58
59 59 class DbManage(object):
60 60 def __init__(self, log_sql, dbconf, root, tests=False):
61 61 self.dbname = dbconf.split('/')[-1]
62 62 self.tests = tests
63 63 self.root = root
64 64 self.dburi = dbconf
65 65 self.log_sql = log_sql
66 66 self.db_exists = False
67 67 self.init_db()
68 68
69 69 def init_db(self):
70 70 engine = create_engine(self.dburi, echo=self.log_sql)
71 71 init_model(engine)
72 72 self.sa = Session()
73 73
74 74 def create_tables(self, override=False, defaults={}):
75 75 """
76 76 Create a auth database
77 77 """
78 78 quiet = defaults.get('quiet')
79 79 log.info("Any existing database is going to be destroyed")
80 80 if self.tests or quiet:
81 81 destroy = True
82 82 else:
83 83 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
84 84 if not destroy:
85 85 sys.exit()
86 86 if destroy:
87 87 Base.metadata.drop_all()
88 88
89 89 checkfirst = not override
90 90 Base.metadata.create_all(checkfirst=checkfirst)
91 91 log.info('Created tables for %s' % self.dbname)
92 92
93 93 def set_db_version(self):
94 94 ver = DbMigrateVersion()
95 95 ver.version = __dbversion__
96 96 ver.repository_id = 'rhodecode_db_migrations'
97 97 ver.repository_path = 'versions'
98 98 self.sa.add(ver)
99 99 log.info('db version set to: %s' % __dbversion__)
100 100
101 101 def upgrade(self):
102 102 """
103 103 Upgrades given database schema to given revision following
104 104 all needed steps, to perform the upgrade
105 105
106 106 """
107 107
108 108 from rhodecode.lib.dbmigrate.migrate.versioning import api
109 109 from rhodecode.lib.dbmigrate.migrate.exceptions import \
110 110 DatabaseNotControlledError
111 111
112 112 if 'sqlite' in self.dburi:
113 113 print (
114 114 '********************** WARNING **********************\n'
115 115 'Make sure your version of sqlite is at least 3.7.X. \n'
116 116 'Earlier versions are known to fail on some migrations\n'
117 117 '*****************************************************\n'
118 118 )
119 119 upgrade = ask_ok('You are about to perform database upgrade, make '
120 120 'sure You backed up your database before. '
121 121 'Continue ? [y/n]')
122 122 if not upgrade:
123 123 sys.exit('Nothing done')
124 124
125 125 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
126 126 'rhodecode/lib/dbmigrate')
127 127 db_uri = self.dburi
128 128
129 129 try:
130 130 curr_version = api.db_version(db_uri, repository_path)
131 131 msg = ('Found current database under version'
132 132 ' control with version %s' % curr_version)
133 133
134 134 except (RuntimeError, DatabaseNotControlledError):
135 135 curr_version = 1
136 136 msg = ('Current database is not under version control. Setting'
137 137 ' as version %s' % curr_version)
138 138 api.version_control(db_uri, repository_path, curr_version)
139 139
140 140 notify(msg)
141 141
142 142 if curr_version == __dbversion__:
143 143 sys.exit('This database is already at the newest version')
144 144
145 145 #======================================================================
146 146 # UPGRADE STEPS
147 147 #======================================================================
148 148
149 149 class UpgradeSteps(object):
150 150 """
151 151 Those steps follow schema versions so for example schema
152 152 for example schema with seq 002 == step_2 and so on.
153 153 """
154 154
155 155 def __init__(self, klass):
156 156 self.klass = klass
157 157
158 158 def step_0(self):
159 159 # step 0 is the schema upgrade, and than follow proper upgrades
160 160 notify('attempting to do database upgrade to version %s' \
161 161 % __dbversion__)
162 162 api.upgrade(db_uri, repository_path, __dbversion__)
163 163 notify('Schema upgrade completed')
164 164
165 165 def step_1(self):
166 166 pass
167 167
168 168 def step_2(self):
169 169 notify('Patching repo paths for newer version of RhodeCode')
170 170 self.klass.fix_repo_paths()
171 171
172 172 notify('Patching default user of RhodeCode')
173 173 self.klass.fix_default_user()
174 174
175 175 log.info('Changing ui settings')
176 176 self.klass.create_ui_settings()
177 177
178 178 def step_3(self):
179 179 notify('Adding additional settings into RhodeCode db')
180 180 self.klass.fix_settings()
181 181 notify('Adding ldap defaults')
182 182 self.klass.create_ldap_options(skip_existing=True)
183 183
184 184 def step_4(self):
185 185 notify('create permissions and fix groups')
186 186 self.klass.create_permissions()
187 187 self.klass.fixup_groups()
188 188
189 189 def step_5(self):
190 190 pass
191 191
192 192 def step_6(self):
193 193
194 194 notify('re-checking permissions')
195 195 self.klass.create_permissions()
196 196
197 197 notify('installing new UI options')
198 198 sett4 = RhodeCodeSetting('show_public_icon', True)
199 199 Session().add(sett4)
200 200 sett5 = RhodeCodeSetting('show_private_icon', True)
201 201 Session().add(sett5)
202 202 sett6 = RhodeCodeSetting('stylify_metatags', False)
203 203 Session().add(sett6)
204 204
205 205 notify('fixing old PULL hook')
206 206 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
207 207 if _pull:
208 208 _pull.ui_key = RhodeCodeUi.HOOK_PULL
209 209 Session().add(_pull)
210 210
211 211 notify('fixing old PUSH hook')
212 212 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
213 213 if _push:
214 214 _push.ui_key = RhodeCodeUi.HOOK_PUSH
215 215 Session().add(_push)
216 216
217 217 notify('installing new pre-push hook')
218 218 hooks4 = RhodeCodeUi()
219 219 hooks4.ui_section = 'hooks'
220 220 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
221 221 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
222 222 Session().add(hooks4)
223 223
224 224 notify('installing new pre-pull hook')
225 225 hooks6 = RhodeCodeUi()
226 226 hooks6.ui_section = 'hooks'
227 227 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
228 228 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
229 229 Session().add(hooks6)
230 230
231 231 notify('installing hgsubversion option')
232 232 # enable hgsubversion disabled by default
233 233 hgsubversion = RhodeCodeUi()
234 234 hgsubversion.ui_section = 'extensions'
235 235 hgsubversion.ui_key = 'hgsubversion'
236 236 hgsubversion.ui_value = ''
237 237 hgsubversion.ui_active = False
238 238 Session().add(hgsubversion)
239 239
240 240 notify('installing hg git option')
241 241 # enable hggit disabled by default
242 242 hggit = RhodeCodeUi()
243 243 hggit.ui_section = 'extensions'
244 244 hggit.ui_key = 'hggit'
245 245 hggit.ui_value = ''
246 246 hggit.ui_active = False
247 247 Session().add(hggit)
248 248
249 249 notify('re-check default permissions')
250 250 default_user = User.get_by_username(User.DEFAULT_USER)
251 251 perm = Permission.get_by_key('hg.fork.repository')
252 252 reg_perm = UserToPerm()
253 253 reg_perm.user = default_user
254 254 reg_perm.permission = perm
255 255 Session().add(reg_perm)
256 256
257 257 def step_7(self):
258 258 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
259 259 Session().commit()
260 260 if perm_fixes:
261 261 notify('There was an inconsistent state of permissions '
262 262 'detected for default user. Permissions are now '
263 263 'reset to the default value for default user. '
264 264 'Please validate and check default permissions '
265 265 'in admin panel')
266 266
267 267 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
268 268
269 269 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
270 270 _step = None
271 271 for step in upgrade_steps:
272 272 notify('performing upgrade step %s' % step)
273 273 getattr(UpgradeSteps(self), 'step_%s' % step)()
274 274 self.sa.commit()
275 275 _step = step
276 276
277 277 notify('upgrade to version %s successful' % _step)
278 278
279 279 def fix_repo_paths(self):
280 280 """
281 281 Fixes a old rhodecode version path into new one without a '*'
282 282 """
283 283
284 284 paths = self.sa.query(RhodeCodeUi)\
285 285 .filter(RhodeCodeUi.ui_key == '/')\
286 286 .scalar()
287 287
288 288 paths.ui_value = paths.ui_value.replace('*', '')
289 289
290 290 try:
291 291 self.sa.add(paths)
292 292 self.sa.commit()
293 293 except:
294 294 self.sa.rollback()
295 295 raise
296 296
297 297 def fix_default_user(self):
298 298 """
299 299 Fixes a old default user with some 'nicer' default values,
300 300 used mostly for anonymous access
301 301 """
302 302 def_user = self.sa.query(User)\
303 303 .filter(User.username == 'default')\
304 304 .one()
305 305
306 306 def_user.name = 'Anonymous'
307 307 def_user.lastname = 'User'
308 308 def_user.email = 'anonymous@rhodecode.org'
309 309
310 310 try:
311 311 self.sa.add(def_user)
312 312 self.sa.commit()
313 313 except:
314 314 self.sa.rollback()
315 315 raise
316 316
317 317 def fix_settings(self):
318 318 """
319 319 Fixes rhodecode settings adds ga_code key for google analytics
320 320 """
321 321
322 322 hgsettings3 = RhodeCodeSetting('ga_code', '')
323 323
324 324 try:
325 325 self.sa.add(hgsettings3)
326 326 self.sa.commit()
327 327 except:
328 328 self.sa.rollback()
329 329 raise
330 330
331 331 def admin_prompt(self, second=False, defaults={}):
332 332 if not self.tests:
333 333 import getpass
334 334
335 335 # defaults
336 336 username = defaults.get('username')
337 337 password = defaults.get('password')
338 338 email = defaults.get('email')
339 339
340 340 def get_password():
341 341 password = getpass.getpass('Specify admin password '
342 342 '(min 6 chars):')
343 343 confirm = getpass.getpass('Confirm password:')
344 344
345 345 if password != confirm:
346 346 log.error('passwords mismatch')
347 347 return False
348 348 if len(password) < 6:
349 349 log.error('password is to short use at least 6 characters')
350 350 return False
351 351
352 352 return password
353 353 if username is None:
354 354 username = raw_input('Specify admin username:')
355 355 if password is None:
356 356 password = get_password()
357 357 if not password:
358 358 #second try
359 359 password = get_password()
360 360 if not password:
361 361 sys.exit()
362 362 if email is None:
363 363 email = raw_input('Specify admin email:')
364 364 self.create_user(username, password, email, True)
365 365 else:
366 366 log.info('creating admin and regular test users')
367 367 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
368 368 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
369 369 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
370 370 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
371 371 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
372 372
373 373 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
374 374 TEST_USER_ADMIN_EMAIL, True)
375 375
376 376 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
377 377 TEST_USER_REGULAR_EMAIL, False)
378 378
379 379 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
380 380 TEST_USER_REGULAR2_EMAIL, False)
381 381
382 382 def create_ui_settings(self):
383 383 """
384 384 Creates ui settings, fills out hooks
385 385 and disables dotencode
386 386 """
387 387
388 388 #HOOKS
389 389 hooks1_key = RhodeCodeUi.HOOK_UPDATE
390 390 hooks1_ = self.sa.query(RhodeCodeUi)\
391 391 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
392 392
393 393 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
394 394 hooks1.ui_section = 'hooks'
395 395 hooks1.ui_key = hooks1_key
396 396 hooks1.ui_value = 'hg update >&2'
397 397 hooks1.ui_active = False
398 398 self.sa.add(hooks1)
399 399
400 400 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
401 401 hooks2_ = self.sa.query(RhodeCodeUi)\
402 402 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
403 403 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
404 404 hooks2.ui_section = 'hooks'
405 405 hooks2.ui_key = hooks2_key
406 406 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
407 407 self.sa.add(hooks2)
408 408
409 409 hooks3 = RhodeCodeUi()
410 410 hooks3.ui_section = 'hooks'
411 411 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
412 412 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
413 413 self.sa.add(hooks3)
414 414
415 415 hooks4 = RhodeCodeUi()
416 416 hooks4.ui_section = 'hooks'
417 417 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
418 418 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
419 419 self.sa.add(hooks4)
420 420
421 421 hooks5 = RhodeCodeUi()
422 422 hooks5.ui_section = 'hooks'
423 423 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
424 424 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
425 425 self.sa.add(hooks5)
426 426
427 427 hooks6 = RhodeCodeUi()
428 428 hooks6.ui_section = 'hooks'
429 429 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
430 430 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
431 431 self.sa.add(hooks6)
432 432
433 433 # enable largefiles
434 434 largefiles = RhodeCodeUi()
435 435 largefiles.ui_section = 'extensions'
436 436 largefiles.ui_key = 'largefiles'
437 437 largefiles.ui_value = ''
438 438 self.sa.add(largefiles)
439 439
440 440 # enable hgsubversion disabled by default
441 441 hgsubversion = RhodeCodeUi()
442 442 hgsubversion.ui_section = 'extensions'
443 443 hgsubversion.ui_key = 'hgsubversion'
444 444 hgsubversion.ui_value = ''
445 445 hgsubversion.ui_active = False
446 446 self.sa.add(hgsubversion)
447 447
448 448 # enable hggit disabled by default
449 449 hggit = RhodeCodeUi()
450 450 hggit.ui_section = 'extensions'
451 451 hggit.ui_key = 'hggit'
452 452 hggit.ui_value = ''
453 453 hggit.ui_active = False
454 454 self.sa.add(hggit)
455 455
456 456 def create_ldap_options(self, skip_existing=False):
457 457 """Creates ldap settings"""
458 458
459 459 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
460 460 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
461 461 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
462 462 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
463 463 ('ldap_filter', ''), ('ldap_search_scope', ''),
464 464 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
465 465 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
466 466
467 467 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
468 468 log.debug('Skipping option %s' % k)
469 469 continue
470 470 setting = RhodeCodeSetting(k, v)
471 471 self.sa.add(setting)
472 472
473 473 def fixup_groups(self):
474 474 def_usr = User.get_by_username('default')
475 475 for g in RepoGroup.query().all():
476 476 g.group_name = g.get_new_name(g.name)
477 477 self.sa.add(g)
478 478 # get default perm
479 479 default = UserRepoGroupToPerm.query()\
480 480 .filter(UserRepoGroupToPerm.group == g)\
481 481 .filter(UserRepoGroupToPerm.user == def_usr)\
482 482 .scalar()
483 483
484 484 if default is None:
485 485 log.debug('missing default permission for group %s adding' % g)
486 486 ReposGroupModel()._create_default_perms(g)
487 487
488 488 def reset_permissions(self, username):
489 489 """
490 490 Resets permissions to default state, usefull when old systems had
491 491 bad permissions, we must clean them up
492 492
493 493 :param username:
494 494 :type username:
495 495 """
496 496 default_user = User.get_by_username(username)
497 497 if not default_user:
498 498 return
499 499
500 500 u2p = UserToPerm.query()\
501 501 .filter(UserToPerm.user == default_user).all()
502 502 fixed = False
503 503 if len(u2p) != len(User.DEFAULT_PERMISSIONS):
504 504 for p in u2p:
505 505 Session().delete(p)
506 506 fixed = True
507 507 self.populate_default_permissions()
508 508 return fixed
509 509
510 510 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
511 511 _path = defaults.get('repos_location')
512 512 if retries == 3:
513 513 log.info('Setting up repositories config')
514 514
515 515 if _path is not None:
516 516 path = _path
517 517 elif not self.tests and not test_repo_path:
518 518 path = raw_input(
519 519 'Enter a valid absolute path to store repositories. '
520 520 'All repositories in that path will be added automatically:'
521 521 )
522 522 else:
523 523 path = test_repo_path
524 524 path_ok = True
525 525
526 526 # check proper dir
527 527 if not os.path.isdir(path):
528 528 path_ok = False
529 529 log.error('Given path %s is not a valid directory' % path)
530 530
531 531 elif not os.path.isabs(path):
532 532 path_ok = False
533 533 log.error('Given path %s is not an absolute path' % path)
534 534
535 535 # check write access
536 536 elif not os.access(path, os.W_OK) and path_ok:
537 537 path_ok = False
538 538 log.error('No write permission to given path %s' % path)
539 539
540 540 if retries == 0:
541 541 sys.exit('max retries reached')
542 542 if path_ok is False:
543 543 retries -= 1
544 544 return self.config_prompt(test_repo_path, retries)
545 545
546 546 real_path = os.path.realpath(path)
547 547
548 548 if real_path != path:
549 549 if not ask_ok(('Path looks like a symlink, Rhodecode will store '
550 550 'given path as %s ? [y/n]') % (real_path)):
551 551 log.error('Canceled by user')
552 552 sys.exit(-1)
553 553
554 554 return real_path
555 555
556 556 def create_settings(self, path):
557 557
558 558 self.create_ui_settings()
559 559
560 560 #HG UI OPTIONS
561 561 web1 = RhodeCodeUi()
562 562 web1.ui_section = 'web'
563 563 web1.ui_key = 'push_ssl'
564 564 web1.ui_value = 'false'
565 565
566 566 web2 = RhodeCodeUi()
567 567 web2.ui_section = 'web'
568 568 web2.ui_key = 'allow_archive'
569 569 web2.ui_value = 'gz zip bz2'
570 570
571 571 web3 = RhodeCodeUi()
572 572 web3.ui_section = 'web'
573 573 web3.ui_key = 'allow_push'
574 574 web3.ui_value = '*'
575 575
576 576 web4 = RhodeCodeUi()
577 577 web4.ui_section = 'web'
578 578 web4.ui_key = 'baseurl'
579 579 web4.ui_value = '/'
580 580
581 581 paths = RhodeCodeUi()
582 582 paths.ui_section = 'paths'
583 583 paths.ui_key = '/'
584 584 paths.ui_value = path
585 585
586 586 phases = RhodeCodeUi()
587 587 phases.ui_section = 'phases'
588 588 phases.ui_key = 'publish'
589 589 phases.ui_value = False
590 590
591 591 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
592 592 sett2 = RhodeCodeSetting('title', 'RhodeCode')
593 593 sett3 = RhodeCodeSetting('ga_code', '')
594 594
595 595 sett4 = RhodeCodeSetting('show_public_icon', True)
596 596 sett5 = RhodeCodeSetting('show_private_icon', True)
597 597 sett6 = RhodeCodeSetting('stylify_metatags', False)
598 598
599 599 self.sa.add(web1)
600 600 self.sa.add(web2)
601 601 self.sa.add(web3)
602 602 self.sa.add(web4)
603 603 self.sa.add(paths)
604 604 self.sa.add(sett1)
605 605 self.sa.add(sett2)
606 606 self.sa.add(sett3)
607 607 self.sa.add(sett4)
608 608 self.sa.add(sett5)
609 609 self.sa.add(sett6)
610 610
611 611 self.create_ldap_options()
612 612
613 613 log.info('created ui config')
614 614
615 615 def create_user(self, username, password, email='', admin=False):
616 616 log.info('creating user %s' % username)
617 617 UserModel().create_or_update(username, password, email,
618 618 firstname='RhodeCode', lastname='Admin',
619 619 active=True, admin=admin)
620 620
621 621 def create_default_user(self):
622 622 log.info('creating default user')
623 623 # create default user for handling default permissions.
624 624 UserModel().create_or_update(username='default',
625 625 password=str(uuid.uuid1())[:8],
626 626 email='anonymous@rhodecode.org',
627 627 firstname='Anonymous', lastname='User')
628 628
629 629 def create_permissions(self):
630 630 # module.(access|create|change|delete)_[name]
631 631 # module.(none|read|write|admin)
632 632
633 633 for p in Permission.PERMS:
634 634 if not Permission.get_by_key(p[0]):
635 635 new_perm = Permission()
636 636 new_perm.permission_name = p[0]
637 637 new_perm.permission_longname = p[0]
638 638 self.sa.add(new_perm)
639 639
640 640 def populate_default_permissions(self):
641 641 log.info('creating default user permissions')
642 642
643 643 default_user = User.get_by_username('default')
644 644
645 645 for def_perm in User.DEFAULT_PERMISSIONS:
646 646
647 647 perm = self.sa.query(Permission)\
648 648 .filter(Permission.permission_name == def_perm)\
649 649 .scalar()
650 650 if not perm:
651 651 raise Exception(
652 652 'CRITICAL: permission %s not found inside database !!'
653 653 % def_perm
654 654 )
655 655 if not UserToPerm.query()\
656 656 .filter(UserToPerm.permission == perm)\
657 657 .filter(UserToPerm.user == default_user).scalar():
658 658 reg_perm = UserToPerm()
659 659 reg_perm.user = default_user
660 660 reg_perm.permission = perm
661 661 self.sa.add(reg_perm)
662 662
663 663 def finish(self):
664 664 """
665 665 Function executed at the end of setup
666 666 """
667 667 if not __py_version__ >= (2, 6):
668 668 notify('Python2.5 detected, please switch '
669 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 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.hooks
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Hooks runned by rhodecode
7 7
8 8 :created_on: Aug 6, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import sys
27 import time
27 28 import binascii
28 29 from inspect import isfunction
29 30
30 31 from mercurial.scmutil import revrange
31 32 from mercurial.node import nullrev
32 33
33 34 from rhodecode.lib import helpers as h
34 35 from rhodecode.lib.utils import action_logger
35 36 from rhodecode.lib.vcs.backends.base import EmptyChangeset
36 37 from rhodecode.lib.compat import json
37 38 from rhodecode.lib.exceptions import HTTPLockedRC
38 39 from rhodecode.lib.utils2 import safe_str
39 40 from rhodecode.model.db import Repository, User
40 41
42
41 43 def _get_scm_size(alias, root_path):
42 44
43 45 if not alias.startswith('.'):
44 46 alias += '.'
45 47
46 48 size_scm, size_root = 0, 0
47 49 for path, dirs, files in os.walk(root_path):
48 50 if path.find(alias) != -1:
49 51 for f in files:
50 52 try:
51 53 size_scm += os.path.getsize(os.path.join(path, f))
52 54 except OSError:
53 55 pass
54 56 else:
55 57 for f in files:
56 58 try:
57 59 size_root += os.path.getsize(os.path.join(path, f))
58 60 except OSError:
59 61 pass
60 62
61 63 size_scm_f = h.format_byte_size(size_scm)
62 64 size_root_f = h.format_byte_size(size_root)
63 65 size_total_f = h.format_byte_size(size_root + size_scm)
64 66
65 67 return size_scm_f, size_root_f, size_total_f
66 68
67 69
68 70 def repo_size(ui, repo, hooktype=None, **kwargs):
69 71 """
70 72 Presents size of repository after push
71 73
72 74 :param ui:
73 75 :param repo:
74 76 :param hooktype:
75 77 """
76 78
77 79 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
78 80
79 81 last_cs = repo[len(repo) - 1]
80 82
81 83 msg = ('Repository size .hg:%s repo:%s total:%s\n'
82 84 'Last revision is now r%s:%s\n') % (
83 85 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
84 86 )
85 87
86 88 sys.stdout.write(msg)
87 89
88 90
89 91 def pre_push(ui, repo, **kwargs):
90 92 # pre push function, currently used to ban pushing when
91 93 # repository is locked
92 94 try:
93 95 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
94 96 except:
95 97 rc_extras = {}
96 98 extras = dict(repo.ui.configitems('rhodecode_extras'))
97 99
98 100 if 'username' in extras:
99 101 username = extras['username']
100 102 repository = extras['repository']
101 103 scm = extras['scm']
102 104 locked_by = extras['locked_by']
103 105 elif 'username' in rc_extras:
104 106 username = rc_extras['username']
105 107 repository = rc_extras['repository']
106 108 scm = rc_extras['scm']
107 109 locked_by = rc_extras['locked_by']
108 110 else:
109 111 raise Exception('Missing data in repo.ui and os.environ')
110 112
111 113 usr = User.get_by_username(username)
112 114 if locked_by[0] and usr.user_id != int(locked_by[0]):
113 115 locked_by = User.get(locked_by[0]).username
114 116 raise HTTPLockedRC(repository, locked_by)
115 117
116 118
117 119 def pre_pull(ui, repo, **kwargs):
118 120 # pre push function, currently used to ban pushing when
119 121 # repository is locked
120 122 try:
121 123 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
122 124 except:
123 125 rc_extras = {}
124 126 extras = dict(repo.ui.configitems('rhodecode_extras'))
125 127 if 'username' in extras:
126 128 username = extras['username']
127 129 repository = extras['repository']
128 130 scm = extras['scm']
129 131 locked_by = extras['locked_by']
130 132 elif 'username' in rc_extras:
131 133 username = rc_extras['username']
132 134 repository = rc_extras['repository']
133 135 scm = rc_extras['scm']
134 136 locked_by = rc_extras['locked_by']
135 137 else:
136 138 raise Exception('Missing data in repo.ui and os.environ')
137 139
138 140 if locked_by[0]:
139 141 locked_by = User.get(locked_by[0]).username
140 142 raise HTTPLockedRC(repository, locked_by)
141 143
142 144
143 145 def log_pull_action(ui, repo, **kwargs):
144 146 """
145 147 Logs user last pull action
146 148
147 149 :param ui:
148 150 :param repo:
149 151 """
150 152 try:
151 153 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
152 154 except:
153 155 rc_extras = {}
154 156 extras = dict(repo.ui.configitems('rhodecode_extras'))
155 157 if 'username' in extras:
156 158 username = extras['username']
157 159 repository = extras['repository']
158 160 scm = extras['scm']
159 161 make_lock = extras['make_lock']
160 162 elif 'username' in rc_extras:
161 163 username = rc_extras['username']
162 164 repository = rc_extras['repository']
163 165 scm = rc_extras['scm']
164 166 make_lock = rc_extras['make_lock']
165 167 else:
166 168 raise Exception('Missing data in repo.ui and os.environ')
167 169 user = User.get_by_username(username)
168 170 action = 'pull'
169 171 action_logger(user, action, repository, extras['ip'], commit=True)
170 172 # extension hook call
171 173 from rhodecode import EXTENSIONS
172 174 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
173 175
174 176 if isfunction(callback):
175 177 kw = {}
176 178 kw.update(extras)
177 179 callback(**kw)
178 180
179 181 if make_lock is True:
180 182 Repository.lock(Repository.get_by_repo_name(repository), user.user_id)
181 183 #msg = 'Made lock on repo `%s`' % repository
182 184 #sys.stdout.write(msg)
183 185
184 186 return 0
185 187
186 188
187 189 def log_push_action(ui, repo, **kwargs):
188 190 """
189 191 Maps user last push action to new changeset id, from mercurial
190 192
191 193 :param ui:
192 194 :param repo: repo object containing the `ui` object
193 195 """
194 196
195 197 try:
196 198 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
197 199 except:
198 200 rc_extras = {}
199 201
200 202 extras = dict(repo.ui.configitems('rhodecode_extras'))
201 203 if 'username' in extras:
202 204 username = extras['username']
203 205 repository = extras['repository']
204 206 scm = extras['scm']
205 207 make_lock = extras['make_lock']
206 208 elif 'username' in rc_extras:
207 209 username = rc_extras['username']
208 210 repository = rc_extras['repository']
209 211 scm = rc_extras['scm']
210 212 make_lock = rc_extras['make_lock']
211 213 else:
212 214 raise Exception('Missing data in repo.ui and os.environ')
213 215
214 216 action = 'push' + ':%s'
215 217
216 218 if scm == 'hg':
217 219 node = kwargs['node']
218 220
219 221 def get_revs(repo, rev_opt):
220 222 if rev_opt:
221 223 revs = revrange(repo, rev_opt)
222 224
223 225 if len(revs) == 0:
224 226 return (nullrev, nullrev)
225 227 return (max(revs), min(revs))
226 228 else:
227 229 return (len(repo) - 1, 0)
228 230
229 231 stop, start = get_revs(repo, [node + ':'])
230 232 h = binascii.hexlify
231 233 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
232 234 elif scm == 'git':
233 235 revs = kwargs.get('_git_revs', [])
234 236 if '_git_revs' in kwargs:
235 237 kwargs.pop('_git_revs')
236 238
237 239 action = action % ','.join(revs)
238 240
239 241 action_logger(username, action, repository, extras['ip'], commit=True)
240 242
241 243 # extension hook call
242 244 from rhodecode import EXTENSIONS
243 245 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
244 246 if isfunction(callback):
245 247 kw = {'pushed_revs': revs}
246 248 kw.update(extras)
247 249 callback(**kw)
248 250
249 251 if make_lock is False:
250 252 Repository.unlock(Repository.get_by_repo_name(repository))
251 253 msg = 'Released lock on repo `%s`\n' % repository
252 254 sys.stdout.write(msg)
253 255
254 256 return 0
255 257
256 258
257 259 def log_create_repository(repository_dict, created_by, **kwargs):
258 260 """
259 261 Post create repository Hook. This is a dummy function for admins to re-use
260 262 if needed. It's taken from rhodecode-extensions module and executed
261 263 if present
262 264
263 265 :param repository: dict dump of repository object
264 266 :param created_by: username who created repository
265 :param created_date: date of creation
266 267
267 268 available keys of repository_dict:
268 269
269 270 'repo_type',
270 271 'description',
271 272 'private',
272 273 'created_on',
273 274 'enable_downloads',
274 275 'repo_id',
275 276 'user_id',
276 277 'enable_statistics',
277 278 'clone_uri',
278 279 'fork_id',
279 280 'group_id',
280 281 'repo_name'
281 282
282 283 """
283 284 from rhodecode import EXTENSIONS
284 285 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
285 286 if isfunction(callback):
286 287 kw = {}
287 288 kw.update(repository_dict)
288 289 kw.update({'created_by': created_by})
289 290 kw.update(kwargs)
290 291 return callback(**kw)
291 292
292 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 334 handle_git_pre_receive = (lambda repo_path, revs, env:
295 335 handle_git_receive(repo_path, revs, env, hook_type='pre'))
296 336 handle_git_post_receive = (lambda repo_path, revs, env:
297 337 handle_git_receive(repo_path, revs, env, hook_type='post'))
298 338
299 339
300 340 def handle_git_receive(repo_path, revs, env, hook_type='post'):
301 341 """
302 342 A really hacky method that is runned by git post-receive hook and logs
303 343 an push action together with pushed revisions. It's executed by subprocess
304 344 thus needs all info to be able to create a on the fly pylons enviroment,
305 345 connect to database and run the logging code. Hacky as sh*t but works.
306 346
307 347 :param repo_path:
308 348 :type repo_path:
309 349 :param revs:
310 350 :type revs:
311 351 :param env:
312 352 :type env:
313 353 """
314 354 from paste.deploy import appconfig
315 355 from sqlalchemy import engine_from_config
316 356 from rhodecode.config.environment import load_environment
317 357 from rhodecode.model import init_model
318 358 from rhodecode.model.db import RhodeCodeUi
319 359 from rhodecode.lib.utils import make_ui
320 360 extras = json.loads(env['RHODECODE_EXTRAS'])
321 361
322 362 path, ini_name = os.path.split(extras['config'])
323 363 conf = appconfig('config:%s' % ini_name, relative_to=path)
324 364 load_environment(conf.global_conf, conf.local_conf)
325 365
326 366 engine = engine_from_config(conf, 'sqlalchemy.db1.')
327 367 init_model(engine)
328 368
329 369 baseui = make_ui('db')
330 370 # fix if it's not a bare repo
331 371 if repo_path.endswith(os.sep + '.git'):
332 372 repo_path = repo_path[:-5]
333 373
334 374 repo = Repository.get_by_full_path(repo_path)
335 375 if not repo:
336 376 raise OSError('Repository %s not found in database'
337 377 % (safe_str(repo_path)))
338 378
339 379 _hooks = dict(baseui.configitems('hooks')) or {}
340 380
341 381 for k, v in extras.items():
342 382 baseui.setconfig('rhodecode_extras', k, v)
343 383 repo = repo.scm_instance
344 384 repo.ui = baseui
345 385
346 386 if hook_type == 'pre':
347 387 pre_push(baseui, repo)
348 388
349 389 # if push hook is enabled via web interface
350 390 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
351 391
352 392 rev_data = []
353 393 for l in revs:
354 394 old_rev, new_rev, ref = l.split(' ')
355 395 _ref_data = ref.split('/')
356 396 if _ref_data[1] in ['tags', 'heads']:
357 397 rev_data.append({'old_rev': old_rev,
358 398 'new_rev': new_rev,
359 399 'ref': ref,
360 400 'type': _ref_data[1],
361 401 'name': _ref_data[2].strip()})
362 402
363 403 git_revs = []
364 404 for push_ref in rev_data:
365 405 _type = push_ref['type']
366 406 if _type == 'heads':
367 407 if push_ref['old_rev'] == EmptyChangeset().raw_id:
368 408 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
369 409 heads = repo.run_git_command(cmd)[0]
370 410 heads = heads.replace(push_ref['ref'], '')
371 411 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
372 412 heads.splitlines()))
373 413 cmd = (('log %(new_rev)s' % push_ref) +
374 414 ' --reverse --pretty=format:"%H" --not ' + heads)
375 415 else:
376 416 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
377 417 ' --reverse --pretty=format:"%H"')
378 418 git_revs += repo.run_git_command(cmd)[0].splitlines()
379 419 elif _type == 'tags':
380 420 git_revs += [push_ref['name']]
381 421
382 422 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,709 +1,709 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 28 import logging
29 29 import datetime
30 30 import traceback
31 31 import paste
32 32 import beaker
33 33 import tarfile
34 34 import shutil
35 35 from os.path import abspath
36 36 from os.path import dirname as dn, join as jn
37 37
38 38 from paste.script.command import Command, BadCommand
39 39
40 40 from mercurial import ui, config
41 41
42 42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 43
44 44 from rhodecode.lib.vcs import get_backend
45 45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 48 from rhodecode.lib.vcs.exceptions import VCSError
49 49
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.model.repos_group import ReposGroupModel
57 57 from rhodecode.lib.utils2 import safe_str, safe_unicode
58 58 from rhodecode.lib.vcs.utils.fakemod import create_module
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
63 63
64 64
65 65 def recursive_replace(str_, replace=' '):
66 66 """
67 67 Recursive replace of given sign to just one instance
68 68
69 69 :param str_: given string
70 70 :param replace: char to find and replace multiple instances
71 71
72 72 Examples::
73 73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
74 74 'Mighty-Mighty-Bo-sstones'
75 75 """
76 76
77 77 if str_.find(replace * 2) == -1:
78 78 return str_
79 79 else:
80 80 str_ = str_.replace(replace * 2, replace)
81 81 return recursive_replace(str_, replace)
82 82
83 83
84 84 def repo_name_slug(value):
85 85 """
86 86 Return slug of name of repository
87 87 This function is called on each creation/modification
88 88 of repository to prevent bad names in repo
89 89 """
90 90
91 91 slug = remove_formatting(value)
92 92 slug = strip_tags(slug)
93 93
94 94 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
95 95 slug = slug.replace(c, '-')
96 96 slug = recursive_replace(slug, '-')
97 97 slug = collapse(slug, '-')
98 98 return slug
99 99
100 100
101 101 def get_repo_slug(request):
102 102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
103 103 if _repo:
104 104 _repo = _repo.rstrip('/')
105 105 return _repo
106 106
107 107
108 108 def get_repos_group_slug(request):
109 109 _group = request.environ['pylons.routes_dict'].get('group_name')
110 110 if _group:
111 111 _group = _group.rstrip('/')
112 112 return _group
113 113
114 114
115 115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
116 116 """
117 117 Action logger for various actions made by users
118 118
119 119 :param user: user that made this action, can be a unique username string or
120 120 object containing user_id attribute
121 121 :param action: action to log, should be on of predefined unique actions for
122 122 easy translations
123 123 :param repo: string name of repository or object containing repo_id,
124 124 that action was made on
125 125 :param ipaddr: optional ip address from what the action was made
126 126 :param sa: optional sqlalchemy session
127 127
128 128 """
129 129
130 130 if not sa:
131 131 sa = meta.Session()
132 132
133 133 try:
134 134 if hasattr(user, 'user_id'):
135 135 user_obj = user
136 136 elif isinstance(user, basestring):
137 137 user_obj = User.get_by_username(user)
138 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 141 if hasattr(repo, 'repo_id'):
142 142 repo_obj = Repository.get(repo.repo_id)
143 143 repo_name = repo_obj.repo_name
144 144 elif isinstance(repo, basestring):
145 145 repo_name = repo.lstrip('/')
146 146 repo_obj = Repository.get_by_repo_name(repo_name)
147 147 else:
148 148 repo_obj = None
149 149 repo_name = ''
150 150
151 151 user_log = UserLog()
152 152 user_log.user_id = user_obj.user_id
153 153 user_log.action = safe_unicode(action)
154 154
155 155 user_log.repository = repo_obj
156 156 user_log.repository_name = repo_name
157 157
158 158 user_log.action_date = datetime.datetime.now()
159 159 user_log.user_ip = ipaddr
160 160 sa.add(user_log)
161 161
162 162 log.info(
163 163 'Adding user %s, action %s on %s' % (user_obj, action,
164 164 safe_unicode(repo))
165 165 )
166 166 if commit:
167 167 sa.commit()
168 168 except:
169 169 log.error(traceback.format_exc())
170 170 raise
171 171
172 172
173 173 def get_repos(path, recursive=False):
174 174 """
175 175 Scans given path for repos and return (name,(type,path)) tuple
176 176
177 177 :param path: path to scan for repositories
178 178 :param recursive: recursive search and return names with subdirs in front
179 179 """
180 180
181 181 # remove ending slash for better results
182 182 path = path.rstrip(os.sep)
183 183
184 184 def _get_repos(p):
185 185 if not os.access(p, os.W_OK):
186 186 return
187 187 for dirpath in os.listdir(p):
188 188 if os.path.isfile(os.path.join(p, dirpath)):
189 189 continue
190 190 cur_path = os.path.join(p, dirpath)
191 191 try:
192 192 scm_info = get_scm(cur_path)
193 193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 194 except VCSError:
195 195 if not recursive:
196 196 continue
197 197 #check if this dir containts other repos for recursive scan
198 198 rec_path = os.path.join(p, dirpath)
199 199 if os.path.isdir(rec_path):
200 200 for inner_scm in _get_repos(rec_path):
201 201 yield inner_scm
202 202
203 203 return _get_repos(path)
204 204
205 205
206 206 def is_valid_repo(repo_name, base_path, scm=None):
207 207 """
208 208 Returns True if given path is a valid repository False otherwise.
209 209 If scm param is given also compare if given scm is the same as expected
210 210 from scm parameter
211 211
212 212 :param repo_name:
213 213 :param base_path:
214 214 :param scm:
215 215
216 216 :return True: if given path is a valid repository
217 217 """
218 218 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
219 219
220 220 try:
221 221 scm_ = get_scm(full_path)
222 222 if scm:
223 223 return scm_[0] == scm
224 224 return True
225 225 except VCSError:
226 226 return False
227 227
228 228
229 229 def is_valid_repos_group(repos_group_name, base_path):
230 230 """
231 231 Returns True if given path is a repos group False otherwise
232 232
233 233 :param repo_name:
234 234 :param base_path:
235 235 """
236 236 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
237 237
238 238 # check if it's not a repo
239 239 if is_valid_repo(repos_group_name, base_path):
240 240 return False
241 241
242 242 try:
243 243 # we need to check bare git repos at higher level
244 244 # since we might match branches/hooks/info/objects or possible
245 245 # other things inside bare git repo
246 246 get_scm(os.path.dirname(full_path))
247 247 return False
248 248 except VCSError:
249 249 pass
250 250
251 251 # check if it's a valid path
252 252 if os.path.isdir(full_path):
253 253 return True
254 254
255 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 259 while True:
260 260 ok = raw_input(prompt)
261 261 if ok in ('y', 'ye', 'yes'):
262 262 return True
263 263 if ok in ('n', 'no', 'nop', 'nope'):
264 264 return False
265 265 retries = retries - 1
266 266 if retries < 0:
267 267 raise IOError
268 268 print complaint
269 269
270 270 #propagated from mercurial documentation
271 271 ui_sections = ['alias', 'auth',
272 272 'decode/encode', 'defaults',
273 273 'diff', 'email',
274 274 'extensions', 'format',
275 275 'merge-patterns', 'merge-tools',
276 276 'hooks', 'http_proxy',
277 277 'smtp', 'patch',
278 278 'paths', 'profiling',
279 279 'server', 'trusted',
280 280 'ui', 'web', ]
281 281
282 282
283 283 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
284 284 """
285 285 A function that will read python rc files or database
286 286 and make an mercurial ui object from read options
287 287
288 288 :param path: path to mercurial config file
289 289 :param checkpaths: check the path
290 290 :param read_from: read from 'file' or 'db'
291 291 """
292 292
293 293 baseui = ui.ui()
294 294
295 295 # clean the baseui object
296 296 baseui._ocfg = config.config()
297 297 baseui._ucfg = config.config()
298 298 baseui._tcfg = config.config()
299 299
300 300 if read_from == 'file':
301 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 303 return False
304 304 log.debug('reading hgrc from %s' % path)
305 305 cfg = config.config()
306 306 cfg.read(path)
307 307 for section in ui_sections:
308 308 for k, v in cfg.items(section):
309 309 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
310 310 baseui.setconfig(section, k, v)
311 311
312 312 elif read_from == 'db':
313 313 sa = meta.Session()
314 314 ret = sa.query(RhodeCodeUi)\
315 315 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
316 316 .all()
317 317
318 318 hg_ui = ret
319 319 for ui_ in hg_ui:
320 320 if ui_.ui_active:
321 321 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
322 322 ui_.ui_key, ui_.ui_value)
323 323 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
324 324 if ui_.ui_key == 'push_ssl':
325 325 # force set push_ssl requirement to False, rhodecode
326 326 # handles that
327 327 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
328 328 if clear_session:
329 329 meta.Session.remove()
330 330 return baseui
331 331
332 332
333 333 def set_rhodecode_config(config):
334 334 """
335 335 Updates pylons config with new settings from database
336 336
337 337 :param config:
338 338 """
339 339 hgsettings = RhodeCodeSetting.get_app_settings()
340 340
341 341 for k, v in hgsettings.items():
342 342 config[k] = v
343 343
344 344
345 345 def invalidate_cache(cache_key, *args):
346 346 """
347 347 Puts cache invalidation task into db for
348 348 further global cache invalidation
349 349 """
350 350
351 351 from rhodecode.model.scm import ScmModel
352 352
353 353 if cache_key.startswith('get_repo_cached_'):
354 354 name = cache_key.split('get_repo_cached_')[-1]
355 355 ScmModel().mark_for_invalidation(name)
356 356
357 357
358 358 def map_groups(path):
359 359 """
360 360 Given a full path to a repository, create all nested groups that this
361 361 repo is inside. This function creates parent-child relationships between
362 362 groups and creates default perms for all new groups.
363 363
364 364 :param paths: full path to repository
365 365 """
366 366 sa = meta.Session()
367 367 groups = path.split(Repository.url_sep())
368 368 parent = None
369 369 group = None
370 370
371 371 # last element is repo in nested groups structure
372 372 groups = groups[:-1]
373 373 rgm = ReposGroupModel(sa)
374 374 for lvl, group_name in enumerate(groups):
375 375 group_name = '/'.join(groups[:lvl] + [group_name])
376 376 group = RepoGroup.get_by_group_name(group_name)
377 377 desc = '%s group' % group_name
378 378
379 379 # skip folders that are now removed repos
380 380 if REMOVED_REPO_PAT.match(group_name):
381 381 break
382 382
383 383 if group is None:
384 384 log.debug('creating group level: %s group_name: %s' % (lvl,
385 385 group_name))
386 386 group = RepoGroup(group_name, parent)
387 387 group.group_description = desc
388 388 sa.add(group)
389 389 rgm._create_default_perms(group)
390 390 sa.flush()
391 391 parent = group
392 392 return group
393 393
394 394
395 395 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
396 396 install_git_hook=False):
397 397 """
398 398 maps all repos given in initial_repo_list, non existing repositories
399 399 are created, if remove_obsolete is True it also check for db entries
400 400 that are not in initial_repo_list and removes them.
401 401
402 402 :param initial_repo_list: list of repositories found by scanning methods
403 403 :param remove_obsolete: check for obsolete entries in database
404 404 :param install_git_hook: if this is True, also check and install githook
405 405 for a repo if missing
406 406 """
407 407 from rhodecode.model.repo import RepoModel
408 408 from rhodecode.model.scm import ScmModel
409 409 sa = meta.Session()
410 410 rm = RepoModel()
411 411 user = sa.query(User).filter(User.admin == True).first()
412 412 if user is None:
413 raise Exception('Missing administrative account !')
413 raise Exception('Missing administrative account!')
414 414 added = []
415 415
416 416 # # clear cache keys
417 417 # log.debug("Clearing cache keys now...")
418 418 # CacheInvalidation.clear_cache()
419 419 # sa.commit()
420 420
421 421 for name, repo in initial_repo_list.items():
422 422 group = map_groups(name)
423 423 db_repo = rm.get_by_repo_name(name)
424 424 # found repo that is on filesystem not in RhodeCode database
425 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 427 added.append(name)
428 428 desc = (repo.description
429 429 if repo.description != 'unknown'
430 430 else '%s repository' % name)
431 431 new_repo = rm.create_repo(
432 432 repo_name=name,
433 433 repo_type=repo.alias,
434 434 description=desc,
435 435 repos_group=getattr(group, 'group_id', None),
436 436 owner=user,
437 437 just_db=True
438 438 )
439 439 # we added that repo just now, and make sure it has githook
440 440 # installed
441 441 if new_repo.repo_type == 'git':
442 442 ScmModel().install_git_hook(new_repo.scm_instance)
443 443 elif install_git_hook:
444 444 if db_repo.repo_type == 'git':
445 445 ScmModel().install_git_hook(db_repo.scm_instance)
446 446 # during starting install all cache keys for all repositories in the
447 447 # system, this will register all repos and multiple instances
448 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 450 CacheInvalidation._get_or_create_key(key, _prefix, _org_key, commit=False)
451 451 sa.commit()
452 452 removed = []
453 453 if remove_obsolete:
454 454 # remove from database those repositories that are not in the filesystem
455 455 for repo in sa.query(Repository).all():
456 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 458 repo.repo_name)
459 459 try:
460 460 sa.delete(repo)
461 461 sa.commit()
462 462 removed.append(repo.repo_name)
463 463 except:
464 464 #don't hold further removals on error
465 465 log.error(traceback.format_exc())
466 466 sa.rollback()
467 467
468 468 return added, removed
469 469
470 470
471 471 # set cache regions for beaker so celery can utilise it
472 472 def add_cache(settings):
473 473 cache_settings = {'regions': None}
474 474 for key in settings.keys():
475 475 for prefix in ['beaker.cache.', 'cache.']:
476 476 if key.startswith(prefix):
477 477 name = key.split(prefix)[1].strip()
478 478 cache_settings[name] = settings[key].strip()
479 479 if cache_settings['regions']:
480 480 for region in cache_settings['regions'].split(','):
481 481 region = region.strip()
482 482 region_settings = {}
483 483 for key, value in cache_settings.items():
484 484 if key.startswith(region):
485 485 region_settings[key.split('.')[1]] = value
486 486 region_settings['expire'] = int(region_settings.get('expire',
487 487 60))
488 488 region_settings.setdefault('lock_dir',
489 489 cache_settings.get('lock_dir'))
490 490 region_settings.setdefault('data_dir',
491 491 cache_settings.get('data_dir'))
492 492
493 493 if 'type' not in region_settings:
494 494 region_settings['type'] = cache_settings.get('type',
495 495 'memory')
496 496 beaker.cache.cache_regions[region] = region_settings
497 497
498 498
499 499 def load_rcextensions(root_path):
500 500 import rhodecode
501 501 from rhodecode.config import conf
502 502
503 503 path = os.path.join(root_path, 'rcextensions', '__init__.py')
504 504 if os.path.isfile(path):
505 505 rcext = create_module('rc', path)
506 506 EXT = rhodecode.EXTENSIONS = rcext
507 507 log.debug('Found rcextensions now loading %s...' % rcext)
508 508
509 509 # Additional mappings that are not present in the pygments lexers
510 510 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
511 511
512 512 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
513 513
514 514 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
515 515 log.debug('settings custom INDEX_EXTENSIONS')
516 516 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
517 517
518 518 #ADDITIONAL MAPPINGS
519 519 log.debug('adding extra into INDEX_EXTENSIONS')
520 520 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
521 521
522 522
523 523 #==============================================================================
524 524 # TEST FUNCTIONS AND CREATORS
525 525 #==============================================================================
526 526 def create_test_index(repo_location, config, full_index):
527 527 """
528 528 Makes default test index
529 529
530 530 :param config: test config
531 531 :param full_index:
532 532 """
533 533
534 534 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
535 535 from rhodecode.lib.pidlock import DaemonLock, LockHeld
536 536
537 537 repo_location = repo_location
538 538
539 539 index_location = os.path.join(config['app_conf']['index_dir'])
540 540 if not os.path.exists(index_location):
541 541 os.makedirs(index_location)
542 542
543 543 try:
544 544 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
545 545 WhooshIndexingDaemon(index_location=index_location,
546 546 repo_location=repo_location)\
547 547 .run(full_index=full_index)
548 548 l.release()
549 549 except LockHeld:
550 550 pass
551 551
552 552
553 553 def create_test_env(repos_test_path, config):
554 554 """
555 555 Makes a fresh database and
556 556 install test repository into tmp dir
557 557 """
558 558 from rhodecode.lib.db_manage import DbManage
559 559 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
560 560
561 561 # PART ONE create db
562 562 dbconf = config['sqlalchemy.db1.url']
563 563 log.debug('making test db %s' % dbconf)
564 564
565 565 # create test dir if it doesn't exist
566 566 if not os.path.isdir(repos_test_path):
567 567 log.debug('Creating testdir %s' % repos_test_path)
568 568 os.makedirs(repos_test_path)
569 569
570 570 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
571 571 tests=True)
572 572 dbmanage.create_tables(override=True)
573 573 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
574 574 dbmanage.create_default_user()
575 575 dbmanage.admin_prompt()
576 576 dbmanage.create_permissions()
577 577 dbmanage.populate_default_permissions()
578 578 Session().commit()
579 579 # PART TWO make test repo
580 580 log.debug('making test vcs repositories')
581 581
582 582 idx_path = config['app_conf']['index_dir']
583 583 data_path = config['app_conf']['cache_dir']
584 584
585 585 #clean index and data
586 586 if idx_path and os.path.exists(idx_path):
587 587 log.debug('remove %s' % idx_path)
588 588 shutil.rmtree(idx_path)
589 589
590 590 if data_path and os.path.exists(data_path):
591 591 log.debug('remove %s' % data_path)
592 592 shutil.rmtree(data_path)
593 593
594 594 #CREATE DEFAULT TEST REPOS
595 595 cur_dir = dn(dn(abspath(__file__)))
596 596 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
597 597 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
598 598 tar.close()
599 599
600 600 cur_dir = dn(dn(abspath(__file__)))
601 601 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
602 602 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
603 603 tar.close()
604 604
605 605 #LOAD VCS test stuff
606 606 from rhodecode.tests.vcs import setup_package
607 607 setup_package()
608 608
609 609
610 610 #==============================================================================
611 611 # PASTER COMMANDS
612 612 #==============================================================================
613 613 class BasePasterCommand(Command):
614 614 """
615 615 Abstract Base Class for paster commands.
616 616
617 617 The celery commands are somewhat aggressive about loading
618 618 celery.conf, and since our module sets the `CELERY_LOADER`
619 619 environment variable to our loader, we have to bootstrap a bit and
620 620 make sure we've had a chance to load the pylons config off of the
621 621 command line, otherwise everything fails.
622 622 """
623 623 min_args = 1
624 624 min_args_error = "Please provide a paster config file as an argument."
625 625 takes_config_file = 1
626 626 requires_config_file = True
627 627
628 628 def notify_msg(self, msg, log=False):
629 629 """Make a notification to user, additionally if logger is passed
630 630 it logs this action using given logger
631 631
632 632 :param msg: message that will be printed to user
633 633 :param log: logging instance, to use to additionally log this message
634 634
635 635 """
636 636 if log and isinstance(log, logging):
637 637 log(msg)
638 638
639 639 def run(self, args):
640 640 """
641 641 Overrides Command.run
642 642
643 643 Checks for a config file argument and loads it.
644 644 """
645 645 if len(args) < self.min_args:
646 646 raise BadCommand(
647 647 self.min_args_error % {'min_args': self.min_args,
648 648 'actual_args': len(args)})
649 649
650 650 # Decrement because we're going to lob off the first argument.
651 651 # @@ This is hacky
652 652 self.min_args -= 1
653 653 self.bootstrap_config(args[0])
654 654 self.update_parser()
655 655 return super(BasePasterCommand, self).run(args[1:])
656 656
657 657 def update_parser(self):
658 658 """
659 659 Abstract method. Allows for the class's parser to be updated
660 660 before the superclass's `run` method is called. Necessary to
661 661 allow options/arguments to be passed through to the underlying
662 662 celery command.
663 663 """
664 664 raise NotImplementedError("Abstract Method.")
665 665
666 666 def bootstrap_config(self, conf):
667 667 """
668 668 Loads the pylons configuration.
669 669 """
670 670 from pylons import config as pylonsconfig
671 671
672 672 self.path_to_ini_file = os.path.realpath(conf)
673 673 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
674 674 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
675 675
676 676
677 677 def check_git_version():
678 678 """
679 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 682 import subprocess
683 683 from distutils.version import StrictVersion
684 684 from rhodecode import BACKENDS
685 685
686 686 p = subprocess.Popen('git --version', shell=True,
687 687 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
688 688 stdout, stderr = p.communicate()
689 689 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
690 690 try:
691 691 _ver = StrictVersion(ver)
692 692 except:
693 693 _ver = StrictVersion('0.0.0')
694 694 stderr = traceback.format_exc()
695 695
696 696 req_ver = '1.7.4'
697 697 to_old_git = False
698 698 if _ver <= StrictVersion(req_ver):
699 699 to_old_git = True
700 700
701 701 if 'git' in BACKENDS:
702 702 log.debug('GIT version detected: %s' % stdout)
703 703 if stderr:
704 704 log.warning('Unable to detect git version org error was:%r' % stderr)
705 705 elif to_old_git:
706 log.warning('RhodeCode detected git version %s, which is to old '
707 'for the system to function properly make sure '
708 'it is at least in version %s' % (ver, req_ver))
709 return _ver No newline at end of file
706 log.warning('RhodeCode detected git version %s, which is too old '
707 'for the system to function properly. Make sure '
708 'its version is at least %s' % (ver, req_ver))
709 return _ver
@@ -1,507 +1,518 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import re
27 27 import time
28 28 import datetime
29 29 from pylons.i18n.translation import _, ungettext
30 30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31 31
32 32
33 33 def __get_lem():
34 34 """
35 35 Get language extension map based on what's inside pygments lexers
36 36 """
37 37 from pygments import lexers
38 38 from string import lower
39 39 from collections import defaultdict
40 40
41 41 d = defaultdict(lambda: [])
42 42
43 43 def __clean(s):
44 44 s = s.lstrip('*')
45 45 s = s.lstrip('.')
46 46
47 47 if s.find('[') != -1:
48 48 exts = []
49 49 start, stop = s.find('['), s.find(']')
50 50
51 51 for suffix in s[start + 1:stop]:
52 52 exts.append(s[:s.find('[')] + suffix)
53 53 return map(lower, exts)
54 54 else:
55 55 return map(lower, [s])
56 56
57 57 for lx, t in sorted(lexers.LEXERS.items()):
58 58 m = map(__clean, t[-2])
59 59 if m:
60 60 m = reduce(lambda x, y: x + y, m)
61 61 for ext in m:
62 62 desc = lx.replace('Lexer', '')
63 63 d[ext].append(desc)
64 64
65 65 return dict(d)
66 66
67 67 def str2bool(_str):
68 68 """
69 69 returs True/False value from given string, it tries to translate the
70 70 string into boolean
71 71
72 72 :param _str: string value to translate into boolean
73 73 :rtype: boolean
74 74 :returns: boolean from given string
75 75 """
76 76 if _str is None:
77 77 return False
78 78 if _str in (True, False):
79 79 return _str
80 80 _str = str(_str).strip().lower()
81 81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
82 82
83 83
84 84 def convert_line_endings(line, mode):
85 85 """
86 86 Converts a given line "line end" accordingly to given mode
87 87
88 88 Available modes are::
89 89 0 - Unix
90 90 1 - Mac
91 91 2 - DOS
92 92
93 93 :param line: given line to convert
94 94 :param mode: mode to convert to
95 95 :rtype: str
96 96 :return: converted line according to mode
97 97 """
98 98 from string import replace
99 99
100 100 if mode == 0:
101 101 line = replace(line, '\r\n', '\n')
102 102 line = replace(line, '\r', '\n')
103 103 elif mode == 1:
104 104 line = replace(line, '\r\n', '\r')
105 105 line = replace(line, '\n', '\r')
106 106 elif mode == 2:
107 107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
108 108 return line
109 109
110 110
111 111 def detect_mode(line, default):
112 112 """
113 113 Detects line break for given line, if line break couldn't be found
114 114 given default value is returned
115 115
116 116 :param line: str line
117 117 :param default: default
118 118 :rtype: int
119 119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
120 120 """
121 121 if line.endswith('\r\n'):
122 122 return 2
123 123 elif line.endswith('\n'):
124 124 return 0
125 125 elif line.endswith('\r'):
126 126 return 1
127 127 else:
128 128 return default
129 129
130 130
131 131 def generate_api_key(username, salt=None):
132 132 """
133 133 Generates unique API key for given username, if salt is not given
134 134 it'll be generated from some random string
135 135
136 136 :param username: username as string
137 137 :param salt: salt to hash generate KEY
138 138 :rtype: str
139 139 :returns: sha1 hash from username+salt
140 140 """
141 141 from tempfile import _RandomNameSequence
142 142 import hashlib
143 143
144 144 if salt is None:
145 145 salt = _RandomNameSequence().next()
146 146
147 147 return hashlib.sha1(username + salt).hexdigest()
148 148
149 149
150 150 def safe_int(val, default=None):
151 151 """
152 152 Returns int() of val if val is not convertable to int use default
153 153 instead
154 154
155 155 :param val:
156 156 :param default:
157 157 """
158 158
159 159 try:
160 160 val = int(val)
161 161 except ValueError:
162 162 val = default
163 163
164 164 return val
165 165
166 166
167 167 def safe_unicode(str_, from_encoding=None):
168 168 """
169 169 safe unicode function. Does few trick to turn str_ into unicode
170 170
171 171 In case of UnicodeDecode error we try to return it with encoding detected
172 172 by chardet library if it fails fallback to unicode with errors replaced
173 173
174 174 :param str_: string to decode
175 175 :rtype: unicode
176 176 :returns: unicode object
177 177 """
178 178 if isinstance(str_, unicode):
179 179 return str_
180 180
181 181 if not from_encoding:
182 182 import rhodecode
183 183 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
184 184 from_encoding = DEFAULT_ENCODING
185 185
186 186 try:
187 187 return unicode(str_)
188 188 except UnicodeDecodeError:
189 189 pass
190 190
191 191 try:
192 192 return unicode(str_, from_encoding)
193 193 except UnicodeDecodeError:
194 194 pass
195 195
196 196 try:
197 197 import chardet
198 198 encoding = chardet.detect(str_)['encoding']
199 199 if encoding is None:
200 200 raise Exception()
201 201 return str_.decode(encoding)
202 202 except (ImportError, UnicodeDecodeError, Exception):
203 203 return unicode(str_, from_encoding, 'replace')
204 204
205 205
206 206 def safe_str(unicode_, to_encoding=None):
207 207 """
208 208 safe str function. Does few trick to turn unicode_ into string
209 209
210 210 In case of UnicodeEncodeError we try to return it with encoding detected
211 211 by chardet library if it fails fallback to string with errors replaced
212 212
213 213 :param unicode_: unicode to encode
214 214 :rtype: str
215 215 :returns: str object
216 216 """
217 217
218 218 # if it's not basestr cast to str
219 219 if not isinstance(unicode_, basestring):
220 220 return str(unicode_)
221 221
222 222 if isinstance(unicode_, str):
223 223 return unicode_
224 224
225 225 if not to_encoding:
226 226 import rhodecode
227 227 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
228 228 to_encoding = DEFAULT_ENCODING
229 229
230 230 try:
231 231 return unicode_.encode(to_encoding)
232 232 except UnicodeEncodeError:
233 233 pass
234 234
235 235 try:
236 236 import chardet
237 237 encoding = chardet.detect(unicode_)['encoding']
238 238 if encoding is None:
239 239 raise UnicodeEncodeError()
240 240
241 241 return unicode_.encode(encoding)
242 242 except (ImportError, UnicodeEncodeError):
243 243 return unicode_.encode(to_encoding, 'replace')
244 244
245 245 return safe_str
246 246
247 247
248 248 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
249 249 """
250 250 Custom engine_from_config functions that makes sure we use NullPool for
251 251 file based sqlite databases. This prevents errors on sqlite. This only
252 252 applies to sqlalchemy versions < 0.7.0
253 253
254 254 """
255 255 import sqlalchemy
256 256 from sqlalchemy import engine_from_config as efc
257 257 import logging
258 258
259 259 if int(sqlalchemy.__version__.split('.')[1]) < 7:
260 260
261 261 # This solution should work for sqlalchemy < 0.7.0, and should use
262 262 # proxy=TimerProxy() for execution time profiling
263 263
264 264 from sqlalchemy.pool import NullPool
265 265 url = configuration[prefix + 'url']
266 266
267 267 if url.startswith('sqlite'):
268 268 kwargs.update({'poolclass': NullPool})
269 269 return efc(configuration, prefix, **kwargs)
270 270 else:
271 271 import time
272 272 from sqlalchemy import event
273 273 from sqlalchemy.engine import Engine
274 274
275 275 log = logging.getLogger('sqlalchemy.engine')
276 276 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
277 277 engine = efc(configuration, prefix, **kwargs)
278 278
279 279 def color_sql(sql):
280 280 COLOR_SEQ = "\033[1;%dm"
281 281 COLOR_SQL = YELLOW
282 282 normal = '\x1b[0m'
283 283 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
284 284
285 285 if configuration['debug']:
286 286 #attach events only for debug configuration
287 287
288 288 def before_cursor_execute(conn, cursor, statement,
289 289 parameters, context, executemany):
290 290 context._query_start_time = time.time()
291 291 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
292 292
293 293 def after_cursor_execute(conn, cursor, statement,
294 294 parameters, context, executemany):
295 295 total = time.time() - context._query_start_time
296 296 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
297 297
298 298 event.listen(engine, "before_cursor_execute",
299 299 before_cursor_execute)
300 300 event.listen(engine, "after_cursor_execute",
301 301 after_cursor_execute)
302 302
303 303 return engine
304 304
305 305
306 306 def age(prevdate):
307 307 """
308 308 turns a datetime into an age string.
309 309
310 310 :param prevdate: datetime object
311 311 :rtype: unicode
312 312 :returns: unicode words describing age
313 313 """
314 314
315 315 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
316 316 deltas = {}
317 future = False
317 318
318 319 # Get date parts deltas
319 320 now = datetime.datetime.now()
321 if prevdate > now:
322 now, prevdate = prevdate, now
323 future = True
324
320 325 for part in order:
321 326 deltas[part] = getattr(now, part) - getattr(prevdate, part)
322 327
323 328 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
324 329 # not 1 hour, -59 minutes and -59 seconds)
325 330
326 331 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
327 332 part = order[num]
328 333 carry_part = order[num - 1]
329 334
330 335 if deltas[part] < 0:
331 336 deltas[part] += length
332 337 deltas[carry_part] -= 1
333 338
334 339 # Same thing for days except that the increment depends on the (variable)
335 340 # number of days in the month
336 341 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
337 342 if deltas['day'] < 0:
338 343 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
339 344 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
340 345 deltas['day'] += 29
341 346 else:
342 347 deltas['day'] += month_lengths[prevdate.month - 1]
343 348
344 349 deltas['month'] -= 1
345 350
346 351 if deltas['month'] < 0:
347 352 deltas['month'] += 12
348 353 deltas['year'] -= 1
349 354
350 355 # Format the result
351 356 fmt_funcs = {
352 357 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
353 358 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
354 359 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
355 360 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
356 361 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
357 362 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
358 363 }
359 364
360 365 for i, part in enumerate(order):
361 366 value = deltas[part]
362 367 if value == 0:
363 368 continue
364 369
365 370 if i < 5:
366 371 sub_part = order[i + 1]
367 372 sub_value = deltas[sub_part]
368 373 else:
369 374 sub_value = 0
370 375
371 376 if sub_value == 0:
372 return _(u'%s ago') % fmt_funcs[part](value)
373
374 return _(u'%s and %s ago') % (fmt_funcs[part](value),
375 fmt_funcs[sub_part](sub_value))
377 if future:
378 return _(u'in %s') % fmt_funcs[part](value)
379 else:
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 388 return _(u'just now')
378 389
379 390
380 391 def uri_filter(uri):
381 392 """
382 393 Removes user:password from given url string
383 394
384 395 :param uri:
385 396 :rtype: unicode
386 397 :returns: filtered list of strings
387 398 """
388 399 if not uri:
389 400 return ''
390 401
391 402 proto = ''
392 403
393 404 for pat in ('https://', 'http://'):
394 405 if uri.startswith(pat):
395 406 uri = uri[len(pat):]
396 407 proto = pat
397 408 break
398 409
399 410 # remove passwords and username
400 411 uri = uri[uri.find('@') + 1:]
401 412
402 413 # get the port
403 414 cred_pos = uri.find(':')
404 415 if cred_pos == -1:
405 416 host, port = uri, None
406 417 else:
407 418 host, port = uri[:cred_pos], uri[cred_pos + 1:]
408 419
409 420 return filter(None, [proto, host, port])
410 421
411 422
412 423 def credentials_filter(uri):
413 424 """
414 425 Returns a url with removed credentials
415 426
416 427 :param uri:
417 428 """
418 429
419 430 uri = uri_filter(uri)
420 431 #check if we have port
421 432 if len(uri) > 2 and uri[2]:
422 433 uri[2] = ':' + uri[2]
423 434
424 435 return ''.join(uri)
425 436
426 437
427 438 def get_changeset_safe(repo, rev):
428 439 """
429 440 Safe version of get_changeset if this changeset doesn't exists for a
430 441 repo it returns a Dummy one instead
431 442
432 443 :param repo:
433 444 :param rev:
434 445 """
435 446 from rhodecode.lib.vcs.backends.base import BaseRepository
436 447 from rhodecode.lib.vcs.exceptions import RepositoryError
437 448 from rhodecode.lib.vcs.backends.base import EmptyChangeset
438 449 if not isinstance(repo, BaseRepository):
439 450 raise Exception('You must pass an Repository '
440 451 'object as first argument got %s', type(repo))
441 452
442 453 try:
443 454 cs = repo.get_changeset(rev)
444 455 except RepositoryError:
445 456 cs = EmptyChangeset(requested_revision=rev)
446 457 return cs
447 458
448 459
449 460 def datetime_to_time(dt):
450 461 if dt:
451 462 return time.mktime(dt.timetuple())
452 463
453 464
454 465 def time_to_datetime(tm):
455 466 if tm:
456 467 if isinstance(tm, basestring):
457 468 try:
458 469 tm = float(tm)
459 470 except ValueError:
460 471 return
461 472 return datetime.datetime.fromtimestamp(tm)
462 473
463 474 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
464 475
465 476
466 477 def extract_mentioned_users(s):
467 478 """
468 479 Returns unique usernames from given string s that have @mention
469 480
470 481 :param s: string to get mentions
471 482 """
472 483 usrs = set()
473 484 for username in re.findall(MENTIONS_REGEX, s):
474 485 usrs.add(username)
475 486
476 487 return sorted(list(usrs), key=lambda k: k.lower())
477 488
478 489
479 490 class AttributeDict(dict):
480 491 def __getattr__(self, attr):
481 492 return self.get(attr, None)
482 493 __setattr__ = dict.__setitem__
483 494 __delattr__ = dict.__delitem__
484 495
485 496
486 497 def fix_PATH(os_=None):
487 498 """
488 499 Get current active python path, and append it to PATH variable to fix issues
489 500 of subprocess calls and different python versions
490 501 """
491 502 import sys
492 503 if os_ is None:
493 504 import os
494 505 else:
495 506 os = os_
496 507
497 508 cur_path = os.path.split(sys.executable)[0]
498 509 if not os.environ['PATH'].startswith(cur_path):
499 510 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
500 511
501 512
502 513 def obfuscate_url_pw(engine):
503 514 from sqlalchemy.engine import url
504 515 url = url.make_url(engine)
505 516 if url.password:
506 517 url.password = 'XXXXX'
507 return str(url) No newline at end of file
518 return str(url)
@@ -1,544 +1,548 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 from __future__ import with_statement
26 26 import os
27 27 import shutil
28 28 import logging
29 29 import traceback
30 30 from datetime import datetime
31 31
32 32 from rhodecode.lib.vcs.backends import get_backend
33 33 from rhodecode.lib.compat import json
34 34 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode
35 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 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
40 40 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
41 41 from rhodecode.lib import helpers as h
42 42
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class RepoModel(BaseModel):
48 48
49 49 cls = Repository
50 50 URL_SEPARATOR = Repository.url_sep()
51 51
52 52 def __get_users_group(self, users_group):
53 53 return self._get_instance(UsersGroup, users_group,
54 54 callback=UsersGroup.get_by_group_name)
55 55
56 56 def _get_repos_group(self, repos_group):
57 57 return self._get_instance(RepoGroup, repos_group,
58 58 callback=RepoGroup.get_by_group_name)
59 59
60 60 @LazyProperty
61 61 def repos_path(self):
62 62 """
63 63 Get's the repositories root path from database
64 64 """
65 65
66 66 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
67 67 return q.ui_value
68 68
69 69 def get(self, repo_id, cache=False):
70 70 repo = self.sa.query(Repository)\
71 71 .filter(Repository.repo_id == repo_id)
72 72
73 73 if cache:
74 74 repo = repo.options(FromCache("sql_cache_short",
75 75 "get_repo_%s" % repo_id))
76 76 return repo.scalar()
77 77
78 78 def get_repo(self, repository):
79 79 return self._get_repo(repository)
80 80
81 81 def get_by_repo_name(self, repo_name, cache=False):
82 82 repo = self.sa.query(Repository)\
83 83 .filter(Repository.repo_name == repo_name)
84 84
85 85 if cache:
86 86 repo = repo.options(FromCache("sql_cache_short",
87 87 "get_repo_%s" % repo_name))
88 88 return repo.scalar()
89 89
90 90 def get_users_js(self):
91 91 users = self.sa.query(User).filter(User.active == True).all()
92 92 return json.dumps([
93 93 {
94 94 'id': u.user_id,
95 95 'fname': u.name,
96 96 'lname': u.lastname,
97 97 'nname': u.username,
98 98 'gravatar_lnk': h.gravatar_url(u.email, 14)
99 99 } for u in users]
100 100 )
101 101
102 102 def get_users_groups_js(self):
103 103 users_groups = self.sa.query(UsersGroup)\
104 104 .filter(UsersGroup.users_group_active == True).all()
105 105
106 106 return json.dumps([
107 107 {
108 108 'id': gr.users_group_id,
109 109 'grname': gr.users_group_name,
110 110 'grmembers': len(gr.members),
111 111 } for gr in users_groups]
112 112 )
113 113
114 114 def _get_defaults(self, repo_name):
115 115 """
116 116 Get's information about repository, and returns a dict for
117 117 usage in forms
118 118
119 119 :param repo_name:
120 120 """
121 121
122 122 repo_info = Repository.get_by_repo_name(repo_name)
123 123
124 124 if repo_info is None:
125 125 return None
126 126
127 127 defaults = repo_info.get_dict()
128 128 group, repo_name = repo_info.groups_and_repo
129 129 defaults['repo_name'] = repo_name
130 130 defaults['repo_group'] = getattr(group[-1] if group else None,
131 131 'group_id', None)
132 132
133 133 # fill owner
134 134 if repo_info.user:
135 135 defaults.update({'user': repo_info.user.username})
136 136 else:
137 137 replacement_user = User.query().filter(User.admin ==
138 138 True).first().username
139 139 defaults.update({'user': replacement_user})
140 140
141 141 # fill repository users
142 142 for p in repo_info.repo_to_perm:
143 143 defaults.update({'u_perm_%s' % p.user.username:
144 144 p.permission.permission_name})
145 145
146 146 # fill repository groups
147 147 for p in repo_info.users_group_to_perm:
148 148 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
149 149 p.permission.permission_name})
150 150
151 151 return defaults
152 152
153 153 def update(self, repo_name, form_data):
154 154 try:
155 155 cur_repo = self.get_by_repo_name(repo_name, cache=False)
156 156
157 157 # update permissions
158 158 for member, perm, member_type in form_data['perms_updates']:
159 159 if member_type == 'user':
160 160 # this updates existing one
161 161 RepoModel().grant_user_permission(
162 162 repo=cur_repo, user=member, perm=perm
163 163 )
164 164 else:
165 165 RepoModel().grant_users_group_permission(
166 166 repo=cur_repo, group_name=member, perm=perm
167 167 )
168 168 # set new permissions
169 169 for member, perm, member_type in form_data['perms_new']:
170 170 if member_type == 'user':
171 171 RepoModel().grant_user_permission(
172 172 repo=cur_repo, user=member, perm=perm
173 173 )
174 174 else:
175 175 RepoModel().grant_users_group_permission(
176 176 repo=cur_repo, group_name=member, perm=perm
177 177 )
178 178
179 179 # update current repo
180 180 for k, v in form_data.items():
181 181 if k == 'user':
182 182 cur_repo.user = User.get_by_username(v)
183 183 elif k == 'repo_name':
184 184 pass
185 185 elif k == 'repo_group':
186 186 cur_repo.group = RepoGroup.get(v)
187 187
188 188 else:
189 189 setattr(cur_repo, k, v)
190 190
191 191 new_name = cur_repo.get_new_name(form_data['repo_name'])
192 192 cur_repo.repo_name = new_name
193 193
194 194 self.sa.add(cur_repo)
195 195
196 196 if repo_name != new_name:
197 197 # rename repository
198 198 self.__rename_repo(old=repo_name, new=new_name)
199 199
200 200 return cur_repo
201 201 except:
202 202 log.error(traceback.format_exc())
203 203 raise
204 204
205 205 def create_repo(self, repo_name, repo_type, description, owner,
206 206 private=False, clone_uri=None, repos_group=None,
207 207 landing_rev='tip', just_db=False, fork_of=None,
208 208 copy_fork_permissions=False):
209 209 """
210 210 Create repository
211 211
212 212 """
213 213 from rhodecode.model.scm import ScmModel
214 214
215 215 owner = self._get_user(owner)
216 216 fork_of = self._get_repo(fork_of)
217 217 repos_group = self._get_repos_group(repos_group)
218 218 try:
219 219
220 220 # repo name is just a name of repository
221 221 # while repo_name_full is a full qualified name that is combined
222 222 # with name and path of group
223 223 repo_name_full = repo_name
224 224 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
225 225
226 226 new_repo = Repository()
227 227 new_repo.enable_statistics = False
228 228 new_repo.repo_name = repo_name_full
229 229 new_repo.repo_type = repo_type
230 230 new_repo.user = owner
231 231 new_repo.group = repos_group
232 232 new_repo.description = description or repo_name
233 233 new_repo.private = private
234 234 new_repo.clone_uri = clone_uri
235 235 new_repo.landing_rev = landing_rev
236 236
237 237 if repos_group:
238 238 new_repo.enable_locking = repos_group.enable_locking
239 239
240 240 if fork_of:
241 241 parent_repo = fork_of
242 242 new_repo.fork = parent_repo
243 243
244 244 self.sa.add(new_repo)
245 245
246 246 def _create_default_perms():
247 247 # create default permission
248 248 repo_to_perm = UserRepoToPerm()
249 249 default = 'repository.read'
250 250 for p in User.get_by_username('default').user_perms:
251 251 if p.permission.permission_name.startswith('repository.'):
252 252 default = p.permission.permission_name
253 253 break
254 254
255 255 default_perm = 'repository.none' if private else default
256 256
257 257 repo_to_perm.permission_id = self.sa.query(Permission)\
258 258 .filter(Permission.permission_name == default_perm)\
259 259 .one().permission_id
260 260
261 261 repo_to_perm.repository = new_repo
262 262 repo_to_perm.user_id = User.get_by_username('default').user_id
263 263
264 264 self.sa.add(repo_to_perm)
265 265
266 266 if fork_of:
267 267 if copy_fork_permissions:
268 268 repo = fork_of
269 269 user_perms = UserRepoToPerm.query()\
270 270 .filter(UserRepoToPerm.repository == repo).all()
271 271 group_perms = UsersGroupRepoToPerm.query()\
272 272 .filter(UsersGroupRepoToPerm.repository == repo).all()
273 273
274 274 for perm in user_perms:
275 275 UserRepoToPerm.create(perm.user, new_repo,
276 276 perm.permission)
277 277
278 278 for perm in group_perms:
279 279 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
280 280 perm.permission)
281 281 else:
282 282 _create_default_perms()
283 283 else:
284 284 _create_default_perms()
285 285
286 286 if not just_db:
287 287 self.__create_repo(repo_name, repo_type,
288 288 repos_group,
289 289 clone_uri)
290 290 log_create_repository(new_repo.get_dict(),
291 291 created_by=owner.username)
292 292
293 293 # now automatically start following this repository as owner
294 294 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
295 295 owner.user_id)
296 296 return new_repo
297 297 except:
298 298 log.error(traceback.format_exc())
299 299 raise
300 300
301 301 def create(self, form_data, cur_user, just_db=False, fork=None):
302 302 """
303 303 Backward compatibility function, just a wrapper on top of create_repo
304 304
305 305 :param form_data:
306 306 :param cur_user:
307 307 :param just_db:
308 308 :param fork:
309 309 """
310 310
311 311 repo_name = form_data['repo_name_full']
312 312 repo_type = form_data['repo_type']
313 313 description = form_data['description']
314 314 owner = cur_user
315 315 private = form_data['private']
316 316 clone_uri = form_data.get('clone_uri')
317 317 repos_group = form_data['repo_group']
318 318 landing_rev = form_data['landing_rev']
319 319 copy_fork_permissions = form_data.get('copy_permissions')
320 320 fork_of = form_data.get('fork_parent_id')
321 321 return self.create_repo(
322 322 repo_name, repo_type, description, owner, private, clone_uri,
323 323 repos_group, landing_rev, just_db, fork_of, copy_fork_permissions
324 324 )
325 325
326 326 def create_fork(self, form_data, cur_user):
327 327 """
328 328 Simple wrapper into executing celery task for fork creation
329 329
330 330 :param form_data:
331 331 :param cur_user:
332 332 """
333 333 from rhodecode.lib.celerylib import tasks, run_task
334 334 run_task(tasks.create_repo_fork, form_data, cur_user)
335 335
336 336 def delete(self, repo):
337 337 repo = self._get_repo(repo)
338 338 if repo:
339 old_repo_dict = repo.get_dict()
340 owner = repo.user
339 341 try:
340 342 self.sa.delete(repo)
341 343 self.__delete_repo(repo)
344 log_delete_repository(old_repo_dict,
345 deleted_by=owner.username)
342 346 except:
343 347 log.error(traceback.format_exc())
344 348 raise
345 349
346 350 def grant_user_permission(self, repo, user, perm):
347 351 """
348 352 Grant permission for user on given repository, or update existing one
349 353 if found
350 354
351 355 :param repo: Instance of Repository, repository_id, or repository name
352 356 :param user: Instance of User, user_id or username
353 357 :param perm: Instance of Permission, or permission_name
354 358 """
355 359 user = self._get_user(user)
356 360 repo = self._get_repo(repo)
357 361 permission = self._get_perm(perm)
358 362
359 363 # check if we have that permission already
360 364 obj = self.sa.query(UserRepoToPerm)\
361 365 .filter(UserRepoToPerm.user == user)\
362 366 .filter(UserRepoToPerm.repository == repo)\
363 367 .scalar()
364 368 if obj is None:
365 369 # create new !
366 370 obj = UserRepoToPerm()
367 371 obj.repository = repo
368 372 obj.user = user
369 373 obj.permission = permission
370 374 self.sa.add(obj)
371 375 log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
372 376
373 377 def revoke_user_permission(self, repo, user):
374 378 """
375 379 Revoke permission for user on given repository
376 380
377 381 :param repo: Instance of Repository, repository_id, or repository name
378 382 :param user: Instance of User, user_id or username
379 383 """
380 384
381 385 user = self._get_user(user)
382 386 repo = self._get_repo(repo)
383 387
384 388 obj = self.sa.query(UserRepoToPerm)\
385 389 .filter(UserRepoToPerm.repository == repo)\
386 390 .filter(UserRepoToPerm.user == user)\
387 391 .scalar()
388 392 if obj:
389 393 self.sa.delete(obj)
390 394 log.debug('Revoked perm on %s on %s' % (repo, user))
391 395
392 396 def grant_users_group_permission(self, repo, group_name, perm):
393 397 """
394 398 Grant permission for users group on given repository, or update
395 399 existing one if found
396 400
397 401 :param repo: Instance of Repository, repository_id, or repository name
398 402 :param group_name: Instance of UserGroup, users_group_id,
399 403 or users group name
400 404 :param perm: Instance of Permission, or permission_name
401 405 """
402 406 repo = self._get_repo(repo)
403 407 group_name = self.__get_users_group(group_name)
404 408 permission = self._get_perm(perm)
405 409
406 410 # check if we have that permission already
407 411 obj = self.sa.query(UsersGroupRepoToPerm)\
408 412 .filter(UsersGroupRepoToPerm.users_group == group_name)\
409 413 .filter(UsersGroupRepoToPerm.repository == repo)\
410 414 .scalar()
411 415
412 416 if obj is None:
413 417 # create new
414 418 obj = UsersGroupRepoToPerm()
415 419
416 420 obj.repository = repo
417 421 obj.users_group = group_name
418 422 obj.permission = permission
419 423 self.sa.add(obj)
420 424 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
421 425
422 426 def revoke_users_group_permission(self, repo, group_name):
423 427 """
424 428 Revoke permission for users group on given repository
425 429
426 430 :param repo: Instance of Repository, repository_id, or repository name
427 431 :param group_name: Instance of UserGroup, users_group_id,
428 432 or users group name
429 433 """
430 434 repo = self._get_repo(repo)
431 435 group_name = self.__get_users_group(group_name)
432 436
433 437 obj = self.sa.query(UsersGroupRepoToPerm)\
434 438 .filter(UsersGroupRepoToPerm.repository == repo)\
435 439 .filter(UsersGroupRepoToPerm.users_group == group_name)\
436 440 .scalar()
437 441 if obj:
438 442 self.sa.delete(obj)
439 443 log.debug('Revoked perm to %s on %s' % (repo, group_name))
440 444
441 445 def delete_stats(self, repo_name):
442 446 """
443 447 removes stats for given repo
444 448
445 449 :param repo_name:
446 450 """
447 451 try:
448 452 obj = self.sa.query(Statistics)\
449 453 .filter(Statistics.repository ==
450 454 self.get_by_repo_name(repo_name))\
451 455 .one()
452 456 self.sa.delete(obj)
453 457 except:
454 458 log.error(traceback.format_exc())
455 459 raise
456 460
457 461 def __create_repo(self, repo_name, alias, parent, clone_uri=False):
458 462 """
459 463 makes repository on filesystem. It's group aware means it'll create
460 464 a repository within a group, and alter the paths accordingly of
461 465 group location
462 466
463 467 :param repo_name:
464 468 :param alias:
465 469 :param parent_id:
466 470 :param clone_uri:
467 471 """
468 472 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
469 473 from rhodecode.model.scm import ScmModel
470 474
471 475 if parent:
472 476 new_parent_path = os.sep.join(parent.full_path_splitted)
473 477 else:
474 478 new_parent_path = ''
475 479
476 480 # we need to make it str for mercurial
477 481 repo_path = os.path.join(*map(lambda x: safe_str(x),
478 482 [self.repos_path, new_parent_path, repo_name]))
479 483
480 484 # check if this path is not a repository
481 485 if is_valid_repo(repo_path, self.repos_path):
482 486 raise Exception('This path %s is a valid repository' % repo_path)
483 487
484 488 # check if this path is a group
485 489 if is_valid_repos_group(repo_path, self.repos_path):
486 490 raise Exception('This path %s is a valid group' % repo_path)
487 491
488 492 log.info('creating repo %s in %s @ %s' % (
489 493 repo_name, safe_unicode(repo_path), clone_uri
490 494 )
491 495 )
492 496 backend = get_backend(alias)
493 497 if alias == 'hg':
494 498 backend(repo_path, create=True, src_url=clone_uri)
495 499 elif alias == 'git':
496 500 r = backend(repo_path, create=True, src_url=clone_uri, bare=True)
497 501 # add rhodecode hook into this repo
498 502 ScmModel().install_git_hook(repo=r)
499 503 else:
500 504 raise Exception('Undefined alias %s' % alias)
501 505
502 506 def __rename_repo(self, old, new):
503 507 """
504 508 renames repository on filesystem
505 509
506 510 :param old: old name
507 511 :param new: new name
508 512 """
509 513 log.info('renaming repo from %s to %s' % (old, new))
510 514
511 515 old_path = os.path.join(self.repos_path, old)
512 516 new_path = os.path.join(self.repos_path, new)
513 517 if os.path.isdir(new_path):
514 518 raise Exception(
515 519 'Was trying to rename to already existing dir %s' % new_path
516 520 )
517 521 shutil.move(old_path, new_path)
518 522
519 523 def __delete_repo(self, repo):
520 524 """
521 525 removes repo from filesystem, the removal is acctually made by
522 526 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
523 527 repository is no longer valid for rhodecode, can be undeleted later on
524 528 by reverting the renames on this repository
525 529
526 530 :param repo: repo object
527 531 """
528 532 rm_path = os.path.join(self.repos_path, repo.repo_name)
529 533 log.info("Removing %s" % (rm_path))
530 534 # disable hg/git internal that it doesn't get detected as repo
531 535 alias = repo.repo_type
532 536
533 537 bare = getattr(repo.scm_instance, 'bare', False)
534 538
535 539 if not bare:
536 540 # skip this for bare git repos
537 541 shutil.move(os.path.join(rm_path, '.%s' % alias),
538 542 os.path.join(rm_path, 'rm__.%s' % alias))
539 543 # disable repo
540 544 _now = datetime.now()
541 545 _ms = str(_now.microsecond).rjust(6, '0')
542 546 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
543 547 repo.repo_name)
544 548 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,246 +1,246 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${_('My Account')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17
18 18 <div class="box box-left">
19 19 <!-- box / title -->
20 20 <div class="title">
21 21 ${self.breadcrumbs()}
22 22 </div>
23 23 <!-- end box / title -->
24 24 ${c.form|n}
25 25 </div>
26 26
27 27 <div class="box box-right">
28 28 <!-- box / title -->
29 29 <div class="title">
30 30 <h5>
31 31 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}" style="display: none"/>
32 32 </h5>
33 33 <ul class="links" style="color:#DADADA">
34 34 <li>
35 35 <span><a id="show_perms" class="link-white current" href="#perms">${_('My permissions')}</a> </span>
36 36 </li>
37 37 <li>
38 38 <span><a id="show_my" class="link-white" href="#my">${_('My repos')}</a> </span>
39 39 </li>
40 40 <li>
41 41 <span><a id="show_pullrequests" class="link-white" href="#pullrequests">${_('My pull requests')}</a> </span>
42 42 </li>
43 43 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
44 44 <li>
45 45 <span>${h.link_to(_('Add repo'),h.url('admin_settings_create_repository'))}</span>
46 46 </li>
47 47 %endif
48 48 </ul>
49 49 </div>
50 50 <!-- end box / title -->
51 51 <div id="perms" class="table">
52 52 %for section in sorted(c.rhodecode_user.permissions.keys()):
53 53 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
54 54
55 55 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
56 56 <table id="tbl_list_${section}">
57 57 <thead>
58 58 <tr>
59 59 <th class="left">${_('Name')}</th>
60 60 <th class="left">${_('Permission')}</th>
61 61 </thead>
62 62 <tbody>
63 63 %for k in c.rhodecode_user.permissions[section]:
64 64 <%
65 65 if section != 'global':
66 66 section_perm = c.rhodecode_user.permissions[section].get(k)
67 67 _perm = section_perm.split('.')[-1]
68 68 else:
69 69 _perm = section_perm = None
70 70 %>
71 71 %if _perm not in ['none']:
72 72 <tr>
73 73 <td>
74 74 %if section == 'repositories':
75 75 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
76 76 %elif section == 'repositories_groups':
77 77 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
78 78 %else:
79 79 ${k}
80 80 %endif
81 81 </td>
82 82 <td>
83 83 %if section == 'global':
84 84 ${h.bool2icon(True)}
85 85 %else:
86 86 <span class="perm_tag ${_perm}">${section_perm}</span>
87 87 %endif
88 88 </td>
89 89 </tr>
90 90 %endif
91 91 %endfor
92 92 </tbody>
93 93 </table>
94 94 </div>
95 95 %endfor
96 96 </div>
97 97 <div id="my" class="table" style="display:none">
98 98 </div>
99 99 <div id="pullrequests" class="table" style="display:none"></div>
100 100 </div>
101 101
102 102
103 103
104 104 <script type="text/javascript">
105 105 var filter_activate = function(){
106 106 var nodes = YUQ('#my tr td a.repo_name');
107 107 var func = function(node){
108 108 return node.parentNode.parentNode.parentNode.parentNode;
109 109 }
110 110 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
111 111 }
112 112
113 113 var show_perms = function(e){
114 114 YUD.addClass('show_perms', 'current');
115 115 YUD.removeClass('show_my','current');
116 116 YUD.removeClass('show_pullrequests','current');
117 117
118 118 YUD.setStyle('my','display','none');
119 119 YUD.setStyle('pullrequests','display','none');
120 120 YUD.setStyle('perms','display','');
121 YUD.setStyle('q_filter','display','none');
121 YUD.setStyle('q_filter','display','none');
122 122 }
123 123 YUE.on('show_perms','click',function(e){
124 124 show_perms();
125 125 })
126 126
127 127 var show_my = function(e){
128 128 YUD.addClass('show_my', 'current');
129 129 YUD.removeClass('show_perms','current');
130 130 YUD.removeClass('show_pullrequests','current');
131 131
132 132 YUD.setStyle('perms','display','none');
133 133 YUD.setStyle('pullrequests','display','none');
134 134 YUD.setStyle('my','display','');
135 135 YUD.setStyle('q_filter','display','');
136 136
137
137
138 138 var url = "${h.url('admin_settings_my_repos')}";
139 139 ypjax(url, 'my', function(){
140 140 table_sort();
141 141 filter_activate();
142 });
142 });
143 143 }
144 144 YUE.on('show_my','click',function(e){
145 145 show_my(e);
146 146 })
147 147
148 148 var show_pullrequests = function(e){
149 149 YUD.addClass('show_pullrequests', 'current');
150 150 YUD.removeClass('show_my','current');
151 151 YUD.removeClass('show_perms','current');
152 152
153 153 YUD.setStyle('my','display','none');
154 154 YUD.setStyle('perms','display','none');
155 155 YUD.setStyle('pullrequests','display','');
156 156 YUD.setStyle('q_filter','display','none');
157
157
158 158 var url = "${h.url('admin_settings_my_pullrequests')}";
159 ypjax(url, 'pullrequests');
159 ypjax(url, 'pullrequests');
160 160 }
161 161 YUE.on('show_pullrequests','click',function(e){
162 162 show_pullrequests(e)
163 163 })
164 164
165 165 var tabs = {
166 166 'perms': show_perms,
167 167 'my': show_my,
168 168 'pullrequests': show_pullrequests
169 169 }
170 var url = location.href.split('#');
171 if (url[1]) {
172 //We have a hash
170 var url = location.href.split('#');
171 if (url[1]) {
172 //We have a hash
173 173 var tabHash = url[1];
174 174 console.log(tabs, tabHash)
175 tabs[tabHash]();
175 tabs[tabHash]();
176 176 }
177 177
178 178 // main table sorting
179 179 var myColumnDefs = [
180 180 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
181 181 {key:"name",label:"${_('Name')}",sortable:true,
182 182 sortOptions: { sortFunction: nameSort }},
183 183 {key:"tip",label:"${_('Tip')}",sortable:true,
184 184 sortOptions: { sortFunction: revisionSort }},
185 185 {key:"action1",label:"",sortable:false},
186 186 {key:"action2",label:"",sortable:false},
187 187 ];
188 188
189 189 function table_sort(){
190 190 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
191 191 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
192 192 myDataSource.responseSchema = {
193 193 fields: [
194 194 {key:"menu"},
195 195 {key:"name"},
196 196 {key:"tip"},
197 197 {key:"action1"},
198 198 {key:"action2"},
199 199 ]
200 200 };
201 201 var trans_defs = {
202 202 sortedBy:{key:"name",dir:"asc"},
203 203 MSG_SORTASC:"${_('Click to sort ascending')}",
204 204 MSG_SORTDESC:"${_('Click to sort descending')}",
205 205 MSG_EMPTY:"${_('No records found.')}",
206 206 MSG_ERROR:"${_('Data error.')}",
207 207 MSG_LOADING:"${_('Loading...')}",
208 208 }
209 209 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,trans_defs);
210 210 myDataTable.subscribe('postRenderEvent',function(oArgs) {
211 211 tooltip_activate();
212 212 quick_repo_menu();
213 213 filter_activate();
214 214 });
215 215
216 216 var permsColumnDefs = [
217 217 {key:"name",label:"${_('Name')}",sortable:true, sortOptions: { sortFunction: permNameSort }},
218 218 {key:"perm",label:"${_('Permission')}",sortable:false,},
219 219 ];
220 220
221 221 // perms repos table
222 222 var myDataSource2 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories"));
223 223 myDataSource2.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
224 224 myDataSource2.responseSchema = {
225 225 fields: [
226 226 {key:"name"},
227 227 {key:"perm"},
228 228 ]
229 229 };
230 230
231 231 new YAHOO.widget.DataTable("tbl_list_wrap_repositories", permsColumnDefs, myDataSource2, trans_defs);
232 232
233 233 //perms groups table
234 234 var myDataSource3 = new YAHOO.util.DataSource(YUD.get("tbl_list_repositories_groups"));
235 235 myDataSource3.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
236 236 myDataSource3.responseSchema = {
237 237 fields: [
238 238 {key:"name"},
239 239 {key:"perm"},
240 240 ]
241 241 };
242 242
243 243 new YAHOO.widget.DataTable("tbl_list_wrap_repositories_groups", permsColumnDefs, myDataSource3, trans_defs);
244 244 }
245 245 </script>
246 246 </%def>
@@ -1,200 +1,200 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('New pull request')}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('New pull request')}
13 13 </%def>
14 14
15 15 <%def name="main()">
16 16
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
23 23 <div style="float:left;padding:0px 30px 30px 30px">
24 24
25 25 ##ORG
26 26 <div style="float:left">
27 27 <div class="fork_user">
28 28 <div class="gravatar">
29 29 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
30 30 </div>
31 31 <span style="font-size: 20px">
32 32 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')}
33 33 </span>
34 34 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
35 35 </div>
36 36 <div style="clear:both;padding-top: 10px"></div>
37 37 </div>
38 38 <div style="float:left;font-size:24px;padding:0px 20px">
39 39 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
40 40 </div>
41 41
42 42 ##OTHER, most Probably the PARENT OF THIS FORK
43 43 <div style="float:left">
44 44 <div class="fork_user">
45 45 <div class="gravatar">
46 46 <img id="other_repo_gravatar" alt="gravatar" src=""/>
47 47 </div>
48 48 <span style="font-size: 20px">
49 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 50 </span>
51 51 <span style="padding:3px">
52 52 <a id="refresh" href="#" class="tooltip" title="${h.tooltip(_('refresh overview'))}">
53 53 <img style="margin:3px" class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
54 54 </a>
55 55 </span>
56 56 <div id="other_repo_desc" style="padding:5px 3px 3px 42px;"></div>
57 57 </div>
58 58 <div style="clear:both;padding-top: 10px"></div>
59 59 </div>
60 60 <div style="clear:both;padding-top: 10px"></div>
61 61 ## overview pulled by ajax
62 62 <div style="float:left" id="pull_request_overview"></div>
63 63 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
64 64 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
65 65 </div>
66 66 </div>
67 67 <div style="float:left; border-left:1px dashed #eee">
68 68 <h4>${_('Pull request reviewers')}</h4>
69 69 <div id="reviewers" style="padding:0px 0px 0px 15px">
70 70 ## members goes here !
71 71 <div class="group_members_wrap">
72 72 <ul id="review_members" class="group_members">
73 73 %for member in c.review_members:
74 74 <li id="reviewer_${member.user_id}">
75 75 <div class="reviewers_member">
76 76 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
77 77 <div style="float:left">${member.full_name} (${_('owner')})</div>
78 78 <input type="hidden" value="${member.user_id}" name="review_members" />
79 79 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
80 80 </div>
81 81 </li>
82 82 %endfor
83 83 </ul>
84 84 </div>
85 85
86 86 <div class='ac'>
87 87 <div class="reviewer_ac">
88 88 ${h.text('user', class_='yui-ac-input')}
89 89 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
90 90 <div id="reviewers_container"></div>
91 91 </div>
92 92 </div>
93 93 </div>
94 94 </div>
95 95 <h3>${_('Create new pull request')}</h3>
96 96
97 97 <div class="form">
98 98 <!-- fields -->
99 99
100 100 <div class="fields">
101 101
102 102 <div class="field">
103 103 <div class="label">
104 104 <label for="pullrequest_title">${_('Title')}:</label>
105 105 </div>
106 106 <div class="input">
107 107 ${h.text('pullrequest_title',size=30)}
108 108 </div>
109 109 </div>
110 110
111 111 <div class="field">
112 112 <div class="label label-textarea">
113 113 <label for="pullrequest_desc">${_('description')}:</label>
114 114 </div>
115 115 <div class="textarea text-area editor">
116 116 ${h.textarea('pullrequest_desc',size=30)}
117 117 </div>
118 118 </div>
119 119
120 120 <div class="buttons">
121 121 ${h.submit('save',_('Send pull request'),class_="ui-btn large")}
122 122 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
123 123 </div>
124 124 </div>
125 125 </div>
126 126 ${h.end_form()}
127 127
128 128 </div>
129 129
130 130 <script type="text/javascript">
131 131 var _USERS_AC_DATA = ${c.users_array|n};
132 132 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
133 133 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
134 134
135 135 var other_repos_info = ${c.other_repos_info|n};
136
136
137 137 var loadPreview = function(){
138 138 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
139 139 var url = "${h.url('compare_url',
140 140 repo_name='org_repo',
141 141 org_ref_type='org_ref_type', org_ref='org_ref',
142 142 other_ref_type='other_ref_type', other_ref='other_ref',
143 143 repo='other_repo',
144 144 as_form=True, bundle=False)}";
145 145
146 146 var select_refs = YUQ('#pull_request_form select.refs')
147 147 var rev_data = {}; // gather the org/other ref and repo here
148 148 for(var i=0;i<select_refs.length;i++){
149 149 var select_ref = select_refs[i];
150 150 var select_ref_data = select_ref.value.split(':');
151 151 var key = null;
152 152 var val = null;
153
153
154 154 if(select_ref_data.length>1){
155 155 key = select_ref.name+"_type";
156 156 val = select_ref_data[0];
157 157 url = url.replace(key,val);
158 158 rev_data[key] = val;
159
159
160 160 key = select_ref.name;
161 161 val = select_ref_data[1];
162 162 url = url.replace(key,val);
163 163 rev_data[key] = val;
164
164
165 165 }else{
166 166 key = select_ref.name;
167 167 val = select_ref.value;
168 168 url = url.replace(key,val);
169 169 rev_data[key] = val;
170 170 }
171 171 }
172 172
173 173 YUE.on('other_repo', 'change', function(e){
174 174 var repo_name = e.currentTarget.value;
175 175 // replace the <select> of changed repo
176 176 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
177 177 });
178
178
179 179 ypjax(url,'pull_request_overview', function(data){
180 180 var sel_box = YUQ('#pull_request_form #other_repo')[0];
181 181 var repo_name = sel_box.options[sel_box.selectedIndex].value;
182 182 YUD.get('pull_request_overview_url').href = url;
183 183 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
184 184 YUD.get('other_repo_gravatar').src = other_repos_info[repo_name]['gravatar'];
185 185 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
186 186 YUD.get('other_ref').innerHTML = other_repos_info[repo_name]['revs'];
187 187 // select back the revision that was just compared
188 188 setSelectValue(YUD.get('other_ref'), rev_data['other_ref']);
189 189 })
190 190 }
191 191 YUE.on('refresh','click',function(e){
192 192 loadPreview()
193 193 })
194 194
195 195 //lazy load overview after 0.5s
196 196 setTimeout(loadPreview, 500)
197 197
198 198 </script>
199 199
200 200 </%def>
@@ -1,199 +1,199 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 13 </%def>
14 14
15 15 <%def name="main()">
16 16
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 %if c.pull_request.is_closed():
23 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 24 %endif
25 25 <h3>${_('Title')}: ${c.pull_request.title}</h3>
26 26
27 27 <div class="form">
28 28 <div id="summary" class="fields">
29 29 <div class="field">
30 30 <div class="label-summary">
31 31 <label>${_('Status')}:</label>
32 32 </div>
33 33 <div class="input">
34 34 <div class="changeset-status-container" style="float:none;clear:both">
35 35 %if c.current_changeset_status:
36 36 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
37 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 38 %endif
39 39 </div>
40 40 </div>
41 41 </div>
42 42 <div class="field">
43 43 <div class="label-summary">
44 44 <label>${_('Still not reviewed by')}:</label>
45 45 </div>
46 46 <div class="input">
47 47 % if len(c.pull_request_pending_reviewers) > 0:
48 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 49 %else:
50 <div>${_('pull request was reviewed by all reviewers')}</div>
50 <div>${_('pull request was reviewed by all reviewers')}</div>
51 51 %endif
52 52 </div>
53 53 </div>
54 54 </div>
55 55 </div>
56 56 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
57 57 <div style="padding:4px 4px 10px 20px">
58 58 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
59 59 </div>
60 60
61 61 <div style="min-height:160px">
62 62 ##DIFF
63 63 <div class="table" style="float:left;clear:none">
64 64 <div id="body" class="diffblock">
65 65 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
66 66 </div>
67 67 <div id="changeset_compare_view_content">
68 68 ##CS
69 69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
70 70 <%include file="/compare/compare_cs.html" />
71 71
72 72 ## FILES
73 73 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
74 74 <div class="cs_files">
75 75 %for fid, change, f, stat in c.files:
76 76 <div class="cs_${change}">
77 77 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
78 78 <div class="changes">${h.fancy_file_stats(stat)}</div>
79 79 </div>
80 80 %endfor
81 81 </div>
82 82 </div>
83 83 </div>
84 84 ## REVIEWERS
85 85 <div style="float:left; border-left:1px dashed #eee">
86 86 <h4>${_('Pull request reviewers')}</h4>
87 87 <div id="reviewers" style="padding:0px 0px 0px 15px">
88 88 ## members goes here !
89 89 <div class="group_members_wrap">
90 90 <ul id="review_members" class="group_members">
91 91 %for member,status in c.pull_request_reviewers:
92 92 <li id="reviewer_${member.user_id}">
93 93 <div class="reviewers_member">
94 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 95 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
96 96 </div>
97 97 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(member.email,14)}"/> </div>
98 98 <div style="float:left">${member.full_name} (${_('owner')})</div>
99 99 <input type="hidden" value="${member.user_id}" name="review_members" />
100 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 101 <span class="delete_icon action_button" onclick="removeReviewer(${member.user_id})"></span>
102 102 %endif
103 103 </div>
104 104 </li>
105 105 %endfor
106 106 </ul>
107 107 </div>
108 108 %if not c.pull_request.is_closed():
109 109 <div class='ac'>
110 110 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id:
111 111 <div class="reviewer_ac">
112 112 ${h.text('user', class_='yui-ac-input')}
113 113 <span class="help-block">${_('Add reviewer to this pull request.')}</span>
114 114 <div id="reviewers_container"></div>
115 115 </div>
116 116 <div style="padding:0px 10px">
117 117 <span id="update_pull_request" class="ui-btn xsmall">${_('save')}</span>
118 118 </div>
119 119 %endif
120 120 </div>
121 121 %endif
122 122 </div>
123 123 </div>
124 124 </div>
125 125 <script>
126 126 var _USERS_AC_DATA = ${c.users_array|n};
127 127 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
128 128 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
129 129 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
130 130 AJAX_UPDATE_PULLREQUEST = "${url('pullrequest_update',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}"
131 131 </script>
132 132
133 133 ## diff block
134 134 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
135 135 %for fid, change, f, stat in c.files:
136 136 ${diff_block.diff_block_simple([c.changes[fid]])}
137 137 %endfor
138 138
139 139 ## template for inline comment form
140 140 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
141 141 ${comment.comment_inline_form()}
142 142
143 143 ## render comments and inlines
144 144 ${comment.generate_comments()}
145 145
146 146 % if not c.pull_request.is_closed():
147 147 ## main comment form and it status
148 148 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
149 149 pull_request_id=c.pull_request.pull_request_id),
150 150 c.current_changeset_status,
151 151 close_btn=True)}
152 152 %endif
153 153
154 154 <script type="text/javascript">
155 155 YUE.onDOMReady(function(){
156 156 PullRequestAutoComplete('user', 'reviewers_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
157 157
158 158 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
159 159 var show = 'none';
160 160 var target = e.currentTarget;
161 161 if(target.checked){
162 162 var show = ''
163 163 }
164 164 var boxid = YUD.getAttribute(target,'id_for');
165 165 var comments = YUQ('#{0} .inline-comments'.format(boxid));
166 166 for(c in comments){
167 167 YUD.setStyle(comments[c],'display',show);
168 168 }
169 169 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
170 170 for(c in btns){
171 171 YUD.setStyle(btns[c],'display',show);
172 172 }
173 173 })
174 174
175 175 YUE.on(YUQ('.line'),'click',function(e){
176 176 var tr = e.currentTarget;
177 177 injectInlineForm(tr);
178 178 });
179 179
180 180 // inject comments into they proper positions
181 181 var file_comments = YUQ('.inline-comment-placeholder');
182 182 renderInlineComments(file_comments);
183 183
184 184 YUE.on(YUD.get('update_pull_request'),'click',function(e){
185 185
186 186 var reviewers_ids = [];
187 187 var ids = YUQ('#review_members input');
188 188 for(var i=0; i<ids.length;i++){
189 189 var id = ids[i].value
190 190 reviewers_ids.push(id);
191 191 }
192 192 updateReviewers(reviewers_ids);
193 193 })
194 194 })
195 195 </script>
196 196
197 197 </div>
198 198
199 199 </%def>
@@ -1,103 +1,103 b''
1 1 ## -*- coding: utf-8 -*-
2 2 %if c.repo_changesets:
3 3 <table class="table_disp">
4 4 <tr>
5 5 <th class="left">${_('revision')}</th>
6 6 <th class="left">${_('commit message')}</th>
7 7 <th class="left">${_('age')}</th>
8 8 <th class="left">${_('author')}</th>
9 9 <th class="left">${_('branch')}</th>
10 10 <th class="left">${_('tags')}</th>
11 11 </tr>
12 12 %for cnt,cs in enumerate(c.repo_changesets):
13 13 <tr class="parity${cnt%2}">
14 14 <td>
15 15 <div>
16 16 <div class="changeset-status-container">
17 17 %if c.statuses.get(cs.raw_id):
18 18 <div class="changeset-status-ico">
19 19 %if c.statuses.get(cs.raw_id)[2]:
20 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 21 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
22 22 </a>
23 23 %else:
24 24 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
25 25 %endif
26 26 </div>
27 27 %endif
28 </div>
28 </div>
29 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 30 </div>
31 31 </td>
32 32 <td>
33 33 ${h.link_to(h.truncate(cs.message,50) or _('No commit message'),
34 34 h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
35 35 title=cs.message)}
36 36 </td>
37 37 <td><span class="tooltip" title="${h.tooltip(h.fmt_date(cs.date))}">
38 38 ${h.age(cs.date)}</span>
39 39 </td>
40 40 <td title="${cs.author}">${h.person(cs.author)}</td>
41 41 <td>
42 42 <span class="logtags">
43 43 %if cs.branch:
44 44 <span class="branchtag">
45 45 ${cs.branch}
46 46 </span>
47 47 %endif
48 48 </span>
49 49 </td>
50 50 <td>
51 51 <span class="logtags">
52 52 %for tag in cs.tags:
53 53 <span class="tagtag">${tag}</span>
54 54 %endfor
55 55 </span>
56 56 </td>
57 57 </tr>
58 58 %endfor
59 59
60 60 </table>
61 61
62 62 <script type="text/javascript">
63 63 YUE.onDOMReady(function(){
64 64 YUE.delegate("shortlog_data","click",function(e, matchedEl, container){
65 65 ypjax(e.target.href,"shortlog_data",function(){tooltip_activate();});
66 66 YUE.preventDefault(e);
67 67 },'.pager_link');
68 68 });
69 69 </script>
70 70
71 71 <div class="pagination-wh pagination-left">
72 72 ${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
73 73 </div>
74 74 %else:
75 75
76 76 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
77 77 <h4>${_('Add or upload files directly via RhodeCode')}</h4>
78 78 <div style="margin: 20px 30px;">
79 79 <div id="add_node_id" class="add_node">
80 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 81 </div>
82 82 </div>
83 83 %endif
84 84
85 85
86 86 <h4>${_('Push new repo')}</h4>
87 87 <pre>
88 88 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
89 89 ${c.rhodecode_repo.alias} add README # add first file
90 90 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
91 91 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
92 92 </pre>
93 93
94 94 <h4>${_('Existing repository?')}</h4>
95 95 <pre>
96 96 %if h.is_git(c.rhodecode_repo):
97 97 git remote add origin ${c.clone_repo_url}
98 98 git push -u origin master
99 99 %else:
100 100 hg push ${c.clone_repo_url}
101 101 %endif
102 102 </pre>
103 103 %endif
@@ -1,403 +1,403 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.repo import RepoModel
3 3 from rhodecode.model.meta import Session
4 4 from rhodecode.model.db import Repository
5 5 from rhodecode.model.scm import ScmModel
6 6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
7 7
8 8
9 9 class TestCompareController(TestController):
10 10
11 11 def test_index_tag(self):
12 12 self.log_user()
13 13 tag1 = '0.1.3'
14 14 tag2 = '0.1.2'
15 15 response = self.app.get(url(controller='compare', action='index',
16 16 repo_name=HG_REPO,
17 17 org_ref_type="tag",
18 18 org_ref=tag1,
19 19 other_ref_type="tag",
20 20 other_ref=tag2,
21 21 ))
22 22 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
23 23 ## outgoing changesets between tags
24 24 response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
25 25 response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
26 26 response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
27 27 response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
28 28 response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
29 29 response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
30 30 response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
31 31
32 32 ## files diff
33 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 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 35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
36 36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
37 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 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 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 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 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 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 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 45 def test_index_branch(self):
46 46 self.log_user()
47 47 response = self.app.get(url(controller='compare', action='index',
48 48 repo_name=HG_REPO,
49 49 org_ref_type="branch",
50 50 org_ref='default',
51 51 other_ref_type="branch",
52 52 other_ref='default',
53 53 ))
54 54
55 55 response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO))
56 56 # branch are equal
57 57 response.mustcontain('<tr><td>No changesets</td></tr>')
58 58
59 59 def test_compare_revisions(self):
60 60 self.log_user()
61 61 rev1 = '3d8f361e72ab'
62 62 rev2 = 'b986218ba1c9'
63 63 response = self.app.get(url(controller='compare', action='index',
64 64 repo_name=HG_REPO,
65 65 org_ref_type="rev",
66 66 org_ref=rev1,
67 67 other_ref_type="rev",
68 68 other_ref=rev2,
69 69 ))
70 70 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
71 71 ## outgoing changesets between those revisions
72 72 response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev1))
73 73
74 74 ## files
75 75 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (HG_REPO, rev1, rev2))
76 76
77 77 def test_compare_remote_repos(self):
78 78 self.log_user()
79 79
80 80 form_data = dict(
81 81 repo_name=HG_FORK,
82 82 repo_name_full=HG_FORK,
83 83 repo_group=None,
84 84 repo_type='hg',
85 85 description='',
86 86 private=False,
87 87 copy_permissions=False,
88 88 landing_rev='tip',
89 89 update_after_clone=False,
90 90 fork_parent_id=Repository.get_by_repo_name(HG_REPO),
91 91 )
92 92 RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN)
93 93
94 94 Session().commit()
95 95
96 96 rev1 = '7d4bc8ec6be5'
97 97 rev2 = '56349e29c2af'
98 98
99 99 response = self.app.get(url(controller='compare', action='index',
100 100 repo_name=HG_REPO,
101 101 org_ref_type="rev",
102 102 org_ref=rev1,
103 103 other_ref_type="rev",
104 104 other_ref=rev2,
105 105 repo=HG_FORK
106 106 ))
107 107
108 108 try:
109 109 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
110 110 ## outgoing changesets between those revisions
111 111
112 112 response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_REPO, rev1))
113 113 response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_REPO))
114 114 response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO))
115 115
116 116 ## files
117 117 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2))
118 118 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2))
119 119 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2))
120 120 finally:
121 121 RepoModel().delete(HG_FORK)
122 122
123 123 def test_compare_extra_commits(self):
124 124 self.log_user()
125 125
126 126 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
127 127 description='diff-test',
128 128 owner=TEST_USER_ADMIN_LOGIN)
129 129
130 130 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
131 131 description='diff-test',
132 132 owner=TEST_USER_ADMIN_LOGIN)
133 133
134 134 Session().commit()
135 135 r1_id = repo1.repo_id
136 136 r1_name = repo1.repo_name
137 137 r2_id = repo2.repo_id
138 138 r2_name = repo2.repo_name
139 139
140 140 #commit something !
141 141 cs0 = ScmModel().create_node(
142 142 repo=repo1.scm_instance, repo_name=r1_name,
143 143 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
144 144 author=TEST_USER_ADMIN_LOGIN,
145 145 message='commit1',
146 146 content='line1',
147 147 f_path='file1'
148 148 )
149 149
150 150 cs0_prim = ScmModel().create_node(
151 151 repo=repo2.scm_instance, repo_name=r2_name,
152 152 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
153 153 author=TEST_USER_ADMIN_LOGIN,
154 154 message='commit1',
155 155 content='line1',
156 156 f_path='file1'
157 157 )
158 158
159 159 cs1 = ScmModel().commit_change(
160 160 repo=repo2.scm_instance, repo_name=r2_name,
161 161 cs=cs0_prim, user=TEST_USER_ADMIN_LOGIN, author=TEST_USER_ADMIN_LOGIN,
162 162 message='commit2',
163 163 content='line1\nline2',
164 164 f_path='file1'
165 165 )
166 166
167 167 rev1 = 'default'
168 168 rev2 = 'default'
169 169 response = self.app.get(url(controller='compare', action='index',
170 170 repo_name=r2_name,
171 171 org_ref_type="branch",
172 172 org_ref=rev1,
173 173 other_ref_type="branch",
174 174 other_ref=rev2,
175 175 repo=r1_name
176 176 ))
177 177
178 178 try:
179 179 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
180 180
181 181 response.mustcontain("""<div class="message">commit2</div>""")
182 182 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (r2_name, cs1.raw_id, cs1.short_id))
183 183 ## files
184 184 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s#C--826e8142e6ba">file1</a>""" % (r2_name, rev1, rev2))
185 185
186 186 finally:
187 187 RepoModel().delete(r1_id)
188 188 RepoModel().delete(r2_id)
189 189
190 190 def test_org_repo_new_commits_after_forking(self):
191 191 self.log_user()
192 192
193 193 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
194 194 description='diff-test',
195 195 owner=TEST_USER_ADMIN_LOGIN)
196 196
197 197 Session().commit()
198 198 r1_id = repo1.repo_id
199 199 r1_name = repo1.repo_name
200 200
201 201 #commit something initially !
202 202 cs0 = ScmModel().create_node(
203 203 repo=repo1.scm_instance, repo_name=r1_name,
204 204 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
205 205 author=TEST_USER_ADMIN_LOGIN,
206 206 message='commit1',
207 207 content='line1',
208 208 f_path='file1'
209 209 )
210 210 Session().commit()
211 211 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
212 212 #fork the repo1
213 213 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
214 214 description='compare-test',
215 215 clone_uri=repo1.repo_full_path,
216 216 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
217 217 Session().commit()
218 218 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
219 219 r2_id = repo2.repo_id
220 220 r2_name = repo2.repo_name
221 221
222 222 #make 3 new commits in fork
223 223 cs1 = ScmModel().create_node(
224 224 repo=repo2.scm_instance, repo_name=r2_name,
225 225 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
226 226 author=TEST_USER_ADMIN_LOGIN,
227 227 message='commit1-fork',
228 228 content='file1-line1-from-fork',
229 229 f_path='file1-fork'
230 230 )
231 231 cs2 = ScmModel().create_node(
232 232 repo=repo2.scm_instance, repo_name=r2_name,
233 233 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
234 234 author=TEST_USER_ADMIN_LOGIN,
235 235 message='commit2-fork',
236 236 content='file2-line1-from-fork',
237 237 f_path='file2-fork'
238 238 )
239 239 cs3 = ScmModel().create_node(
240 240 repo=repo2.scm_instance, repo_name=r2_name,
241 241 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
242 242 author=TEST_USER_ADMIN_LOGIN,
243 243 message='commit3-fork',
244 244 content='file3-line1-from-fork',
245 245 f_path='file3-fork'
246 246 )
247 247
248 248 #compare !
249 249 rev1 = 'default'
250 250 rev2 = 'default'
251 251 response = self.app.get(url(controller='compare', action='index',
252 252 repo_name=r2_name,
253 253 org_ref_type="branch",
254 254 org_ref=rev1,
255 255 other_ref_type="branch",
256 256 other_ref=rev2,
257 257 repo=r1_name,
258 258 bundle=True,
259 259 ))
260 260
261 261 try:
262 262 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
263 263 response.mustcontain("""file1-line1-from-fork""")
264 264 response.mustcontain("""file2-line1-from-fork""")
265 265 response.mustcontain("""file3-line1-from-fork""")
266 266
267 267 #add new commit into parent !
268 268 cs0 = ScmModel().create_node(
269 269 repo=repo1.scm_instance, repo_name=r1_name,
270 270 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
271 271 author=TEST_USER_ADMIN_LOGIN,
272 272 message='commit2',
273 273 content='line1-from-new-parent',
274 274 f_path='file2'
275 275 )
276 276 #compare !
277 277 rev1 = 'default'
278 278 rev2 = 'default'
279 279 response = self.app.get(url(controller='compare', action='index',
280 280 repo_name=r2_name,
281 281 org_ref_type="branch",
282 282 org_ref=rev1,
283 283 other_ref_type="branch",
284 284 other_ref=rev2,
285 285 repo=r1_name,
286 286 bundle=True,
287 287 ))
288 288
289 289 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
290 290 response.mustcontain("""<a href="#">file2</a>""") # new commit from parent
291 291 response.mustcontain("""line1-from-new-parent""")
292 292 response.mustcontain("""file1-line1-from-fork""")
293 293 response.mustcontain("""file2-line1-from-fork""")
294 294 response.mustcontain("""file3-line1-from-fork""")
295 295 finally:
296 296 RepoModel().delete(r2_id)
297 297 RepoModel().delete(r1_id)
298 298
299 299 def test_org_repo_new_commits_after_forking_simple_diff(self):
300 300 self.log_user()
301 301
302 302 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
303 303 description='diff-test',
304 304 owner=TEST_USER_ADMIN_LOGIN)
305 305
306 306 Session().commit()
307 307 r1_id = repo1.repo_id
308 308 r1_name = repo1.repo_name
309 309
310 310 #commit something initially !
311 311 cs0 = ScmModel().create_node(
312 312 repo=repo1.scm_instance, repo_name=r1_name,
313 313 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
314 314 author=TEST_USER_ADMIN_LOGIN,
315 315 message='commit1',
316 316 content='line1',
317 317 f_path='file1'
318 318 )
319 319 Session().commit()
320 320 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
321 321 #fork the repo1
322 322 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
323 323 description='compare-test',
324 324 clone_uri=repo1.repo_full_path,
325 325 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
326 326 Session().commit()
327 327 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
328 328 r2_id = repo2.repo_id
329 329 r2_name = repo2.repo_name
330 330
331 331 #make 3 new commits in fork
332 332 cs1 = ScmModel().create_node(
333 333 repo=repo2.scm_instance, repo_name=r2_name,
334 334 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
335 335 author=TEST_USER_ADMIN_LOGIN,
336 336 message='commit1-fork',
337 337 content='file1-line1-from-fork',
338 338 f_path='file1-fork'
339 339 )
340 340 cs2 = ScmModel().create_node(
341 341 repo=repo2.scm_instance, repo_name=r2_name,
342 342 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
343 343 author=TEST_USER_ADMIN_LOGIN,
344 344 message='commit2-fork',
345 345 content='file2-line1-from-fork',
346 346 f_path='file2-fork'
347 347 )
348 348 cs3 = ScmModel().create_node(
349 349 repo=repo2.scm_instance, repo_name=r2_name,
350 350 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
351 351 author=TEST_USER_ADMIN_LOGIN,
352 352 message='commit3-fork',
353 353 content='file3-line1-from-fork',
354 354 f_path='file3-fork'
355 355 )
356 356
357 357 #compare !
358 358 rev1 = 'default'
359 359 rev2 = 'default'
360 360 response = self.app.get(url(controller='compare', action='index',
361 361 repo_name=r2_name,
362 362 org_ref_type="branch",
363 363 org_ref=rev1,
364 364 other_ref_type="branch",
365 365 other_ref=rev2,
366 366 repo=r1_name,
367 367 bundle=False,
368 368 ))
369 369
370 370 try:
371 371 #response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
372 372
373 373 #add new commit into parent !
374 374 cs0 = ScmModel().create_node(
375 375 repo=repo1.scm_instance, repo_name=r1_name,
376 376 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
377 377 author=TEST_USER_ADMIN_LOGIN,
378 378 message='commit2',
379 379 content='line1',
380 380 f_path='file2'
381 381 )
382 382 #compare !
383 383 rev1 = 'default'
384 384 rev2 = 'default'
385 385 response = self.app.get(url(controller='compare', action='index',
386 386 repo_name=r2_name,
387 387 org_ref_type="branch",
388 388 org_ref=rev1,
389 389 other_ref_type="branch",
390 390 other_ref=rev2,
391 391 repo=r1_name,
392 392 bundle=False
393 393 ))
394 394 rev2 = cs0.parents[0].raw_id
395 395 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
396 396 response.mustcontain("""file1-line1-from-fork""")
397 397 response.mustcontain("""file2-line1-from-fork""")
398 398 response.mustcontain("""file3-line1-from-fork""")
399 399 self.assertFalse("""<a href="#">file2</a>""" in response.body) # new commit from parent
400 400 self.assertFalse("""line1-from-new-parent""" in response.body)
401 401 finally:
402 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 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.tests.test_libs
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6
7 7 Package for testing various lib/helper functions in rhodecode
8 8
9 9 :created_on: Jun 9, 2011
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 from __future__ import with_statement
26 26 import unittest
27 27 import datetime
28 28 import hashlib
29 29 import mock
30 30 from rhodecode.tests import *
31 31
32 32 proto = 'http'
33 33 TEST_URLS = [
34 34 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
35 35 '%s://127.0.0.1' % proto),
36 36 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
37 37 '%s://127.0.0.1' % proto),
38 38 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
39 39 '%s://127.0.0.1' % proto),
40 40 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
41 41 '%s://127.0.0.1:8080' % proto),
42 42 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
43 43 '%s://domain.org' % proto),
44 44 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
45 45 '8080'],
46 46 '%s://domain.org:8080' % proto),
47 47 ]
48 48
49 49 proto = 'https'
50 50 TEST_URLS += [
51 51 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
52 52 '%s://127.0.0.1' % proto),
53 53 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
54 54 '%s://127.0.0.1' % proto),
55 55 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
56 56 '%s://127.0.0.1' % proto),
57 57 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
58 58 '%s://127.0.0.1:8080' % proto),
59 59 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
60 60 '%s://domain.org' % proto),
61 61 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
62 62 '8080'],
63 63 '%s://domain.org:8080' % proto),
64 64 ]
65 65
66 66
67 67 class TestLibs(unittest.TestCase):
68 68
69 69 def test_uri_filter(self):
70 70 from rhodecode.lib.utils2 import uri_filter
71 71
72 72 for url in TEST_URLS:
73 73 self.assertEqual(uri_filter(url[0]), url[1])
74 74
75 75 def test_credentials_filter(self):
76 76 from rhodecode.lib.utils2 import credentials_filter
77 77
78 78 for url in TEST_URLS:
79 79 self.assertEqual(credentials_filter(url[0]), url[2])
80 80
81 81 def test_str2bool(self):
82 82 from rhodecode.lib.utils2 import str2bool
83 83 test_cases = [
84 84 ('t', True),
85 85 ('true', True),
86 86 ('y', True),
87 87 ('yes', True),
88 88 ('on', True),
89 89 ('1', True),
90 90 ('Y', True),
91 91 ('yeS', True),
92 92 ('Y', True),
93 93 ('TRUE', True),
94 94 ('T', True),
95 95 ('False', False),
96 96 ('F', False),
97 97 ('FALSE', False),
98 98 ('0', False),
99 99 ('-1', False),
100 100 ('', False), ]
101 101
102 102 for case in test_cases:
103 103 self.assertEqual(str2bool(case[0]), case[1])
104 104
105 105 def test_mention_extractor(self):
106 106 from rhodecode.lib.utils2 import extract_mentioned_users
107 107 sample = (
108 108 "@first hi there @marcink here's my email marcin@email.com "
109 109 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
110 110 "@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl "
111 111 "@marian.user just do it @marco-polo and next extract @marco_polo "
112 112 "user.dot hej ! not-needed maril@domain.org"
113 113 )
114 114
115 115 s = sorted([
116 116 'first', 'marcink', 'lukaszb', 'one_more22', 'MARCIN', 'maRCiN', 'john',
117 117 'marian.user', 'marco-polo', 'marco_polo'
118 118 ], key=lambda k: k.lower())
119 119 self.assertEqual(s, extract_mentioned_users(sample))
120 120
121 121 def test_age(self):
122 122 import calendar
123 123 from rhodecode.lib.utils2 import age
124 124 n = datetime.datetime.now()
125 125 delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs)
126 126 self.assertEqual(age(n), u'just now')
127 127 self.assertEqual(age(n - delt(seconds=1)), u'1 second ago')
128 128 self.assertEqual(age(n - delt(seconds=60 * 2)), u'2 minutes ago')
129 129 self.assertEqual(age(n - delt(hours=1)), u'1 hour ago')
130 130 self.assertEqual(age(n - delt(hours=24)), u'1 day ago')
131 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 133 u'1 month and 2 days ago')
134 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 151 def test_tag_exctrator(self):
137 152 sample = (
138 153 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
139 154 "[requires] [stale] [see<>=>] [see => http://url.com]"
140 155 "[requires => url] [lang => python] [just a tag]"
141 156 "[,d] [ => ULR ] [obsolete] [desc]]"
142 157 )
143 158 from rhodecode.lib.helpers import desc_stylize
144 159 res = desc_stylize(sample)
145 160 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
146 161 self.assertTrue('<div class="metatag" tag="obsolete">obsolete</div>' in res)
147 162 self.assertTrue('<div class="metatag" tag="stale">stale</div>' in res)
148 163 self.assertTrue('<div class="metatag" tag="lang">python</div>' in res)
149 164 self.assertTrue('<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res)
150 165 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
151 166
152 167 def test_alternative_gravatar(self):
153 168 from rhodecode.lib.helpers import gravatar_url
154 169 _md5 = lambda s: hashlib.md5(s).hexdigest()
155 170
156 171 def fake_conf(**kwargs):
157 172 from pylons import config
158 173 config['app_conf'] = {}
159 174 config['app_conf']['use_gravatar'] = True
160 175 config['app_conf'].update(kwargs)
161 176 return config
162 177
163 178 class fake_url():
164 179 @classmethod
165 180 def current(cls, *args, **kwargs):
166 181 return 'https://server.com'
167 182
168 183 with mock.patch('pylons.url', fake_url):
169 184 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
170 185 with mock.patch('pylons.config', fake):
171 186 from pylons import url
172 187 assert url.current() == 'https://server.com'
173 188 grav = gravatar_url(email_address='test@foo.com', size=24)
174 189 assert grav == 'http://test.com/test@foo.com'
175 190
176 191 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
177 192 with mock.patch('pylons.config', fake):
178 193 grav = gravatar_url(email_address='test@foo.com', size=24)
179 194 assert grav == 'http://test.com/test@foo.com'
180 195
181 196 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}')
182 197 with mock.patch('pylons.config', fake):
183 198 em = 'test@foo.com'
184 199 grav = gravatar_url(email_address=em, size=24)
185 200 assert grav == 'http://test.com/%s' % (_md5(em))
186 201
187 202 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}/{size}')
188 203 with mock.patch('pylons.config', fake):
189 204 em = 'test@foo.com'
190 205 grav = gravatar_url(email_address=em, size=24)
191 206 assert grav == 'http://test.com/%s/%s' % (_md5(em), 24)
192 207
193 208 fake = fake_conf(alternative_gravatar_url='{scheme}://{netloc}/{md5email}/{size}')
194 209 with mock.patch('pylons.config', fake):
195 210 em = 'test@foo.com'
196 211 grav = gravatar_url(email_address=em, size=24)
197 212 assert grav == 'https://server.com/%s/%s' % (_md5(em), 24)
198
@@ -1,246 +1,247 b''
1 1 # -*- coding: utf-8 -*-
2 2 import unittest
3 3 import formencode
4 4
5 5 from rhodecode.tests import *
6 6
7 7 from rhodecode.model import validators as v
8 8 from rhodecode.model.users_group import UsersGroupModel
9 9
10 10 from rhodecode.model.meta import Session
11 11 from rhodecode.model.repos_group import ReposGroupModel
12 12 from rhodecode.config.routing import ADMIN_PREFIX
13 from rhodecode.model.db import ChangesetStatus
13 from rhodecode.model.db import ChangesetStatus, Repository
14 14 from rhodecode.model.changeset_status import ChangesetStatusModel
15 15 from rhodecode.model.comment import ChangesetCommentsModel
16 16
17 17
18 18 class TestReposGroups(unittest.TestCase):
19 19
20 20 def setUp(self):
21 21 pass
22 22
23 23 def tearDown(self):
24 24 pass
25 25
26 26 def test_Message_extractor(self):
27 27 validator = v.ValidUsername()
28 28 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
29 29
30 30 class StateObj(object):
31 31 pass
32 32
33 33 self.assertRaises(formencode.Invalid,
34 34 validator.to_python, 'default', StateObj)
35 35
36 36 def test_ValidUsername(self):
37 37 validator = v.ValidUsername()
38 38
39 39 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
40 40 self.assertRaises(formencode.Invalid, validator.to_python, 'new_user')
41 41 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
42 42 self.assertRaises(formencode.Invalid, validator.to_python,
43 43 TEST_USER_ADMIN_LOGIN)
44 44 self.assertEqual('test', validator.to_python('test'))
45 45
46 46 validator = v.ValidUsername(edit=True, old_data={'user_id': 1})
47 47
48 48 def test_ValidRepoUser(self):
49 49 validator = v.ValidRepoUser()
50 50 self.assertRaises(formencode.Invalid, validator.to_python, 'nouser')
51 51 self.assertEqual(TEST_USER_ADMIN_LOGIN,
52 52 validator.to_python(TEST_USER_ADMIN_LOGIN))
53 53
54 54 def test_ValidUsersGroup(self):
55 55 validator = v.ValidUsersGroup()
56 56 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
57 57 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
58 58
59 59 gr = UsersGroupModel().create('test')
60 60 gr2 = UsersGroupModel().create('tes2')
61 61 Session.commit()
62 62 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
63 63 assert gr.users_group_id != None
64 64 validator = v.ValidUsersGroup(edit=True,
65 65 old_data={'users_group_id':
66 66 gr2.users_group_id})
67 67
68 68 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
69 69 self.assertRaises(formencode.Invalid, validator.to_python, 'TesT')
70 70 self.assertRaises(formencode.Invalid, validator.to_python, 'TEST')
71 71 UsersGroupModel().delete(gr)
72 72 UsersGroupModel().delete(gr2)
73 73 Session.commit()
74 74
75 75 def test_ValidReposGroup(self):
76 76 validator = v.ValidReposGroup()
77 77 model = ReposGroupModel()
78 78 self.assertRaises(formencode.Invalid, validator.to_python,
79 79 {'group_name': HG_REPO, })
80 80 gr = model.create(group_name='test_gr', group_description='desc',
81 81 parent=None,
82 82 just_db=True)
83 83 self.assertRaises(formencode.Invalid,
84 84 validator.to_python, {'group_name': gr.group_name, })
85 85
86 86 validator = v.ValidReposGroup(edit=True,
87 87 old_data={'group_id': gr.group_id})
88 88 self.assertRaises(formencode.Invalid,
89 89 validator.to_python, {
90 90 'group_name': gr.group_name + 'n',
91 91 'group_parent_id': gr.group_id
92 92 })
93 93 model.delete(gr)
94 94
95 95 def test_ValidPassword(self):
96 96 validator = v.ValidPassword()
97 97 self.assertEqual('lol', validator.to_python('lol'))
98 98 self.assertEqual(None, validator.to_python(None))
99 99 self.assertRaises(formencode.Invalid, validator.to_python, 'Δ…Δ‡ΕΌΕΊ')
100 100
101 101 def test_ValidPasswordsMatch(self):
102 102 validator = v.ValidPasswordsMatch()
103 103 self.assertRaises(formencode.Invalid,
104 104 validator.to_python, {'password': 'pass',
105 105 'password_confirmation': 'pass2'})
106 106
107 107 self.assertRaises(formencode.Invalid,
108 108 validator.to_python, {'new_password': 'pass',
109 109 'password_confirmation': 'pass2'})
110 110
111 111 self.assertEqual({'new_password': 'pass',
112 112 'password_confirmation': 'pass'},
113 113 validator.to_python({'new_password': 'pass',
114 114 'password_confirmation': 'pass'}))
115 115
116 116 self.assertEqual({'password': 'pass',
117 117 'password_confirmation': 'pass'},
118 118 validator.to_python({'password': 'pass',
119 119 'password_confirmation': 'pass'}))
120 120
121 121 def test_ValidAuth(self):
122 122 validator = v.ValidAuth()
123 123 valid_creds = {
124 124 'username': TEST_USER_REGULAR2_LOGIN,
125 125 'password': TEST_USER_REGULAR2_PASS,
126 126 }
127 127 invalid_creds = {
128 128 'username': 'err',
129 129 'password': 'err',
130 130 }
131 131 self.assertEqual(valid_creds, validator.to_python(valid_creds))
132 132 self.assertRaises(formencode.Invalid,
133 133 validator.to_python, invalid_creds)
134 134
135 135 def test_ValidAuthToken(self):
136 136 validator = v.ValidAuthToken()
137 137 # this is untestable without a threadlocal
138 138 # self.assertRaises(formencode.Invalid,
139 139 # validator.to_python, 'BadToken')
140 140 validator
141 141
142 142 def test_ValidRepoName(self):
143 143 validator = v.ValidRepoName()
144 144
145 145 self.assertRaises(formencode.Invalid,
146 146 validator.to_python, {'repo_name': ''})
147 147
148 148 self.assertRaises(formencode.Invalid,
149 149 validator.to_python, {'repo_name': HG_REPO})
150 150
151 151 gr = ReposGroupModel().create(group_name='group_test',
152 152 group_description='desc',
153 153 parent=None,)
154 154 self.assertRaises(formencode.Invalid,
155 155 validator.to_python, {'repo_name': gr.group_name})
156 156
157 157 #TODO: write an error case for that ie. create a repo withinh a group
158 158 # self.assertRaises(formencode.Invalid,
159 159 # validator.to_python, {'repo_name': 'some',
160 160 # 'repo_group': gr.group_id})
161 161
162 162 def test_ValidForkName(self):
163 163 # this uses ValidRepoName validator
164 164 assert True
165 165
166 166 @parameterized.expand([
167 167 ('test', 'test'), ('lolz!', 'lolz'), (' aavv', 'aavv'),
168 168 ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'),
169 169 ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'),
170 170 ('/]re po', 're-po')])
171 171 def test_SlugifyName(self, name, expected):
172 172 validator = v.SlugifyName()
173 173 self.assertEqual(expected, validator.to_python(name))
174 174
175 175 def test_ValidCloneUri(self):
176 176 #TODO: write this one
177 177 pass
178 178
179 179 def test_ValidForkType(self):
180 180 validator = v.ValidForkType(old_data={'repo_type': 'hg'})
181 181 self.assertEqual('hg', validator.to_python('hg'))
182 182 self.assertRaises(formencode.Invalid, validator.to_python, 'git')
183 183
184 184 def test_ValidPerms(self):
185 185 #TODO: write this one
186 186 pass
187 187
188 188 def test_ValidSettings(self):
189 189 validator = v.ValidSettings()
190 190 self.assertEqual({'pass': 'pass'},
191 191 validator.to_python(value={'user': 'test',
192 192 'pass': 'pass'}))
193 193
194 194 self.assertEqual({'user2': 'test', 'pass': 'pass'},
195 195 validator.to_python(value={'user2': 'test',
196 196 'pass': 'pass'}))
197 197
198 198 def test_ValidPath(self):
199 199 validator = v.ValidPath()
200 200 self.assertEqual(TESTS_TMP_PATH,
201 201 validator.to_python(TESTS_TMP_PATH))
202 202 self.assertRaises(formencode.Invalid, validator.to_python,
203 203 '/no_such_dir')
204 204
205 205 def test_UniqSystemEmail(self):
206 206 validator = v.UniqSystemEmail(old_data={})
207 207
208 208 self.assertEqual('mail@python.org',
209 209 validator.to_python('MaiL@Python.org'))
210 210
211 211 email = TEST_USER_REGULAR2_EMAIL
212 212 self.assertRaises(formencode.Invalid, validator.to_python, email)
213 213
214 214 def test_ValidSystemEmail(self):
215 215 validator = v.ValidSystemEmail()
216 216 email = TEST_USER_REGULAR2_EMAIL
217 217
218 218 self.assertEqual(email, validator.to_python(email))
219 219 self.assertRaises(formencode.Invalid, validator.to_python, 'err')
220 220
221 221 def test_LdapLibValidator(self):
222 222 validator = v.LdapLibValidator()
223 223 self.assertRaises(v.LdapImportError, validator.to_python, 'err')
224 224
225 225 def test_AttrLoginValidator(self):
226 226 validator = v.AttrLoginValidator()
227 227 self.assertRaises(formencode.Invalid, validator.to_python, 123)
228 228
229 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 232 rev = '0' * 40
232 233 # add status for a rev, that should throw an error because it is already
233 234 # reviewed
234 235 new_status = ChangesetStatus()
235 236 new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
236 237 new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO)
237 238 new_status.status = ChangesetStatus.STATUS_APPROVED
238 239 new_status.comment = None
239 240 new_status.revision = rev
240 241 Session().add(new_status)
241 242 Session().commit()
242 243 try:
243 244 self.assertRaises(formencode.Invalid, validator.to_python, [rev])
244 245 finally:
245 246 Session().delete(new_status)
246 247 Session().commit()
General Comments 0
You need to be logged in to leave comments. Login now