##// END OF EJS Templates
Fix 'repos group' - it is 'repository group'
Mads Kiilerich -
r3653:4c78a085 beta
parent child Browse files
Show More
@@ -1,989 +1,989 b''
1 1 .. _changelog:
2 2
3 3 =========
4 4 Changelog
5 5 =========
6 6
7 7 1.6.0 (**2013-XX-XX**)
8 8 ----------------------
9 9
10 10 :status: in-progress
11 11 :branch: beta
12 12
13 13 news
14 14 ++++
15 15
16 16 fixes
17 17 +++++
18 18
19 19 1.5.4 (**2013-03-13**)
20 20 ----------------------
21 21
22 22 news
23 23 ++++
24 24
25 25
26 26 fixes
27 27 +++++
28 28
29 29 - fixed webtest dependency issues
30 30 - fixed issues with celery tasks for password reset
31 31 - fixed #763 gravatar helper function should fallback into default image
32 32 if email is empty
33 33 - fixes #762 user global activation flag is also respected for LDAP created
34 34 accounts
35 35 - use password obfuscate when clonning a remote repo with credentials inside
36 - fixed issue with renaming repos group together with changing parents
36 - fixed issue with renaming repository group together with changing parents
37 37 - disallow cloning from file:/// URIs
38 38 - handle all cases with multiple IP addresses in proxy headers
39 39
40 40 1.5.3 (**2013-02-12**)
41 41 ----------------------
42 42
43 43 news
44 44 ++++
45 45
46 46 - IP restrictions now also enabled for IPv6
47 47
48 48 fixes
49 49 +++++
50 50
51 51 - fixed issues with private checkbox not always working
52 52 - fixed #746 unicodeDedode errors on feed controllers
53 53 - fixes issue #756 cleanup repos didn't properly compose paths of repos to be cleaned up.
54 54 - fixed cache invalidation issues together with vcs_full_cache option
55 55 - repo scan should skip directories with starting with '.'
56 56 - fixes for issue #731, update-repoinfo sometimes failed to update data when changesets
57 57 were initial commits
58 58 - recursive mode of setting permission skips private repositories
59 59
60 60 1.5.2 (**2013-01-14**)
61 61 ----------------------
62 62
63 63 news
64 64 ++++
65 65
66 66 - IP restrictions for users. Each user can get a set of whitelist IP+mask for
67 67 extra protection. Useful for buildbots etc.
68 68 - added full last changeset info to lightweight dashboard. lightweight dashboard
69 69 is now fully functional replacement of original dashboard.
70 70 - implemented certain API calls for non-admin users.
71 71 - enabled all Markdown Extra plugins
72 72 - implemented #725 Pull Request View - Show origin repo URL
73 73 - show comments from pull requests into associated changesets
74 74
75 75 fixes
76 76 +++++
77 77
78 78 - update repoinfo script is more failsafe
79 79 - fixed #687 Lazy loaded tooltip bug with simultaneous ajax requests
80 80 - fixed #691: Notifications for pull requests: move link to top for better
81 81 readability
82 82 - fixed #699: fix missing fork docs for API
83 83 - fixed #693 Opening changeset from pull request fails
84 84 - fixed #710 File view stripping empty lines from beginning and end of file
85 85 - fixed issues with getting repos by path on windows, caused GIT hooks to fail
86 86 - fixed issues with groups paginator on main dashboard
87 87 - improved fetch/pull command for git repos, now pulling all refs
88 88 - fixed issue #719 Journal revision ID tooltip AJAX query path is incorrect
89 89 when running in a subdir
90 90 - fixed issue #702 API methods without arguments fail when "args":null
91 91 - set the status of changesets initially on pull request. Fixes issues #690 and #587
92 92
93 93 1.5.1 (**2012-12-13**)
94 94 ----------------------
95 95
96 96 news
97 97 ++++
98 98
99 99 - implements #677: Don't allow to close pull requests when they are
100 100 under-review status
101 101 - implemented #670 Implementation of Roles in Pull Request
102 102
103 103 fixes
104 104 +++++
105 105
106 106 - default permissions can get duplicated after migration
107 107 - fixed changeset status labels, they now select radio buttons
108 108 - #682 translation difficult for multi-line text
109 109 - #683 fixed difference between messages about not mapped repositories
110 110 - email: fail nicely when no SMTP server has been configured
111 111
112 112 1.5.0 (**2012-12-12**)
113 113 ----------------------
114 114
115 115 news
116 116 ++++
117 117
118 118 - new rewritten from scratch diff engine. 10x faster in edge cases. Handling
119 119 of file renames, copies, change flags and binary files
120 120 - added lightweight dashboard option. ref #500. New version of dashboard
121 121 page that doesn't use any VCS data and is super fast to render. Recommended
122 122 for large amount of repositories.
123 123 - implements #648 write Script for updating last modification time for
124 124 lightweight dashboard
125 125 - implemented compare engine for git repositories.
126 126 - LDAP failover, option to specify multiple servers
127 127 - added Errormator and Sentry support for monitoring RhodeCode
128 128 - implemented #628: Pass server URL to rc-extensions hooks
129 129 - new tooltip implementation - added lazy loading of changesets from journal
130 130 pages. This can significantly improve speed of rendering the page
131 131 - implements #632,added branch/tag/bookmarks info into feeds
132 132 added changeset link to body of message
133 133 - implemented #638 permissions overview to groups
134 134 - implements #636, lazy loading of history and authors to speed up source
135 135 pages rendering
136 136 - implemented #647, option to pass list of default encoding used to
137 137 encode to/decode from unicode
138 138 - added caching layer into RSS/ATOM feeds.
139 139 - basic implementation of cherry picking changesets for pull request, ref #575
140 140 - implemented #661 Add option to include diff in RSS feed
141 141 - implemented file history page for showing detailed changelog for a given file
142 142 - implemented #663 Admin/permission: specify default repogroup perms
143 143 - implemented #379 defaults settings page for creation of repositories, locking
144 144 statistics, downloads, repository type
145 145 - implemented #210 filtering of admin journal based on Whoosh Query language
146 146 - added parents/children links in changeset viewref #650
147 147
148 148 fixes
149 149 +++++
150 150
151 151 - fixed git version checker
152 152 - #586 patched basic auth handler to fix issues with git behind proxy
153 153 - #589 search urlgenerator didn't properly escape special characters
154 154 - fixed issue #614 Include repo name in delete confirmation dialog
155 155 - fixed #623: Lang meta-tag doesn't work with C#/C++
156 156 - fixes #612 Double quotes to Single quotes result in bad html in diff
157 157 - fixes #630 git statistics do too much work making them slow.
158 158 - fixes #625 Git-Tags are not displayed in Shortlog
159 159 - fix for issue #602, enforce str when setting mercurial UI object.
160 160 When this is used together with mercurial internal translation system
161 161 it can lead to UnicodeDecodeErrors
162 162 - fixes #645 Fix git handler when doing delete remote branch
163 163 - implements #649 added two seperate method for author and committer to VCS
164 164 changeset class switch author for git backed to be the real author not committer
165 165 - fix issue #504 RhodeCode is showing different versions of README on
166 166 different summary page loads
167 167 - implemented #658 Changing username in LDAP-Mode should not be allowed.
168 168 - fixes #652 switch to generator approach when doing file annotation to prevent
169 169 huge memory consumption
170 170 - fixes #666 move lockkey path location to cache_dir to ensure this path is
171 171 always writable for rhodecode server
172 172 - many more small fixes and improvements
173 173 - fixed issues with recursive scans on removed repositories that could take
174 174 long time on instance start
175 175
176 176 1.4.4 (**2012-10-08**)
177 177 ----------------------
178 178
179 179 news
180 180 ++++
181 181
182 182 - obfuscate db password in logs for engine connection string
183 183 - #574 Show pull request status also in shortlog (if any)
184 184 - remember selected tab in my account page
185 185 - Bumped mercurial version to 2.3.2
186 186 - #595 rcextension hook for repository delete
187 187
188 188 fixes
189 189 +++++
190 190
191 191 - Add git version detection to warn users that Git used in system is to
192 192 old. Ref #588 - also show git version in system details in settings page
193 193 - fixed files quick filter links
194 194 - #590 Add GET flag that controls the way the diff are generated, for pull
195 195 requests we want to use non-bundle based diffs, That are far better for
196 196 doing code reviews. The /compare url still uses bundle compare for full
197 197 comparison including the incoming changesets
198 198 - Fixed #585, checks for status of revision where to strict, and made
199 199 opening pull request with those revision impossible due to previously set
200 200 status. Checks now are made also for the repository.
201 201 - fixes #591 git backend was causing encoding errors when handling binary
202 202 files - added a test case for VCS lib tests
203 203 - fixed #597 commits in future get negative age.
204 204 - fixed #598 API docs methods had wrong members parameter as returned data
205 205
206 206 1.4.3 (**2012-09-28**)
207 207 ----------------------
208 208
209 209 news
210 210 ++++
211 211
212 212 - #558 Added config file to hooks extra data
213 213 - bumped mercurial version to 2.3.1
214 214 - #518 added possibility of specifying multiple patterns for issues
215 215 - update codemirror to latest version
216 216
217 217 fixes
218 218 +++++
219 219
220 220 - fixed #570 explicit user group permissions can overwrite owner permissions
221 221 - fixed #578 set proper PATH with current Python for Git
222 222 hooks to execute within same Python as RhodeCode
223 223 - fixed issue with Git bare repos that ends with .git in name
224 224
225 225 1.4.2 (**2012-09-12**)
226 226 ----------------------
227 227
228 228 news
229 229 ++++
230 230
231 231 - added option to menu to quick lock/unlock repository for users that have
232 232 write access to
233 233 - Implemented permissions for writing to repo
234 234 groups. Now only write access to group allows to create a repostiory
235 235 within that group
236 236 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
237 237 - updated translation for zh_CN
238 238
239 239 fixes
240 240 +++++
241 241
242 - fixed visual permissions check on repos groups inside groups
242 - fixed visual permissions check on repository groups inside groups
243 243 - fixed issues with non-ascii search terms in search, and indexers
244 244 - fixed parsing of page number in GET parameters
245 245 - fixed issues with generating pull-request overview for repos with
246 246 bookmarks and tags, also preview doesn't loose chosen revision from
247 247 select dropdown
248 248
249 249 1.4.1 (**2012-09-07**)
250 250 ----------------------
251 251
252 252 news
253 253 ++++
254 254
255 255 - always put a comment about code-review status change even if user send
256 256 empty data
257 257 - modified_on column saves repository update and it's going to be used
258 258 later for light version of main page ref #500
259 259 - pull request notifications send much nicer emails with details about pull
260 260 request
261 261 - #551 show breadcrumbs in summary view for repositories inside a group
262 262
263 263 fixes
264 264 +++++
265 265
266 266 - fixed migrations of permissions that can lead to inconsistency.
267 267 Some users sent feedback that after upgrading from older versions issues
268 268 with updating default permissions occurred. RhodeCode detects that now and
269 269 resets default user permission to initial state if there is a need for that.
270 270 Also forces users to set the default value for new forking permission.
271 271 - #535 improved apache wsgi example configuration in docs
272 272 - fixes #550 mercurial repositories comparision failed when origin repo had
273 273 additional not-common changesets
274 274 - fixed status of code-review in preview windows of pull request
275 275 - git forks were not initialized at bare repos
276 276 - fixes #555 fixes issues with comparing non-related repositories
277 277 - fixes #557 follower counter always counts up
278 278 - fixed issue #560 require push ssl checkbox wasn't shown when option was
279 279 enabled
280 280 - fixed #559
281 281 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
282 282 if it was a request to url by repository ID
283 283
284 284 1.4.0 (**2012-09-03**)
285 285 ----------------------
286 286
287 287 news
288 288 ++++
289 289
290 290 - new codereview system
291 291 - email map, allowing users to have multiple email addresses mapped into
292 292 their accounts
293 293 - improved git-hook system. Now all actions for git are logged into journal
294 294 including pushed revisions, user and IP address
295 295 - changed setup-app into setup-rhodecode and added default options to it.
296 296 - new git repos are created as bare now by default
297 297 - #464 added links to groups in permission box
298 298 - #465 mentions autocomplete inside comments boxes
299 299 - #469 added --update-only option to whoosh to re-index only given list
300 300 of repos in index
301 301 - rhodecode-api CLI client
302 302 - new git http protocol replaced buggy dulwich implementation.
303 303 Now based on pygrack & gitweb
304 304 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
305 305 reformated based on user suggestions. Additional rss/atom feeds for user
306 306 journal
307 307 - various i18n improvements
308 308 - #478 permissions overview for admin in user edit view
309 309 - File view now displays small gravatars off all authors of given file
310 310 - Implemented landing revisions. Each repository will get landing_rev attribute
311 311 that defines 'default' revision/branch for generating readme files
312 312 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
313 313 earliest possible call.
314 314 - Import remote svn repositories to mercurial using hgsubversion.
315 315 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
316 316 - RhodeCode can use alternative server for generating avatar icons
317 317 - implemented repositories locking. Pull locks, push unlocks. Also can be done
318 318 via API calls
319 319 - #538 form for permissions can handle multiple users at once
320 320
321 321 fixes
322 322 +++++
323 323
324 324 - improved translations
325 325 - fixes issue #455 Creating an archive generates an exception on Windows
326 326 - fixes #448 Download ZIP archive keeps file in /tmp open and results
327 327 in out of disk space
328 328 - fixes issue #454 Search results under Windows include proceeding
329 329 backslash
330 330 - fixed issue #450. Rhodecode no longer will crash when bad revision is
331 331 present in journal data.
332 332 - fix for issue #417, git execution was broken on windows for certain
333 333 commands.
334 334 - fixed #413. Don't disable .git directory for bare repos on deleting
335 335 - fixed issue #459. Changed the way of obtaining logger in reindex task.
336 336 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
337 337 reindexing modified files
338 338 - fixed #481 rhodecode emails are sent without Date header
339 339 - fixed #458 wrong count when no repos are present
340 340 - fixed issue #492 missing `\ No newline at end of file` test at the end of
341 341 new chunk in html diff
342 342 - full text search now works also for commit messages
343 343
344 344 1.3.6 (**2012-05-17**)
345 345 ----------------------
346 346
347 347 news
348 348 ++++
349 349
350 350 - chinese traditional translation
351 351 - changed setup-app into setup-rhodecode and added arguments for auto-setup
352 352 mode that doesn't need user interaction
353 353
354 354 fixes
355 355 +++++
356 356
357 357 - fixed no scm found warning
358 358 - fixed __future__ import error on rcextensions
359 359 - made simplejson required lib for speedup on JSON encoding
360 360 - fixes #449 bad regex could get more than revisions from parsing history
361 361 - don't clear DB session when CELERY_EAGER is turned ON
362 362
363 363 1.3.5 (**2012-05-10**)
364 364 ----------------------
365 365
366 366 news
367 367 ++++
368 368
369 369 - use ext_json for json module
370 370 - unified annotation view with file source view
371 371 - notification improvements, better inbox + css
372 372 - #419 don't strip passwords for login forms, make rhodecode
373 373 more compatible with LDAP servers
374 374 - Added HTTP_X_FORWARDED_FOR as another method of extracting
375 375 IP for pull/push logs. - moved all to base controller
376 376 - #415: Adding comment to changeset causes reload.
377 377 Comments are now added via ajax and doesn't reload the page
378 378 - #374 LDAP config is discarded when LDAP can't be activated
379 379 - limited push/pull operations are now logged for git in the journal
380 380 - bumped mercurial to 2.2.X series
381 381 - added support for displaying submodules in file-browser
382 382 - #421 added bookmarks in changelog view
383 383
384 384 fixes
385 385 +++++
386 386
387 387 - fixed dev-version marker for stable when served from source codes
388 388 - fixed missing permission checks on show forks page
389 389 - #418 cast to unicode fixes in notification objects
390 390 - #426 fixed mention extracting regex
391 391 - fixed remote-pulling for git remotes remopositories
392 392 - fixed #434: Error when accessing files or changesets of a git repository
393 393 with submodules
394 394 - fixed issue with empty APIKEYS for users after registration ref. #438
395 395 - fixed issue with getting README files from git repositories
396 396
397 397 1.3.4 (**2012-03-28**)
398 398 ----------------------
399 399
400 400 news
401 401 ++++
402 402
403 403 - Whoosh logging is now controlled by the .ini files logging setup
404 404 - added clone-url into edit form on /settings page
405 405 - added help text into repo add/edit forms
406 406 - created rcextensions module with additional mappings (ref #322) and
407 407 post push/pull/create repo hooks callbacks
408 408 - implemented #377 Users view for his own permissions on account page
409 - #399 added inheritance of permissions for user group on repos groups
409 - #399 added inheritance of permissions for user group on repository groups
410 410 - #401 repository group is automatically pre-selected when adding repos
411 411 inside a repository group
412 412 - added alternative HTTP 403 response when client failed to authenticate. Helps
413 413 solving issues with Mercurial and LDAP
414 414 - #402 removed group prefix from repository name when listing repositories
415 415 inside a group
416 416 - added gravatars into permission view and permissions autocomplete
417 417 - #347 when running multiple RhodeCode instances, properly invalidates cache
418 418 for all registered servers
419 419
420 420 fixes
421 421 +++++
422 422
423 423 - fixed #390 cache invalidation problems on repos inside group
424 424 - fixed #385 clone by ID url was loosing proxy prefix in URL
425 425 - fixed some unicode problems with waitress
426 426 - fixed issue with escaping < and > in changeset commits
427 427 - fixed error occurring during recursive group creation in API
428 428 create_repo function
429 429 - fixed #393 py2.5 fixes for routes url generator
430 430 - fixed #397 Private repository groups shows up before login
431 431 - fixed #396 fixed problems with revoking users in nested groups
432 432 - fixed mysql unicode issues + specified InnoDB as default engine with
433 433 utf8 charset
434 434 - #406 trim long branch/tag names in changelog to not break UI
435 435
436 436 1.3.3 (**2012-03-02**)
437 437 ----------------------
438 438
439 439 news
440 440 ++++
441 441
442 442
443 443 fixes
444 444 +++++
445 445
446 446 - fixed some python2.5 compatibility issues
447 447 - fixed issues with removed repos was accidentally added as groups, after
448 448 full rescan of paths
449 449 - fixes #376 Cannot edit user (using container auth)
450 450 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
451 451 configuration
452 452 - fixed initial sorting of repos inside repo group
453 453 - fixes issue when user tried to resubmit same permission into user/user_groups
454 454 - bumped beaker version that fixes #375 leap error bug
455 455 - fixed raw_changeset for git. It was generated with hg patch headers
456 456 - fixed vcs issue with last_changeset for filenodes
457 457 - fixed missing commit after hook delete
458 458 - fixed #372 issues with git operation detection that caused a security issue
459 459 for git repos
460 460
461 461 1.3.2 (**2012-02-28**)
462 462 ----------------------
463 463
464 464 news
465 465 ++++
466 466
467 467
468 468 fixes
469 469 +++++
470 470
471 471 - fixed git protocol issues with repos-groups
472 472 - fixed git remote repos validator that prevented from cloning remote git repos
473 473 - fixes #370 ending slashes fixes for repo and groups
474 474 - fixes #368 improved git-protocol detection to handle other clients
475 475 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
476 476 Moved To Root
477 477 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
478 478 - fixed #373 missing cascade drop on user_group_to_perm table
479 479
480 480 1.3.1 (**2012-02-27**)
481 481 ----------------------
482 482
483 483 news
484 484 ++++
485 485
486 486
487 487 fixes
488 488 +++++
489 489
490 490 - redirection loop occurs when remember-me wasn't checked during login
491 491 - fixes issues with git blob history generation
492 492 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
493 493
494 494 1.3.0 (**2012-02-26**)
495 495 ----------------------
496 496
497 497 news
498 498 ++++
499 499
500 500 - code review, inspired by github code-comments
501 501 - #215 rst and markdown README files support
502 502 - #252 Container-based and proxy pass-through authentication support
503 503 - #44 branch browser. Filtering of changelog by branches
504 504 - mercurial bookmarks support
505 505 - new hover top menu, optimized to add maximum size for important views
506 506 - configurable clone url template with possibility to specify protocol like
507 507 ssh:// or http:// and also manually alter other parts of clone_url.
508 508 - enabled largefiles extension by default
509 509 - optimized summary file pages and saved a lot of unused space in them
510 510 - #239 option to manually mark repository as fork
511 511 - #320 mapping of commit authors to RhodeCode users
512 512 - #304 hashes are displayed using monospace font
513 513 - diff configuration, toggle white lines and context lines
514 514 - #307 configurable diffs, whitespace toggle, increasing context lines
515 515 - sorting on branches, tags and bookmarks using YUI datatable
516 516 - improved file filter on files page
517 517 - implements #330 api method for listing nodes ar particular revision
518 518 - #73 added linking issues in commit messages to chosen issue tracker url
519 519 based on user defined regular expression
520 520 - added linking of changesets in commit messages
521 521 - new compact changelog with expandable commit messages
522 522 - firstname and lastname are optional in user creation
523 523 - #348 added post-create repository hook
524 524 - #212 global encoding settings is now configurable from .ini files
525 525 - #227 added repository groups permissions
526 526 - markdown gets codehilite extensions
527 527 - new API methods, delete_repositories, grante/revoke permissions for groups
528 528 and repos
529 529
530 530
531 531 fixes
532 532 +++++
533 533
534 534 - rewrote dbsession management for atomic operations, and better error handling
535 535 - fixed sorting of repo tables
536 536 - #326 escape of special html entities in diffs
537 537 - normalized user_name => username in api attributes
538 538 - fixes #298 ldap created users with mixed case emails created conflicts
539 539 on saving a form
540 540 - fixes issue when owner of a repo couldn't revoke permissions for users
541 541 and groups
542 542 - fixes #271 rare JSON serialization problem with statistics
543 543 - fixes #337 missing validation check for conflicting names of a group with a
544 544 repository group
545 545 - #340 fixed session problem for mysql and celery tasks
546 546 - fixed #331 RhodeCode mangles repository names if the a repository group
547 547 contains the "full path" to the repositories
548 548 - #355 RhodeCode doesn't store encrypted LDAP passwords
549 549
550 550 1.2.5 (**2012-01-28**)
551 551 ----------------------
552 552
553 553 news
554 554 ++++
555 555
556 556 fixes
557 557 +++++
558 558
559 559 - #340 Celery complains about MySQL server gone away, added session cleanup
560 560 for celery tasks
561 561 - #341 "scanning for repositories in None" log message during Rescan was missing
562 562 a parameter
563 563 - fixed creating archives with subrepos. Some hooks were triggered during that
564 564 operation leading to crash.
565 565 - fixed missing email in account page.
566 566 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
567 567 forking on windows impossible
568 568
569 569 1.2.4 (**2012-01-19**)
570 570 ----------------------
571 571
572 572 news
573 573 ++++
574 574
575 575 - RhodeCode is bundled with mercurial series 2.0.X by default, with
576 576 full support to largefiles extension. Enabled by default in new installations
577 577 - #329 Ability to Add/Remove Groups to/from a Repository via AP
578 578 - added requires.txt file with requirements
579 579
580 580 fixes
581 581 +++++
582 582
583 583 - fixes db session issues with celery when emailing admins
584 584 - #331 RhodeCode mangles repository names if the a repository group
585 585 contains the "full path" to the repositories
586 586 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
587 587 - DB session cleanup after hg protocol operations, fixes issues with
588 588 `mysql has gone away` errors
589 589 - #333 doc fixes for get_repo api function
590 590 - #271 rare JSON serialization problem with statistics enabled
591 591 - #337 Fixes issues with validation of repository name conflicting with
592 592 a group name. A proper message is now displayed.
593 593 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
594 594 doesn't work
595 595 - #316 fixes issues with web description in hgrc files
596 596
597 597 1.2.3 (**2011-11-02**)
598 598 ----------------------
599 599
600 600 news
601 601 ++++
602 602
603 - added option to manage repos group for non admin users
603 - added option to manage repository group for non admin users
604 604 - added following API methods for get_users, create_user, get_users_groups,
605 605 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
606 606 get_repo, create_repo, add_user_to_repo
607 607 - implements #237 added password confirmation for my account
608 608 and admin edit user.
609 609 - implements #291 email notification for global events are now sent to all
610 610 administrator users, and global config email.
611 611
612 612 fixes
613 613 +++++
614 614
615 615 - added option for passing auth method for smtp mailer
616 616 - #276 issue with adding a single user with id>10 to usergroups
617 617 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
618 618 - #288 fixes managing of repos in a group for non admin user
619 619
620 620 1.2.2 (**2011-10-17**)
621 621 ----------------------
622 622
623 623 news
624 624 ++++
625 625
626 626 - #226 repo groups are available by path instead of numerical id
627 627
628 628 fixes
629 629 +++++
630 630
631 631 - #259 Groups with the same name but with different parent group
632 632 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
633 633 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
634 634 - #265 ldap save fails sometimes on converting attributes to booleans,
635 635 added getter and setter into model that will prevent from this on db model level
636 636 - fixed problems with timestamps issues #251 and #213
637 637 - fixes #266 RhodeCode allows to create repo with the same name and in
638 638 the same parent as group
639 639 - fixes #245 Rescan of the repositories on Windows
640 640 - fixes #248 cannot edit repos inside a group on windows
641 641 - fixes #219 forking problems on windows
642 642
643 643 1.2.1 (**2011-10-08**)
644 644 ----------------------
645 645
646 646 news
647 647 ++++
648 648
649 649
650 650 fixes
651 651 +++++
652 652
653 653 - fixed problems with basic auth and push problems
654 654 - gui fixes
655 655 - fixed logger
656 656
657 657 1.2.0 (**2011-10-07**)
658 658 ----------------------
659 659
660 660 news
661 661 ++++
662 662
663 663 - implemented #47 repository groups
664 664 - implemented #89 Can setup google analytics code from settings menu
665 665 - implemented #91 added nicer looking archive urls with more download options
666 666 like tags, branches
667 667 - implemented #44 into file browsing, and added follow branch option
668 668 - implemented #84 downloads can be enabled/disabled for each repository
669 669 - anonymous repository can be cloned without having to pass default:default
670 670 into clone url
671 671 - fixed #90 whoosh indexer can index chooses repositories passed in command
672 672 line
673 673 - extended journal with day aggregates and paging
674 674 - implemented #107 source code lines highlight ranges
675 675 - implemented #93 customizable changelog on combined revision ranges -
676 676 equivalent of githubs compare view
677 677 - implemented #108 extended and more powerful LDAP configuration
678 678 - implemented #56 user groups
679 679 - major code rewrites optimized codes for speed and memory usage
680 680 - raw and diff downloads are now in git format
681 681 - setup command checks for write access to given path
682 682 - fixed many issues with international characters and unicode. It uses utf8
683 683 decode with replace to provide less errors even with non utf8 encoded strings
684 684 - #125 added API KEY access to feeds
685 685 - #109 Repository can be created from external Mercurial link (aka. remote
686 686 repository, and manually updated (via pull) from admin panel
687 687 - beta git support - push/pull server + basic view for git repos
688 688 - added followers page and forks page
689 689 - server side file creation (with binary file upload interface)
690 690 and edition with commits powered by codemirror
691 691 - #111 file browser file finder, quick lookup files on whole file tree
692 692 - added quick login sliding menu into main page
693 693 - changelog uses lazy loading of affected files details, in some scenarios
694 694 this can improve speed of changelog page dramatically especially for
695 695 larger repositories.
696 696 - implements #214 added support for downloading subrepos in download menu.
697 697 - Added basic API for direct operations on rhodecode via JSON
698 698 - Implemented advanced hook management
699 699
700 700 fixes
701 701 +++++
702 702
703 703 - fixed file browser bug, when switching into given form revision the url was
704 704 not changing
705 705 - fixed propagation to error controller on simplehg and simplegit middlewares
706 706 - fixed error when trying to make a download on empty repository
707 707 - fixed problem with '[' chars in commit messages in journal
708 708 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
709 709 - journal fork fixes
710 710 - removed issue with space inside renamed repository after deletion
711 711 - fixed strange issue on formencode imports
712 712 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
713 713 - #150 fixes for errors on repositories mapped in db but corrupted in
714 714 filesystem
715 715 - fixed problem with ascendant characters in realm #181
716 716 - fixed problem with sqlite file based database connection pool
717 717 - whoosh indexer and code stats share the same dynamic extensions map
718 718 - fixes #188 - relationship delete of repo_to_perm entry on user removal
719 719 - fixes issue #189 Trending source files shows "show more" when no more exist
720 720 - fixes issue #197 Relative paths for pidlocks
721 721 - fixes issue #198 password will require only 3 chars now for login form
722 722 - fixes issue #199 wrong redirection for non admin users after creating a repository
723 723 - fixes issues #202, bad db constraint made impossible to attach same group
724 724 more than one time. Affects only mysql/postgres
725 725 - fixes #218 os.kill patch for windows was missing sig param
726 726 - improved rendering of dag (they are not trimmed anymore when number of
727 727 heads exceeds 5)
728 728
729 729 1.1.8 (**2011-04-12**)
730 730 ----------------------
731 731
732 732 news
733 733 ++++
734 734
735 735 - improved windows support
736 736
737 737 fixes
738 738 +++++
739 739
740 740 - fixed #140 freeze of python dateutil library, since new version is python2.x
741 741 incompatible
742 742 - setup-app will check for write permission in given path
743 743 - cleaned up license info issue #149
744 744 - fixes for issues #137,#116 and problems with unicode and accented characters.
745 745 - fixes crashes on gravatar, when passed in email as unicode
746 746 - fixed tooltip flickering problems
747 747 - fixed came_from redirection on windows
748 748 - fixed logging modules, and sql formatters
749 749 - windows fixes for os.kill issue #133
750 750 - fixes path splitting for windows issues #148
751 751 - fixed issue #143 wrong import on migration to 1.1.X
752 752 - fixed problems with displaying binary files, thanks to Thomas Waldmann
753 753 - removed name from archive files since it's breaking ui for long repo names
754 754 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
755 755 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
756 756 Thomas Waldmann
757 757 - fixed issue #166 summary pager was skipping 10 revisions on second page
758 758
759 759
760 760 1.1.7 (**2011-03-23**)
761 761 ----------------------
762 762
763 763 news
764 764 ++++
765 765
766 766 fixes
767 767 +++++
768 768
769 769 - fixed (again) #136 installation support for FreeBSD
770 770
771 771
772 772 1.1.6 (**2011-03-21**)
773 773 ----------------------
774 774
775 775 news
776 776 ++++
777 777
778 778 fixes
779 779 +++++
780 780
781 781 - fixed #136 installation support for FreeBSD
782 782 - RhodeCode will check for python version during installation
783 783
784 784 1.1.5 (**2011-03-17**)
785 785 ----------------------
786 786
787 787 news
788 788 ++++
789 789
790 790 - basic windows support, by exchanging pybcrypt into sha256 for windows only
791 791 highly inspired by idea of mantis406
792 792
793 793 fixes
794 794 +++++
795 795
796 796 - fixed sorting by author in main page
797 797 - fixed crashes with diffs on binary files
798 798 - fixed #131 problem with boolean values for LDAP
799 799 - fixed #122 mysql problems thanks to striker69
800 800 - fixed problem with errors on calling raw/raw_files/annotate functions
801 801 with unknown revisions
802 802 - fixed returned rawfiles attachment names with international character
803 803 - cleaned out docs, big thanks to Jason Harris
804 804
805 805 1.1.4 (**2011-02-19**)
806 806 ----------------------
807 807
808 808 news
809 809 ++++
810 810
811 811 fixes
812 812 +++++
813 813
814 814 - fixed formencode import problem on settings page, that caused server crash
815 815 when that page was accessed as first after server start
816 816 - journal fixes
817 817 - fixed option to access repository just by entering http://server/<repo_name>
818 818
819 819 1.1.3 (**2011-02-16**)
820 820 ----------------------
821 821
822 822 news
823 823 ++++
824 824
825 825 - implemented #102 allowing the '.' character in username
826 826 - added option to access repository just by entering http://server/<repo_name>
827 827 - celery task ignores result for better performance
828 828
829 829 fixes
830 830 +++++
831 831
832 832 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
833 833 apollo13 and Johan Walles
834 834 - small fixes in journal
835 835 - fixed problems with getting setting for celery from .ini files
836 836 - registration, password reset and login boxes share the same title as main
837 837 application now
838 838 - fixed #113: to high permissions to fork repository
839 839 - fixed problem with '[' chars in commit messages in journal
840 840 - removed issue with space inside renamed repository after deletion
841 841 - db transaction fixes when filesystem repository creation failed
842 842 - fixed #106 relation issues on databases different than sqlite
843 843 - fixed static files paths links to use of url() method
844 844
845 845 1.1.2 (**2011-01-12**)
846 846 ----------------------
847 847
848 848 news
849 849 ++++
850 850
851 851
852 852 fixes
853 853 +++++
854 854
855 855 - fixes #98 protection against float division of percentage stats
856 856 - fixed graph bug
857 857 - forced webhelpers version since it was making troubles during installation
858 858
859 859 1.1.1 (**2011-01-06**)
860 860 ----------------------
861 861
862 862 news
863 863 ++++
864 864
865 865 - added force https option into ini files for easier https usage (no need to
866 866 set server headers with this options)
867 867 - small css updates
868 868
869 869 fixes
870 870 +++++
871 871
872 872 - fixed #96 redirect loop on files view on repositories without changesets
873 873 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
874 874 and server crashed with errors
875 875 - fixed large tooltips problems on main page
876 876 - fixed #92 whoosh indexer is more error proof
877 877
878 878 1.1.0 (**2010-12-18**)
879 879 ----------------------
880 880
881 881 news
882 882 ++++
883 883
884 884 - rewrite of internals for vcs >=0.1.10
885 885 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
886 886 with older clients
887 887 - anonymous access, authentication via ldap
888 888 - performance upgrade for cached repos list - each repository has its own
889 889 cache that's invalidated when needed.
890 890 - performance upgrades on repositories with large amount of commits (20K+)
891 891 - main page quick filter for filtering repositories
892 892 - user dashboards with ability to follow chosen repositories actions
893 893 - sends email to admin on new user registration
894 894 - added cache/statistics reset options into repository settings
895 895 - more detailed action logger (based on hooks) with pushed changesets lists
896 896 and options to disable those hooks from admin panel
897 897 - introduced new enhanced changelog for merges that shows more accurate results
898 898 - new improved and faster code stats (based on pygments lexers mapping tables,
899 899 showing up to 10 trending sources for each repository. Additionally stats
900 900 can be disabled in repository settings.
901 901 - gui optimizations, fixed application width to 1024px
902 902 - added cut off (for large files/changesets) limit into config files
903 903 - whoosh, celeryd, upgrade moved to paster command
904 904 - other than sqlite database backends can be used
905 905
906 906 fixes
907 907 +++++
908 908
909 909 - fixes #61 forked repo was showing only after cache expired
910 910 - fixes #76 no confirmation on user deletes
911 911 - fixes #66 Name field misspelled
912 912 - fixes #72 block user removal when he owns repositories
913 913 - fixes #69 added password confirmation fields
914 914 - fixes #87 RhodeCode crashes occasionally on updating repository owner
915 915 - fixes #82 broken annotations on files with more than 1 blank line at the end
916 916 - a lot of fixes and tweaks for file browser
917 917 - fixed detached session issues
918 918 - fixed when user had no repos he would see all repos listed in my account
919 919 - fixed ui() instance bug when global hgrc settings was loaded for server
920 920 instance and all hgrc options were merged with our db ui() object
921 921 - numerous small bugfixes
922 922
923 923 (special thanks for TkSoh for detailed feedback)
924 924
925 925
926 926 1.0.2 (**2010-11-12**)
927 927 ----------------------
928 928
929 929 news
930 930 ++++
931 931
932 932 - tested under python2.7
933 933 - bumped sqlalchemy and celery versions
934 934
935 935 fixes
936 936 +++++
937 937
938 938 - fixed #59 missing graph.js
939 939 - fixed repo_size crash when repository had broken symlinks
940 940 - fixed python2.5 crashes.
941 941
942 942
943 943 1.0.1 (**2010-11-10**)
944 944 ----------------------
945 945
946 946 news
947 947 ++++
948 948
949 949 - small css updated
950 950
951 951 fixes
952 952 +++++
953 953
954 954 - fixed #53 python2.5 incompatible enumerate calls
955 955 - fixed #52 disable mercurial extension for web
956 956 - fixed #51 deleting repositories don't delete it's dependent objects
957 957
958 958
959 959 1.0.0 (**2010-11-02**)
960 960 ----------------------
961 961
962 962 - security bugfix simplehg wasn't checking for permissions on commands
963 963 other than pull or push.
964 964 - fixed doubled messages after push or pull in admin journal
965 965 - templating and css corrections, fixed repo switcher on chrome, updated titles
966 966 - admin menu accessible from options menu on repository view
967 967 - permissions cached queries
968 968
969 969 1.0.0rc4 (**2010-10-12**)
970 970 --------------------------
971 971
972 972 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
973 973 - removed cache_manager settings from sqlalchemy meta
974 974 - added sqlalchemy cache settings to ini files
975 975 - validated password length and added second try of failure on paster setup-app
976 976 - fixed setup database destroy prompt even when there was no db
977 977
978 978
979 979 1.0.0rc3 (**2010-10-11**)
980 980 -------------------------
981 981
982 982 - fixed i18n during installation.
983 983
984 984 1.0.0rc2 (**2010-10-11**)
985 985 -------------------------
986 986
987 987 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
988 988 occure. After vcs is fixed it'll be put back again.
989 989 - templating/css rewrites, optimized css.
@@ -1,661 +1,661 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10
11 11 # prefix for non repository related links needs to be prefixed with `/`
12 12 ADMIN_PREFIX = '/_admin'
13 13
14 14
15 15 def make_map(config):
16 16 """Create, configure and return the routes Mapper"""
17 17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 18 always_scan=config['debug'])
19 19 rmap.minimization = False
20 20 rmap.explicit = False
21 21
22 22 from rhodecode.lib.utils import is_valid_repo
23 23 from rhodecode.lib.utils import is_valid_repos_group
24 24
25 25 def check_repo(environ, match_dict):
26 26 """
27 27 check for valid repository for proper 404 handling
28 28
29 29 :param environ:
30 30 :param match_dict:
31 31 """
32 32 from rhodecode.model.db import Repository
33 33 repo_name = match_dict.get('repo_name')
34 34
35 35 if match_dict.get('f_path'):
36 36 #fix for multiple initial slashes that causes errors
37 37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
38 38
39 39 try:
40 40 by_id = repo_name.split('_')
41 41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
42 42 repo_name = Repository.get(by_id[1]).repo_name
43 43 match_dict['repo_name'] = repo_name
44 44 except Exception:
45 45 pass
46 46
47 47 return is_valid_repo(repo_name, config['base_path'])
48 48
49 49 def check_group(environ, match_dict):
50 50 """
51 51 check for valid repository group for proper 404 handling
52 52
53 53 :param environ:
54 54 :param match_dict:
55 55 """
56 56 repos_group_name = match_dict.get('group_name')
57 57 return is_valid_repos_group(repos_group_name, config['base_path'])
58 58
59 59 def check_group_skip_path(environ, match_dict):
60 60 """
61 61 check for valid repository group for proper 404 handling, but skips
62 62 verification of existing path
63 63
64 64 :param environ:
65 65 :param match_dict:
66 66 """
67 67 repos_group_name = match_dict.get('group_name')
68 68 return is_valid_repos_group(repos_group_name, config['base_path'],
69 69 skip_path_check=True)
70 70
71 71 def check_int(environ, match_dict):
72 72 return match_dict.get('id').isdigit()
73 73
74 74 # The ErrorController route (handles 404/500 error pages); it should
75 75 # likely stay at the top, ensuring it can always be resolved
76 76 rmap.connect('/error/{action}', controller='error')
77 77 rmap.connect('/error/{action}/{id}', controller='error')
78 78
79 79 #==========================================================================
80 80 # CUSTOM ROUTES HERE
81 81 #==========================================================================
82 82
83 83 #MAIN PAGE
84 84 rmap.connect('home', '/', controller='home', action='index')
85 85 rmap.connect('repo_switcher', '/repos', controller='home',
86 86 action='repo_switcher')
87 87 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
88 88 controller='home', action='branch_tag_switcher')
89 89 rmap.connect('bugtracker',
90 90 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
91 91 _static=True)
92 92 rmap.connect('rst_help',
93 93 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
94 94 _static=True)
95 95 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
96 96
97 97 #ADMIN REPOSITORY REST ROUTES
98 98 with rmap.submapper(path_prefix=ADMIN_PREFIX,
99 99 controller='admin/repos') as m:
100 100 m.connect("repos", "/repos",
101 101 action="create", conditions=dict(method=["POST"]))
102 102 m.connect("repos", "/repos",
103 103 action="index", conditions=dict(method=["GET"]))
104 104 m.connect("formatted_repos", "/repos.{format}",
105 105 action="index",
106 106 conditions=dict(method=["GET"]))
107 107 m.connect("new_repo", "/repos/new",
108 108 action="new", conditions=dict(method=["GET"]))
109 109 #TODO: refactor the name
110 110 m.connect("admin_settings_create_repository", "/create_repository",
111 111 action="create_repository", conditions=dict(method=["GET"]))
112 112 m.connect("formatted_new_repo", "/repos/new.{format}",
113 113 action="new", conditions=dict(method=["GET"]))
114 114 m.connect("/repos/{repo_name:.*?}",
115 115 action="update", conditions=dict(method=["PUT"],
116 116 function=check_repo))
117 117 m.connect("/repos/{repo_name:.*?}",
118 118 action="delete", conditions=dict(method=["DELETE"],
119 119 function=check_repo))
120 120 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
121 121 action="edit", conditions=dict(method=["GET"],
122 122 function=check_repo))
123 123 m.connect("repo", "/repos/{repo_name:.*?}",
124 124 action="show", conditions=dict(method=["GET"],
125 125 function=check_repo))
126 126 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
127 127 action="show", conditions=dict(method=["GET"],
128 128 function=check_repo))
129 129 #add repo perm member
130 130 m.connect('set_repo_perm_member', "/set_repo_perm_member/{repo_name:.*?}",
131 131 action="set_repo_perm_member",
132 132 conditions=dict(method=["POST"], function=check_repo))
133 133
134 134 #ajax delete repo perm user
135 135 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
136 136 action="delete_perm_user",
137 137 conditions=dict(method=["DELETE"], function=check_repo))
138 138
139 139 #ajax delete repo perm users_group
140 140 m.connect('delete_repo_users_group',
141 141 "/repos_delete_users_group/{repo_name:.*?}",
142 142 action="delete_perm_users_group",
143 143 conditions=dict(method=["DELETE"], function=check_repo))
144 144
145 145 #settings actions
146 146 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
147 147 action="repo_stats", conditions=dict(method=["DELETE"],
148 148 function=check_repo))
149 149 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
150 150 action="repo_cache", conditions=dict(method=["DELETE"],
151 151 function=check_repo))
152 152 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
153 153 action="repo_public_journal", conditions=dict(method=["PUT"],
154 154 function=check_repo))
155 155 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
156 156 action="repo_pull", conditions=dict(method=["PUT"],
157 157 function=check_repo))
158 158 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
159 159 action="repo_as_fork", conditions=dict(method=["PUT"],
160 160 function=check_repo))
161 161 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
162 162 action="repo_locking", conditions=dict(method=["PUT"],
163 163 function=check_repo))
164 164 m.connect('toggle_locking', "/locking_toggle/{repo_name:.*?}",
165 165 action="toggle_locking", conditions=dict(method=["GET"],
166 166 function=check_repo))
167 167
168 168 #repo fields
169 169 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
170 170 action="create_repo_field", conditions=dict(method=["PUT"],
171 171 function=check_repo))
172 172
173 173 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
174 174 action="delete_repo_field", conditions=dict(method=["DELETE"],
175 175 function=check_repo))
176 176
177 177 with rmap.submapper(path_prefix=ADMIN_PREFIX,
178 178 controller='admin/repos_groups') as m:
179 179 m.connect("repos_groups", "/repos_groups",
180 180 action="create", conditions=dict(method=["POST"]))
181 181 m.connect("repos_groups", "/repos_groups",
182 182 action="index", conditions=dict(method=["GET"]))
183 183 m.connect("formatted_repos_groups", "/repos_groups.{format}",
184 184 action="index", conditions=dict(method=["GET"]))
185 185 m.connect("new_repos_group", "/repos_groups/new",
186 186 action="new", conditions=dict(method=["GET"]))
187 187 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
188 188 action="new", conditions=dict(method=["GET"]))
189 189 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
190 190 action="update", conditions=dict(method=["PUT"],
191 191 function=check_group))
192 192 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
193 193 action="delete", conditions=dict(method=["DELETE"],
194 194 function=check_group_skip_path))
195 195 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
196 196 action="edit", conditions=dict(method=["GET"],
197 197 function=check_group))
198 198 m.connect("formatted_edit_repos_group",
199 199 "/repos_groups/{group_name:.*?}.{format}/edit",
200 200 action="edit", conditions=dict(method=["GET"],
201 201 function=check_group))
202 202 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
203 203 action="show", conditions=dict(method=["GET"],
204 204 function=check_group))
205 205 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
206 206 action="show", conditions=dict(method=["GET"],
207 207 function=check_group))
208 # ajax delete repos group perm user
208 # ajax delete repository group perm user
209 209 m.connect('delete_repos_group_user_perm',
210 210 "/delete_repos_group_user_perm/{group_name:.*?}",
211 211 action="delete_repos_group_user_perm",
212 212 conditions=dict(method=["DELETE"], function=check_group))
213 213
214 # ajax delete repos group perm users_group
214 # ajax delete repository group perm users_group
215 215 m.connect('delete_repos_group_users_group_perm',
216 216 "/delete_repos_group_users_group_perm/{group_name:.*?}",
217 217 action="delete_repos_group_users_group_perm",
218 218 conditions=dict(method=["DELETE"], function=check_group))
219 219
220 220 #ADMIN USER REST ROUTES
221 221 with rmap.submapper(path_prefix=ADMIN_PREFIX,
222 222 controller='admin/users') as m:
223 223 m.connect("users", "/users",
224 224 action="create", conditions=dict(method=["POST"]))
225 225 m.connect("users", "/users",
226 226 action="index", conditions=dict(method=["GET"]))
227 227 m.connect("formatted_users", "/users.{format}",
228 228 action="index", conditions=dict(method=["GET"]))
229 229 m.connect("new_user", "/users/new",
230 230 action="new", conditions=dict(method=["GET"]))
231 231 m.connect("formatted_new_user", "/users/new.{format}",
232 232 action="new", conditions=dict(method=["GET"]))
233 233 m.connect("update_user", "/users/{id}",
234 234 action="update", conditions=dict(method=["PUT"]))
235 235 m.connect("delete_user", "/users/{id}",
236 236 action="delete", conditions=dict(method=["DELETE"]))
237 237 m.connect("edit_user", "/users/{id}/edit",
238 238 action="edit", conditions=dict(method=["GET"]))
239 239 m.connect("formatted_edit_user",
240 240 "/users/{id}.{format}/edit",
241 241 action="edit", conditions=dict(method=["GET"]))
242 242 m.connect("user", "/users/{id}",
243 243 action="show", conditions=dict(method=["GET"]))
244 244 m.connect("formatted_user", "/users/{id}.{format}",
245 245 action="show", conditions=dict(method=["GET"]))
246 246
247 247 #EXTRAS USER ROUTES
248 248 m.connect("user_perm", "/users_perm/{id}",
249 249 action="update_perm", conditions=dict(method=["PUT"]))
250 250 m.connect("user_emails", "/users_emails/{id}",
251 251 action="add_email", conditions=dict(method=["PUT"]))
252 252 m.connect("user_emails_delete", "/users_emails/{id}",
253 253 action="delete_email", conditions=dict(method=["DELETE"]))
254 254 m.connect("user_ips", "/users_ips/{id}",
255 255 action="add_ip", conditions=dict(method=["PUT"]))
256 256 m.connect("user_ips_delete", "/users_ips/{id}",
257 257 action="delete_ip", conditions=dict(method=["DELETE"]))
258 258
259 259 #ADMIN USER GROUPS REST ROUTES
260 260 with rmap.submapper(path_prefix=ADMIN_PREFIX,
261 261 controller='admin/users_groups') as m:
262 262 m.connect("users_groups", "/users_groups",
263 263 action="create", conditions=dict(method=["POST"]))
264 264 m.connect("users_groups", "/users_groups",
265 265 action="index", conditions=dict(method=["GET"]))
266 266 m.connect("formatted_users_groups", "/users_groups.{format}",
267 267 action="index", conditions=dict(method=["GET"]))
268 268 m.connect("new_users_group", "/users_groups/new",
269 269 action="new", conditions=dict(method=["GET"]))
270 270 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
271 271 action="new", conditions=dict(method=["GET"]))
272 272 m.connect("update_users_group", "/users_groups/{id}",
273 273 action="update", conditions=dict(method=["PUT"]))
274 274 m.connect("delete_users_group", "/users_groups/{id}",
275 275 action="delete", conditions=dict(method=["DELETE"]))
276 276 m.connect("edit_users_group", "/users_groups/{id}/edit",
277 277 action="edit", conditions=dict(method=["GET"]))
278 278 m.connect("formatted_edit_users_group",
279 279 "/users_groups/{id}.{format}/edit",
280 280 action="edit", conditions=dict(method=["GET"]))
281 281 m.connect("users_group", "/users_groups/{id}",
282 282 action="show", conditions=dict(method=["GET"]))
283 283 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
284 284 action="show", conditions=dict(method=["GET"]))
285 285
286 286 #EXTRAS USER ROUTES
287 287 m.connect("users_group_perm", "/users_groups_perm/{id}",
288 288 action="update_perm", conditions=dict(method=["PUT"]))
289 289
290 290 #ADMIN GROUP REST ROUTES
291 291 rmap.resource('group', 'groups',
292 292 controller='admin/groups', path_prefix=ADMIN_PREFIX)
293 293
294 294 #ADMIN PERMISSIONS REST ROUTES
295 295 rmap.resource('permission', 'permissions',
296 296 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
297 297
298 298 #ADMIN DEFAULTS REST ROUTES
299 299 rmap.resource('default', 'defaults',
300 300 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
301 301
302 302 ##ADMIN LDAP SETTINGS
303 303 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
304 304 controller='admin/ldap_settings', action='ldap_settings',
305 305 conditions=dict(method=["POST"]))
306 306
307 307 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
308 308 controller='admin/ldap_settings')
309 309
310 310 #ADMIN SETTINGS REST ROUTES
311 311 with rmap.submapper(path_prefix=ADMIN_PREFIX,
312 312 controller='admin/settings') as m:
313 313 m.connect("admin_settings", "/settings",
314 314 action="create", conditions=dict(method=["POST"]))
315 315 m.connect("admin_settings", "/settings",
316 316 action="index", conditions=dict(method=["GET"]))
317 317 m.connect("formatted_admin_settings", "/settings.{format}",
318 318 action="index", conditions=dict(method=["GET"]))
319 319 m.connect("admin_new_setting", "/settings/new",
320 320 action="new", conditions=dict(method=["GET"]))
321 321 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
322 322 action="new", conditions=dict(method=["GET"]))
323 323 m.connect("/settings/{setting_id}",
324 324 action="update", conditions=dict(method=["PUT"]))
325 325 m.connect("/settings/{setting_id}",
326 326 action="delete", conditions=dict(method=["DELETE"]))
327 327 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
328 328 action="edit", conditions=dict(method=["GET"]))
329 329 m.connect("formatted_admin_edit_setting",
330 330 "/settings/{setting_id}.{format}/edit",
331 331 action="edit", conditions=dict(method=["GET"]))
332 332 m.connect("admin_setting", "/settings/{setting_id}",
333 333 action="show", conditions=dict(method=["GET"]))
334 334 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
335 335 action="show", conditions=dict(method=["GET"]))
336 336 m.connect("admin_settings_my_account", "/my_account",
337 337 action="my_account", conditions=dict(method=["GET"]))
338 338 m.connect("admin_settings_my_account_update", "/my_account_update",
339 339 action="my_account_update", conditions=dict(method=["PUT"]))
340 340 m.connect("admin_settings_my_repos", "/my_account/repos",
341 341 action="my_account_my_repos", conditions=dict(method=["GET"]))
342 342 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
343 343 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
344 344
345 345 #NOTIFICATION REST ROUTES
346 346 with rmap.submapper(path_prefix=ADMIN_PREFIX,
347 347 controller='admin/notifications') as m:
348 348 m.connect("notifications", "/notifications",
349 349 action="create", conditions=dict(method=["POST"]))
350 350 m.connect("notifications", "/notifications",
351 351 action="index", conditions=dict(method=["GET"]))
352 352 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
353 353 action="mark_all_read", conditions=dict(method=["GET"]))
354 354 m.connect("formatted_notifications", "/notifications.{format}",
355 355 action="index", conditions=dict(method=["GET"]))
356 356 m.connect("new_notification", "/notifications/new",
357 357 action="new", conditions=dict(method=["GET"]))
358 358 m.connect("formatted_new_notification", "/notifications/new.{format}",
359 359 action="new", conditions=dict(method=["GET"]))
360 360 m.connect("/notification/{notification_id}",
361 361 action="update", conditions=dict(method=["PUT"]))
362 362 m.connect("/notification/{notification_id}",
363 363 action="delete", conditions=dict(method=["DELETE"]))
364 364 m.connect("edit_notification", "/notification/{notification_id}/edit",
365 365 action="edit", conditions=dict(method=["GET"]))
366 366 m.connect("formatted_edit_notification",
367 367 "/notification/{notification_id}.{format}/edit",
368 368 action="edit", conditions=dict(method=["GET"]))
369 369 m.connect("notification", "/notification/{notification_id}",
370 370 action="show", conditions=dict(method=["GET"]))
371 371 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
372 372 action="show", conditions=dict(method=["GET"]))
373 373
374 374 #ADMIN MAIN PAGES
375 375 with rmap.submapper(path_prefix=ADMIN_PREFIX,
376 376 controller='admin/admin') as m:
377 377 m.connect('admin_home', '', action='index')
378 378 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
379 379 action='add_repo')
380 380
381 381 #==========================================================================
382 382 # API V2
383 383 #==========================================================================
384 384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
385 385 controller='api/api') as m:
386 386 m.connect('api', '/api')
387 387
388 388 #USER JOURNAL
389 389 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
390 390 controller='journal', action='index')
391 391 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
392 392 controller='journal', action='journal_rss')
393 393 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
394 394 controller='journal', action='journal_atom')
395 395
396 396 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
397 397 controller='journal', action="public_journal")
398 398
399 399 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
400 400 controller='journal', action="public_journal_rss")
401 401
402 402 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
403 403 controller='journal', action="public_journal_rss")
404 404
405 405 rmap.connect('public_journal_atom',
406 406 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
407 407 action="public_journal_atom")
408 408
409 409 rmap.connect('public_journal_atom_old',
410 410 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
411 411 action="public_journal_atom")
412 412
413 413 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
414 414 controller='journal', action='toggle_following',
415 415 conditions=dict(method=["POST"]))
416 416
417 417 #SEARCH
418 418 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
419 419 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
420 420 controller='search',
421 421 conditions=dict(function=check_repo))
422 422 rmap.connect('search_repo', '/{repo_name:.*?}/search',
423 423 controller='search',
424 424 conditions=dict(function=check_repo),
425 425 )
426 426
427 427 #LOGIN/LOGOUT/REGISTER/SIGN IN
428 428 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
429 429 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
430 430 action='logout')
431 431
432 432 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
433 433 action='register')
434 434
435 435 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
436 436 controller='login', action='password_reset')
437 437
438 438 rmap.connect('reset_password_confirmation',
439 439 '%s/password_reset_confirmation' % ADMIN_PREFIX,
440 440 controller='login', action='password_reset_confirmation')
441 441
442 442 #FEEDS
443 443 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
444 444 controller='feed', action='rss',
445 445 conditions=dict(function=check_repo))
446 446
447 447 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
448 448 controller='feed', action='atom',
449 449 conditions=dict(function=check_repo))
450 450
451 451 #==========================================================================
452 452 # REPOSITORY ROUTES
453 453 #==========================================================================
454 454 rmap.connect('summary_home', '/{repo_name:.*?}',
455 455 controller='summary',
456 456 conditions=dict(function=check_repo))
457 457
458 458 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
459 459 controller='summary', action='repo_size',
460 460 conditions=dict(function=check_repo))
461 461
462 462 rmap.connect('repos_group_home', '/{group_name:.*}',
463 463 controller='admin/repos_groups', action="show_by_name",
464 464 conditions=dict(function=check_group))
465 465
466 466 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
467 467 controller='changeset', revision='tip',
468 468 conditions=dict(function=check_repo))
469 469
470 470 # no longer user, but kept for routes to work
471 471 rmap.connect("_edit_repo", "/{repo_name:.*?}/edit",
472 472 controller='admin/repos', action="edit",
473 473 conditions=dict(method=["GET"], function=check_repo)
474 474 )
475 475
476 476 rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
477 477 controller='admin/repos', action="edit",
478 478 conditions=dict(method=["GET"], function=check_repo)
479 479 )
480 480
481 481 #still working url for backward compat.
482 482 rmap.connect('raw_changeset_home_depraced',
483 483 '/{repo_name:.*?}/raw-changeset/{revision}',
484 484 controller='changeset', action='changeset_raw',
485 485 revision='tip', conditions=dict(function=check_repo))
486 486
487 487 ## new URLs
488 488 rmap.connect('changeset_raw_home',
489 489 '/{repo_name:.*?}/changeset-diff/{revision}',
490 490 controller='changeset', action='changeset_raw',
491 491 revision='tip', conditions=dict(function=check_repo))
492 492
493 493 rmap.connect('changeset_patch_home',
494 494 '/{repo_name:.*?}/changeset-patch/{revision}',
495 495 controller='changeset', action='changeset_patch',
496 496 revision='tip', conditions=dict(function=check_repo))
497 497
498 498 rmap.connect('changeset_download_home',
499 499 '/{repo_name:.*?}/changeset-download/{revision}',
500 500 controller='changeset', action='changeset_download',
501 501 revision='tip', conditions=dict(function=check_repo))
502 502
503 503 rmap.connect('changeset_comment',
504 504 '/{repo_name:.*?}/changeset/{revision}/comment',
505 505 controller='changeset', revision='tip', action='comment',
506 506 conditions=dict(function=check_repo))
507 507
508 508 rmap.connect('changeset_comment_delete',
509 509 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
510 510 controller='changeset', action='delete_comment',
511 511 conditions=dict(function=check_repo, method=["DELETE"]))
512 512
513 513 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
514 514 controller='changeset', action='changeset_info')
515 515
516 516 rmap.connect('compare_url',
517 517 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
518 518 controller='compare', action='index',
519 519 conditions=dict(function=check_repo),
520 520 requirements=dict(
521 521 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
522 522 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
523 523 )
524 524
525 525 rmap.connect('pullrequest_home',
526 526 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
527 527 action='index', conditions=dict(function=check_repo,
528 528 method=["GET"]))
529 529
530 530 rmap.connect('pullrequest',
531 531 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
532 532 action='create', conditions=dict(function=check_repo,
533 533 method=["POST"]))
534 534
535 535 rmap.connect('pullrequest_show',
536 536 '/{repo_name:.*?}/pull-request/{pull_request_id}',
537 537 controller='pullrequests',
538 538 action='show', conditions=dict(function=check_repo,
539 539 method=["GET"]))
540 540 rmap.connect('pullrequest_update',
541 541 '/{repo_name:.*?}/pull-request/{pull_request_id}',
542 542 controller='pullrequests',
543 543 action='update', conditions=dict(function=check_repo,
544 544 method=["PUT"]))
545 545 rmap.connect('pullrequest_delete',
546 546 '/{repo_name:.*?}/pull-request/{pull_request_id}',
547 547 controller='pullrequests',
548 548 action='delete', conditions=dict(function=check_repo,
549 549 method=["DELETE"]))
550 550
551 551 rmap.connect('pullrequest_show_all',
552 552 '/{repo_name:.*?}/pull-request',
553 553 controller='pullrequests',
554 554 action='show_all', conditions=dict(function=check_repo,
555 555 method=["GET"]))
556 556
557 557 rmap.connect('pullrequest_comment',
558 558 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
559 559 controller='pullrequests',
560 560 action='comment', conditions=dict(function=check_repo,
561 561 method=["POST"]))
562 562
563 563 rmap.connect('pullrequest_comment_delete',
564 564 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
565 565 controller='pullrequests', action='delete_comment',
566 566 conditions=dict(function=check_repo, method=["DELETE"]))
567 567
568 568 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
569 569 controller='summary', conditions=dict(function=check_repo))
570 570
571 571 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
572 572 controller='shortlog', conditions=dict(function=check_repo))
573 573
574 574 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
575 575 controller='shortlog', f_path=None,
576 576 conditions=dict(function=check_repo))
577 577
578 578 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
579 579 controller='branches', conditions=dict(function=check_repo))
580 580
581 581 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
582 582 controller='tags', conditions=dict(function=check_repo))
583 583
584 584 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
585 585 controller='bookmarks', conditions=dict(function=check_repo))
586 586
587 587 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
588 588 controller='changelog', conditions=dict(function=check_repo))
589 589
590 590 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
591 591 controller='changelog', action='changelog_details',
592 592 conditions=dict(function=check_repo))
593 593
594 594 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
595 595 controller='files', revision='tip', f_path='',
596 596 conditions=dict(function=check_repo))
597 597
598 598 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
599 599 controller='files', revision='tip', f_path='',
600 600 conditions=dict(function=check_repo))
601 601
602 602 rmap.connect('files_history_home',
603 603 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
604 604 controller='files', action='history', revision='tip', f_path='',
605 605 conditions=dict(function=check_repo))
606 606
607 607 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
608 608 controller='files', action='diff', revision='tip', f_path='',
609 609 conditions=dict(function=check_repo))
610 610
611 611 rmap.connect('files_rawfile_home',
612 612 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
613 613 controller='files', action='rawfile', revision='tip',
614 614 f_path='', conditions=dict(function=check_repo))
615 615
616 616 rmap.connect('files_raw_home',
617 617 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
618 618 controller='files', action='raw', revision='tip', f_path='',
619 619 conditions=dict(function=check_repo))
620 620
621 621 rmap.connect('files_annotate_home',
622 622 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
623 623 controller='files', action='index', revision='tip',
624 624 f_path='', annotate=True, conditions=dict(function=check_repo))
625 625
626 626 rmap.connect('files_edit_home',
627 627 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
628 628 controller='files', action='edit', revision='tip',
629 629 f_path='', conditions=dict(function=check_repo))
630 630
631 631 rmap.connect('files_add_home',
632 632 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
633 633 controller='files', action='add', revision='tip',
634 634 f_path='', conditions=dict(function=check_repo))
635 635
636 636 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
637 637 controller='files', action='archivefile',
638 638 conditions=dict(function=check_repo))
639 639
640 640 rmap.connect('files_nodelist_home',
641 641 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
642 642 controller='files', action='nodelist',
643 643 conditions=dict(function=check_repo))
644 644
645 645 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
646 646 controller='forks', action='fork_create',
647 647 conditions=dict(function=check_repo, method=["POST"]))
648 648
649 649 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
650 650 controller='forks', action='fork',
651 651 conditions=dict(function=check_repo))
652 652
653 653 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
654 654 controller='forks', action='forks',
655 655 conditions=dict(function=check_repo))
656 656
657 657 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
658 658 controller='followers', action='followers',
659 659 conditions=dict(function=check_repo))
660 660
661 661 return rmap
@@ -1,392 +1,392 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository groups controller for RhodeCode
7 7
8 8 :created_on: Mar 23, 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 request, tmpl_context as c, url
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from sqlalchemy.exc import IntegrityError
37 37
38 38 import rhodecode
39 39 from rhodecode.lib import helpers as h
40 40 from rhodecode.lib.ext_json import json
41 41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
42 42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
43 43 HasPermissionAll
44 44 from rhodecode.lib.base import BaseController, render
45 45 from rhodecode.model.db import RepoGroup, Repository
46 46 from rhodecode.model.repos_group import ReposGroupModel
47 47 from rhodecode.model.forms import ReposGroupForm
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo import RepoModel
50 50 from webob.exc import HTTPInternalServerError, HTTPNotFound
51 51 from rhodecode.lib.utils2 import str2bool, safe_int
52 52 from sqlalchemy.sql.expression import func
53 53 from rhodecode.model.scm import GroupList
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class ReposGroupsController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60 # To properly map this controller, ensure your config/routing.py
61 61 # file has a resource setup:
62 62 # map.resource('repos_group', 'repos_groups')
63 63
64 64 @LoginRequired()
65 65 def __before__(self):
66 66 super(ReposGroupsController, self).__before__()
67 67
68 68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
69 69 if HasPermissionAll('hg.admin')('group edit'):
70 70 #we're global admin, we're ok and we can create TOP level groups
71 71 allow_empty_group = True
72 72
73 73 #override the choices for this form, we need to filter choices
74 74 #and display only those we have ADMIN right
75 75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
76 76 perm_set=['group.admin'])
77 77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
78 78 show_empty_group=allow_empty_group)
79 79 # exclude filtered ids
80 80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
81 81 c.repo_groups)
82 82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
83 83 repo_model = RepoModel()
84 84 c.users_array = repo_model.get_users_js()
85 85 c.users_groups_array = repo_model.get_users_groups_js()
86 86
87 87 def __load_data(self, group_id):
88 88 """
89 89 Load defaults settings for edit, and update
90 90
91 91 :param group_id:
92 92 """
93 93 repo_group = RepoGroup.get_or_404(group_id)
94 94 data = repo_group.get_dict()
95 95 data['group_name'] = repo_group.name
96 96
97 97 # fill repository users
98 98 for p in repo_group.repo_group_to_perm:
99 99 data.update({'u_perm_%s' % p.user.username:
100 100 p.permission.permission_name})
101 101
102 102 # fill repository groups
103 103 for p in repo_group.users_group_to_perm:
104 104 data.update({'g_perm_%s' % p.users_group.users_group_name:
105 105 p.permission.permission_name})
106 106
107 107 return data
108 108
109 109 def _revoke_perms_on_yourself(self, form_result):
110 110 _up = filter(lambda u: c.rhodecode_user.username == u[0],
111 111 form_result['perms_updates'])
112 112 _new = filter(lambda u: c.rhodecode_user.username == u[0],
113 113 form_result['perms_new'])
114 114 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
115 115 return True
116 116 return False
117 117
118 118 def index(self, format='html'):
119 119 """GET /repos_groups: All items in the collection"""
120 120 # url('repos_groups')
121 121 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
122 122 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
123 123 c.groups = sorted(group_iter, key=sk)
124 124 return render('admin/repos_groups/repos_groups_show.html')
125 125
126 126 def create(self):
127 127 """POST /repos_groups: Create a new item"""
128 128 # url('repos_groups')
129 129
130 130 self.__load_defaults()
131 131
132 132 # permissions for can create group based on parent_id are checked
133 133 # here in the Form
134 134 repos_group_form = ReposGroupForm(available_groups=
135 135 map(lambda k: unicode(k[0]), c.repo_groups))()
136 136 try:
137 137 form_result = repos_group_form.to_python(dict(request.POST))
138 138 ReposGroupModel().create(
139 139 group_name=form_result['group_name'],
140 140 group_description=form_result['group_description'],
141 141 parent=form_result['group_parent_id'],
142 142 owner=self.rhodecode_user.user_id
143 143 )
144 144 Session().commit()
145 h.flash(_('Created repos group %s') \
145 h.flash(_('Created repository group %s') \
146 146 % form_result['group_name'], category='success')
147 147 #TODO: in futureaction_logger(, '', '', '', self.sa)
148 148 except formencode.Invalid, errors:
149 149 return htmlfill.render(
150 150 render('admin/repos_groups/repos_groups_add.html'),
151 151 defaults=errors.value,
152 152 errors=errors.error_dict or {},
153 153 prefix_error=False,
154 154 encoding="UTF-8")
155 155 except Exception:
156 156 log.error(traceback.format_exc())
157 h.flash(_('Error occurred during creation of repos group %s') \
157 h.flash(_('Error occurred during creation of repository group %s') \
158 158 % request.POST.get('group_name'), category='error')
159 159 parent_group_id = form_result['group_parent_id']
160 160 #TODO: maybe we should get back to the main view, not the admin one
161 161 return redirect(url('repos_groups', parent_group=parent_group_id))
162 162
163 163 def new(self, format='html'):
164 164 """GET /repos_groups/new: Form to create a new item"""
165 165 # url('new_repos_group')
166 166 if HasPermissionAll('hg.admin')('group create'):
167 167 #we're global admin, we're ok and we can create TOP level groups
168 168 pass
169 169 else:
170 170 # we pass in parent group into creation form, thus we know
171 171 # what would be the group, we can check perms here !
172 172 group_id = safe_int(request.GET.get('parent_group'))
173 173 group = RepoGroup.get(group_id) if group_id else None
174 174 group_name = group.group_name if group else None
175 175 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
176 176 pass
177 177 else:
178 178 return abort(403)
179 179
180 180 self.__load_defaults()
181 181 return render('admin/repos_groups/repos_groups_add.html')
182 182
183 183 @HasReposGroupPermissionAnyDecorator('group.admin')
184 184 def update(self, group_name):
185 185 """PUT /repos_groups/group_name: Update an existing item"""
186 186 # Forms posted to this method should contain a hidden field:
187 187 # <input type="hidden" name="_method" value="PUT" />
188 188 # Or using helpers:
189 189 # h.form(url('repos_group', group_name=GROUP_NAME),
190 190 # method='put')
191 191 # url('repos_group', group_name=GROUP_NAME)
192 192
193 193 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
194 194 if HasPermissionAll('hg.admin')('group edit'):
195 195 #we're global admin, we're ok and we can create TOP level groups
196 196 allow_empty_group = True
197 197 elif not c.repos_group.parent_group:
198 198 allow_empty_group = True
199 199 else:
200 200 allow_empty_group = False
201 201 self.__load_defaults(allow_empty_group=allow_empty_group,
202 202 exclude_group_ids=[c.repos_group.group_id])
203 203
204 204 repos_group_form = ReposGroupForm(
205 205 edit=True,
206 206 old_data=c.repos_group.get_dict(),
207 207 available_groups=c.repo_groups_choices,
208 208 can_create_in_root=allow_empty_group,
209 209 )()
210 210 try:
211 211 form_result = repos_group_form.to_python(dict(request.POST))
212 212 if not c.rhodecode_user.is_admin:
213 213 if self._revoke_perms_on_yourself(form_result):
214 214 msg = _('Cannot revoke permission for yourself as admin')
215 215 h.flash(msg, category='warning')
216 216 raise Exception('revoke admin permission on self')
217 217
218 218 new_gr = ReposGroupModel().update(group_name, form_result)
219 219 Session().commit()
220 h.flash(_('Updated repos group %s') \
220 h.flash(_('Updated repository group %s') \
221 221 % form_result['group_name'], category='success')
222 222 # we now have new name !
223 223 group_name = new_gr.group_name
224 224 #TODO: in future action_logger(, '', '', '', self.sa)
225 225 except formencode.Invalid, errors:
226 226
227 227 return htmlfill.render(
228 228 render('admin/repos_groups/repos_groups_edit.html'),
229 229 defaults=errors.value,
230 230 errors=errors.error_dict or {},
231 231 prefix_error=False,
232 232 encoding="UTF-8")
233 233 except Exception:
234 234 log.error(traceback.format_exc())
235 h.flash(_('Error occurred during update of repos group %s') \
235 h.flash(_('Error occurred during update of repository group %s') \
236 236 % request.POST.get('group_name'), category='error')
237 237
238 238 return redirect(url('edit_repos_group', group_name=group_name))
239 239
240 240 @HasReposGroupPermissionAnyDecorator('group.admin')
241 241 def delete(self, group_name):
242 242 """DELETE /repos_groups/group_name: Delete an existing item"""
243 243 # Forms posted to this method should contain a hidden field:
244 244 # <input type="hidden" name="_method" value="DELETE" />
245 245 # Or using helpers:
246 246 # h.form(url('repos_group', group_name=GROUP_NAME),
247 247 # method='delete')
248 248 # url('repos_group', group_name=GROUP_NAME)
249 249
250 250 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
251 251 repos = gr.repositories.all()
252 252 if repos:
253 253 h.flash(_('This group contains %s repositores and cannot be '
254 254 'deleted') % len(repos), category='warning')
255 255 return redirect(url('repos_groups'))
256 256
257 257 children = gr.children.all()
258 258 if children:
259 259 h.flash(_('This group contains %s subgroups and cannot be deleted'
260 260 % (len(children))), category='warning')
261 261 return redirect(url('repos_groups'))
262 262
263 263 try:
264 264 ReposGroupModel().delete(group_name)
265 265 Session().commit()
266 h.flash(_('Removed repos group %s') % group_name,
266 h.flash(_('Removed repository group %s') % group_name,
267 267 category='success')
268 268 #TODO: in future action_logger(, '', '', '', self.sa)
269 269 except Exception:
270 270 log.error(traceback.format_exc())
271 271 h.flash(_('Error occurred during deletion of repos '
272 272 'group %s') % group_name, category='error')
273 273
274 274 return redirect(url('repos_groups'))
275 275
276 276 @HasReposGroupPermissionAnyDecorator('group.admin')
277 277 def delete_repos_group_user_perm(self, group_name):
278 278 """
279 279 DELETE an existing repository group permission user
280 280
281 281 :param group_name:
282 282 """
283 283 try:
284 284 if not c.rhodecode_user.is_admin:
285 285 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
286 286 msg = _('Cannot revoke permission for yourself as admin')
287 287 h.flash(msg, category='warning')
288 288 raise Exception('revoke admin permission on self')
289 289 recursive = str2bool(request.POST.get('recursive', False))
290 290 ReposGroupModel().delete_permission(
291 291 repos_group=group_name, obj=request.POST['user_id'],
292 292 obj_type='user', recursive=recursive
293 293 )
294 294 Session().commit()
295 295 except Exception:
296 296 log.error(traceback.format_exc())
297 297 h.flash(_('An error occurred during deletion of group user'),
298 298 category='error')
299 299 raise HTTPInternalServerError()
300 300
301 301 @HasReposGroupPermissionAnyDecorator('group.admin')
302 302 def delete_repos_group_users_group_perm(self, group_name):
303 303 """
304 304 DELETE an existing repository group permission user group
305 305
306 306 :param group_name:
307 307 """
308 308
309 309 try:
310 310 recursive = str2bool(request.POST.get('recursive', False))
311 311 ReposGroupModel().delete_permission(
312 312 repos_group=group_name, obj=request.POST['users_group_id'],
313 313 obj_type='users_group', recursive=recursive
314 314 )
315 315 Session().commit()
316 316 except Exception:
317 317 log.error(traceback.format_exc())
318 318 h.flash(_('An error occurred during deletion of group'
319 319 ' user groups'),
320 320 category='error')
321 321 raise HTTPInternalServerError()
322 322
323 323 def show_by_name(self, group_name):
324 324 """
325 325 This is a proxy that does a lookup group_name -> id, and shows
326 326 the group by id view instead
327 327 """
328 328 group_name = group_name.rstrip('/')
329 329 id_ = RepoGroup.get_by_group_name(group_name)
330 330 if id_:
331 331 return self.show(id_.group_id)
332 332 raise HTTPNotFound
333 333
334 334 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
335 335 'group.admin')
336 336 def show(self, group_name, format='html'):
337 337 """GET /repos_groups/group_name: Show a specific item"""
338 338 # url('repos_group', group_name=GROUP_NAME)
339 339
340 340 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
341 341 c.group_repos = c.group.repositories.all()
342 342
343 343 #overwrite our cached list with current filter
344 344 gr_filter = c.group_repos
345 345 c.repo_cnt = 0
346 346
347 347 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
348 348 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
349 349 c.groups = self.scm_model.get_repos_groups(groups)
350 350
351 351 if not c.visual.lightweight_dashboard:
352 352 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
353 353 ## lightweight version of dashboard
354 354 else:
355 355 c.repos_list = Repository.query()\
356 356 .filter(Repository.group_id == c.group.group_id)\
357 357 .order_by(func.lower(Repository.repo_name))\
358 358 .all()
359 359
360 360 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
361 361 admin=False)
362 362 #json used to render the grid
363 363 c.data = json.dumps(repos_data)
364 364
365 365 return render('admin/repos_groups/repos_groups.html')
366 366
367 367 @HasReposGroupPermissionAnyDecorator('group.admin')
368 368 def edit(self, group_name, format='html'):
369 369 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
370 370 # url('edit_repos_group', group_name=GROUP_NAME)
371 371
372 372 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
373 373 #we can only allow moving empty group if it's already a top-level
374 374 #group, ie has no parents, or we're admin
375 375 if HasPermissionAll('hg.admin')('group edit'):
376 376 #we're global admin, we're ok and we can create TOP level groups
377 377 allow_empty_group = True
378 378 elif not c.repos_group.parent_group:
379 379 allow_empty_group = True
380 380 else:
381 381 allow_empty_group = False
382 382
383 383 self.__load_defaults(allow_empty_group=allow_empty_group,
384 384 exclude_group_ids=[c.repos_group.group_id])
385 385 defaults = self.__load_data(c.repos_group.group_id)
386 386
387 387 return htmlfill.render(
388 388 render('admin/repos_groups/repos_groups_edit.html'),
389 389 defaults=defaults,
390 390 encoding="UTF-8",
391 391 force_defaults=False
392 392 )
@@ -1,792 +1,792 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 import decorator
36 36 import warnings
37 37 from os.path import abspath
38 38 from os.path import dirname as dn, join as jn
39 39
40 40 from paste.script.command import Command, BadCommand
41 41
42 42 from mercurial import ui, config
43 43
44 44 from webhelpers.text import collapse, remove_formatting, strip_tags
45 45
46 46 from rhodecode.lib.vcs import get_backend
47 47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 50 from rhodecode.lib.vcs.exceptions import VCSError
51 51
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model import meta
55 55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 57 from rhodecode.model.meta import Session
58 58 from rhodecode.model.repos_group import ReposGroupModel
59 59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 60 from rhodecode.lib.vcs.utils.fakemod import create_module
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65 65
66 66
67 67 def recursive_replace(str_, replace=' '):
68 68 """
69 69 Recursive replace of given sign to just one instance
70 70
71 71 :param str_: given string
72 72 :param replace: char to find and replace multiple instances
73 73
74 74 Examples::
75 75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 76 'Mighty-Mighty-Bo-sstones'
77 77 """
78 78
79 79 if str_.find(replace * 2) == -1:
80 80 return str_
81 81 else:
82 82 str_ = str_.replace(replace * 2, replace)
83 83 return recursive_replace(str_, replace)
84 84
85 85
86 86 def repo_name_slug(value):
87 87 """
88 88 Return slug of name of repository
89 89 This function is called on each creation/modification
90 90 of repository to prevent bad names in repo
91 91 """
92 92
93 93 slug = remove_formatting(value)
94 94 slug = strip_tags(slug)
95 95
96 96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 97 slug = slug.replace(c, '-')
98 98 slug = recursive_replace(slug, '-')
99 99 slug = collapse(slug, '-')
100 100 return slug
101 101
102 102
103 103 def get_repo_slug(request):
104 104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 105 if _repo:
106 106 _repo = _repo.rstrip('/')
107 107 return _repo
108 108
109 109
110 110 def get_repos_group_slug(request):
111 111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 112 if _group:
113 113 _group = _group.rstrip('/')
114 114 return _group
115 115
116 116
117 117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 118 """
119 119 Action logger for various actions made by users
120 120
121 121 :param user: user that made this action, can be a unique username string or
122 122 object containing user_id attribute
123 123 :param action: action to log, should be on of predefined unique actions for
124 124 easy translations
125 125 :param repo: string name of repository or object containing repo_id,
126 126 that action was made on
127 127 :param ipaddr: optional ip address from what the action was made
128 128 :param sa: optional sqlalchemy session
129 129
130 130 """
131 131
132 132 if not sa:
133 133 sa = meta.Session()
134 134
135 135 try:
136 136 if hasattr(user, 'user_id'):
137 137 user_obj = User.get(user.user_id)
138 138 elif isinstance(user, basestring):
139 139 user_obj = User.get_by_username(user)
140 140 else:
141 141 raise Exception('You have to provide a user object or a username')
142 142
143 143 if hasattr(repo, 'repo_id'):
144 144 repo_obj = Repository.get(repo.repo_id)
145 145 repo_name = repo_obj.repo_name
146 146 elif isinstance(repo, basestring):
147 147 repo_name = repo.lstrip('/')
148 148 repo_obj = Repository.get_by_repo_name(repo_name)
149 149 else:
150 150 repo_obj = None
151 151 repo_name = ''
152 152
153 153 user_log = UserLog()
154 154 user_log.user_id = user_obj.user_id
155 155 user_log.username = user_obj.username
156 156 user_log.action = safe_unicode(action)
157 157
158 158 user_log.repository = repo_obj
159 159 user_log.repository_name = repo_name
160 160
161 161 user_log.action_date = datetime.datetime.now()
162 162 user_log.user_ip = ipaddr
163 163 sa.add(user_log)
164 164
165 165 log.info('Logging action:%s on %s by user:%s ip:%s' %
166 166 (action, safe_unicode(repo), user_obj, ipaddr))
167 167 if commit:
168 168 sa.commit()
169 169 except Exception:
170 170 log.error(traceback.format_exc())
171 171 raise
172 172
173 173
174 174 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
175 175 """
176 176 Scans given path for repos and return (name,(type,path)) tuple
177 177
178 178 :param path: path to scan for repositories
179 179 :param recursive: recursive search and return names with subdirs in front
180 180 """
181 181
182 182 # remove ending slash for better results
183 183 path = path.rstrip(os.sep)
184 184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
185 185
186 186 def _get_repos(p):
187 187 if not os.access(p, os.W_OK):
188 188 log.warn('ignoring repo path without write access: %s', p)
189 189 return
190 190 for dirpath in os.listdir(p):
191 191 if os.path.isfile(os.path.join(p, dirpath)):
192 192 continue
193 193 cur_path = os.path.join(p, dirpath)
194 194
195 195 # skip removed repos
196 196 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
197 197 continue
198 198
199 199 #skip .<somethin> dirs
200 200 if dirpath.startswith('.'):
201 201 continue
202 202
203 203 try:
204 204 scm_info = get_scm(cur_path)
205 205 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
206 206 except VCSError:
207 207 if not recursive:
208 208 continue
209 209 #check if this dir containts other repos for recursive scan
210 210 rec_path = os.path.join(p, dirpath)
211 211 if os.path.isdir(rec_path):
212 212 for inner_scm in _get_repos(rec_path):
213 213 yield inner_scm
214 214
215 215 return _get_repos(path)
216 216
217 217
218 218 def is_valid_repo(repo_name, base_path, scm=None):
219 219 """
220 220 Returns True if given path is a valid repository False otherwise.
221 221 If scm param is given also compare if given scm is the same as expected
222 222 from scm parameter
223 223
224 224 :param repo_name:
225 225 :param base_path:
226 226 :param scm:
227 227
228 228 :return True: if given path is a valid repository
229 229 """
230 230 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
231 231
232 232 try:
233 233 scm_ = get_scm(full_path)
234 234 if scm:
235 235 return scm_[0] == scm
236 236 return True
237 237 except VCSError:
238 238 return False
239 239
240 240
241 241 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
242 242 """
243 Returns True if given path is a repos group False otherwise
243 Returns True if given path is a repository group False otherwise
244 244
245 245 :param repo_name:
246 246 :param base_path:
247 247 """
248 248 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
249 249
250 250 # check if it's not a repo
251 251 if is_valid_repo(repos_group_name, base_path):
252 252 return False
253 253
254 254 try:
255 255 # we need to check bare git repos at higher level
256 256 # since we might match branches/hooks/info/objects or possible
257 257 # other things inside bare git repo
258 258 get_scm(os.path.dirname(full_path))
259 259 return False
260 260 except VCSError:
261 261 pass
262 262
263 263 # check if it's a valid path
264 264 if skip_path_check or os.path.isdir(full_path):
265 265 return True
266 266
267 267 return False
268 268
269 269
270 270 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
271 271 while True:
272 272 ok = raw_input(prompt)
273 273 if ok in ('y', 'ye', 'yes'):
274 274 return True
275 275 if ok in ('n', 'no', 'nop', 'nope'):
276 276 return False
277 277 retries = retries - 1
278 278 if retries < 0:
279 279 raise IOError
280 280 print complaint
281 281
282 282 #propagated from mercurial documentation
283 283 ui_sections = ['alias', 'auth',
284 284 'decode/encode', 'defaults',
285 285 'diff', 'email',
286 286 'extensions', 'format',
287 287 'merge-patterns', 'merge-tools',
288 288 'hooks', 'http_proxy',
289 289 'smtp', 'patch',
290 290 'paths', 'profiling',
291 291 'server', 'trusted',
292 292 'ui', 'web', ]
293 293
294 294
295 295 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
296 296 """
297 297 A function that will read python rc files or database
298 298 and make an mercurial ui object from read options
299 299
300 300 :param path: path to mercurial config file
301 301 :param checkpaths: check the path
302 302 :param read_from: read from 'file' or 'db'
303 303 """
304 304
305 305 baseui = ui.ui()
306 306
307 307 # clean the baseui object
308 308 baseui._ocfg = config.config()
309 309 baseui._ucfg = config.config()
310 310 baseui._tcfg = config.config()
311 311
312 312 if read_from == 'file':
313 313 if not os.path.isfile(path):
314 314 log.debug('hgrc file is not present at %s, skipping...' % path)
315 315 return False
316 316 log.debug('reading hgrc from %s' % path)
317 317 cfg = config.config()
318 318 cfg.read(path)
319 319 for section in ui_sections:
320 320 for k, v in cfg.items(section):
321 321 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
322 322 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
323 323
324 324 elif read_from == 'db':
325 325 sa = meta.Session()
326 326 ret = sa.query(RhodeCodeUi)\
327 327 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
328 328 .all()
329 329
330 330 hg_ui = ret
331 331 for ui_ in hg_ui:
332 332 if ui_.ui_active:
333 333 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
334 334 ui_.ui_key, ui_.ui_value)
335 335 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
336 336 safe_str(ui_.ui_value))
337 337 if ui_.ui_key == 'push_ssl':
338 338 # force set push_ssl requirement to False, rhodecode
339 339 # handles that
340 340 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
341 341 False)
342 342 if clear_session:
343 343 meta.Session.remove()
344 344 return baseui
345 345
346 346
347 347 def set_rhodecode_config(config):
348 348 """
349 349 Updates pylons config with new settings from database
350 350
351 351 :param config:
352 352 """
353 353 hgsettings = RhodeCodeSetting.get_app_settings()
354 354
355 355 for k, v in hgsettings.items():
356 356 config[k] = v
357 357
358 358
359 359 def invalidate_cache(cache_key, *args):
360 360 """
361 361 Puts cache invalidation task into db for
362 362 further global cache invalidation
363 363 """
364 364
365 365 from rhodecode.model.scm import ScmModel
366 366
367 367 if cache_key.startswith('get_repo_cached_'):
368 368 name = cache_key.split('get_repo_cached_')[-1]
369 369 ScmModel().mark_for_invalidation(name)
370 370
371 371
372 372 def map_groups(path):
373 373 """
374 374 Given a full path to a repository, create all nested groups that this
375 375 repo is inside. This function creates parent-child relationships between
376 376 groups and creates default perms for all new groups.
377 377
378 378 :param paths: full path to repository
379 379 """
380 380 sa = meta.Session()
381 381 groups = path.split(Repository.url_sep())
382 382 parent = None
383 383 group = None
384 384
385 385 # last element is repo in nested groups structure
386 386 groups = groups[:-1]
387 387 rgm = ReposGroupModel(sa)
388 388 for lvl, group_name in enumerate(groups):
389 389 group_name = '/'.join(groups[:lvl] + [group_name])
390 390 group = RepoGroup.get_by_group_name(group_name)
391 391 desc = '%s group' % group_name
392 392
393 393 # skip folders that are now removed repos
394 394 if REMOVED_REPO_PAT.match(group_name):
395 395 break
396 396
397 397 if group is None:
398 398 log.debug('creating group level: %s group_name: %s' % (lvl,
399 399 group_name))
400 400 group = RepoGroup(group_name, parent)
401 401 group.group_description = desc
402 402 sa.add(group)
403 403 rgm._create_default_perms(group)
404 404 sa.flush()
405 405 parent = group
406 406 return group
407 407
408 408
409 409 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
410 410 install_git_hook=False):
411 411 """
412 412 maps all repos given in initial_repo_list, non existing repositories
413 413 are created, if remove_obsolete is True it also check for db entries
414 414 that are not in initial_repo_list and removes them.
415 415
416 416 :param initial_repo_list: list of repositories found by scanning methods
417 417 :param remove_obsolete: check for obsolete entries in database
418 418 :param install_git_hook: if this is True, also check and install githook
419 419 for a repo if missing
420 420 """
421 421 from rhodecode.model.repo import RepoModel
422 422 from rhodecode.model.scm import ScmModel
423 423 sa = meta.Session()
424 424 rm = RepoModel()
425 425 user = sa.query(User).filter(User.admin == True).first()
426 426 if user is None:
427 427 raise Exception('Missing administrative account!')
428 428 added = []
429 429
430 430 ##creation defaults
431 431 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
432 432 enable_statistics = defs.get('repo_enable_statistics')
433 433 enable_locking = defs.get('repo_enable_locking')
434 434 enable_downloads = defs.get('repo_enable_downloads')
435 435 private = defs.get('repo_private')
436 436
437 437 for name, repo in initial_repo_list.items():
438 438 group = map_groups(name)
439 439 db_repo = rm.get_by_repo_name(name)
440 440 # found repo that is on filesystem not in RhodeCode database
441 441 if not db_repo:
442 442 log.info('repository %s not found, creating now' % name)
443 443 added.append(name)
444 444 desc = (repo.description
445 445 if repo.description != 'unknown'
446 446 else '%s repository' % name)
447 447
448 448 new_repo = rm.create_repo(
449 449 repo_name=name,
450 450 repo_type=repo.alias,
451 451 description=desc,
452 452 repos_group=getattr(group, 'group_id', None),
453 453 owner=user,
454 454 just_db=True,
455 455 enable_locking=enable_locking,
456 456 enable_downloads=enable_downloads,
457 457 enable_statistics=enable_statistics,
458 458 private=private
459 459 )
460 460 # we added that repo just now, and make sure it has githook
461 461 # installed
462 462 if new_repo.repo_type == 'git':
463 463 ScmModel().install_git_hook(new_repo.scm_instance)
464 464 new_repo.update_changeset_cache()
465 465 elif install_git_hook:
466 466 if db_repo.repo_type == 'git':
467 467 ScmModel().install_git_hook(db_repo.scm_instance)
468 468 # during starting install all cache keys for all repositories in the
469 469 # system, this will register all repos and multiple instances
470 470 cache_key = CacheInvalidation._get_cache_key(name)
471 471 log.debug("Creating invalidation cache key for %s: %s", name, cache_key)
472 472 CacheInvalidation.invalidate(name)
473 473
474 474 sa.commit()
475 475 removed = []
476 476 if remove_obsolete:
477 477 # remove from database those repositories that are not in the filesystem
478 478 for repo in sa.query(Repository).all():
479 479 if repo.repo_name not in initial_repo_list.keys():
480 480 log.debug("Removing non-existing repository found in db `%s`" %
481 481 repo.repo_name)
482 482 try:
483 483 sa.delete(repo)
484 484 sa.commit()
485 485 removed.append(repo.repo_name)
486 486 except Exception:
487 487 #don't hold further removals on error
488 488 log.error(traceback.format_exc())
489 489 sa.rollback()
490 490 return added, removed
491 491
492 492
493 493 # set cache regions for beaker so celery can utilise it
494 494 def add_cache(settings):
495 495 cache_settings = {'regions': None}
496 496 for key in settings.keys():
497 497 for prefix in ['beaker.cache.', 'cache.']:
498 498 if key.startswith(prefix):
499 499 name = key.split(prefix)[1].strip()
500 500 cache_settings[name] = settings[key].strip()
501 501 if cache_settings['regions']:
502 502 for region in cache_settings['regions'].split(','):
503 503 region = region.strip()
504 504 region_settings = {}
505 505 for key, value in cache_settings.items():
506 506 if key.startswith(region):
507 507 region_settings[key.split('.')[1]] = value
508 508 region_settings['expire'] = int(region_settings.get('expire',
509 509 60))
510 510 region_settings.setdefault('lock_dir',
511 511 cache_settings.get('lock_dir'))
512 512 region_settings.setdefault('data_dir',
513 513 cache_settings.get('data_dir'))
514 514
515 515 if 'type' not in region_settings:
516 516 region_settings['type'] = cache_settings.get('type',
517 517 'memory')
518 518 beaker.cache.cache_regions[region] = region_settings
519 519
520 520
521 521 def load_rcextensions(root_path):
522 522 import rhodecode
523 523 from rhodecode.config import conf
524 524
525 525 path = os.path.join(root_path, 'rcextensions', '__init__.py')
526 526 if os.path.isfile(path):
527 527 rcext = create_module('rc', path)
528 528 EXT = rhodecode.EXTENSIONS = rcext
529 529 log.debug('Found rcextensions now loading %s...' % rcext)
530 530
531 531 # Additional mappings that are not present in the pygments lexers
532 532 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
533 533
534 534 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
535 535
536 536 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
537 537 log.debug('settings custom INDEX_EXTENSIONS')
538 538 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
539 539
540 540 #ADDITIONAL MAPPINGS
541 541 log.debug('adding extra into INDEX_EXTENSIONS')
542 542 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
543 543
544 544 # auto check if the module is not missing any data, set to default if is
545 545 # this will help autoupdate new feature of rcext module
546 546 from rhodecode.config import rcextensions
547 547 for k in dir(rcextensions):
548 548 if not k.startswith('_') and not hasattr(EXT, k):
549 549 setattr(EXT, k, getattr(rcextensions, k))
550 550
551 551
552 552 def get_custom_lexer(extension):
553 553 """
554 554 returns a custom lexer if it's defined in rcextensions module, or None
555 555 if there's no custom lexer defined
556 556 """
557 557 import rhodecode
558 558 from pygments import lexers
559 559 #check if we didn't define this extension as other lexer
560 560 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
561 561 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
562 562 return lexers.get_lexer_by_name(_lexer_name)
563 563
564 564
565 565 #==============================================================================
566 566 # TEST FUNCTIONS AND CREATORS
567 567 #==============================================================================
568 568 def create_test_index(repo_location, config, full_index):
569 569 """
570 570 Makes default test index
571 571
572 572 :param config: test config
573 573 :param full_index:
574 574 """
575 575
576 576 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
577 577 from rhodecode.lib.pidlock import DaemonLock, LockHeld
578 578
579 579 repo_location = repo_location
580 580
581 581 index_location = os.path.join(config['app_conf']['index_dir'])
582 582 if not os.path.exists(index_location):
583 583 os.makedirs(index_location)
584 584
585 585 try:
586 586 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
587 587 WhooshIndexingDaemon(index_location=index_location,
588 588 repo_location=repo_location)\
589 589 .run(full_index=full_index)
590 590 l.release()
591 591 except LockHeld:
592 592 pass
593 593
594 594
595 595 def create_test_env(repos_test_path, config):
596 596 """
597 597 Makes a fresh database and
598 598 install test repository into tmp dir
599 599 """
600 600 from rhodecode.lib.db_manage import DbManage
601 601 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
602 602
603 603 # PART ONE create db
604 604 dbconf = config['sqlalchemy.db1.url']
605 605 log.debug('making test db %s' % dbconf)
606 606
607 607 # create test dir if it doesn't exist
608 608 if not os.path.isdir(repos_test_path):
609 609 log.debug('Creating testdir %s' % repos_test_path)
610 610 os.makedirs(repos_test_path)
611 611
612 612 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
613 613 tests=True)
614 614 dbmanage.create_tables(override=True)
615 615 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
616 616 dbmanage.create_default_user()
617 617 dbmanage.admin_prompt()
618 618 dbmanage.create_permissions()
619 619 dbmanage.populate_default_permissions()
620 620 Session().commit()
621 621 # PART TWO make test repo
622 622 log.debug('making test vcs repositories')
623 623
624 624 idx_path = config['app_conf']['index_dir']
625 625 data_path = config['app_conf']['cache_dir']
626 626
627 627 #clean index and data
628 628 if idx_path and os.path.exists(idx_path):
629 629 log.debug('remove %s' % idx_path)
630 630 shutil.rmtree(idx_path)
631 631
632 632 if data_path and os.path.exists(data_path):
633 633 log.debug('remove %s' % data_path)
634 634 shutil.rmtree(data_path)
635 635
636 636 #CREATE DEFAULT TEST REPOS
637 637 cur_dir = dn(dn(abspath(__file__)))
638 638 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
639 639 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
640 640 tar.close()
641 641
642 642 cur_dir = dn(dn(abspath(__file__)))
643 643 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
644 644 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
645 645 tar.close()
646 646
647 647 #LOAD VCS test stuff
648 648 from rhodecode.tests.vcs import setup_package
649 649 setup_package()
650 650
651 651
652 652 #==============================================================================
653 653 # PASTER COMMANDS
654 654 #==============================================================================
655 655 class BasePasterCommand(Command):
656 656 """
657 657 Abstract Base Class for paster commands.
658 658
659 659 The celery commands are somewhat aggressive about loading
660 660 celery.conf, and since our module sets the `CELERY_LOADER`
661 661 environment variable to our loader, we have to bootstrap a bit and
662 662 make sure we've had a chance to load the pylons config off of the
663 663 command line, otherwise everything fails.
664 664 """
665 665 min_args = 1
666 666 min_args_error = "Please provide a paster config file as an argument."
667 667 takes_config_file = 1
668 668 requires_config_file = True
669 669
670 670 def notify_msg(self, msg, log=False):
671 671 """Make a notification to user, additionally if logger is passed
672 672 it logs this action using given logger
673 673
674 674 :param msg: message that will be printed to user
675 675 :param log: logging instance, to use to additionally log this message
676 676
677 677 """
678 678 if log and isinstance(log, logging):
679 679 log(msg)
680 680
681 681 def run(self, args):
682 682 """
683 683 Overrides Command.run
684 684
685 685 Checks for a config file argument and loads it.
686 686 """
687 687 if len(args) < self.min_args:
688 688 raise BadCommand(
689 689 self.min_args_error % {'min_args': self.min_args,
690 690 'actual_args': len(args)})
691 691
692 692 # Decrement because we're going to lob off the first argument.
693 693 # @@ This is hacky
694 694 self.min_args -= 1
695 695 self.bootstrap_config(args[0])
696 696 self.update_parser()
697 697 return super(BasePasterCommand, self).run(args[1:])
698 698
699 699 def update_parser(self):
700 700 """
701 701 Abstract method. Allows for the class's parser to be updated
702 702 before the superclass's `run` method is called. Necessary to
703 703 allow options/arguments to be passed through to the underlying
704 704 celery command.
705 705 """
706 706 raise NotImplementedError("Abstract Method.")
707 707
708 708 def bootstrap_config(self, conf):
709 709 """
710 710 Loads the pylons configuration.
711 711 """
712 712 from pylons import config as pylonsconfig
713 713
714 714 self.path_to_ini_file = os.path.realpath(conf)
715 715 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
716 716 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
717 717
718 718 def _init_session(self):
719 719 """
720 720 Inits SqlAlchemy Session
721 721 """
722 722 logging.config.fileConfig(self.path_to_ini_file)
723 723 from pylons import config
724 724 from rhodecode.model import init_model
725 725 from rhodecode.lib.utils2 import engine_from_config
726 726
727 727 #get to remove repos !!
728 728 add_cache(config)
729 729 engine = engine_from_config(config, 'sqlalchemy.db1.')
730 730 init_model(engine)
731 731
732 732
733 733 def check_git_version():
734 734 """
735 735 Checks what version of git is installed in system, and issues a warning
736 736 if it's too old for RhodeCode to properly work.
737 737 """
738 738 from rhodecode import BACKENDS
739 739 from rhodecode.lib.vcs.backends.git.repository import GitRepository
740 740 from distutils.version import StrictVersion
741 741
742 742 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
743 743 _safe=True)
744 744
745 745 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
746 746 if len(ver.split('.')) > 3:
747 747 #StrictVersion needs to be only 3 element type
748 748 ver = '.'.join(ver.split('.')[:3])
749 749 try:
750 750 _ver = StrictVersion(ver)
751 751 except Exception:
752 752 _ver = StrictVersion('0.0.0')
753 753 stderr = traceback.format_exc()
754 754
755 755 req_ver = '1.7.4'
756 756 to_old_git = False
757 757 if _ver < StrictVersion(req_ver):
758 758 to_old_git = True
759 759
760 760 if 'git' in BACKENDS:
761 761 log.debug('GIT version detected: %s' % stdout)
762 762 if stderr:
763 763 log.warning('Unable to detect git version, org error was: %r' % stderr)
764 764 elif to_old_git:
765 765 log.warning('RhodeCode detected git version %s, which is too old '
766 766 'for the system to function properly. Make sure '
767 767 'its version is at least %s' % (ver, req_ver))
768 768 return _ver
769 769
770 770
771 771 @decorator.decorator
772 772 def jsonify(func, *args, **kwargs):
773 773 """Action decorator that formats output for JSON
774 774
775 775 Given a function that will return content, this decorator will turn
776 776 the result into JSON, with a content-type of 'application/json' and
777 777 output it.
778 778
779 779 """
780 780 from pylons.decorators.util import get_pylons
781 781 from rhodecode.lib.ext_json import json
782 782 pylons = get_pylons(args)
783 783 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
784 784 data = func(*args, **kwargs)
785 785 if isinstance(data, (list, tuple)):
786 786 msg = "JSON responses with Array envelopes are susceptible to " \
787 787 "cross-site data leak attacks, see " \
788 788 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
789 789 warnings.warn(msg, Warning, 2)
790 790 log.warning(msg)
791 791 log.debug("Returning JSON wrapped action output")
792 792 return json.dumps(data, encoding='utf-8')
@@ -1,436 +1,436 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 repo group model for RhodeCode
7 7
8 8 :created_on: Jan 25, 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 os
27 27 import logging
28 28 import traceback
29 29 import shutil
30 30 import datetime
31 31
32 32 from rhodecode.lib.utils2 import LazyProperty
33 33
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
36 36 User, Permission, UserGroupRepoGroupToPerm, UserGroup, Repository
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class ReposGroupModel(BaseModel):
42 42
43 43 cls = RepoGroup
44 44
45 45 def __get_users_group(self, users_group):
46 46 return self._get_instance(UserGroup, users_group,
47 47 callback=UserGroup.get_by_group_name)
48 48
49 49 def _get_repos_group(self, repos_group):
50 50 return self._get_instance(RepoGroup, repos_group,
51 51 callback=RepoGroup.get_by_group_name)
52 52
53 53 @LazyProperty
54 54 def repos_path(self):
55 55 """
56 56 Get's the repositories root path from database
57 57 """
58 58
59 59 q = RhodeCodeUi.get_by_key('/')
60 60 return q.ui_value
61 61
62 62 def _create_default_perms(self, new_group):
63 63 # create default permission
64 64 repo_group_to_perm = UserRepoGroupToPerm()
65 65 default_perm = 'group.read'
66 66 for p in User.get_by_username('default').user_perms:
67 67 if p.permission.permission_name.startswith('group.'):
68 68 default_perm = p.permission.permission_name
69 69 break
70 70
71 71 repo_group_to_perm.permission_id = self.sa.query(Permission)\
72 72 .filter(Permission.permission_name == default_perm)\
73 73 .one().permission_id
74 74
75 75 repo_group_to_perm.group = new_group
76 76 repo_group_to_perm.user_id = User.get_by_username('default').user_id
77 77
78 78 self.sa.add(repo_group_to_perm)
79 79
80 80 def __create_group(self, group_name):
81 81 """
82 82 makes repository group on filesystem
83 83
84 84 :param repo_name:
85 85 :param parent_id:
86 86 """
87 87
88 88 create_path = os.path.join(self.repos_path, group_name)
89 89 log.debug('creating new group in %s' % create_path)
90 90
91 91 if os.path.isdir(create_path):
92 92 raise Exception('That directory already exists !')
93 93
94 94 os.makedirs(create_path)
95 95
96 96 def __rename_group(self, old, new):
97 97 """
98 98 Renames a group on filesystem
99 99
100 100 :param group_name:
101 101 """
102 102
103 103 if old == new:
104 104 log.debug('skipping group rename')
105 105 return
106 106
107 log.debug('renaming repos group from %s to %s' % (old, new))
107 log.debug('renaming repository group from %s to %s' % (old, new))
108 108
109 109 old_path = os.path.join(self.repos_path, old)
110 110 new_path = os.path.join(self.repos_path, new)
111 111
112 112 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
113 113
114 114 if os.path.isdir(new_path):
115 115 raise Exception('Was trying to rename to already '
116 116 'existing dir %s' % new_path)
117 117 shutil.move(old_path, new_path)
118 118
119 119 def __delete_group(self, group, force_delete=False):
120 120 """
121 121 Deletes a group from a filesystem
122 122
123 123 :param group: instance of group from database
124 124 :param force_delete: use shutil rmtree to remove all objects
125 125 """
126 126 paths = group.full_path.split(RepoGroup.url_sep())
127 127 paths = os.sep.join(paths)
128 128
129 129 rm_path = os.path.join(self.repos_path, paths)
130 130 log.info("Removing group %s" % (rm_path))
131 131 # delete only if that path really exists
132 132 if os.path.isdir(rm_path):
133 133 if force_delete:
134 134 shutil.rmtree(rm_path)
135 135 else:
136 136 #archive that group`
137 137 _now = datetime.datetime.now()
138 138 _ms = str(_now.microsecond).rjust(6, '0')
139 139 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
140 140 group.name)
141 141 shutil.move(rm_path, os.path.join(self.repos_path, _d))
142 142
143 143 def create(self, group_name, group_description, owner, parent=None, just_db=False):
144 144 try:
145 145 new_repos_group = RepoGroup()
146 146 new_repos_group.group_description = group_description or group_name
147 147 new_repos_group.parent_group = self._get_repos_group(parent)
148 148 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
149 149
150 150 self.sa.add(new_repos_group)
151 151 self._create_default_perms(new_repos_group)
152 152
153 153 #create an ADMIN permission for owner, later owner should go into
154 154 #the owner field of groups
155 155 self.grant_user_permission(repos_group=new_repos_group,
156 156 user=owner, perm='group.admin')
157 157
158 158 if not just_db:
159 159 # we need to flush here, in order to check if database won't
160 160 # throw any exceptions, create filesystem dirs at the very end
161 161 self.sa.flush()
162 162 self.__create_group(new_repos_group.group_name)
163 163
164 164 return new_repos_group
165 165 except Exception:
166 166 log.error(traceback.format_exc())
167 167 raise
168 168
169 169 def _update_permissions(self, repos_group, perms_new=None,
170 170 perms_updates=None, recursive=False):
171 171 from rhodecode.model.repo import RepoModel
172 172 if not perms_new:
173 173 perms_new = []
174 174 if not perms_updates:
175 175 perms_updates = []
176 176
177 177 def _set_perm_user(obj, user, perm):
178 178 if isinstance(obj, RepoGroup):
179 179 ReposGroupModel().grant_user_permission(
180 180 repos_group=obj, user=user, perm=perm
181 181 )
182 182 elif isinstance(obj, Repository):
183 183 #we do this ONLY IF repository is non-private
184 184 if obj.private:
185 185 return
186 186
187 187 # we set group permission but we have to switch to repo
188 188 # permission
189 189 perm = perm.replace('group.', 'repository.')
190 190 RepoModel().grant_user_permission(
191 191 repo=obj, user=user, perm=perm
192 192 )
193 193
194 194 def _set_perm_group(obj, users_group, perm):
195 195 if isinstance(obj, RepoGroup):
196 196 ReposGroupModel().grant_users_group_permission(
197 197 repos_group=obj, group_name=users_group, perm=perm
198 198 )
199 199 elif isinstance(obj, Repository):
200 200 # we set group permission but we have to switch to repo
201 201 # permission
202 202 perm = perm.replace('group.', 'repository.')
203 203 RepoModel().grant_users_group_permission(
204 204 repo=obj, group_name=users_group, perm=perm
205 205 )
206 206 updates = []
207 207 log.debug('Now updating permissions for %s in recursive mode:%s'
208 208 % (repos_group, recursive))
209 209
210 210 for obj in repos_group.recursive_groups_and_repos():
211 211 #obj is an instance of a group or repositories in that group
212 212 if not recursive:
213 213 obj = repos_group
214 214
215 215 # update permissions
216 216 for member, perm, member_type in perms_updates:
217 217 ## set for user
218 218 if member_type == 'user':
219 219 # this updates also current one if found
220 220 _set_perm_user(obj, user=member, perm=perm)
221 221 ## set for user group
222 222 else:
223 223 _set_perm_group(obj, users_group=member, perm=perm)
224 224 # set new permissions
225 225 for member, perm, member_type in perms_new:
226 226 if member_type == 'user':
227 227 _set_perm_user(obj, user=member, perm=perm)
228 228 else:
229 229 _set_perm_group(obj, users_group=member, perm=perm)
230 230 updates.append(obj)
231 231 #if it's not recursive call
232 232 # break the loop and don't proceed with other changes
233 233 if not recursive:
234 234 break
235 235 return updates
236 236
237 237 def update(self, repos_group, form_data):
238 238
239 239 try:
240 240 repos_group = self._get_repos_group(repos_group)
241 241 recursive = form_data['recursive']
242 242 # iterate over all members(if in recursive mode) of this groups and
243 243 # set the permissions !
244 244 # this can be potentially heavy operation
245 245 self._update_permissions(repos_group, form_data['perms_new'],
246 246 form_data['perms_updates'], recursive)
247 247
248 248 old_path = repos_group.full_path
249 249
250 250 # change properties
251 251 repos_group.group_description = form_data['group_description']
252 252 repos_group.group_parent_id = form_data['group_parent_id']
253 253 repos_group.enable_locking = form_data['enable_locking']
254 254
255 255 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
256 256 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
257 257 new_path = repos_group.full_path
258 258 self.sa.add(repos_group)
259 259
260 260 # iterate over all members of this groups and do fixes
261 261 # set locking if given
262 262 # if obj is a repoGroup also fix the name of the group according
263 263 # to the parent
264 264 # if obj is a Repo fix it's name
265 265 # this can be potentially heavy operation
266 266 for obj in repos_group.recursive_groups_and_repos():
267 267 #set the value from it's parent
268 268 obj.enable_locking = repos_group.enable_locking
269 269 if isinstance(obj, RepoGroup):
270 270 new_name = obj.get_new_name(obj.name)
271 271 log.debug('Fixing group %s to new name %s' \
272 272 % (obj.group_name, new_name))
273 273 obj.group_name = new_name
274 274 elif isinstance(obj, Repository):
275 275 # we need to get all repositories from this new group and
276 276 # rename them accordingly to new group path
277 277 new_name = obj.get_new_name(obj.just_name)
278 278 log.debug('Fixing repo %s to new name %s' \
279 279 % (obj.repo_name, new_name))
280 280 obj.repo_name = new_name
281 281 self.sa.add(obj)
282 282
283 283 self.__rename_group(old_path, new_path)
284 284
285 285 return repos_group
286 286 except Exception:
287 287 log.error(traceback.format_exc())
288 288 raise
289 289
290 290 def delete(self, repos_group, force_delete=False):
291 291 repos_group = self._get_repos_group(repos_group)
292 292 try:
293 293 self.sa.delete(repos_group)
294 294 self.__delete_group(repos_group, force_delete)
295 295 except Exception:
296 296 log.error('Error removing repos_group %s' % repos_group)
297 297 raise
298 298
299 299 def delete_permission(self, repos_group, obj, obj_type, recursive):
300 300 """
301 301 Revokes permission for repos_group for given obj(user or users_group),
302 302 obj_type can be user or user group
303 303
304 304 :param repos_group:
305 305 :param obj: user or user group id
306 306 :param obj_type: user or user group type
307 307 :param recursive: recurse to all children of group
308 308 """
309 309 from rhodecode.model.repo import RepoModel
310 310 repos_group = self._get_repos_group(repos_group)
311 311
312 312 for el in repos_group.recursive_groups_and_repos():
313 313 if not recursive:
314 314 # if we don't recurse set the permission on only the top level
315 315 # object
316 316 el = repos_group
317 317
318 318 if isinstance(el, RepoGroup):
319 319 if obj_type == 'user':
320 320 ReposGroupModel().revoke_user_permission(el, user=obj)
321 321 elif obj_type == 'users_group':
322 322 ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
323 323 else:
324 324 raise Exception('undefined object type %s' % obj_type)
325 325 elif isinstance(el, Repository):
326 326 if obj_type == 'user':
327 327 RepoModel().revoke_user_permission(el, user=obj)
328 328 elif obj_type == 'users_group':
329 329 RepoModel().revoke_users_group_permission(el, group_name=obj)
330 330 else:
331 331 raise Exception('undefined object type %s' % obj_type)
332 332
333 333 #if it's not recursive call
334 334 # break the loop and don't proceed with other changes
335 335 if not recursive:
336 336 break
337 337
338 338 def grant_user_permission(self, repos_group, user, perm):
339 339 """
340 340 Grant permission for user on given repository group, or update
341 341 existing one if found
342 342
343 343 :param repos_group: Instance of ReposGroup, repositories_group_id,
344 344 or repositories_group name
345 345 :param user: Instance of User, user_id or username
346 346 :param perm: Instance of Permission, or permission_name
347 347 """
348 348
349 349 repos_group = self._get_repos_group(repos_group)
350 350 user = self._get_user(user)
351 351 permission = self._get_perm(perm)
352 352
353 353 # check if we have that permission already
354 354 obj = self.sa.query(UserRepoGroupToPerm)\
355 355 .filter(UserRepoGroupToPerm.user == user)\
356 356 .filter(UserRepoGroupToPerm.group == repos_group)\
357 357 .scalar()
358 358 if obj is None:
359 359 # create new !
360 360 obj = UserRepoGroupToPerm()
361 361 obj.group = repos_group
362 362 obj.user = user
363 363 obj.permission = permission
364 364 self.sa.add(obj)
365 365 log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group))
366 366
367 367 def revoke_user_permission(self, repos_group, user):
368 368 """
369 369 Revoke permission for user on given repository group
370 370
371 371 :param repos_group: Instance of ReposGroup, repositories_group_id,
372 372 or repositories_group name
373 373 :param user: Instance of User, user_id or username
374 374 """
375 375
376 376 repos_group = self._get_repos_group(repos_group)
377 377 user = self._get_user(user)
378 378
379 379 obj = self.sa.query(UserRepoGroupToPerm)\
380 380 .filter(UserRepoGroupToPerm.user == user)\
381 381 .filter(UserRepoGroupToPerm.group == repos_group)\
382 382 .scalar()
383 383 if obj:
384 384 self.sa.delete(obj)
385 385 log.debug('Revoked perm on %s on %s' % (repos_group, user))
386 386
387 387 def grant_users_group_permission(self, repos_group, group_name, perm):
388 388 """
389 389 Grant permission for user group on given repository group, or update
390 390 existing one if found
391 391
392 392 :param repos_group: Instance of ReposGroup, repositories_group_id,
393 393 or repositories_group name
394 394 :param group_name: Instance of UserGroup, users_group_id,
395 395 or user group name
396 396 :param perm: Instance of Permission, or permission_name
397 397 """
398 398 repos_group = self._get_repos_group(repos_group)
399 399 group_name = self.__get_users_group(group_name)
400 400 permission = self._get_perm(perm)
401 401
402 402 # check if we have that permission already
403 403 obj = self.sa.query(UserGroupRepoGroupToPerm)\
404 404 .filter(UserGroupRepoGroupToPerm.group == repos_group)\
405 405 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
406 406 .scalar()
407 407
408 408 if obj is None:
409 409 # create new
410 410 obj = UserGroupRepoGroupToPerm()
411 411
412 412 obj.group = repos_group
413 413 obj.users_group = group_name
414 414 obj.permission = permission
415 415 self.sa.add(obj)
416 416 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group))
417 417
418 418 def revoke_users_group_permission(self, repos_group, group_name):
419 419 """
420 420 Revoke permission for user group on given repository group
421 421
422 422 :param repos_group: Instance of ReposGroup, repositories_group_id,
423 423 or repositories_group name
424 424 :param group_name: Instance of UserGroup, users_group_id,
425 425 or user group name
426 426 """
427 427 repos_group = self._get_repos_group(repos_group)
428 428 group_name = self.__get_users_group(group_name)
429 429
430 430 obj = self.sa.query(UserGroupRepoGroupToPerm)\
431 431 .filter(UserGroupRepoGroupToPerm.group == repos_group)\
432 432 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
433 433 .scalar()
434 434 if obj:
435 435 self.sa.delete(obj)
436 436 log.debug('Revoked perm to %s on %s' % (repos_group, group_name))
@@ -1,763 +1,763 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 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 itertools
29 29 import collections
30 30 from pylons import url
31 31 from pylons.i18n.translation import _
32 32
33 33 from sqlalchemy.exc import DatabaseError
34 34 from sqlalchemy.orm import joinedload
35 35
36 36 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
37 37 from rhodecode.lib.caching_query import FromCache
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
40 40 UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
41 41 Notification, RepoGroup, UserRepoGroupToPerm, UserGroupRepoGroupToPerm, \
42 42 UserEmailMap, UserIpMap
43 43 from rhodecode.lib.exceptions import DefaultUserException, \
44 44 UserOwnsReposException
45 45 from rhodecode.model.meta import Session
46 46
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 PERM_WEIGHTS = Permission.PERM_WEIGHTS
51 51
52 52
53 53 class UserModel(BaseModel):
54 54 cls = User
55 55
56 56 def get(self, user_id, cache=False):
57 57 user = self.sa.query(User)
58 58 if cache:
59 59 user = user.options(FromCache("sql_cache_short",
60 60 "get_user_%s" % user_id))
61 61 return user.get(user_id)
62 62
63 63 def get_user(self, user):
64 64 return self._get_user(user)
65 65
66 66 def get_by_username(self, username, cache=False, case_insensitive=False):
67 67
68 68 if case_insensitive:
69 69 user = self.sa.query(User).filter(User.username.ilike(username))
70 70 else:
71 71 user = self.sa.query(User)\
72 72 .filter(User.username == username)
73 73 if cache:
74 74 user = user.options(FromCache("sql_cache_short",
75 75 "get_user_%s" % username))
76 76 return user.scalar()
77 77
78 78 def get_by_email(self, email, cache=False, case_insensitive=False):
79 79 return User.get_by_email(email, case_insensitive, cache)
80 80
81 81 def get_by_api_key(self, api_key, cache=False):
82 82 return User.get_by_api_key(api_key, cache)
83 83
84 84 def create(self, form_data):
85 85 from rhodecode.lib.auth import get_crypt_password
86 86 try:
87 87 new_user = User()
88 88 for k, v in form_data.items():
89 89 if k == 'password':
90 90 v = get_crypt_password(v)
91 91 if k == 'firstname':
92 92 k = 'name'
93 93 setattr(new_user, k, v)
94 94
95 95 new_user.api_key = generate_api_key(form_data['username'])
96 96 self.sa.add(new_user)
97 97 return new_user
98 98 except Exception:
99 99 log.error(traceback.format_exc())
100 100 raise
101 101
102 102 def create_or_update(self, username, password, email, firstname='',
103 103 lastname='', active=True, admin=False, ldap_dn=None):
104 104 """
105 105 Creates a new instance if not found, or updates current one
106 106
107 107 :param username:
108 108 :param password:
109 109 :param email:
110 110 :param active:
111 111 :param firstname:
112 112 :param lastname:
113 113 :param active:
114 114 :param admin:
115 115 :param ldap_dn:
116 116 """
117 117
118 118 from rhodecode.lib.auth import get_crypt_password
119 119
120 120 log.debug('Checking for %s account in RhodeCode database' % username)
121 121 user = User.get_by_username(username, case_insensitive=True)
122 122 if user is None:
123 123 log.debug('creating new user %s' % username)
124 124 new_user = User()
125 125 edit = False
126 126 else:
127 127 log.debug('updating user %s' % username)
128 128 new_user = user
129 129 edit = True
130 130
131 131 try:
132 132 new_user.username = username
133 133 new_user.admin = admin
134 134 # set password only if creating an user or password is changed
135 135 if not edit or user.password != password:
136 136 new_user.password = get_crypt_password(password)
137 137 new_user.api_key = generate_api_key(username)
138 138 new_user.email = email
139 139 new_user.active = active
140 140 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
141 141 new_user.name = firstname
142 142 new_user.lastname = lastname
143 143 self.sa.add(new_user)
144 144 return new_user
145 145 except (DatabaseError,):
146 146 log.error(traceback.format_exc())
147 147 raise
148 148
149 149 def create_for_container_auth(self, username, attrs):
150 150 """
151 151 Creates the given user if it's not already in the database
152 152
153 153 :param username:
154 154 :param attrs:
155 155 """
156 156 if self.get_by_username(username, case_insensitive=True) is None:
157 157
158 158 # autogenerate email for container account without one
159 159 generate_email = lambda usr: '%s@container_auth.account' % usr
160 160
161 161 try:
162 162 new_user = User()
163 163 new_user.username = username
164 164 new_user.password = None
165 165 new_user.api_key = generate_api_key(username)
166 166 new_user.email = attrs['email']
167 167 new_user.active = attrs.get('active', True)
168 168 new_user.name = attrs['name'] or generate_email(username)
169 169 new_user.lastname = attrs['lastname']
170 170
171 171 self.sa.add(new_user)
172 172 return new_user
173 173 except (DatabaseError,):
174 174 log.error(traceback.format_exc())
175 175 self.sa.rollback()
176 176 raise
177 177 log.debug('User %s already exists. Skipping creation of account'
178 178 ' for container auth.', username)
179 179 return None
180 180
181 181 def create_ldap(self, username, password, user_dn, attrs):
182 182 """
183 183 Checks if user is in database, if not creates this user marked
184 184 as ldap user
185 185
186 186 :param username:
187 187 :param password:
188 188 :param user_dn:
189 189 :param attrs:
190 190 """
191 191 from rhodecode.lib.auth import get_crypt_password
192 192 log.debug('Checking for such ldap account in RhodeCode database')
193 193 if self.get_by_username(username, case_insensitive=True) is None:
194 194
195 195 # autogenerate email for ldap account without one
196 196 generate_email = lambda usr: '%s@ldap.account' % usr
197 197
198 198 try:
199 199 new_user = User()
200 200 username = username.lower()
201 201 # add ldap account always lowercase
202 202 new_user.username = username
203 203 new_user.password = get_crypt_password(password)
204 204 new_user.api_key = generate_api_key(username)
205 205 new_user.email = attrs['email'] or generate_email(username)
206 206 new_user.active = attrs.get('active', True)
207 207 new_user.ldap_dn = safe_unicode(user_dn)
208 208 new_user.name = attrs['name']
209 209 new_user.lastname = attrs['lastname']
210 210
211 211 self.sa.add(new_user)
212 212 return new_user
213 213 except (DatabaseError,):
214 214 log.error(traceback.format_exc())
215 215 self.sa.rollback()
216 216 raise
217 217 log.debug('this %s user exists skipping creation of ldap account',
218 218 username)
219 219 return None
220 220
221 221 def create_registration(self, form_data):
222 222 from rhodecode.model.notification import NotificationModel
223 223
224 224 try:
225 225 form_data['admin'] = False
226 226 new_user = self.create(form_data)
227 227
228 228 self.sa.add(new_user)
229 229 self.sa.flush()
230 230
231 231 # notification to admins
232 232 subject = _('new user registration')
233 233 body = ('New user registration\n'
234 234 '---------------------\n'
235 235 '- Username: %s\n'
236 236 '- Full Name: %s\n'
237 237 '- Email: %s\n')
238 238 body = body % (new_user.username, new_user.full_name,
239 239 new_user.email)
240 240 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
241 241 kw = {'registered_user_url': edit_url}
242 242 NotificationModel().create(created_by=new_user, subject=subject,
243 243 body=body, recipients=None,
244 244 type_=Notification.TYPE_REGISTRATION,
245 245 email_kwargs=kw)
246 246
247 247 except Exception:
248 248 log.error(traceback.format_exc())
249 249 raise
250 250
251 251 def update(self, user_id, form_data, skip_attrs=[]):
252 252 from rhodecode.lib.auth import get_crypt_password
253 253 try:
254 254 user = self.get(user_id, cache=False)
255 255 if user.username == 'default':
256 256 raise DefaultUserException(
257 257 _("You can't Edit this user since it's"
258 258 " crucial for entire application"))
259 259
260 260 for k, v in form_data.items():
261 261 if k in skip_attrs:
262 262 continue
263 263 if k == 'new_password' and v:
264 264 user.password = get_crypt_password(v)
265 265 user.api_key = generate_api_key(user.username)
266 266 else:
267 267 if k == 'firstname':
268 268 k = 'name'
269 269 setattr(user, k, v)
270 270 self.sa.add(user)
271 271 except Exception:
272 272 log.error(traceback.format_exc())
273 273 raise
274 274
275 275 def update_user(self, user, **kwargs):
276 276 from rhodecode.lib.auth import get_crypt_password
277 277 try:
278 278 user = self._get_user(user)
279 279 if user.username == 'default':
280 280 raise DefaultUserException(
281 281 _("You can't Edit this user since it's"
282 282 " crucial for entire application")
283 283 )
284 284
285 285 for k, v in kwargs.items():
286 286 if k == 'password' and v:
287 287 v = get_crypt_password(v)
288 288 user.api_key = generate_api_key(user.username)
289 289
290 290 setattr(user, k, v)
291 291 self.sa.add(user)
292 292 return user
293 293 except Exception:
294 294 log.error(traceback.format_exc())
295 295 raise
296 296
297 297 def delete(self, user):
298 298 user = self._get_user(user)
299 299
300 300 try:
301 301 if user.username == 'default':
302 302 raise DefaultUserException(
303 303 _(u"You can't remove this user since it's"
304 304 " crucial for entire application")
305 305 )
306 306 if user.repositories:
307 307 repos = [x.repo_name for x in user.repositories]
308 308 raise UserOwnsReposException(
309 309 _(u'user "%s" still owns %s repositories and cannot be '
310 310 'removed. Switch owners or remove those repositories. %s')
311 311 % (user.username, len(repos), ', '.join(repos))
312 312 )
313 313 self.sa.delete(user)
314 314 except Exception:
315 315 log.error(traceback.format_exc())
316 316 raise
317 317
318 318 def reset_password_link(self, data):
319 319 from rhodecode.lib.celerylib import tasks, run_task
320 320 from rhodecode.model.notification import EmailNotificationModel
321 321 user_email = data['email']
322 322 try:
323 323 user = User.get_by_email(user_email)
324 324 if user:
325 325 log.debug('password reset user found %s' % user)
326 326 link = url('reset_password_confirmation', key=user.api_key,
327 327 qualified=True)
328 328 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
329 329 body = EmailNotificationModel().get_email_tmpl(reg_type,
330 330 **{'user': user.short_contact,
331 331 'reset_url': link})
332 332 log.debug('sending email')
333 333 run_task(tasks.send_email, user_email,
334 334 _("password reset link"), body, body)
335 335 log.info('send new password mail to %s' % user_email)
336 336 else:
337 337 log.debug("password reset email %s not found" % user_email)
338 338 except Exception:
339 339 log.error(traceback.format_exc())
340 340 return False
341 341
342 342 return True
343 343
344 344 def reset_password(self, data):
345 345 from rhodecode.lib.celerylib import tasks, run_task
346 346 from rhodecode.lib import auth
347 347 user_email = data['email']
348 348 try:
349 349 try:
350 350 user = User.get_by_email(user_email)
351 351 new_passwd = auth.PasswordGenerator().gen_password(8,
352 352 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
353 353 if user:
354 354 user.password = auth.get_crypt_password(new_passwd)
355 355 user.api_key = auth.generate_api_key(user.username)
356 356 Session().add(user)
357 357 Session().commit()
358 358 log.info('change password for %s' % user_email)
359 359 if new_passwd is None:
360 360 raise Exception('unable to generate new password')
361 361 except Exception:
362 362 log.error(traceback.format_exc())
363 363 Session().rollback()
364 364
365 365 run_task(tasks.send_email, user_email,
366 366 _('Your new password'),
367 367 _('Your new RhodeCode password:%s') % (new_passwd))
368 368 log.info('send new password mail to %s' % user_email)
369 369
370 370 except Exception:
371 371 log.error('Failed to update user password')
372 372 log.error(traceback.format_exc())
373 373
374 374 return True
375 375
376 376 def fill_data(self, auth_user, user_id=None, api_key=None):
377 377 """
378 378 Fetches auth_user by user_id,or api_key if present.
379 379 Fills auth_user attributes with those taken from database.
380 380 Additionally set's is_authenitated if lookup fails
381 381 present in database
382 382
383 383 :param auth_user: instance of user to set attributes
384 384 :param user_id: user id to fetch by
385 385 :param api_key: api key to fetch by
386 386 """
387 387 if user_id is None and api_key is None:
388 388 raise Exception('You need to pass user_id or api_key')
389 389
390 390 try:
391 391 if api_key:
392 392 dbuser = self.get_by_api_key(api_key)
393 393 else:
394 394 dbuser = self.get(user_id)
395 395
396 396 if dbuser is not None and dbuser.active:
397 397 log.debug('filling %s data' % dbuser)
398 398 for k, v in dbuser.get_dict().items():
399 399 setattr(auth_user, k, v)
400 400 else:
401 401 return False
402 402
403 403 except Exception:
404 404 log.error(traceback.format_exc())
405 405 auth_user.is_authenticated = False
406 406 return False
407 407
408 408 return True
409 409
410 410 def fill_perms(self, user, explicit=True, algo='higherwin'):
411 411 """
412 412 Fills user permission attribute with permissions taken from database
413 413 works for permissions given for repositories, and for permissions that
414 414 are granted to groups
415 415
416 416 :param user: user instance to fill his perms
417 417 :param explicit: In case there are permissions both for user and a group
418 418 that user is part of, explicit flag will defiine if user will
419 419 explicitly override permissions from group, if it's False it will
420 420 make decision based on the algo
421 421 :param algo: algorithm to decide what permission should be choose if
422 422 it's multiple defined, eg user in two different groups. It also
423 423 decides if explicit flag is turned off how to specify the permission
424 424 for case when user is in a group + have defined separate permission
425 425 """
426 426 RK = 'repositories'
427 427 GK = 'repositories_groups'
428 428 GLOBAL = 'global'
429 429 user.permissions[RK] = {}
430 430 user.permissions[GK] = {}
431 431 user.permissions[GLOBAL] = set()
432 432
433 433 def _choose_perm(new_perm, cur_perm):
434 434 new_perm_val = PERM_WEIGHTS[new_perm]
435 435 cur_perm_val = PERM_WEIGHTS[cur_perm]
436 436 if algo == 'higherwin':
437 437 if new_perm_val > cur_perm_val:
438 438 return new_perm
439 439 return cur_perm
440 440 elif algo == 'lowerwin':
441 441 if new_perm_val < cur_perm_val:
442 442 return new_perm
443 443 return cur_perm
444 444
445 445 #======================================================================
446 446 # fetch default permissions
447 447 #======================================================================
448 448 default_user = User.get_by_username('default', cache=True)
449 449 default_user_id = default_user.user_id
450 450
451 451 default_repo_perms = Permission.get_default_perms(default_user_id)
452 452 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
453 453
454 454 if user.is_admin:
455 455 #==================================================================
456 456 # admin user have all default rights for repositories
457 457 # and groups set to admin
458 458 #==================================================================
459 459 user.permissions[GLOBAL].add('hg.admin')
460 460
461 461 # repositories
462 462 for perm in default_repo_perms:
463 463 r_k = perm.UserRepoToPerm.repository.repo_name
464 464 p = 'repository.admin'
465 465 user.permissions[RK][r_k] = p
466 466
467 467 # repository groups
468 468 for perm in default_repo_groups_perms:
469 469 rg_k = perm.UserRepoGroupToPerm.group.group_name
470 470 p = 'group.admin'
471 471 user.permissions[GK][rg_k] = p
472 472 return user
473 473
474 474 #==================================================================
475 # SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS
475 # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
476 476 #==================================================================
477 477 uid = user.user_id
478 478
479 479 # default global permissions taken fron the default user
480 480 default_global_perms = self.sa.query(UserToPerm)\
481 481 .filter(UserToPerm.user_id == default_user_id)
482 482
483 483 for perm in default_global_perms:
484 484 user.permissions[GLOBAL].add(perm.permission.permission_name)
485 485
486 486 # defaults for repositories, taken from default user
487 487 for perm in default_repo_perms:
488 488 r_k = perm.UserRepoToPerm.repository.repo_name
489 489 if perm.Repository.private and not (perm.Repository.user_id == uid):
490 490 # disable defaults for private repos,
491 491 p = 'repository.none'
492 492 elif perm.Repository.user_id == uid:
493 493 # set admin if owner
494 494 p = 'repository.admin'
495 495 else:
496 496 p = perm.Permission.permission_name
497 497
498 498 user.permissions[RK][r_k] = p
499 499
500 500 # defaults for repository groups taken from default user permission
501 501 # on given group
502 502 for perm in default_repo_groups_perms:
503 503 rg_k = perm.UserRepoGroupToPerm.group.group_name
504 504 p = perm.Permission.permission_name
505 505 user.permissions[GK][rg_k] = p
506 506
507 507 #======================================================================
508 508 # !! OVERRIDE GLOBALS !! with user permissions if any found
509 509 #======================================================================
510 510 # those can be configured from groups or users explicitly
511 511 _configurable = set(['hg.fork.none', 'hg.fork.repository',
512 512 'hg.create.none', 'hg.create.repository'])
513 513
514 514 # USER GROUPS comes first
515 515 # user group global permissions
516 516 user_perms_from_users_groups = self.sa.query(UserGroupToPerm)\
517 517 .options(joinedload(UserGroupToPerm.permission))\
518 518 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
519 519 UserGroupMember.users_group_id))\
520 520 .filter(UserGroupMember.user_id == uid)\
521 521 .order_by(UserGroupToPerm.users_group_id)\
522 522 .all()
523 523 #need to group here by groups since user can be in more than one group
524 524 _grouped = [[x, list(y)] for x, y in
525 525 itertools.groupby(user_perms_from_users_groups,
526 526 lambda x:x.users_group)]
527 527 for gr, perms in _grouped:
528 528 # since user can be in multiple groups iterate over them and
529 529 # select the lowest permissions first (more explicit)
530 530 ##TODO: do this^^
531 531 if not gr.inherit_default_permissions:
532 532 # NEED TO IGNORE all configurable permissions and
533 533 # replace them with explicitly set
534 534 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
535 535 .difference(_configurable)
536 536 for perm in perms:
537 537 user.permissions[GLOBAL].add(perm.permission.permission_name)
538 538
539 539 # user specific global permissions
540 540 user_perms = self.sa.query(UserToPerm)\
541 541 .options(joinedload(UserToPerm.permission))\
542 542 .filter(UserToPerm.user_id == uid).all()
543 543
544 544 if not user.inherit_default_permissions:
545 545 # NEED TO IGNORE all configurable permissions and
546 546 # replace them with explicitly set
547 547 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
548 548 .difference(_configurable)
549 549
550 550 for perm in user_perms:
551 551 user.permissions[GLOBAL].add(perm.permission.permission_name)
552 552
553 553 #======================================================================
554 554 # !! PERMISSIONS FOR REPOSITORIES !!
555 555 #======================================================================
556 556 #======================================================================
557 557 # check if user is part of user groups for this repository and
558 558 # fill in his permission from it. _choose_perm decides of which
559 559 # permission should be selected based on selected method
560 560 #======================================================================
561 561
562 562 # user group for repositories permissions
563 563 user_repo_perms_from_users_groups = \
564 564 self.sa.query(UserGroupRepoToPerm, Permission, Repository,)\
565 565 .join((Repository, UserGroupRepoToPerm.repository_id ==
566 566 Repository.repo_id))\
567 567 .join((Permission, UserGroupRepoToPerm.permission_id ==
568 568 Permission.permission_id))\
569 569 .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
570 570 UserGroupMember.users_group_id))\
571 571 .filter(UserGroupMember.user_id == uid)\
572 572 .all()
573 573
574 574 multiple_counter = collections.defaultdict(int)
575 575 for perm in user_repo_perms_from_users_groups:
576 576 r_k = perm.UserGroupRepoToPerm.repository.repo_name
577 577 multiple_counter[r_k] += 1
578 578 p = perm.Permission.permission_name
579 579 cur_perm = user.permissions[RK][r_k]
580 580
581 581 if perm.Repository.user_id == uid:
582 582 # set admin if owner
583 583 p = 'repository.admin'
584 584 else:
585 585 if multiple_counter[r_k] > 1:
586 586 p = _choose_perm(p, cur_perm)
587 587 user.permissions[RK][r_k] = p
588 588
589 589 # user explicit permissions for repositories, overrides any specified
590 590 # by the group permission
591 591 user_repo_perms = \
592 592 self.sa.query(UserRepoToPerm, Permission, Repository)\
593 593 .join((Repository, UserRepoToPerm.repository_id ==
594 594 Repository.repo_id))\
595 595 .join((Permission, UserRepoToPerm.permission_id ==
596 596 Permission.permission_id))\
597 597 .filter(UserRepoToPerm.user_id == uid)\
598 598 .all()
599 599
600 600 for perm in user_repo_perms:
601 601 r_k = perm.UserRepoToPerm.repository.repo_name
602 602 cur_perm = user.permissions[RK][r_k]
603 603 # set admin if owner
604 604 if perm.Repository.user_id == uid:
605 605 p = 'repository.admin'
606 606 else:
607 607 p = perm.Permission.permission_name
608 608 if not explicit:
609 609 p = _choose_perm(p, cur_perm)
610 610 user.permissions[RK][r_k] = p
611 611
612 612 #======================================================================
613 613 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
614 614 #======================================================================
615 615 #======================================================================
616 616 # check if user is part of user groups for this repository groups and
617 617 # fill in his permission from it. _choose_perm decides of which
618 618 # permission should be selected based on selected method
619 619 #======================================================================
620 620 # user group for repo groups permissions
621 621 user_repo_group_perms_from_users_groups = \
622 622 self.sa.query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
623 623 .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
624 624 .join((Permission, UserGroupRepoGroupToPerm.permission_id
625 625 == Permission.permission_id))\
626 626 .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
627 627 == UserGroupMember.users_group_id))\
628 628 .filter(UserGroupMember.user_id == uid)\
629 629 .all()
630 630
631 631 multiple_counter = collections.defaultdict(int)
632 632 for perm in user_repo_group_perms_from_users_groups:
633 633 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
634 634 multiple_counter[g_k] += 1
635 635 p = perm.Permission.permission_name
636 636 cur_perm = user.permissions[GK][g_k]
637 637 if multiple_counter[g_k] > 1:
638 638 p = _choose_perm(p, cur_perm)
639 639 user.permissions[GK][g_k] = p
640 640
641 641 # user explicit permissions for repository groups
642 642 user_repo_groups_perms = \
643 643 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
644 644 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
645 645 .join((Permission, UserRepoGroupToPerm.permission_id
646 646 == Permission.permission_id))\
647 647 .filter(UserRepoGroupToPerm.user_id == uid)\
648 648 .all()
649 649
650 650 for perm in user_repo_groups_perms:
651 651 rg_k = perm.UserRepoGroupToPerm.group.group_name
652 652 p = perm.Permission.permission_name
653 653 cur_perm = user.permissions[GK][rg_k]
654 654 if not explicit:
655 655 p = _choose_perm(p, cur_perm)
656 656 user.permissions[GK][rg_k] = p
657 657
658 658 return user
659 659
660 660 def has_perm(self, user, perm):
661 661 perm = self._get_perm(perm)
662 662 user = self._get_user(user)
663 663
664 664 return UserToPerm.query().filter(UserToPerm.user == user)\
665 665 .filter(UserToPerm.permission == perm).scalar() is not None
666 666
667 667 def grant_perm(self, user, perm):
668 668 """
669 669 Grant user global permissions
670 670
671 671 :param user:
672 672 :param perm:
673 673 """
674 674 user = self._get_user(user)
675 675 perm = self._get_perm(perm)
676 676 # if this permission is already granted skip it
677 677 _perm = UserToPerm.query()\
678 678 .filter(UserToPerm.user == user)\
679 679 .filter(UserToPerm.permission == perm)\
680 680 .scalar()
681 681 if _perm:
682 682 return
683 683 new = UserToPerm()
684 684 new.user = user
685 685 new.permission = perm
686 686 self.sa.add(new)
687 687
688 688 def revoke_perm(self, user, perm):
689 689 """
690 690 Revoke users global permissions
691 691
692 692 :param user:
693 693 :param perm:
694 694 """
695 695 user = self._get_user(user)
696 696 perm = self._get_perm(perm)
697 697
698 698 obj = UserToPerm.query()\
699 699 .filter(UserToPerm.user == user)\
700 700 .filter(UserToPerm.permission == perm)\
701 701 .scalar()
702 702 if obj:
703 703 self.sa.delete(obj)
704 704
705 705 def add_extra_email(self, user, email):
706 706 """
707 707 Adds email address to UserEmailMap
708 708
709 709 :param user:
710 710 :param email:
711 711 """
712 712 from rhodecode.model import forms
713 713 form = forms.UserExtraEmailForm()()
714 714 data = form.to_python(dict(email=email))
715 715 user = self._get_user(user)
716 716
717 717 obj = UserEmailMap()
718 718 obj.user = user
719 719 obj.email = data['email']
720 720 self.sa.add(obj)
721 721 return obj
722 722
723 723 def delete_extra_email(self, user, email_id):
724 724 """
725 725 Removes email address from UserEmailMap
726 726
727 727 :param user:
728 728 :param email_id:
729 729 """
730 730 user = self._get_user(user)
731 731 obj = UserEmailMap.query().get(email_id)
732 732 if obj:
733 733 self.sa.delete(obj)
734 734
735 735 def add_extra_ip(self, user, ip):
736 736 """
737 737 Adds ip address to UserIpMap
738 738
739 739 :param user:
740 740 :param ip:
741 741 """
742 742 from rhodecode.model import forms
743 743 form = forms.UserExtraIpForm()()
744 744 data = form.to_python(dict(ip=ip))
745 745 user = self._get_user(user)
746 746
747 747 obj = UserIpMap()
748 748 obj.user = user
749 749 obj.ip_addr = data['ip']
750 750 self.sa.add(obj)
751 751 return obj
752 752
753 753 def delete_extra_ip(self, user, ip_id):
754 754 """
755 755 Removes ip address from UserIpMap
756 756
757 757 :param user:
758 758 :param ip_id:
759 759 """
760 760 user = self._get_user(user)
761 761 obj = UserIpMap.query().get(ip_id)
762 762 if obj:
763 763 self.sa.delete(obj)
@@ -1,192 +1,192 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.users_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 user group model for RhodeCode
7 7
8 8 :created_on: Oct 1, 2011
9 9 :author: nvinot
10 10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
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 logging
28 28 import traceback
29 29
30 30 from rhodecode.model import BaseModel
31 31 from rhodecode.model.db import UserGroupMember, UserGroup,\
32 32 UserGroupRepoToPerm, Permission, UserGroupToPerm, User
33 33 from rhodecode.lib.exceptions import UserGroupsAssignedException
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class UserGroupModel(BaseModel):
39 39
40 40 cls = UserGroup
41 41
42 42 def __get_users_group(self, users_group):
43 43 return self._get_instance(UserGroup, users_group,
44 44 callback=UserGroup.get_by_group_name)
45 45
46 46 def get(self, users_group_id, cache=False):
47 47 return UserGroup.get(users_group_id)
48 48
49 49 def get_group(self, users_group):
50 50 return self.__get_users_group(users_group)
51 51
52 52 def get_by_name(self, name, cache=False, case_insensitive=False):
53 53 return UserGroup.get_by_group_name(name, cache, case_insensitive)
54 54
55 55 def create(self, name, active=True):
56 56 try:
57 57 new = UserGroup()
58 58 new.users_group_name = name
59 59 new.users_group_active = active
60 60 self.sa.add(new)
61 61 return new
62 62 except Exception:
63 63 log.error(traceback.format_exc())
64 64 raise
65 65
66 66 def update(self, users_group, form_data):
67 67
68 68 try:
69 69 users_group = self.__get_users_group(users_group)
70 70
71 71 for k, v in form_data.items():
72 72 if k == 'users_group_members':
73 73 users_group.members = []
74 74 self.sa.flush()
75 75 members_list = []
76 76 if v:
77 77 v = [v] if isinstance(v, basestring) else v
78 78 for u_id in set(v):
79 79 member = UserGroupMember(users_group.users_group_id, u_id)
80 80 members_list.append(member)
81 81 setattr(users_group, 'members', members_list)
82 82 setattr(users_group, k, v)
83 83
84 84 self.sa.add(users_group)
85 85 except Exception:
86 86 log.error(traceback.format_exc())
87 87 raise
88 88
89 89 def delete(self, users_group, force=False):
90 90 """
91 Deletes repos group, unless force flag is used
91 Deletes repository group, unless force flag is used
92 92 raises exception if there are members in that group, else deletes
93 93 group and users
94 94
95 95 :param users_group:
96 96 :param force:
97 97 """
98 98 try:
99 99 users_group = self.__get_users_group(users_group)
100 100
101 101 # check if this group is not assigned to repo
102 102 assigned_groups = UserGroupRepoToPerm.query()\
103 103 .filter(UserGroupRepoToPerm.users_group == users_group).all()
104 104
105 105 if assigned_groups and not force:
106 106 raise UserGroupsAssignedException('RepoGroup assigned to %s' %
107 107 assigned_groups)
108 108
109 109 self.sa.delete(users_group)
110 110 except Exception:
111 111 log.error(traceback.format_exc())
112 112 raise
113 113
114 114 def add_user_to_group(self, users_group, user):
115 115 users_group = self.__get_users_group(users_group)
116 116 user = self._get_user(user)
117 117
118 118 for m in users_group.members:
119 119 u = m.user
120 120 if u.user_id == user.user_id:
121 121 return True
122 122
123 123 try:
124 124 users_group_member = UserGroupMember()
125 125 users_group_member.user = user
126 126 users_group_member.users_group = users_group
127 127
128 128 users_group.members.append(users_group_member)
129 129 user.group_member.append(users_group_member)
130 130
131 131 self.sa.add(users_group_member)
132 132 return users_group_member
133 133 except Exception:
134 134 log.error(traceback.format_exc())
135 135 raise
136 136
137 137 def remove_user_from_group(self, users_group, user):
138 138 users_group = self.__get_users_group(users_group)
139 139 user = self._get_user(user)
140 140
141 141 users_group_member = None
142 142 for m in users_group.members:
143 143 if m.user.user_id == user.user_id:
144 144 # Found this user's membership row
145 145 users_group_member = m
146 146 break
147 147
148 148 if users_group_member:
149 149 try:
150 150 self.sa.delete(users_group_member)
151 151 return True
152 152 except Exception:
153 153 log.error(traceback.format_exc())
154 154 raise
155 155 else:
156 156 # User isn't in that group
157 157 return False
158 158
159 159 def has_perm(self, users_group, perm):
160 160 users_group = self.__get_users_group(users_group)
161 161 perm = self._get_perm(perm)
162 162
163 163 return UserGroupToPerm.query()\
164 164 .filter(UserGroupToPerm.users_group == users_group)\
165 165 .filter(UserGroupToPerm.permission == perm).scalar() is not None
166 166
167 167 def grant_perm(self, users_group, perm):
168 168 users_group = self.__get_users_group(users_group)
169 169 perm = self._get_perm(perm)
170 170
171 171 # if this permission is already granted skip it
172 172 _perm = UserGroupToPerm.query()\
173 173 .filter(UserGroupToPerm.users_group == users_group)\
174 174 .filter(UserGroupToPerm.permission == perm)\
175 175 .scalar()
176 176 if _perm:
177 177 return
178 178
179 179 new = UserGroupToPerm()
180 180 new.users_group = users_group
181 181 new.permission = perm
182 182 self.sa.add(new)
183 183
184 184 def revoke_perm(self, users_group, perm):
185 185 users_group = self.__get_users_group(users_group)
186 186 perm = self._get_perm(perm)
187 187
188 188 obj = UserGroupToPerm.query()\
189 189 .filter(UserGroupToPerm.users_group == users_group)\
190 190 .filter(UserGroupToPerm.permission == perm).scalar()
191 191 if obj:
192 192 self.sa.delete(obj)
@@ -1,65 +1,65 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 ${_('Add repos group')} &middot; ${c.rhodecode_name}
5 ${_('Add repository group')} &middot; ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 ${h.link_to(_('Repository groups'),h.url('repos_groups'))}
12 12 &raquo;
13 ${_('add new repos group')}
13 ${_('Add new repository group')}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('repos_groups'))}
28 28 <div class="form">
29 29 <!-- fields -->
30 30 <div class="fields">
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="group_name">${_('Group name')}:</label>
34 34 </div>
35 35 <div class="input">
36 36 ${h.text('group_name',class_='medium')}
37 37 </div>
38 38 </div>
39 39
40 40 <div class="field">
41 41 <div class="label label-textarea">
42 42 <label for="group_description">${_('Description')}:</label>
43 43 </div>
44 44 <div class="textarea text-area editor">
45 45 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
46 46 </div>
47 47 </div>
48 48
49 49 <div class="field">
50 50 <div class="label">
51 51 <label for="group_parent_id">${_('Group parent')}:</label>
52 52 </div>
53 53 <div class="input">
54 54 ${h.select('group_parent_id',request.GET.get('parent_group'),c.repo_groups,class_="medium")}
55 55 </div>
56 56 </div>
57 57
58 58 <div class="buttons">
59 59 ${h.submit('save',_('save'),class_="ui-btn large")}
60 60 </div>
61 61 </div>
62 62 </div>
63 63 ${h.end_form()}
64 64 </div>
65 65 </%def>
@@ -1,87 +1,87 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 ${_('Edit repos group')} ${c.repos_group.name} &middot; ${c.rhodecode_name}
5 ${_('Edit repository group')} ${c.repos_group.name} &middot; ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 ${h.link_to(_('Repository groups'),h.url('repos_groups'))}
12 12 &raquo;
13 ${_('edit repos group')} "${c.repos_group.name}"
13 ${_('Edit repository group')} "${c.repos_group.name}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 <ul class="links">
26 26 <li>
27 27 <span>${h.link_to(_(u'Add child group'),h.url('new_repos_group', parent_group=c.repos_group.group_id))}</span>
28 28 </li>
29 29 </ul>
30 30 </div>
31 31 <!-- end box / title -->
32 32 ${h.form(url('repos_group',group_name=c.repos_group.group_name),method='put')}
33 33 <div class="form">
34 34 <!-- fields -->
35 35 <div class="fields">
36 36 <div class="field">
37 37 <div class="label">
38 38 <label for="group_name">${_('Group name')}:</label>
39 39 </div>
40 40 <div class="input">
41 41 ${h.text('group_name',class_='medium')}
42 42 </div>
43 43 </div>
44 44
45 45 <div class="field">
46 46 <div class="label label-textarea">
47 47 <label for="group_description">${_('Description')}:</label>
48 48 </div>
49 49 <div class="textarea text-area editor">
50 50 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
51 51 </div>
52 52 </div>
53 53
54 54 <div class="field">
55 55 <div class="label">
56 56 <label for="group_parent_id">${_('Group parent')}:</label>
57 57 </div>
58 58 <div class="input">
59 59 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
60 60 </div>
61 61 </div>
62 62 <div class="field">
63 63 <div class="label">
64 64 <label for="input">${_('Permissions')}:</label>
65 65 </div>
66 66 <div class="input">
67 67 <%include file="repos_group_edit_perms.html"/>
68 68 </div>
69 69 </div>
70 70 <div class="field">
71 71 <div class="label label-checkbox">
72 72 <label for="enable_locking">${_('Enable locking')}:</label>
73 73 </div>
74 74 <div class="checkboxes">
75 75 ${h.checkbox('enable_locking',value="True")}
76 76 <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
77 77 </div>
78 78 </div>
79 79 <div class="buttons">
80 80 ${h.submit('save',_('Save'),class_="ui-btn large")}
81 81 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
82 82 </div>
83 83 </div>
84 84 </div>
85 85 ${h.end_form()}
86 86 </div>
87 87 </%def>
General Comments 0
You need to be logged in to leave comments. Login now