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