##// END OF EJS Templates
auto-healing of permissions for default user after upgrading from some old versions.
marcink -
r2798:091e99b2 beta
parent child Browse files
Show More
@@ -1,731 +1,737 b''
1 1 .. _changelog:
2 2
3 3 =========
4 4 Changelog
5 5 =========
6 6
7 7
8 1.4.1 (**2012-XX-XX**)
8 1.4.1 (**2012-09-04**)
9 9 ----------------------
10 10
11 11 :status: in-progress
12 12 :branch: beta
13 13
14 14 news
15 15 ++++
16 16
17 17 - always put a comment about code-review status change even if user send
18 18 empty data
19 - modified_on column saves repository update and it's going to be used
20 later for light version of main page ref #500
19 21
20 22 fixes
21 23 +++++
22 24
23 - fixed migrations of permissions that can lead to inconsistency issue
25 - fixed migrations of permissions that can lead to inconsistency.
26 Some users sent feedback that after upgrading from older versions issues with updating
27 default permissions occured. RhodeCode detects that now and resets default user
28 permission to initial state if there is a need for that. Also forces users to set
29 the default value for new forking permission.
24 30
25 31
26 32 1.4.0 (**2012-09-03**)
27 33 ----------------------
28 34
29 35 news
30 36 ++++
31 37
32 38 - new codereview system
33 39 - email map, allowing users to have multiple email addresses mapped into
34 40 their accounts
35 41 - improved git-hook system. Now all actions for git are logged into journal
36 42 including pushed revisions, user and IP address
37 43 - changed setup-app into setup-rhodecode and added default options to it.
38 44 - new git repos are created as bare now by default
39 45 - #464 added links to groups in permission box
40 46 - #465 mentions autocomplete inside comments boxes
41 47 - #469 added --update-only option to whoosh to re-index only given list
42 48 of repos in index
43 49 - rhodecode-api CLI client
44 50 - new git http protocol replaced buggy dulwich implementation.
45 51 Now based on pygrack & gitweb
46 52 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
47 53 reformated based on user suggestions. Additional rss/atom feeds for user
48 54 journal
49 55 - various i18n improvements
50 56 - #478 permissions overview for admin in user edit view
51 57 - File view now displays small gravatars off all authors of given file
52 58 - Implemented landing revisions. Each repository will get landing_rev attribute
53 59 that defines 'default' revision/branch for generating readme files
54 60 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
55 61 earliest possible call.
56 62 - Import remote svn repositories to mercurial using hgsubversion.
57 63 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
58 64 - RhodeCode can use alternative server for generating avatar icons
59 65 - implemented repositories locking. Pull locks, push unlocks. Also can be done
60 66 via API calls
61 67 - #538 form for permissions can handle multiple users at once
62 68
63 69 fixes
64 70 +++++
65 71
66 72 - improved translations
67 73 - fixes issue #455 Creating an archive generates an exception on Windows
68 74 - fixes #448 Download ZIP archive keeps file in /tmp open and results
69 75 in out of disk space
70 76 - fixes issue #454 Search results under Windows include proceeding
71 77 backslash
72 78 - fixed issue #450. Rhodecode no longer will crash when bad revision is
73 79 present in journal data.
74 80 - fix for issue #417, git execution was broken on windows for certain
75 81 commands.
76 82 - fixed #413. Don't disable .git directory for bare repos on deleting
77 83 - fixed issue #459. Changed the way of obtaining logger in reindex task.
78 84 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
79 85 reindexing modified files
80 86 - fixed #481 rhodecode emails are sent without Date header
81 87 - fixed #458 wrong count when no repos are present
82 88 - fixed issue #492 missing `\ No newline at end of file` test at the end of
83 89 new chunk in html diff
84 90 - full text search now works also for commit messages
85 91
86 92 1.3.6 (**2012-05-17**)
87 93 ----------------------
88 94
89 95 news
90 96 ++++
91 97
92 98 - chinese traditional translation
93 99 - changed setup-app into setup-rhodecode and added arguments for auto-setup
94 100 mode that doesn't need user interaction
95 101
96 102 fixes
97 103 +++++
98 104
99 105 - fixed no scm found warning
100 106 - fixed __future__ import error on rcextensions
101 107 - made simplejson required lib for speedup on JSON encoding
102 108 - fixes #449 bad regex could get more than revisions from parsing history
103 109 - don't clear DB session when CELERY_EAGER is turned ON
104 110
105 111 1.3.5 (**2012-05-10**)
106 112 ----------------------
107 113
108 114 news
109 115 ++++
110 116
111 117 - use ext_json for json module
112 118 - unified annotation view with file source view
113 119 - notification improvements, better inbox + css
114 120 - #419 don't strip passwords for login forms, make rhodecode
115 121 more compatible with LDAP servers
116 122 - Added HTTP_X_FORWARDED_FOR as another method of extracting
117 123 IP for pull/push logs. - moved all to base controller
118 124 - #415: Adding comment to changeset causes reload.
119 125 Comments are now added via ajax and doesn't reload the page
120 126 - #374 LDAP config is discarded when LDAP can't be activated
121 127 - limited push/pull operations are now logged for git in the journal
122 128 - bumped mercurial to 2.2.X series
123 129 - added support for displaying submodules in file-browser
124 130 - #421 added bookmarks in changelog view
125 131
126 132 fixes
127 133 +++++
128 134
129 135 - fixed dev-version marker for stable when served from source codes
130 136 - fixed missing permission checks on show forks page
131 137 - #418 cast to unicode fixes in notification objects
132 138 - #426 fixed mention extracting regex
133 139 - fixed remote-pulling for git remotes remopositories
134 140 - fixed #434: Error when accessing files or changesets of a git repository
135 141 with submodules
136 142 - fixed issue with empty APIKEYS for users after registration ref. #438
137 143 - fixed issue with getting README files from git repositories
138 144
139 145 1.3.4 (**2012-03-28**)
140 146 ----------------------
141 147
142 148 news
143 149 ++++
144 150
145 151 - Whoosh logging is now controlled by the .ini files logging setup
146 152 - added clone-url into edit form on /settings page
147 153 - added help text into repo add/edit forms
148 154 - created rcextensions module with additional mappings (ref #322) and
149 155 post push/pull/create repo hooks callbacks
150 156 - implemented #377 Users view for his own permissions on account page
151 157 - #399 added inheritance of permissions for users group on repos groups
152 158 - #401 repository group is automatically pre-selected when adding repos
153 159 inside a repository group
154 160 - added alternative HTTP 403 response when client failed to authenticate. Helps
155 161 solving issues with Mercurial and LDAP
156 162 - #402 removed group prefix from repository name when listing repositories
157 163 inside a group
158 164 - added gravatars into permission view and permissions autocomplete
159 165 - #347 when running multiple RhodeCode instances, properly invalidates cache
160 166 for all registered servers
161 167
162 168 fixes
163 169 +++++
164 170
165 171 - fixed #390 cache invalidation problems on repos inside group
166 172 - fixed #385 clone by ID url was loosing proxy prefix in URL
167 173 - fixed some unicode problems with waitress
168 174 - fixed issue with escaping < and > in changeset commits
169 175 - fixed error occurring during recursive group creation in API
170 176 create_repo function
171 177 - fixed #393 py2.5 fixes for routes url generator
172 178 - fixed #397 Private repository groups shows up before login
173 179 - fixed #396 fixed problems with revoking users in nested groups
174 180 - fixed mysql unicode issues + specified InnoDB as default engine with
175 181 utf8 charset
176 182 - #406 trim long branch/tag names in changelog to not break UI
177 183
178 184 1.3.3 (**2012-03-02**)
179 185 ----------------------
180 186
181 187 news
182 188 ++++
183 189
184 190
185 191 fixes
186 192 +++++
187 193
188 194 - fixed some python2.5 compatibility issues
189 195 - fixed issues with removed repos was accidentally added as groups, after
190 196 full rescan of paths
191 197 - fixes #376 Cannot edit user (using container auth)
192 198 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
193 199 configuration
194 200 - fixed initial sorting of repos inside repo group
195 201 - fixes issue when user tried to resubmit same permission into user/user_groups
196 202 - bumped beaker version that fixes #375 leap error bug
197 203 - fixed raw_changeset for git. It was generated with hg patch headers
198 204 - fixed vcs issue with last_changeset for filenodes
199 205 - fixed missing commit after hook delete
200 206 - fixed #372 issues with git operation detection that caused a security issue
201 207 for git repos
202 208
203 209 1.3.2 (**2012-02-28**)
204 210 ----------------------
205 211
206 212 news
207 213 ++++
208 214
209 215
210 216 fixes
211 217 +++++
212 218
213 219 - fixed git protocol issues with repos-groups
214 220 - fixed git remote repos validator that prevented from cloning remote git repos
215 221 - fixes #370 ending slashes fixes for repo and groups
216 222 - fixes #368 improved git-protocol detection to handle other clients
217 223 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
218 224 Moved To Root
219 225 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
220 226 - fixed #373 missing cascade drop on user_group_to_perm table
221 227
222 228 1.3.1 (**2012-02-27**)
223 229 ----------------------
224 230
225 231 news
226 232 ++++
227 233
228 234
229 235 fixes
230 236 +++++
231 237
232 238 - redirection loop occurs when remember-me wasn't checked during login
233 239 - fixes issues with git blob history generation
234 240 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
235 241
236 242 1.3.0 (**2012-02-26**)
237 243 ----------------------
238 244
239 245 news
240 246 ++++
241 247
242 248 - code review, inspired by github code-comments
243 249 - #215 rst and markdown README files support
244 250 - #252 Container-based and proxy pass-through authentication support
245 251 - #44 branch browser. Filtering of changelog by branches
246 252 - mercurial bookmarks support
247 253 - new hover top menu, optimized to add maximum size for important views
248 254 - configurable clone url template with possibility to specify protocol like
249 255 ssh:// or http:// and also manually alter other parts of clone_url.
250 256 - enabled largefiles extension by default
251 257 - optimized summary file pages and saved a lot of unused space in them
252 258 - #239 option to manually mark repository as fork
253 259 - #320 mapping of commit authors to RhodeCode users
254 260 - #304 hashes are displayed using monospace font
255 261 - diff configuration, toggle white lines and context lines
256 262 - #307 configurable diffs, whitespace toggle, increasing context lines
257 263 - sorting on branches, tags and bookmarks using YUI datatable
258 264 - improved file filter on files page
259 265 - implements #330 api method for listing nodes ar particular revision
260 266 - #73 added linking issues in commit messages to chosen issue tracker url
261 267 based on user defined regular expression
262 268 - added linking of changesets in commit messages
263 269 - new compact changelog with expandable commit messages
264 270 - firstname and lastname are optional in user creation
265 271 - #348 added post-create repository hook
266 272 - #212 global encoding settings is now configurable from .ini files
267 273 - #227 added repository groups permissions
268 274 - markdown gets codehilite extensions
269 275 - new API methods, delete_repositories, grante/revoke permissions for groups
270 276 and repos
271 277
272 278
273 279 fixes
274 280 +++++
275 281
276 282 - rewrote dbsession management for atomic operations, and better error handling
277 283 - fixed sorting of repo tables
278 284 - #326 escape of special html entities in diffs
279 285 - normalized user_name => username in api attributes
280 286 - fixes #298 ldap created users with mixed case emails created conflicts
281 287 on saving a form
282 288 - fixes issue when owner of a repo couldn't revoke permissions for users
283 289 and groups
284 290 - fixes #271 rare JSON serialization problem with statistics
285 291 - fixes #337 missing validation check for conflicting names of a group with a
286 292 repositories group
287 293 - #340 fixed session problem for mysql and celery tasks
288 294 - fixed #331 RhodeCode mangles repository names if the a repository group
289 295 contains the "full path" to the repositories
290 296 - #355 RhodeCode doesn't store encrypted LDAP passwords
291 297
292 298 1.2.5 (**2012-01-28**)
293 299 ----------------------
294 300
295 301 news
296 302 ++++
297 303
298 304 fixes
299 305 +++++
300 306
301 307 - #340 Celery complains about MySQL server gone away, added session cleanup
302 308 for celery tasks
303 309 - #341 "scanning for repositories in None" log message during Rescan was missing
304 310 a parameter
305 311 - fixed creating archives with subrepos. Some hooks were triggered during that
306 312 operation leading to crash.
307 313 - fixed missing email in account page.
308 314 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
309 315 forking on windows impossible
310 316
311 317 1.2.4 (**2012-01-19**)
312 318 ----------------------
313 319
314 320 news
315 321 ++++
316 322
317 323 - RhodeCode is bundled with mercurial series 2.0.X by default, with
318 324 full support to largefiles extension. Enabled by default in new installations
319 325 - #329 Ability to Add/Remove Groups to/from a Repository via AP
320 326 - added requires.txt file with requirements
321 327
322 328 fixes
323 329 +++++
324 330
325 331 - fixes db session issues with celery when emailing admins
326 332 - #331 RhodeCode mangles repository names if the a repository group
327 333 contains the "full path" to the repositories
328 334 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
329 335 - DB session cleanup after hg protocol operations, fixes issues with
330 336 `mysql has gone away` errors
331 337 - #333 doc fixes for get_repo api function
332 338 - #271 rare JSON serialization problem with statistics enabled
333 339 - #337 Fixes issues with validation of repository name conflicting with
334 340 a group name. A proper message is now displayed.
335 341 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
336 342 doesn't work
337 343 - #316 fixes issues with web description in hgrc files
338 344
339 345 1.2.3 (**2011-11-02**)
340 346 ----------------------
341 347
342 348 news
343 349 ++++
344 350
345 351 - added option to manage repos group for non admin users
346 352 - added following API methods for get_users, create_user, get_users_groups,
347 353 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
348 354 get_repo, create_repo, add_user_to_repo
349 355 - implements #237 added password confirmation for my account
350 356 and admin edit user.
351 357 - implements #291 email notification for global events are now sent to all
352 358 administrator users, and global config email.
353 359
354 360 fixes
355 361 +++++
356 362
357 363 - added option for passing auth method for smtp mailer
358 364 - #276 issue with adding a single user with id>10 to usergroups
359 365 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
360 366 - #288 fixes managing of repos in a group for non admin user
361 367
362 368 1.2.2 (**2011-10-17**)
363 369 ----------------------
364 370
365 371 news
366 372 ++++
367 373
368 374 - #226 repo groups are available by path instead of numerical id
369 375
370 376 fixes
371 377 +++++
372 378
373 379 - #259 Groups with the same name but with different parent group
374 380 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
375 381 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
376 382 - #265 ldap save fails sometimes on converting attributes to booleans,
377 383 added getter and setter into model that will prevent from this on db model level
378 384 - fixed problems with timestamps issues #251 and #213
379 385 - fixes #266 RhodeCode allows to create repo with the same name and in
380 386 the same parent as group
381 387 - fixes #245 Rescan of the repositories on Windows
382 388 - fixes #248 cannot edit repos inside a group on windows
383 389 - fixes #219 forking problems on windows
384 390
385 391 1.2.1 (**2011-10-08**)
386 392 ----------------------
387 393
388 394 news
389 395 ++++
390 396
391 397
392 398 fixes
393 399 +++++
394 400
395 401 - fixed problems with basic auth and push problems
396 402 - gui fixes
397 403 - fixed logger
398 404
399 405 1.2.0 (**2011-10-07**)
400 406 ----------------------
401 407
402 408 news
403 409 ++++
404 410
405 411 - implemented #47 repository groups
406 412 - implemented #89 Can setup google analytics code from settings menu
407 413 - implemented #91 added nicer looking archive urls with more download options
408 414 like tags, branches
409 415 - implemented #44 into file browsing, and added follow branch option
410 416 - implemented #84 downloads can be enabled/disabled for each repository
411 417 - anonymous repository can be cloned without having to pass default:default
412 418 into clone url
413 419 - fixed #90 whoosh indexer can index chooses repositories passed in command
414 420 line
415 421 - extended journal with day aggregates and paging
416 422 - implemented #107 source code lines highlight ranges
417 423 - implemented #93 customizable changelog on combined revision ranges -
418 424 equivalent of githubs compare view
419 425 - implemented #108 extended and more powerful LDAP configuration
420 426 - implemented #56 users groups
421 427 - major code rewrites optimized codes for speed and memory usage
422 428 - raw and diff downloads are now in git format
423 429 - setup command checks for write access to given path
424 430 - fixed many issues with international characters and unicode. It uses utf8
425 431 decode with replace to provide less errors even with non utf8 encoded strings
426 432 - #125 added API KEY access to feeds
427 433 - #109 Repository can be created from external Mercurial link (aka. remote
428 434 repository, and manually updated (via pull) from admin panel
429 435 - beta git support - push/pull server + basic view for git repos
430 436 - added followers page and forks page
431 437 - server side file creation (with binary file upload interface)
432 438 and edition with commits powered by codemirror
433 439 - #111 file browser file finder, quick lookup files on whole file tree
434 440 - added quick login sliding menu into main page
435 441 - changelog uses lazy loading of affected files details, in some scenarios
436 442 this can improve speed of changelog page dramatically especially for
437 443 larger repositories.
438 444 - implements #214 added support for downloading subrepos in download menu.
439 445 - Added basic API for direct operations on rhodecode via JSON
440 446 - Implemented advanced hook management
441 447
442 448 fixes
443 449 +++++
444 450
445 451 - fixed file browser bug, when switching into given form revision the url was
446 452 not changing
447 453 - fixed propagation to error controller on simplehg and simplegit middlewares
448 454 - fixed error when trying to make a download on empty repository
449 455 - fixed problem with '[' chars in commit messages in journal
450 456 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
451 457 - journal fork fixes
452 458 - removed issue with space inside renamed repository after deletion
453 459 - fixed strange issue on formencode imports
454 460 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
455 461 - #150 fixes for errors on repositories mapped in db but corrupted in
456 462 filesystem
457 463 - fixed problem with ascendant characters in realm #181
458 464 - fixed problem with sqlite file based database connection pool
459 465 - whoosh indexer and code stats share the same dynamic extensions map
460 466 - fixes #188 - relationship delete of repo_to_perm entry on user removal
461 467 - fixes issue #189 Trending source files shows "show more" when no more exist
462 468 - fixes issue #197 Relative paths for pidlocks
463 469 - fixes issue #198 password will require only 3 chars now for login form
464 470 - fixes issue #199 wrong redirection for non admin users after creating a repository
465 471 - fixes issues #202, bad db constraint made impossible to attach same group
466 472 more than one time. Affects only mysql/postgres
467 473 - fixes #218 os.kill patch for windows was missing sig param
468 474 - improved rendering of dag (they are not trimmed anymore when number of
469 475 heads exceeds 5)
470 476
471 477 1.1.8 (**2011-04-12**)
472 478 ----------------------
473 479
474 480 news
475 481 ++++
476 482
477 483 - improved windows support
478 484
479 485 fixes
480 486 +++++
481 487
482 488 - fixed #140 freeze of python dateutil library, since new version is python2.x
483 489 incompatible
484 490 - setup-app will check for write permission in given path
485 491 - cleaned up license info issue #149
486 492 - fixes for issues #137,#116 and problems with unicode and accented characters.
487 493 - fixes crashes on gravatar, when passed in email as unicode
488 494 - fixed tooltip flickering problems
489 495 - fixed came_from redirection on windows
490 496 - fixed logging modules, and sql formatters
491 497 - windows fixes for os.kill issue #133
492 498 - fixes path splitting for windows issues #148
493 499 - fixed issue #143 wrong import on migration to 1.1.X
494 500 - fixed problems with displaying binary files, thanks to Thomas Waldmann
495 501 - removed name from archive files since it's breaking ui for long repo names
496 502 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
497 503 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
498 504 Thomas Waldmann
499 505 - fixed issue #166 summary pager was skipping 10 revisions on second page
500 506
501 507
502 508 1.1.7 (**2011-03-23**)
503 509 ----------------------
504 510
505 511 news
506 512 ++++
507 513
508 514 fixes
509 515 +++++
510 516
511 517 - fixed (again) #136 installation support for FreeBSD
512 518
513 519
514 520 1.1.6 (**2011-03-21**)
515 521 ----------------------
516 522
517 523 news
518 524 ++++
519 525
520 526 fixes
521 527 +++++
522 528
523 529 - fixed #136 installation support for FreeBSD
524 530 - RhodeCode will check for python version during installation
525 531
526 532 1.1.5 (**2011-03-17**)
527 533 ----------------------
528 534
529 535 news
530 536 ++++
531 537
532 538 - basic windows support, by exchanging pybcrypt into sha256 for windows only
533 539 highly inspired by idea of mantis406
534 540
535 541 fixes
536 542 +++++
537 543
538 544 - fixed sorting by author in main page
539 545 - fixed crashes with diffs on binary files
540 546 - fixed #131 problem with boolean values for LDAP
541 547 - fixed #122 mysql problems thanks to striker69
542 548 - fixed problem with errors on calling raw/raw_files/annotate functions
543 549 with unknown revisions
544 550 - fixed returned rawfiles attachment names with international character
545 551 - cleaned out docs, big thanks to Jason Harris
546 552
547 553 1.1.4 (**2011-02-19**)
548 554 ----------------------
549 555
550 556 news
551 557 ++++
552 558
553 559 fixes
554 560 +++++
555 561
556 562 - fixed formencode import problem on settings page, that caused server crash
557 563 when that page was accessed as first after server start
558 564 - journal fixes
559 565 - fixed option to access repository just by entering http://server/<repo_name>
560 566
561 567 1.1.3 (**2011-02-16**)
562 568 ----------------------
563 569
564 570 news
565 571 ++++
566 572
567 573 - implemented #102 allowing the '.' character in username
568 574 - added option to access repository just by entering http://server/<repo_name>
569 575 - celery task ignores result for better performance
570 576
571 577 fixes
572 578 +++++
573 579
574 580 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
575 581 apollo13 and Johan Walles
576 582 - small fixes in journal
577 583 - fixed problems with getting setting for celery from .ini files
578 584 - registration, password reset and login boxes share the same title as main
579 585 application now
580 586 - fixed #113: to high permissions to fork repository
581 587 - fixed problem with '[' chars in commit messages in journal
582 588 - removed issue with space inside renamed repository after deletion
583 589 - db transaction fixes when filesystem repository creation failed
584 590 - fixed #106 relation issues on databases different than sqlite
585 591 - fixed static files paths links to use of url() method
586 592
587 593 1.1.2 (**2011-01-12**)
588 594 ----------------------
589 595
590 596 news
591 597 ++++
592 598
593 599
594 600 fixes
595 601 +++++
596 602
597 603 - fixes #98 protection against float division of percentage stats
598 604 - fixed graph bug
599 605 - forced webhelpers version since it was making troubles during installation
600 606
601 607 1.1.1 (**2011-01-06**)
602 608 ----------------------
603 609
604 610 news
605 611 ++++
606 612
607 613 - added force https option into ini files for easier https usage (no need to
608 614 set server headers with this options)
609 615 - small css updates
610 616
611 617 fixes
612 618 +++++
613 619
614 620 - fixed #96 redirect loop on files view on repositories without changesets
615 621 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
616 622 and server crashed with errors
617 623 - fixed large tooltips problems on main page
618 624 - fixed #92 whoosh indexer is more error proof
619 625
620 626 1.1.0 (**2010-12-18**)
621 627 ----------------------
622 628
623 629 news
624 630 ++++
625 631
626 632 - rewrite of internals for vcs >=0.1.10
627 633 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
628 634 with older clients
629 635 - anonymous access, authentication via ldap
630 636 - performance upgrade for cached repos list - each repository has its own
631 637 cache that's invalidated when needed.
632 638 - performance upgrades on repositories with large amount of commits (20K+)
633 639 - main page quick filter for filtering repositories
634 640 - user dashboards with ability to follow chosen repositories actions
635 641 - sends email to admin on new user registration
636 642 - added cache/statistics reset options into repository settings
637 643 - more detailed action logger (based on hooks) with pushed changesets lists
638 644 and options to disable those hooks from admin panel
639 645 - introduced new enhanced changelog for merges that shows more accurate results
640 646 - new improved and faster code stats (based on pygments lexers mapping tables,
641 647 showing up to 10 trending sources for each repository. Additionally stats
642 648 can be disabled in repository settings.
643 649 - gui optimizations, fixed application width to 1024px
644 650 - added cut off (for large files/changesets) limit into config files
645 651 - whoosh, celeryd, upgrade moved to paster command
646 652 - other than sqlite database backends can be used
647 653
648 654 fixes
649 655 +++++
650 656
651 657 - fixes #61 forked repo was showing only after cache expired
652 658 - fixes #76 no confirmation on user deletes
653 659 - fixes #66 Name field misspelled
654 660 - fixes #72 block user removal when he owns repositories
655 661 - fixes #69 added password confirmation fields
656 662 - fixes #87 RhodeCode crashes occasionally on updating repository owner
657 663 - fixes #82 broken annotations on files with more than 1 blank line at the end
658 664 - a lot of fixes and tweaks for file browser
659 665 - fixed detached session issues
660 666 - fixed when user had no repos he would see all repos listed in my account
661 667 - fixed ui() instance bug when global hgrc settings was loaded for server
662 668 instance and all hgrc options were merged with our db ui() object
663 669 - numerous small bugfixes
664 670
665 671 (special thanks for TkSoh for detailed feedback)
666 672
667 673
668 674 1.0.2 (**2010-11-12**)
669 675 ----------------------
670 676
671 677 news
672 678 ++++
673 679
674 680 - tested under python2.7
675 681 - bumped sqlalchemy and celery versions
676 682
677 683 fixes
678 684 +++++
679 685
680 686 - fixed #59 missing graph.js
681 687 - fixed repo_size crash when repository had broken symlinks
682 688 - fixed python2.5 crashes.
683 689
684 690
685 691 1.0.1 (**2010-11-10**)
686 692 ----------------------
687 693
688 694 news
689 695 ++++
690 696
691 697 - small css updated
692 698
693 699 fixes
694 700 +++++
695 701
696 702 - fixed #53 python2.5 incompatible enumerate calls
697 703 - fixed #52 disable mercurial extension for web
698 704 - fixed #51 deleting repositories don't delete it's dependent objects
699 705
700 706
701 707 1.0.0 (**2010-11-02**)
702 708 ----------------------
703 709
704 710 - security bugfix simplehg wasn't checking for permissions on commands
705 711 other than pull or push.
706 712 - fixed doubled messages after push or pull in admin journal
707 713 - templating and css corrections, fixed repo switcher on chrome, updated titles
708 714 - admin menu accessible from options menu on repository view
709 715 - permissions cached queries
710 716
711 717 1.0.0rc4 (**2010-10-12**)
712 718 --------------------------
713 719
714 720 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
715 721 - removed cache_manager settings from sqlalchemy meta
716 722 - added sqlalchemy cache settings to ini files
717 723 - validated password length and added second try of failure on paster setup-app
718 724 - fixed setup database destroy prompt even when there was no db
719 725
720 726
721 727 1.0.0rc3 (**2010-10-11**)
722 728 -------------------------
723 729
724 730 - fixed i18n during installation.
725 731
726 732 1.0.0rc2 (**2010-10-11**)
727 733 -------------------------
728 734
729 735 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
730 736 occure. After vcs is fixed it'll be put back again.
731 737 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,625 +1,653 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import sys
29 29 import uuid
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from rhodecode import __dbversion__
34 34
35 35 from rhodecode.model.user import UserModel
36 36 from rhodecode.lib.utils import ask_ok
37 37 from rhodecode.model import init_model
38 38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
40 40 UserRepoGroupToPerm
41 41
42 42 from sqlalchemy.engine import create_engine
43 43 from rhodecode.model.repos_group import ReposGroupModel
44 44 #from rhodecode.model import meta
45 45 from rhodecode.model.meta import Session, Base
46 46
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 def notify(msg):
52 52 """
53 53 Notification for migrations messages
54 54 """
55 55 ml = len(msg) + (4 * 2)
56 56 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
57 57
58 58
59 59 class DbManage(object):
60 60 def __init__(self, log_sql, dbconf, root, tests=False):
61 61 self.dbname = dbconf.split('/')[-1]
62 62 self.tests = tests
63 63 self.root = root
64 64 self.dburi = dbconf
65 65 self.log_sql = log_sql
66 66 self.db_exists = False
67 67 self.init_db()
68 68
69 69 def init_db(self):
70 70 engine = create_engine(self.dburi, echo=self.log_sql)
71 71 init_model(engine)
72 72 self.sa = Session()
73 73
74 74 def create_tables(self, override=False, defaults={}):
75 75 """
76 76 Create a auth database
77 77 """
78 78 quiet = defaults.get('quiet')
79 79 log.info("Any existing database is going to be destroyed")
80 80 if self.tests or quiet:
81 81 destroy = True
82 82 else:
83 83 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
84 84 if not destroy:
85 85 sys.exit()
86 86 if destroy:
87 87 Base.metadata.drop_all()
88 88
89 89 checkfirst = not override
90 90 Base.metadata.create_all(checkfirst=checkfirst)
91 91 log.info('Created tables for %s' % self.dbname)
92 92
93 93 def set_db_version(self):
94 94 ver = DbMigrateVersion()
95 95 ver.version = __dbversion__
96 96 ver.repository_id = 'rhodecode_db_migrations'
97 97 ver.repository_path = 'versions'
98 98 self.sa.add(ver)
99 99 log.info('db version set to: %s' % __dbversion__)
100 100
101 101 def upgrade(self):
102 102 """
103 103 Upgrades given database schema to given revision following
104 104 all needed steps, to perform the upgrade
105 105
106 106 """
107 107
108 108 from rhodecode.lib.dbmigrate.migrate.versioning import api
109 109 from rhodecode.lib.dbmigrate.migrate.exceptions import \
110 110 DatabaseNotControlledError
111 111
112 112 if 'sqlite' in self.dburi:
113 113 print (
114 114 '********************** WARNING **********************\n'
115 115 'Make sure your version of sqlite is at least 3.7.X. \n'
116 116 'Earlier versions are known to fail on some migrations\n'
117 117 '*****************************************************\n'
118 118 )
119 119 upgrade = ask_ok('You are about to perform database upgrade, make '
120 120 'sure You backed up your database before. '
121 121 'Continue ? [y/n]')
122 122 if not upgrade:
123 123 sys.exit('Nothing done')
124 124
125 125 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
126 126 'rhodecode/lib/dbmigrate')
127 127 db_uri = self.dburi
128 128
129 129 try:
130 130 curr_version = api.db_version(db_uri, repository_path)
131 131 msg = ('Found current database under version'
132 132 ' control with version %s' % curr_version)
133 133
134 134 except (RuntimeError, DatabaseNotControlledError):
135 135 curr_version = 1
136 136 msg = ('Current database is not under version control. Setting'
137 137 ' as version %s' % curr_version)
138 138 api.version_control(db_uri, repository_path, curr_version)
139 139
140 140 notify(msg)
141 141
142 142 if curr_version == __dbversion__:
143 143 sys.exit('This database is already at the newest version')
144 144
145 145 #======================================================================
146 146 # UPGRADE STEPS
147 147 #======================================================================
148 148
149 149 class UpgradeSteps(object):
150 150 """
151 151 Those steps follow schema versions so for example schema
152 152 for example schema with seq 002 == step_2 and so on.
153 153 """
154 154
155 155 def __init__(self, klass):
156 156 self.klass = klass
157 157
158 158 def step_0(self):
159 159 # step 0 is the schema upgrade, and than follow proper upgrades
160 160 notify('attempting to do database upgrade to version %s' \
161 161 % __dbversion__)
162 162 api.upgrade(db_uri, repository_path, __dbversion__)
163 163 notify('Schema upgrade completed')
164 164
165 165 def step_1(self):
166 166 pass
167 167
168 168 def step_2(self):
169 169 notify('Patching repo paths for newer version of RhodeCode')
170 170 self.klass.fix_repo_paths()
171 171
172 172 notify('Patching default user of RhodeCode')
173 173 self.klass.fix_default_user()
174 174
175 175 log.info('Changing ui settings')
176 176 self.klass.create_ui_settings()
177 177
178 178 def step_3(self):
179 179 notify('Adding additional settings into RhodeCode db')
180 180 self.klass.fix_settings()
181 181 notify('Adding ldap defaults')
182 182 self.klass.create_ldap_options(skip_existing=True)
183 183
184 184 def step_4(self):
185 185 notify('create permissions and fix groups')
186 186 self.klass.create_permissions()
187 187 self.klass.fixup_groups()
188 188
189 189 def step_5(self):
190 190 pass
191 191
192 192 def step_6(self):
193 193
194 194 notify('re-checking permissions')
195 195 self.klass.create_permissions()
196 196
197 197 notify('installing new UI options')
198 198 sett4 = RhodeCodeSetting('show_public_icon', True)
199 199 Session().add(sett4)
200 200 sett5 = RhodeCodeSetting('show_private_icon', True)
201 201 Session().add(sett5)
202 202 sett6 = RhodeCodeSetting('stylify_metatags', False)
203 203 Session().add(sett6)
204 204
205 205 notify('fixing old PULL hook')
206 206 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
207 207 if _pull:
208 208 _pull.ui_key = RhodeCodeUi.HOOK_PULL
209 209 Session().add(_pull)
210 210
211 211 notify('fixing old PUSH hook')
212 212 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
213 213 if _push:
214 214 _push.ui_key = RhodeCodeUi.HOOK_PUSH
215 215 Session().add(_push)
216 216
217 217 notify('installing new pre-push hook')
218 218 hooks4 = RhodeCodeUi()
219 219 hooks4.ui_section = 'hooks'
220 220 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
221 221 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
222 222 Session().add(hooks4)
223 223
224 224 notify('installing new pre-pull hook')
225 225 hooks6 = RhodeCodeUi()
226 226 hooks6.ui_section = 'hooks'
227 227 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
228 228 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
229 229 Session().add(hooks6)
230 230
231 231 notify('installing hgsubversion option')
232 232 # enable hgsubversion disabled by default
233 233 hgsubversion = RhodeCodeUi()
234 234 hgsubversion.ui_section = 'extensions'
235 235 hgsubversion.ui_key = 'hgsubversion'
236 236 hgsubversion.ui_value = ''
237 237 hgsubversion.ui_active = False
238 238 Session().add(hgsubversion)
239 239
240 240 notify('installing hg git option')
241 241 # enable hggit disabled by default
242 242 hggit = RhodeCodeUi()
243 243 hggit.ui_section = 'extensions'
244 244 hggit.ui_key = 'hggit'
245 245 hggit.ui_value = ''
246 246 hggit.ui_active = False
247 247 Session().add(hggit)
248 248
249 249 notify('re-check default permissions')
250 250 default_user = User.get_by_username(User.DEFAULT_USER)
251 251 perm = Permission.get_by_key('hg.fork.repository')
252 252 reg_perm = UserToPerm()
253 253 reg_perm.user = default_user
254 254 reg_perm.permission = perm
255 255 Session().add(reg_perm)
256 256
257 257 def step_7(self):
258 pass
258 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
259 Session().commit()
260 if perm_fixes:
261 notify('There was an inconsistent state of permissions '
262 'detected for default user. Permissions are now '
263 'reset to the default value for default user. '
264 'Please validate and check default permissions '
265 'in admin panel')
259 266
260 267 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
261 268
262 269 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
263 270 _step = None
264 271 for step in upgrade_steps:
265 272 notify('performing upgrade step %s' % step)
266 273 getattr(UpgradeSteps(self), 'step_%s' % step)()
267 274 self.sa.commit()
268 275 _step = step
269 276
270 277 notify('upgrade to version %s successful' % _step)
271 278
272 279 def fix_repo_paths(self):
273 280 """
274 281 Fixes a old rhodecode version path into new one without a '*'
275 282 """
276 283
277 284 paths = self.sa.query(RhodeCodeUi)\
278 285 .filter(RhodeCodeUi.ui_key == '/')\
279 286 .scalar()
280 287
281 288 paths.ui_value = paths.ui_value.replace('*', '')
282 289
283 290 try:
284 291 self.sa.add(paths)
285 292 self.sa.commit()
286 293 except:
287 294 self.sa.rollback()
288 295 raise
289 296
290 297 def fix_default_user(self):
291 298 """
292 299 Fixes a old default user with some 'nicer' default values,
293 300 used mostly for anonymous access
294 301 """
295 302 def_user = self.sa.query(User)\
296 303 .filter(User.username == 'default')\
297 304 .one()
298 305
299 306 def_user.name = 'Anonymous'
300 307 def_user.lastname = 'User'
301 308 def_user.email = 'anonymous@rhodecode.org'
302 309
303 310 try:
304 311 self.sa.add(def_user)
305 312 self.sa.commit()
306 313 except:
307 314 self.sa.rollback()
308 315 raise
309 316
310 317 def fix_settings(self):
311 318 """
312 319 Fixes rhodecode settings adds ga_code key for google analytics
313 320 """
314 321
315 322 hgsettings3 = RhodeCodeSetting('ga_code', '')
316 323
317 324 try:
318 325 self.sa.add(hgsettings3)
319 326 self.sa.commit()
320 327 except:
321 328 self.sa.rollback()
322 329 raise
323 330
324 331 def admin_prompt(self, second=False, defaults={}):
325 332 if not self.tests:
326 333 import getpass
327 334
328 335 # defaults
329 336 username = defaults.get('username')
330 337 password = defaults.get('password')
331 338 email = defaults.get('email')
332 339
333 340 def get_password():
334 341 password = getpass.getpass('Specify admin password '
335 342 '(min 6 chars):')
336 343 confirm = getpass.getpass('Confirm password:')
337 344
338 345 if password != confirm:
339 346 log.error('passwords mismatch')
340 347 return False
341 348 if len(password) < 6:
342 349 log.error('password is to short use at least 6 characters')
343 350 return False
344 351
345 352 return password
346 353 if username is None:
347 354 username = raw_input('Specify admin username:')
348 355 if password is None:
349 356 password = get_password()
350 357 if not password:
351 358 #second try
352 359 password = get_password()
353 360 if not password:
354 361 sys.exit()
355 362 if email is None:
356 363 email = raw_input('Specify admin email:')
357 364 self.create_user(username, password, email, True)
358 365 else:
359 366 log.info('creating admin and regular test users')
360 367 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
361 368 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
362 369 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
363 370 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
364 371 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
365 372
366 373 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
367 374 TEST_USER_ADMIN_EMAIL, True)
368 375
369 376 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
370 377 TEST_USER_REGULAR_EMAIL, False)
371 378
372 379 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
373 380 TEST_USER_REGULAR2_EMAIL, False)
374 381
375 382 def create_ui_settings(self):
376 383 """
377 384 Creates ui settings, fills out hooks
378 385 and disables dotencode
379 386 """
380 387
381 388 #HOOKS
382 389 hooks1_key = RhodeCodeUi.HOOK_UPDATE
383 390 hooks1_ = self.sa.query(RhodeCodeUi)\
384 391 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
385 392
386 393 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
387 394 hooks1.ui_section = 'hooks'
388 395 hooks1.ui_key = hooks1_key
389 396 hooks1.ui_value = 'hg update >&2'
390 397 hooks1.ui_active = False
391 398 self.sa.add(hooks1)
392 399
393 400 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
394 401 hooks2_ = self.sa.query(RhodeCodeUi)\
395 402 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
396 403 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
397 404 hooks2.ui_section = 'hooks'
398 405 hooks2.ui_key = hooks2_key
399 406 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
400 407 self.sa.add(hooks2)
401 408
402 409 hooks3 = RhodeCodeUi()
403 410 hooks3.ui_section = 'hooks'
404 411 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
405 412 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
406 413 self.sa.add(hooks3)
407 414
408 415 hooks4 = RhodeCodeUi()
409 416 hooks4.ui_section = 'hooks'
410 417 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
411 418 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
412 419 self.sa.add(hooks4)
413 420
414 421 hooks5 = RhodeCodeUi()
415 422 hooks5.ui_section = 'hooks'
416 423 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
417 424 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
418 425 self.sa.add(hooks5)
419 426
420 427 hooks6 = RhodeCodeUi()
421 428 hooks6.ui_section = 'hooks'
422 429 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
423 430 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
424 431 self.sa.add(hooks6)
425 432
426 433 # enable largefiles
427 434 largefiles = RhodeCodeUi()
428 435 largefiles.ui_section = 'extensions'
429 436 largefiles.ui_key = 'largefiles'
430 437 largefiles.ui_value = ''
431 438 self.sa.add(largefiles)
432 439
433 440 # enable hgsubversion disabled by default
434 441 hgsubversion = RhodeCodeUi()
435 442 hgsubversion.ui_section = 'extensions'
436 443 hgsubversion.ui_key = 'hgsubversion'
437 444 hgsubversion.ui_value = ''
438 445 hgsubversion.ui_active = False
439 446 self.sa.add(hgsubversion)
440 447
441 448 # enable hggit disabled by default
442 449 hggit = RhodeCodeUi()
443 450 hggit.ui_section = 'extensions'
444 451 hggit.ui_key = 'hggit'
445 452 hggit.ui_value = ''
446 453 hggit.ui_active = False
447 454 self.sa.add(hggit)
448 455
449 456 def create_ldap_options(self, skip_existing=False):
450 457 """Creates ldap settings"""
451 458
452 459 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
453 460 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
454 461 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
455 462 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
456 463 ('ldap_filter', ''), ('ldap_search_scope', ''),
457 464 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
458 465 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
459 466
460 467 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
461 468 log.debug('Skipping option %s' % k)
462 469 continue
463 470 setting = RhodeCodeSetting(k, v)
464 471 self.sa.add(setting)
465 472
466 473 def fixup_groups(self):
467 474 def_usr = User.get_by_username('default')
468 475 for g in RepoGroup.query().all():
469 476 g.group_name = g.get_new_name(g.name)
470 477 self.sa.add(g)
471 478 # get default perm
472 479 default = UserRepoGroupToPerm.query()\
473 480 .filter(UserRepoGroupToPerm.group == g)\
474 481 .filter(UserRepoGroupToPerm.user == def_usr)\
475 482 .scalar()
476 483
477 484 if default is None:
478 485 log.debug('missing default permission for group %s adding' % g)
479 486 ReposGroupModel()._create_default_perms(g)
480 487
488 def reset_permissions(self, username):
489 """
490 Resets permissions to default state, usefull when old systems had
491 bad permissions, we must clean them up
492
493 :param username:
494 :type username:
495 """
496 default_user = User.get_by_username(username)
497 if not default_user:
498 return
499
500 u2p = UserToPerm.query()\
501 .filter(UserToPerm.user == default_user).all()
502 fixed = False
503 if len(u2p) != len(User.DEFAULT_PERMISSIONS):
504 for p in u2p:
505 Session().delete(p)
506 fixed = True
507 self.populate_default_permissions()
508 return fixed
509
481 510 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
482 511 _path = defaults.get('repos_location')
483 512 if retries == 3:
484 513 log.info('Setting up repositories config')
485 514
486 515 if _path is not None:
487 516 path = _path
488 517 elif not self.tests and not test_repo_path:
489 518 path = raw_input(
490 519 'Enter a valid absolute path to store repositories. '
491 520 'All repositories in that path will be added automatically:'
492 521 )
493 522 else:
494 523 path = test_repo_path
495 524 path_ok = True
496 525
497 526 # check proper dir
498 527 if not os.path.isdir(path):
499 528 path_ok = False
500 529 log.error('Given path %s is not a valid directory' % path)
501 530
502 531 elif not os.path.isabs(path):
503 532 path_ok = False
504 533 log.error('Given path %s is not an absolute path' % path)
505 534
506 535 # check write access
507 536 elif not os.access(path, os.W_OK) and path_ok:
508 537 path_ok = False
509 538 log.error('No write permission to given path %s' % path)
510 539
511 540 if retries == 0:
512 541 sys.exit('max retries reached')
513 542 if path_ok is False:
514 543 retries -= 1
515 544 return self.config_prompt(test_repo_path, retries)
516 545
517 546 return path
518 547
519 548 def create_settings(self, path):
520 549
521 550 self.create_ui_settings()
522 551
523 552 #HG UI OPTIONS
524 553 web1 = RhodeCodeUi()
525 554 web1.ui_section = 'web'
526 555 web1.ui_key = 'push_ssl'
527 556 web1.ui_value = 'false'
528 557
529 558 web2 = RhodeCodeUi()
530 559 web2.ui_section = 'web'
531 560 web2.ui_key = 'allow_archive'
532 561 web2.ui_value = 'gz zip bz2'
533 562
534 563 web3 = RhodeCodeUi()
535 564 web3.ui_section = 'web'
536 565 web3.ui_key = 'allow_push'
537 566 web3.ui_value = '*'
538 567
539 568 web4 = RhodeCodeUi()
540 569 web4.ui_section = 'web'
541 570 web4.ui_key = 'baseurl'
542 571 web4.ui_value = '/'
543 572
544 573 paths = RhodeCodeUi()
545 574 paths.ui_section = 'paths'
546 575 paths.ui_key = '/'
547 576 paths.ui_value = path
548 577
549 578 phases = RhodeCodeUi()
550 579 phases.ui_section = 'phases'
551 580 phases.ui_key = 'publish'
552 581 phases.ui_value = False
553 582
554 583 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
555 584 sett2 = RhodeCodeSetting('title', 'RhodeCode')
556 585 sett3 = RhodeCodeSetting('ga_code', '')
557 586
558 587 sett4 = RhodeCodeSetting('show_public_icon', True)
559 588 sett5 = RhodeCodeSetting('show_private_icon', True)
560 589 sett6 = RhodeCodeSetting('stylify_metatags', False)
561 590
562 591 self.sa.add(web1)
563 592 self.sa.add(web2)
564 593 self.sa.add(web3)
565 594 self.sa.add(web4)
566 595 self.sa.add(paths)
567 596 self.sa.add(sett1)
568 597 self.sa.add(sett2)
569 598 self.sa.add(sett3)
570 599 self.sa.add(sett4)
571 600 self.sa.add(sett5)
572 601 self.sa.add(sett6)
573 602
574 603 self.create_ldap_options()
575 604
576 605 log.info('created ui config')
577 606
578 607 def create_user(self, username, password, email='', admin=False):
579 608 log.info('creating user %s' % username)
580 609 UserModel().create_or_update(username, password, email,
581 610 firstname='RhodeCode', lastname='Admin',
582 611 active=True, admin=admin)
583 612
584 613 def create_default_user(self):
585 614 log.info('creating default user')
586 615 # create default user for handling default permissions.
587 616 UserModel().create_or_update(username='default',
588 617 password=str(uuid.uuid1())[:8],
589 618 email='anonymous@rhodecode.org',
590 619 firstname='Anonymous', lastname='User')
591 620
592 621 def create_permissions(self):
593 622 # module.(access|create|change|delete)_[name]
594 623 # module.(none|read|write|admin)
595 624
596 625 for p in Permission.PERMS:
597 626 if not Permission.get_by_key(p[0]):
598 627 new_perm = Permission()
599 628 new_perm.permission_name = p[0]
600 629 new_perm.permission_longname = p[0]
601 630 self.sa.add(new_perm)
602 631
603 632 def populate_default_permissions(self):
604 633 log.info('creating default user permissions')
605 634
606 635 default_user = User.get_by_username('default')
607 636
608 for def_perm in ['hg.register.manual_activate', 'hg.create.repository',
609 'hg.fork.repository', 'repository.read']:
637 for def_perm in User.DEFAULT_PERMISSIONS:
610 638
611 639 perm = self.sa.query(Permission)\
612 640 .filter(Permission.permission_name == def_perm)\
613 641 .scalar()
614 642 if not perm:
615 643 raise Exception(
616 644 'CRITICAL: permission %s not found inside database !!'
617 645 % def_perm
618 646 )
619 647 if not UserToPerm.query()\
620 648 .filter(UserToPerm.permission == perm)\
621 649 .filter(UserToPerm.user == default_user).scalar():
622 650 reg_perm = UserToPerm()
623 651 reg_perm.user = default_user
624 652 reg_perm.permission = perm
625 653 self.sa.add(reg_perm)
@@ -1,1771 +1,1774 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 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 logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 if id_:
122 122 res = cls.query().get(id_)
123 123 if not res:
124 124 raise HTTPNotFound
125 125 return res
126 126
127 127 @classmethod
128 128 def getAll(cls):
129 129 return cls.query().all()
130 130
131 131 @classmethod
132 132 def delete(cls, id_):
133 133 obj = cls.query().get(id_)
134 134 Session().delete(obj)
135 135
136 136 def __repr__(self):
137 137 if hasattr(self, '__unicode__'):
138 138 # python repr needs to return str
139 139 return safe_str(self.__unicode__())
140 140 return '<DB:%s>' % (self.__class__.__name__)
141 141
142 142
143 143 class RhodeCodeSetting(Base, BaseModel):
144 144 __tablename__ = 'rhodecode_settings'
145 145 __table_args__ = (
146 146 UniqueConstraint('app_settings_name'),
147 147 {'extend_existing': True, 'mysql_engine': 'InnoDB',
148 148 'mysql_charset': 'utf8'}
149 149 )
150 150 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
151 151 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 152 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
153 153
154 154 def __init__(self, k='', v=''):
155 155 self.app_settings_name = k
156 156 self.app_settings_value = v
157 157
158 158 @validates('_app_settings_value')
159 159 def validate_settings_value(self, key, val):
160 160 assert type(val) == unicode
161 161 return val
162 162
163 163 @hybrid_property
164 164 def app_settings_value(self):
165 165 v = self._app_settings_value
166 166 if self.app_settings_name == 'ldap_active':
167 167 v = str2bool(v)
168 168 return v
169 169
170 170 @app_settings_value.setter
171 171 def app_settings_value(self, val):
172 172 """
173 173 Setter that will always make sure we use unicode in app_settings_value
174 174
175 175 :param val:
176 176 """
177 177 self._app_settings_value = safe_unicode(val)
178 178
179 179 def __unicode__(self):
180 180 return u"<%s('%s:%s')>" % (
181 181 self.__class__.__name__,
182 182 self.app_settings_name, self.app_settings_value
183 183 )
184 184
185 185 @classmethod
186 186 def get_by_name(cls, key):
187 187 return cls.query()\
188 188 .filter(cls.app_settings_name == key).scalar()
189 189
190 190 @classmethod
191 191 def get_by_name_or_create(cls, key):
192 192 res = cls.get_by_name(key)
193 193 if not res:
194 194 res = cls(key)
195 195 return res
196 196
197 197 @classmethod
198 198 def get_app_settings(cls, cache=False):
199 199
200 200 ret = cls.query()
201 201
202 202 if cache:
203 203 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
204 204
205 205 if not ret:
206 206 raise Exception('Could not get application settings !')
207 207 settings = {}
208 208 for each in ret:
209 209 settings['rhodecode_' + each.app_settings_name] = \
210 210 each.app_settings_value
211 211
212 212 return settings
213 213
214 214 @classmethod
215 215 def get_ldap_settings(cls, cache=False):
216 216 ret = cls.query()\
217 217 .filter(cls.app_settings_name.startswith('ldap_')).all()
218 218 fd = {}
219 219 for row in ret:
220 220 fd.update({row.app_settings_name: row.app_settings_value})
221 221
222 222 return fd
223 223
224 224
225 225 class RhodeCodeUi(Base, BaseModel):
226 226 __tablename__ = 'rhodecode_ui'
227 227 __table_args__ = (
228 228 UniqueConstraint('ui_key'),
229 229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
230 230 'mysql_charset': 'utf8'}
231 231 )
232 232
233 233 HOOK_UPDATE = 'changegroup.update'
234 234 HOOK_REPO_SIZE = 'changegroup.repo_size'
235 235 HOOK_PUSH = 'changegroup.push_logger'
236 236 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
237 237 HOOK_PULL = 'outgoing.pull_logger'
238 238 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
239 239
240 240 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
241 241 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
242 242 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
243 243 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
244 244 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
245 245
246 246 @classmethod
247 247 def get_by_key(cls, key):
248 248 return cls.query().filter(cls.ui_key == key).scalar()
249 249
250 250 @classmethod
251 251 def get_builtin_hooks(cls):
252 252 q = cls.query()
253 253 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
254 254 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
255 255 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
256 256 return q.all()
257 257
258 258 @classmethod
259 259 def get_custom_hooks(cls):
260 260 q = cls.query()
261 261 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
262 262 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
263 263 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
264 264 q = q.filter(cls.ui_section == 'hooks')
265 265 return q.all()
266 266
267 267 @classmethod
268 268 def get_repos_location(cls):
269 269 return cls.get_by_key('/').ui_value
270 270
271 271 @classmethod
272 272 def create_or_update_hook(cls, key, val):
273 273 new_ui = cls.get_by_key(key) or cls()
274 274 new_ui.ui_section = 'hooks'
275 275 new_ui.ui_active = True
276 276 new_ui.ui_key = key
277 277 new_ui.ui_value = val
278 278
279 279 Session().add(new_ui)
280 280
281 281
282 282 class User(Base, BaseModel):
283 283 __tablename__ = 'users'
284 284 __table_args__ = (
285 285 UniqueConstraint('username'), UniqueConstraint('email'),
286 286 Index('u_username_idx', 'username'),
287 287 Index('u_email_idx', 'email'),
288 288 {'extend_existing': True, 'mysql_engine': 'InnoDB',
289 289 'mysql_charset': 'utf8'}
290 290 )
291 291 DEFAULT_USER = 'default'
292
292 DEFAULT_PERMISSIONS = [
293 'hg.register.manual_activate', 'hg.create.repository',
294 'hg.fork.repository', 'repository.read'
295 ]
293 296 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
294 297 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 298 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 299 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
297 300 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
298 301 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 302 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 303 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
301 304 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
302 305 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 306 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 307 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
305 308
306 309 user_log = relationship('UserLog', cascade='all')
307 310 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
308 311
309 312 repositories = relationship('Repository')
310 313 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
311 314 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
312 315 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
313 316
314 317 group_member = relationship('UsersGroupMember', cascade='all')
315 318
316 319 notifications = relationship('UserNotification', cascade='all')
317 320 # notifications assigned to this user
318 321 user_created_notifications = relationship('Notification', cascade='all')
319 322 # comments created by this user
320 323 user_comments = relationship('ChangesetComment', cascade='all')
321 324 #extra emails for this user
322 325 user_emails = relationship('UserEmailMap', cascade='all')
323 326
324 327 @hybrid_property
325 328 def email(self):
326 329 return self._email
327 330
328 331 @email.setter
329 332 def email(self, val):
330 333 self._email = val.lower() if val else None
331 334
332 335 @property
333 336 def firstname(self):
334 337 # alias for future
335 338 return self.name
336 339
337 340 @property
338 341 def emails(self):
339 342 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
340 343 return [self.email] + [x.email for x in other]
341 344
342 345 @property
343 346 def username_and_name(self):
344 347 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
345 348
346 349 @property
347 350 def full_name(self):
348 351 return '%s %s' % (self.firstname, self.lastname)
349 352
350 353 @property
351 354 def full_name_or_username(self):
352 355 return ('%s %s' % (self.firstname, self.lastname)
353 356 if (self.firstname and self.lastname) else self.username)
354 357
355 358 @property
356 359 def full_contact(self):
357 360 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
358 361
359 362 @property
360 363 def short_contact(self):
361 364 return '%s %s' % (self.firstname, self.lastname)
362 365
363 366 @property
364 367 def is_admin(self):
365 368 return self.admin
366 369
367 370 def __unicode__(self):
368 371 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
369 372 self.user_id, self.username)
370 373
371 374 @classmethod
372 375 def get_by_username(cls, username, case_insensitive=False, cache=False):
373 376 if case_insensitive:
374 377 q = cls.query().filter(cls.username.ilike(username))
375 378 else:
376 379 q = cls.query().filter(cls.username == username)
377 380
378 381 if cache:
379 382 q = q.options(FromCache(
380 383 "sql_cache_short",
381 384 "get_user_%s" % _hash_key(username)
382 385 )
383 386 )
384 387 return q.scalar()
385 388
386 389 @classmethod
387 390 def get_by_api_key(cls, api_key, cache=False):
388 391 q = cls.query().filter(cls.api_key == api_key)
389 392
390 393 if cache:
391 394 q = q.options(FromCache("sql_cache_short",
392 395 "get_api_key_%s" % api_key))
393 396 return q.scalar()
394 397
395 398 @classmethod
396 399 def get_by_email(cls, email, case_insensitive=False, cache=False):
397 400 if case_insensitive:
398 401 q = cls.query().filter(cls.email.ilike(email))
399 402 else:
400 403 q = cls.query().filter(cls.email == email)
401 404
402 405 if cache:
403 406 q = q.options(FromCache("sql_cache_short",
404 407 "get_email_key_%s" % email))
405 408
406 409 ret = q.scalar()
407 410 if ret is None:
408 411 q = UserEmailMap.query()
409 412 # try fetching in alternate email map
410 413 if case_insensitive:
411 414 q = q.filter(UserEmailMap.email.ilike(email))
412 415 else:
413 416 q = q.filter(UserEmailMap.email == email)
414 417 q = q.options(joinedload(UserEmailMap.user))
415 418 if cache:
416 419 q = q.options(FromCache("sql_cache_short",
417 420 "get_email_map_key_%s" % email))
418 421 ret = getattr(q.scalar(), 'user', None)
419 422
420 423 return ret
421 424
422 425 def update_lastlogin(self):
423 426 """Update user lastlogin"""
424 427 self.last_login = datetime.datetime.now()
425 428 Session().add(self)
426 429 log.debug('updated user %s lastlogin' % self.username)
427 430
428 431 def get_api_data(self):
429 432 """
430 433 Common function for generating user related data for API
431 434 """
432 435 user = self
433 436 data = dict(
434 437 user_id=user.user_id,
435 438 username=user.username,
436 439 firstname=user.name,
437 440 lastname=user.lastname,
438 441 email=user.email,
439 442 emails=user.emails,
440 443 api_key=user.api_key,
441 444 active=user.active,
442 445 admin=user.admin,
443 446 ldap_dn=user.ldap_dn,
444 447 last_login=user.last_login,
445 448 )
446 449 return data
447 450
448 451 def __json__(self):
449 452 data = dict(
450 453 full_name=self.full_name,
451 454 full_name_or_username=self.full_name_or_username,
452 455 short_contact=self.short_contact,
453 456 full_contact=self.full_contact
454 457 )
455 458 data.update(self.get_api_data())
456 459 return data
457 460
458 461
459 462 class UserEmailMap(Base, BaseModel):
460 463 __tablename__ = 'user_email_map'
461 464 __table_args__ = (
462 465 Index('uem_email_idx', 'email'),
463 466 UniqueConstraint('email'),
464 467 {'extend_existing': True, 'mysql_engine': 'InnoDB',
465 468 'mysql_charset': 'utf8'}
466 469 )
467 470 __mapper_args__ = {}
468 471
469 472 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
470 473 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
471 474 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
472 475 user = relationship('User', lazy='joined')
473 476
474 477 @validates('_email')
475 478 def validate_email(self, key, email):
476 479 # check if this email is not main one
477 480 main_email = Session().query(User).filter(User.email == email).scalar()
478 481 if main_email is not None:
479 482 raise AttributeError('email %s is present is user table' % email)
480 483 return email
481 484
482 485 @hybrid_property
483 486 def email(self):
484 487 return self._email
485 488
486 489 @email.setter
487 490 def email(self, val):
488 491 self._email = val.lower() if val else None
489 492
490 493
491 494 class UserLog(Base, BaseModel):
492 495 __tablename__ = 'user_logs'
493 496 __table_args__ = (
494 497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 498 'mysql_charset': 'utf8'},
496 499 )
497 500 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
498 501 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
499 502 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
500 503 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
501 504 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
502 505 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
503 506 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
504 507
505 508 @property
506 509 def action_as_day(self):
507 510 return datetime.date(*self.action_date.timetuple()[:3])
508 511
509 512 user = relationship('User')
510 513 repository = relationship('Repository', cascade='')
511 514
512 515
513 516 class UsersGroup(Base, BaseModel):
514 517 __tablename__ = 'users_groups'
515 518 __table_args__ = (
516 519 {'extend_existing': True, 'mysql_engine': 'InnoDB',
517 520 'mysql_charset': 'utf8'},
518 521 )
519 522
520 523 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
521 524 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
522 525 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
523 526 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
524 527
525 528 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
526 529 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
527 530 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
528 531
529 532 def __unicode__(self):
530 533 return u'<userGroup(%s)>' % (self.users_group_name)
531 534
532 535 @classmethod
533 536 def get_by_group_name(cls, group_name, cache=False,
534 537 case_insensitive=False):
535 538 if case_insensitive:
536 539 q = cls.query().filter(cls.users_group_name.ilike(group_name))
537 540 else:
538 541 q = cls.query().filter(cls.users_group_name == group_name)
539 542 if cache:
540 543 q = q.options(FromCache(
541 544 "sql_cache_short",
542 545 "get_user_%s" % _hash_key(group_name)
543 546 )
544 547 )
545 548 return q.scalar()
546 549
547 550 @classmethod
548 551 def get(cls, users_group_id, cache=False):
549 552 users_group = cls.query()
550 553 if cache:
551 554 users_group = users_group.options(FromCache("sql_cache_short",
552 555 "get_users_group_%s" % users_group_id))
553 556 return users_group.get(users_group_id)
554 557
555 558 def get_api_data(self):
556 559 users_group = self
557 560
558 561 data = dict(
559 562 users_group_id=users_group.users_group_id,
560 563 group_name=users_group.users_group_name,
561 564 active=users_group.users_group_active,
562 565 )
563 566
564 567 return data
565 568
566 569
567 570 class UsersGroupMember(Base, BaseModel):
568 571 __tablename__ = 'users_groups_members'
569 572 __table_args__ = (
570 573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
571 574 'mysql_charset': 'utf8'},
572 575 )
573 576
574 577 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
575 578 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
576 579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
577 580
578 581 user = relationship('User', lazy='joined')
579 582 users_group = relationship('UsersGroup')
580 583
581 584 def __init__(self, gr_id='', u_id=''):
582 585 self.users_group_id = gr_id
583 586 self.user_id = u_id
584 587
585 588
586 589 class Repository(Base, BaseModel):
587 590 __tablename__ = 'repositories'
588 591 __table_args__ = (
589 592 UniqueConstraint('repo_name'),
590 593 Index('r_repo_name_idx', 'repo_name'),
591 594 {'extend_existing': True, 'mysql_engine': 'InnoDB',
592 595 'mysql_charset': 'utf8'},
593 596 )
594 597
595 598 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
596 599 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
597 600 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
598 601 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
599 602 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
600 603 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
601 604 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
602 605 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
603 606 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
604 607 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
605 608 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
606 609 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
607 610 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
608 611 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
609 612
610 613 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
611 614 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
612 615
613 616 user = relationship('User')
614 617 fork = relationship('Repository', remote_side=repo_id)
615 618 group = relationship('RepoGroup')
616 619 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
617 620 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
618 621 stats = relationship('Statistics', cascade='all', uselist=False)
619 622
620 623 followers = relationship('UserFollowing',
621 624 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
622 625 cascade='all')
623 626
624 627 logs = relationship('UserLog')
625 628 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
626 629
627 630 pull_requests_org = relationship('PullRequest',
628 631 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
629 632 cascade="all, delete, delete-orphan")
630 633
631 634 pull_requests_other = relationship('PullRequest',
632 635 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
633 636 cascade="all, delete, delete-orphan")
634 637
635 638 def __unicode__(self):
636 639 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
637 640 self.repo_name)
638 641
639 642 @hybrid_property
640 643 def locked(self):
641 644 # always should return [user_id, timelocked]
642 645 if self._locked:
643 646 _lock_info = self._locked.split(':')
644 647 return int(_lock_info[0]), _lock_info[1]
645 648 return [None, None]
646 649
647 650 @locked.setter
648 651 def locked(self, val):
649 652 if val and isinstance(val, (list, tuple)):
650 653 self._locked = ':'.join(map(str, val))
651 654 else:
652 655 self._locked = None
653 656
654 657 @classmethod
655 658 def url_sep(cls):
656 659 return URL_SEP
657 660
658 661 @classmethod
659 662 def get_by_repo_name(cls, repo_name):
660 663 q = Session().query(cls).filter(cls.repo_name == repo_name)
661 664 q = q.options(joinedload(Repository.fork))\
662 665 .options(joinedload(Repository.user))\
663 666 .options(joinedload(Repository.group))
664 667 return q.scalar()
665 668
666 669 @classmethod
667 670 def get_by_full_path(cls, repo_full_path):
668 671 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
669 672 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
670 673
671 674 @classmethod
672 675 def get_repo_forks(cls, repo_id):
673 676 return cls.query().filter(Repository.fork_id == repo_id)
674 677
675 678 @classmethod
676 679 def base_path(cls):
677 680 """
678 681 Returns base path when all repos are stored
679 682
680 683 :param cls:
681 684 """
682 685 q = Session().query(RhodeCodeUi)\
683 686 .filter(RhodeCodeUi.ui_key == cls.url_sep())
684 687 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
685 688 return q.one().ui_value
686 689
687 690 @property
688 691 def forks(self):
689 692 """
690 693 Return forks of this repo
691 694 """
692 695 return Repository.get_repo_forks(self.repo_id)
693 696
694 697 @property
695 698 def parent(self):
696 699 """
697 700 Returns fork parent
698 701 """
699 702 return self.fork
700 703
701 704 @property
702 705 def just_name(self):
703 706 return self.repo_name.split(Repository.url_sep())[-1]
704 707
705 708 @property
706 709 def groups_with_parents(self):
707 710 groups = []
708 711 if self.group is None:
709 712 return groups
710 713
711 714 cur_gr = self.group
712 715 groups.insert(0, cur_gr)
713 716 while 1:
714 717 gr = getattr(cur_gr, 'parent_group', None)
715 718 cur_gr = cur_gr.parent_group
716 719 if gr is None:
717 720 break
718 721 groups.insert(0, gr)
719 722
720 723 return groups
721 724
722 725 @property
723 726 def groups_and_repo(self):
724 727 return self.groups_with_parents, self.just_name
725 728
726 729 @LazyProperty
727 730 def repo_path(self):
728 731 """
729 732 Returns base full path for that repository means where it actually
730 733 exists on a filesystem
731 734 """
732 735 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
733 736 Repository.url_sep())
734 737 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
735 738 return q.one().ui_value
736 739
737 740 @property
738 741 def repo_full_path(self):
739 742 p = [self.repo_path]
740 743 # we need to split the name by / since this is how we store the
741 744 # names in the database, but that eventually needs to be converted
742 745 # into a valid system path
743 746 p += self.repo_name.split(Repository.url_sep())
744 747 return os.path.join(*p)
745 748
746 749 def get_new_name(self, repo_name):
747 750 """
748 751 returns new full repository name based on assigned group and new new
749 752
750 753 :param group_name:
751 754 """
752 755 path_prefix = self.group.full_path_splitted if self.group else []
753 756 return Repository.url_sep().join(path_prefix + [repo_name])
754 757
755 758 @property
756 759 def _ui(self):
757 760 """
758 761 Creates an db based ui object for this repository
759 762 """
760 763 from mercurial import ui
761 764 from mercurial import config
762 765 baseui = ui.ui()
763 766
764 767 #clean the baseui object
765 768 baseui._ocfg = config.config()
766 769 baseui._ucfg = config.config()
767 770 baseui._tcfg = config.config()
768 771
769 772 ret = RhodeCodeUi.query()\
770 773 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
771 774
772 775 hg_ui = ret
773 776 for ui_ in hg_ui:
774 777 if ui_.ui_active:
775 778 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
776 779 ui_.ui_key, ui_.ui_value)
777 780 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
778 781 if ui_.ui_key == 'push_ssl':
779 782 # force set push_ssl requirement to False, rhodecode
780 783 # handles that
781 784 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
782 785
783 786 return baseui
784 787
785 788 @classmethod
786 789 def inject_ui(cls, repo, extras={}):
787 790 from rhodecode.lib.vcs.backends.hg import MercurialRepository
788 791 from rhodecode.lib.vcs.backends.git import GitRepository
789 792 required = (MercurialRepository, GitRepository)
790 793 if not isinstance(repo, required):
791 794 raise Exception('repo must be instance of %s' % required)
792 795
793 796 # inject ui extra param to log this action via push logger
794 797 for k, v in extras.items():
795 798 repo._repo.ui.setconfig('rhodecode_extras', k, v)
796 799
797 800 @classmethod
798 801 def is_valid(cls, repo_name):
799 802 """
800 803 returns True if given repo name is a valid filesystem repository
801 804
802 805 :param cls:
803 806 :param repo_name:
804 807 """
805 808 from rhodecode.lib.utils import is_valid_repo
806 809
807 810 return is_valid_repo(repo_name, cls.base_path())
808 811
809 812 def get_api_data(self):
810 813 """
811 814 Common function for generating repo api data
812 815
813 816 """
814 817 repo = self
815 818 data = dict(
816 819 repo_id=repo.repo_id,
817 820 repo_name=repo.repo_name,
818 821 repo_type=repo.repo_type,
819 822 clone_uri=repo.clone_uri,
820 823 private=repo.private,
821 824 created_on=repo.created_on,
822 825 description=repo.description,
823 826 landing_rev=repo.landing_rev,
824 827 owner=repo.user.username,
825 828 fork_of=repo.fork.repo_name if repo.fork else None
826 829 )
827 830
828 831 return data
829 832
830 833 @classmethod
831 834 def lock(cls, repo, user_id):
832 835 repo.locked = [user_id, time.time()]
833 836 Session().add(repo)
834 837 Session().commit()
835 838
836 839 @classmethod
837 840 def unlock(cls, repo):
838 841 repo.locked = None
839 842 Session().add(repo)
840 843 Session().commit()
841 844
842 845 #==========================================================================
843 846 # SCM PROPERTIES
844 847 #==========================================================================
845 848
846 849 def get_changeset(self, rev=None):
847 850 return get_changeset_safe(self.scm_instance, rev)
848 851
849 852 def get_landing_changeset(self):
850 853 """
851 854 Returns landing changeset, or if that doesn't exist returns the tip
852 855 """
853 856 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
854 857 return cs
855 858
856 859 @property
857 860 def tip(self):
858 861 return self.get_changeset('tip')
859 862
860 863 @property
861 864 def author(self):
862 865 return self.tip.author
863 866
864 867 @property
865 868 def last_change(self):
866 869 return self.scm_instance.last_change
867 870
868 871 def get_comments(self, revisions=None):
869 872 """
870 873 Returns comments for this repository grouped by revisions
871 874
872 875 :param revisions: filter query by revisions only
873 876 """
874 877 cmts = ChangesetComment.query()\
875 878 .filter(ChangesetComment.repo == self)
876 879 if revisions:
877 880 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
878 881 grouped = defaultdict(list)
879 882 for cmt in cmts.all():
880 883 grouped[cmt.revision].append(cmt)
881 884 return grouped
882 885
883 886 def statuses(self, revisions=None):
884 887 """
885 888 Returns statuses for this repository
886 889
887 890 :param revisions: list of revisions to get statuses for
888 891 :type revisions: list
889 892 """
890 893
891 894 statuses = ChangesetStatus.query()\
892 895 .filter(ChangesetStatus.repo == self)\
893 896 .filter(ChangesetStatus.version == 0)
894 897 if revisions:
895 898 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
896 899 grouped = {}
897 900
898 901 #maybe we have open new pullrequest without a status ?
899 902 stat = ChangesetStatus.STATUS_UNDER_REVIEW
900 903 status_lbl = ChangesetStatus.get_status_lbl(stat)
901 904 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
902 905 for rev in pr.revisions:
903 906 pr_id = pr.pull_request_id
904 907 pr_repo = pr.other_repo.repo_name
905 908 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
906 909
907 910 for stat in statuses.all():
908 911 pr_id = pr_repo = None
909 912 if stat.pull_request:
910 913 pr_id = stat.pull_request.pull_request_id
911 914 pr_repo = stat.pull_request.other_repo.repo_name
912 915 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
913 916 pr_id, pr_repo]
914 917 return grouped
915 918
916 919 #==========================================================================
917 920 # SCM CACHE INSTANCE
918 921 #==========================================================================
919 922
920 923 @property
921 924 def invalidate(self):
922 925 return CacheInvalidation.invalidate(self.repo_name)
923 926
924 927 def set_invalidate(self):
925 928 """
926 929 set a cache for invalidation for this instance
927 930 """
928 931 CacheInvalidation.set_invalidate(self.repo_name)
929 932
930 933 @LazyProperty
931 934 def scm_instance(self):
932 935 return self.__get_instance()
933 936
934 937 def scm_instance_cached(self, cache_map=None):
935 938 @cache_region('long_term')
936 939 def _c(repo_name):
937 940 return self.__get_instance()
938 941 rn = self.repo_name
939 942 log.debug('Getting cached instance of repo')
940 943
941 944 if cache_map:
942 945 # get using prefilled cache_map
943 946 invalidate_repo = cache_map[self.repo_name]
944 947 if invalidate_repo:
945 948 invalidate_repo = (None if invalidate_repo.cache_active
946 949 else invalidate_repo)
947 950 else:
948 951 # get from invalidate
949 952 invalidate_repo = self.invalidate
950 953
951 954 if invalidate_repo is not None:
952 955 region_invalidate(_c, None, rn)
953 956 # update our cache
954 957 CacheInvalidation.set_valid(invalidate_repo.cache_key)
955 958 return _c(rn)
956 959
957 960 def __get_instance(self):
958 961 repo_full_path = self.repo_full_path
959 962 try:
960 963 alias = get_scm(repo_full_path)[0]
961 964 log.debug('Creating instance of %s repository' % alias)
962 965 backend = get_backend(alias)
963 966 except VCSError:
964 967 log.error(traceback.format_exc())
965 968 log.error('Perhaps this repository is in db and not in '
966 969 'filesystem run rescan repositories with '
967 970 '"destroy old data " option from admin panel')
968 971 return
969 972
970 973 if alias == 'hg':
971 974
972 975 repo = backend(safe_str(repo_full_path), create=False,
973 976 baseui=self._ui)
974 977 # skip hidden web repository
975 978 if repo._get_hidden():
976 979 return
977 980 else:
978 981 repo = backend(repo_full_path, create=False)
979 982
980 983 return repo
981 984
982 985
983 986 class RepoGroup(Base, BaseModel):
984 987 __tablename__ = 'groups'
985 988 __table_args__ = (
986 989 UniqueConstraint('group_name', 'group_parent_id'),
987 990 CheckConstraint('group_id != group_parent_id'),
988 991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
989 992 'mysql_charset': 'utf8'},
990 993 )
991 994 __mapper_args__ = {'order_by': 'group_name'}
992 995
993 996 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
994 997 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
995 998 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
996 999 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
997 1000 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
998 1001
999 1002 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1000 1003 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1001 1004
1002 1005 parent_group = relationship('RepoGroup', remote_side=group_id)
1003 1006
1004 1007 def __init__(self, group_name='', parent_group=None):
1005 1008 self.group_name = group_name
1006 1009 self.parent_group = parent_group
1007 1010
1008 1011 def __unicode__(self):
1009 1012 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1010 1013 self.group_name)
1011 1014
1012 1015 @classmethod
1013 1016 def groups_choices(cls):
1014 1017 from webhelpers.html import literal as _literal
1015 1018 repo_groups = [('', '')]
1016 1019 sep = ' &raquo; '
1017 1020 _name = lambda k: _literal(sep.join(k))
1018 1021
1019 1022 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1020 1023 for x in cls.query().all()])
1021 1024
1022 1025 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1023 1026 return repo_groups
1024 1027
1025 1028 @classmethod
1026 1029 def url_sep(cls):
1027 1030 return URL_SEP
1028 1031
1029 1032 @classmethod
1030 1033 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1031 1034 if case_insensitive:
1032 1035 gr = cls.query()\
1033 1036 .filter(cls.group_name.ilike(group_name))
1034 1037 else:
1035 1038 gr = cls.query()\
1036 1039 .filter(cls.group_name == group_name)
1037 1040 if cache:
1038 1041 gr = gr.options(FromCache(
1039 1042 "sql_cache_short",
1040 1043 "get_group_%s" % _hash_key(group_name)
1041 1044 )
1042 1045 )
1043 1046 return gr.scalar()
1044 1047
1045 1048 @property
1046 1049 def parents(self):
1047 1050 parents_recursion_limit = 5
1048 1051 groups = []
1049 1052 if self.parent_group is None:
1050 1053 return groups
1051 1054 cur_gr = self.parent_group
1052 1055 groups.insert(0, cur_gr)
1053 1056 cnt = 0
1054 1057 while 1:
1055 1058 cnt += 1
1056 1059 gr = getattr(cur_gr, 'parent_group', None)
1057 1060 cur_gr = cur_gr.parent_group
1058 1061 if gr is None:
1059 1062 break
1060 1063 if cnt == parents_recursion_limit:
1061 1064 # this will prevent accidental infinit loops
1062 1065 log.error('group nested more than %s' %
1063 1066 parents_recursion_limit)
1064 1067 break
1065 1068
1066 1069 groups.insert(0, gr)
1067 1070 return groups
1068 1071
1069 1072 @property
1070 1073 def children(self):
1071 1074 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1072 1075
1073 1076 @property
1074 1077 def name(self):
1075 1078 return self.group_name.split(RepoGroup.url_sep())[-1]
1076 1079
1077 1080 @property
1078 1081 def full_path(self):
1079 1082 return self.group_name
1080 1083
1081 1084 @property
1082 1085 def full_path_splitted(self):
1083 1086 return self.group_name.split(RepoGroup.url_sep())
1084 1087
1085 1088 @property
1086 1089 def repositories(self):
1087 1090 return Repository.query()\
1088 1091 .filter(Repository.group == self)\
1089 1092 .order_by(Repository.repo_name)
1090 1093
1091 1094 @property
1092 1095 def repositories_recursive_count(self):
1093 1096 cnt = self.repositories.count()
1094 1097
1095 1098 def children_count(group):
1096 1099 cnt = 0
1097 1100 for child in group.children:
1098 1101 cnt += child.repositories.count()
1099 1102 cnt += children_count(child)
1100 1103 return cnt
1101 1104
1102 1105 return cnt + children_count(self)
1103 1106
1104 1107 def recursive_groups_and_repos(self):
1105 1108 """
1106 1109 Recursive return all groups, with repositories in those groups
1107 1110 """
1108 1111 all_ = []
1109 1112
1110 1113 def _get_members(root_gr):
1111 1114 for r in root_gr.repositories:
1112 1115 all_.append(r)
1113 1116 childs = root_gr.children.all()
1114 1117 if childs:
1115 1118 for gr in childs:
1116 1119 all_.append(gr)
1117 1120 _get_members(gr)
1118 1121
1119 1122 _get_members(self)
1120 1123 return [self] + all_
1121 1124
1122 1125 def get_new_name(self, group_name):
1123 1126 """
1124 1127 returns new full group name based on parent and new name
1125 1128
1126 1129 :param group_name:
1127 1130 """
1128 1131 path_prefix = (self.parent_group.full_path_splitted if
1129 1132 self.parent_group else [])
1130 1133 return RepoGroup.url_sep().join(path_prefix + [group_name])
1131 1134
1132 1135
1133 1136 class Permission(Base, BaseModel):
1134 1137 __tablename__ = 'permissions'
1135 1138 __table_args__ = (
1136 1139 Index('p_perm_name_idx', 'permission_name'),
1137 1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1138 1141 'mysql_charset': 'utf8'},
1139 1142 )
1140 1143 PERMS = [
1141 1144 ('repository.none', _('Repository no access')),
1142 1145 ('repository.read', _('Repository read access')),
1143 1146 ('repository.write', _('Repository write access')),
1144 1147 ('repository.admin', _('Repository admin access')),
1145 1148
1146 1149 ('group.none', _('Repositories Group no access')),
1147 1150 ('group.read', _('Repositories Group read access')),
1148 1151 ('group.write', _('Repositories Group write access')),
1149 1152 ('group.admin', _('Repositories Group admin access')),
1150 1153
1151 1154 ('hg.admin', _('RhodeCode Administrator')),
1152 1155 ('hg.create.none', _('Repository creation disabled')),
1153 1156 ('hg.create.repository', _('Repository creation enabled')),
1154 1157 ('hg.fork.none', _('Repository forking disabled')),
1155 1158 ('hg.fork.repository', _('Repository forking enabled')),
1156 1159 ('hg.register.none', _('Register disabled')),
1157 1160 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1158 1161 'with manual activation')),
1159 1162
1160 1163 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1161 1164 'with auto activation')),
1162 1165 ]
1163 1166
1164 1167 # defines which permissions are more important higher the more important
1165 1168 PERM_WEIGHTS = {
1166 1169 'repository.none': 0,
1167 1170 'repository.read': 1,
1168 1171 'repository.write': 3,
1169 1172 'repository.admin': 4,
1170 1173
1171 1174 'group.none': 0,
1172 1175 'group.read': 1,
1173 1176 'group.write': 3,
1174 1177 'group.admin': 4,
1175 1178
1176 1179 'hg.fork.none': 0,
1177 1180 'hg.fork.repository': 1,
1178 1181 'hg.create.none': 0,
1179 1182 'hg.create.repository':1
1180 1183 }
1181 1184
1182 1185 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1183 1186 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1184 1187 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1185 1188
1186 1189 def __unicode__(self):
1187 1190 return u"<%s('%s:%s')>" % (
1188 1191 self.__class__.__name__, self.permission_id, self.permission_name
1189 1192 )
1190 1193
1191 1194 @classmethod
1192 1195 def get_by_key(cls, key):
1193 1196 return cls.query().filter(cls.permission_name == key).scalar()
1194 1197
1195 1198 @classmethod
1196 1199 def get_default_perms(cls, default_user_id):
1197 1200 q = Session().query(UserRepoToPerm, Repository, cls)\
1198 1201 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1199 1202 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1200 1203 .filter(UserRepoToPerm.user_id == default_user_id)
1201 1204
1202 1205 return q.all()
1203 1206
1204 1207 @classmethod
1205 1208 def get_default_group_perms(cls, default_user_id):
1206 1209 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1207 1210 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1208 1211 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1209 1212 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1210 1213
1211 1214 return q.all()
1212 1215
1213 1216
1214 1217 class UserRepoToPerm(Base, BaseModel):
1215 1218 __tablename__ = 'repo_to_perm'
1216 1219 __table_args__ = (
1217 1220 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1218 1221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1219 1222 'mysql_charset': 'utf8'}
1220 1223 )
1221 1224 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1222 1225 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1223 1226 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1224 1227 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1225 1228
1226 1229 user = relationship('User')
1227 1230 repository = relationship('Repository')
1228 1231 permission = relationship('Permission')
1229 1232
1230 1233 @classmethod
1231 1234 def create(cls, user, repository, permission):
1232 1235 n = cls()
1233 1236 n.user = user
1234 1237 n.repository = repository
1235 1238 n.permission = permission
1236 1239 Session().add(n)
1237 1240 return n
1238 1241
1239 1242 def __unicode__(self):
1240 1243 return u'<user:%s => %s >' % (self.user, self.repository)
1241 1244
1242 1245
1243 1246 class UserToPerm(Base, BaseModel):
1244 1247 __tablename__ = 'user_to_perm'
1245 1248 __table_args__ = (
1246 1249 UniqueConstraint('user_id', 'permission_id'),
1247 1250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1248 1251 'mysql_charset': 'utf8'}
1249 1252 )
1250 1253 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1251 1254 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1252 1255 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1253 1256
1254 1257 user = relationship('User')
1255 1258 permission = relationship('Permission', lazy='joined')
1256 1259
1257 1260
1258 1261 class UsersGroupRepoToPerm(Base, BaseModel):
1259 1262 __tablename__ = 'users_group_repo_to_perm'
1260 1263 __table_args__ = (
1261 1264 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1262 1265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1263 1266 'mysql_charset': 'utf8'}
1264 1267 )
1265 1268 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1266 1269 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1267 1270 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1268 1271 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1269 1272
1270 1273 users_group = relationship('UsersGroup')
1271 1274 permission = relationship('Permission')
1272 1275 repository = relationship('Repository')
1273 1276
1274 1277 @classmethod
1275 1278 def create(cls, users_group, repository, permission):
1276 1279 n = cls()
1277 1280 n.users_group = users_group
1278 1281 n.repository = repository
1279 1282 n.permission = permission
1280 1283 Session().add(n)
1281 1284 return n
1282 1285
1283 1286 def __unicode__(self):
1284 1287 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1285 1288
1286 1289
1287 1290 class UsersGroupToPerm(Base, BaseModel):
1288 1291 __tablename__ = 'users_group_to_perm'
1289 1292 __table_args__ = (
1290 1293 UniqueConstraint('users_group_id', 'permission_id',),
1291 1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1292 1295 'mysql_charset': 'utf8'}
1293 1296 )
1294 1297 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1295 1298 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1296 1299 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1297 1300
1298 1301 users_group = relationship('UsersGroup')
1299 1302 permission = relationship('Permission')
1300 1303
1301 1304
1302 1305 class UserRepoGroupToPerm(Base, BaseModel):
1303 1306 __tablename__ = 'user_repo_group_to_perm'
1304 1307 __table_args__ = (
1305 1308 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1306 1309 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1307 1310 'mysql_charset': 'utf8'}
1308 1311 )
1309 1312
1310 1313 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1311 1314 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1312 1315 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1313 1316 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1314 1317
1315 1318 user = relationship('User')
1316 1319 group = relationship('RepoGroup')
1317 1320 permission = relationship('Permission')
1318 1321
1319 1322
1320 1323 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1321 1324 __tablename__ = 'users_group_repo_group_to_perm'
1322 1325 __table_args__ = (
1323 1326 UniqueConstraint('users_group_id', 'group_id'),
1324 1327 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1325 1328 'mysql_charset': 'utf8'}
1326 1329 )
1327 1330
1328 1331 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1329 1332 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1330 1333 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1331 1334 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1332 1335
1333 1336 users_group = relationship('UsersGroup')
1334 1337 permission = relationship('Permission')
1335 1338 group = relationship('RepoGroup')
1336 1339
1337 1340
1338 1341 class Statistics(Base, BaseModel):
1339 1342 __tablename__ = 'statistics'
1340 1343 __table_args__ = (
1341 1344 UniqueConstraint('repository_id'),
1342 1345 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1343 1346 'mysql_charset': 'utf8'}
1344 1347 )
1345 1348 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1346 1349 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1347 1350 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1348 1351 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1349 1352 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1350 1353 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1351 1354
1352 1355 repository = relationship('Repository', single_parent=True)
1353 1356
1354 1357
1355 1358 class UserFollowing(Base, BaseModel):
1356 1359 __tablename__ = 'user_followings'
1357 1360 __table_args__ = (
1358 1361 UniqueConstraint('user_id', 'follows_repository_id'),
1359 1362 UniqueConstraint('user_id', 'follows_user_id'),
1360 1363 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1361 1364 'mysql_charset': 'utf8'}
1362 1365 )
1363 1366
1364 1367 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1365 1368 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1366 1369 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1367 1370 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1368 1371 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1369 1372
1370 1373 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1371 1374
1372 1375 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1373 1376 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1374 1377
1375 1378 @classmethod
1376 1379 def get_repo_followers(cls, repo_id):
1377 1380 return cls.query().filter(cls.follows_repo_id == repo_id)
1378 1381
1379 1382
1380 1383 class CacheInvalidation(Base, BaseModel):
1381 1384 __tablename__ = 'cache_invalidation'
1382 1385 __table_args__ = (
1383 1386 UniqueConstraint('cache_key'),
1384 1387 Index('key_idx', 'cache_key'),
1385 1388 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1386 1389 'mysql_charset': 'utf8'},
1387 1390 )
1388 1391 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1389 1392 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1390 1393 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1391 1394 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1392 1395
1393 1396 def __init__(self, cache_key, cache_args=''):
1394 1397 self.cache_key = cache_key
1395 1398 self.cache_args = cache_args
1396 1399 self.cache_active = False
1397 1400
1398 1401 def __unicode__(self):
1399 1402 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1400 1403 self.cache_id, self.cache_key)
1401 1404
1402 1405 @classmethod
1403 1406 def clear_cache(cls):
1404 1407 cls.query().delete()
1405 1408
1406 1409 @classmethod
1407 1410 def _get_key(cls, key):
1408 1411 """
1409 1412 Wrapper for generating a key, together with a prefix
1410 1413
1411 1414 :param key:
1412 1415 """
1413 1416 import rhodecode
1414 1417 prefix = ''
1415 1418 iid = rhodecode.CONFIG.get('instance_id')
1416 1419 if iid:
1417 1420 prefix = iid
1418 1421 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1419 1422
1420 1423 @classmethod
1421 1424 def get_by_key(cls, key):
1422 1425 return cls.query().filter(cls.cache_key == key).scalar()
1423 1426
1424 1427 @classmethod
1425 1428 def _get_or_create_key(cls, key, prefix, org_key):
1426 1429 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1427 1430 if not inv_obj:
1428 1431 try:
1429 1432 inv_obj = CacheInvalidation(key, org_key)
1430 1433 Session().add(inv_obj)
1431 1434 Session().commit()
1432 1435 except Exception:
1433 1436 log.error(traceback.format_exc())
1434 1437 Session().rollback()
1435 1438 return inv_obj
1436 1439
1437 1440 @classmethod
1438 1441 def invalidate(cls, key):
1439 1442 """
1440 1443 Returns Invalidation object if this given key should be invalidated
1441 1444 None otherwise. `cache_active = False` means that this cache
1442 1445 state is not valid and needs to be invalidated
1443 1446
1444 1447 :param key:
1445 1448 """
1446 1449
1447 1450 key, _prefix, _org_key = cls._get_key(key)
1448 1451 inv = cls._get_or_create_key(key, _prefix, _org_key)
1449 1452
1450 1453 if inv and inv.cache_active is False:
1451 1454 return inv
1452 1455
1453 1456 @classmethod
1454 1457 def set_invalidate(cls, key):
1455 1458 """
1456 1459 Mark this Cache key for invalidation
1457 1460
1458 1461 :param key:
1459 1462 """
1460 1463
1461 1464 key, _prefix, _org_key = cls._get_key(key)
1462 1465 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1463 1466 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1464 1467 _org_key))
1465 1468 try:
1466 1469 for inv_obj in inv_objs:
1467 1470 if inv_obj:
1468 1471 inv_obj.cache_active = False
1469 1472
1470 1473 Session().add(inv_obj)
1471 1474 Session().commit()
1472 1475 except Exception:
1473 1476 log.error(traceback.format_exc())
1474 1477 Session().rollback()
1475 1478
1476 1479 @classmethod
1477 1480 def set_valid(cls, key):
1478 1481 """
1479 1482 Mark this cache key as active and currently cached
1480 1483
1481 1484 :param key:
1482 1485 """
1483 1486 inv_obj = cls.get_by_key(key)
1484 1487 inv_obj.cache_active = True
1485 1488 Session().add(inv_obj)
1486 1489 Session().commit()
1487 1490
1488 1491 @classmethod
1489 1492 def get_cache_map(cls):
1490 1493
1491 1494 class cachemapdict(dict):
1492 1495
1493 1496 def __init__(self, *args, **kwargs):
1494 1497 fixkey = kwargs.get('fixkey')
1495 1498 if fixkey:
1496 1499 del kwargs['fixkey']
1497 1500 self.fixkey = fixkey
1498 1501 super(cachemapdict, self).__init__(*args, **kwargs)
1499 1502
1500 1503 def __getattr__(self, name):
1501 1504 key = name
1502 1505 if self.fixkey:
1503 1506 key, _prefix, _org_key = cls._get_key(key)
1504 1507 if key in self.__dict__:
1505 1508 return self.__dict__[key]
1506 1509 else:
1507 1510 return self[key]
1508 1511
1509 1512 def __getitem__(self, key):
1510 1513 if self.fixkey:
1511 1514 key, _prefix, _org_key = cls._get_key(key)
1512 1515 try:
1513 1516 return super(cachemapdict, self).__getitem__(key)
1514 1517 except KeyError:
1515 1518 return
1516 1519
1517 1520 cache_map = cachemapdict(fixkey=True)
1518 1521 for obj in cls.query().all():
1519 1522 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1520 1523 return cache_map
1521 1524
1522 1525
1523 1526 class ChangesetComment(Base, BaseModel):
1524 1527 __tablename__ = 'changeset_comments'
1525 1528 __table_args__ = (
1526 1529 Index('cc_revision_idx', 'revision'),
1527 1530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1528 1531 'mysql_charset': 'utf8'},
1529 1532 )
1530 1533 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1531 1534 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1532 1535 revision = Column('revision', String(40), nullable=True)
1533 1536 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1534 1537 line_no = Column('line_no', Unicode(10), nullable=True)
1535 1538 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1536 1539 f_path = Column('f_path', Unicode(1000), nullable=True)
1537 1540 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1538 1541 text = Column('text', UnicodeText(25000), nullable=False)
1539 1542 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1540 1543 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1541 1544
1542 1545 author = relationship('User', lazy='joined')
1543 1546 repo = relationship('Repository')
1544 1547 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1545 1548 pull_request = relationship('PullRequest', lazy='joined')
1546 1549
1547 1550 @classmethod
1548 1551 def get_users(cls, revision=None, pull_request_id=None):
1549 1552 """
1550 1553 Returns user associated with this ChangesetComment. ie those
1551 1554 who actually commented
1552 1555
1553 1556 :param cls:
1554 1557 :param revision:
1555 1558 """
1556 1559 q = Session().query(User)\
1557 1560 .join(ChangesetComment.author)
1558 1561 if revision:
1559 1562 q = q.filter(cls.revision == revision)
1560 1563 elif pull_request_id:
1561 1564 q = q.filter(cls.pull_request_id == pull_request_id)
1562 1565 return q.all()
1563 1566
1564 1567
1565 1568 class ChangesetStatus(Base, BaseModel):
1566 1569 __tablename__ = 'changeset_statuses'
1567 1570 __table_args__ = (
1568 1571 Index('cs_revision_idx', 'revision'),
1569 1572 Index('cs_version_idx', 'version'),
1570 1573 UniqueConstraint('repo_id', 'revision', 'version'),
1571 1574 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1572 1575 'mysql_charset': 'utf8'}
1573 1576 )
1574 1577 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1575 1578 STATUS_APPROVED = 'approved'
1576 1579 STATUS_REJECTED = 'rejected'
1577 1580 STATUS_UNDER_REVIEW = 'under_review'
1578 1581
1579 1582 STATUSES = [
1580 1583 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1581 1584 (STATUS_APPROVED, _("Approved")),
1582 1585 (STATUS_REJECTED, _("Rejected")),
1583 1586 (STATUS_UNDER_REVIEW, _("Under Review")),
1584 1587 ]
1585 1588
1586 1589 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1587 1590 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1588 1591 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1589 1592 revision = Column('revision', String(40), nullable=False)
1590 1593 status = Column('status', String(128), nullable=False, default=DEFAULT)
1591 1594 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1592 1595 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1593 1596 version = Column('version', Integer(), nullable=False, default=0)
1594 1597 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1595 1598
1596 1599 author = relationship('User', lazy='joined')
1597 1600 repo = relationship('Repository')
1598 1601 comment = relationship('ChangesetComment', lazy='joined')
1599 1602 pull_request = relationship('PullRequest', lazy='joined')
1600 1603
1601 1604 def __unicode__(self):
1602 1605 return u"<%s('%s:%s')>" % (
1603 1606 self.__class__.__name__,
1604 1607 self.status, self.author
1605 1608 )
1606 1609
1607 1610 @classmethod
1608 1611 def get_status_lbl(cls, value):
1609 1612 return dict(cls.STATUSES).get(value)
1610 1613
1611 1614 @property
1612 1615 def status_lbl(self):
1613 1616 return ChangesetStatus.get_status_lbl(self.status)
1614 1617
1615 1618
1616 1619 class PullRequest(Base, BaseModel):
1617 1620 __tablename__ = 'pull_requests'
1618 1621 __table_args__ = (
1619 1622 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1620 1623 'mysql_charset': 'utf8'},
1621 1624 )
1622 1625
1623 1626 STATUS_NEW = u'new'
1624 1627 STATUS_OPEN = u'open'
1625 1628 STATUS_CLOSED = u'closed'
1626 1629
1627 1630 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1628 1631 title = Column('title', Unicode(256), nullable=True)
1629 1632 description = Column('description', UnicodeText(10240), nullable=True)
1630 1633 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1631 1634 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1632 1635 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1633 1636 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1634 1637 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1635 1638 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1636 1639 org_ref = Column('org_ref', Unicode(256), nullable=False)
1637 1640 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1638 1641 other_ref = Column('other_ref', Unicode(256), nullable=False)
1639 1642
1640 1643 @hybrid_property
1641 1644 def revisions(self):
1642 1645 return self._revisions.split(':')
1643 1646
1644 1647 @revisions.setter
1645 1648 def revisions(self, val):
1646 1649 self._revisions = ':'.join(val)
1647 1650
1648 1651 author = relationship('User', lazy='joined')
1649 1652 reviewers = relationship('PullRequestReviewers',
1650 1653 cascade="all, delete, delete-orphan")
1651 1654 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1652 1655 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1653 1656 statuses = relationship('ChangesetStatus')
1654 1657 comments = relationship('ChangesetComment',
1655 1658 cascade="all, delete, delete-orphan")
1656 1659
1657 1660 def is_closed(self):
1658 1661 return self.status == self.STATUS_CLOSED
1659 1662
1660 1663 def __json__(self):
1661 1664 return dict(
1662 1665 revisions=self.revisions
1663 1666 )
1664 1667
1665 1668
1666 1669 class PullRequestReviewers(Base, BaseModel):
1667 1670 __tablename__ = 'pull_request_reviewers'
1668 1671 __table_args__ = (
1669 1672 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1670 1673 'mysql_charset': 'utf8'},
1671 1674 )
1672 1675
1673 1676 def __init__(self, user=None, pull_request=None):
1674 1677 self.user = user
1675 1678 self.pull_request = pull_request
1676 1679
1677 1680 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1678 1681 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1679 1682 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1680 1683
1681 1684 user = relationship('User')
1682 1685 pull_request = relationship('PullRequest')
1683 1686
1684 1687
1685 1688 class Notification(Base, BaseModel):
1686 1689 __tablename__ = 'notifications'
1687 1690 __table_args__ = (
1688 1691 Index('notification_type_idx', 'type'),
1689 1692 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1690 1693 'mysql_charset': 'utf8'},
1691 1694 )
1692 1695
1693 1696 TYPE_CHANGESET_COMMENT = u'cs_comment'
1694 1697 TYPE_MESSAGE = u'message'
1695 1698 TYPE_MENTION = u'mention'
1696 1699 TYPE_REGISTRATION = u'registration'
1697 1700 TYPE_PULL_REQUEST = u'pull_request'
1698 1701 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1699 1702
1700 1703 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1701 1704 subject = Column('subject', Unicode(512), nullable=True)
1702 1705 body = Column('body', UnicodeText(50000), nullable=True)
1703 1706 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1704 1707 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1705 1708 type_ = Column('type', Unicode(256))
1706 1709
1707 1710 created_by_user = relationship('User')
1708 1711 notifications_to_users = relationship('UserNotification', lazy='joined',
1709 1712 cascade="all, delete, delete-orphan")
1710 1713
1711 1714 @property
1712 1715 def recipients(self):
1713 1716 return [x.user for x in UserNotification.query()\
1714 1717 .filter(UserNotification.notification == self)\
1715 1718 .order_by(UserNotification.user_id.asc()).all()]
1716 1719
1717 1720 @classmethod
1718 1721 def create(cls, created_by, subject, body, recipients, type_=None):
1719 1722 if type_ is None:
1720 1723 type_ = Notification.TYPE_MESSAGE
1721 1724
1722 1725 notification = cls()
1723 1726 notification.created_by_user = created_by
1724 1727 notification.subject = subject
1725 1728 notification.body = body
1726 1729 notification.type_ = type_
1727 1730 notification.created_on = datetime.datetime.now()
1728 1731
1729 1732 for u in recipients:
1730 1733 assoc = UserNotification()
1731 1734 assoc.notification = notification
1732 1735 u.notifications.append(assoc)
1733 1736 Session().add(notification)
1734 1737 return notification
1735 1738
1736 1739 @property
1737 1740 def description(self):
1738 1741 from rhodecode.model.notification import NotificationModel
1739 1742 return NotificationModel().make_description(self)
1740 1743
1741 1744
1742 1745 class UserNotification(Base, BaseModel):
1743 1746 __tablename__ = 'user_to_notification'
1744 1747 __table_args__ = (
1745 1748 UniqueConstraint('user_id', 'notification_id'),
1746 1749 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1747 1750 'mysql_charset': 'utf8'}
1748 1751 )
1749 1752 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1750 1753 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1751 1754 read = Column('read', Boolean, default=False)
1752 1755 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1753 1756
1754 1757 user = relationship('User', lazy="joined")
1755 1758 notification = relationship('Notification', lazy="joined",
1756 1759 order_by=lambda: Notification.created_on.desc(),)
1757 1760
1758 1761 def mark_as_read(self):
1759 1762 self.read = True
1760 1763 Session().add(self)
1761 1764
1762 1765
1763 1766 class DbMigrateVersion(Base, BaseModel):
1764 1767 __tablename__ = 'db_migrate_version'
1765 1768 __table_args__ = (
1766 1769 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1767 1770 'mysql_charset': 'utf8'},
1768 1771 )
1769 1772 repository_id = Column('repository_id', String(250), primary_key=True)
1770 1773 repository_path = Column('repository_path', Text)
1771 1774 version = Column('version', Integer)
@@ -1,133 +1,133 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.permission
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 permissions model for RhodeCode
7 7
8 8 :created_on: Aug 20, 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
29 29 from sqlalchemy.exc import DatabaseError
30 30
31 31 from rhodecode.lib.caching_query import FromCache
32 32
33 33 from rhodecode.model import BaseModel
34 34 from rhodecode.model.db import User, Permission, UserToPerm, UserRepoToPerm,\
35 35 UserRepoGroupToPerm
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class PermissionModel(BaseModel):
41 41 """
42 42 Permissions model for RhodeCode
43 43 """
44 44
45 45 cls = Permission
46 46
47 47 def get_permission(self, permission_id, cache=False):
48 48 """
49 49 Get's permissions by id
50 50
51 51 :param permission_id: id of permission to get from database
52 52 :param cache: use Cache for this query
53 53 """
54 54 perm = self.sa.query(Permission)
55 55 if cache:
56 56 perm = perm.options(FromCache("sql_cache_short",
57 57 "get_permission_%s" % permission_id))
58 58 return perm.get(permission_id)
59 59
60 60 def get_permission_by_name(self, name, cache=False):
61 61 """
62 62 Get's permissions by given name
63 63
64 64 :param name: name to fetch
65 65 :param cache: Use cache for this query
66 66 """
67 67 perm = self.sa.query(Permission)\
68 68 .filter(Permission.permission_name == name)
69 69 if cache:
70 70 perm = perm.options(FromCache("sql_cache_short",
71 71 "get_permission_%s" % name))
72 72 return perm.scalar()
73 73
74 74 def update(self, form_result):
75 75 perm_user = self.sa.query(User)\
76 76 .filter(User.username ==
77 77 form_result['perm_user_name']).scalar()
78 78 u2p = self.sa.query(UserToPerm).filter(UserToPerm.user ==
79 79 perm_user).all()
80 if len(u2p) != 4:
80 if len(u2p) != len(User.DEFAULT_PERMISSIONS):
81 81 raise Exception('Defined: %s should be 4 permissions for default'
82 82 ' user. This should not happen please verify'
83 83 ' your database' % len(u2p))
84 84
85 85 try:
86 86 # stage 1 change defaults
87 87 for p in u2p:
88 88 if p.permission.permission_name.startswith('repository.'):
89 89 p.permission = self.get_permission_by_name(
90 90 form_result['default_perm'])
91 91 self.sa.add(p)
92 92
93 93 elif p.permission.permission_name.startswith('hg.register.'):
94 94 p.permission = self.get_permission_by_name(
95 95 form_result['default_register'])
96 96 self.sa.add(p)
97 97
98 98 elif p.permission.permission_name.startswith('hg.create.'):
99 99 p.permission = self.get_permission_by_name(
100 100 form_result['default_create'])
101 101 self.sa.add(p)
102 102
103 103 elif p.permission.permission_name.startswith('hg.fork.'):
104 104 p.permission = self.get_permission_by_name(
105 105 form_result['default_fork'])
106 106 self.sa.add(p)
107 107
108 108 _def_name = form_result['default_perm'].split('repository.')[-1]
109 109 #stage 2 update all default permissions for repos if checked
110 110 if form_result['overwrite_default'] == True:
111 111 _def = self.get_permission_by_name('repository.' + _def_name)
112 112 # repos
113 113 for r2p in self.sa.query(UserRepoToPerm)\
114 114 .filter(UserRepoToPerm.user == perm_user)\
115 115 .all():
116 116 r2p.permission = _def
117 117 self.sa.add(r2p)
118 118 # groups
119 119 _def = self.get_permission_by_name('group.' + _def_name)
120 120 for g2p in self.sa.query(UserRepoGroupToPerm)\
121 121 .filter(UserRepoGroupToPerm.user == perm_user)\
122 122 .all():
123 123 g2p.permission = _def
124 124 self.sa.add(g2p)
125 125
126 126 # stage 3 set anonymous access
127 127 if perm_user.username == 'default':
128 128 perm_user.active = bool(form_result['anonymous'])
129 129 self.sa.add(perm_user)
130 130
131 131 except (DatabaseError,):
132 132 log.error(traceback.format_exc())
133 133 raise
General Comments 0
You need to be logged in to leave comments. Login now