##// END OF EJS Templates
fixed issues with removed repos was accidentally added as groups, after...
marcink -
r2069:003c504d beta
parent child Browse files
Show More
@@ -1,550 +1,552 b''
1 1 .. _changelog:
2 2
3 3 Changelog
4 4 =========
5 5
6 6
7 7 1.3.3 (**2012-XX-XX**)
8 8 ----------------------
9 9
10 10 :status: in-progress
11 11 :branch: beta
12 12
13 13 news
14 14 ++++
15 15
16 16
17 17 fixes
18 18 +++++
19 19
20 20 - fixed some python2.5 compatibility issues
21 - fixed issues with removed repos was accidentally added as groups, after
22 full rescan of paths
21 23
22 24 1.3.2 (**2012-02-28**)
23 25 ----------------------
24 26
25 27 news
26 28 ++++
27 29
28 30
29 31 fixes
30 32 +++++
31 33
32 34 - fixed git protocol issues with repos-groups
33 35 - fixed git remote repos validator that prevented from cloning remote git repos
34 36 - fixes #370 ending slashes fixes for repo and groups
35 37 - fixes #368 improved git-protocol detection to handle other clients
36 38 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
37 39 Moved To Root
38 40 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
39 41 - fixed #373 missing cascade drop on user_group_to_perm table
40 42
41 43 1.3.1 (**2012-02-27**)
42 44 ----------------------
43 45
44 46 news
45 47 ++++
46 48
47 49
48 50 fixes
49 51 +++++
50 52
51 53 - redirection loop occurs when remember-me wasn't checked during login
52 54 - fixes issues with git blob history generation
53 55 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
54 56
55 57 1.3.0 (**2012-02-26**)
56 58 ----------------------
57 59
58 60 news
59 61 ++++
60 62
61 63 - code review, inspired by github code-comments
62 64 - #215 rst and markdown README files support
63 65 - #252 Container-based and proxy pass-through authentication support
64 66 - #44 branch browser. Filtering of changelog by branches
65 67 - mercurial bookmarks support
66 68 - new hover top menu, optimized to add maximum size for important views
67 69 - configurable clone url template with possibility to specify protocol like
68 70 ssh:// or http:// and also manually alter other parts of clone_url.
69 71 - enabled largefiles extension by default
70 72 - optimized summary file pages and saved a lot of unused space in them
71 73 - #239 option to manually mark repository as fork
72 74 - #320 mapping of commit authors to RhodeCode users
73 75 - #304 hashes are displayed using monospace font
74 76 - diff configuration, toggle white lines and context lines
75 77 - #307 configurable diffs, whitespace toggle, increasing context lines
76 78 - sorting on branches, tags and bookmarks using YUI datatable
77 79 - improved file filter on files page
78 80 - implements #330 api method for listing nodes ar particular revision
79 81 - #73 added linking issues in commit messages to chosen issue tracker url
80 82 based on user defined regular expression
81 83 - added linking of changesets in commit messages
82 84 - new compact changelog with expandable commit messages
83 85 - firstname and lastname are optional in user creation
84 86 - #348 added post-create repository hook
85 87 - #212 global encoding settings is now configurable from .ini files
86 88 - #227 added repository groups permissions
87 89 - markdown gets codehilite extensions
88 90 - new API methods, delete_repositories, grante/revoke permissions for groups
89 91 and repos
90 92
91 93
92 94 fixes
93 95 +++++
94 96
95 97 - rewrote dbsession management for atomic operations, and better error handling
96 98 - fixed sorting of repo tables
97 99 - #326 escape of special html entities in diffs
98 100 - normalized user_name => username in api attributes
99 101 - fixes #298 ldap created users with mixed case emails created conflicts
100 102 on saving a form
101 103 - fixes issue when owner of a repo couldn't revoke permissions for users
102 104 and groups
103 105 - fixes #271 rare JSON serialization problem with statistics
104 106 - fixes #337 missing validation check for conflicting names of a group with a
105 107 repositories group
106 108 - #340 fixed session problem for mysql and celery tasks
107 109 - fixed #331 RhodeCode mangles repository names if the a repository group
108 110 contains the "full path" to the repositories
109 111 - #355 RhodeCode doesn't store encrypted LDAP passwords
110 112
111 113 1.2.5 (**2012-01-28**)
112 114 ----------------------
113 115
114 116 news
115 117 ++++
116 118
117 119 fixes
118 120 +++++
119 121
120 122 - #340 Celery complains about MySQL server gone away, added session cleanup
121 123 for celery tasks
122 124 - #341 "scanning for repositories in None" log message during Rescan was missing
123 125 a parameter
124 126 - fixed creating archives with subrepos. Some hooks were triggered during that
125 127 operation leading to crash.
126 128 - fixed missing email in account page.
127 129 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
128 130 forking on windows impossible
129 131
130 132 1.2.4 (**2012-01-19**)
131 133 ----------------------
132 134
133 135 news
134 136 ++++
135 137
136 138 - RhodeCode is bundled with mercurial series 2.0.X by default, with
137 139 full support to largefiles extension. Enabled by default in new installations
138 140 - #329 Ability to Add/Remove Groups to/from a Repository via AP
139 141 - added requires.txt file with requirements
140 142
141 143 fixes
142 144 +++++
143 145
144 146 - fixes db session issues with celery when emailing admins
145 147 - #331 RhodeCode mangles repository names if the a repository group
146 148 contains the "full path" to the repositories
147 149 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
148 150 - DB session cleanup after hg protocol operations, fixes issues with
149 151 `mysql has gone away` errors
150 152 - #333 doc fixes for get_repo api function
151 153 - #271 rare JSON serialization problem with statistics enabled
152 154 - #337 Fixes issues with validation of repository name conflicting with
153 155 a group name. A proper message is now displayed.
154 156 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
155 157 doesn't work
156 158 - #316 fixes issues with web description in hgrc files
157 159
158 160 1.2.3 (**2011-11-02**)
159 161 ----------------------
160 162
161 163 news
162 164 ++++
163 165
164 166 - added option to manage repos group for non admin users
165 167 - added following API methods for get_users, create_user, get_users_groups,
166 168 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
167 169 get_repo, create_repo, add_user_to_repo
168 170 - implements #237 added password confirmation for my account
169 171 and admin edit user.
170 172 - implements #291 email notification for global events are now sent to all
171 173 administrator users, and global config email.
172 174
173 175 fixes
174 176 +++++
175 177
176 178 - added option for passing auth method for smtp mailer
177 179 - #276 issue with adding a single user with id>10 to usergroups
178 180 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
179 181 - #288 fixes managing of repos in a group for non admin user
180 182
181 183 1.2.2 (**2011-10-17**)
182 184 ----------------------
183 185
184 186 news
185 187 ++++
186 188
187 189 - #226 repo groups are available by path instead of numerical id
188 190
189 191 fixes
190 192 +++++
191 193
192 194 - #259 Groups with the same name but with different parent group
193 195 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
194 196 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
195 197 - #265 ldap save fails sometimes on converting attributes to booleans,
196 198 added getter and setter into model that will prevent from this on db model level
197 199 - fixed problems with timestamps issues #251 and #213
198 200 - fixes #266 RhodeCode allows to create repo with the same name and in
199 201 the same parent as group
200 202 - fixes #245 Rescan of the repositories on Windows
201 203 - fixes #248 cannot edit repos inside a group on windows
202 204 - fixes #219 forking problems on windows
203 205
204 206 1.2.1 (**2011-10-08**)
205 207 ----------------------
206 208
207 209 news
208 210 ++++
209 211
210 212
211 213 fixes
212 214 +++++
213 215
214 216 - fixed problems with basic auth and push problems
215 217 - gui fixes
216 218 - fixed logger
217 219
218 220 1.2.0 (**2011-10-07**)
219 221 ----------------------
220 222
221 223 news
222 224 ++++
223 225
224 226 - implemented #47 repository groups
225 227 - implemented #89 Can setup google analytics code from settings menu
226 228 - implemented #91 added nicer looking archive urls with more download options
227 229 like tags, branches
228 230 - implemented #44 into file browsing, and added follow branch option
229 231 - implemented #84 downloads can be enabled/disabled for each repository
230 232 - anonymous repository can be cloned without having to pass default:default
231 233 into clone url
232 234 - fixed #90 whoosh indexer can index chooses repositories passed in command
233 235 line
234 236 - extended journal with day aggregates and paging
235 237 - implemented #107 source code lines highlight ranges
236 238 - implemented #93 customizable changelog on combined revision ranges -
237 239 equivalent of githubs compare view
238 240 - implemented #108 extended and more powerful LDAP configuration
239 241 - implemented #56 users groups
240 242 - major code rewrites optimized codes for speed and memory usage
241 243 - raw and diff downloads are now in git format
242 244 - setup command checks for write access to given path
243 245 - fixed many issues with international characters and unicode. It uses utf8
244 246 decode with replace to provide less errors even with non utf8 encoded strings
245 247 - #125 added API KEY access to feeds
246 248 - #109 Repository can be created from external Mercurial link (aka. remote
247 249 repository, and manually updated (via pull) from admin panel
248 250 - beta git support - push/pull server + basic view for git repos
249 251 - added followers page and forks page
250 252 - server side file creation (with binary file upload interface)
251 253 and edition with commits powered by codemirror
252 254 - #111 file browser file finder, quick lookup files on whole file tree
253 255 - added quick login sliding menu into main page
254 256 - changelog uses lazy loading of affected files details, in some scenarios
255 257 this can improve speed of changelog page dramatically especially for
256 258 larger repositories.
257 259 - implements #214 added support for downloading subrepos in download menu.
258 260 - Added basic API for direct operations on rhodecode via JSON
259 261 - Implemented advanced hook management
260 262
261 263 fixes
262 264 +++++
263 265
264 266 - fixed file browser bug, when switching into given form revision the url was
265 267 not changing
266 268 - fixed propagation to error controller on simplehg and simplegit middlewares
267 269 - fixed error when trying to make a download on empty repository
268 270 - fixed problem with '[' chars in commit messages in journal
269 271 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
270 272 - journal fork fixes
271 273 - removed issue with space inside renamed repository after deletion
272 274 - fixed strange issue on formencode imports
273 275 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
274 276 - #150 fixes for errors on repositories mapped in db but corrupted in
275 277 filesystem
276 278 - fixed problem with ascendant characters in realm #181
277 279 - fixed problem with sqlite file based database connection pool
278 280 - whoosh indexer and code stats share the same dynamic extensions map
279 281 - fixes #188 - relationship delete of repo_to_perm entry on user removal
280 282 - fixes issue #189 Trending source files shows "show more" when no more exist
281 283 - fixes issue #197 Relative paths for pidlocks
282 284 - fixes issue #198 password will require only 3 chars now for login form
283 285 - fixes issue #199 wrong redirection for non admin users after creating a repository
284 286 - fixes issues #202, bad db constraint made impossible to attach same group
285 287 more than one time. Affects only mysql/postgres
286 288 - fixes #218 os.kill patch for windows was missing sig param
287 289 - improved rendering of dag (they are not trimmed anymore when number of
288 290 heads exceeds 5)
289 291
290 292 1.1.8 (**2011-04-12**)
291 293 ----------------------
292 294
293 295 news
294 296 ++++
295 297
296 298 - improved windows support
297 299
298 300 fixes
299 301 +++++
300 302
301 303 - fixed #140 freeze of python dateutil library, since new version is python2.x
302 304 incompatible
303 305 - setup-app will check for write permission in given path
304 306 - cleaned up license info issue #149
305 307 - fixes for issues #137,#116 and problems with unicode and accented characters.
306 308 - fixes crashes on gravatar, when passed in email as unicode
307 309 - fixed tooltip flickering problems
308 310 - fixed came_from redirection on windows
309 311 - fixed logging modules, and sql formatters
310 312 - windows fixes for os.kill issue #133
311 313 - fixes path splitting for windows issues #148
312 314 - fixed issue #143 wrong import on migration to 1.1.X
313 315 - fixed problems with displaying binary files, thanks to Thomas Waldmann
314 316 - removed name from archive files since it's breaking ui for long repo names
315 317 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
316 318 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
317 319 Thomas Waldmann
318 320 - fixed issue #166 summary pager was skipping 10 revisions on second page
319 321
320 322
321 323 1.1.7 (**2011-03-23**)
322 324 ----------------------
323 325
324 326 news
325 327 ++++
326 328
327 329 fixes
328 330 +++++
329 331
330 332 - fixed (again) #136 installation support for FreeBSD
331 333
332 334
333 335 1.1.6 (**2011-03-21**)
334 336 ----------------------
335 337
336 338 news
337 339 ++++
338 340
339 341 fixes
340 342 +++++
341 343
342 344 - fixed #136 installation support for FreeBSD
343 345 - RhodeCode will check for python version during installation
344 346
345 347 1.1.5 (**2011-03-17**)
346 348 ----------------------
347 349
348 350 news
349 351 ++++
350 352
351 353 - basic windows support, by exchanging pybcrypt into sha256 for windows only
352 354 highly inspired by idea of mantis406
353 355
354 356 fixes
355 357 +++++
356 358
357 359 - fixed sorting by author in main page
358 360 - fixed crashes with diffs on binary files
359 361 - fixed #131 problem with boolean values for LDAP
360 362 - fixed #122 mysql problems thanks to striker69
361 363 - fixed problem with errors on calling raw/raw_files/annotate functions
362 364 with unknown revisions
363 365 - fixed returned rawfiles attachment names with international character
364 366 - cleaned out docs, big thanks to Jason Harris
365 367
366 368 1.1.4 (**2011-02-19**)
367 369 ----------------------
368 370
369 371 news
370 372 ++++
371 373
372 374 fixes
373 375 +++++
374 376
375 377 - fixed formencode import problem on settings page, that caused server crash
376 378 when that page was accessed as first after server start
377 379 - journal fixes
378 380 - fixed option to access repository just by entering http://server/<repo_name>
379 381
380 382 1.1.3 (**2011-02-16**)
381 383 ----------------------
382 384
383 385 news
384 386 ++++
385 387
386 388 - implemented #102 allowing the '.' character in username
387 389 - added option to access repository just by entering http://server/<repo_name>
388 390 - celery task ignores result for better performance
389 391
390 392 fixes
391 393 +++++
392 394
393 395 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
394 396 apollo13 and Johan Walles
395 397 - small fixes in journal
396 398 - fixed problems with getting setting for celery from .ini files
397 399 - registration, password reset and login boxes share the same title as main
398 400 application now
399 401 - fixed #113: to high permissions to fork repository
400 402 - fixed problem with '[' chars in commit messages in journal
401 403 - removed issue with space inside renamed repository after deletion
402 404 - db transaction fixes when filesystem repository creation failed
403 405 - fixed #106 relation issues on databases different than sqlite
404 406 - fixed static files paths links to use of url() method
405 407
406 408 1.1.2 (**2011-01-12**)
407 409 ----------------------
408 410
409 411 news
410 412 ++++
411 413
412 414
413 415 fixes
414 416 +++++
415 417
416 418 - fixes #98 protection against float division of percentage stats
417 419 - fixed graph bug
418 420 - forced webhelpers version since it was making troubles during installation
419 421
420 422 1.1.1 (**2011-01-06**)
421 423 ----------------------
422 424
423 425 news
424 426 ++++
425 427
426 428 - added force https option into ini files for easier https usage (no need to
427 429 set server headers with this options)
428 430 - small css updates
429 431
430 432 fixes
431 433 +++++
432 434
433 435 - fixed #96 redirect loop on files view on repositories without changesets
434 436 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
435 437 and server crashed with errors
436 438 - fixed large tooltips problems on main page
437 439 - fixed #92 whoosh indexer is more error proof
438 440
439 441 1.1.0 (**2010-12-18**)
440 442 ----------------------
441 443
442 444 news
443 445 ++++
444 446
445 447 - rewrite of internals for vcs >=0.1.10
446 448 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
447 449 with older clients
448 450 - anonymous access, authentication via ldap
449 451 - performance upgrade for cached repos list - each repository has its own
450 452 cache that's invalidated when needed.
451 453 - performance upgrades on repositories with large amount of commits (20K+)
452 454 - main page quick filter for filtering repositories
453 455 - user dashboards with ability to follow chosen repositories actions
454 456 - sends email to admin on new user registration
455 457 - added cache/statistics reset options into repository settings
456 458 - more detailed action logger (based on hooks) with pushed changesets lists
457 459 and options to disable those hooks from admin panel
458 460 - introduced new enhanced changelog for merges that shows more accurate results
459 461 - new improved and faster code stats (based on pygments lexers mapping tables,
460 462 showing up to 10 trending sources for each repository. Additionally stats
461 463 can be disabled in repository settings.
462 464 - gui optimizations, fixed application width to 1024px
463 465 - added cut off (for large files/changesets) limit into config files
464 466 - whoosh, celeryd, upgrade moved to paster command
465 467 - other than sqlite database backends can be used
466 468
467 469 fixes
468 470 +++++
469 471
470 472 - fixes #61 forked repo was showing only after cache expired
471 473 - fixes #76 no confirmation on user deletes
472 474 - fixes #66 Name field misspelled
473 475 - fixes #72 block user removal when he owns repositories
474 476 - fixes #69 added password confirmation fields
475 477 - fixes #87 RhodeCode crashes occasionally on updating repository owner
476 478 - fixes #82 broken annotations on files with more than 1 blank line at the end
477 479 - a lot of fixes and tweaks for file browser
478 480 - fixed detached session issues
479 481 - fixed when user had no repos he would see all repos listed in my account
480 482 - fixed ui() instance bug when global hgrc settings was loaded for server
481 483 instance and all hgrc options were merged with our db ui() object
482 484 - numerous small bugfixes
483 485
484 486 (special thanks for TkSoh for detailed feedback)
485 487
486 488
487 489 1.0.2 (**2010-11-12**)
488 490 ----------------------
489 491
490 492 news
491 493 ++++
492 494
493 495 - tested under python2.7
494 496 - bumped sqlalchemy and celery versions
495 497
496 498 fixes
497 499 +++++
498 500
499 501 - fixed #59 missing graph.js
500 502 - fixed repo_size crash when repository had broken symlinks
501 503 - fixed python2.5 crashes.
502 504
503 505
504 506 1.0.1 (**2010-11-10**)
505 507 ----------------------
506 508
507 509 news
508 510 ++++
509 511
510 512 - small css updated
511 513
512 514 fixes
513 515 +++++
514 516
515 517 - fixed #53 python2.5 incompatible enumerate calls
516 518 - fixed #52 disable mercurial extension for web
517 519 - fixed #51 deleting repositories don't delete it's dependent objects
518 520
519 521
520 522 1.0.0 (**2010-11-02**)
521 523 ----------------------
522 524
523 525 - security bugfix simplehg wasn't checking for permissions on commands
524 526 other than pull or push.
525 527 - fixed doubled messages after push or pull in admin journal
526 528 - templating and css corrections, fixed repo switcher on chrome, updated titles
527 529 - admin menu accessible from options menu on repository view
528 530 - permissions cached queries
529 531
530 532 1.0.0rc4 (**2010-10-12**)
531 533 --------------------------
532 534
533 535 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
534 536 - removed cache_manager settings from sqlalchemy meta
535 537 - added sqlalchemy cache settings to ini files
536 538 - validated password length and added second try of failure on paster setup-app
537 539 - fixed setup database destroy prompt even when there was no db
538 540
539 541
540 542 1.0.0rc3 (**2010-10-11**)
541 543 -------------------------
542 544
543 545 - fixed i18n during installation.
544 546
545 547 1.0.0rc2 (**2010-10-11**)
546 548 -------------------------
547 549
548 550 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
549 551 occure. After vcs is fixed it'll be put back again.
550 552 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,622 +1,629 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 import re
27 28 import logging
28 29 import datetime
29 30 import traceback
30 31 import paste
31 32 import beaker
32 33 import tarfile
33 34 import shutil
34 35 from os.path import abspath
35 36 from os.path import dirname as dn, join as jn
36 37
37 38 from paste.script.command import Command, BadCommand
38 39
39 40 from mercurial import ui, config
40 41
41 42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 43
43 44 from rhodecode.lib.vcs import get_backend
44 45 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 47 from rhodecode.lib.vcs.utils.helpers import get_scm
47 48 from rhodecode.lib.vcs.exceptions import VCSError
48 49
49 50 from rhodecode.lib.caching_query import FromCache
50 51
51 52 from rhodecode.model import meta
52 53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 54 UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
54 55 from rhodecode.model.meta import Session
55 56 from rhodecode.model.repos_group import ReposGroupModel
56 57
57 58 log = logging.getLogger(__name__)
58 59
60 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61
59 62
60 63 def recursive_replace(str_, replace=' '):
61 64 """Recursive replace of given sign to just one instance
62 65
63 66 :param str_: given string
64 67 :param replace: char to find and replace multiple instances
65 68
66 69 Examples::
67 70 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
68 71 'Mighty-Mighty-Bo-sstones'
69 72 """
70 73
71 74 if str_.find(replace * 2) == -1:
72 75 return str_
73 76 else:
74 77 str_ = str_.replace(replace * 2, replace)
75 78 return recursive_replace(str_, replace)
76 79
77 80
78 81 def repo_name_slug(value):
79 82 """Return slug of name of repository
80 83 This function is called on each creation/modification
81 84 of repository to prevent bad names in repo
82 85 """
83 86
84 87 slug = remove_formatting(value)
85 88 slug = strip_tags(slug)
86 89
87 90 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
88 91 slug = slug.replace(c, '-')
89 92 slug = recursive_replace(slug, '-')
90 93 slug = collapse(slug, '-')
91 94 return slug
92 95
93 96
94 97 def get_repo_slug(request):
95 98 _repo = request.environ['pylons.routes_dict'].get('repo_name')
96 99 if _repo:
97 100 _repo = _repo.rstrip('/')
98 101 return _repo
99 102
100 103
101 104 def get_repos_group_slug(request):
102 105 _group = request.environ['pylons.routes_dict'].get('group_name')
103 106 if _group:
104 107 _group = _group.rstrip('/')
105 108 return _group
106 109
107 110
108 111 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
109 112 """
110 113 Action logger for various actions made by users
111 114
112 115 :param user: user that made this action, can be a unique username string or
113 116 object containing user_id attribute
114 117 :param action: action to log, should be on of predefined unique actions for
115 118 easy translations
116 119 :param repo: string name of repository or object containing repo_id,
117 120 that action was made on
118 121 :param ipaddr: optional ip address from what the action was made
119 122 :param sa: optional sqlalchemy session
120 123
121 124 """
122 125
123 126 if not sa:
124 127 sa = meta.Session
125 128
126 129 try:
127 130 if hasattr(user, 'user_id'):
128 131 user_obj = user
129 132 elif isinstance(user, basestring):
130 133 user_obj = User.get_by_username(user)
131 134 else:
132 135 raise Exception('You have to provide user object or username')
133 136
134 137 if hasattr(repo, 'repo_id'):
135 138 repo_obj = Repository.get(repo.repo_id)
136 139 repo_name = repo_obj.repo_name
137 140 elif isinstance(repo, basestring):
138 141 repo_name = repo.lstrip('/')
139 142 repo_obj = Repository.get_by_repo_name(repo_name)
140 143 else:
141 144 raise Exception('You have to provide repository to action logger')
142 145
143 146 user_log = UserLog()
144 147 user_log.user_id = user_obj.user_id
145 148 user_log.action = action
146 149
147 150 user_log.repository_id = repo_obj.repo_id
148 151 user_log.repository_name = repo_name
149 152
150 153 user_log.action_date = datetime.datetime.now()
151 154 user_log.user_ip = ipaddr
152 155 sa.add(user_log)
153 156
154 157 log.info('Adding user %s, action %s on %s' % (user_obj, action, repo))
155 158 if commit:
156 159 sa.commit()
157 160 except:
158 161 log.error(traceback.format_exc())
159 162 raise
160 163
161 164
162 165 def get_repos(path, recursive=False):
163 166 """
164 167 Scans given path for repos and return (name,(type,path)) tuple
165 168
166 169 :param path: path to scan for repositories
167 170 :param recursive: recursive search and return names with subdirs in front
168 171 """
169 172
170 173 # remove ending slash for better results
171 174 path = path.rstrip(os.sep)
172 175
173 176 def _get_repos(p):
174 177 if not os.access(p, os.W_OK):
175 178 return
176 179 for dirpath in os.listdir(p):
177 180 if os.path.isfile(os.path.join(p, dirpath)):
178 181 continue
179 182 cur_path = os.path.join(p, dirpath)
180 183 try:
181 184 scm_info = get_scm(cur_path)
182 185 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
183 186 except VCSError:
184 187 if not recursive:
185 188 continue
186 189 #check if this dir containts other repos for recursive scan
187 190 rec_path = os.path.join(p, dirpath)
188 191 if os.path.isdir(rec_path):
189 192 for inner_scm in _get_repos(rec_path):
190 193 yield inner_scm
191 194
192 195 return _get_repos(path)
193 196
194 197
195 198 def is_valid_repo(repo_name, base_path):
196 199 """
197 200 Returns True if given path is a valid repository False otherwise
198 201 :param repo_name:
199 202 :param base_path:
200 203
201 204 :return True: if given path is a valid repository
202 205 """
203 206 full_path = os.path.join(base_path, repo_name)
204 207
205 208 try:
206 209 get_scm(full_path)
207 210 return True
208 211 except VCSError:
209 212 return False
210 213
211 214
212 215 def is_valid_repos_group(repos_group_name, base_path):
213 216 """
214 217 Returns True if given path is a repos group False otherwise
215 218
216 219 :param repo_name:
217 220 :param base_path:
218 221 """
219 222 full_path = os.path.join(base_path, repos_group_name)
220 223
221 224 # check if it's not a repo
222 225 if is_valid_repo(repos_group_name, base_path):
223 226 return False
224 227
225 228 # check if it's a valid path
226 229 if os.path.isdir(full_path):
227 230 return True
228 231
229 232 return False
230 233
231 234
232 235 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
233 236 while True:
234 237 ok = raw_input(prompt)
235 238 if ok in ('y', 'ye', 'yes'):
236 239 return True
237 240 if ok in ('n', 'no', 'nop', 'nope'):
238 241 return False
239 242 retries = retries - 1
240 243 if retries < 0:
241 244 raise IOError
242 245 print complaint
243 246
244 247 #propagated from mercurial documentation
245 248 ui_sections = ['alias', 'auth',
246 249 'decode/encode', 'defaults',
247 250 'diff', 'email',
248 251 'extensions', 'format',
249 252 'merge-patterns', 'merge-tools',
250 253 'hooks', 'http_proxy',
251 254 'smtp', 'patch',
252 255 'paths', 'profiling',
253 256 'server', 'trusted',
254 257 'ui', 'web', ]
255 258
256 259
257 260 def make_ui(read_from='file', path=None, checkpaths=True):
258 261 """A function that will read python rc files or database
259 262 and make an mercurial ui object from read options
260 263
261 264 :param path: path to mercurial config file
262 265 :param checkpaths: check the path
263 266 :param read_from: read from 'file' or 'db'
264 267 """
265 268
266 269 baseui = ui.ui()
267 270
268 271 # clean the baseui object
269 272 baseui._ocfg = config.config()
270 273 baseui._ucfg = config.config()
271 274 baseui._tcfg = config.config()
272 275
273 276 if read_from == 'file':
274 277 if not os.path.isfile(path):
275 278 log.debug('hgrc file is not present at %s skipping...' % path)
276 279 return False
277 280 log.debug('reading hgrc from %s' % path)
278 281 cfg = config.config()
279 282 cfg.read(path)
280 283 for section in ui_sections:
281 284 for k, v in cfg.items(section):
282 285 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
283 286 baseui.setconfig(section, k, v)
284 287
285 288 elif read_from == 'db':
286 289 sa = meta.Session
287 290 ret = sa.query(RhodeCodeUi)\
288 291 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
289 292 .all()
290 293
291 294 hg_ui = ret
292 295 for ui_ in hg_ui:
293 296 if ui_.ui_active:
294 297 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
295 298 ui_.ui_key, ui_.ui_value)
296 299 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
297 300
298 301 meta.Session.remove()
299 302 return baseui
300 303
301 304
302 305 def set_rhodecode_config(config):
303 306 """
304 307 Updates pylons config with new settings from database
305 308
306 309 :param config:
307 310 """
308 311 hgsettings = RhodeCodeSetting.get_app_settings()
309 312
310 313 for k, v in hgsettings.items():
311 314 config[k] = v
312 315
313 316
314 317 def invalidate_cache(cache_key, *args):
315 318 """
316 319 Puts cache invalidation task into db for
317 320 further global cache invalidation
318 321 """
319 322
320 323 from rhodecode.model.scm import ScmModel
321 324
322 325 if cache_key.startswith('get_repo_cached_'):
323 326 name = cache_key.split('get_repo_cached_')[-1]
324 327 ScmModel().mark_for_invalidation(name)
325 328
326 329
327 330 class EmptyChangeset(BaseChangeset):
328 331 """
329 332 An dummy empty changeset. It's possible to pass hash when creating
330 333 an EmptyChangeset
331 334 """
332 335
333 336 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
334 337 alias=None):
335 338 self._empty_cs = cs
336 339 self.revision = -1
337 340 self.message = ''
338 341 self.author = ''
339 342 self.date = ''
340 343 self.repository = repo
341 344 self.requested_revision = requested_revision
342 345 self.alias = alias
343 346
344 347 @LazyProperty
345 348 def raw_id(self):
346 349 """
347 350 Returns raw string identifying this changeset, useful for web
348 351 representation.
349 352 """
350 353
351 354 return self._empty_cs
352 355
353 356 @LazyProperty
354 357 def branch(self):
355 358 return get_backend(self.alias).DEFAULT_BRANCH_NAME
356 359
357 360 @LazyProperty
358 361 def short_id(self):
359 362 return self.raw_id[:12]
360 363
361 364 def get_file_changeset(self, path):
362 365 return self
363 366
364 367 def get_file_content(self, path):
365 368 return u''
366 369
367 370 def get_file_size(self, path):
368 371 return 0
369 372
370 373
371 374 def map_groups(groups):
372 375 """
373 376 Checks for groups existence, and creates groups structures.
374 377 It returns last group in structure
375 378
376 379 :param groups: list of groups structure
377 380 """
378 381 sa = meta.Session
379 382
380 383 parent = None
381 384 group = None
382 385
383 386 # last element is repo in nested groups structure
384 387 groups = groups[:-1]
385 388 rgm = ReposGroupModel(sa)
386 389 for lvl, group_name in enumerate(groups):
387 390 group_name = '/'.join(groups[:lvl] + [group_name])
388 391 group = RepoGroup.get_by_group_name(group_name)
389 392 desc = '%s group' % group_name
390 393
391 394 # # WTF that doesn't work !?
392 395 # if group is None:
393 396 # group = rgm.create(group_name, desc, parent, just_db=True)
394 397 # sa.commit()
395 398
399 # skip folders that are now removed repos
400 if REMOVED_REPO_PAT.match(group_name):
401 break
402
396 403 if group is None:
397 404 log.debug('creating group level: %s group_name: %s' % (lvl, group_name))
398 405 group = RepoGroup(group_name, parent)
399 406 group.group_description = desc
400 407 sa.add(group)
401 408 rgm._create_default_perms(group)
402 409 sa.commit()
403 410 parent = group
404 411 return group
405 412
406 413
407 414 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
408 415 """
409 416 maps all repos given in initial_repo_list, non existing repositories
410 417 are created, if remove_obsolete is True it also check for db entries
411 418 that are not in initial_repo_list and removes them.
412 419
413 420 :param initial_repo_list: list of repositories found by scanning methods
414 421 :param remove_obsolete: check for obsolete entries in database
415 422 """
416 423 from rhodecode.model.repo import RepoModel
417 424 sa = meta.Session
418 425 rm = RepoModel()
419 426 user = sa.query(User).filter(User.admin == True).first()
420 427 if user is None:
421 428 raise Exception('Missing administrative account !')
422 429 added = []
423 430
424 431 for name, repo in initial_repo_list.items():
425 432 group = map_groups(name.split(Repository.url_sep()))
426 433 if not rm.get_by_repo_name(name, cache=False):
427 434 log.info('repository %s not found creating default' % name)
428 435 added.append(name)
429 436 form_data = {
430 437 'repo_name': name,
431 438 'repo_name_full': name,
432 439 'repo_type': repo.alias,
433 440 'description': repo.description \
434 441 if repo.description != 'unknown' else '%s repository' % name,
435 442 'private': False,
436 443 'group_id': getattr(group, 'group_id', None)
437 444 }
438 445 rm.create(form_data, user, just_db=True)
439 446 sa.commit()
440 447 removed = []
441 448 if remove_obsolete:
442 449 #remove from database those repositories that are not in the filesystem
443 450 for repo in sa.query(Repository).all():
444 451 if repo.repo_name not in initial_repo_list.keys():
445 452 removed.append(repo.repo_name)
446 453 sa.delete(repo)
447 454 sa.commit()
448 455
449 456 return added, removed
450 457
451 458
452 459 # set cache regions for beaker so celery can utilise it
453 460 def add_cache(settings):
454 461 cache_settings = {'regions': None}
455 462 for key in settings.keys():
456 463 for prefix in ['beaker.cache.', 'cache.']:
457 464 if key.startswith(prefix):
458 465 name = key.split(prefix)[1].strip()
459 466 cache_settings[name] = settings[key].strip()
460 467 if cache_settings['regions']:
461 468 for region in cache_settings['regions'].split(','):
462 469 region = region.strip()
463 470 region_settings = {}
464 471 for key, value in cache_settings.items():
465 472 if key.startswith(region):
466 473 region_settings[key.split('.')[1]] = value
467 474 region_settings['expire'] = int(region_settings.get('expire',
468 475 60))
469 476 region_settings.setdefault('lock_dir',
470 477 cache_settings.get('lock_dir'))
471 478 region_settings.setdefault('data_dir',
472 479 cache_settings.get('data_dir'))
473 480
474 481 if 'type' not in region_settings:
475 482 region_settings['type'] = cache_settings.get('type',
476 483 'memory')
477 484 beaker.cache.cache_regions[region] = region_settings
478 485
479 486
480 487 #==============================================================================
481 488 # TEST FUNCTIONS AND CREATORS
482 489 #==============================================================================
483 490 def create_test_index(repo_location, config, full_index):
484 491 """
485 492 Makes default test index
486 493
487 494 :param config: test config
488 495 :param full_index:
489 496 """
490 497
491 498 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
492 499 from rhodecode.lib.pidlock import DaemonLock, LockHeld
493 500
494 501 repo_location = repo_location
495 502
496 503 index_location = os.path.join(config['app_conf']['index_dir'])
497 504 if not os.path.exists(index_location):
498 505 os.makedirs(index_location)
499 506
500 507 try:
501 508 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
502 509 WhooshIndexingDaemon(index_location=index_location,
503 510 repo_location=repo_location)\
504 511 .run(full_index=full_index)
505 512 l.release()
506 513 except LockHeld:
507 514 pass
508 515
509 516
510 517 def create_test_env(repos_test_path, config):
511 518 """
512 519 Makes a fresh database and
513 520 install test repository into tmp dir
514 521 """
515 522 from rhodecode.lib.db_manage import DbManage
516 523 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
517 524
518 525 # PART ONE create db
519 526 dbconf = config['sqlalchemy.db1.url']
520 527 log.debug('making test db %s' % dbconf)
521 528
522 529 # create test dir if it doesn't exist
523 530 if not os.path.isdir(repos_test_path):
524 531 log.debug('Creating testdir %s' % repos_test_path)
525 532 os.makedirs(repos_test_path)
526 533
527 534 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
528 535 tests=True)
529 536 dbmanage.create_tables(override=True)
530 537 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
531 538 dbmanage.create_default_user()
532 539 dbmanage.admin_prompt()
533 540 dbmanage.create_permissions()
534 541 dbmanage.populate_default_permissions()
535 542 Session.commit()
536 543 # PART TWO make test repo
537 544 log.debug('making test vcs repositories')
538 545
539 546 idx_path = config['app_conf']['index_dir']
540 547 data_path = config['app_conf']['cache_dir']
541 548
542 549 #clean index and data
543 550 if idx_path and os.path.exists(idx_path):
544 551 log.debug('remove %s' % idx_path)
545 552 shutil.rmtree(idx_path)
546 553
547 554 if data_path and os.path.exists(data_path):
548 555 log.debug('remove %s' % data_path)
549 556 shutil.rmtree(data_path)
550 557
551 558 #CREATE DEFAULT HG REPOSITORY
552 559 cur_dir = dn(dn(abspath(__file__)))
553 560 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
554 561 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
555 562 tar.close()
556 563
557 564
558 565 #==============================================================================
559 566 # PASTER COMMANDS
560 567 #==============================================================================
561 568 class BasePasterCommand(Command):
562 569 """
563 570 Abstract Base Class for paster commands.
564 571
565 572 The celery commands are somewhat aggressive about loading
566 573 celery.conf, and since our module sets the `CELERY_LOADER`
567 574 environment variable to our loader, we have to bootstrap a bit and
568 575 make sure we've had a chance to load the pylons config off of the
569 576 command line, otherwise everything fails.
570 577 """
571 578 min_args = 1
572 579 min_args_error = "Please provide a paster config file as an argument."
573 580 takes_config_file = 1
574 581 requires_config_file = True
575 582
576 583 def notify_msg(self, msg, log=False):
577 584 """Make a notification to user, additionally if logger is passed
578 585 it logs this action using given logger
579 586
580 587 :param msg: message that will be printed to user
581 588 :param log: logging instance, to use to additionally log this message
582 589
583 590 """
584 591 if log and isinstance(log, logging):
585 592 log(msg)
586 593
587 594 def run(self, args):
588 595 """
589 596 Overrides Command.run
590 597
591 598 Checks for a config file argument and loads it.
592 599 """
593 600 if len(args) < self.min_args:
594 601 raise BadCommand(
595 602 self.min_args_error % {'min_args': self.min_args,
596 603 'actual_args': len(args)})
597 604
598 605 # Decrement because we're going to lob off the first argument.
599 606 # @@ This is hacky
600 607 self.min_args -= 1
601 608 self.bootstrap_config(args[0])
602 609 self.update_parser()
603 610 return super(BasePasterCommand, self).run(args[1:])
604 611
605 612 def update_parser(self):
606 613 """
607 614 Abstract method. Allows for the class's parser to be updated
608 615 before the superclass's `run` method is called. Necessary to
609 616 allow options/arguments to be passed through to the underlying
610 617 celery command.
611 618 """
612 619 raise NotImplementedError("Abstract Method.")
613 620
614 621 def bootstrap_config(self, conf):
615 622 """
616 623 Loads the pylons configuration.
617 624 """
618 625 from pylons import config as pylonsconfig
619 626
620 627 path_to_ini_file = os.path.realpath(conf)
621 628 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
622 629 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,456 +1,459 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import time
27 27 import traceback
28 28 import logging
29 29 import cStringIO
30 30
31 31 from rhodecode.lib.vcs import get_backend
32 32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 33 from rhodecode.lib.vcs.utils.lazy import LazyProperty
34 34 from rhodecode.lib.vcs.nodes import FileNode
35 35
36 36 from rhodecode import BACKENDS
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib import safe_str
39 39 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
40 40 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 action_logger, EmptyChangeset
41 action_logger, EmptyChangeset, REMOVED_REPO_PAT
42 42 from rhodecode.model import BaseModel
43 43 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
44 44 UserFollowing, UserLog, User, RepoGroup
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class UserTemp(object):
50 50 def __init__(self, user_id):
51 51 self.user_id = user_id
52 52
53 53 def __repr__(self):
54 54 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
55 55
56 56
57 57 class RepoTemp(object):
58 58 def __init__(self, repo_id):
59 59 self.repo_id = repo_id
60 60
61 61 def __repr__(self):
62 62 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
63 63
64 64
65 65 class CachedRepoList(object):
66 66
67 67 def __init__(self, db_repo_list, repos_path, order_by=None):
68 68 self.db_repo_list = db_repo_list
69 69 self.repos_path = repos_path
70 70 self.order_by = order_by
71 71 self.reversed = (order_by or '').startswith('-')
72 72
73 73 def __len__(self):
74 74 return len(self.db_repo_list)
75 75
76 76 def __repr__(self):
77 77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
78 78
79 79 def __iter__(self):
80 80 for dbr in self.db_repo_list:
81 81 scmr = dbr.scm_instance_cached
82 82 # check permission at this level
83 83 if not HasRepoPermissionAny(
84 84 'repository.read', 'repository.write', 'repository.admin'
85 85 )(dbr.repo_name, 'get repo check'):
86 86 continue
87 87
88 88 if scmr is None:
89 89 log.error(
90 90 '%s this repository is present in database but it '
91 91 'cannot be created as an scm instance' % dbr.repo_name
92 92 )
93 93 continue
94 94
95 95 last_change = scmr.last_change
96 96 tip = h.get_changeset_safe(scmr, 'tip')
97 97
98 98 tmp_d = {}
99 99 tmp_d['name'] = dbr.repo_name
100 100 tmp_d['name_sort'] = tmp_d['name'].lower()
101 101 tmp_d['description'] = dbr.description
102 102 tmp_d['description_sort'] = tmp_d['description']
103 103 tmp_d['last_change'] = last_change
104 104 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
105 105 tmp_d['tip'] = tip.raw_id
106 106 tmp_d['tip_sort'] = tip.revision
107 107 tmp_d['rev'] = tip.revision
108 108 tmp_d['contact'] = dbr.user.full_contact
109 109 tmp_d['contact_sort'] = tmp_d['contact']
110 110 tmp_d['owner_sort'] = tmp_d['contact']
111 111 tmp_d['repo_archives'] = list(scmr._get_archives())
112 112 tmp_d['last_msg'] = tip.message
113 113 tmp_d['author'] = tip.author
114 114 tmp_d['dbrepo'] = dbr.get_dict()
115 115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
116 116 yield tmp_d
117 117
118 118
119 119 class GroupList(object):
120 120
121 121 def __init__(self, db_repo_group_list):
122 122 self.db_repo_group_list = db_repo_group_list
123 123
124 124 def __len__(self):
125 125 return len(self.db_repo_group_list)
126 126
127 127 def __repr__(self):
128 128 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
129 129
130 130 def __iter__(self):
131 131 for dbgr in self.db_repo_group_list:
132 132 # check permission at this level
133 133 if not HasReposGroupPermissionAny(
134 134 'group.read', 'group.write', 'group.admin'
135 135 )(dbgr.group_name, 'get group repo check'):
136 136 continue
137 137
138 138 yield dbgr
139 139
140 140
141 141 class ScmModel(BaseModel):
142 142 """
143 143 Generic Scm Model
144 144 """
145 145
146 146 def __get_repo(self, instance):
147 147 cls = Repository
148 148 if isinstance(instance, cls):
149 149 return instance
150 150 elif isinstance(instance, int) or str(instance).isdigit():
151 151 return cls.get(instance)
152 152 elif isinstance(instance, basestring):
153 153 return cls.get_by_repo_name(instance)
154 154 elif instance:
155 155 raise Exception('given object must be int, basestr or Instance'
156 156 ' of %s got %s' % (type(cls), type(instance)))
157 157
158 158 @LazyProperty
159 159 def repos_path(self):
160 160 """
161 161 Get's the repositories root path from database
162 162 """
163 163
164 164 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
165 165
166 166 return q.ui_value
167 167
168 168 def repo_scan(self, repos_path=None):
169 169 """
170 170 Listing of repositories in given path. This path should not be a
171 171 repository itself. Return a dictionary of repository objects
172 172
173 173 :param repos_path: path to directory containing repositories
174 174 """
175 175
176 176 if repos_path is None:
177 177 repos_path = self.repos_path
178 178
179 179 log.info('scanning for repositories in %s' % repos_path)
180 180
181 181 baseui = make_ui('db')
182 182 repos = {}
183 183
184 184 for name, path in get_filesystem_repos(repos_path, recursive=True):
185 # skip removed repos
186 if REMOVED_REPO_PAT.match(name):
187 continue
185 188
186 189 # name need to be decomposed and put back together using the /
187 190 # since this is internal storage separator for rhodecode
188 191 name = Repository.url_sep().join(name.split(os.sep))
189 192
190 193 try:
191 194 if name in repos:
192 195 raise RepositoryError('Duplicate repository name %s '
193 196 'found in %s' % (name, path))
194 197 else:
195 198
196 199 klass = get_backend(path[0])
197 200
198 201 if path[0] == 'hg' and path[0] in BACKENDS.keys():
199 202 repos[name] = klass(safe_str(path[1]), baseui=baseui)
200 203
201 204 if path[0] == 'git' and path[0] in BACKENDS.keys():
202 205 repos[name] = klass(path[1])
203 206 except OSError:
204 207 continue
205 208
206 209 return repos
207 210
208 211 def get_repos(self, all_repos=None, sort_key=None):
209 212 """
210 213 Get all repos from db and for each repo create it's
211 214 backend instance and fill that backed with information from database
212 215
213 216 :param all_repos: list of repository names as strings
214 217 give specific repositories list, good for filtering
215 218 """
216 219 if all_repos is None:
217 220 all_repos = self.sa.query(Repository)\
218 221 .filter(Repository.group_id == None)\
219 222 .order_by(Repository.repo_name).all()
220 223
221 224 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
222 225 order_by=sort_key)
223 226
224 227 return repo_iter
225 228
226 229 def get_repos_groups(self, all_groups=None):
227 230 if all_groups is None:
228 231 all_groups = RepoGroup.query()\
229 232 .filter(RepoGroup.group_parent_id == None).all()
230 233 group_iter = GroupList(all_groups)
231 234
232 235 return group_iter
233 236
234 237 def mark_for_invalidation(self, repo_name):
235 238 """Puts cache invalidation task into db for
236 239 further global cache invalidation
237 240
238 241 :param repo_name: this repo that should invalidation take place
239 242 """
240 243 CacheInvalidation.set_invalidate(repo_name)
241 244 CacheInvalidation.set_invalidate(repo_name + "_README")
242 245
243 246 def toggle_following_repo(self, follow_repo_id, user_id):
244 247
245 248 f = self.sa.query(UserFollowing)\
246 249 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
247 250 .filter(UserFollowing.user_id == user_id).scalar()
248 251
249 252 if f is not None:
250 253 try:
251 254 self.sa.delete(f)
252 255 action_logger(UserTemp(user_id),
253 256 'stopped_following_repo',
254 257 RepoTemp(follow_repo_id))
255 258 return
256 259 except:
257 260 log.error(traceback.format_exc())
258 261 raise
259 262
260 263 try:
261 264 f = UserFollowing()
262 265 f.user_id = user_id
263 266 f.follows_repo_id = follow_repo_id
264 267 self.sa.add(f)
265 268
266 269 action_logger(UserTemp(user_id),
267 270 'started_following_repo',
268 271 RepoTemp(follow_repo_id))
269 272 except:
270 273 log.error(traceback.format_exc())
271 274 raise
272 275
273 276 def toggle_following_user(self, follow_user_id, user_id):
274 277 f = self.sa.query(UserFollowing)\
275 278 .filter(UserFollowing.follows_user_id == follow_user_id)\
276 279 .filter(UserFollowing.user_id == user_id).scalar()
277 280
278 281 if f is not None:
279 282 try:
280 283 self.sa.delete(f)
281 284 return
282 285 except:
283 286 log.error(traceback.format_exc())
284 287 raise
285 288
286 289 try:
287 290 f = UserFollowing()
288 291 f.user_id = user_id
289 292 f.follows_user_id = follow_user_id
290 293 self.sa.add(f)
291 294 except:
292 295 log.error(traceback.format_exc())
293 296 raise
294 297
295 298 def is_following_repo(self, repo_name, user_id, cache=False):
296 299 r = self.sa.query(Repository)\
297 300 .filter(Repository.repo_name == repo_name).scalar()
298 301
299 302 f = self.sa.query(UserFollowing)\
300 303 .filter(UserFollowing.follows_repository == r)\
301 304 .filter(UserFollowing.user_id == user_id).scalar()
302 305
303 306 return f is not None
304 307
305 308 def is_following_user(self, username, user_id, cache=False):
306 309 u = User.get_by_username(username)
307 310
308 311 f = self.sa.query(UserFollowing)\
309 312 .filter(UserFollowing.follows_user == u)\
310 313 .filter(UserFollowing.user_id == user_id).scalar()
311 314
312 315 return f is not None
313 316
314 317 def get_followers(self, repo_id):
315 318 if not isinstance(repo_id, int):
316 319 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
317 320
318 321 return self.sa.query(UserFollowing)\
319 322 .filter(UserFollowing.follows_repo_id == repo_id).count()
320 323
321 324 def get_forks(self, repo_id):
322 325 if not isinstance(repo_id, int):
323 326 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
324 327
325 328 return self.sa.query(Repository)\
326 329 .filter(Repository.fork_id == repo_id).count()
327 330
328 331 def mark_as_fork(self, repo, fork, user):
329 332 repo = self.__get_repo(repo)
330 333 fork = self.__get_repo(fork)
331 334 repo.fork = fork
332 335 self.sa.add(repo)
333 336 return repo
334 337
335 338 def pull_changes(self, repo_name, username):
336 339 dbrepo = Repository.get_by_repo_name(repo_name)
337 340 clone_uri = dbrepo.clone_uri
338 341 if not clone_uri:
339 342 raise Exception("This repository doesn't have a clone uri")
340 343
341 344 repo = dbrepo.scm_instance
342 345 try:
343 346 extras = {'ip': '',
344 347 'username': username,
345 348 'action': 'push_remote',
346 349 'repository': repo_name}
347 350
348 351 #inject ui extra param to log this action via push logger
349 352 for k, v in extras.items():
350 353 repo._repo.ui.setconfig('rhodecode_extras', k, v)
351 354
352 355 repo.pull(clone_uri)
353 356 self.mark_for_invalidation(repo_name)
354 357 except:
355 358 log.error(traceback.format_exc())
356 359 raise
357 360
358 361 def commit_change(self, repo, repo_name, cs, user, author, message,
359 362 content, f_path):
360 363
361 364 if repo.alias == 'hg':
362 365 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
363 366 elif repo.alias == 'git':
364 367 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
365 368
366 369 # decoding here will force that we have proper encoded values
367 370 # in any other case this will throw exceptions and deny commit
368 371 content = safe_str(content)
369 372 message = safe_str(message)
370 373 path = safe_str(f_path)
371 374 author = safe_str(author)
372 375 m = IMC(repo)
373 376 m.change(FileNode(path, content))
374 377 tip = m.commit(message=message,
375 378 author=author,
376 379 parents=[cs], branch=cs.branch)
377 380
378 381 new_cs = tip.short_id
379 382 action = 'push_local:%s' % new_cs
380 383
381 384 action_logger(user, action, repo_name)
382 385
383 386 self.mark_for_invalidation(repo_name)
384 387
385 388 def create_node(self, repo, repo_name, cs, user, author, message, content,
386 389 f_path):
387 390 if repo.alias == 'hg':
388 391 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
389 392 elif repo.alias == 'git':
390 393 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
391 394 # decoding here will force that we have proper encoded values
392 395 # in any other case this will throw exceptions and deny commit
393 396
394 397 if isinstance(content, (basestring,)):
395 398 content = safe_str(content)
396 399 elif isinstance(content, (file, cStringIO.OutputType,)):
397 400 content = content.read()
398 401 else:
399 402 raise Exception('Content is of unrecognized type %s' % (
400 403 type(content)
401 404 ))
402 405
403 406 message = safe_str(message)
404 407 path = safe_str(f_path)
405 408 author = safe_str(author)
406 409 m = IMC(repo)
407 410
408 411 if isinstance(cs, EmptyChangeset):
409 412 # Emptychangeset means we we're editing empty repository
410 413 parents = None
411 414 else:
412 415 parents = [cs]
413 416
414 417 m.add(FileNode(path, content=content))
415 418 tip = m.commit(message=message,
416 419 author=author,
417 420 parents=parents, branch=cs.branch)
418 421 new_cs = tip.short_id
419 422 action = 'push_local:%s' % new_cs
420 423
421 424 action_logger(user, action, repo_name)
422 425
423 426 self.mark_for_invalidation(repo_name)
424 427
425 428 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
426 429 """
427 430 recursive walk in root dir and return a set of all path in that dir
428 431 based on repository walk function
429 432
430 433 :param repo_name: name of repository
431 434 :param revision: revision for which to list nodes
432 435 :param root_path: root path to list
433 436 :param flat: return as a list, if False returns a dict with decription
434 437
435 438 """
436 439 _files = list()
437 440 _dirs = list()
438 441 try:
439 442 _repo = self.__get_repo(repo_name)
440 443 changeset = _repo.scm_instance.get_changeset(revision)
441 444 root_path = root_path.lstrip('/')
442 445 for topnode, dirs, files in changeset.walk(root_path):
443 446 for f in files:
444 447 _files.append(f.path if flat else {"name": f.path,
445 448 "type": "file"})
446 449 for d in dirs:
447 450 _dirs.append(d.path if flat else {"name": d.path,
448 451 "type": "dir"})
449 452 except RepositoryError:
450 453 log.debug(traceback.format_exc())
451 454 raise
452 455
453 456 return _dirs, _files
454 457
455 458 def get_unread_journal(self):
456 459 return self.sa.query(UserLog).count()
General Comments 0
You need to be logged in to leave comments. Login now