Show More
@@ -1,36 +1,42 b'' | |||||
1 | .. _changelog: |
|
1 | .. _changelog: | |
2 |
|
2 | |||
3 | Changelog |
|
3 | Changelog | |
4 | ========= |
|
4 | ========= | |
5 |
|
5 | |||
|
6 | 1.1.0 (**XXXX-XX-XX**) | |||
|
7 | ---------------------- | |||
|
8 | - git support | |||
|
9 | - performance upgrade for cached repos list | |||
|
10 | ||||
|
11 | ||||
6 | 1.0.0 (**2010-10-xx**) |
|
12 | 1.0.0 (**2010-10-xx**) | |
7 | ---------------------- |
|
13 | ---------------------- | |
8 |
|
14 | |||
9 | - security bugfix simplehg wasn't checking for permissions on commands |
|
15 | - security bugfix simplehg wasn't checking for permissions on commands | |
10 | other than pull or push. |
|
16 | other than pull or push. | |
11 | - fixed doubled messages after push or pull in admin journal |
|
17 | - fixed doubled messages after push or pull in admin journal | |
12 | - templating and css corrections, fixed repo switcher on chrome,updated titles |
|
18 | - templating and css corrections, fixed repo switcher on chrome,updated titles | |
13 | - admin menu accessible from options menu on repository view |
|
19 | - admin menu accessible from options menu on repository view | |
14 | - permissions cached queries |
|
20 | - permissions cached queries | |
15 |
|
21 | |||
16 | 1.0.0rc4 (**2010-10-12**) |
|
22 | 1.0.0rc4 (**2010-10-12**) | |
17 | -------------------------- |
|
23 | -------------------------- | |
18 |
|
24 | |||
19 | - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman) |
|
25 | - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman) | |
20 | - removed cache_manager settings from sqlalchemy meta |
|
26 | - removed cache_manager settings from sqlalchemy meta | |
21 | - added sqlalchemy cache settings to ini files |
|
27 | - added sqlalchemy cache settings to ini files | |
22 | - validated password length and added second try of failure on paster setup-app |
|
28 | - validated password length and added second try of failure on paster setup-app | |
23 | - fixed setup database destroy prompt even when there was no db |
|
29 | - fixed setup database destroy prompt even when there was no db | |
24 |
|
30 | |||
25 |
|
31 | |||
26 | 1.0.0rc3 (**2010-10-11**) |
|
32 | 1.0.0rc3 (**2010-10-11**) | |
27 | ------------------------- |
|
33 | ------------------------- | |
28 |
|
34 | |||
29 | - fixed i18n during installation. |
|
35 | - fixed i18n during installation. | |
30 |
|
36 | |||
31 | 1.0.0rc2 (**2010-10-11**) |
|
37 | 1.0.0rc2 (**2010-10-11**) | |
32 | ------------------------- |
|
38 | ------------------------- | |
33 |
|
39 | |||
34 | - Disabled dirsize in file browser, it's causing nasty bug when dir renames |
|
40 | - Disabled dirsize in file browser, it's causing nasty bug when dir renames | |
35 | occure. After vcs is fixed it'll be put back again. |
|
41 | occure. After vcs is fixed it'll be put back again. | |
36 | - templating/css rewrites, optimized css. |
|
42 | - templating/css rewrites, optimized css. |
@@ -1,35 +1,35 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | # encoding: utf-8 |
|
2 | # encoding: utf-8 | |
3 | # RhodeCode, a web based repository management based on pylons |
|
3 | # RhodeCode, a web based repository management based on pylons | |
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> | |
5 | # |
|
5 | # | |
6 | # This program is free software; you can redistribute it and/or |
|
6 | # This program is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU General Public License |
|
7 | # modify it under the terms of the GNU General Public License | |
8 | # as published by the Free Software Foundation; version 2 |
|
8 | # as published by the Free Software Foundation; version 2 | |
9 | # of the License or (at your opinion) any later version of the license. |
|
9 | # of the License or (at your opinion) any later version of the license. | |
10 | # |
|
10 | # | |
11 | # This program is distributed in the hope that it will be useful, |
|
11 | # This program is distributed in the hope that it will be useful, | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | # GNU General Public License for more details. |
|
14 | # GNU General Public License for more details. | |
15 | # |
|
15 | # | |
16 | # You should have received a copy of the GNU General Public License |
|
16 | # You should have received a copy of the GNU General Public License | |
17 | # along with this program; if not, write to the Free Software |
|
17 | # along with this program; if not, write to the Free Software | |
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
19 | # MA 02110-1301, USA. |
|
19 | # MA 02110-1301, USA. | |
20 | """ |
|
20 | """ | |
21 | Created on April 9, 2010 |
|
21 | Created on April 9, 2010 | |
22 | RhodeCode, a web based repository management based on pylons |
|
22 | RhodeCode, a web based repository management based on pylons | |
23 | versioning implementation: http://semver.org/ |
|
23 | versioning implementation: http://semver.org/ | |
24 | @author: marcink |
|
24 | @author: marcink | |
25 | """ |
|
25 | """ | |
26 |
|
26 | |||
27 |
VERSION = (1, |
|
27 | VERSION = (1, 1, 0, 'beta') | |
28 |
|
28 | |||
29 | __version__ = '.'.join((str(each) for each in VERSION[:4])) |
|
29 | __version__ = '.'.join((str(each) for each in VERSION[:4])) | |
30 |
|
30 | |||
31 | def get_version(): |
|
31 | def get_version(): | |
32 | """ |
|
32 | """ | |
33 | Returns shorter version (digit parts only) as string. |
|
33 | Returns shorter version (digit parts only) as string. | |
34 | """ |
|
34 | """ | |
35 | return '.'.join((str(each) for each in VERSION[:3])) |
|
35 | return '.'.join((str(each) for each in VERSION[:3])) |
@@ -1,79 +1,79 b'' | |||||
1 | """Pylons environment configuration""" |
|
1 | """Pylons environment configuration""" | |
2 | from mako.lookup import TemplateLookup |
|
2 | from mako.lookup import TemplateLookup | |
3 | from pylons.configuration import PylonsConfig |
|
3 | from pylons.configuration import PylonsConfig | |
4 | from pylons.error import handle_mako_error |
|
4 | from pylons.error import handle_mako_error | |
5 | from rhodecode.config.routing import make_map |
|
5 | from rhodecode.config.routing import make_map | |
6 | from rhodecode.lib.auth import set_available_permissions, set_base_path |
|
6 | from rhodecode.lib.auth import set_available_permissions, set_base_path | |
7 | from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config |
|
7 | from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config | |
8 | from rhodecode.model import init_model |
|
8 | from rhodecode.model import init_model | |
9 | from rhodecode.model.hg import _get_repos_cached_initial |
|
9 | from rhodecode.model.hg import _get_repos_cached_initial | |
10 | from sqlalchemy import engine_from_config |
|
10 | from sqlalchemy import engine_from_config | |
11 | import logging |
|
11 | import logging | |
12 | import os |
|
12 | import os | |
13 | import rhodecode.lib.app_globals as app_globals |
|
13 | import rhodecode.lib.app_globals as app_globals | |
14 | import rhodecode.lib.helpers |
|
14 | import rhodecode.lib.helpers | |
15 |
|
15 | |||
16 | log = logging.getLogger(__name__) |
|
16 | log = logging.getLogger(__name__) | |
17 |
|
17 | |||
18 | def load_environment(global_conf, app_conf, initial=False): |
|
18 | def load_environment(global_conf, app_conf, initial=False): | |
19 | """Configure the Pylons environment via the ``pylons.config`` |
|
19 | """Configure the Pylons environment via the ``pylons.config`` | |
20 | object |
|
20 | object | |
21 | """ |
|
21 | """ | |
22 | config = PylonsConfig() |
|
22 | config = PylonsConfig() | |
23 |
|
23 | |||
24 | # Pylons paths |
|
24 | # Pylons paths | |
25 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
|
25 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
26 | paths = dict(root=root, |
|
26 | paths = dict(root=root, | |
27 | controllers=os.path.join(root, 'controllers'), |
|
27 | controllers=os.path.join(root, 'controllers'), | |
28 | static_files=os.path.join(root, 'public'), |
|
28 | static_files=os.path.join(root, 'public'), | |
29 | templates=[os.path.join(root, 'templates')]) |
|
29 | templates=[os.path.join(root, 'templates')]) | |
30 |
|
30 | |||
31 | # Initialize config with the basic options |
|
31 | # Initialize config with the basic options | |
32 | config.init_app(global_conf, app_conf, package='rhodecode', paths=paths) |
|
32 | config.init_app(global_conf, app_conf, package='rhodecode', paths=paths) | |
33 |
|
33 | |||
34 | config['routes.map'] = make_map(config) |
|
34 | config['routes.map'] = make_map(config) | |
35 | config['pylons.app_globals'] = app_globals.Globals(config) |
|
35 | config['pylons.app_globals'] = app_globals.Globals(config) | |
36 | config['pylons.h'] = rhodecode.lib.helpers |
|
36 | config['pylons.h'] = rhodecode.lib.helpers | |
37 |
|
37 | |||
38 | # Setup cache object as early as possible |
|
38 | # Setup cache object as early as possible | |
39 | import pylons |
|
39 | import pylons | |
40 | pylons.cache._push_object(config['pylons.app_globals'].cache) |
|
40 | pylons.cache._push_object(config['pylons.app_globals'].cache) | |
41 |
|
41 | |||
42 | # Create the Mako TemplateLookup, with the default auto-escaping |
|
42 | # Create the Mako TemplateLookup, with the default auto-escaping | |
43 | config['pylons.app_globals'].mako_lookup = TemplateLookup( |
|
43 | config['pylons.app_globals'].mako_lookup = TemplateLookup( | |
44 | directories=paths['templates'], |
|
44 | directories=paths['templates'], | |
45 | error_handler=handle_mako_error, |
|
45 | error_handler=handle_mako_error, | |
46 | module_directory=os.path.join(app_conf['cache_dir'], 'templates'), |
|
46 | module_directory=os.path.join(app_conf['cache_dir'], 'templates'), | |
47 | input_encoding='utf-8', default_filters=['escape'], |
|
47 | input_encoding='utf-8', default_filters=['escape'], | |
48 | imports=['from webhelpers.html import escape']) |
|
48 | imports=['from webhelpers.html import escape']) | |
49 |
|
49 | |||
50 | #sets the c attribute access when don't existing attribute are accessed |
|
50 | #sets the c attribute access when don't existing attribute are accessed | |
51 | config['pylons.strict_tmpl_context'] = True |
|
51 | config['pylons.strict_tmpl_context'] = True | |
52 | test = os.path.split(config['__file__'])[-1] == 'test.ini' |
|
52 | test = os.path.split(config['__file__'])[-1] == 'test.ini' | |
53 | if test: |
|
53 | if test: | |
54 | from rhodecode.lib.utils import create_test_env, create_test_index |
|
54 | from rhodecode.lib.utils import create_test_env, create_test_index | |
55 | create_test_env('/tmp', config) |
|
55 | create_test_env('/tmp', config) | |
56 |
create_test_index('/tmp |
|
56 | create_test_index('/tmp', True) | |
57 |
|
57 | |||
58 | #MULTIPLE DB configs |
|
58 | #MULTIPLE DB configs | |
59 | # Setup the SQLAlchemy database engine |
|
59 | # Setup the SQLAlchemy database engine | |
60 | if config['debug'] and not test: |
|
60 | if config['debug'] and not test: | |
61 | #use query time debugging. |
|
61 | #use query time debugging. | |
62 | from rhodecode.lib.timerproxy import TimerProxy |
|
62 | from rhodecode.lib.timerproxy import TimerProxy | |
63 | sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.', |
|
63 | sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.', | |
64 | proxy=TimerProxy()) |
|
64 | proxy=TimerProxy()) | |
65 | else: |
|
65 | else: | |
66 | sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.') |
|
66 | sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.') | |
67 |
|
67 | |||
68 | init_model(sa_engine_db1) |
|
68 | init_model(sa_engine_db1) | |
69 | #init baseui |
|
69 | #init baseui | |
70 | config['pylons.app_globals'].baseui = make_ui('db') |
|
70 | config['pylons.app_globals'].baseui = make_ui('db') | |
71 |
|
71 | |||
72 | repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'], initial)) |
|
72 | repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'], initial)) | |
73 | set_available_permissions(config) |
|
73 | set_available_permissions(config) | |
74 | set_base_path(config) |
|
74 | set_base_path(config) | |
75 | set_rhodecode_config(config) |
|
75 | set_rhodecode_config(config) | |
76 | # CONFIGURATION OPTIONS HERE (note: all config options will override |
|
76 | # CONFIGURATION OPTIONS HERE (note: all config options will override | |
77 | # any Pylons config options) |
|
77 | # any Pylons config options) | |
78 |
|
78 | |||
79 | return config |
|
79 | return config |
@@ -1,31 +1,31 b'' | |||||
1 | """The application's Globals object""" |
|
1 | """The application's Globals object""" | |
2 |
|
2 | |||
3 | from beaker.cache import CacheManager |
|
3 | from beaker.cache import CacheManager | |
4 | from beaker.util import parse_cache_config_options |
|
4 | from beaker.util import parse_cache_config_options | |
5 | from vcs.utils.lazy import LazyProperty |
|
5 | from vcs.utils.lazy import LazyProperty | |
6 |
|
6 | |||
7 | class Globals(object): |
|
7 | class Globals(object): | |
8 | """Globals acts as a container for objects available throughout the |
|
8 | """Globals acts as a container for objects available throughout the | |
9 | life of the application |
|
9 | life of the application | |
10 |
|
10 | |||
11 | """ |
|
11 | """ | |
12 |
|
12 | |||
13 | def __init__(self, config): |
|
13 | def __init__(self, config): | |
14 | """One instance of Globals is created during application |
|
14 | """One instance of Globals is created during application | |
15 | initialization and is available during requests via the |
|
15 | initialization and is available during requests via the | |
16 | 'app_globals' variable |
|
16 | 'app_globals' variable | |
17 |
|
17 | |||
18 | """ |
|
18 | """ | |
19 | self.cache = CacheManager(**parse_cache_config_options(config)) |
|
19 | self.cache = CacheManager(**parse_cache_config_options(config)) | |
20 | self.available_permissions = None # propagated after init_model |
|
20 | self.available_permissions = None # propagated after init_model | |
21 | self.baseui = None # propagated after init_model |
|
21 | self.baseui = None # propagated after init_model | |
22 |
|
22 | |||
23 | @LazyProperty |
|
23 | @LazyProperty | |
24 | def paths(self): |
|
24 | def paths(self): | |
25 | if self.baseui: |
|
25 | if self.baseui: | |
26 | return self.baseui.configitems('paths') |
|
26 | return self.baseui.configitems('paths') | |
27 |
|
27 | |||
28 | @LazyProperty |
|
28 | @LazyProperty | |
29 | def base_path(self): |
|
29 | def base_path(self): | |
30 | if self.baseui: |
|
30 | if self.baseui: | |
31 |
return self.paths[0][1] |
|
31 | return self.paths[0][1] |
@@ -1,329 +1,336 b'' | |||||
1 | from celery.decorators import task |
|
1 | from celery.decorators import task | |
2 |
|
2 | |||
3 | from operator import itemgetter |
|
3 | from operator import itemgetter | |
4 | from pylons.i18n.translation import _ |
|
4 | from pylons.i18n.translation import _ | |
5 | from rhodecode.lib.celerylib import run_task, locked_task |
|
5 | from rhodecode.lib.celerylib import run_task, locked_task | |
6 | from rhodecode.lib.helpers import person |
|
6 | from rhodecode.lib.helpers import person | |
7 | from rhodecode.lib.smtp_mailer import SmtpMailer |
|
7 | from rhodecode.lib.smtp_mailer import SmtpMailer | |
8 | from rhodecode.lib.utils import OrderedDict |
|
8 | from rhodecode.lib.utils import OrderedDict | |
9 | from time import mktime |
|
9 | from time import mktime | |
10 | from vcs.backends.hg import MercurialRepository |
|
10 | from vcs.backends.hg import MercurialRepository | |
|
11 | from vcs.backends.git import GitRepository | |||
|
12 | import os | |||
11 | import traceback |
|
13 | import traceback | |
|
14 | from vcs.backends import get_repo | |||
|
15 | from vcs.utils.helpers import get_scm | |||
12 |
|
16 | |||
13 | try: |
|
17 | try: | |
14 | import json |
|
18 | import json | |
15 | except ImportError: |
|
19 | except ImportError: | |
16 | #python 2.5 compatibility |
|
20 | #python 2.5 compatibility | |
17 | import simplejson as json |
|
21 | import simplejson as json | |
18 |
|
22 | |||
19 | try: |
|
23 | try: | |
20 | from celeryconfig import PYLONS_CONFIG as config |
|
24 | from celeryconfig import PYLONS_CONFIG as config | |
21 | celery_on = True |
|
25 | celery_on = True | |
22 | except ImportError: |
|
26 | except ImportError: | |
23 | #if celeryconfig is not present let's just load our pylons |
|
27 | #if celeryconfig is not present let's just load our pylons | |
24 | #config instead |
|
28 | #config instead | |
25 | from pylons import config |
|
29 | from pylons import config | |
26 | celery_on = False |
|
30 | celery_on = False | |
27 |
|
31 | |||
28 |
|
32 | |||
29 | __all__ = ['whoosh_index', 'get_commits_stats', |
|
33 | __all__ = ['whoosh_index', 'get_commits_stats', | |
30 | 'reset_user_password', 'send_email'] |
|
34 | 'reset_user_password', 'send_email'] | |
31 |
|
35 | |||
32 | def get_session(): |
|
36 | def get_session(): | |
33 | if celery_on: |
|
37 | if celery_on: | |
34 | from sqlalchemy import engine_from_config |
|
38 | from sqlalchemy import engine_from_config | |
35 | from sqlalchemy.orm import sessionmaker, scoped_session |
|
39 | from sqlalchemy.orm import sessionmaker, scoped_session | |
36 | engine = engine_from_config(dict(config.items('app:main')), 'sqlalchemy.db1.') |
|
40 | engine = engine_from_config(dict(config.items('app:main')), 'sqlalchemy.db1.') | |
37 | sa = scoped_session(sessionmaker(bind=engine)) |
|
41 | sa = scoped_session(sessionmaker(bind=engine)) | |
38 | else: |
|
42 | else: | |
39 | #If we don't use celery reuse our current application Session |
|
43 | #If we don't use celery reuse our current application Session | |
40 | from rhodecode.model.meta import Session |
|
44 | from rhodecode.model.meta import Session | |
41 | sa = Session() |
|
45 | sa = Session() | |
42 |
|
46 | |||
43 | return sa |
|
47 | return sa | |
44 |
|
48 | |||
45 | def get_hg_settings(): |
|
49 | def get_hg_settings(): | |
46 | from rhodecode.model.db import RhodeCodeSettings |
|
50 | from rhodecode.model.db import RhodeCodeSettings | |
47 | sa = get_session() |
|
51 | sa = get_session() | |
48 | ret = sa.query(RhodeCodeSettings).all() |
|
52 | ret = sa.query(RhodeCodeSettings).all() | |
49 |
|
53 | |||
50 | if not ret: |
|
54 | if not ret: | |
51 | raise Exception('Could not get application settings !') |
|
55 | raise Exception('Could not get application settings !') | |
52 | settings = {} |
|
56 | settings = {} | |
53 | for each in ret: |
|
57 | for each in ret: | |
54 | settings['rhodecode_' + each.app_settings_name] = each.app_settings_value |
|
58 | settings['rhodecode_' + each.app_settings_name] = each.app_settings_value | |
55 |
|
59 | |||
56 | return settings |
|
60 | return settings | |
57 |
|
61 | |||
58 | def get_hg_ui_settings(): |
|
62 | def get_hg_ui_settings(): | |
59 | from rhodecode.model.db import RhodeCodeUi |
|
63 | from rhodecode.model.db import RhodeCodeUi | |
60 | sa = get_session() |
|
64 | sa = get_session() | |
61 | ret = sa.query(RhodeCodeUi).all() |
|
65 | ret = sa.query(RhodeCodeUi).all() | |
62 |
|
66 | |||
63 | if not ret: |
|
67 | if not ret: | |
64 | raise Exception('Could not get application ui settings !') |
|
68 | raise Exception('Could not get application ui settings !') | |
65 | settings = {} |
|
69 | settings = {} | |
66 | for each in ret: |
|
70 | for each in ret: | |
67 | k = each.ui_key |
|
71 | k = each.ui_key | |
68 | v = each.ui_value |
|
72 | v = each.ui_value | |
69 | if k == '/': |
|
73 | if k == '/': | |
70 | k = 'root_path' |
|
74 | k = 'root_path' | |
71 |
|
75 | |||
72 | if k.find('.') != -1: |
|
76 | if k.find('.') != -1: | |
73 | k = k.replace('.', '_') |
|
77 | k = k.replace('.', '_') | |
74 |
|
78 | |||
75 | if each.ui_section == 'hooks': |
|
79 | if each.ui_section == 'hooks': | |
76 | v = each.ui_active |
|
80 | v = each.ui_active | |
77 |
|
81 | |||
78 | settings[each.ui_section + '_' + k] = v |
|
82 | settings[each.ui_section + '_' + k] = v | |
79 |
|
83 | |||
80 | return settings |
|
84 | return settings | |
81 |
|
85 | |||
82 | @task |
|
86 | @task | |
83 | @locked_task |
|
87 | @locked_task | |
84 | def whoosh_index(repo_location, full_index): |
|
88 | def whoosh_index(repo_location, full_index): | |
85 | log = whoosh_index.get_logger() |
|
89 | log = whoosh_index.get_logger() | |
86 | from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon |
|
90 | from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon | |
87 | WhooshIndexingDaemon(repo_location=repo_location).run(full_index=full_index) |
|
91 | WhooshIndexingDaemon(repo_location=repo_location).run(full_index=full_index) | |
88 |
|
92 | |||
89 | @task |
|
93 | @task | |
90 | @locked_task |
|
94 | @locked_task | |
91 | def get_commits_stats(repo_name, ts_min_y, ts_max_y): |
|
95 | def get_commits_stats(repo_name, ts_min_y, ts_max_y): | |
92 | from rhodecode.model.db import Statistics, Repository |
|
96 | from rhodecode.model.db import Statistics, Repository | |
93 | log = get_commits_stats.get_logger() |
|
97 | log = get_commits_stats.get_logger() | |
94 | author_key_cleaner = lambda k: person(k).replace('"', "") #for js data compatibilty |
|
98 | author_key_cleaner = lambda k: person(k).replace('"', "") #for js data compatibilty | |
95 |
|
99 | |||
96 | commits_by_day_author_aggregate = {} |
|
100 | commits_by_day_author_aggregate = {} | |
97 | commits_by_day_aggregate = {} |
|
101 | commits_by_day_aggregate = {} | |
98 |
repos_path = get_hg_ui_settings()['paths_root_path'] |
|
102 | repos_path = get_hg_ui_settings()['paths_root_path'] | |
99 |
|
|
103 | p = os.path.join(repos_path, repo_name) | |
|
104 | repo = get_repo(get_scm(p)[0], p) | |||
100 |
|
105 | |||
101 | skip_date_limit = True |
|
106 | skip_date_limit = True | |
102 | parse_limit = 250 #limit for single task changeset parsing optimal for |
|
107 | parse_limit = 250 #limit for single task changeset parsing optimal for | |
103 | last_rev = 0 |
|
108 | last_rev = 0 | |
104 | last_cs = None |
|
109 | last_cs = None | |
105 | timegetter = itemgetter('time') |
|
110 | timegetter = itemgetter('time') | |
106 |
|
111 | |||
107 | sa = get_session() |
|
112 | sa = get_session() | |
108 |
|
113 | |||
109 | dbrepo = sa.query(Repository)\ |
|
114 | dbrepo = sa.query(Repository)\ | |
110 | .filter(Repository.repo_name == repo_name).scalar() |
|
115 | .filter(Repository.repo_name == repo_name).scalar() | |
111 | cur_stats = sa.query(Statistics)\ |
|
116 | cur_stats = sa.query(Statistics)\ | |
112 | .filter(Statistics.repository == dbrepo).scalar() |
|
117 | .filter(Statistics.repository == dbrepo).scalar() | |
113 | if cur_stats: |
|
118 | if cur_stats: | |
114 | last_rev = cur_stats.stat_on_revision |
|
119 | last_rev = cur_stats.stat_on_revision | |
115 | if not repo.revisions: |
|
120 | if not repo.revisions: | |
116 | return True |
|
121 | return True | |
117 |
|
122 | |||
118 | if last_rev == repo.revisions[-1] and len(repo.revisions) > 1: |
|
123 | if last_rev == repo.revisions[-1] and len(repo.revisions) > 1: | |
119 | #pass silently without any work if we're not on first revision or current |
|
124 | #pass silently without any work if we're not on first revision or current | |
120 | #state of parsing revision(from db marker) is the last revision |
|
125 | #state of parsing revision(from db marker) is the last revision | |
121 | return True |
|
126 | return True | |
122 |
|
127 | |||
123 | if cur_stats: |
|
128 | if cur_stats: | |
124 | commits_by_day_aggregate = OrderedDict( |
|
129 | commits_by_day_aggregate = OrderedDict( | |
125 | json.loads( |
|
130 | json.loads( | |
126 | cur_stats.commit_activity_combined)) |
|
131 | cur_stats.commit_activity_combined)) | |
127 | commits_by_day_author_aggregate = json.loads(cur_stats.commit_activity) |
|
132 | commits_by_day_author_aggregate = json.loads(cur_stats.commit_activity) | |
128 |
|
133 | |||
129 | log.debug('starting parsing %s', parse_limit) |
|
134 | log.debug('starting parsing %s', parse_limit) | |
130 | lmktime = mktime |
|
135 | lmktime = mktime | |
131 |
|
136 | |||
132 | for cnt, rev in enumerate(repo.revisions[last_rev:]): |
|
137 | for cnt, rev in enumerate(repo.revisions[last_rev:]): | |
133 | last_cs = cs = repo.get_changeset(rev) |
|
138 | last_cs = cs = repo.get_changeset(rev) | |
134 | k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1], |
|
139 | k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1], | |
135 | cs.date.timetuple()[2]) |
|
140 | cs.date.timetuple()[2]) | |
136 | timetupple = [int(x) for x in k.split('-')] |
|
141 | timetupple = [int(x) for x in k.split('-')] | |
137 | timetupple.extend([0 for _ in xrange(6)]) |
|
142 | timetupple.extend([0 for _ in xrange(6)]) | |
138 | k = lmktime(timetupple) |
|
143 | k = lmktime(timetupple) | |
139 | if commits_by_day_author_aggregate.has_key(author_key_cleaner(cs.author)): |
|
144 | if commits_by_day_author_aggregate.has_key(author_key_cleaner(cs.author)): | |
140 | try: |
|
145 | try: | |
141 | l = [timegetter(x) for x in commits_by_day_author_aggregate\ |
|
146 | l = [timegetter(x) for x in commits_by_day_author_aggregate\ | |
142 | [author_key_cleaner(cs.author)]['data']] |
|
147 | [author_key_cleaner(cs.author)]['data']] | |
143 | time_pos = l.index(k) |
|
148 | time_pos = l.index(k) | |
144 | except ValueError: |
|
149 | except ValueError: | |
145 | time_pos = False |
|
150 | time_pos = False | |
146 |
|
151 | |||
147 | if time_pos >= 0 and time_pos is not False: |
|
152 | if time_pos >= 0 and time_pos is not False: | |
148 |
|
153 | |||
149 | datadict = commits_by_day_author_aggregate\ |
|
154 | datadict = commits_by_day_author_aggregate\ | |
150 | [author_key_cleaner(cs.author)]['data'][time_pos] |
|
155 | [author_key_cleaner(cs.author)]['data'][time_pos] | |
151 |
|
156 | |||
152 | datadict["commits"] += 1 |
|
157 | datadict["commits"] += 1 | |
153 | datadict["added"] += len(cs.added) |
|
158 | datadict["added"] += len(cs.added) | |
154 | datadict["changed"] += len(cs.changed) |
|
159 | datadict["changed"] += len(cs.changed) | |
155 | datadict["removed"] += len(cs.removed) |
|
160 | datadict["removed"] += len(cs.removed) | |
156 |
|
161 | |||
157 | else: |
|
162 | else: | |
158 | if k >= ts_min_y and k <= ts_max_y or skip_date_limit: |
|
163 | if k >= ts_min_y and k <= ts_max_y or skip_date_limit: | |
159 |
|
164 | |||
160 | datadict = {"time":k, |
|
165 | datadict = {"time":k, | |
161 | "commits":1, |
|
166 | "commits":1, | |
162 | "added":len(cs.added), |
|
167 | "added":len(cs.added), | |
163 | "changed":len(cs.changed), |
|
168 | "changed":len(cs.changed), | |
164 | "removed":len(cs.removed), |
|
169 | "removed":len(cs.removed), | |
165 | } |
|
170 | } | |
166 | commits_by_day_author_aggregate\ |
|
171 | commits_by_day_author_aggregate\ | |
167 | [author_key_cleaner(cs.author)]['data'].append(datadict) |
|
172 | [author_key_cleaner(cs.author)]['data'].append(datadict) | |
168 |
|
173 | |||
169 | else: |
|
174 | else: | |
170 | if k >= ts_min_y and k <= ts_max_y or skip_date_limit: |
|
175 | if k >= ts_min_y and k <= ts_max_y or skip_date_limit: | |
171 | commits_by_day_author_aggregate[author_key_cleaner(cs.author)] = { |
|
176 | commits_by_day_author_aggregate[author_key_cleaner(cs.author)] = { | |
172 | "label":author_key_cleaner(cs.author), |
|
177 | "label":author_key_cleaner(cs.author), | |
173 | "data":[{"time":k, |
|
178 | "data":[{"time":k, | |
174 | "commits":1, |
|
179 | "commits":1, | |
175 | "added":len(cs.added), |
|
180 | "added":len(cs.added), | |
176 | "changed":len(cs.changed), |
|
181 | "changed":len(cs.changed), | |
177 | "removed":len(cs.removed), |
|
182 | "removed":len(cs.removed), | |
178 | }], |
|
183 | }], | |
179 | "schema":["commits"], |
|
184 | "schema":["commits"], | |
180 | } |
|
185 | } | |
181 |
|
186 | |||
182 | #gather all data by day |
|
187 | #gather all data by day | |
183 | if commits_by_day_aggregate.has_key(k): |
|
188 | if commits_by_day_aggregate.has_key(k): | |
184 | commits_by_day_aggregate[k] += 1 |
|
189 | commits_by_day_aggregate[k] += 1 | |
185 | else: |
|
190 | else: | |
186 | commits_by_day_aggregate[k] = 1 |
|
191 | commits_by_day_aggregate[k] = 1 | |
187 |
|
192 | |||
188 | if cnt >= parse_limit: |
|
193 | if cnt >= parse_limit: | |
189 | #don't fetch to much data since we can freeze application |
|
194 | #don't fetch to much data since we can freeze application | |
190 | break |
|
195 | break | |
191 | overview_data = [] |
|
196 | overview_data = [] | |
192 | for k, v in commits_by_day_aggregate.items(): |
|
197 | for k, v in commits_by_day_aggregate.items(): | |
193 | overview_data.append([k, v]) |
|
198 | overview_data.append([k, v]) | |
194 | overview_data = sorted(overview_data, key=itemgetter(0)) |
|
199 | overview_data = sorted(overview_data, key=itemgetter(0)) | |
195 | if not commits_by_day_author_aggregate: |
|
200 | if not commits_by_day_author_aggregate: | |
196 | commits_by_day_author_aggregate[author_key_cleaner(repo.contact)] = { |
|
201 | commits_by_day_author_aggregate[author_key_cleaner(repo.contact)] = { | |
197 | "label":author_key_cleaner(repo.contact), |
|
202 | "label":author_key_cleaner(repo.contact), | |
198 | "data":[0, 1], |
|
203 | "data":[0, 1], | |
199 | "schema":["commits"], |
|
204 | "schema":["commits"], | |
200 | } |
|
205 | } | |
201 |
|
206 | |||
202 | stats = cur_stats if cur_stats else Statistics() |
|
207 | stats = cur_stats if cur_stats else Statistics() | |
203 | stats.commit_activity = json.dumps(commits_by_day_author_aggregate) |
|
208 | stats.commit_activity = json.dumps(commits_by_day_author_aggregate) | |
204 | stats.commit_activity_combined = json.dumps(overview_data) |
|
209 | stats.commit_activity_combined = json.dumps(overview_data) | |
205 |
|
210 | |||
206 | log.debug('last revison %s', last_rev) |
|
211 | log.debug('last revison %s', last_rev) | |
207 | leftovers = len(repo.revisions[last_rev:]) |
|
212 | leftovers = len(repo.revisions[last_rev:]) | |
208 | log.debug('revisions to parse %s', leftovers) |
|
213 | log.debug('revisions to parse %s', leftovers) | |
209 |
|
214 | |||
210 | if last_rev == 0 or leftovers < parse_limit: |
|
215 | if last_rev == 0 or leftovers < parse_limit: | |
211 | stats.languages = json.dumps(__get_codes_stats(repo_name)) |
|
216 | stats.languages = json.dumps(__get_codes_stats(repo_name)) | |
212 |
|
217 | |||
213 | stats.repository = dbrepo |
|
218 | stats.repository = dbrepo | |
214 | stats.stat_on_revision = last_cs.revision |
|
219 | stats.stat_on_revision = last_cs.revision | |
215 |
|
220 | |||
216 | try: |
|
221 | try: | |
217 | sa.add(stats) |
|
222 | sa.add(stats) | |
218 | sa.commit() |
|
223 | sa.commit() | |
219 | except: |
|
224 | except: | |
220 | log.error(traceback.format_exc()) |
|
225 | log.error(traceback.format_exc()) | |
221 | sa.rollback() |
|
226 | sa.rollback() | |
222 | return False |
|
227 | return False | |
223 | if len(repo.revisions) > 1: |
|
228 | if len(repo.revisions) > 1: | |
224 | run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y) |
|
229 | run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y) | |
225 |
|
230 | |||
226 | return True |
|
231 | return True | |
227 |
|
232 | |||
228 | @task |
|
233 | @task | |
229 | def reset_user_password(user_email): |
|
234 | def reset_user_password(user_email): | |
230 | log = reset_user_password.get_logger() |
|
235 | log = reset_user_password.get_logger() | |
231 | from rhodecode.lib import auth |
|
236 | from rhodecode.lib import auth | |
232 | from rhodecode.model.db import User |
|
237 | from rhodecode.model.db import User | |
233 |
|
238 | |||
234 | try: |
|
239 | try: | |
235 | try: |
|
240 | try: | |
236 | sa = get_session() |
|
241 | sa = get_session() | |
237 | user = sa.query(User).filter(User.email == user_email).scalar() |
|
242 | user = sa.query(User).filter(User.email == user_email).scalar() | |
238 | new_passwd = auth.PasswordGenerator().gen_password(8, |
|
243 | new_passwd = auth.PasswordGenerator().gen_password(8, | |
239 | auth.PasswordGenerator.ALPHABETS_BIG_SMALL) |
|
244 | auth.PasswordGenerator.ALPHABETS_BIG_SMALL) | |
240 | if user: |
|
245 | if user: | |
241 | user.password = auth.get_crypt_password(new_passwd) |
|
246 | user.password = auth.get_crypt_password(new_passwd) | |
242 | sa.add(user) |
|
247 | sa.add(user) | |
243 | sa.commit() |
|
248 | sa.commit() | |
244 | log.info('change password for %s', user_email) |
|
249 | log.info('change password for %s', user_email) | |
245 | if new_passwd is None: |
|
250 | if new_passwd is None: | |
246 | raise Exception('unable to generate new password') |
|
251 | raise Exception('unable to generate new password') | |
247 |
|
252 | |||
248 | except: |
|
253 | except: | |
249 | log.error(traceback.format_exc()) |
|
254 | log.error(traceback.format_exc()) | |
250 | sa.rollback() |
|
255 | sa.rollback() | |
251 |
|
256 | |||
252 | run_task(send_email, user_email, |
|
257 | run_task(send_email, user_email, | |
253 | "Your new rhodecode password", |
|
258 | "Your new rhodecode password", | |
254 | 'Your new rhodecode password:%s' % (new_passwd)) |
|
259 | 'Your new rhodecode password:%s' % (new_passwd)) | |
255 | log.info('send new password mail to %s', user_email) |
|
260 | log.info('send new password mail to %s', user_email) | |
256 |
|
261 | |||
257 |
|
262 | |||
258 | except: |
|
263 | except: | |
259 | log.error('Failed to update user password') |
|
264 | log.error('Failed to update user password') | |
260 | log.error(traceback.format_exc()) |
|
265 | log.error(traceback.format_exc()) | |
261 | return True |
|
266 | return True | |
262 |
|
267 | |||
263 | @task |
|
268 | @task | |
264 | def send_email(recipients, subject, body): |
|
269 | def send_email(recipients, subject, body): | |
265 | log = send_email.get_logger() |
|
270 | log = send_email.get_logger() | |
266 | email_config = dict(config.items('DEFAULT')) |
|
271 | email_config = dict(config.items('DEFAULT')) | |
267 | mail_from = email_config.get('app_email_from') |
|
272 | mail_from = email_config.get('app_email_from') | |
268 | user = email_config.get('smtp_username') |
|
273 | user = email_config.get('smtp_username') | |
269 | passwd = email_config.get('smtp_password') |
|
274 | passwd = email_config.get('smtp_password') | |
270 | mail_server = email_config.get('smtp_server') |
|
275 | mail_server = email_config.get('smtp_server') | |
271 | mail_port = email_config.get('smtp_port') |
|
276 | mail_port = email_config.get('smtp_port') | |
272 | tls = email_config.get('smtp_use_tls') |
|
277 | tls = email_config.get('smtp_use_tls') | |
273 | ssl = False |
|
278 | ssl = False | |
274 |
|
279 | |||
275 | try: |
|
280 | try: | |
276 | m = SmtpMailer(mail_from, user, passwd, mail_server, |
|
281 | m = SmtpMailer(mail_from, user, passwd, mail_server, | |
277 | mail_port, ssl, tls) |
|
282 | mail_port, ssl, tls) | |
278 | m.send(recipients, subject, body) |
|
283 | m.send(recipients, subject, body) | |
279 | except: |
|
284 | except: | |
280 | log.error('Mail sending failed') |
|
285 | log.error('Mail sending failed') | |
281 | log.error(traceback.format_exc()) |
|
286 | log.error(traceback.format_exc()) | |
282 | return False |
|
287 | return False | |
283 | return True |
|
288 | return True | |
284 |
|
289 | |||
285 | @task |
|
290 | @task | |
286 | def create_repo_fork(form_data, cur_user): |
|
291 | def create_repo_fork(form_data, cur_user): | |
287 | import os |
|
292 | import os | |
288 | from rhodecode.model.repo import RepoModel |
|
293 | from rhodecode.model.repo import RepoModel | |
289 |
|
294 | |||
290 | repo_model = RepoModel(get_session()) |
|
295 | repo_model = RepoModel(get_session()) | |
291 | repo_model.create(form_data, cur_user, just_db=True, fork=True) |
|
296 | repo_model.create(form_data, cur_user, just_db=True, fork=True) | |
292 |
|
297 | |||
293 | repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '') |
|
298 | repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '') | |
294 | repo_path = os.path.join(repos_path, form_data['repo_name']) |
|
299 | repo_path = os.path.join(repos_path, form_data['repo_name']) | |
295 | repo_fork_path = os.path.join(repos_path, form_data['fork_name']) |
|
300 | repo_fork_path = os.path.join(repos_path, form_data['fork_name']) | |
296 |
|
301 | |||
297 | MercurialRepository(str(repo_fork_path), True, clone_url=str(repo_path)) |
|
302 | MercurialRepository(str(repo_fork_path), True, clone_url=str(repo_path)) | |
298 |
|
303 | |||
299 |
|
304 | |||
300 | def __get_codes_stats(repo_name): |
|
305 | def __get_codes_stats(repo_name): | |
301 | LANGUAGES_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', |
|
306 | LANGUAGES_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', | |
302 | 'aspx', 'asx', 'axd', 'c', 'cfg', 'cfm', 'cpp', 'cs', 'diff', 'do', 'el', |
|
307 | 'aspx', 'asx', 'axd', 'c', 'cfg', 'cfm', 'cpp', 'cs', 'diff', 'do', 'el', | |
303 | 'erl', 'h', 'java', 'js', 'jsp', 'jspx', 'lisp', 'lua', 'm', 'mako', 'ml', |
|
308 | 'erl', 'h', 'java', 'js', 'jsp', 'jspx', 'lisp', 'lua', 'm', 'mako', 'ml', | |
304 | 'pas', 'patch', 'php', 'php3', 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', |
|
309 | 'pas', 'patch', 'php', 'php3', 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', | |
305 | 's', 'sh', 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt', 'yaws'] |
|
310 | 's', 'sh', 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt', 'yaws'] | |
306 |
|
311 | |||
307 |
|
312 | |||
308 |
repos_path = get_hg_ui_settings()['paths_root_path'] |
|
313 | repos_path = get_hg_ui_settings()['paths_root_path'] | |
309 |
|
|
314 | p = os.path.join(repos_path, repo_name) | |
|
315 | repo = get_repo(get_scm(p)[0], p) | |||
|
316 | ||||
310 | tip = repo.get_changeset() |
|
317 | tip = repo.get_changeset() | |
311 |
|
318 | |||
312 | code_stats = {} |
|
319 | code_stats = {} | |
313 |
|
320 | |||
314 | def aggregate(cs): |
|
321 | def aggregate(cs): | |
315 | for f in cs[2]: |
|
322 | for f in cs[2]: | |
316 | k = f.mimetype |
|
323 | k = f.mimetype | |
317 | if f.extension in LANGUAGES_EXTENSIONS: |
|
324 | if f.extension in LANGUAGES_EXTENSIONS: | |
318 | if code_stats.has_key(k): |
|
325 | if code_stats.has_key(k): | |
319 | code_stats[k] += 1 |
|
326 | code_stats[k] += 1 | |
320 | else: |
|
327 | else: | |
321 | code_stats[k] = 1 |
|
328 | code_stats[k] = 1 | |
322 |
|
329 | |||
323 | map(aggregate, tip.walk('/')) |
|
330 | map(aggregate, tip.walk('/')) | |
324 |
|
331 | |||
325 | return code_stats or {} |
|
332 | return code_stats or {} | |
326 |
|
333 | |||
327 |
|
334 | |||
328 |
|
335 | |||
329 |
|
336 |
@@ -1,287 +1,287 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | # encoding: utf-8 |
|
2 | # encoding: utf-8 | |
3 | # database management for RhodeCode |
|
3 | # database management for RhodeCode | |
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> | |
5 | # |
|
5 | # | |
6 | # This program is free software; you can redistribute it and/or |
|
6 | # This program is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU General Public License |
|
7 | # modify it under the terms of the GNU General Public License | |
8 | # as published by the Free Software Foundation; version 2 |
|
8 | # as published by the Free Software Foundation; version 2 | |
9 | # of the License or (at your opinion) any later version of the license. |
|
9 | # of the License or (at your opinion) any later version of the license. | |
10 | # |
|
10 | # | |
11 | # This program is distributed in the hope that it will be useful, |
|
11 | # This program is distributed in the hope that it will be useful, | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | # GNU General Public License for more details. |
|
14 | # GNU General Public License for more details. | |
15 | # |
|
15 | # | |
16 | # You should have received a copy of the GNU General Public License |
|
16 | # You should have received a copy of the GNU General Public License | |
17 | # along with this program; if not, write to the Free Software |
|
17 | # along with this program; if not, write to the Free Software | |
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
19 | # MA 02110-1301, USA. |
|
19 | # MA 02110-1301, USA. | |
20 |
|
20 | |||
21 | """ |
|
21 | """ | |
22 | Created on April 10, 2010 |
|
22 | Created on April 10, 2010 | |
23 | database management and creation for RhodeCode |
|
23 | database management and creation for RhodeCode | |
24 | @author: marcink |
|
24 | @author: marcink | |
25 | """ |
|
25 | """ | |
26 |
|
26 | |||
27 | from os.path import dirname as dn, join as jn |
|
27 | from os.path import dirname as dn, join as jn | |
28 | import os |
|
28 | import os | |
29 | import sys |
|
29 | import sys | |
30 | import uuid |
|
30 | import uuid | |
31 |
|
31 | |||
32 | from rhodecode.lib.auth import get_crypt_password |
|
32 | from rhodecode.lib.auth import get_crypt_password | |
33 | from rhodecode.lib.utils import ask_ok |
|
33 | from rhodecode.lib.utils import ask_ok | |
34 | from rhodecode.model import init_model |
|
34 | from rhodecode.model import init_model | |
35 | from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \ |
|
35 | from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \ | |
36 | UserToPerm |
|
36 | UserToPerm | |
37 | from rhodecode.model import meta |
|
37 | from rhodecode.model import meta | |
38 | from sqlalchemy.engine import create_engine |
|
38 | from sqlalchemy.engine import create_engine | |
39 | import logging |
|
39 | import logging | |
40 |
|
40 | |||
41 | log = logging.getLogger(__name__) |
|
41 | log = logging.getLogger(__name__) | |
42 |
|
42 | |||
43 | class DbManage(object): |
|
43 | class DbManage(object): | |
44 | def __init__(self, log_sql, dbname, root, tests=False): |
|
44 | def __init__(self, log_sql, dbname, root, tests=False): | |
45 | self.dbname = dbname |
|
45 | self.dbname = dbname | |
46 | self.tests = tests |
|
46 | self.tests = tests | |
47 | self.root = root |
|
47 | self.root = root | |
48 | dburi = 'sqlite:////%s' % jn(self.root, self.dbname) |
|
48 | dburi = 'sqlite:////%s' % jn(self.root, self.dbname) | |
49 | engine = create_engine(dburi, echo=log_sql) |
|
49 | engine = create_engine(dburi, echo=log_sql) | |
50 | init_model(engine) |
|
50 | init_model(engine) | |
51 | self.sa = meta.Session() |
|
51 | self.sa = meta.Session() | |
52 | self.db_exists = False |
|
52 | self.db_exists = False | |
53 |
|
53 | |||
54 | def check_for_db(self, override): |
|
54 | def check_for_db(self, override): | |
55 | db_path = jn(self.root, self.dbname) |
|
55 | db_path = jn(self.root, self.dbname) | |
56 | log.info('checking for existing db in %s', db_path) |
|
56 | log.info('checking for existing db in %s', db_path) | |
57 | if os.path.isfile(db_path): |
|
57 | if os.path.isfile(db_path): | |
58 | self.db_exists = True |
|
58 | self.db_exists = True | |
59 | if not override: |
|
59 | if not override: | |
60 | raise Exception('database already exists') |
|
60 | raise Exception('database already exists') | |
61 |
|
61 | |||
62 | def create_tables(self, override=False): |
|
62 | def create_tables(self, override=False): | |
63 | """ |
|
63 | """ | |
64 | Create a auth database |
|
64 | Create a auth database | |
65 | """ |
|
65 | """ | |
66 | self.check_for_db(override) |
|
66 | self.check_for_db(override) | |
67 | if self.db_exists: |
|
67 | if self.db_exists: | |
68 | log.info("database exist and it's going to be destroyed") |
|
68 | log.info("database exist and it's going to be destroyed") | |
69 | if self.tests: |
|
69 | if self.tests: | |
70 | destroy = True |
|
70 | destroy = True | |
71 | else: |
|
71 | else: | |
72 | destroy = ask_ok('Are you sure to destroy old database ? [y/n]') |
|
72 | destroy = ask_ok('Are you sure to destroy old database ? [y/n]') | |
73 | if not destroy: |
|
73 | if not destroy: | |
74 | sys.exit() |
|
74 | sys.exit() | |
75 | if self.db_exists and destroy: |
|
75 | if self.db_exists and destroy: | |
76 | os.remove(jn(self.root, self.dbname)) |
|
76 | os.remove(jn(self.root, self.dbname)) | |
77 | checkfirst = not override |
|
77 | checkfirst = not override | |
78 | meta.Base.metadata.create_all(checkfirst=checkfirst) |
|
78 | meta.Base.metadata.create_all(checkfirst=checkfirst) | |
79 | log.info('Created tables for %s', self.dbname) |
|
79 | log.info('Created tables for %s', self.dbname) | |
80 |
|
80 | |||
81 | def admin_prompt(self, second=False): |
|
81 | def admin_prompt(self, second=False): | |
82 | if not self.tests: |
|
82 | if not self.tests: | |
83 | import getpass |
|
83 | import getpass | |
84 |
|
84 | |||
85 |
|
85 | |||
86 | def get_password(): |
|
86 | def get_password(): | |
87 | password = getpass.getpass('Specify admin password (min 6 chars):') |
|
87 | password = getpass.getpass('Specify admin password (min 6 chars):') | |
88 | confirm = getpass.getpass('Confirm password:') |
|
88 | confirm = getpass.getpass('Confirm password:') | |
89 |
|
89 | |||
90 | if password != confirm: |
|
90 | if password != confirm: | |
91 | log.error('passwords mismatch') |
|
91 | log.error('passwords mismatch') | |
92 | return False |
|
92 | return False | |
93 | if len(password) < 6: |
|
93 | if len(password) < 6: | |
94 | log.error('password is to short use at least 6 characters') |
|
94 | log.error('password is to short use at least 6 characters') | |
95 | return False |
|
95 | return False | |
96 |
|
96 | |||
97 | return password |
|
97 | return password | |
98 |
|
98 | |||
99 | username = raw_input('Specify admin username:') |
|
99 | username = raw_input('Specify admin username:') | |
100 |
|
100 | |||
101 | password = get_password() |
|
101 | password = get_password() | |
102 | if not password: |
|
102 | if not password: | |
103 | #second try |
|
103 | #second try | |
104 | password = get_password() |
|
104 | password = get_password() | |
105 | if not password: |
|
105 | if not password: | |
106 | sys.exit() |
|
106 | sys.exit() | |
107 |
|
107 | |||
108 | email = raw_input('Specify admin email:') |
|
108 | email = raw_input('Specify admin email:') | |
109 | self.create_user(username, password, email, True) |
|
109 | self.create_user(username, password, email, True) | |
110 | else: |
|
110 | else: | |
111 | log.info('creating admin and regular test users') |
|
111 | log.info('creating admin and regular test users') | |
112 | self.create_user('test_admin', 'test12', 'test_admin@mail.com', True) |
|
112 | self.create_user('test_admin', 'test12', 'test_admin@mail.com', True) | |
113 | self.create_user('test_regular', 'test12', 'test_regular@mail.com', False) |
|
113 | self.create_user('test_regular', 'test12', 'test_regular@mail.com', False) | |
114 | self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False) |
|
114 | self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False) | |
115 |
|
115 | |||
116 |
|
116 | |||
117 |
|
117 | |||
118 | def config_prompt(self, test_repo_path=''): |
|
118 | def config_prompt(self, test_repo_path=''): | |
119 | log.info('Setting up repositories config') |
|
119 | log.info('Setting up repositories config') | |
120 |
|
120 | |||
121 | if not self.tests and not test_repo_path: |
|
121 | if not self.tests and not test_repo_path: | |
122 | path = raw_input('Specify valid full path to your repositories' |
|
122 | path = raw_input('Specify valid full path to your repositories' | |
123 | ' you can change this later in application settings:') |
|
123 | ' you can change this later in application settings:') | |
124 | else: |
|
124 | else: | |
125 | path = test_repo_path |
|
125 | path = test_repo_path | |
126 |
|
126 | |||
127 | if not os.path.isdir(path): |
|
127 | if not os.path.isdir(path): | |
128 | log.error('You entered wrong path: %s', path) |
|
128 | log.error('You entered wrong path: %s', path) | |
129 | sys.exit() |
|
129 | sys.exit() | |
130 |
|
130 | |||
131 | hooks1 = RhodeCodeUi() |
|
131 | hooks1 = RhodeCodeUi() | |
132 | hooks1.ui_section = 'hooks' |
|
132 | hooks1.ui_section = 'hooks' | |
133 | hooks1.ui_key = 'changegroup.update' |
|
133 | hooks1.ui_key = 'changegroup.update' | |
134 | hooks1.ui_value = 'hg update >&2' |
|
134 | hooks1.ui_value = 'hg update >&2' | |
135 | hooks1.ui_active = False |
|
135 | hooks1.ui_active = False | |
136 |
|
136 | |||
137 | hooks2 = RhodeCodeUi() |
|
137 | hooks2 = RhodeCodeUi() | |
138 | hooks2.ui_section = 'hooks' |
|
138 | hooks2.ui_section = 'hooks' | |
139 | hooks2.ui_key = 'changegroup.repo_size' |
|
139 | hooks2.ui_key = 'changegroup.repo_size' | |
140 | hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size' |
|
140 | hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size' | |
141 |
|
141 | |||
142 | web1 = RhodeCodeUi() |
|
142 | web1 = RhodeCodeUi() | |
143 | web1.ui_section = 'web' |
|
143 | web1.ui_section = 'web' | |
144 | web1.ui_key = 'push_ssl' |
|
144 | web1.ui_key = 'push_ssl' | |
145 | web1.ui_value = 'false' |
|
145 | web1.ui_value = 'false' | |
146 |
|
146 | |||
147 | web2 = RhodeCodeUi() |
|
147 | web2 = RhodeCodeUi() | |
148 | web2.ui_section = 'web' |
|
148 | web2.ui_section = 'web' | |
149 | web2.ui_key = 'allow_archive' |
|
149 | web2.ui_key = 'allow_archive' | |
150 | web2.ui_value = 'gz zip bz2' |
|
150 | web2.ui_value = 'gz zip bz2' | |
151 |
|
151 | |||
152 | web3 = RhodeCodeUi() |
|
152 | web3 = RhodeCodeUi() | |
153 | web3.ui_section = 'web' |
|
153 | web3.ui_section = 'web' | |
154 | web3.ui_key = 'allow_push' |
|
154 | web3.ui_key = 'allow_push' | |
155 | web3.ui_value = '*' |
|
155 | web3.ui_value = '*' | |
156 |
|
156 | |||
157 | web4 = RhodeCodeUi() |
|
157 | web4 = RhodeCodeUi() | |
158 | web4.ui_section = 'web' |
|
158 | web4.ui_section = 'web' | |
159 | web4.ui_key = 'baseurl' |
|
159 | web4.ui_key = 'baseurl' | |
160 | web4.ui_value = '/' |
|
160 | web4.ui_value = '/' | |
161 |
|
161 | |||
162 | paths = RhodeCodeUi() |
|
162 | paths = RhodeCodeUi() | |
163 | paths.ui_section = 'paths' |
|
163 | paths.ui_section = 'paths' | |
164 | paths.ui_key = '/' |
|
164 | paths.ui_key = '/' | |
165 |
paths.ui_value = |
|
165 | paths.ui_value = path | |
166 |
|
166 | |||
167 |
|
167 | |||
168 | hgsettings1 = RhodeCodeSettings() |
|
168 | hgsettings1 = RhodeCodeSettings() | |
169 |
|
169 | |||
170 | hgsettings1.app_settings_name = 'realm' |
|
170 | hgsettings1.app_settings_name = 'realm' | |
171 | hgsettings1.app_settings_value = 'RhodeCode authentication' |
|
171 | hgsettings1.app_settings_value = 'RhodeCode authentication' | |
172 |
|
172 | |||
173 | hgsettings2 = RhodeCodeSettings() |
|
173 | hgsettings2 = RhodeCodeSettings() | |
174 | hgsettings2.app_settings_name = 'title' |
|
174 | hgsettings2.app_settings_name = 'title' | |
175 | hgsettings2.app_settings_value = 'RhodeCode' |
|
175 | hgsettings2.app_settings_value = 'RhodeCode' | |
176 |
|
176 | |||
177 | try: |
|
177 | try: | |
178 | self.sa.add(hooks1) |
|
178 | self.sa.add(hooks1) | |
179 | self.sa.add(hooks2) |
|
179 | self.sa.add(hooks2) | |
180 | self.sa.add(web1) |
|
180 | self.sa.add(web1) | |
181 | self.sa.add(web2) |
|
181 | self.sa.add(web2) | |
182 | self.sa.add(web3) |
|
182 | self.sa.add(web3) | |
183 | self.sa.add(web4) |
|
183 | self.sa.add(web4) | |
184 | self.sa.add(paths) |
|
184 | self.sa.add(paths) | |
185 | self.sa.add(hgsettings1) |
|
185 | self.sa.add(hgsettings1) | |
186 | self.sa.add(hgsettings2) |
|
186 | self.sa.add(hgsettings2) | |
187 | self.sa.commit() |
|
187 | self.sa.commit() | |
188 | except: |
|
188 | except: | |
189 | self.sa.rollback() |
|
189 | self.sa.rollback() | |
190 | raise |
|
190 | raise | |
191 | log.info('created ui config') |
|
191 | log.info('created ui config') | |
192 |
|
192 | |||
193 | def create_user(self, username, password, email='', admin=False): |
|
193 | def create_user(self, username, password, email='', admin=False): | |
194 | log.info('creating administrator user %s', username) |
|
194 | log.info('creating administrator user %s', username) | |
195 | new_user = User() |
|
195 | new_user = User() | |
196 | new_user.username = username |
|
196 | new_user.username = username | |
197 | new_user.password = get_crypt_password(password) |
|
197 | new_user.password = get_crypt_password(password) | |
198 | new_user.name = 'RhodeCode' |
|
198 | new_user.name = 'RhodeCode' | |
199 | new_user.lastname = 'Admin' |
|
199 | new_user.lastname = 'Admin' | |
200 | new_user.email = email |
|
200 | new_user.email = email | |
201 | new_user.admin = admin |
|
201 | new_user.admin = admin | |
202 | new_user.active = True |
|
202 | new_user.active = True | |
203 |
|
203 | |||
204 | try: |
|
204 | try: | |
205 | self.sa.add(new_user) |
|
205 | self.sa.add(new_user) | |
206 | self.sa.commit() |
|
206 | self.sa.commit() | |
207 | except: |
|
207 | except: | |
208 | self.sa.rollback() |
|
208 | self.sa.rollback() | |
209 | raise |
|
209 | raise | |
210 |
|
210 | |||
211 | def create_default_user(self): |
|
211 | def create_default_user(self): | |
212 | log.info('creating default user') |
|
212 | log.info('creating default user') | |
213 | #create default user for handling default permissions. |
|
213 | #create default user for handling default permissions. | |
214 | def_user = User() |
|
214 | def_user = User() | |
215 | def_user.username = 'default' |
|
215 | def_user.username = 'default' | |
216 | def_user.password = get_crypt_password(str(uuid.uuid1())[:8]) |
|
216 | def_user.password = get_crypt_password(str(uuid.uuid1())[:8]) | |
217 | def_user.name = 'default' |
|
217 | def_user.name = 'default' | |
218 | def_user.lastname = 'default' |
|
218 | def_user.lastname = 'default' | |
219 | def_user.email = 'default@default.com' |
|
219 | def_user.email = 'default@default.com' | |
220 | def_user.admin = False |
|
220 | def_user.admin = False | |
221 | def_user.active = False |
|
221 | def_user.active = False | |
222 | try: |
|
222 | try: | |
223 | self.sa.add(def_user) |
|
223 | self.sa.add(def_user) | |
224 | self.sa.commit() |
|
224 | self.sa.commit() | |
225 | except: |
|
225 | except: | |
226 | self.sa.rollback() |
|
226 | self.sa.rollback() | |
227 | raise |
|
227 | raise | |
228 |
|
228 | |||
229 | def create_permissions(self): |
|
229 | def create_permissions(self): | |
230 | #module.(access|create|change|delete)_[name] |
|
230 | #module.(access|create|change|delete)_[name] | |
231 | #module.(read|write|owner) |
|
231 | #module.(read|write|owner) | |
232 | perms = [('repository.none', 'Repository no access'), |
|
232 | perms = [('repository.none', 'Repository no access'), | |
233 | ('repository.read', 'Repository read access'), |
|
233 | ('repository.read', 'Repository read access'), | |
234 | ('repository.write', 'Repository write access'), |
|
234 | ('repository.write', 'Repository write access'), | |
235 | ('repository.admin', 'Repository admin access'), |
|
235 | ('repository.admin', 'Repository admin access'), | |
236 | ('hg.admin', 'Hg Administrator'), |
|
236 | ('hg.admin', 'Hg Administrator'), | |
237 | ('hg.create.repository', 'Repository create'), |
|
237 | ('hg.create.repository', 'Repository create'), | |
238 | ('hg.create.none', 'Repository creation disabled'), |
|
238 | ('hg.create.none', 'Repository creation disabled'), | |
239 | ('hg.register.none', 'Register disabled'), |
|
239 | ('hg.register.none', 'Register disabled'), | |
240 | ('hg.register.manual_activate', 'Register new user with rhodecode without manual activation'), |
|
240 | ('hg.register.manual_activate', 'Register new user with rhodecode without manual activation'), | |
241 | ('hg.register.auto_activate', 'Register new user with rhodecode without auto activation'), |
|
241 | ('hg.register.auto_activate', 'Register new user with rhodecode without auto activation'), | |
242 | ] |
|
242 | ] | |
243 |
|
243 | |||
244 | for p in perms: |
|
244 | for p in perms: | |
245 | new_perm = Permission() |
|
245 | new_perm = Permission() | |
246 | new_perm.permission_name = p[0] |
|
246 | new_perm.permission_name = p[0] | |
247 | new_perm.permission_longname = p[1] |
|
247 | new_perm.permission_longname = p[1] | |
248 | try: |
|
248 | try: | |
249 | self.sa.add(new_perm) |
|
249 | self.sa.add(new_perm) | |
250 | self.sa.commit() |
|
250 | self.sa.commit() | |
251 | except: |
|
251 | except: | |
252 | self.sa.rollback() |
|
252 | self.sa.rollback() | |
253 | raise |
|
253 | raise | |
254 |
|
254 | |||
255 | def populate_default_permissions(self): |
|
255 | def populate_default_permissions(self): | |
256 | log.info('creating default user permissions') |
|
256 | log.info('creating default user permissions') | |
257 |
|
257 | |||
258 | default_user = self.sa.query(User)\ |
|
258 | default_user = self.sa.query(User)\ | |
259 | .filter(User.username == 'default').scalar() |
|
259 | .filter(User.username == 'default').scalar() | |
260 |
|
260 | |||
261 | reg_perm = UserToPerm() |
|
261 | reg_perm = UserToPerm() | |
262 | reg_perm.user = default_user |
|
262 | reg_perm.user = default_user | |
263 | reg_perm.permission = self.sa.query(Permission)\ |
|
263 | reg_perm.permission = self.sa.query(Permission)\ | |
264 | .filter(Permission.permission_name == 'hg.register.manual_activate')\ |
|
264 | .filter(Permission.permission_name == 'hg.register.manual_activate')\ | |
265 | .scalar() |
|
265 | .scalar() | |
266 |
|
266 | |||
267 | create_repo_perm = UserToPerm() |
|
267 | create_repo_perm = UserToPerm() | |
268 | create_repo_perm.user = default_user |
|
268 | create_repo_perm.user = default_user | |
269 | create_repo_perm.permission = self.sa.query(Permission)\ |
|
269 | create_repo_perm.permission = self.sa.query(Permission)\ | |
270 | .filter(Permission.permission_name == 'hg.create.repository')\ |
|
270 | .filter(Permission.permission_name == 'hg.create.repository')\ | |
271 | .scalar() |
|
271 | .scalar() | |
272 |
|
272 | |||
273 | default_repo_perm = UserToPerm() |
|
273 | default_repo_perm = UserToPerm() | |
274 | default_repo_perm.user = default_user |
|
274 | default_repo_perm.user = default_user | |
275 | default_repo_perm.permission = self.sa.query(Permission)\ |
|
275 | default_repo_perm.permission = self.sa.query(Permission)\ | |
276 | .filter(Permission.permission_name == 'repository.read')\ |
|
276 | .filter(Permission.permission_name == 'repository.read')\ | |
277 | .scalar() |
|
277 | .scalar() | |
278 |
|
278 | |||
279 | try: |
|
279 | try: | |
280 | self.sa.add(reg_perm) |
|
280 | self.sa.add(reg_perm) | |
281 | self.sa.add(create_repo_perm) |
|
281 | self.sa.add(create_repo_perm) | |
282 | self.sa.add(default_repo_perm) |
|
282 | self.sa.add(default_repo_perm) | |
283 | self.sa.commit() |
|
283 | self.sa.commit() | |
284 | except: |
|
284 | except: | |
285 | self.sa.rollback() |
|
285 | self.sa.rollback() | |
286 | raise |
|
286 | raise | |
287 |
|
287 |
@@ -1,383 +1,383 b'' | |||||
1 | """Helper functions |
|
1 | """Helper functions | |
2 |
|
2 | |||
3 | Consists of functions to typically be used within templates, but also |
|
3 | Consists of functions to typically be used within templates, but also | |
4 | available to Controllers. This module is available to both as 'h'. |
|
4 | available to Controllers. This module is available to both as 'h'. | |
5 | """ |
|
5 | """ | |
6 | from pygments.formatters import HtmlFormatter |
|
6 | from pygments.formatters import HtmlFormatter | |
7 | from pygments import highlight as code_highlight |
|
7 | from pygments import highlight as code_highlight | |
8 | from pylons import url, app_globals as g |
|
8 | from pylons import url, app_globals as g | |
9 | from pylons.i18n.translation import _, ungettext |
|
9 | from pylons.i18n.translation import _, ungettext | |
10 | from vcs.utils.annotate import annotate_highlight |
|
10 | from vcs.utils.annotate import annotate_highlight | |
11 | from webhelpers.html import literal, HTML, escape |
|
11 | from webhelpers.html import literal, HTML, escape | |
12 | from webhelpers.html.tools import * |
|
12 | from webhelpers.html.tools import * | |
13 | from webhelpers.html.builder import make_tag |
|
13 | from webhelpers.html.builder import make_tag | |
14 | from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \ |
|
14 | from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \ | |
15 | end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \ |
|
15 | end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \ | |
16 | link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \ |
|
16 | link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \ | |
17 | password, textarea, title, ul, xml_declaration, radio |
|
17 | password, textarea, title, ul, xml_declaration, radio | |
18 | from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \ |
|
18 | from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \ | |
19 | mail_to, strip_links, strip_tags, tag_re |
|
19 | mail_to, strip_links, strip_tags, tag_re | |
20 | from webhelpers.number import format_byte_size, format_bit_size |
|
20 | from webhelpers.number import format_byte_size, format_bit_size | |
21 | from webhelpers.pylonslib import Flash as _Flash |
|
21 | from webhelpers.pylonslib import Flash as _Flash | |
22 | from webhelpers.pylonslib.secure_form import secure_form |
|
22 | from webhelpers.pylonslib.secure_form import secure_form | |
23 | from webhelpers.text import chop_at, collapse, convert_accented_entities, \ |
|
23 | from webhelpers.text import chop_at, collapse, convert_accented_entities, \ | |
24 | convert_misc_entities, lchop, plural, rchop, remove_formatting, \ |
|
24 | convert_misc_entities, lchop, plural, rchop, remove_formatting, \ | |
25 | replace_whitespace, urlify, truncate, wrap_paragraphs |
|
25 | replace_whitespace, urlify, truncate, wrap_paragraphs | |
26 |
|
26 | |||
27 | #Custom helpers here :) |
|
27 | #Custom helpers here :) | |
28 | class _Link(object): |
|
28 | class _Link(object): | |
29 | ''' |
|
29 | ''' | |
30 | Make a url based on label and url with help of url_for |
|
30 | Make a url based on label and url with help of url_for | |
31 | :param label:name of link if not defined url is used |
|
31 | :param label:name of link if not defined url is used | |
32 | :param url: the url for link |
|
32 | :param url: the url for link | |
33 | ''' |
|
33 | ''' | |
34 |
|
34 | |||
35 | def __call__(self, label='', *url_, **urlargs): |
|
35 | def __call__(self, label='', *url_, **urlargs): | |
36 | if label is None or '': |
|
36 | if label is None or '': | |
37 | label = url |
|
37 | label = url | |
38 | link_fn = link_to(label, url(*url_, **urlargs)) |
|
38 | link_fn = link_to(label, url(*url_, **urlargs)) | |
39 | return link_fn |
|
39 | return link_fn | |
40 |
|
40 | |||
41 | link = _Link() |
|
41 | link = _Link() | |
42 |
|
42 | |||
43 | class _GetError(object): |
|
43 | class _GetError(object): | |
44 |
|
44 | |||
45 | def __call__(self, field_name, form_errors): |
|
45 | def __call__(self, field_name, form_errors): | |
46 | tmpl = """<span class="error_msg">%s</span>""" |
|
46 | tmpl = """<span class="error_msg">%s</span>""" | |
47 | if form_errors and form_errors.has_key(field_name): |
|
47 | if form_errors and form_errors.has_key(field_name): | |
48 | return literal(tmpl % form_errors.get(field_name)) |
|
48 | return literal(tmpl % form_errors.get(field_name)) | |
49 |
|
49 | |||
50 | get_error = _GetError() |
|
50 | get_error = _GetError() | |
51 |
|
51 | |||
52 | def recursive_replace(str, replace=' '): |
|
52 | def recursive_replace(str, replace=' '): | |
53 | """ |
|
53 | """ | |
54 | Recursive replace of given sign to just one instance |
|
54 | Recursive replace of given sign to just one instance | |
55 | :param str: given string |
|
55 | :param str: given string | |
56 | :param replace:char to find and replace multiple instances |
|
56 | :param replace:char to find and replace multiple instances | |
57 |
|
57 | |||
58 | Examples:: |
|
58 | Examples:: | |
59 | >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-') |
|
59 | >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-') | |
60 | 'Mighty-Mighty-Bo-sstones' |
|
60 | 'Mighty-Mighty-Bo-sstones' | |
61 | """ |
|
61 | """ | |
62 |
|
62 | |||
63 | if str.find(replace * 2) == -1: |
|
63 | if str.find(replace * 2) == -1: | |
64 | return str |
|
64 | return str | |
65 | else: |
|
65 | else: | |
66 | str = str.replace(replace * 2, replace) |
|
66 | str = str.replace(replace * 2, replace) | |
67 |
return recursive_replace(str, replace) |
|
67 | return recursive_replace(str, replace) | |
68 |
|
68 | |||
69 | class _ToolTip(object): |
|
69 | class _ToolTip(object): | |
70 |
|
70 | |||
71 | def __call__(self, tooltip_title, trim_at=50): |
|
71 | def __call__(self, tooltip_title, trim_at=50): | |
72 | """ |
|
72 | """ | |
73 | Special function just to wrap our text into nice formatted autowrapped |
|
73 | Special function just to wrap our text into nice formatted autowrapped | |
74 | text |
|
74 | text | |
75 | :param tooltip_title: |
|
75 | :param tooltip_title: | |
76 | """ |
|
76 | """ | |
77 |
|
77 | |||
78 | return wrap_paragraphs(escape(tooltip_title), trim_at)\ |
|
78 | return wrap_paragraphs(escape(tooltip_title), trim_at)\ | |
79 | .replace('\n', '<br/>') |
|
79 | .replace('\n', '<br/>') | |
80 |
|
80 | |||
81 | def activate(self): |
|
81 | def activate(self): | |
82 | """ |
|
82 | """ | |
83 | Adds tooltip mechanism to the given Html all tooltips have to have |
|
83 | Adds tooltip mechanism to the given Html all tooltips have to have | |
84 | set class tooltip and set attribute tooltip_title. |
|
84 | set class tooltip and set attribute tooltip_title. | |
85 | Then a tooltip will be generated based on that |
|
85 | Then a tooltip will be generated based on that | |
86 | All with yui js tooltip |
|
86 | All with yui js tooltip | |
87 | """ |
|
87 | """ | |
88 |
|
88 | |||
89 | js = ''' |
|
89 | js = ''' | |
90 | YAHOO.util.Event.onDOMReady(function(){ |
|
90 | YAHOO.util.Event.onDOMReady(function(){ | |
91 | function toolTipsId(){ |
|
91 | function toolTipsId(){ | |
92 | var ids = []; |
|
92 | var ids = []; | |
93 | var tts = YAHOO.util.Dom.getElementsByClassName('tooltip'); |
|
93 | var tts = YAHOO.util.Dom.getElementsByClassName('tooltip'); | |
94 |
|
94 | |||
95 | for (var i = 0; i < tts.length; i++) { |
|
95 | for (var i = 0; i < tts.length; i++) { | |
96 | //if element doesn not have and id autgenerate one for tooltip |
|
96 | //if element doesn not have and id autgenerate one for tooltip | |
97 |
|
97 | |||
98 | if (!tts[i].id){ |
|
98 | if (!tts[i].id){ | |
99 | tts[i].id='tt'+i*100; |
|
99 | tts[i].id='tt'+i*100; | |
100 | } |
|
100 | } | |
101 | ids.push(tts[i].id); |
|
101 | ids.push(tts[i].id); | |
102 | } |
|
102 | } | |
103 | return ids |
|
103 | return ids | |
104 | }; |
|
104 | }; | |
105 | var myToolTips = new YAHOO.widget.Tooltip("tooltip", { |
|
105 | var myToolTips = new YAHOO.widget.Tooltip("tooltip", { | |
106 | context: toolTipsId(), |
|
106 | context: toolTipsId(), | |
107 | monitorresize:false, |
|
107 | monitorresize:false, | |
108 | xyoffset :[0,0], |
|
108 | xyoffset :[0,0], | |
109 | autodismissdelay:300000, |
|
109 | autodismissdelay:300000, | |
110 | hidedelay:5, |
|
110 | hidedelay:5, | |
111 | showdelay:20, |
|
111 | showdelay:20, | |
112 | }); |
|
112 | }); | |
113 |
|
113 | |||
114 | //Mouse Over event disabled for new repositories since they dont |
|
114 | //Mouse Over event disabled for new repositories since they dont | |
115 | //have last commit message |
|
115 | //have last commit message | |
116 | myToolTips.contextMouseOverEvent.subscribe( |
|
116 | myToolTips.contextMouseOverEvent.subscribe( | |
117 | function(type, args) { |
|
117 | function(type, args) { | |
118 | var context = args[0]; |
|
118 | var context = args[0]; | |
119 | var txt = context.getAttribute('tooltip_title'); |
|
119 | var txt = context.getAttribute('tooltip_title'); | |
120 | if(txt){ |
|
120 | if(txt){ | |
121 | return true; |
|
121 | return true; | |
122 | } |
|
122 | } | |
123 | else{ |
|
123 | else{ | |
124 | return false; |
|
124 | return false; | |
125 | } |
|
125 | } | |
126 | }); |
|
126 | }); | |
127 |
|
127 | |||
128 |
|
128 | |||
129 | // Set the text for the tooltip just before we display it. Lazy method |
|
129 | // Set the text for the tooltip just before we display it. Lazy method | |
130 | myToolTips.contextTriggerEvent.subscribe( |
|
130 | myToolTips.contextTriggerEvent.subscribe( | |
131 | function(type, args) { |
|
131 | function(type, args) { | |
132 |
|
132 | |||
133 |
|
133 | |||
134 | var context = args[0]; |
|
134 | var context = args[0]; | |
135 |
|
135 | |||
136 | var txt = context.getAttribute('tooltip_title'); |
|
136 | var txt = context.getAttribute('tooltip_title'); | |
137 | this.cfg.setProperty("text", txt); |
|
137 | this.cfg.setProperty("text", txt); | |
138 |
|
138 | |||
139 |
|
139 | |||
140 | // positioning of tooltip |
|
140 | // positioning of tooltip | |
141 | var tt_w = this.element.clientWidth; |
|
141 | var tt_w = this.element.clientWidth; | |
142 | var tt_h = this.element.clientHeight; |
|
142 | var tt_h = this.element.clientHeight; | |
143 |
|
143 | |||
144 | var context_w = context.offsetWidth; |
|
144 | var context_w = context.offsetWidth; | |
145 | var context_h = context.offsetHeight; |
|
145 | var context_h = context.offsetHeight; | |
146 |
|
146 | |||
147 | var pos_x = YAHOO.util.Dom.getX(context); |
|
147 | var pos_x = YAHOO.util.Dom.getX(context); | |
148 | var pos_y = YAHOO.util.Dom.getY(context); |
|
148 | var pos_y = YAHOO.util.Dom.getY(context); | |
149 |
|
149 | |||
150 | var display_strategy = 'top'; |
|
150 | var display_strategy = 'top'; | |
151 | var xy_pos = [0,0]; |
|
151 | var xy_pos = [0,0]; | |
152 | switch (display_strategy){ |
|
152 | switch (display_strategy){ | |
153 |
|
153 | |||
154 | case 'top': |
|
154 | case 'top': | |
155 | var cur_x = (pos_x+context_w/2)-(tt_w/2); |
|
155 | var cur_x = (pos_x+context_w/2)-(tt_w/2); | |
156 | var cur_y = pos_y-tt_h-4; |
|
156 | var cur_y = pos_y-tt_h-4; | |
157 | xy_pos = [cur_x,cur_y]; |
|
157 | xy_pos = [cur_x,cur_y]; | |
158 | break; |
|
158 | break; | |
159 | case 'bottom': |
|
159 | case 'bottom': | |
160 | var cur_x = (pos_x+context_w/2)-(tt_w/2); |
|
160 | var cur_x = (pos_x+context_w/2)-(tt_w/2); | |
161 | var cur_y = pos_y+context_h+4; |
|
161 | var cur_y = pos_y+context_h+4; | |
162 | xy_pos = [cur_x,cur_y]; |
|
162 | xy_pos = [cur_x,cur_y]; | |
163 | break; |
|
163 | break; | |
164 | case 'left': |
|
164 | case 'left': | |
165 | var cur_x = (pos_x-tt_w-4); |
|
165 | var cur_x = (pos_x-tt_w-4); | |
166 | var cur_y = pos_y-((tt_h/2)-context_h/2); |
|
166 | var cur_y = pos_y-((tt_h/2)-context_h/2); | |
167 | xy_pos = [cur_x,cur_y]; |
|
167 | xy_pos = [cur_x,cur_y]; | |
168 | break; |
|
168 | break; | |
169 | case 'right': |
|
169 | case 'right': | |
170 | var cur_x = (pos_x+context_w+4); |
|
170 | var cur_x = (pos_x+context_w+4); | |
171 | var cur_y = pos_y-((tt_h/2)-context_h/2); |
|
171 | var cur_y = pos_y-((tt_h/2)-context_h/2); | |
172 | xy_pos = [cur_x,cur_y]; |
|
172 | xy_pos = [cur_x,cur_y]; | |
173 | break; |
|
173 | break; | |
174 | default: |
|
174 | default: | |
175 | var cur_x = (pos_x+context_w/2)-(tt_w/2); |
|
175 | var cur_x = (pos_x+context_w/2)-(tt_w/2); | |
176 | var cur_y = pos_y-tt_h-4; |
|
176 | var cur_y = pos_y-tt_h-4; | |
177 | xy_pos = [cur_x,cur_y]; |
|
177 | xy_pos = [cur_x,cur_y]; | |
178 | break; |
|
178 | break; | |
179 |
|
179 | |||
180 | } |
|
180 | } | |
181 |
|
181 | |||
182 | this.cfg.setProperty("xy",xy_pos); |
|
182 | this.cfg.setProperty("xy",xy_pos); | |
183 |
|
183 | |||
184 | }); |
|
184 | }); | |
185 |
|
185 | |||
186 | //Mouse out |
|
186 | //Mouse out | |
187 | myToolTips.contextMouseOutEvent.subscribe( |
|
187 | myToolTips.contextMouseOutEvent.subscribe( | |
188 | function(type, args) { |
|
188 | function(type, args) { | |
189 | var context = args[0]; |
|
189 | var context = args[0]; | |
190 |
|
190 | |||
191 | }); |
|
191 | }); | |
192 | }); |
|
192 | }); | |
193 |
''' |
|
193 | ''' | |
194 | return literal(js) |
|
194 | return literal(js) | |
195 |
|
195 | |||
196 | tooltip = _ToolTip() |
|
196 | tooltip = _ToolTip() | |
197 |
|
197 | |||
198 | class _FilesBreadCrumbs(object): |
|
198 | class _FilesBreadCrumbs(object): | |
199 |
|
199 | |||
200 | def __call__(self, repo_name, rev, paths): |
|
200 | def __call__(self, repo_name, rev, paths): | |
201 | url_l = [link_to(repo_name, url('files_home', |
|
201 | url_l = [link_to(repo_name, url('files_home', | |
202 | repo_name=repo_name, |
|
202 | repo_name=repo_name, | |
203 | revision=rev, f_path=''))] |
|
203 | revision=rev, f_path=''))] | |
204 | paths_l = paths.split('/') |
|
204 | paths_l = paths.split('/') | |
205 |
|
205 | |||
206 | for cnt, p in enumerate(paths_l, 1): |
|
206 | for cnt, p in enumerate(paths_l, 1): | |
207 | if p != '': |
|
207 | if p != '': | |
208 | url_l.append(link_to(p, url('files_home', |
|
208 | url_l.append(link_to(p, url('files_home', | |
209 | repo_name=repo_name, |
|
209 | repo_name=repo_name, | |
210 | revision=rev, |
|
210 | revision=rev, | |
211 | f_path='/'.join(paths_l[:cnt])))) |
|
211 | f_path='/'.join(paths_l[:cnt])))) | |
212 |
|
212 | |||
213 | return literal('/'.join(url_l)) |
|
213 | return literal('/'.join(url_l)) | |
214 |
|
214 | |||
215 | files_breadcrumbs = _FilesBreadCrumbs() |
|
215 | files_breadcrumbs = _FilesBreadCrumbs() | |
216 | class CodeHtmlFormatter(HtmlFormatter): |
|
216 | class CodeHtmlFormatter(HtmlFormatter): | |
217 |
|
217 | |||
218 | def wrap(self, source, outfile): |
|
218 | def wrap(self, source, outfile): | |
219 | return self._wrap_div(self._wrap_pre(self._wrap_code(source))) |
|
219 | return self._wrap_div(self._wrap_pre(self._wrap_code(source))) | |
220 |
|
220 | |||
221 | def _wrap_code(self, source): |
|
221 | def _wrap_code(self, source): | |
222 | for cnt, it in enumerate(source, 1): |
|
222 | for cnt, it in enumerate(source, 1): | |
223 | i, t = it |
|
223 | i, t = it | |
224 | t = '<div id="#S-%s">%s</div>' % (cnt, t) |
|
224 | t = '<div id="#S-%s">%s</div>' % (cnt, t) | |
225 | yield i, t |
|
225 | yield i, t | |
226 | def pygmentize(filenode, **kwargs): |
|
226 | def pygmentize(filenode, **kwargs): | |
227 | """ |
|
227 | """ | |
228 | pygmentize function using pygments |
|
228 | pygmentize function using pygments | |
229 | :param filenode: |
|
229 | :param filenode: | |
230 | """ |
|
230 | """ | |
231 | return literal(code_highlight(filenode.content, |
|
231 | return literal(code_highlight(filenode.content, | |
232 | filenode.lexer, CodeHtmlFormatter(**kwargs))) |
|
232 | filenode.lexer, CodeHtmlFormatter(**kwargs))) | |
233 |
|
233 | |||
234 | def pygmentize_annotation(filenode, **kwargs): |
|
234 | def pygmentize_annotation(filenode, **kwargs): | |
235 | """ |
|
235 | """ | |
236 | pygmentize function for annotation |
|
236 | pygmentize function for annotation | |
237 | :param filenode: |
|
237 | :param filenode: | |
238 | """ |
|
238 | """ | |
239 |
|
239 | |||
240 | color_dict = {} |
|
240 | color_dict = {} | |
241 | def gen_color(): |
|
241 | def gen_color(): | |
242 | """generator for getting 10k of evenly distibuted colors using hsv color |
|
242 | """generator for getting 10k of evenly distibuted colors using hsv color | |
243 | and golden ratio. |
|
243 | and golden ratio. | |
244 |
""" |
|
244 | """ | |
245 | import colorsys |
|
245 | import colorsys | |
246 | n = 10000 |
|
246 | n = 10000 | |
247 | golden_ratio = 0.618033988749895 |
|
247 | golden_ratio = 0.618033988749895 | |
248 | h = 0.22717784590367374 |
|
248 | h = 0.22717784590367374 | |
249 | #generate 10k nice web friendly colors in the same order |
|
249 | #generate 10k nice web friendly colors in the same order | |
250 | for c in xrange(n): |
|
250 | for c in xrange(n): | |
251 | h += golden_ratio |
|
251 | h += golden_ratio | |
252 | h %= 1 |
|
252 | h %= 1 | |
253 | HSV_tuple = [h, 0.95, 0.95] |
|
253 | HSV_tuple = [h, 0.95, 0.95] | |
254 | RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple) |
|
254 | RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple) | |
255 |
yield map(lambda x:str(int(x * 256)), RGB_tuple) |
|
255 | yield map(lambda x:str(int(x * 256)), RGB_tuple) | |
256 |
|
256 | |||
257 | cgenerator = gen_color() |
|
257 | cgenerator = gen_color() | |
258 |
|
258 | |||
259 | def get_color_string(cs): |
|
259 | def get_color_string(cs): | |
260 | if color_dict.has_key(cs): |
|
260 | if color_dict.has_key(cs): | |
261 | col = color_dict[cs] |
|
261 | col = color_dict[cs] | |
262 | else: |
|
262 | else: | |
263 | col = color_dict[cs] = cgenerator.next() |
|
263 | col = color_dict[cs] = cgenerator.next() | |
264 | return "color: rgb(%s)! important;" % (', '.join(col)) |
|
264 | return "color: rgb(%s)! important;" % (', '.join(col)) | |
265 |
|
265 | |||
266 | def url_func(changeset): |
|
266 | def url_func(changeset): | |
267 | tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \ |
|
267 | tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \ | |
268 |
" %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>" |
|
268 | " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>" | |
269 |
|
269 | |||
270 | tooltip_html = tooltip_html % (changeset.author, |
|
270 | tooltip_html = tooltip_html % (changeset.author, | |
271 | changeset.date, |
|
271 | changeset.date, | |
272 | tooltip(changeset.message)) |
|
272 | tooltip(changeset.message)) | |
273 | lnk_format = 'r%-5s:%s' % (changeset.revision, |
|
273 | lnk_format = 'r%-5s:%s' % (changeset.revision, | |
274 | changeset.short_id) |
|
274 | changeset.short_id) | |
275 | uri = link_to( |
|
275 | uri = link_to( | |
276 | lnk_format, |
|
276 | lnk_format, | |
277 | url('changeset_home', repo_name=changeset.repository.name, |
|
277 | url('changeset_home', repo_name=changeset.repository.name, | |
278 | revision=changeset.short_id), |
|
278 | revision=changeset.short_id), | |
279 | style=get_color_string(changeset.short_id), |
|
279 | style=get_color_string(changeset.short_id), | |
280 | class_='tooltip', |
|
280 | class_='tooltip', | |
281 | tooltip_title=tooltip_html |
|
281 | tooltip_title=tooltip_html | |
282 | ) |
|
282 | ) | |
283 |
|
283 | |||
284 | uri += '\n' |
|
284 | uri += '\n' | |
285 |
return uri |
|
285 | return uri | |
286 | return literal(annotate_highlight(filenode, url_func, **kwargs)) |
|
286 | return literal(annotate_highlight(filenode, url_func, **kwargs)) | |
287 |
|
287 | |||
288 | def repo_name_slug(value): |
|
288 | def repo_name_slug(value): | |
289 | """Return slug of name of repository |
|
289 | """Return slug of name of repository | |
290 | This function is called on each creation/modification |
|
290 | This function is called on each creation/modification | |
291 | of repository to prevent bad names in repo |
|
291 | of repository to prevent bad names in repo | |
292 | """ |
|
292 | """ | |
293 | slug = remove_formatting(value) |
|
293 | slug = remove_formatting(value) | |
294 | slug = strip_tags(slug) |
|
294 | slug = strip_tags(slug) | |
295 |
|
295 | |||
296 | for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """: |
|
296 | for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """: | |
297 | slug = slug.replace(c, '-') |
|
297 | slug = slug.replace(c, '-') | |
298 | slug = recursive_replace(slug, '-') |
|
298 | slug = recursive_replace(slug, '-') | |
299 | slug = collapse(slug, '-') |
|
299 | slug = collapse(slug, '-') | |
300 | return slug |
|
300 | return slug | |
301 |
|
301 | |||
302 | def get_changeset_safe(repo, rev): |
|
302 | def get_changeset_safe(repo, rev): | |
303 | from vcs.backends.base import BaseRepository |
|
303 | from vcs.backends.base import BaseRepository | |
304 | from vcs.exceptions import RepositoryError |
|
304 | from vcs.exceptions import RepositoryError | |
305 | if not isinstance(repo, BaseRepository): |
|
305 | if not isinstance(repo, BaseRepository): | |
306 | raise Exception('You must pass an Repository ' |
|
306 | raise Exception('You must pass an Repository ' | |
307 | 'object as first argument got %s', type(repo)) |
|
307 | 'object as first argument got %s', type(repo)) | |
308 |
|
308 | |||
309 | try: |
|
309 | try: | |
310 | cs = repo.get_changeset(rev) |
|
310 | cs = repo.get_changeset(rev) | |
311 | except RepositoryError: |
|
311 | except RepositoryError: | |
312 | from rhodecode.lib.utils import EmptyChangeset |
|
312 | from rhodecode.lib.utils import EmptyChangeset | |
313 | cs = EmptyChangeset() |
|
313 | cs = EmptyChangeset() | |
314 | return cs |
|
314 | return cs | |
315 |
|
315 | |||
316 |
|
316 | |||
317 | flash = _Flash() |
|
317 | flash = _Flash() | |
318 |
|
318 | |||
319 |
|
319 | |||
320 | #=============================================================================== |
|
320 | #=============================================================================== | |
321 | # MERCURIAL FILTERS available via h. |
|
321 | # MERCURIAL FILTERS available via h. | |
322 | #=============================================================================== |
|
322 | #=============================================================================== | |
323 | from mercurial import util |
|
323 | from mercurial import util | |
324 | from mercurial.templatefilters import age as _age, person as _person |
|
324 | from mercurial.templatefilters import age as _age, person as _person | |
325 |
|
325 | |||
326 |
age = lambda x: |
|
326 | age = lambda x:x | |
327 | capitalize = lambda x: x.capitalize() |
|
327 | capitalize = lambda x: x.capitalize() | |
328 | date = lambda x: util.datestr(x) |
|
328 | date = lambda x: util.datestr(x) | |
329 | email = util.email |
|
329 | email = util.email | |
330 | email_or_none = lambda x: util.email(x) if util.email(x) != x else None |
|
330 | email_or_none = lambda x: util.email(x) if util.email(x) != x else None | |
331 | person = lambda x: _person(x) |
|
331 | person = lambda x: _person(x) | |
332 | hgdate = lambda x: "%d %d" % x |
|
332 | hgdate = lambda x: "%d %d" % x | |
333 | isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2') |
|
333 | isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2') | |
334 | isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2') |
|
334 | isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2') | |
335 | localdate = lambda x: (x[0], util.makedate()[1]) |
|
335 | localdate = lambda x: (x[0], util.makedate()[1]) | |
336 |
rfc822date = lambda x: |
|
336 | rfc822date = lambda x: x#util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2") | |
337 |
rfc822date_notz = lambda x: |
|
337 | rfc822date_notz = lambda x: x#util.datestr(x, "%a, %d %b %Y %H:%M:%S") | |
338 | rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2") |
|
338 | rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2") | |
339 | time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2") |
|
339 | time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2") | |
340 |
|
340 | |||
341 |
|
341 | |||
342 | #=============================================================================== |
|
342 | #=============================================================================== | |
343 | # PERMS |
|
343 | # PERMS | |
344 | #=============================================================================== |
|
344 | #=============================================================================== | |
345 | from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \ |
|
345 | from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \ | |
346 | HasRepoPermissionAny, HasRepoPermissionAll |
|
346 | HasRepoPermissionAny, HasRepoPermissionAll | |
347 |
|
347 | |||
348 | #=============================================================================== |
|
348 | #=============================================================================== | |
349 | # GRAVATAR URL |
|
349 | # GRAVATAR URL | |
350 | #=============================================================================== |
|
350 | #=============================================================================== | |
351 | import hashlib |
|
351 | import hashlib | |
352 | import urllib |
|
352 | import urllib | |
353 | from pylons import request |
|
353 | from pylons import request | |
354 |
|
354 | |||
355 | def gravatar_url(email_address, size=30): |
|
355 | def gravatar_url(email_address, size=30): | |
356 | ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME') |
|
356 | ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME') | |
357 | default = 'identicon' |
|
357 | default = 'identicon' | |
358 | baseurl_nossl = "http://www.gravatar.com/avatar/" |
|
358 | baseurl_nossl = "http://www.gravatar.com/avatar/" | |
359 | baseurl_ssl = "https://secure.gravatar.com/avatar/" |
|
359 | baseurl_ssl = "https://secure.gravatar.com/avatar/" | |
360 | baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl |
|
360 | baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl | |
361 |
|
361 | |||
362 |
|
362 | |||
363 | # construct the url |
|
363 | # construct the url | |
364 | gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?" |
|
364 | gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?" | |
365 | gravatar_url += urllib.urlencode({'d':default, 's':str(size)}) |
|
365 | gravatar_url += urllib.urlencode({'d':default, 's':str(size)}) | |
366 |
|
366 | |||
367 | return gravatar_url |
|
367 | return gravatar_url | |
368 |
|
368 | |||
369 | def safe_unicode(str): |
|
369 | def safe_unicode(str): | |
370 | """safe unicode function. In case of UnicodeDecode error we try to return |
|
370 | """safe unicode function. In case of UnicodeDecode error we try to return | |
371 | unicode with errors replace, if this failes we return unicode with |
|
371 | unicode with errors replace, if this failes we return unicode with | |
372 | string_escape decoding """ |
|
372 | string_escape decoding """ | |
373 |
|
373 | |||
374 | try: |
|
374 | try: | |
375 | u_str = unicode(str) |
|
375 | u_str = unicode(str) | |
376 | except UnicodeDecodeError: |
|
376 | except UnicodeDecodeError: | |
377 | try: |
|
377 | try: | |
378 | u_str = unicode(str, 'utf-8', 'replace') |
|
378 | u_str = unicode(str, 'utf-8', 'replace') | |
379 | except UnicodeDecodeError: |
|
379 | except UnicodeDecodeError: | |
380 | #incase we have a decode error just represent as byte string |
|
380 | #incase we have a decode error just represent as byte string | |
381 | u_str = unicode(str(str).encode('string_escape')) |
|
381 | u_str = unicode(str(str).encode('string_escape')) | |
382 |
|
382 | |||
383 | return u_str |
|
383 | return u_str |
@@ -1,142 +1,196 b'' | |||||
|
1 | import os | |||
|
2 | import sys | |||
1 | from os.path import dirname as dn, join as jn |
|
3 | from os.path import dirname as dn, join as jn | |
|
4 | ||||
|
5 | #to get the rhodecode import | |||
|
6 | sys.path.append(dn(dn(dn(os.path.realpath(__file__))))) | |||
|
7 | ||||
2 | from rhodecode.config.environment import load_environment |
|
8 | from rhodecode.config.environment import load_environment | |
3 | from rhodecode.model.hg import HgModel |
|
9 | from rhodecode.model.hg import HgModel | |
4 | from shutil import rmtree |
|
10 | from shutil import rmtree | |
5 | from webhelpers.html.builder import escape |
|
11 | from webhelpers.html.builder import escape | |
6 | from vcs.utils.lazy import LazyProperty |
|
12 | from vcs.utils.lazy import LazyProperty | |
7 |
|
13 | |||
8 | from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter |
|
14 | from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter | |
9 | from whoosh.fields import TEXT, ID, STORED, Schema, FieldType |
|
15 | from whoosh.fields import TEXT, ID, STORED, Schema, FieldType | |
10 | from whoosh.index import create_in, open_dir |
|
16 | from whoosh.index import create_in, open_dir | |
11 | from whoosh.formats import Characters |
|
17 | from whoosh.formats import Characters | |
12 |
from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter |
|
18 | from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter | |
13 |
|
19 | |||
14 | import os |
|
|||
15 | import sys |
|
|||
16 | import traceback |
|
20 | import traceback | |
17 |
|
21 | |||
18 | #to get the rhodecode import |
|
|||
19 | sys.path.append(dn(dn(dn(os.path.realpath(__file__))))) |
|
|||
20 |
|
||||
21 |
|
22 | |||
22 | #LOCATION WE KEEP THE INDEX |
|
23 | #LOCATION WE KEEP THE INDEX | |
23 | IDX_LOCATION = jn(dn(dn(dn(dn(os.path.abspath(__file__))))), 'data', 'index') |
|
24 | IDX_LOCATION = jn(dn(dn(dn(dn(os.path.abspath(__file__))))), 'data', 'index') | |
24 |
|
25 | |||
25 | #EXTENSIONS WE WANT TO INDEX CONTENT OFF |
|
26 | #EXTENSIONS WE WANT TO INDEX CONTENT OFF | |
26 | INDEX_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c', |
|
27 | INDEX_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c', | |
27 | 'cfg', 'cfm', 'cpp', 'cs', 'css', 'diff', 'do', 'el', 'erl', |
|
28 | 'cfg', 'cfm', 'cpp', 'cs', 'css', 'diff', 'do', 'el', 'erl', | |
28 | 'h', 'htm', 'html', 'ini', 'java', 'js', 'jsp', 'jspx', 'lisp', |
|
29 | 'h', 'htm', 'html', 'ini', 'java', 'js', 'jsp', 'jspx', 'lisp', | |
29 | 'lua', 'm', 'mako', 'ml', 'pas', 'patch', 'php', 'php3', |
|
30 | 'lua', 'm', 'mako', 'ml', 'pas', 'patch', 'php', 'php3', | |
30 | 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', 's', 'sh', 'sql', |
|
31 | 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', 's', 'sh', 'sql', | |
31 | 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt', |
|
32 | 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt', | |
32 | 'yaws'] |
|
33 | 'yaws'] | |
33 |
|
34 | |||
34 | #CUSTOM ANALYZER wordsplit + lowercase filter |
|
35 | #CUSTOM ANALYZER wordsplit + lowercase filter | |
35 | ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter() |
|
36 | ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter() | |
36 |
|
37 | |||
37 |
|
38 | |||
38 | #INDEX SCHEMA DEFINITION |
|
39 | #INDEX SCHEMA DEFINITION | |
39 | SCHEMA = Schema(owner=TEXT(), |
|
40 | SCHEMA = Schema(owner=TEXT(), | |
40 | repository=TEXT(stored=True), |
|
41 | repository=TEXT(stored=True), | |
41 | path=TEXT(stored=True), |
|
42 | path=TEXT(stored=True), | |
42 | content=FieldType(format=Characters(ANALYZER), |
|
43 | content=FieldType(format=Characters(ANALYZER), | |
43 | scorable=True, stored=True), |
|
44 | scorable=True, stored=True), | |
44 | modtime=STORED(), extension=TEXT(stored=True)) |
|
45 | modtime=STORED(), extension=TEXT(stored=True)) | |
45 |
|
46 | |||
46 |
|
47 | |||
47 | IDX_NAME = 'HG_INDEX' |
|
48 | IDX_NAME = 'HG_INDEX' | |
48 |
FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n') |
|
49 | FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n') | |
49 | FRAGMENTER = SimpleFragmenter(200) |
|
50 | FRAGMENTER = SimpleFragmenter(200) | |
50 |
|
51 | |||
|
52 | from paste.script import command | |||
|
53 | import ConfigParser | |||
|
54 | ||||
|
55 | class MakeIndex(command.Command): | |||
|
56 | ||||
|
57 | max_args = 1 | |||
|
58 | min_args = 1 | |||
|
59 | ||||
|
60 | usage = "CONFIG_FILE" | |||
|
61 | summary = "Creates index for full text search given configuration file" | |||
|
62 | group_name = "Whoosh indexing" | |||
|
63 | ||||
|
64 | parser = command.Command.standard_parser(verbose=True) | |||
|
65 | # parser.add_option('--repo-location', | |||
|
66 | # action='store', | |||
|
67 | # dest='repo_location', | |||
|
68 | # help="Specifies repositories location to index", | |||
|
69 | # ) | |||
|
70 | parser.add_option('-f', | |||
|
71 | action='store_true', | |||
|
72 | dest='full_index', | |||
|
73 | help="Specifies that index should be made full i.e" | |||
|
74 | " destroy old and build from scratch", | |||
|
75 | default=False) | |||
|
76 | def command(self): | |||
|
77 | config_name = self.args[0] | |||
|
78 | ||||
|
79 | p = config_name.split('/') | |||
|
80 | if len(p) == 1: | |||
|
81 | root = '.' | |||
|
82 | else: | |||
|
83 | root = '/'.join(p[:-1]) | |||
|
84 | print root | |||
|
85 | config = ConfigParser.ConfigParser({'here':root}) | |||
|
86 | config.read(config_name) | |||
|
87 | print dict(config.items('app:main'))['index_dir'] | |||
|
88 | index_location = dict(config.items('app:main'))['index_dir'] | |||
|
89 | #return | |||
|
90 | ||||
|
91 | #======================================================================= | |||
|
92 | # WHOOSH DAEMON | |||
|
93 | #======================================================================= | |||
|
94 | from rhodecode.lib.pidlock import LockHeld, DaemonLock | |||
|
95 | from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon | |||
|
96 | try: | |||
|
97 | l = DaemonLock() | |||
|
98 | WhooshIndexingDaemon(index_location=index_location)\ | |||
|
99 | .run(full_index=self.options.full_index) | |||
|
100 | l.release() | |||
|
101 | except LockHeld: | |||
|
102 | sys.exit(1) | |||
|
103 | ||||
|
104 | ||||
51 | class ResultWrapper(object): |
|
105 | class ResultWrapper(object): | |
52 | def __init__(self, search_type, searcher, matcher, highlight_items): |
|
106 | def __init__(self, search_type, searcher, matcher, highlight_items): | |
53 | self.search_type = search_type |
|
107 | self.search_type = search_type | |
54 | self.searcher = searcher |
|
108 | self.searcher = searcher | |
55 | self.matcher = matcher |
|
109 | self.matcher = matcher | |
56 | self.highlight_items = highlight_items |
|
110 | self.highlight_items = highlight_items | |
57 | self.fragment_size = 200 / 2 |
|
111 | self.fragment_size = 200 / 2 | |
58 |
|
112 | |||
59 | @LazyProperty |
|
113 | @LazyProperty | |
60 | def doc_ids(self): |
|
114 | def doc_ids(self): | |
61 | docs_id = [] |
|
115 | docs_id = [] | |
62 | while self.matcher.is_active(): |
|
116 | while self.matcher.is_active(): | |
63 | docnum = self.matcher.id() |
|
117 | docnum = self.matcher.id() | |
64 | chunks = [offsets for offsets in self.get_chunks()] |
|
118 | chunks = [offsets for offsets in self.get_chunks()] | |
65 | docs_id.append([docnum, chunks]) |
|
119 | docs_id.append([docnum, chunks]) | |
66 | self.matcher.next() |
|
120 | self.matcher.next() | |
67 |
return docs_id |
|
121 | return docs_id | |
68 |
|
122 | |||
69 | def __str__(self): |
|
123 | def __str__(self): | |
70 | return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids)) |
|
124 | return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids)) | |
71 |
|
125 | |||
72 | def __repr__(self): |
|
126 | def __repr__(self): | |
73 | return self.__str__() |
|
127 | return self.__str__() | |
74 |
|
128 | |||
75 | def __len__(self): |
|
129 | def __len__(self): | |
76 | return len(self.doc_ids) |
|
130 | return len(self.doc_ids) | |
77 |
|
131 | |||
78 | def __iter__(self): |
|
132 | def __iter__(self): | |
79 | """ |
|
133 | """ | |
80 | Allows Iteration over results,and lazy generate content |
|
134 | Allows Iteration over results,and lazy generate content | |
81 |
|
135 | |||
82 | *Requires* implementation of ``__getitem__`` method. |
|
136 | *Requires* implementation of ``__getitem__`` method. | |
83 | """ |
|
137 | """ | |
84 | for docid in self.doc_ids: |
|
138 | for docid in self.doc_ids: | |
85 | yield self.get_full_content(docid) |
|
139 | yield self.get_full_content(docid) | |
86 |
|
140 | |||
87 | def __getslice__(self, i, j): |
|
141 | def __getslice__(self, i, j): | |
88 | """ |
|
142 | """ | |
89 | Slicing of resultWrapper |
|
143 | Slicing of resultWrapper | |
90 | """ |
|
144 | """ | |
91 | slice = [] |
|
145 | slice = [] | |
92 | for docid in self.doc_ids[i:j]: |
|
146 | for docid in self.doc_ids[i:j]: | |
93 | slice.append(self.get_full_content(docid)) |
|
147 | slice.append(self.get_full_content(docid)) | |
94 |
return slice |
|
148 | return slice | |
95 |
|
149 | |||
96 |
|
150 | |||
97 | def get_full_content(self, docid): |
|
151 | def get_full_content(self, docid): | |
98 | res = self.searcher.stored_fields(docid[0]) |
|
152 | res = self.searcher.stored_fields(docid[0]) | |
99 | f_path = res['path'][res['path'].find(res['repository']) \ |
|
153 | f_path = res['path'][res['path'].find(res['repository']) \ | |
100 | + len(res['repository']):].lstrip('/') |
|
154 | + len(res['repository']):].lstrip('/') | |
101 |
|
155 | |||
102 | content_short = self.get_short_content(res, docid[1]) |
|
156 | content_short = self.get_short_content(res, docid[1]) | |
103 | res.update({'content_short':content_short, |
|
157 | res.update({'content_short':content_short, | |
104 | 'content_short_hl':self.highlight(content_short), |
|
158 | 'content_short_hl':self.highlight(content_short), | |
105 | 'f_path':f_path}) |
|
159 | 'f_path':f_path}) | |
106 |
|
160 | |||
107 |
return res |
|
161 | return res | |
108 |
|
162 | |||
109 | def get_short_content(self, res, chunks): |
|
163 | def get_short_content(self, res, chunks): | |
110 |
|
164 | |||
111 | return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks]) |
|
165 | return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks]) | |
112 |
|
166 | |||
113 | def get_chunks(self): |
|
167 | def get_chunks(self): | |
114 | """ |
|
168 | """ | |
115 | Smart function that implements chunking the content |
|
169 | Smart function that implements chunking the content | |
116 | but not overlap chunks so it doesn't highlight the same |
|
170 | but not overlap chunks so it doesn't highlight the same | |
117 | close occurrences twice. |
|
171 | close occurrences twice. | |
118 |
|
|
172 | @param matcher: | |
119 |
|
|
173 | @param size: | |
120 | """ |
|
174 | """ | |
121 | memory = [(0, 0)] |
|
175 | memory = [(0, 0)] | |
122 | for span in self.matcher.spans(): |
|
176 | for span in self.matcher.spans(): | |
123 | start = span.startchar or 0 |
|
177 | start = span.startchar or 0 | |
124 | end = span.endchar or 0 |
|
178 | end = span.endchar or 0 | |
125 | start_offseted = max(0, start - self.fragment_size) |
|
179 | start_offseted = max(0, start - self.fragment_size) | |
126 | end_offseted = end + self.fragment_size |
|
180 | end_offseted = end + self.fragment_size | |
127 |
|
181 | |||
128 | if start_offseted < memory[-1][1]: |
|
182 | if start_offseted < memory[-1][1]: | |
129 | start_offseted = memory[-1][1] |
|
183 | start_offseted = memory[-1][1] | |
130 |
memory.append((start_offseted, end_offseted,)) |
|
184 | memory.append((start_offseted, end_offseted,)) | |
131 |
yield (start_offseted, end_offseted,) |
|
185 | yield (start_offseted, end_offseted,) | |
132 |
|
186 | |||
133 | def highlight(self, content, top=5): |
|
187 | def highlight(self, content, top=5): | |
134 | if self.search_type != 'content': |
|
188 | if self.search_type != 'content': | |
135 | return '' |
|
189 | return '' | |
136 | hl = highlight(escape(content), |
|
190 | hl = highlight(escape(content), | |
137 | self.highlight_items, |
|
191 | self.highlight_items, | |
138 | analyzer=ANALYZER, |
|
192 | analyzer=ANALYZER, | |
139 | fragmenter=FRAGMENTER, |
|
193 | fragmenter=FRAGMENTER, | |
140 | formatter=FORMATTER, |
|
194 | formatter=FORMATTER, | |
141 | top=top) |
|
195 | top=top) | |
142 |
return hl |
|
196 | return hl |
@@ -1,248 +1,221 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | # encoding: utf-8 |
|
2 | # encoding: utf-8 | |
3 | # whoosh indexer daemon for rhodecode |
|
3 | # whoosh indexer daemon for rhodecode | |
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> | |
5 | # |
|
5 | # | |
6 | # This program is free software; you can redistribute it and/or |
|
6 | # This program is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU General Public License |
|
7 | # modify it under the terms of the GNU General Public License | |
8 | # as published by the Free Software Foundation; version 2 |
|
8 | # as published by the Free Software Foundation; version 2 | |
9 | # of the License or (at your opinion) any later version of the license. |
|
9 | # of the License or (at your opinion) any later version of the license. | |
10 | # |
|
10 | # | |
11 | # This program is distributed in the hope that it will be useful, |
|
11 | # This program is distributed in the hope that it will be useful, | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | # GNU General Public License for more details. |
|
14 | # GNU General Public License for more details. | |
15 | # |
|
15 | # | |
16 | # You should have received a copy of the GNU General Public License |
|
16 | # You should have received a copy of the GNU General Public License | |
17 | # along with this program; if not, write to the Free Software |
|
17 | # along with this program; if not, write to the Free Software | |
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
19 | # MA 02110-1301, USA. |
|
19 | # MA 02110-1301, USA. | |
20 | """ |
|
20 | """ | |
21 | Created on Jan 26, 2010 |
|
21 | Created on Jan 26, 2010 | |
22 |
|
22 | |||
23 | @author: marcink |
|
23 | @author: marcink | |
24 | A deamon will read from task table and run tasks |
|
24 | A deamon will read from task table and run tasks | |
25 | """ |
|
25 | """ | |
26 | import sys |
|
26 | import sys | |
27 | import os |
|
27 | import os | |
28 | from os.path import dirname as dn |
|
28 | from os.path import dirname as dn | |
29 | from os.path import join as jn |
|
29 | from os.path import join as jn | |
30 |
|
30 | |||
31 | #to get the rhodecode import |
|
31 | #to get the rhodecode import | |
32 | project_path = dn(dn(dn(dn(os.path.realpath(__file__))))) |
|
32 | project_path = dn(dn(dn(dn(os.path.realpath(__file__))))) | |
33 | sys.path.append(project_path) |
|
33 | sys.path.append(project_path) | |
34 |
|
34 | |||
35 | from rhodecode.lib.pidlock import LockHeld, DaemonLock |
|
35 | ||
36 | from rhodecode.model.hg import HgModel |
|
36 | from rhodecode.model.hg import HgModel | |
37 | from rhodecode.lib.helpers import safe_unicode |
|
37 | from rhodecode.lib.helpers import safe_unicode | |
38 | from whoosh.index import create_in, open_dir |
|
38 | from whoosh.index import create_in, open_dir | |
39 | from shutil import rmtree |
|
39 | from shutil import rmtree | |
40 |
from rhodecode.lib.indexers import INDEX_EXTENSIONS, |
|
40 | from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME | |
41 |
|
41 | |||
42 | from time import mktime |
|
42 | from time import mktime | |
43 | from vcs.exceptions import ChangesetError, RepositoryError |
|
43 | from vcs.exceptions import ChangesetError, RepositoryError | |
44 |
|
44 | |||
45 | import logging |
|
45 | import logging | |
46 |
|
46 | |||
47 | log = logging.getLogger('whooshIndexer') |
|
47 | log = logging.getLogger('whooshIndexer') | |
48 | # create logger |
|
48 | # create logger | |
49 | log.setLevel(logging.DEBUG) |
|
49 | log.setLevel(logging.DEBUG) | |
50 | log.propagate = False |
|
50 | log.propagate = False | |
51 | # create console handler and set level to debug |
|
51 | # create console handler and set level to debug | |
52 | ch = logging.StreamHandler() |
|
52 | ch = logging.StreamHandler() | |
53 | ch.setLevel(logging.DEBUG) |
|
53 | ch.setLevel(logging.DEBUG) | |
54 |
|
54 | |||
55 | # create formatter |
|
55 | # create formatter | |
56 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") |
|
56 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") | |
57 |
|
57 | |||
58 | # add formatter to ch |
|
58 | # add formatter to ch | |
59 | ch.setFormatter(formatter) |
|
59 | ch.setFormatter(formatter) | |
60 |
|
60 | |||
61 | # add ch to logger |
|
61 | # add ch to logger | |
62 | log.addHandler(ch) |
|
62 | log.addHandler(ch) | |
63 |
|
63 | |||
64 | def scan_paths(root_location): |
|
64 | def get_repos_location(): | |
65 |
return HgModel. |
|
65 | return HgModel.get_repos_location() | |
|
66 | ||||
66 |
|
67 | |||
67 | class WhooshIndexingDaemon(object): |
|
68 | class WhooshIndexingDaemon(object): | |
68 | """ |
|
69 | """ | |
69 | Deamon for atomic jobs |
|
70 | Deamon for atomic jobs | |
70 | """ |
|
71 | """ | |
71 |
|
72 | |||
72 |
def __init__(self, indexname='HG_INDEX', |
|
73 | def __init__(self, indexname='HG_INDEX', index_location=None, | |
|
74 | repo_location=None): | |||
73 | self.indexname = indexname |
|
75 | self.indexname = indexname | |
|
76 | ||||
|
77 | self.index_location = index_location | |||
|
78 | if not index_location: | |||
|
79 | raise Exception('You have to provide index location') | |||
|
80 | ||||
74 | self.repo_location = repo_location |
|
81 | self.repo_location = repo_location | |
75 | self.repo_paths = scan_paths(self.repo_location) |
|
82 | if not repo_location: | |
|
83 | raise Exception('You have to provide repositories location') | |||
|
84 | ||||
|
85 | ||||
|
86 | ||||
|
87 | self.repo_paths = HgModel.repo_scan('/', self.repo_location, None, True) | |||
76 | self.initial = False |
|
88 | self.initial = False | |
77 |
if not os.path.isdir( |
|
89 | if not os.path.isdir(self.index_location): | |
78 |
os.mkdir( |
|
90 | os.mkdir(self.index_location) | |
79 | log.info('Cannot run incremental index since it does not' |
|
91 | log.info('Cannot run incremental index since it does not' | |
80 | ' yet exist running full build') |
|
92 | ' yet exist running full build') | |
81 | self.initial = True |
|
93 | self.initial = True | |
82 |
|
94 | |||
83 | def get_paths(self, repo): |
|
95 | def get_paths(self, repo): | |
84 | """ |
|
96 | """ | |
85 | recursive walk in root dir and return a set of all path in that dir |
|
97 | recursive walk in root dir and return a set of all path in that dir | |
86 | based on repository walk function |
|
98 | based on repository walk function | |
87 | """ |
|
99 | """ | |
88 | index_paths_ = set() |
|
100 | index_paths_ = set() | |
89 | try: |
|
101 | try: | |
90 | tip = repo.get_changeset() |
|
102 | for topnode, dirs, files in repo.walk('/', 'tip'): | |
91 |
|
||||
92 | for topnode, dirs, files in tip.walk('/'): |
|
|||
93 | for f in files: |
|
103 | for f in files: | |
94 | index_paths_.add(jn(repo.path, f.path)) |
|
104 | index_paths_.add(jn(repo.path, f.path)) | |
95 | for dir in dirs: |
|
105 | for dir in dirs: | |
96 | for f in files: |
|
106 | for f in files: | |
97 | index_paths_.add(jn(repo.path, f.path)) |
|
107 | index_paths_.add(jn(repo.path, f.path)) | |
98 |
|
108 | |||
99 | except RepositoryError: |
|
109 | except RepositoryError: | |
100 | pass |
|
110 | pass | |
101 |
return index_paths_ |
|
111 | return index_paths_ | |
102 |
|
112 | |||
103 | def get_node(self, repo, path): |
|
113 | def get_node(self, repo, path): | |
104 | n_path = path[len(repo.path) + 1:] |
|
114 | n_path = path[len(repo.path) + 1:] | |
105 | node = repo.get_changeset().get_node(n_path) |
|
115 | node = repo.get_changeset().get_node(n_path) | |
106 | return node |
|
116 | return node | |
107 |
|
117 | |||
108 | def get_node_mtime(self, node): |
|
118 | def get_node_mtime(self, node): | |
109 | return mktime(node.last_changeset.date.timetuple()) |
|
119 | return mktime(node.last_changeset.date.timetuple()) | |
110 |
|
120 | |||
111 | def add_doc(self, writer, path, repo): |
|
121 | def add_doc(self, writer, path, repo): | |
112 | """Adding doc to writer""" |
|
122 | """Adding doc to writer""" | |
113 | node = self.get_node(repo, path) |
|
123 | node = self.get_node(repo, path) | |
114 |
|
124 | |||
115 | #we just index the content of chosen files |
|
125 | #we just index the content of chosen files | |
116 | if node.extension in INDEX_EXTENSIONS: |
|
126 | if node.extension in INDEX_EXTENSIONS: | |
117 | log.debug(' >> %s [WITH CONTENT]' % path) |
|
127 | log.debug(' >> %s [WITH CONTENT]' % path) | |
118 | u_content = node.content |
|
128 | u_content = node.content | |
119 | else: |
|
129 | else: | |
120 | log.debug(' >> %s' % path) |
|
130 | log.debug(' >> %s' % path) | |
121 | #just index file name without it's content |
|
131 | #just index file name without it's content | |
122 | u_content = u'' |
|
132 | u_content = u'' | |
123 |
|
133 | |||
124 | writer.add_document(owner=unicode(repo.contact), |
|
134 | writer.add_document(owner=unicode(repo.contact), | |
125 | repository=safe_unicode(repo.name), |
|
135 | repository=safe_unicode(repo.name), | |
126 | path=safe_unicode(path), |
|
136 | path=safe_unicode(path), | |
127 | content=u_content, |
|
137 | content=u_content, | |
128 | modtime=self.get_node_mtime(node), |
|
138 | modtime=self.get_node_mtime(node), | |
129 |
extension=node.extension) |
|
139 | extension=node.extension) | |
130 |
|
140 | |||
131 |
|
141 | |||
132 | def build_index(self): |
|
142 | def build_index(self): | |
133 |
if os.path.exists( |
|
143 | if os.path.exists(self.index_location): | |
134 | log.debug('removing previous index') |
|
144 | log.debug('removing previous index') | |
135 | rmtree(IDX_LOCATION) |
|
145 | rmtree(self.index_location) | |
136 |
|
146 | |||
137 |
if not os.path.exists( |
|
147 | if not os.path.exists(self.index_location): | |
138 |
os.mkdir( |
|
148 | os.mkdir(self.index_location) | |
139 |
|
149 | |||
140 |
idx = create_in( |
|
150 | idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME) | |
141 | writer = idx.writer() |
|
151 | writer = idx.writer() | |
142 |
|
152 | |||
143 | for cnt, repo in enumerate(self.repo_paths.values()): |
|
153 | for cnt, repo in enumerate(self.repo_paths.values()): | |
144 | log.debug('building index @ %s' % repo.path) |
|
154 | log.debug('building index @ %s' % repo.path) | |
145 |
|
155 | |||
146 | for idx_path in self.get_paths(repo): |
|
156 | for idx_path in self.get_paths(repo): | |
147 | self.add_doc(writer, idx_path, repo) |
|
157 | self.add_doc(writer, idx_path, repo) | |
148 |
|
158 | |||
149 | log.debug('>> COMMITING CHANGES <<') |
|
159 | log.debug('>> COMMITING CHANGES <<') | |
150 | writer.commit(merge=True) |
|
160 | writer.commit(merge=True) | |
151 | log.debug('>>> FINISHED BUILDING INDEX <<<') |
|
161 | log.debug('>>> FINISHED BUILDING INDEX <<<') | |
152 |
|
162 | |||
153 |
|
163 | |||
154 | def update_index(self): |
|
164 | def update_index(self): | |
155 | log.debug('STARTING INCREMENTAL INDEXING UPDATE') |
|
165 | log.debug('STARTING INCREMENTAL INDEXING UPDATE') | |
156 |
|
166 | |||
157 |
idx = open_dir( |
|
167 | idx = open_dir(self.index_location, indexname=self.indexname) | |
158 | # The set of all paths in the index |
|
168 | # The set of all paths in the index | |
159 | indexed_paths = set() |
|
169 | indexed_paths = set() | |
160 | # The set of all paths we need to re-index |
|
170 | # The set of all paths we need to re-index | |
161 | to_index = set() |
|
171 | to_index = set() | |
162 |
|
172 | |||
163 | reader = idx.reader() |
|
173 | reader = idx.reader() | |
164 | writer = idx.writer() |
|
174 | writer = idx.writer() | |
165 |
|
175 | |||
166 | # Loop over the stored fields in the index |
|
176 | # Loop over the stored fields in the index | |
167 | for fields in reader.all_stored_fields(): |
|
177 | for fields in reader.all_stored_fields(): | |
168 | indexed_path = fields['path'] |
|
178 | indexed_path = fields['path'] | |
169 | indexed_paths.add(indexed_path) |
|
179 | indexed_paths.add(indexed_path) | |
170 |
|
180 | |||
171 | repo = self.repo_paths[fields['repository']] |
|
181 | repo = self.repo_paths[fields['repository']] | |
172 |
|
182 | |||
173 | try: |
|
183 | try: | |
174 | node = self.get_node(repo, indexed_path) |
|
184 | node = self.get_node(repo, indexed_path) | |
175 | except ChangesetError: |
|
185 | except ChangesetError: | |
176 | # This file was deleted since it was indexed |
|
186 | # This file was deleted since it was indexed | |
177 | log.debug('removing from index %s' % indexed_path) |
|
187 | log.debug('removing from index %s' % indexed_path) | |
178 | writer.delete_by_term('path', indexed_path) |
|
188 | writer.delete_by_term('path', indexed_path) | |
179 |
|
189 | |||
180 | else: |
|
190 | else: | |
181 | # Check if this file was changed since it was indexed |
|
191 | # Check if this file was changed since it was indexed | |
182 | indexed_time = fields['modtime'] |
|
192 | indexed_time = fields['modtime'] | |
183 | mtime = self.get_node_mtime(node) |
|
193 | mtime = self.get_node_mtime(node) | |
184 | if mtime > indexed_time: |
|
194 | if mtime > indexed_time: | |
185 | # The file has changed, delete it and add it to the list of |
|
195 | # The file has changed, delete it and add it to the list of | |
186 | # files to reindex |
|
196 | # files to reindex | |
187 | log.debug('adding to reindex list %s' % indexed_path) |
|
197 | log.debug('adding to reindex list %s' % indexed_path) | |
188 | writer.delete_by_term('path', indexed_path) |
|
198 | writer.delete_by_term('path', indexed_path) | |
189 | to_index.add(indexed_path) |
|
199 | to_index.add(indexed_path) | |
190 |
|
200 | |||
191 | # Loop over the files in the filesystem |
|
201 | # Loop over the files in the filesystem | |
192 | # Assume we have a function that gathers the filenames of the |
|
202 | # Assume we have a function that gathers the filenames of the | |
193 | # documents to be indexed |
|
203 | # documents to be indexed | |
194 | for repo in self.repo_paths.values(): |
|
204 | for repo in self.repo_paths.values(): | |
195 | for path in self.get_paths(repo): |
|
205 | for path in self.get_paths(repo): | |
196 | if path in to_index or path not in indexed_paths: |
|
206 | if path in to_index or path not in indexed_paths: | |
197 | # This is either a file that's changed, or a new file |
|
207 | # This is either a file that's changed, or a new file | |
198 | # that wasn't indexed before. So index it! |
|
208 | # that wasn't indexed before. So index it! | |
199 | self.add_doc(writer, path, repo) |
|
209 | self.add_doc(writer, path, repo) | |
200 | log.debug('re indexing %s' % path) |
|
210 | log.debug('re indexing %s' % path) | |
201 |
|
211 | |||
202 | log.debug('>> COMMITING CHANGES <<') |
|
212 | log.debug('>> COMMITING CHANGES <<') | |
203 | writer.commit(merge=True) |
|
213 | writer.commit(merge=True) | |
204 | log.debug('>>> FINISHED REBUILDING INDEX <<<') |
|
214 | log.debug('>>> FINISHED REBUILDING INDEX <<<') | |
205 |
|
215 | |||
206 | def run(self, full_index=False): |
|
216 | def run(self, full_index=False): | |
207 | """Run daemon""" |
|
217 | """Run daemon""" | |
208 | if full_index or self.initial: |
|
218 | if full_index or self.initial: | |
209 | self.build_index() |
|
219 | self.build_index() | |
210 | else: |
|
220 | else: | |
211 | self.update_index() |
|
221 | self.update_index() | |
212 |
|
||||
213 | if __name__ == "__main__": |
|
|||
214 | arg = sys.argv[1:] |
|
|||
215 | if len(arg) != 2: |
|
|||
216 | sys.stderr.write('Please specify indexing type [full|incremental]' |
|
|||
217 | 'and path to repositories as script args \n') |
|
|||
218 | sys.exit() |
|
|||
219 |
|
||||
220 |
|
||||
221 | if arg[0] == 'full': |
|
|||
222 | full_index = True |
|
|||
223 | elif arg[0] == 'incremental': |
|
|||
224 | # False means looking just for changes |
|
|||
225 | full_index = False |
|
|||
226 | else: |
|
|||
227 | sys.stdout.write('Please use [full|incremental]' |
|
|||
228 | ' as script first arg \n') |
|
|||
229 | sys.exit() |
|
|||
230 |
|
||||
231 | if not os.path.isdir(arg[1]): |
|
|||
232 | sys.stderr.write('%s is not a valid path \n' % arg[1]) |
|
|||
233 | sys.exit() |
|
|||
234 | else: |
|
|||
235 | if arg[1].endswith('/'): |
|
|||
236 | repo_location = arg[1] + '*' |
|
|||
237 | else: |
|
|||
238 | repo_location = arg[1] + '/*' |
|
|||
239 |
|
||||
240 | try: |
|
|||
241 | l = DaemonLock() |
|
|||
242 | WhooshIndexingDaemon(repo_location=repo_location)\ |
|
|||
243 | .run(full_index=full_index) |
|
|||
244 | l.release() |
|
|||
245 | reload(logging) |
|
|||
246 | except LockHeld: |
|
|||
247 | sys.exit(1) |
|
|||
248 |
|
@@ -1,511 +1,533 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | # encoding: utf-8 |
|
2 | # encoding: utf-8 | |
3 | # Utilities for RhodeCode |
|
3 | # Utilities for RhodeCode | |
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> | |
5 | # This program is free software; you can redistribute it and/or |
|
5 | # This program is free software; you can redistribute it and/or | |
6 | # modify it under the terms of the GNU General Public License |
|
6 | # modify it under the terms of the GNU General Public License | |
7 | # as published by the Free Software Foundation; version 2 |
|
7 | # as published by the Free Software Foundation; version 2 | |
8 | # of the License or (at your opinion) any later version of the license. |
|
8 | # of the License or (at your opinion) any later version of the license. | |
9 | # |
|
9 | # | |
10 | # This program is distributed in the hope that it will be useful, |
|
10 | # This program is distributed in the hope that it will be useful, | |
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | # GNU General Public License for more details. |
|
13 | # GNU General Public License for more details. | |
14 | # |
|
14 | # | |
15 | # You should have received a copy of the GNU General Public License |
|
15 | # You should have received a copy of the GNU General Public License | |
16 | # along with this program; if not, write to the Free Software |
|
16 | # along with this program; if not, write to the Free Software | |
17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
18 | # MA 02110-1301, USA. |
|
18 | # MA 02110-1301, USA. | |
|
19 | from UserDict import DictMixin | |||
|
20 | from mercurial import ui, config, hg | |||
|
21 | from mercurial.error import RepoError | |||
|
22 | from rhodecode.model import meta | |||
|
23 | from rhodecode.model.caching_query import FromCache | |||
|
24 | from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, \ | |||
|
25 | UserLog | |||
|
26 | from rhodecode.model.repo import RepoModel | |||
|
27 | from rhodecode.model.user import UserModel | |||
|
28 | from vcs.backends.base import BaseChangeset | |||
|
29 | from vcs.backends.git import GitRepository | |||
|
30 | from vcs.backends.hg import MercurialRepository | |||
|
31 | from vcs.utils.lazy import LazyProperty | |||
|
32 | import datetime | |||
|
33 | import logging | |||
|
34 | import os | |||
19 |
|
35 | |||
20 | """ |
|
36 | """ | |
21 | Created on April 18, 2010 |
|
37 | Created on April 18, 2010 | |
22 | Utilities for RhodeCode |
|
38 | Utilities for RhodeCode | |
23 | @author: marcink |
|
39 | @author: marcink | |
24 | """ |
|
40 | """ | |
25 | from rhodecode.model.caching_query import FromCache |
|
|||
26 | from mercurial import ui, config, hg |
|
|||
27 | from mercurial.error import RepoError |
|
|||
28 | from rhodecode.model import meta |
|
|||
29 | from rhodecode.model.user import UserModel |
|
|||
30 | from rhodecode.model.repo import RepoModel |
|
|||
31 | from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, UserLog |
|
|||
32 | from vcs.backends.base import BaseChangeset |
|
|||
33 | from vcs.utils.lazy import LazyProperty |
|
|||
34 | import logging |
|
|||
35 | import datetime |
|
|||
36 | import os |
|
|||
37 |
|
41 | |||
38 | log = logging.getLogger(__name__) |
|
42 | log = logging.getLogger(__name__) | |
39 |
|
43 | |||
40 |
|
44 | |||
41 | def get_repo_slug(request): |
|
45 | def get_repo_slug(request): | |
42 | return request.environ['pylons.routes_dict'].get('repo_name') |
|
46 | return request.environ['pylons.routes_dict'].get('repo_name') | |
43 |
|
47 | |||
44 | def is_mercurial(environ): |
|
48 | def is_mercurial(environ): | |
45 | """ |
|
49 | """ | |
46 | Returns True if request's target is mercurial server - header |
|
50 | Returns True if request's target is mercurial server - header | |
47 | ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``. |
|
51 | ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``. | |
48 | """ |
|
52 | """ | |
49 | http_accept = environ.get('HTTP_ACCEPT') |
|
53 | http_accept = environ.get('HTTP_ACCEPT') | |
50 | if http_accept and http_accept.startswith('application/mercurial'): |
|
54 | if http_accept and http_accept.startswith('application/mercurial'): | |
51 | return True |
|
55 | return True | |
52 | return False |
|
56 | return False | |
53 |
|
57 | |||
54 | def is_git(environ): |
|
58 | def is_git(environ): | |
55 | """ |
|
59 | """ | |
56 | Returns True if request's target is git server. ``HTTP_USER_AGENT`` would |
|
60 | Returns True if request's target is git server. ``HTTP_USER_AGENT`` would | |
57 | then have git client version given. |
|
61 | then have git client version given. | |
58 |
|
62 | |||
59 | :param environ: |
|
63 | :param environ: | |
60 | """ |
|
64 | """ | |
61 | http_user_agent = environ.get('HTTP_USER_AGENT') |
|
65 | http_user_agent = environ.get('HTTP_USER_AGENT') | |
62 | if http_user_agent.startswith('git'): |
|
66 | if http_user_agent.startswith('git'): | |
63 | return True |
|
67 | return True | |
64 | return False |
|
68 | return False | |
65 |
|
69 | |||
66 | def action_logger(user, action, repo, ipaddr, sa=None): |
|
70 | def action_logger(user, action, repo, ipaddr, sa=None): | |
67 | """ |
|
71 | """ | |
68 | Action logger for various action made by users |
|
72 | Action logger for various action made by users | |
69 | """ |
|
73 | """ | |
70 |
|
74 | |||
71 | if not sa: |
|
75 | if not sa: | |
72 | sa = meta.Session() |
|
76 | sa = meta.Session() | |
73 |
|
77 | |||
74 | try: |
|
78 | try: | |
75 | if hasattr(user, 'user_id'): |
|
79 | if hasattr(user, 'user_id'): | |
76 | user_id = user.user_id |
|
80 | user_id = user.user_id | |
77 | elif isinstance(user, basestring): |
|
81 | elif isinstance(user, basestring): | |
78 | user_id = UserModel(sa).get_by_username(user, cache=False).user_id |
|
82 | user_id = UserModel(sa).get_by_username(user, cache=False).user_id | |
79 | else: |
|
83 | else: | |
80 | raise Exception('You have to provide user object or username') |
|
84 | raise Exception('You have to provide user object or username') | |
81 |
|
85 | |||
82 | repo_name = repo.lstrip('/') |
|
86 | repo_name = repo.lstrip('/') | |
83 | user_log = UserLog() |
|
87 | user_log = UserLog() | |
84 | user_log.user_id = user_id |
|
88 | user_log.user_id = user_id | |
85 | user_log.action = action |
|
89 | user_log.action = action | |
86 | user_log.repository_name = repo_name |
|
90 | user_log.repository_name = repo_name | |
87 | user_log.repository = RepoModel(sa).get(repo_name, cache=False) |
|
91 | user_log.repository = RepoModel(sa).get(repo_name, cache=False) | |
88 | user_log.action_date = datetime.datetime.now() |
|
92 | user_log.action_date = datetime.datetime.now() | |
89 | user_log.user_ip = ipaddr |
|
93 | user_log.user_ip = ipaddr | |
90 | sa.add(user_log) |
|
94 | sa.add(user_log) | |
91 | sa.commit() |
|
95 | sa.commit() | |
92 |
|
96 | |||
93 | log.info('Adding user %s, action %s on %s', |
|
97 | log.info('Adding user %s, action %s on %s', | |
94 | user.username, action, repo) |
|
98 | user.username, action, repo) | |
95 | except Exception, e: |
|
99 | except Exception, e: | |
96 | sa.rollback() |
|
100 | sa.rollback() | |
97 | log.error('could not log user action:%s', str(e)) |
|
101 | log.error('could not log user action:%s', str(e)) | |
98 |
|
102 | |||
99 | def check_repo_dir(paths): |
|
103 | def get_repos(path, recursive=False, initial=False): | |
100 | repos_path = paths[0][1].split('/') |
|
104 | """ | |
101 | if repos_path[-1] in ['*', '**']: |
|
105 | Scans given path for repos and return (name,(type,path)) tuple | |
102 | repos_path = repos_path[:-1] |
|
106 | :param prefix: | |
103 | if repos_path[0] != '/': |
|
107 | :param path: | |
104 | repos_path[0] = '/' |
|
108 | :param recursive: | |
105 | if not os.path.isdir(os.path.join(*repos_path)): |
|
109 | :param initial: | |
106 | raise Exception('Not a valid repository in %s' % paths[0][1]) |
|
110 | """ | |
|
111 | from vcs.utils.helpers import get_scm | |||
|
112 | from vcs.exceptions import VCSError | |||
|
113 | scm = get_scm(path) | |||
|
114 | if scm: | |||
|
115 | raise Exception('The given path %s should not be a repository got %s', | |||
|
116 | path, scm) | |||
|
117 | ||||
|
118 | for dirpath in os.listdir(path): | |||
|
119 | try: | |||
|
120 | yield dirpath, get_scm(os.path.join(path, dirpath)) | |||
|
121 | except VCSError: | |||
|
122 | pass | |||
|
123 | ||||
|
124 | if __name__ == '__main__': | |||
|
125 | get_repos('', '/home/marcink/workspace-python') | |||
|
126 | ||||
107 |
|
127 | |||
108 | def check_repo_fast(repo_name, base_path): |
|
128 | def check_repo_fast(repo_name, base_path): | |
109 | if os.path.isdir(os.path.join(base_path, repo_name)):return False |
|
129 | if os.path.isdir(os.path.join(base_path, repo_name)):return False | |
110 | return True |
|
130 | return True | |
111 |
|
131 | |||
112 | def check_repo(repo_name, base_path, verify=True): |
|
132 | def check_repo(repo_name, base_path, verify=True): | |
113 |
|
133 | |||
114 | repo_path = os.path.join(base_path, repo_name) |
|
134 | repo_path = os.path.join(base_path, repo_name) | |
115 |
|
135 | |||
116 | try: |
|
136 | try: | |
117 | if not check_repo_fast(repo_name, base_path): |
|
137 | if not check_repo_fast(repo_name, base_path): | |
118 | return False |
|
138 | return False | |
119 | r = hg.repository(ui.ui(), repo_path) |
|
139 | r = hg.repository(ui.ui(), repo_path) | |
120 | if verify: |
|
140 | if verify: | |
121 | hg.verify(r) |
|
141 | hg.verify(r) | |
122 | #here we hnow that repo exists it was verified |
|
142 | #here we hnow that repo exists it was verified | |
123 | log.info('%s repo is already created', repo_name) |
|
143 | log.info('%s repo is already created', repo_name) | |
124 | return False |
|
144 | return False | |
125 | except RepoError: |
|
145 | except RepoError: | |
126 | #it means that there is no valid repo there... |
|
146 | #it means that there is no valid repo there... | |
127 | log.info('%s repo is free for creation', repo_name) |
|
147 | log.info('%s repo is free for creation', repo_name) | |
128 | return True |
|
148 | return True | |
129 |
|
149 | |||
130 | def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): |
|
150 | def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): | |
131 | while True: |
|
151 | while True: | |
132 | ok = raw_input(prompt) |
|
152 | ok = raw_input(prompt) | |
133 | if ok in ('y', 'ye', 'yes'): return True |
|
153 | if ok in ('y', 'ye', 'yes'): return True | |
134 | if ok in ('n', 'no', 'nop', 'nope'): return False |
|
154 | if ok in ('n', 'no', 'nop', 'nope'): return False | |
135 | retries = retries - 1 |
|
155 | retries = retries - 1 | |
136 | if retries < 0: raise IOError |
|
156 | if retries < 0: raise IOError | |
137 | print complaint |
|
157 | print complaint | |
138 |
|
158 | |||
139 | def get_hg_ui_cached(): |
|
159 | def get_hg_ui_cached(): | |
140 | try: |
|
160 | try: | |
141 | sa = meta.Session |
|
161 | sa = meta.Session | |
142 | ret = sa.query(RhodeCodeUi)\ |
|
162 | ret = sa.query(RhodeCodeUi)\ | |
143 | .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\ |
|
163 | .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\ | |
144 | .all() |
|
164 | .all() | |
145 | except: |
|
165 | except: | |
146 | pass |
|
166 | pass | |
147 | finally: |
|
167 | finally: | |
148 | meta.Session.remove() |
|
168 | meta.Session.remove() | |
149 | return ret |
|
169 | return ret | |
150 |
|
170 | |||
151 |
|
171 | |||
152 | def get_hg_settings(): |
|
172 | def get_hg_settings(): | |
153 | try: |
|
173 | try: | |
154 | sa = meta.Session() |
|
174 | sa = meta.Session() | |
155 | ret = sa.query(RhodeCodeSettings)\ |
|
175 | ret = sa.query(RhodeCodeSettings)\ | |
156 | .options(FromCache("sql_cache_short", "get_hg_settings"))\ |
|
176 | .options(FromCache("sql_cache_short", "get_hg_settings"))\ | |
157 | .all() |
|
177 | .all() | |
158 | except: |
|
178 | except: | |
159 | pass |
|
179 | pass | |
160 | finally: |
|
180 | finally: | |
161 | meta.Session.remove() |
|
181 | meta.Session.remove() | |
162 |
|
182 | |||
163 | if not ret: |
|
183 | if not ret: | |
164 | raise Exception('Could not get application settings !') |
|
184 | raise Exception('Could not get application settings !') | |
165 | settings = {} |
|
185 | settings = {} | |
166 | for each in ret: |
|
186 | for each in ret: | |
167 | settings['rhodecode_' + each.app_settings_name] = each.app_settings_value |
|
187 | settings['rhodecode_' + each.app_settings_name] = each.app_settings_value | |
168 |
|
188 | |||
169 | return settings |
|
189 | return settings | |
170 |
|
190 | |||
171 | def get_hg_ui_settings(): |
|
191 | def get_hg_ui_settings(): | |
172 | try: |
|
192 | try: | |
173 | sa = meta.Session() |
|
193 | sa = meta.Session() | |
174 | ret = sa.query(RhodeCodeUi).all() |
|
194 | ret = sa.query(RhodeCodeUi).all() | |
175 | except: |
|
195 | except: | |
176 | pass |
|
196 | pass | |
177 | finally: |
|
197 | finally: | |
178 | meta.Session.remove() |
|
198 | meta.Session.remove() | |
179 |
|
199 | |||
180 | if not ret: |
|
200 | if not ret: | |
181 | raise Exception('Could not get application ui settings !') |
|
201 | raise Exception('Could not get application ui settings !') | |
182 | settings = {} |
|
202 | settings = {} | |
183 | for each in ret: |
|
203 | for each in ret: | |
184 | k = each.ui_key |
|
204 | k = each.ui_key | |
185 | v = each.ui_value |
|
205 | v = each.ui_value | |
186 | if k == '/': |
|
206 | if k == '/': | |
187 | k = 'root_path' |
|
207 | k = 'root_path' | |
188 |
|
208 | |||
189 | if k.find('.') != -1: |
|
209 | if k.find('.') != -1: | |
190 | k = k.replace('.', '_') |
|
210 | k = k.replace('.', '_') | |
191 |
|
211 | |||
192 | if each.ui_section == 'hooks': |
|
212 | if each.ui_section == 'hooks': | |
193 | v = each.ui_active |
|
213 | v = each.ui_active | |
194 |
|
214 | |||
195 | settings[each.ui_section + '_' + k] = v |
|
215 | settings[each.ui_section + '_' + k] = v | |
196 |
|
216 | |||
197 | return settings |
|
217 | return settings | |
198 |
|
218 | |||
199 | #propagated from mercurial documentation |
|
219 | #propagated from mercurial documentation | |
200 | ui_sections = ['alias', 'auth', |
|
220 | ui_sections = ['alias', 'auth', | |
201 | 'decode/encode', 'defaults', |
|
221 | 'decode/encode', 'defaults', | |
202 | 'diff', 'email', |
|
222 | 'diff', 'email', | |
203 | 'extensions', 'format', |
|
223 | 'extensions', 'format', | |
204 | 'merge-patterns', 'merge-tools', |
|
224 | 'merge-patterns', 'merge-tools', | |
205 | 'hooks', 'http_proxy', |
|
225 | 'hooks', 'http_proxy', | |
206 | 'smtp', 'patch', |
|
226 | 'smtp', 'patch', | |
207 | 'paths', 'profiling', |
|
227 | 'paths', 'profiling', | |
208 | 'server', 'trusted', |
|
228 | 'server', 'trusted', | |
209 | 'ui', 'web', ] |
|
229 | 'ui', 'web', ] | |
210 |
|
230 | |||
211 | def make_ui(read_from='file', path=None, checkpaths=True): |
|
231 | def make_ui(read_from='file', path=None, checkpaths=True): | |
212 | """ |
|
232 | """ | |
213 | A function that will read python rc files or database |
|
233 | A function that will read python rc files or database | |
214 | and make an mercurial ui object from read options |
|
234 | and make an mercurial ui object from read options | |
215 |
|
235 | |||
216 | :param path: path to mercurial config file |
|
236 | :param path: path to mercurial config file | |
217 | :param checkpaths: check the path |
|
237 | :param checkpaths: check the path | |
218 | :param read_from: read from 'file' or 'db' |
|
238 | :param read_from: read from 'file' or 'db' | |
219 | """ |
|
239 | """ | |
220 |
|
240 | |||
221 | baseui = ui.ui() |
|
241 | baseui = ui.ui() | |
222 |
|
242 | |||
223 | if read_from == 'file': |
|
243 | if read_from == 'file': | |
224 | if not os.path.isfile(path): |
|
244 | if not os.path.isfile(path): | |
225 | log.warning('Unable to read config file %s' % path) |
|
245 | log.warning('Unable to read config file %s' % path) | |
226 | return False |
|
246 | return False | |
227 | log.debug('reading hgrc from %s', path) |
|
247 | log.debug('reading hgrc from %s', path) | |
228 | cfg = config.config() |
|
248 | cfg = config.config() | |
229 | cfg.read(path) |
|
249 | cfg.read(path) | |
230 | for section in ui_sections: |
|
250 | for section in ui_sections: | |
231 | for k, v in cfg.items(section): |
|
251 | for k, v in cfg.items(section): | |
232 | baseui.setconfig(section, k, v) |
|
252 | baseui.setconfig(section, k, v) | |
233 | log.debug('settings ui from file[%s]%s:%s', section, k, v) |
|
253 | log.debug('settings ui from file[%s]%s:%s', section, k, v) | |
234 | if checkpaths:check_repo_dir(cfg.items('paths')) |
|
|||
235 |
|
||||
236 |
|
254 | |||
237 | elif read_from == 'db': |
|
255 | elif read_from == 'db': | |
238 | hg_ui = get_hg_ui_cached() |
|
256 | hg_ui = get_hg_ui_cached() | |
239 | for ui_ in hg_ui: |
|
257 | for ui_ in hg_ui: | |
240 | if ui_.ui_active: |
|
258 | if ui_.ui_active: | |
241 | log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value) |
|
259 | log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value) | |
242 | baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value) |
|
260 | baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value) | |
243 |
|
261 | |||
244 |
|
262 | |||
245 | return baseui |
|
263 | return baseui | |
246 |
|
264 | |||
247 |
|
265 | |||
248 | def set_rhodecode_config(config): |
|
266 | def set_rhodecode_config(config): | |
249 | hgsettings = get_hg_settings() |
|
267 | hgsettings = get_hg_settings() | |
250 |
|
268 | |||
251 | for k, v in hgsettings.items(): |
|
269 | for k, v in hgsettings.items(): | |
252 | config[k] = v |
|
270 | config[k] = v | |
253 |
|
271 | |||
254 | def invalidate_cache(name, *args): |
|
272 | def invalidate_cache(name, *args): | |
255 | """Invalidates given name cache""" |
|
273 | """Invalidates given name cache""" | |
256 |
|
274 | |||
257 | from beaker.cache import region_invalidate |
|
275 | from beaker.cache import region_invalidate | |
258 | log.info('INVALIDATING CACHE FOR %s', name) |
|
276 | log.info('INVALIDATING CACHE FOR %s', name) | |
259 |
|
277 | |||
260 | """propagate our arguments to make sure invalidation works. First |
|
278 | """propagate our arguments to make sure invalidation works. First | |
261 | argument has to be the name of cached func name give to cache decorator |
|
279 | argument has to be the name of cached func name give to cache decorator | |
262 | without that the invalidation would not work""" |
|
280 | without that the invalidation would not work""" | |
263 | tmp = [name] |
|
281 | tmp = [name] | |
264 | tmp.extend(args) |
|
282 | tmp.extend(args) | |
265 | args = tuple(tmp) |
|
283 | args = tuple(tmp) | |
266 |
|
284 | |||
267 | if name == 'cached_repo_list': |
|
285 | if name == 'cached_repo_list': | |
268 | from rhodecode.model.hg import _get_repos_cached |
|
286 | from rhodecode.model.hg import _get_repos_cached | |
269 | region_invalidate(_get_repos_cached, None, *args) |
|
287 | region_invalidate(_get_repos_cached, None, *args) | |
270 |
|
288 | |||
271 | if name == 'full_changelog': |
|
289 | if name == 'full_changelog': | |
272 | from rhodecode.model.hg import _full_changelog_cached |
|
290 | from rhodecode.model.hg import _full_changelog_cached | |
273 | region_invalidate(_full_changelog_cached, None, *args) |
|
291 | region_invalidate(_full_changelog_cached, None, *args) | |
274 |
|
292 | |||
275 | class EmptyChangeset(BaseChangeset): |
|
293 | class EmptyChangeset(BaseChangeset): | |
276 | """ |
|
294 | """ | |
277 | An dummy empty changeset. |
|
295 | An dummy empty changeset. | |
278 | """ |
|
296 | """ | |
279 |
|
297 | |||
280 | revision = -1 |
|
298 | revision = -1 | |
281 | message = '' |
|
299 | message = '' | |
282 | author = '' |
|
300 | author = '' | |
283 | date = '' |
|
301 | date = '' | |
284 | @LazyProperty |
|
302 | @LazyProperty | |
285 | def raw_id(self): |
|
303 | def raw_id(self): | |
286 | """ |
|
304 | """ | |
287 | Returns raw string identifing this changeset, useful for web |
|
305 | Returns raw string identifying this changeset, useful for web | |
288 | representation. |
|
306 | representation. | |
289 | """ |
|
307 | """ | |
290 | return '0' * 40 |
|
308 | return '0' * 40 | |
291 |
|
309 | |||
292 | @LazyProperty |
|
310 | @LazyProperty | |
293 | def short_id(self): |
|
311 | def short_id(self): | |
294 | return self.raw_id[:12] |
|
312 | return self.raw_id[:12] | |
295 |
|
313 | |||
296 | def get_file_changeset(self, path): |
|
314 | def get_file_changeset(self, path): | |
297 | return self |
|
315 | return self | |
298 |
|
316 | |||
299 | def get_file_content(self, path): |
|
317 | def get_file_content(self, path): | |
300 | return u'' |
|
318 | return u'' | |
301 |
|
319 | |||
302 | def get_file_size(self, path): |
|
320 | def get_file_size(self, path): | |
303 | return 0 |
|
321 | return 0 | |
304 |
|
322 | |||
305 | def repo2db_mapper(initial_repo_list, remove_obsolete=False): |
|
323 | def repo2db_mapper(initial_repo_list, remove_obsolete=False): | |
306 | """ |
|
324 | """ | |
307 | maps all found repositories into db |
|
325 | maps all found repositories into db | |
308 | """ |
|
326 | """ | |
309 |
|
327 | |||
310 | sa = meta.Session() |
|
328 | sa = meta.Session() | |
|
329 | rm = RepoModel(sa) | |||
311 | user = sa.query(User).filter(User.admin == True).first() |
|
330 | user = sa.query(User).filter(User.admin == True).first() | |
312 |
|
331 | |||
313 | rm = RepoModel() |
|
332 | for name, repo in initial_repo_list.items(): | |
|
333 | if not rm.get(name, cache=False): | |||
|
334 | log.info('repository %s not found creating default', name) | |||
314 |
|
335 | |||
315 | for name, repo in initial_repo_list.items(): |
|
336 | if isinstance(repo, MercurialRepository): | |
316 | if not RepoModel(sa).get(name, cache=False): |
|
337 | repo_type = 'hg' | |
317 | log.info('repository %s not found creating default', name) |
|
338 | if isinstance(repo, GitRepository): | |
|
339 | repo_type = 'git' | |||
318 |
|
340 | |||
319 | form_data = { |
|
341 | form_data = { | |
320 | 'repo_name':name, |
|
342 | 'repo_name':name, | |
|
343 | 'repo_type':repo_type, | |||
321 | 'description':repo.description if repo.description != 'unknown' else \ |
|
344 | 'description':repo.description if repo.description != 'unknown' else \ | |
322 | 'auto description for %s' % name, |
|
345 | 'auto description for %s' % name, | |
323 | 'private':False |
|
346 | 'private':False | |
324 | } |
|
347 | } | |
325 | rm.create(form_data, user, just_db=True) |
|
348 | rm.create(form_data, user, just_db=True) | |
326 |
|
349 | |||
327 |
|
350 | |||
328 | if remove_obsolete: |
|
351 | if remove_obsolete: | |
329 | #remove from database those repositories that are not in the filesystem |
|
352 | #remove from database those repositories that are not in the filesystem | |
330 | for repo in sa.query(Repository).all(): |
|
353 | for repo in sa.query(Repository).all(): | |
331 | if repo.repo_name not in initial_repo_list.keys(): |
|
354 | if repo.repo_name not in initial_repo_list.keys(): | |
332 | sa.delete(repo) |
|
355 | sa.delete(repo) | |
333 | sa.commit() |
|
356 | sa.commit() | |
334 |
|
357 | |||
335 |
|
358 | |||
336 | meta.Session.remove() |
|
359 | meta.Session.remove() | |
337 |
|
360 | |||
338 | from UserDict import DictMixin |
|
|||
339 |
|
361 | |||
340 | class OrderedDict(dict, DictMixin): |
|
362 | class OrderedDict(dict, DictMixin): | |
341 |
|
363 | |||
342 | def __init__(self, *args, **kwds): |
|
364 | def __init__(self, *args, **kwds): | |
343 | if len(args) > 1: |
|
365 | if len(args) > 1: | |
344 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) |
|
366 | raise TypeError('expected at most 1 arguments, got %d' % len(args)) | |
345 | try: |
|
367 | try: | |
346 | self.__end |
|
368 | self.__end | |
347 | except AttributeError: |
|
369 | except AttributeError: | |
348 | self.clear() |
|
370 | self.clear() | |
349 | self.update(*args, **kwds) |
|
371 | self.update(*args, **kwds) | |
350 |
|
372 | |||
351 | def clear(self): |
|
373 | def clear(self): | |
352 | self.__end = end = [] |
|
374 | self.__end = end = [] | |
353 | end += [None, end, end] # sentinel node for doubly linked list |
|
375 | end += [None, end, end] # sentinel node for doubly linked list | |
354 | self.__map = {} # key --> [key, prev, next] |
|
376 | self.__map = {} # key --> [key, prev, next] | |
355 | dict.clear(self) |
|
377 | dict.clear(self) | |
356 |
|
378 | |||
357 | def __setitem__(self, key, value): |
|
379 | def __setitem__(self, key, value): | |
358 | if key not in self: |
|
380 | if key not in self: | |
359 | end = self.__end |
|
381 | end = self.__end | |
360 | curr = end[1] |
|
382 | curr = end[1] | |
361 | curr[2] = end[1] = self.__map[key] = [key, curr, end] |
|
383 | curr[2] = end[1] = self.__map[key] = [key, curr, end] | |
362 | dict.__setitem__(self, key, value) |
|
384 | dict.__setitem__(self, key, value) | |
363 |
|
385 | |||
364 | def __delitem__(self, key): |
|
386 | def __delitem__(self, key): | |
365 | dict.__delitem__(self, key) |
|
387 | dict.__delitem__(self, key) | |
366 | key, prev, next = self.__map.pop(key) |
|
388 | key, prev, next = self.__map.pop(key) | |
367 | prev[2] = next |
|
389 | prev[2] = next | |
368 | next[1] = prev |
|
390 | next[1] = prev | |
369 |
|
391 | |||
370 | def __iter__(self): |
|
392 | def __iter__(self): | |
371 | end = self.__end |
|
393 | end = self.__end | |
372 | curr = end[2] |
|
394 | curr = end[2] | |
373 | while curr is not end: |
|
395 | while curr is not end: | |
374 | yield curr[0] |
|
396 | yield curr[0] | |
375 | curr = curr[2] |
|
397 | curr = curr[2] | |
376 |
|
398 | |||
377 | def __reversed__(self): |
|
399 | def __reversed__(self): | |
378 | end = self.__end |
|
400 | end = self.__end | |
379 | curr = end[1] |
|
401 | curr = end[1] | |
380 | while curr is not end: |
|
402 | while curr is not end: | |
381 | yield curr[0] |
|
403 | yield curr[0] | |
382 | curr = curr[1] |
|
404 | curr = curr[1] | |
383 |
|
405 | |||
384 | def popitem(self, last=True): |
|
406 | def popitem(self, last=True): | |
385 | if not self: |
|
407 | if not self: | |
386 | raise KeyError('dictionary is empty') |
|
408 | raise KeyError('dictionary is empty') | |
387 | if last: |
|
409 | if last: | |
388 | key = reversed(self).next() |
|
410 | key = reversed(self).next() | |
389 | else: |
|
411 | else: | |
390 | key = iter(self).next() |
|
412 | key = iter(self).next() | |
391 | value = self.pop(key) |
|
413 | value = self.pop(key) | |
392 | return key, value |
|
414 | return key, value | |
393 |
|
415 | |||
394 | def __reduce__(self): |
|
416 | def __reduce__(self): | |
395 | items = [[k, self[k]] for k in self] |
|
417 | items = [[k, self[k]] for k in self] | |
396 | tmp = self.__map, self.__end |
|
418 | tmp = self.__map, self.__end | |
397 | del self.__map, self.__end |
|
419 | del self.__map, self.__end | |
398 | inst_dict = vars(self).copy() |
|
420 | inst_dict = vars(self).copy() | |
399 | self.__map, self.__end = tmp |
|
421 | self.__map, self.__end = tmp | |
400 | if inst_dict: |
|
422 | if inst_dict: | |
401 | return (self.__class__, (items,), inst_dict) |
|
423 | return (self.__class__, (items,), inst_dict) | |
402 | return self.__class__, (items,) |
|
424 | return self.__class__, (items,) | |
403 |
|
425 | |||
404 | def keys(self): |
|
426 | def keys(self): | |
405 | return list(self) |
|
427 | return list(self) | |
406 |
|
428 | |||
407 | setdefault = DictMixin.setdefault |
|
429 | setdefault = DictMixin.setdefault | |
408 | update = DictMixin.update |
|
430 | update = DictMixin.update | |
409 | pop = DictMixin.pop |
|
431 | pop = DictMixin.pop | |
410 | values = DictMixin.values |
|
432 | values = DictMixin.values | |
411 | items = DictMixin.items |
|
433 | items = DictMixin.items | |
412 | iterkeys = DictMixin.iterkeys |
|
434 | iterkeys = DictMixin.iterkeys | |
413 | itervalues = DictMixin.itervalues |
|
435 | itervalues = DictMixin.itervalues | |
414 | iteritems = DictMixin.iteritems |
|
436 | iteritems = DictMixin.iteritems | |
415 |
|
437 | |||
416 | def __repr__(self): |
|
438 | def __repr__(self): | |
417 | if not self: |
|
439 | if not self: | |
418 | return '%s()' % (self.__class__.__name__,) |
|
440 | return '%s()' % (self.__class__.__name__,) | |
419 | return '%s(%r)' % (self.__class__.__name__, self.items()) |
|
441 | return '%s(%r)' % (self.__class__.__name__, self.items()) | |
420 |
|
442 | |||
421 | def copy(self): |
|
443 | def copy(self): | |
422 | return self.__class__(self) |
|
444 | return self.__class__(self) | |
423 |
|
445 | |||
424 | @classmethod |
|
446 | @classmethod | |
425 | def fromkeys(cls, iterable, value=None): |
|
447 | def fromkeys(cls, iterable, value=None): | |
426 | d = cls() |
|
448 | d = cls() | |
427 | for key in iterable: |
|
449 | for key in iterable: | |
428 | d[key] = value |
|
450 | d[key] = value | |
429 | return d |
|
451 | return d | |
430 |
|
452 | |||
431 | def __eq__(self, other): |
|
453 | def __eq__(self, other): | |
432 | if isinstance(other, OrderedDict): |
|
454 | if isinstance(other, OrderedDict): | |
433 | return len(self) == len(other) and self.items() == other.items() |
|
455 | return len(self) == len(other) and self.items() == other.items() | |
434 | return dict.__eq__(self, other) |
|
456 | return dict.__eq__(self, other) | |
435 |
|
457 | |||
436 | def __ne__(self, other): |
|
458 | def __ne__(self, other): | |
437 | return not self == other |
|
459 | return not self == other | |
438 |
|
460 | |||
439 |
|
461 | |||
440 | #=============================================================================== |
|
462 | #=============================================================================== | |
441 | # TEST FUNCTIONS AND CREATORS |
|
463 | # TEST FUNCTIONS AND CREATORS | |
442 | #=============================================================================== |
|
464 | #=============================================================================== | |
443 | def create_test_index(repo_location, full_index): |
|
465 | def create_test_index(repo_location, full_index): | |
444 | """Makes default test index |
|
466 | """Makes default test index | |
445 | :param repo_location: |
|
467 | :param repo_location: | |
446 | :param full_index: |
|
468 | :param full_index: | |
447 | """ |
|
469 | """ | |
448 | from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon |
|
470 | from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon | |
449 | from rhodecode.lib.pidlock import DaemonLock, LockHeld |
|
471 | from rhodecode.lib.pidlock import DaemonLock, LockHeld | |
450 | from rhodecode.lib.indexers import IDX_LOCATION |
|
472 | from rhodecode.lib.indexers import IDX_LOCATION | |
451 | import shutil |
|
473 | import shutil | |
452 |
|
474 | |||
453 | if os.path.exists(IDX_LOCATION): |
|
475 | if os.path.exists(IDX_LOCATION): | |
454 | shutil.rmtree(IDX_LOCATION) |
|
476 | shutil.rmtree(IDX_LOCATION) | |
455 |
|
477 | |||
456 | try: |
|
478 | try: | |
457 | l = DaemonLock() |
|
479 | l = DaemonLock() | |
458 | WhooshIndexingDaemon(repo_location=repo_location)\ |
|
480 | WhooshIndexingDaemon(repo_location=repo_location)\ | |
459 | .run(full_index=full_index) |
|
481 | .run(full_index=full_index) | |
460 | l.release() |
|
482 | l.release() | |
461 | except LockHeld: |
|
483 | except LockHeld: | |
462 | pass |
|
484 | pass | |
463 |
|
485 | |||
464 | def create_test_env(repos_test_path, config): |
|
486 | def create_test_env(repos_test_path, config): | |
465 | """Makes a fresh database and |
|
487 | """Makes a fresh database and | |
466 | install test repository into tmp dir |
|
488 | install test repository into tmp dir | |
467 | """ |
|
489 | """ | |
468 | from rhodecode.lib.db_manage import DbManage |
|
490 | from rhodecode.lib.db_manage import DbManage | |
469 | import tarfile |
|
491 | import tarfile | |
470 | import shutil |
|
492 | import shutil | |
471 | from os.path import dirname as dn, join as jn, abspath |
|
493 | from os.path import dirname as dn, join as jn, abspath | |
472 |
|
494 | |||
473 | log = logging.getLogger('TestEnvCreator') |
|
495 | log = logging.getLogger('TestEnvCreator') | |
474 | # create logger |
|
496 | # create logger | |
475 | log.setLevel(logging.DEBUG) |
|
497 | log.setLevel(logging.DEBUG) | |
476 | log.propagate = True |
|
498 | log.propagate = True | |
477 | # create console handler and set level to debug |
|
499 | # create console handler and set level to debug | |
478 | ch = logging.StreamHandler() |
|
500 | ch = logging.StreamHandler() | |
479 | ch.setLevel(logging.DEBUG) |
|
501 | ch.setLevel(logging.DEBUG) | |
480 |
|
502 | |||
481 | # create formatter |
|
503 | # create formatter | |
482 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") |
|
504 | formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") | |
483 |
|
505 | |||
484 | # add formatter to ch |
|
506 | # add formatter to ch | |
485 | ch.setFormatter(formatter) |
|
507 | ch.setFormatter(formatter) | |
486 |
|
508 | |||
487 | # add ch to logger |
|
509 | # add ch to logger | |
488 | log.addHandler(ch) |
|
510 | log.addHandler(ch) | |
489 |
|
511 | |||
490 | #PART ONE create db |
|
512 | #PART ONE create db | |
491 | dbname = config['sqlalchemy.db1.url'].split('/')[-1] |
|
513 | dbname = config['sqlalchemy.db1.url'].split('/')[-1] | |
492 | log.debug('making test db %s', dbname) |
|
514 | log.debug('making test db %s', dbname) | |
493 |
|
515 | |||
494 | dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'], |
|
516 | dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'], | |
495 | tests=True) |
|
517 | tests=True) | |
496 | dbmanage.create_tables(override=True) |
|
518 | dbmanage.create_tables(override=True) | |
497 | dbmanage.config_prompt(repos_test_path) |
|
519 | dbmanage.config_prompt(repos_test_path) | |
498 | dbmanage.create_default_user() |
|
520 | dbmanage.create_default_user() | |
499 | dbmanage.admin_prompt() |
|
521 | dbmanage.admin_prompt() | |
500 | dbmanage.create_permissions() |
|
522 | dbmanage.create_permissions() | |
501 | dbmanage.populate_default_permissions() |
|
523 | dbmanage.populate_default_permissions() | |
502 |
|
524 | |||
503 | #PART TWO make test repo |
|
525 | #PART TWO make test repo | |
504 | log.debug('making test vcs repo') |
|
526 | log.debug('making test vcs repo') | |
505 | if os.path.isdir('/tmp/vcs_test'): |
|
527 | if os.path.isdir('/tmp/vcs_test'): | |
506 | shutil.rmtree('/tmp/vcs_test') |
|
528 | shutil.rmtree('/tmp/vcs_test') | |
507 |
|
529 | |||
508 | cur_dir = dn(dn(abspath(__file__))) |
|
530 | cur_dir = dn(dn(abspath(__file__))) | |
509 | tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz")) |
|
531 | tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz")) | |
510 | tar.extractall('/tmp') |
|
532 | tar.extractall('/tmp') | |
511 | tar.close() |
|
533 | tar.close() |
@@ -1,139 +1,140 b'' | |||||
1 | from rhodecode.model.meta import Base |
|
1 | from rhodecode.model.meta import Base | |
2 | from sqlalchemy import * |
|
2 | from sqlalchemy import * | |
3 | from sqlalchemy.orm import relation, backref |
|
3 | from sqlalchemy.orm import relation, backref | |
4 | from sqlalchemy.orm.session import Session |
|
4 | from sqlalchemy.orm.session import Session | |
5 | from vcs.utils.lazy import LazyProperty |
|
5 | from vcs.utils.lazy import LazyProperty | |
6 | import logging |
|
6 | import logging | |
7 |
|
7 | |||
8 | log = logging.getLogger(__name__) |
|
8 | log = logging.getLogger(__name__) | |
9 |
|
9 | |||
10 | class RhodeCodeSettings(Base): |
|
10 | class RhodeCodeSettings(Base): | |
11 | __tablename__ = 'rhodecode_settings' |
|
11 | __tablename__ = 'rhodecode_settings' | |
12 | __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True}) |
|
12 | __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True}) | |
13 | app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
13 | app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
14 | app_settings_name = Column("app_settings_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
14 | app_settings_name = Column("app_settings_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
15 | app_settings_value = Column("app_settings_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
15 | app_settings_value = Column("app_settings_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
16 |
|
16 | |||
17 | class RhodeCodeUi(Base): |
|
17 | class RhodeCodeUi(Base): | |
18 | __tablename__ = 'rhodecode_ui' |
|
18 | __tablename__ = 'rhodecode_ui' | |
19 | __table_args__ = {'useexisting':True} |
|
19 | __table_args__ = {'useexisting':True} | |
20 | ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
20 | ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
21 | ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
21 | ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
22 | ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
22 | ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
23 | ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
23 | ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
24 | ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=True) |
|
24 | ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=True) | |
25 |
|
25 | |||
26 |
|
26 | |||
27 |
class User(Base): |
|
27 | class User(Base): | |
28 | __tablename__ = 'users' |
|
28 | __tablename__ = 'users' | |
29 | __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True}) |
|
29 | __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True}) | |
30 | user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
30 | user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
31 | username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
31 | username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
32 | password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
32 | password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
33 | active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None) |
|
33 | active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None) | |
34 | admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False) |
|
34 | admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False) | |
35 | name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
35 | name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
36 | lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
36 | lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
37 | email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
37 | email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
38 | last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None) |
|
38 | last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None) | |
39 |
|
39 | |||
40 | user_log = relation('UserLog') |
|
40 | user_log = relation('UserLog') | |
41 | user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id") |
|
41 | user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id") | |
42 |
|
42 | |||
43 | @LazyProperty |
|
43 | @LazyProperty | |
44 | def full_contact(self): |
|
44 | def full_contact(self): | |
45 | return '%s %s <%s>' % (self.name, self.lastname, self.email) |
|
45 | return '%s %s <%s>' % (self.name, self.lastname, self.email) | |
46 |
|
46 | |||
47 | def __repr__(self): |
|
47 | def __repr__(self): | |
48 | return "<User('id:%s:%s')>" % (self.user_id, self.username) |
|
48 | return "<User('id:%s:%s')>" % (self.user_id, self.username) | |
49 |
|
49 | |||
50 | def update_lastlogin(self): |
|
50 | def update_lastlogin(self): | |
51 | """Update user lastlogin""" |
|
51 | """Update user lastlogin""" | |
52 | import datetime |
|
52 | import datetime | |
53 |
|
53 | |||
54 | try: |
|
54 | try: | |
55 | session = Session.object_session(self) |
|
55 | session = Session.object_session(self) | |
56 | self.last_login = datetime.datetime.now() |
|
56 | self.last_login = datetime.datetime.now() | |
57 | session.add(self) |
|
57 | session.add(self) | |
58 | session.commit() |
|
58 | session.commit() | |
59 | log.debug('updated user %s lastlogin', self.username) |
|
59 | log.debug('updated user %s lastlogin', self.username) | |
60 | except Exception: |
|
60 | except Exception: | |
61 |
session.rollback() |
|
61 | session.rollback() | |
62 |
|
62 | |||
63 |
|
63 | |||
64 |
class UserLog(Base): |
|
64 | class UserLog(Base): | |
65 | __tablename__ = 'user_logs' |
|
65 | __tablename__ = 'user_logs' | |
66 | __table_args__ = {'useexisting':True} |
|
66 | __table_args__ = {'useexisting':True} | |
67 | user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
67 | user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
68 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) |
|
68 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) | |
69 | repository_id = Column("repository_id", INTEGER(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) |
|
69 | repository_id = Column("repository_id", INTEGER(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) | |
70 | repository_name = Column("repository_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
70 | repository_name = Column("repository_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
71 |
user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
71 | user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
72 | action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
72 | action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
73 | action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None) |
|
73 | action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None) | |
74 | revision = Column('revision', TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
74 | revision = Column('revision', TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
75 |
|
75 | |||
76 | user = relation('User') |
|
76 | user = relation('User') | |
77 | repository = relation('Repository') |
|
77 | repository = relation('Repository') | |
78 |
|
78 | |||
79 | class Repository(Base): |
|
79 | class Repository(Base): | |
80 | __tablename__ = 'repositories' |
|
80 | __tablename__ = 'repositories' | |
81 | __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},) |
|
81 | __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},) | |
82 | repo_id = Column("repo_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
82 | repo_id = Column("repo_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
83 | repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) |
|
83 | repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) | |
|
84 | repo_type = Column("repo_type", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) | |||
84 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None) |
|
85 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None) | |
85 | private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None) |
|
86 | private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None) | |
86 | description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
87 | description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
87 | fork_id = Column("fork_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None) |
|
88 | fork_id = Column("fork_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None) | |
88 |
|
89 | |||
89 | user = relation('User') |
|
90 | user = relation('User') | |
90 | fork = relation('Repository', remote_side=repo_id) |
|
91 | fork = relation('Repository', remote_side=repo_id) | |
91 | repo_to_perm = relation('RepoToPerm', cascade='all') |
|
92 | repo_to_perm = relation('RepoToPerm', cascade='all') | |
92 |
|
93 | |||
93 | def __repr__(self): |
|
94 | def __repr__(self): | |
94 | return "<Repository('id:%s:%s')>" % (self.repo_id, self.repo_name) |
|
95 | return "<Repository('id:%s:%s')>" % (self.repo_id, self.repo_name) | |
95 |
|
96 | |||
96 | class Permission(Base): |
|
97 | class Permission(Base): | |
97 | __tablename__ = 'permissions' |
|
98 | __tablename__ = 'permissions' | |
98 | __table_args__ = {'useexisting':True} |
|
99 | __table_args__ = {'useexisting':True} | |
99 | permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
100 | permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
100 | permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
101 | permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
101 | permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
102 | permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
102 |
|
103 | |||
103 | def __repr__(self): |
|
104 | def __repr__(self): | |
104 | return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name) |
|
105 | return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name) | |
105 |
|
106 | |||
106 | class RepoToPerm(Base): |
|
107 | class RepoToPerm(Base): | |
107 | __tablename__ = 'repo_to_perm' |
|
108 | __tablename__ = 'repo_to_perm' | |
108 | __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True}) |
|
109 | __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True}) | |
109 | repo_to_perm_id = Column("repo_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
110 | repo_to_perm_id = Column("repo_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
110 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) |
|
111 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) | |
111 | permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) |
|
112 | permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) | |
112 |
repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) |
|
113 | repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) | |
113 |
|
114 | |||
114 | user = relation('User') |
|
115 | user = relation('User') | |
115 | permission = relation('Permission') |
|
116 | permission = relation('Permission') | |
116 | repository = relation('Repository') |
|
117 | repository = relation('Repository') | |
117 |
|
118 | |||
118 | class UserToPerm(Base): |
|
119 | class UserToPerm(Base): | |
119 | __tablename__ = 'user_to_perm' |
|
120 | __tablename__ = 'user_to_perm' | |
120 | __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True}) |
|
121 | __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True}) | |
121 | user_to_perm_id = Column("user_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
122 | user_to_perm_id = Column("user_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
122 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) |
|
123 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) | |
123 | permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) |
|
124 | permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None) | |
124 |
|
125 | |||
125 | user = relation('User') |
|
126 | user = relation('User') | |
126 | permission = relation('Permission') |
|
127 | permission = relation('Permission') | |
127 |
|
128 | |||
128 | class Statistics(Base): |
|
129 | class Statistics(Base): | |
129 | __tablename__ = 'statistics' |
|
130 | __tablename__ = 'statistics' | |
130 | __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True}) |
|
131 | __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True}) | |
131 | stat_id = Column("stat_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) |
|
132 | stat_id = Column("stat_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
132 | repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None) |
|
133 | repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None) | |
133 | stat_on_revision = Column("stat_on_revision", INTEGER(), nullable=False) |
|
134 | stat_on_revision = Column("stat_on_revision", INTEGER(), nullable=False) | |
134 | commit_activity = Column("commit_activity", BLOB(), nullable=False)#JSON data |
|
135 | commit_activity = Column("commit_activity", BLOB(), nullable=False)#JSON data | |
135 | commit_activity_combined = Column("commit_activity_combined", BLOB(), nullable=False)#JSON data |
|
136 | commit_activity_combined = Column("commit_activity_combined", BLOB(), nullable=False)#JSON data | |
136 | languages = Column("languages", BLOB(), nullable=False)#JSON data |
|
137 | languages = Column("languages", BLOB(), nullable=False)#JSON data | |
137 |
|
138 | |||
138 | repository = relation('Repository') |
|
139 | repository = relation('Repository') | |
139 |
|
140 |
@@ -1,357 +1,353 b'' | |||||
1 | """ this is forms validation classes |
|
1 | """ this is forms validation classes | |
2 | http://formencode.org/module-formencode.validators.html |
|
2 | http://formencode.org/module-formencode.validators.html | |
3 | for list off all availible validators |
|
3 | for list off all availible validators | |
4 |
|
4 | |||
5 | we can create our own validators |
|
5 | we can create our own validators | |
6 |
|
6 | |||
7 | The table below outlines the options which can be used in a schema in addition to the validators themselves |
|
7 | The table below outlines the options which can be used in a schema in addition to the validators themselves | |
8 | pre_validators [] These validators will be applied before the schema |
|
8 | pre_validators [] These validators will be applied before the schema | |
9 | chained_validators [] These validators will be applied after the schema |
|
9 | chained_validators [] These validators will be applied after the schema | |
10 | allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present |
|
10 | allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present | |
11 | filter_extra_fields False If True, then keys that aren't associated with a validator are removed |
|
11 | filter_extra_fields False If True, then keys that aren't associated with a validator are removed | |
12 | if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value. |
|
12 | if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value. | |
13 | ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already |
|
13 | ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already | |
14 |
|
14 | |||
15 |
|
15 | |||
16 | <name> = formencode.validators.<name of validator> |
|
16 | <name> = formencode.validators.<name of validator> | |
17 | <name> must equal form name |
|
17 | <name> must equal form name | |
18 | list=[1,2,3,4,5] |
|
18 | list=[1,2,3,4,5] | |
19 | for SELECT use formencode.All(OneOf(list), Int()) |
|
19 | for SELECT use formencode.All(OneOf(list), Int()) | |
20 |
|
20 | |||
21 | """ |
|
21 | """ | |
22 | from formencode import All |
|
22 | from formencode import All | |
23 | from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \ |
|
23 | from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \ | |
24 | Email, Bool, StringBoolean |
|
24 | Email, Bool, StringBoolean | |
25 | from pylons import session |
|
25 | from pylons import session | |
26 | from pylons.i18n.translation import _ |
|
26 | from pylons.i18n.translation import _ | |
27 | from rhodecode.lib.auth import check_password, get_crypt_password |
|
27 | from rhodecode.lib.auth import check_password, get_crypt_password | |
28 | from rhodecode.model import meta |
|
28 | from rhodecode.model import meta | |
29 | from rhodecode.model.user import UserModel |
|
29 | from rhodecode.model.user import UserModel | |
30 | from rhodecode.model.repo import RepoModel |
|
30 | from rhodecode.model.repo import RepoModel | |
31 | from rhodecode.model.db import User |
|
31 | from rhodecode.model.db import User | |
32 | from webhelpers.pylonslib.secure_form import authentication_token |
|
32 | from webhelpers.pylonslib.secure_form import authentication_token | |
33 | import formencode |
|
33 | import formencode | |
34 | import logging |
|
34 | import logging | |
35 | import os |
|
35 | import os | |
36 | import rhodecode.lib.helpers as h |
|
36 | import rhodecode.lib.helpers as h | |
37 |
|
37 | |||
38 | log = logging.getLogger(__name__) |
|
38 | log = logging.getLogger(__name__) | |
39 |
|
39 | |||
40 | #this is needed to translate the messages using _() in validators |
|
40 | #this is needed to translate the messages using _() in validators | |
41 | class State_obj(object): |
|
41 | class State_obj(object): | |
42 | _ = staticmethod(_) |
|
42 | _ = staticmethod(_) | |
43 |
|
43 | |||
44 | #=============================================================================== |
|
44 | #=============================================================================== | |
45 | # VALIDATORS |
|
45 | # VALIDATORS | |
46 | #=============================================================================== |
|
46 | #=============================================================================== | |
47 | class ValidAuthToken(formencode.validators.FancyValidator): |
|
47 | class ValidAuthToken(formencode.validators.FancyValidator): | |
48 | messages = {'invalid_token':_('Token mismatch')} |
|
48 | messages = {'invalid_token':_('Token mismatch')} | |
49 |
|
49 | |||
50 | def validate_python(self, value, state): |
|
50 | def validate_python(self, value, state): | |
51 |
|
51 | |||
52 | if value != authentication_token(): |
|
52 | if value != authentication_token(): | |
53 | raise formencode.Invalid(self.message('invalid_token', state, |
|
53 | raise formencode.Invalid(self.message('invalid_token', state, | |
54 | search_number=value), value, state) |
|
54 | search_number=value), value, state) | |
55 |
|
55 | |||
56 | def ValidUsername(edit, old_data): |
|
56 | def ValidUsername(edit, old_data): | |
57 | class _ValidUsername(formencode.validators.FancyValidator): |
|
57 | class _ValidUsername(formencode.validators.FancyValidator): | |
58 |
|
58 | |||
59 | def validate_python(self, value, state): |
|
59 | def validate_python(self, value, state): | |
60 | if value in ['default', 'new_user']: |
|
60 | if value in ['default', 'new_user']: | |
61 | raise formencode.Invalid(_('Invalid username'), value, state) |
|
61 | raise formencode.Invalid(_('Invalid username'), value, state) | |
62 | #check if user is unique |
|
62 | #check if user is unique | |
63 | old_un = None |
|
63 | old_un = None | |
64 | if edit: |
|
64 | if edit: | |
65 | old_un = UserModel().get(old_data.get('user_id')).username |
|
65 | old_un = UserModel().get(old_data.get('user_id')).username | |
66 |
|
66 | |||
67 | if old_un != value or not edit: |
|
67 | if old_un != value or not edit: | |
68 | if UserModel().get_by_username(value, cache=False): |
|
68 | if UserModel().get_by_username(value, cache=False): | |
69 | raise formencode.Invalid(_('This username already exists') , |
|
69 | raise formencode.Invalid(_('This username already exists') , | |
70 | value, state) |
|
70 | value, state) | |
71 |
|
71 | |||
72 | return _ValidUsername |
|
72 | return _ValidUsername | |
73 |
|
73 | |||
74 | class ValidPassword(formencode.validators.FancyValidator): |
|
74 | class ValidPassword(formencode.validators.FancyValidator): | |
75 |
|
75 | |||
76 | def to_python(self, value, state): |
|
76 | def to_python(self, value, state): | |
77 | if value: |
|
77 | if value: | |
78 | return get_crypt_password(value) |
|
78 | return get_crypt_password(value) | |
79 |
|
79 | |||
80 | class ValidAuth(formencode.validators.FancyValidator): |
|
80 | class ValidAuth(formencode.validators.FancyValidator): | |
81 | messages = { |
|
81 | messages = { | |
82 | 'invalid_password':_('invalid password'), |
|
82 | 'invalid_password':_('invalid password'), | |
83 | 'invalid_login':_('invalid user name'), |
|
83 | 'invalid_login':_('invalid user name'), | |
84 | 'disabled_account':_('Your acccount is disabled') |
|
84 | 'disabled_account':_('Your acccount is disabled') | |
85 |
|
85 | |||
86 | } |
|
86 | } | |
87 | #error mapping |
|
87 | #error mapping | |
88 | e_dict = {'username':messages['invalid_login'], |
|
88 | e_dict = {'username':messages['invalid_login'], | |
89 | 'password':messages['invalid_password']} |
|
89 | 'password':messages['invalid_password']} | |
90 | e_dict_disable = {'username':messages['disabled_account']} |
|
90 | e_dict_disable = {'username':messages['disabled_account']} | |
91 |
|
91 | |||
92 | def validate_python(self, value, state): |
|
92 | def validate_python(self, value, state): | |
93 | password = value['password'] |
|
93 | password = value['password'] | |
94 | username = value['username'] |
|
94 | username = value['username'] | |
95 | user = UserModel().get_by_username(username) |
|
95 | user = UserModel().get_by_username(username) | |
96 | if user is None: |
|
96 | if user is None: | |
97 | raise formencode.Invalid(self.message('invalid_password', |
|
97 | raise formencode.Invalid(self.message('invalid_password', | |
98 | state=State_obj), value, state, |
|
98 | state=State_obj), value, state, | |
99 | error_dict=self.e_dict) |
|
99 | error_dict=self.e_dict) | |
100 | if user: |
|
100 | if user: | |
101 | if user.active: |
|
101 | if user.active: | |
102 | if user.username == username and check_password(password, |
|
102 | if user.username == username and check_password(password, | |
103 | user.password): |
|
103 | user.password): | |
104 | return value |
|
104 | return value | |
105 | else: |
|
105 | else: | |
106 | log.warning('user %s not authenticated', username) |
|
106 | log.warning('user %s not authenticated', username) | |
107 | raise formencode.Invalid(self.message('invalid_password', |
|
107 | raise formencode.Invalid(self.message('invalid_password', | |
108 | state=State_obj), value, state, |
|
108 | state=State_obj), value, state, | |
109 | error_dict=self.e_dict) |
|
109 | error_dict=self.e_dict) | |
110 | else: |
|
110 | else: | |
111 | log.warning('user %s is disabled', username) |
|
111 | log.warning('user %s is disabled', username) | |
112 | raise formencode.Invalid(self.message('disabled_account', |
|
112 | raise formencode.Invalid(self.message('disabled_account', | |
113 | state=State_obj), |
|
113 | state=State_obj), | |
114 | value, state, |
|
114 | value, state, | |
115 | error_dict=self.e_dict_disable) |
|
115 | error_dict=self.e_dict_disable) | |
116 |
|
116 | |||
117 | class ValidRepoUser(formencode.validators.FancyValidator): |
|
117 | class ValidRepoUser(formencode.validators.FancyValidator): | |
118 |
|
118 | |||
119 | def to_python(self, value, state): |
|
119 | def to_python(self, value, state): | |
120 | sa = meta.Session() |
|
120 | sa = meta.Session() | |
121 | try: |
|
121 | try: | |
122 | self.user_db = sa.query(User)\ |
|
122 | self.user_db = sa.query(User)\ | |
123 | .filter(User.active == True)\ |
|
123 | .filter(User.active == True)\ | |
124 | .filter(User.username == value).one() |
|
124 | .filter(User.username == value).one() | |
125 | except Exception: |
|
125 | except Exception: | |
126 | raise formencode.Invalid(_('This username is not valid'), |
|
126 | raise formencode.Invalid(_('This username is not valid'), | |
127 | value, state) |
|
127 | value, state) | |
128 | finally: |
|
128 | finally: | |
129 | meta.Session.remove() |
|
129 | meta.Session.remove() | |
130 |
|
130 | |||
131 | return self.user_db.user_id |
|
131 | return self.user_db.user_id | |
132 |
|
132 | |||
133 | def ValidRepoName(edit, old_data): |
|
133 | def ValidRepoName(edit, old_data): | |
134 | class _ValidRepoName(formencode.validators.FancyValidator): |
|
134 | class _ValidRepoName(formencode.validators.FancyValidator): | |
135 |
|
135 | |||
136 | def to_python(self, value, state): |
|
136 | def to_python(self, value, state): | |
137 | slug = h.repo_name_slug(value) |
|
137 | slug = h.repo_name_slug(value) | |
138 | if slug in ['_admin']: |
|
138 | if slug in ['_admin']: | |
139 | raise formencode.Invalid(_('This repository name is disallowed'), |
|
139 | raise formencode.Invalid(_('This repository name is disallowed'), | |
140 | value, state) |
|
140 | value, state) | |
141 | if old_data.get('repo_name') != value or not edit: |
|
141 | if old_data.get('repo_name') != value or not edit: | |
142 | if RepoModel().get(slug, cache=False): |
|
142 | if RepoModel().get(slug, cache=False): | |
143 | raise formencode.Invalid(_('This repository already exists') , |
|
143 | raise formencode.Invalid(_('This repository already exists') , | |
144 | value, state) |
|
144 | value, state) | |
145 | return slug |
|
145 | return slug | |
146 |
|
146 | |||
147 |
|
147 | |||
148 | return _ValidRepoName |
|
148 | return _ValidRepoName | |
149 |
|
149 | |||
150 | class ValidPerms(formencode.validators.FancyValidator): |
|
150 | class ValidPerms(formencode.validators.FancyValidator): | |
151 | messages = {'perm_new_user_name':_('This username is not valid')} |
|
151 | messages = {'perm_new_user_name':_('This username is not valid')} | |
152 |
|
152 | |||
153 | def to_python(self, value, state): |
|
153 | def to_python(self, value, state): | |
154 | perms_update = [] |
|
154 | perms_update = [] | |
155 | perms_new = [] |
|
155 | perms_new = [] | |
156 | #build a list of permission to update and new permission to create |
|
156 | #build a list of permission to update and new permission to create | |
157 | for k, v in value.items(): |
|
157 | for k, v in value.items(): | |
158 | if k.startswith('perm_'): |
|
158 | if k.startswith('perm_'): | |
159 | if k.startswith('perm_new_user'): |
|
159 | if k.startswith('perm_new_user'): | |
160 | new_perm = value.get('perm_new_user', False) |
|
160 | new_perm = value.get('perm_new_user', False) | |
161 | new_user = value.get('perm_new_user_name', False) |
|
161 | new_user = value.get('perm_new_user_name', False) | |
162 | if new_user and new_perm: |
|
162 | if new_user and new_perm: | |
163 | if (new_user, new_perm) not in perms_new: |
|
163 | if (new_user, new_perm) not in perms_new: | |
164 | perms_new.append((new_user, new_perm)) |
|
164 | perms_new.append((new_user, new_perm)) | |
165 | else: |
|
165 | else: | |
166 | usr = k[5:] |
|
166 | usr = k[5:] | |
167 | if usr == 'default': |
|
167 | if usr == 'default': | |
168 | if value['private']: |
|
168 | if value['private']: | |
169 | #set none for default when updating to private repo |
|
169 | #set none for default when updating to private repo | |
170 | v = 'repository.none' |
|
170 | v = 'repository.none' | |
171 | perms_update.append((usr, v)) |
|
171 | perms_update.append((usr, v)) | |
172 | value['perms_updates'] = perms_update |
|
172 | value['perms_updates'] = perms_update | |
173 | value['perms_new'] = perms_new |
|
173 | value['perms_new'] = perms_new | |
174 | sa = meta.Session |
|
174 | sa = meta.Session | |
175 | for k, v in perms_new: |
|
175 | for k, v in perms_new: | |
176 | try: |
|
176 | try: | |
177 | self.user_db = sa.query(User)\ |
|
177 | self.user_db = sa.query(User)\ | |
178 | .filter(User.active == True)\ |
|
178 | .filter(User.active == True)\ | |
179 | .filter(User.username == k).one() |
|
179 | .filter(User.username == k).one() | |
180 | except Exception: |
|
180 | except Exception: | |
181 | msg = self.message('perm_new_user_name', |
|
181 | msg = self.message('perm_new_user_name', | |
182 | state=State_obj) |
|
182 | state=State_obj) | |
183 | raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg}) |
|
183 | raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg}) | |
184 | return value |
|
184 | return value | |
185 |
|
185 | |||
186 | class ValidSettings(formencode.validators.FancyValidator): |
|
186 | class ValidSettings(formencode.validators.FancyValidator): | |
187 |
|
187 | |||
188 | def to_python(self, value, state): |
|
188 | def to_python(self, value, state): | |
189 | #settings form can't edit user |
|
189 | #settings form can't edit user | |
190 | if value.has_key('user'): |
|
190 | if value.has_key('user'): | |
191 | del['value']['user'] |
|
191 | del['value']['user'] | |
192 |
|
192 | |||
193 | return value |
|
193 | return value | |
194 |
|
194 | |||
195 | class ValidPath(formencode.validators.FancyValidator): |
|
195 | class ValidPath(formencode.validators.FancyValidator): | |
196 | def to_python(self, value, state): |
|
196 | def to_python(self, value, state): | |
197 | isdir = os.path.isdir(value.replace('*', '')) |
|
197 | ||
198 | if (value.endswith('/*') or value.endswith('/**')) and isdir: |
|
198 | if not os.path.isdir(value): | |
199 | return value |
|
|||
200 | elif not isdir: |
|
|||
201 | msg = _('This is not a valid path') |
|
199 | msg = _('This is not a valid path') | |
202 | else: |
|
|||
203 | msg = _('You need to specify * or ** at the end of path (ie. /tmp/*)') |
|
|||
204 |
|
||||
205 | raise formencode.Invalid(msg, value, state, |
|
200 | raise formencode.Invalid(msg, value, state, | |
206 | error_dict={'paths_root_path':msg}) |
|
201 | error_dict={'paths_root_path':msg}) | |
|
202 | return value | |||
207 |
|
203 | |||
208 | def UniqSystemEmail(old_data): |
|
204 | def UniqSystemEmail(old_data): | |
209 | class _UniqSystemEmail(formencode.validators.FancyValidator): |
|
205 | class _UniqSystemEmail(formencode.validators.FancyValidator): | |
210 | def to_python(self, value, state): |
|
206 | def to_python(self, value, state): | |
211 | if old_data.get('email') != value: |
|
207 | if old_data.get('email') != value: | |
212 | sa = meta.Session() |
|
208 | sa = meta.Session() | |
213 | try: |
|
209 | try: | |
214 | user = sa.query(User).filter(User.email == value).scalar() |
|
210 | user = sa.query(User).filter(User.email == value).scalar() | |
215 | if user: |
|
211 | if user: | |
216 | raise formencode.Invalid(_("That e-mail address is already taken") , |
|
212 | raise formencode.Invalid(_("That e-mail address is already taken") , | |
217 | value, state) |
|
213 | value, state) | |
218 | finally: |
|
214 | finally: | |
219 | meta.Session.remove() |
|
215 | meta.Session.remove() | |
220 |
|
216 | |||
221 | return value |
|
217 | return value | |
222 |
|
218 | |||
223 | return _UniqSystemEmail |
|
219 | return _UniqSystemEmail | |
224 |
|
220 | |||
225 | class ValidSystemEmail(formencode.validators.FancyValidator): |
|
221 | class ValidSystemEmail(formencode.validators.FancyValidator): | |
226 | def to_python(self, value, state): |
|
222 | def to_python(self, value, state): | |
227 | sa = meta.Session |
|
223 | sa = meta.Session | |
228 | try: |
|
224 | try: | |
229 | user = sa.query(User).filter(User.email == value).scalar() |
|
225 | user = sa.query(User).filter(User.email == value).scalar() | |
230 | if user is None: |
|
226 | if user is None: | |
231 | raise formencode.Invalid(_("That e-mail address doesn't exist.") , |
|
227 | raise formencode.Invalid(_("That e-mail address doesn't exist.") , | |
232 | value, state) |
|
228 | value, state) | |
233 | finally: |
|
229 | finally: | |
234 | meta.Session.remove() |
|
230 | meta.Session.remove() | |
235 |
|
231 | |||
236 | return value |
|
232 | return value | |
237 |
|
233 | |||
238 | #=============================================================================== |
|
234 | #=============================================================================== | |
239 | # FORMS |
|
235 | # FORMS | |
240 | #=============================================================================== |
|
236 | #=============================================================================== | |
241 | class LoginForm(formencode.Schema): |
|
237 | class LoginForm(formencode.Schema): | |
242 | allow_extra_fields = True |
|
238 | allow_extra_fields = True | |
243 | filter_extra_fields = True |
|
239 | filter_extra_fields = True | |
244 | username = UnicodeString( |
|
240 | username = UnicodeString( | |
245 | strip=True, |
|
241 | strip=True, | |
246 | min=1, |
|
242 | min=1, | |
247 | not_empty=True, |
|
243 | not_empty=True, | |
248 | messages={ |
|
244 | messages={ | |
249 | 'empty':_('Please enter a login'), |
|
245 | 'empty':_('Please enter a login'), | |
250 | 'tooShort':_('Enter a value %(min)i characters long or more')} |
|
246 | 'tooShort':_('Enter a value %(min)i characters long or more')} | |
251 | ) |
|
247 | ) | |
252 |
|
248 | |||
253 | password = UnicodeString( |
|
249 | password = UnicodeString( | |
254 | strip=True, |
|
250 | strip=True, | |
255 | min=6, |
|
251 | min=6, | |
256 | not_empty=True, |
|
252 | not_empty=True, | |
257 | messages={ |
|
253 | messages={ | |
258 | 'empty':_('Please enter a password'), |
|
254 | 'empty':_('Please enter a password'), | |
259 | 'tooShort':_('Enter %(min)i characters or more')} |
|
255 | 'tooShort':_('Enter %(min)i characters or more')} | |
260 | ) |
|
256 | ) | |
261 |
|
257 | |||
262 |
|
258 | |||
263 | #chained validators have access to all data |
|
259 | #chained validators have access to all data | |
264 | chained_validators = [ValidAuth] |
|
260 | chained_validators = [ValidAuth] | |
265 |
|
261 | |||
266 | def UserForm(edit=False, old_data={}): |
|
262 | def UserForm(edit=False, old_data={}): | |
267 | class _UserForm(formencode.Schema): |
|
263 | class _UserForm(formencode.Schema): | |
268 | allow_extra_fields = True |
|
264 | allow_extra_fields = True | |
269 | filter_extra_fields = True |
|
265 | filter_extra_fields = True | |
270 | username = All(UnicodeString(strip=True, min=1, not_empty=True), ValidUsername(edit, old_data)) |
|
266 | username = All(UnicodeString(strip=True, min=1, not_empty=True), ValidUsername(edit, old_data)) | |
271 | if edit: |
|
267 | if edit: | |
272 | new_password = All(UnicodeString(strip=True, min=6, not_empty=False), ValidPassword) |
|
268 | new_password = All(UnicodeString(strip=True, min=6, not_empty=False), ValidPassword) | |
273 | admin = StringBoolean(if_missing=False) |
|
269 | admin = StringBoolean(if_missing=False) | |
274 | else: |
|
270 | else: | |
275 | password = All(UnicodeString(strip=True, min=6, not_empty=True), ValidPassword) |
|
271 | password = All(UnicodeString(strip=True, min=6, not_empty=True), ValidPassword) | |
276 | active = StringBoolean(if_missing=False) |
|
272 | active = StringBoolean(if_missing=False) | |
277 | name = UnicodeString(strip=True, min=1, not_empty=True) |
|
273 | name = UnicodeString(strip=True, min=1, not_empty=True) | |
278 | lastname = UnicodeString(strip=True, min=1, not_empty=True) |
|
274 | lastname = UnicodeString(strip=True, min=1, not_empty=True) | |
279 | email = All(Email(not_empty=True), UniqSystemEmail(old_data)) |
|
275 | email = All(Email(not_empty=True), UniqSystemEmail(old_data)) | |
280 |
|
276 | |||
281 | return _UserForm |
|
277 | return _UserForm | |
282 |
|
278 | |||
283 | RegisterForm = UserForm |
|
279 | RegisterForm = UserForm | |
284 |
|
280 | |||
285 | def PasswordResetForm(): |
|
281 | def PasswordResetForm(): | |
286 | class _PasswordResetForm(formencode.Schema): |
|
282 | class _PasswordResetForm(formencode.Schema): | |
287 | allow_extra_fields = True |
|
283 | allow_extra_fields = True | |
288 | filter_extra_fields = True |
|
284 | filter_extra_fields = True | |
289 | email = All(ValidSystemEmail(), Email(not_empty=True)) |
|
285 | email = All(ValidSystemEmail(), Email(not_empty=True)) | |
290 | return _PasswordResetForm |
|
286 | return _PasswordResetForm | |
291 |
|
287 | |||
292 | def RepoForm(edit=False, old_data={}): |
|
288 | def RepoForm(edit=False, old_data={}): | |
293 | class _RepoForm(formencode.Schema): |
|
289 | class _RepoForm(formencode.Schema): | |
294 | allow_extra_fields = True |
|
290 | allow_extra_fields = True | |
295 | filter_extra_fields = False |
|
291 | filter_extra_fields = False | |
296 | repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) |
|
292 | repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) | |
297 | description = UnicodeString(strip=True, min=1, not_empty=True) |
|
293 | description = UnicodeString(strip=True, min=1, not_empty=True) | |
298 | private = StringBoolean(if_missing=False) |
|
294 | private = StringBoolean(if_missing=False) | |
299 |
|
295 | |||
300 | if edit: |
|
296 | if edit: | |
301 | user = All(Int(not_empty=True), ValidRepoUser) |
|
297 | user = All(Int(not_empty=True), ValidRepoUser) | |
302 |
|
298 | |||
303 | chained_validators = [ValidPerms] |
|
299 | chained_validators = [ValidPerms] | |
304 | return _RepoForm |
|
300 | return _RepoForm | |
305 |
|
301 | |||
306 | def RepoForkForm(edit=False, old_data={}): |
|
302 | def RepoForkForm(edit=False, old_data={}): | |
307 | class _RepoForkForm(formencode.Schema): |
|
303 | class _RepoForkForm(formencode.Schema): | |
308 | allow_extra_fields = True |
|
304 | allow_extra_fields = True | |
309 | filter_extra_fields = False |
|
305 | filter_extra_fields = False | |
310 | fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) |
|
306 | fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) | |
311 | description = UnicodeString(strip=True, min=1, not_empty=True) |
|
307 | description = UnicodeString(strip=True, min=1, not_empty=True) | |
312 | private = StringBoolean(if_missing=False) |
|
308 | private = StringBoolean(if_missing=False) | |
313 |
|
309 | |||
314 | return _RepoForkForm |
|
310 | return _RepoForkForm | |
315 |
|
311 | |||
316 | def RepoSettingsForm(edit=False, old_data={}): |
|
312 | def RepoSettingsForm(edit=False, old_data={}): | |
317 | class _RepoForm(formencode.Schema): |
|
313 | class _RepoForm(formencode.Schema): | |
318 | allow_extra_fields = True |
|
314 | allow_extra_fields = True | |
319 | filter_extra_fields = False |
|
315 | filter_extra_fields = False | |
320 | repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) |
|
316 | repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) | |
321 | description = UnicodeString(strip=True, min=1, not_empty=True) |
|
317 | description = UnicodeString(strip=True, min=1, not_empty=True) | |
322 | private = StringBoolean(if_missing=False) |
|
318 | private = StringBoolean(if_missing=False) | |
323 |
|
319 | |||
324 | chained_validators = [ValidPerms, ValidSettings] |
|
320 | chained_validators = [ValidPerms, ValidSettings] | |
325 | return _RepoForm |
|
321 | return _RepoForm | |
326 |
|
322 | |||
327 |
|
323 | |||
328 | def ApplicationSettingsForm(): |
|
324 | def ApplicationSettingsForm(): | |
329 | class _ApplicationSettingsForm(formencode.Schema): |
|
325 | class _ApplicationSettingsForm(formencode.Schema): | |
330 | allow_extra_fields = True |
|
326 | allow_extra_fields = True | |
331 | filter_extra_fields = False |
|
327 | filter_extra_fields = False | |
332 | rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True) |
|
328 | rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True) | |
333 | rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True) |
|
329 | rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True) | |
334 |
|
330 | |||
335 | return _ApplicationSettingsForm |
|
331 | return _ApplicationSettingsForm | |
336 |
|
332 | |||
337 | def ApplicationUiSettingsForm(): |
|
333 | def ApplicationUiSettingsForm(): | |
338 | class _ApplicationUiSettingsForm(formencode.Schema): |
|
334 | class _ApplicationUiSettingsForm(formencode.Schema): | |
339 | allow_extra_fields = True |
|
335 | allow_extra_fields = True | |
340 | filter_extra_fields = False |
|
336 | filter_extra_fields = False | |
341 | web_push_ssl = OneOf(['true', 'false'], if_missing='false') |
|
337 | web_push_ssl = OneOf(['true', 'false'], if_missing='false') | |
342 | paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True)) |
|
338 | paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True)) | |
343 | hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False) |
|
339 | hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False) | |
344 | hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False) |
|
340 | hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False) | |
345 |
|
341 | |||
346 | return _ApplicationUiSettingsForm |
|
342 | return _ApplicationUiSettingsForm | |
347 |
|
343 | |||
348 | def DefaultPermissionsForm(perms_choices, register_choices, create_choices): |
|
344 | def DefaultPermissionsForm(perms_choices, register_choices, create_choices): | |
349 | class _DefaultPermissionsForm(formencode.Schema): |
|
345 | class _DefaultPermissionsForm(formencode.Schema): | |
350 | allow_extra_fields = True |
|
346 | allow_extra_fields = True | |
351 | filter_extra_fields = True |
|
347 | filter_extra_fields = True | |
352 | overwrite_default = OneOf(['true', 'false'], if_missing='false') |
|
348 | overwrite_default = OneOf(['true', 'false'], if_missing='false') | |
353 | default_perm = OneOf(perms_choices) |
|
349 | default_perm = OneOf(perms_choices) | |
354 | default_register = OneOf(register_choices) |
|
350 | default_register = OneOf(register_choices) | |
355 | default_create = OneOf(create_choices) |
|
351 | default_create = OneOf(create_choices) | |
356 |
|
352 | |||
357 | return _DefaultPermissionsForm |
|
353 | return _DefaultPermissionsForm |
@@ -1,185 +1,181 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | #!/usr/bin/env python | |
2 | # encoding: utf-8 |
|
2 | # encoding: utf-8 | |
3 | # Model for RhodeCode |
|
3 | # Model for RhodeCode | |
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> |
|
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> | |
5 | # |
|
5 | # | |
6 | # This program is free software; you can redistribute it and/or |
|
6 | # This program is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU General Public License |
|
7 | # modify it under the terms of the GNU General Public License | |
8 | # as published by the Free Software Foundation; version 2 |
|
8 | # as published by the Free Software Foundation; version 2 | |
9 | # of the License or (at your opinion) any later version of the license. |
|
9 | # of the License or (at your opinion) any later version of the license. | |
10 | # |
|
10 | # | |
11 | # This program is distributed in the hope that it will be useful, |
|
11 | # This program is distributed in the hope that it will be useful, | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | # GNU General Public License for more details. |
|
14 | # GNU General Public License for more details. | |
15 | # |
|
15 | # | |
16 | # You should have received a copy of the GNU General Public License |
|
16 | # You should have received a copy of the GNU General Public License | |
17 | # along with this program; if not, write to the Free Software |
|
17 | # along with this program; if not, write to the Free Software | |
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, |
|
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
19 | # MA 02110-1301, USA. |
|
19 | # MA 02110-1301, USA. | |
20 | """ |
|
20 | """ | |
21 | Created on April 9, 2010 |
|
21 | Created on April 9, 2010 | |
22 | Model for RhodeCode |
|
22 | Model for RhodeCode | |
23 | @author: marcink |
|
23 | @author: marcink | |
24 | """ |
|
24 | """ | |
25 | from beaker.cache import cache_region |
|
25 | from beaker.cache import cache_region | |
26 | from mercurial import ui |
|
26 | from mercurial import ui | |
27 | from mercurial.hgweb.hgwebdir_mod import findrepos |
|
|||
28 | from rhodecode.lib import helpers as h |
|
27 | from rhodecode.lib import helpers as h | |
29 | from rhodecode.lib.utils import invalidate_cache |
|
28 | from rhodecode.lib.utils import invalidate_cache | |
30 | from rhodecode.lib.auth import HasRepoPermissionAny |
|
29 | from rhodecode.lib.auth import HasRepoPermissionAny | |
31 | from rhodecode.model import meta |
|
30 | from rhodecode.model import meta | |
32 | from rhodecode.model.db import Repository, User |
|
31 | from rhodecode.model.db import Repository, User | |
33 | from sqlalchemy.orm import joinedload |
|
32 | from sqlalchemy.orm import joinedload | |
34 | from vcs.exceptions import RepositoryError, VCSError |
|
33 | from vcs.exceptions import RepositoryError, VCSError | |
35 | import logging |
|
34 | import logging | |
36 | import os |
|
|||
37 | import sys |
|
35 | import sys | |
38 | log = logging.getLogger(__name__) |
|
36 | log = logging.getLogger(__name__) | |
39 |
|
37 | |||
40 | try: |
|
38 | try: | |
41 | from vcs.backends.hg import MercurialRepository |
|
39 | from vcs.backends.hg import MercurialRepository | |
|
40 | from vcs.backends.git import GitRepository | |||
42 | except ImportError: |
|
41 | except ImportError: | |
43 | sys.stderr.write('You have to import vcs module') |
|
42 | sys.stderr.write('You have to import vcs module') | |
44 | raise Exception('Unable to import vcs') |
|
43 | raise Exception('Unable to import vcs') | |
45 |
|
44 | |||
46 | def _get_repos_cached_initial(app_globals, initial): |
|
45 | def _get_repos_cached_initial(app_globals, initial): | |
47 | """return cached dict with repos |
|
46 | """return cached dict with repos | |
48 | """ |
|
47 | """ | |
49 | g = app_globals |
|
48 | g = app_globals | |
50 |
return HgModel.repo_scan( |
|
49 | return HgModel().repo_scan(g.paths[0][1], g.baseui, initial) | |
51 |
|
50 | |||
52 | @cache_region('long_term', 'cached_repo_list') |
|
51 | @cache_region('long_term', 'cached_repo_list') | |
53 | def _get_repos_cached(): |
|
52 | def _get_repos_cached(): | |
54 | """return cached dict with repos |
|
53 | """return cached dict with repos | |
55 | """ |
|
54 | """ | |
56 | log.info('getting all repositories list') |
|
55 | log.info('getting all repositories list') | |
57 | from pylons import app_globals as g |
|
56 | from pylons import app_globals as g | |
58 |
return HgModel.repo_scan( |
|
57 | return HgModel().repo_scan(g.paths[0][1], g.baseui) | |
59 |
|
58 | |||
60 | @cache_region('super_short_term', 'cached_repos_switcher_list') |
|
59 | @cache_region('super_short_term', 'cached_repos_switcher_list') | |
61 | def _get_repos_switcher_cached(cached_repo_list): |
|
60 | def _get_repos_switcher_cached(cached_repo_list): | |
62 | repos_lst = [] |
|
61 | repos_lst = [] | |
63 | for repo in [x for x in cached_repo_list.values()]: |
|
62 | for repo in [x for x in cached_repo_list.values()]: | |
64 | if HasRepoPermissionAny('repository.write', 'repository.read', |
|
63 | if HasRepoPermissionAny('repository.write', 'repository.read', | |
65 | 'repository.admin')(repo.name, 'main page check'): |
|
64 | 'repository.admin')(repo.name, 'main page check'): | |
66 | repos_lst.append((repo.name, repo.dbrepo.private,)) |
|
65 | repos_lst.append((repo.name, repo.dbrepo.private,)) | |
67 |
|
66 | |||
68 | return sorted(repos_lst, key=lambda k:k[0].lower()) |
|
67 | return sorted(repos_lst, key=lambda k:k[0].lower()) | |
69 |
|
68 | |||
70 | @cache_region('long_term', 'full_changelog') |
|
69 | @cache_region('long_term', 'full_changelog') | |
71 | def _full_changelog_cached(repo_name): |
|
70 | def _full_changelog_cached(repo_name): | |
72 | log.info('getting full changelog for %s', repo_name) |
|
71 | log.info('getting full changelog for %s', repo_name) | |
73 | return list(reversed(list(HgModel().get_repo(repo_name)))) |
|
72 | return list(reversed(list(HgModel().get_repo(repo_name)))) | |
74 |
|
73 | |||
75 | class HgModel(object): |
|
74 | class HgModel(object): | |
76 | """Mercurial Model |
|
75 | """ | |
|
76 | Mercurial Model | |||
77 | """ |
|
77 | """ | |
78 |
|
78 | |||
79 | def __init__(self): |
|
79 | def __init__(self, sa=None): | |
80 |
|
|
80 | if not sa: | |
|
81 | self.sa = meta.Session() | |||
|
82 | else: | |||
|
83 | self.sa = sa | |||
81 |
|
84 | |||
82 | @staticmethod |
|
85 | def repo_scan(self, repos_path, baseui, initial=False): | |
83 | def repo_scan(repos_prefix, repos_path, baseui, initial=False): |
|
|||
84 | """ |
|
86 | """ | |
85 | Listing of repositories in given path. This path should not be a |
|
87 | Listing of repositories in given path. This path should not be a | |
86 | repository itself. Return a dictionary of repository objects |
|
88 | repository itself. Return a dictionary of repository objects | |
87 | :param repos_path: path to directory it could take syntax with |
|
89 | ||
88 | * or ** for deep recursive displaying repositories |
|
90 | :param repos_path: path to directory containing repositories | |
89 | """ |
|
91 | :param baseui | |
90 | sa = meta.Session() |
|
92 | :param initial: initial scann | |
91 | def check_repo_dir(path): |
|
|||
92 | """Checks the repository |
|
|||
93 | :param path: |
|
|||
94 |
|
|
93 | """ | |
95 | repos_path = path.split('/') |
|
94 | log.info('scanning for repositories in %s', repos_path) | |
96 | if repos_path[-1] in ['*', '**']: |
|
|||
97 | repos_path = repos_path[:-1] |
|
|||
98 | if repos_path[0] != '/': |
|
|||
99 | repos_path[0] = '/' |
|
|||
100 | if not os.path.isdir(os.path.join(*repos_path)): |
|
|||
101 | raise RepositoryError('Not a valid repository in %s' % path) |
|
|||
102 | if not repos_path.endswith('*'): |
|
|||
103 | raise VCSError('You need to specify * or ** at the end of path ' |
|
|||
104 | 'for recursive scanning') |
|
|||
105 |
|
95 | |||
106 | check_repo_dir(repos_path) |
|
|||
107 | log.info('scanning for repositories in %s', repos_path) |
|
|||
108 | repos = findrepos([(repos_prefix, repos_path)]) |
|
|||
109 | if not isinstance(baseui, ui.ui): |
|
96 | if not isinstance(baseui, ui.ui): | |
110 | baseui = ui.ui() |
|
97 | baseui = ui.ui() | |
111 |
|
98 | |||
|
99 | from rhodecode.lib.utils import get_repos | |||
|
100 | repos = get_repos(repos_path) | |||
|
101 | ||||
|
102 | ||||
112 | repos_list = {} |
|
103 | repos_list = {} | |
113 | for name, path in repos: |
|
104 | for name, path in repos: | |
114 | try: |
|
105 | try: | |
115 | #name = name.split('/')[-1] |
|
106 | #name = name.split('/')[-1] | |
116 | if repos_list.has_key(name): |
|
107 | if repos_list.has_key(name): | |
117 | raise RepositoryError('Duplicate repository name %s found in' |
|
108 | raise RepositoryError('Duplicate repository name %s found in' | |
118 | ' %s' % (name, path)) |
|
109 | ' %s' % (name, path)) | |
119 | else: |
|
110 | else: | |
|
111 | if path[0] == 'hg': | |||
|
112 | repos_list[name] = MercurialRepository(path[1], baseui=baseui) | |||
|
113 | repos_list[name].name = name | |||
120 |
|
114 | |||
121 | repos_list[name] = MercurialRepository(path, baseui=baseui) |
|
115 | if path[0] == 'git': | |
|
116 | repos_list[name] = GitRepository(path[1]) | |||
122 | repos_list[name].name = name |
|
117 | repos_list[name].name = name | |
123 |
|
118 | |||
124 | dbrepo = None |
|
119 | dbrepo = None | |
125 | if not initial: |
|
120 | if not initial: | |
126 | #for initial scann on application first run we don't |
|
121 | #for initial scann on application first run we don't | |
127 | #have db repos yet. |
|
122 | #have db repos yet. | |
128 | dbrepo = sa.query(Repository)\ |
|
123 | dbrepo = self.sa.query(Repository)\ | |
129 | .options(joinedload(Repository.fork))\ |
|
124 | .options(joinedload(Repository.fork))\ | |
130 | .filter(Repository.repo_name == name)\ |
|
125 | .filter(Repository.repo_name == name)\ | |
131 | .scalar() |
|
126 | .scalar() | |
132 |
|
127 | |||
133 | if dbrepo: |
|
128 | if dbrepo: | |
134 | log.info('Adding db instance to cached list') |
|
129 | log.info('Adding db instance to cached list') | |
135 | repos_list[name].dbrepo = dbrepo |
|
130 | repos_list[name].dbrepo = dbrepo | |
136 | repos_list[name].description = dbrepo.description |
|
131 | repos_list[name].description = dbrepo.description | |
137 | if dbrepo.user: |
|
132 | if dbrepo.user: | |
138 | repos_list[name].contact = dbrepo.user.full_contact |
|
133 | repos_list[name].contact = dbrepo.user.full_contact | |
139 | else: |
|
134 | else: | |
140 | repos_list[name].contact = sa.query(User)\ |
|
135 | repos_list[name].contact = self.sa.query(User)\ | |
141 | .filter(User.admin == True).first().full_contact |
|
136 | .filter(User.admin == True).first().full_contact | |
142 | except OSError: |
|
137 | except OSError: | |
143 | continue |
|
138 | continue | |
144 | meta.Session.remove() |
|
139 | ||
145 | return repos_list |
|
140 | return repos_list | |
146 |
|
141 | |||
147 | def get_repos(self): |
|
142 | def get_repos(self): | |
148 | for name, repo in _get_repos_cached().items(): |
|
143 | for name, repo in _get_repos_cached().items(): | |
149 | if repo._get_hidden(): |
|
144 | ||
|
145 | if isinstance(repo, MercurialRepository) and repo._get_hidden(): | |||
150 | #skip hidden web repository |
|
146 | #skip hidden web repository | |
151 | continue |
|
147 | continue | |
152 |
|
148 | |||
153 | last_change = repo.last_change |
|
149 | last_change = repo.last_change | |
154 | tip = h.get_changeset_safe(repo, 'tip') |
|
150 | tip = h.get_changeset_safe(repo, 'tip') | |
155 |
|
151 | |||
156 | tmp_d = {} |
|
152 | tmp_d = {} | |
157 | tmp_d['name'] = repo.name |
|
153 | tmp_d['name'] = repo.name | |
158 | tmp_d['name_sort'] = tmp_d['name'].lower() |
|
154 | tmp_d['name_sort'] = tmp_d['name'].lower() | |
159 | tmp_d['description'] = repo.description |
|
155 | tmp_d['description'] = repo.description | |
160 | tmp_d['description_sort'] = tmp_d['description'] |
|
156 | tmp_d['description_sort'] = tmp_d['description'] | |
161 | tmp_d['last_change'] = last_change |
|
157 | tmp_d['last_change'] = last_change | |
162 | tmp_d['last_change_sort'] = last_change[1] - last_change[0] |
|
158 | tmp_d['last_change_sort'] = last_change[1] - last_change[0] | |
163 | tmp_d['tip'] = tip.short_id |
|
159 | tmp_d['tip'] = tip.short_id | |
164 | tmp_d['tip_sort'] = tip.revision |
|
160 | tmp_d['tip_sort'] = tip.revision | |
165 | tmp_d['rev'] = tip.revision |
|
161 | tmp_d['rev'] = tip.revision | |
166 | tmp_d['contact'] = repo.contact |
|
162 | tmp_d['contact'] = repo.contact | |
167 | tmp_d['contact_sort'] = tmp_d['contact'] |
|
163 | tmp_d['contact_sort'] = tmp_d['contact'] | |
168 | tmp_d['repo_archives'] = list(repo._get_archives()) |
|
164 | tmp_d['repo_archives'] = list(repo._get_archives()) | |
169 | tmp_d['last_msg'] = tip.message |
|
165 | tmp_d['last_msg'] = tip.message | |
170 | tmp_d['repo'] = repo |
|
166 | tmp_d['repo'] = repo | |
171 | yield tmp_d |
|
167 | yield tmp_d | |
172 |
|
168 | |||
173 | def get_repo(self, repo_name): |
|
169 | def get_repo(self, repo_name): | |
174 | try: |
|
170 | try: | |
175 | repo = _get_repos_cached()[repo_name] |
|
171 | repo = _get_repos_cached()[repo_name] | |
176 | return repo |
|
172 | return repo | |
177 | except KeyError: |
|
173 | except KeyError: | |
178 | #i we're here and we got key errors let's try to invalidate the |
|
174 | #i we're here and we got key errors let's try to invalidate the | |
179 | #cahce and try again |
|
175 | #cahce and try again | |
180 | invalidate_cache('cached_repo_list') |
|
176 | invalidate_cache('cached_repo_list') | |
181 | repo = _get_repos_cached()[repo_name] |
|
177 | repo = _get_repos_cached()[repo_name] | |
182 | return repo |
|
178 | return repo | |
183 |
|
179 | |||
184 |
|
180 | |||
185 |
|
181 |
@@ -1,63 +1,63 b'' | |||||
1 | ## -*- coding: utf-8 -*- |
|
1 | ## -*- coding: utf-8 -*- | |
2 | % if c.repo_changesets: |
|
2 | % if c.repo_changesets: | |
3 | <table> |
|
3 | <table> | |
4 | <tr> |
|
4 | <tr> | |
5 | <th class="left">${_('date')}</th> |
|
5 | <th class="left">${_('date')}</th> | |
6 | <th class="left">${_('author')}</th> |
|
6 | <th class="left">${_('author')}</th> | |
7 | <th class="left">${_('revision')}</th> |
|
7 | <th class="left">${_('revision')}</th> | |
8 | <th class="left">${_('commit message')}</th> |
|
8 | <th class="left">${_('commit message')}</th> | |
9 | <th class="left">${_('branch')}</th> |
|
9 | <th class="left">${_('branch')}</th> | |
10 | <th class="left">${_('tags')}</th> |
|
10 | <th class="left">${_('tags')}</th> | |
11 | <th class="left">${_('links')}</th> |
|
11 | <th class="left">${_('links')}</th> | |
12 |
|
12 | |||
13 | </tr> |
|
13 | </tr> | |
14 | %for cnt,cs in enumerate(c.repo_changesets): |
|
14 | %for cnt,cs in enumerate(c.repo_changesets): | |
15 | <tr class="parity${cnt%2}"> |
|
15 | <tr class="parity${cnt%2}"> | |
16 |
<td>${h.age(cs. |
|
16 | <td>${h.age(cs.date)} - ${h.rfc822date_notz(cs.date)} </td> | |
17 | <td title="${cs.author}">${h.person(cs.author)}</td> |
|
17 | <td title="${cs.author}">${h.person(cs.author)}</td> | |
18 | <td>r${cs.revision}:${cs.short_id}</td> |
|
18 | <td>r${cs.revision}:${cs.short_id}</td> | |
19 | <td> |
|
19 | <td> | |
20 | ${h.link_to(h.truncate(cs.message,60), |
|
20 | ${h.link_to(h.truncate(cs.message,60), | |
21 | h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id), |
|
21 | h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id), | |
22 | title=cs.message)} |
|
22 | title=cs.message)} | |
23 | </td> |
|
23 | </td> | |
24 | <td> |
|
24 | <td> | |
25 | <span class="logtags"> |
|
25 | <span class="logtags"> | |
26 | <span class="branchtag">${cs.branch}</span> |
|
26 | <span class="branchtag">${cs.branch}</span> | |
27 | </span> |
|
27 | </span> | |
28 | </td> |
|
28 | </td> | |
29 | <td> |
|
29 | <td> | |
30 | <span class="logtags"> |
|
30 | <span class="logtags"> | |
31 | %for tag in cs.tags: |
|
31 | %for tag in cs.tags: | |
32 | <span class="tagtag">${tag}</span> |
|
32 | <span class="tagtag">${tag}</span> | |
33 | %endfor |
|
33 | %endfor | |
34 | </span> |
|
34 | </span> | |
35 | </td> |
|
35 | </td> | |
36 | <td class="nowrap"> |
|
36 | <td class="nowrap"> | |
37 | ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id))} |
|
37 | ${h.link_to(_('changeset'),h.url('changeset_home',repo_name=c.repo_name,revision=cs.short_id))} | |
38 | | |
|
38 | | | |
39 | ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.short_id))} |
|
39 | ${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name,revision=cs.short_id))} | |
40 | </td> |
|
40 | </td> | |
41 | </tr> |
|
41 | </tr> | |
42 | %endfor |
|
42 | %endfor | |
43 |
|
43 | |||
44 | </table> |
|
44 | </table> | |
45 |
|
45 | |||
46 | <script type="text/javascript"> |
|
46 | <script type="text/javascript"> | |
47 | var data_div = 'shortlog_data'; |
|
47 | var data_div = 'shortlog_data'; | |
48 | YAHOO.util.Event.onDOMReady(function(){ |
|
48 | YAHOO.util.Event.onDOMReady(function(){ | |
49 | YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){ |
|
49 | YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){ | |
50 | YAHOO.util.Dom.setStyle('shortlog_data','opacity','0.3');});}); |
|
50 | YAHOO.util.Dom.setStyle('shortlog_data','opacity','0.3');});}); | |
51 | </script> |
|
51 | </script> | |
52 |
|
52 | |||
53 | <div class="pagination-wh pagination-left"> |
|
53 | <div class="pagination-wh pagination-left"> | |
54 | ${c.repo_changesets.pager('$link_previous ~2~ $link_next', |
|
54 | ${c.repo_changesets.pager('$link_previous ~2~ $link_next', | |
55 | onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{ |
|
55 | onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{ | |
56 | success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText; |
|
56 | success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText; | |
57 | YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){ |
|
57 | YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){ | |
58 | YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');}); |
|
58 | YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');}); | |
59 | YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")} |
|
59 | YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")} | |
60 | </div> |
|
60 | </div> | |
61 | %else: |
|
61 | %else: | |
62 | ${_('There are no changes yet')} |
|
62 | ${_('There are no changes yet')} | |
63 | %endif |
|
63 | %endif |
General Comments 0
You need to be logged in to leave comments.
Login now