##// END OF EJS Templates
Hacking for git support,and new faster repo scan
marcink -
r631:05528ad9 beta
parent child Browse files
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, 0, 0, 'rc4')
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/*', True)
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].replace('*', '')
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'].replace('*', '')
102 repos_path = get_hg_ui_settings()['paths_root_path']
99 repo = MercurialRepository(repos_path + repo_name)
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'].replace('*', '')
313 repos_path = get_hg_ui_settings()['paths_root_path']
309 repo = MercurialRepository(repos_path + repo_name)
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 = os.path.join(path, '*')
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:_age(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: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2")
336 rfc822date = lambda x: x#util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2")
337 rfc822date_notz = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S")
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 :param matcher:
172 @param matcher:
119 :param size:
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, IDX_LOCATION, SCHEMA, IDX_NAME
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.repo_scan('/', root_location, None, True)
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', repo_location=None):
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(IDX_LOCATION):
89 if not os.path.isdir(self.index_location):
78 os.mkdir(IDX_LOCATION)
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(IDX_LOCATION):
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(IDX_LOCATION):
147 if not os.path.exists(self.index_location):
138 os.mkdir(IDX_LOCATION)
148 os.mkdir(self.index_location)
139
149
140 idx = create_in(IDX_LOCATION, SCHEMA, indexname=IDX_NAME)
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(IDX_LOCATION, indexname=self.indexname)
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(g.paths[0][0], g.paths[0][1], g.baseui, initial)
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(g.paths[0][0], g.paths[0][1], g.baseui)
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 pass
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._ctx.date())} - ${h.rfc822date_notz(cs._ctx.date())} </td>
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