##// END OF EJS Templates
code docs, updates
marcink -
r903:04c9bb9c beta
parent child Browse files
Show More
@@ -1,106 +1,106 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 package.rhodecode.lib.celerylib.__init__
4 ~~~~~~~~~~~~~~
3 rhodecode.lib.celerylib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 celery libs for RhodeCode
7 7
8 8 :created_on: Nov 27, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import os
29 29 import sys
30 30 import socket
31 31 import traceback
32 32 import logging
33 33
34 34 from hashlib import md5
35 35 from decorator import decorator
36 36 from vcs.utils.lazy import LazyProperty
37 37
38 38 from rhodecode.lib.pidlock import DaemonLock, LockHeld
39 39
40 40 from pylons import config
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 def str2bool(v):
45 45 return v.lower() in ["yes", "true", "t", "1"] if v else None
46 46
47 47 try:
48 48 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
49 49 except KeyError:
50 50 CELERY_ON = False
51 51
52 52 class ResultWrapper(object):
53 53 def __init__(self, task):
54 54 self.task = task
55 55
56 56 @LazyProperty
57 57 def result(self):
58 58 return self.task
59 59
60 60 def run_task(task, *args, **kwargs):
61 61 if CELERY_ON:
62 62 try:
63 63 t = task.delay(*args, **kwargs)
64 64 log.info('running task %s:%s', t.task_id, task)
65 65 return t
66 66 except socket.error, e:
67 67 if e.errno == 111:
68 68 log.debug('Unable to connect to celeryd. Sync execution')
69 69 else:
70 70 log.error(traceback.format_exc())
71 71 except KeyError, e:
72 72 log.debug('Unable to connect to celeryd. Sync execution')
73 73 except Exception, e:
74 74 log.error(traceback.format_exc())
75 75
76 76 log.debug('executing task %s in sync mode', task)
77 77 return ResultWrapper(task(*args, **kwargs))
78 78
79 79
80 80 def locked_task(func):
81 81 def __wrapper(func, *fargs, **fkwargs):
82 82 params = list(fargs)
83 83 params.extend(['%s-%s' % ar for ar in fkwargs.items()])
84 84
85 85 lockkey = 'task_%s' % \
86 86 md5(str(func.__name__) + '-' + \
87 87 '-'.join(map(str, params))).hexdigest()
88 88 log.info('running task with lockkey %s', lockkey)
89 89 try:
90 90 l = DaemonLock(lockkey)
91 91 ret = func(*fargs, **fkwargs)
92 92 l.release()
93 93 return ret
94 94 except LockHeld:
95 95 log.info('LockHeld')
96 96 return 'Task with key %s already running' % lockkey
97 97
98 98 return decorator(__wrapper, func)
99 99
100 100
101 101
102 102
103 103
104 104
105 105
106 106
@@ -1,359 +1,386 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.celerylib.tasks
4 ~~~~~~~~~~~~~~
5
6 RhodeCode task modules, containing all task that suppose to be run
7 by celery daemon
8
9 :created_on: Oct 6, 2010
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
13 """
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
1 28 from celery.decorators import task
2 29
3 30 import os
4 31 import traceback
5 32 from time import mktime
6 33 from operator import itemgetter
7 34
8 35 from pylons import config
9 36 from pylons.i18n.translation import _
10 37
11 38 from rhodecode.lib.celerylib import run_task, locked_task, str2bool
12 39 from rhodecode.lib.helpers import person
13 40 from rhodecode.lib.smtp_mailer import SmtpMailer
14 41 from rhodecode.lib.utils import OrderedDict, add_cache
15 42 from rhodecode.model import init_model
16 43 from rhodecode.model import meta
17 44 from rhodecode.model.db import RhodeCodeUi
18 45
19 46 from vcs.backends import get_repo
20 47
21 48 from sqlalchemy import engine_from_config
22 49
23 50 add_cache(config)
24 51
25 52 try:
26 53 import json
27 54 except ImportError:
28 55 #python 2.5 compatibility
29 56 import simplejson as json
30 57
31 58 __all__ = ['whoosh_index', 'get_commits_stats',
32 59 'reset_user_password', 'send_email']
33 60
34 61 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
35 62
36 63 def get_session():
37 64 if CELERY_ON:
38 65 engine = engine_from_config(config, 'sqlalchemy.db1.')
39 66 init_model(engine)
40 67 sa = meta.Session()
41 68 return sa
42 69
43 70 def get_repos_path():
44 71 sa = get_session()
45 72 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
46 73 return q.ui_value
47 74
48 75 @task
49 76 @locked_task
50 77 def whoosh_index(repo_location, full_index):
51 78 log = whoosh_index.get_logger()
52 79 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
53 80 index_location = config['index_dir']
54 81 WhooshIndexingDaemon(index_location=index_location,
55 82 repo_location=repo_location, sa=get_session())\
56 83 .run(full_index=full_index)
57 84
58 85 @task
59 86 @locked_task
60 87 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
61 88 from rhodecode.model.db import Statistics, Repository
62 89 log = get_commits_stats.get_logger()
63 90
64 91 #for js data compatibilty
65 92 author_key_cleaner = lambda k: person(k).replace('"', "")
66 93
67 94 commits_by_day_author_aggregate = {}
68 95 commits_by_day_aggregate = {}
69 96 repos_path = get_repos_path()
70 97 p = os.path.join(repos_path, repo_name)
71 98 repo = get_repo(p)
72 99
73 100 skip_date_limit = True
74 101 parse_limit = 250 #limit for single task changeset parsing optimal for
75 102 last_rev = 0
76 103 last_cs = None
77 104 timegetter = itemgetter('time')
78 105
79 106 sa = get_session()
80 107
81 108 dbrepo = sa.query(Repository)\
82 109 .filter(Repository.repo_name == repo_name).scalar()
83 110 cur_stats = sa.query(Statistics)\
84 111 .filter(Statistics.repository == dbrepo).scalar()
85 112 if cur_stats:
86 113 last_rev = cur_stats.stat_on_revision
87 114 if not repo.revisions:
88 115 return True
89 116
90 117 if last_rev == repo.revisions[-1] and len(repo.revisions) > 1:
91 118 #pass silently without any work if we're not on first revision or
92 119 #current state of parsing revision(from db marker) is the last revision
93 120 return True
94 121
95 122 if cur_stats:
96 123 commits_by_day_aggregate = OrderedDict(
97 124 json.loads(
98 125 cur_stats.commit_activity_combined))
99 126 commits_by_day_author_aggregate = json.loads(cur_stats.commit_activity)
100 127
101 128 log.debug('starting parsing %s', parse_limit)
102 129 lmktime = mktime
103 130
104 131 last_rev = last_rev + 1 if last_rev > 0 else last_rev
105 132 for rev in repo.revisions[last_rev:last_rev + parse_limit]:
106 133 last_cs = cs = repo.get_changeset(rev)
107 134 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
108 135 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
109 136
110 137 if commits_by_day_author_aggregate.has_key(author_key_cleaner(cs.author)):
111 138 try:
112 139 l = [timegetter(x) for x in commits_by_day_author_aggregate\
113 140 [author_key_cleaner(cs.author)]['data']]
114 141 time_pos = l.index(k)
115 142 except ValueError:
116 143 time_pos = False
117 144
118 145 if time_pos >= 0 and time_pos is not False:
119 146
120 147 datadict = commits_by_day_author_aggregate\
121 148 [author_key_cleaner(cs.author)]['data'][time_pos]
122 149
123 150 datadict["commits"] += 1
124 151 datadict["added"] += len(cs.added)
125 152 datadict["changed"] += len(cs.changed)
126 153 datadict["removed"] += len(cs.removed)
127 154
128 155 else:
129 156 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
130 157
131 158 datadict = {"time":k,
132 159 "commits":1,
133 160 "added":len(cs.added),
134 161 "changed":len(cs.changed),
135 162 "removed":len(cs.removed),
136 163 }
137 164 commits_by_day_author_aggregate\
138 165 [author_key_cleaner(cs.author)]['data'].append(datadict)
139 166
140 167 else:
141 168 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
142 169 commits_by_day_author_aggregate[author_key_cleaner(cs.author)] = {
143 170 "label":author_key_cleaner(cs.author),
144 171 "data":[{"time":k,
145 172 "commits":1,
146 173 "added":len(cs.added),
147 174 "changed":len(cs.changed),
148 175 "removed":len(cs.removed),
149 176 }],
150 177 "schema":["commits"],
151 178 }
152 179
153 180 #gather all data by day
154 181 if commits_by_day_aggregate.has_key(k):
155 182 commits_by_day_aggregate[k] += 1
156 183 else:
157 184 commits_by_day_aggregate[k] = 1
158 185
159 186 overview_data = sorted(commits_by_day_aggregate.items(), key=itemgetter(0))
160 187 if not commits_by_day_author_aggregate:
161 188 commits_by_day_author_aggregate[author_key_cleaner(repo.contact)] = {
162 189 "label":author_key_cleaner(repo.contact),
163 190 "data":[0, 1],
164 191 "schema":["commits"],
165 192 }
166 193
167 194 stats = cur_stats if cur_stats else Statistics()
168 195 stats.commit_activity = json.dumps(commits_by_day_author_aggregate)
169 196 stats.commit_activity_combined = json.dumps(overview_data)
170 197
171 198 log.debug('last revison %s', last_rev)
172 199 leftovers = len(repo.revisions[last_rev:])
173 200 log.debug('revisions to parse %s', leftovers)
174 201
175 202 if last_rev == 0 or leftovers < parse_limit:
176 203 log.debug('getting code trending stats')
177 204 stats.languages = json.dumps(__get_codes_stats(repo_name))
178 205
179 206 stats.repository = dbrepo
180 207 stats.stat_on_revision = last_cs.revision
181 208
182 209 try:
183 210 sa.add(stats)
184 211 sa.commit()
185 212 except:
186 213 log.error(traceback.format_exc())
187 214 sa.rollback()
188 215 return False
189 216 if len(repo.revisions) > 1:
190 217 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
191 218
192 219 return True
193 220
194 221 @task
195 222 def reset_user_password(user_email):
196 223 log = reset_user_password.get_logger()
197 224 from rhodecode.lib import auth
198 225 from rhodecode.model.db import User
199 226
200 227 try:
201 228 try:
202 229 sa = get_session()
203 230 user = sa.query(User).filter(User.email == user_email).scalar()
204 231 new_passwd = auth.PasswordGenerator().gen_password(8,
205 232 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
206 233 if user:
207 234 user.password = auth.get_crypt_password(new_passwd)
208 235 sa.add(user)
209 236 sa.commit()
210 237 log.info('change password for %s', user_email)
211 238 if new_passwd is None:
212 239 raise Exception('unable to generate new password')
213 240
214 241 except:
215 242 log.error(traceback.format_exc())
216 243 sa.rollback()
217 244
218 245 run_task(send_email, user_email,
219 246 "Your new rhodecode password",
220 247 'Your new rhodecode password:%s' % (new_passwd))
221 248 log.info('send new password mail to %s', user_email)
222 249
223 250
224 251 except:
225 252 log.error('Failed to update user password')
226 253 log.error(traceback.format_exc())
227 254
228 255 return True
229 256
230 257 @task
231 258 def send_email(recipients, subject, body):
232 259 """
233 260 Sends an email with defined parameters from the .ini files.
234 261
235 262
236 263 :param recipients: list of recipients, it this is empty the defined email
237 264 address from field 'email_to' is used instead
238 265 :param subject: subject of the mail
239 266 :param body: body of the mail
240 267 """
241 268 log = send_email.get_logger()
242 269 email_config = config
243 270
244 271 if not recipients:
245 272 recipients = [email_config.get('email_to')]
246 273
247 274 mail_from = email_config.get('app_email_from')
248 275 user = email_config.get('smtp_username')
249 276 passwd = email_config.get('smtp_password')
250 277 mail_server = email_config.get('smtp_server')
251 278 mail_port = email_config.get('smtp_port')
252 279 tls = str2bool(email_config.get('smtp_use_tls'))
253 280 ssl = str2bool(email_config.get('smtp_use_ssl'))
254 281
255 282 try:
256 283 m = SmtpMailer(mail_from, user, passwd, mail_server,
257 284 mail_port, ssl, tls)
258 285 m.send(recipients, subject, body)
259 286 except:
260 287 log.error('Mail sending failed')
261 288 log.error(traceback.format_exc())
262 289 return False
263 290 return True
264 291
265 292 @task
266 293 def create_repo_fork(form_data, cur_user):
267 294 from rhodecode.model.repo import RepoModel
268 295 from vcs import get_backend
269 296 log = create_repo_fork.get_logger()
270 297 repo_model = RepoModel(get_session())
271 298 repo_model.create(form_data, cur_user, just_db=True, fork=True)
272 299 repo_name = form_data['repo_name']
273 300 repos_path = get_repos_path()
274 301 repo_path = os.path.join(repos_path, repo_name)
275 302 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
276 303 alias = form_data['repo_type']
277 304
278 305 log.info('creating repo fork %s as %s', repo_name, repo_path)
279 306 backend = get_backend(alias)
280 307 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
281 308
282 309 def __get_codes_stats(repo_name):
283 310 LANGUAGES_EXTENSIONS_MAP = {'scm': 'Scheme', 'asmx': 'VbNetAspx', 'Rout':
284 311 'RConsole', 'rest': 'Rst', 'abap': 'ABAP', 'go': 'Go', 'phtml': 'HtmlPhp',
285 312 'ns2': 'Newspeak', 'xml': 'EvoqueXml', 'sh-session': 'BashSession', 'ads':
286 313 'Ada', 'clj': 'Clojure', 'll': 'Llvm', 'ebuild': 'Bash', 'adb': 'Ada',
287 314 'ada': 'Ada', 'c++-objdump': 'CppObjdump', 'aspx':
288 315 'VbNetAspx', 'ksh': 'Bash', 'coffee': 'CoffeeScript', 'vert': 'GLShader',
289 316 'Makefile.*': 'Makefile', 'di': 'D', 'dpatch': 'DarcsPatch', 'rake':
290 317 'Ruby', 'moo': 'MOOCode', 'erl-sh': 'ErlangShell', 'geo': 'GLShader',
291 318 'pov': 'Povray', 'bas': 'VbNet', 'bat': 'Batch', 'd': 'D', 'lisp':
292 319 'CommonLisp', 'h': 'C', 'rbx': 'Ruby', 'tcl': 'Tcl', 'c++': 'Cpp', 'md':
293 320 'MiniD', '.vimrc': 'Vim', 'xsd': 'Xml', 'ml': 'Ocaml', 'el': 'CommonLisp',
294 321 'befunge': 'Befunge', 'xsl': 'Xslt', 'pyx': 'Cython', 'cfm':
295 322 'ColdfusionHtml', 'evoque': 'Evoque', 'cfg': 'Ini', 'htm': 'Html',
296 323 'Makefile': 'Makefile', 'cfc': 'ColdfusionHtml', 'tex': 'Tex', 'cs':
297 324 'CSharp', 'mxml': 'Mxml', 'patch': 'Diff', 'apache.conf': 'ApacheConf',
298 325 'scala': 'Scala', 'applescript': 'AppleScript', 'GNUmakefile': 'Makefile',
299 326 'c-objdump': 'CObjdump', 'lua': 'Lua', 'apache2.conf': 'ApacheConf', 'rb':
300 327 'Ruby', 'gemspec': 'Ruby', 'rl': 'RagelObjectiveC', 'vala': 'Vala', 'tmpl':
301 328 'Cheetah', 'bf': 'Brainfuck', 'plt': 'Gnuplot', 'G': 'AntlrRuby', 'xslt':
302 329 'Xslt', 'flxh': 'Felix', 'asax': 'VbNetAspx', 'Rakefile': 'Ruby', 'S': 'S',
303 330 'wsdl': 'Xml', 'js': 'Javascript', 'autodelegate': 'Myghty', 'properties':
304 331 'Ini', 'bash': 'Bash', 'c': 'C', 'g': 'AntlrRuby', 'r3': 'Rebol', 's':
305 332 'Gas', 'ashx': 'VbNetAspx', 'cxx': 'Cpp', 'boo': 'Boo', 'prolog': 'Prolog',
306 333 'sqlite3-console': 'SqliteConsole', 'cl': 'CommonLisp', 'cc': 'Cpp', 'pot':
307 334 'Gettext', 'vim': 'Vim', 'pxi': 'Cython', 'yaml': 'Yaml', 'SConstruct':
308 335 'Python', 'diff': 'Diff', 'txt': 'Text', 'cw': 'Redcode', 'pxd': 'Cython',
309 336 'plot': 'Gnuplot', 'java': 'Java', 'hrl': 'Erlang', 'py': 'Python',
310 337 'makefile': 'Makefile', 'squid.conf': 'SquidConf', 'asm': 'Nasm', 'toc':
311 338 'Tex', 'kid': 'Genshi', 'rhtml': 'Rhtml', 'po': 'Gettext', 'pl': 'Prolog',
312 339 'pm': 'Perl', 'hx': 'Haxe', 'ascx': 'VbNetAspx', 'ooc': 'Ooc', 'asy':
313 340 'Asymptote', 'hs': 'Haskell', 'SConscript': 'Python', 'pytb':
314 341 'PythonTraceback', 'myt': 'Myghty', 'hh': 'Cpp', 'R': 'S', 'aux': 'Tex',
315 342 'rst': 'Rst', 'cpp-objdump': 'CppObjdump', 'lgt': 'Logtalk', 'rss': 'Xml',
316 343 'flx': 'Felix', 'b': 'Brainfuck', 'f': 'Fortran', 'rbw': 'Ruby',
317 344 '.htaccess': 'ApacheConf', 'cxx-objdump': 'CppObjdump', 'j': 'ObjectiveJ',
318 345 'mll': 'Ocaml', 'yml': 'Yaml', 'mu': 'MuPAD', 'r': 'Rebol', 'ASM': 'Nasm',
319 346 'erl': 'Erlang', 'mly': 'Ocaml', 'mo': 'Modelica', 'def': 'Modula2', 'ini':
320 347 'Ini', 'control': 'DebianControl', 'vb': 'VbNet', 'vapi': 'Vala', 'pro':
321 348 'Prolog', 'spt': 'Cheetah', 'mli': 'Ocaml', 'as': 'ActionScript3', 'cmd':
322 349 'Batch', 'cpp': 'Cpp', 'io': 'Io', 'tac': 'Python', 'haml': 'Haml', 'rkt':
323 350 'Racket', 'st':'Smalltalk', 'inc': 'Povray', 'pas': 'Delphi', 'cmake':
324 351 'CMake', 'csh':'Tcsh', 'hpp': 'Cpp', 'feature': 'Gherkin', 'html': 'Html',
325 352 'php':'Php', 'php3':'Php', 'php4':'Php', 'php5':'Php', 'xhtml': 'Html',
326 353 'hxx': 'Cpp', 'eclass': 'Bash', 'css': 'Css',
327 354 'frag': 'GLShader', 'd-objdump': 'DObjdump', 'weechatlog': 'IrcLogs',
328 355 'tcsh': 'Tcsh', 'objdump': 'Objdump', 'pyw': 'Python', 'h++': 'Cpp',
329 356 'py3tb': 'Python3Traceback', 'jsp': 'Jsp', 'sql': 'Sql', 'mak': 'Makefile',
330 357 'php': 'Php', 'mao': 'Mako', 'man': 'Groff', 'dylan': 'Dylan', 'sass':
331 358 'Sass', 'cfml': 'ColdfusionHtml', 'darcspatch': 'DarcsPatch', 'tpl':
332 359 'Smarty', 'm': 'ObjectiveC', 'f90': 'Fortran', 'mod': 'Modula2', 'sh':
333 360 'Bash', 'lhs': 'LiterateHaskell', 'sources.list': 'SourcesList', 'axd':
334 361 'VbNetAspx', 'sc': 'Python'}
335 362
336 363 repos_path = get_repos_path()
337 364 p = os.path.join(repos_path, repo_name)
338 365 repo = get_repo(p)
339 366 tip = repo.get_changeset()
340 367 code_stats = {}
341 368
342 369 def aggregate(cs):
343 370 for f in cs[2]:
344 371 ext = f.extension
345 372 key = LANGUAGES_EXTENSIONS_MAP.get(ext, ext)
346 373 key = key or ext
347 374 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
348 375 if code_stats.has_key(key):
349 376 code_stats[key] += 1
350 377 else:
351 378 code_stats[key] = 1
352 379
353 380 map(aggregate, tip.walk('/'))
354 381
355 382 return code_stats or {}
356 383
357 384
358 385
359 386
@@ -1,203 +1,229 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.indexers.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Whoosh indexing module for RhodeCode
7
8 :created_on: Aug 17, 2010
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
1 27 import os
2 28 import sys
3 29 import traceback
4 30 from os.path import dirname as dn, join as jn
5 31
6 32 #to get the rhodecode import
7 33 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
8 34
9 35 from string import strip
10 36
11 37 from rhodecode.model import init_model
12 38 from rhodecode.model.scm import ScmModel
13 39 from rhodecode.config.environment import load_environment
14 40 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
15 41
16 42 from shutil import rmtree
17 43 from webhelpers.html.builder import escape
18 44 from vcs.utils.lazy import LazyProperty
19 45
20 46 from sqlalchemy import engine_from_config
21 47
22 48 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
23 49 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
24 50 from whoosh.index import create_in, open_dir
25 51 from whoosh.formats import Characters
26 52 from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter
27 53
28 54
29 55 #EXTENSIONS WE WANT TO INDEX CONTENT OFF
30 56 INDEX_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c',
31 57 'cfg', 'cfm', 'cpp', 'cs', 'css', 'diff', 'do', 'el', 'erl',
32 58 'h', 'htm', 'html', 'ini', 'java', 'js', 'jsp', 'jspx', 'lisp',
33 59 'lua', 'm', 'mako', 'ml', 'pas', 'patch', 'php', 'php3',
34 60 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', 's', 'sh', 'sql',
35 61 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt',
36 62 'yaws']
37 63
38 64 #CUSTOM ANALYZER wordsplit + lowercase filter
39 65 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
40 66
41 67
42 68 #INDEX SCHEMA DEFINITION
43 69 SCHEMA = Schema(owner=TEXT(),
44 70 repository=TEXT(stored=True),
45 71 path=TEXT(stored=True),
46 72 content=FieldType(format=Characters(ANALYZER),
47 73 scorable=True, stored=True),
48 74 modtime=STORED(), extension=TEXT(stored=True))
49 75
50 76
51 77 IDX_NAME = 'HG_INDEX'
52 78 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
53 79 FRAGMENTER = SimpleFragmenter(200)
54 80
55 81
56 82 class MakeIndex(BasePasterCommand):
57 83
58 84 max_args = 1
59 85 min_args = 1
60 86
61 87 usage = "CONFIG_FILE"
62 88 summary = "Creates index for full text search given configuration file"
63 89 group_name = "RhodeCode"
64 90 takes_config_file = -1
65 91 parser = Command.standard_parser(verbose=True)
66 92
67 93 def command(self):
68 94
69 95 from pylons import config
70 96 add_cache(config)
71 97 engine = engine_from_config(config, 'sqlalchemy.db1.')
72 98 init_model(engine)
73 99
74 100 index_location = config['index_dir']
75 101 repo_location = self.options.repo_location
76 102 repo_list = map(strip, self.options.repo_list.split(','))
77 103
78 104 #======================================================================
79 105 # WHOOSH DAEMON
80 106 #======================================================================
81 107 from rhodecode.lib.pidlock import LockHeld, DaemonLock
82 108 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
83 109 try:
84 110 l = DaemonLock()
85 111 WhooshIndexingDaemon(index_location=index_location,
86 112 repo_location=repo_location,
87 113 repo_list=repo_list)\
88 114 .run(full_index=self.options.full_index)
89 115 l.release()
90 116 except LockHeld:
91 117 sys.exit(1)
92 118
93 119 def update_parser(self):
94 120 self.parser.add_option('--repo-location',
95 121 action='store',
96 122 dest='repo_location',
97 123 help="Specifies repositories location to index REQUIRED",
98 124 )
99 125 self.parser.add_option('--index-only',
100 126 action='store',
101 127 dest='repo_list',
102 128 help="Specifies a comma separated list of repositores "
103 129 "to build index on OPTIONAL",
104 130 )
105 131 self.parser.add_option('-f',
106 132 action='store_true',
107 133 dest='full_index',
108 134 help="Specifies that index should be made full i.e"
109 135 " destroy old and build from scratch",
110 136 default=False)
111 137
112 138 class ResultWrapper(object):
113 139 def __init__(self, search_type, searcher, matcher, highlight_items):
114 140 self.search_type = search_type
115 141 self.searcher = searcher
116 142 self.matcher = matcher
117 143 self.highlight_items = highlight_items
118 144 self.fragment_size = 200 / 2
119 145
120 146 @LazyProperty
121 147 def doc_ids(self):
122 148 docs_id = []
123 149 while self.matcher.is_active():
124 150 docnum = self.matcher.id()
125 151 chunks = [offsets for offsets in self.get_chunks()]
126 152 docs_id.append([docnum, chunks])
127 153 self.matcher.next()
128 154 return docs_id
129 155
130 156 def __str__(self):
131 157 return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
132 158
133 159 def __repr__(self):
134 160 return self.__str__()
135 161
136 162 def __len__(self):
137 163 return len(self.doc_ids)
138 164
139 165 def __iter__(self):
140 166 """
141 167 Allows Iteration over results,and lazy generate content
142 168
143 169 *Requires* implementation of ``__getitem__`` method.
144 170 """
145 171 for docid in self.doc_ids:
146 172 yield self.get_full_content(docid)
147 173
148 174 def __getslice__(self, i, j):
149 175 """
150 176 Slicing of resultWrapper
151 177 """
152 178 slice = []
153 179 for docid in self.doc_ids[i:j]:
154 180 slice.append(self.get_full_content(docid))
155 181 return slice
156 182
157 183
158 184 def get_full_content(self, docid):
159 185 res = self.searcher.stored_fields(docid[0])
160 186 f_path = res['path'][res['path'].find(res['repository']) \
161 187 + len(res['repository']):].lstrip('/')
162 188
163 189 content_short = self.get_short_content(res, docid[1])
164 190 res.update({'content_short':content_short,
165 191 'content_short_hl':self.highlight(content_short),
166 192 'f_path':f_path})
167 193
168 194 return res
169 195
170 196 def get_short_content(self, res, chunks):
171 197
172 198 return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
173 199
174 200 def get_chunks(self):
175 201 """
176 202 Smart function that implements chunking the content
177 203 but not overlap chunks so it doesn't highlight the same
178 204 close occurrences twice.
179 205 @param matcher:
180 206 @param size:
181 207 """
182 208 memory = [(0, 0)]
183 209 for span in self.matcher.spans():
184 210 start = span.startchar or 0
185 211 end = span.endchar or 0
186 212 start_offseted = max(0, start - self.fragment_size)
187 213 end_offseted = end + self.fragment_size
188 214
189 215 if start_offseted < memory[-1][1]:
190 216 start_offseted = memory[-1][1]
191 217 memory.append((start_offseted, end_offseted,))
192 218 yield (start_offseted, end_offseted,)
193 219
194 220 def highlight(self, content, top=5):
195 221 if self.search_type != 'content':
196 222 return ''
197 223 hl = highlight(escape(content),
198 224 self.highlight_items,
199 225 analyzer=ANALYZER,
200 226 fragmenter=FRAGMENTER,
201 227 formatter=FORMATTER,
202 228 top=top)
203 229 return hl
@@ -1,47 +1,48 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # middleware to handle https correctly
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.middleware.https_fixup
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 middleware to handle https correctly
7
8 :created_on: May 23, 2010
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
6 13 # This program is free software; you can redistribute it and/or
7 14 # modify it under the terms of the GNU General Public License
8 15 # as published by the Free Software Foundation; version 2
9 16 # of the License or (at your opinion) any later version of the license.
10 17 #
11 18 # This program is distributed in the hope that it will be useful,
12 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 21 # GNU General Public License for more details.
15 22 #
16 23 # You should have received a copy of the GNU General Public License
17 24 # along with this program; if not, write to the Free Software
18 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 26 # MA 02110-1301, USA.
20 27
21 """
22 Created on May 23, 2010
23
24 @author: marcink
25 """
26
27 28 class HttpsFixup(object):
28 29 def __init__(self, app):
29 30 self.application = app
30
31
31 32 def __call__(self, environ, start_response):
32 33 self.__fixup(environ)
33 34 return self.application(environ, start_response)
34
35
35
36
36 37 def __fixup(self, environ):
37 38 """Function to fixup the environ as needed. In order to use this
38 39 middleware you should set this header inside your
39 40 proxy ie. nginx, apache etc.
40 41 """
41 42 proto = environ.get('HTTP_X_URL_SCHEME')
42
43
43 44 if proto == 'https':
44 45 environ['wsgi.url_scheme'] = proto
45 46 else:
46 47 environ['wsgi.url_scheme'] = 'http'
47 48 return None
@@ -1,222 +1,226 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # middleware to handle git api calls
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 #
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
8
9 :created_on: Apr 28, 2010
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
13 """
6 14 # This program is free software; you can redistribute it and/or
7 15 # modify it under the terms of the GNU General Public License
8 16 # as published by the Free Software Foundation; version 2
9 17 # of the License or (at your opinion) any later version of the license.
10 18 #
11 19 # This program is distributed in the hope that it will be useful,
12 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 22 # GNU General Public License for more details.
15 23 #
16 24 # You should have received a copy of the GNU General Public License
17 25 # along with this program; if not, write to the Free Software
18 26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 27 # MA 02110-1301, USA.
20 """
21 Created on 2010-04-28
22 28
23 @author: marcink
24 SimpleGit middleware for handling git protocol request (push/clone etc.)
25 It's implemented with basic auth function
26 """
29 import os
30 import logging
31 import traceback
27 32
28 33 from dulwich import server as dulserver
29 34
30 35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
31 36
32 37 def handle(self):
33 38 write = lambda x: self.proto.write_sideband(1, x)
34 39
35 40 graph_walker = dulserver.ProtocolGraphWalker(self, self.repo.object_store,
36 41 self.repo.get_peeled)
37 42 objects_iter = self.repo.fetch_objects(
38 43 graph_walker.determine_wants, graph_walker, self.progress,
39 44 get_tagged=self.get_tagged)
40 45
41 46 # Do they want any objects?
42 47 if len(objects_iter) == 0:
43 48 return
44 49
45 50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
46 51 dulserver.write_pack_data(dulserver.ProtocolFile(None, write), objects_iter,
47 52 len(objects_iter))
48 53 messages = []
49 54 messages.append('thank you for using rhodecode')
50 55
51 56 for msg in messages:
52 57 self.progress(msg + "\n")
53 58 # we are done
54 59 self.proto.write("0000")
55 60
56 61 dulserver.DEFAULT_HANDLERS = {
57 62 'git-upload-pack': SimpleGitUploadPackHandler,
58 63 'git-receive-pack': dulserver.ReceivePackHandler,
59 64 }
60 65
61 66 from dulwich.repo import Repo
62 67 from dulwich.web import HTTPGitApplication
68
63 69 from paste.auth.basic import AuthBasicAuthenticator
64 70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
65 72 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
66 73 from rhodecode.lib.utils import invalidate_cache, check_repo_fast
67 74 from rhodecode.model.user import UserModel
75
68 76 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
69 import logging
70 import os
71 import traceback
72 77
73 78 log = logging.getLogger(__name__)
74 79
75 80 def is_git(environ):
76 """
77 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
81 """Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
78 82 then have git client version given.
79 83
80 84 :param environ:
81 85 """
82 86 http_user_agent = environ.get('HTTP_USER_AGENT')
83 87 if http_user_agent and http_user_agent.startswith('git'):
84 88 return True
85 89 return False
86 90
87 91 class SimpleGit(object):
88 92
89 93 def __init__(self, application, config):
90 94 self.application = application
91 95 self.config = config
92 96 #authenticate this git request using
93 97 self.authenticate = AuthBasicAuthenticator('', authfunc)
94 98 self.ipaddr = '0.0.0.0'
95 99 self.repository = None
96 100 self.username = None
97 101 self.action = None
98 102
99 103 def __call__(self, environ, start_response):
100 104 if not is_git(environ):
101 105 return self.application(environ, start_response)
102 106
103 107 proxy_key = 'HTTP_X_REAL_IP'
104 108 def_key = 'REMOTE_ADDR'
105 109 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
106 110 # skip passing error to error controller
107 111 environ['pylons.status_code_redirect'] = True
108 112 #===================================================================
109 113 # AUTHENTICATE THIS GIT REQUEST
110 114 #===================================================================
111 115 username = REMOTE_USER(environ)
112 116 if not username:
113 117 self.authenticate.realm = self.config['rhodecode_realm']
114 118 result = self.authenticate(environ)
115 119 if isinstance(result, str):
116 120 AUTH_TYPE.update(environ, 'basic')
117 121 REMOTE_USER.update(environ, result)
118 122 else:
119 123 return result.wsgi_application(environ, start_response)
120 124
121 125 #=======================================================================
122 126 # GET REPOSITORY
123 127 #=======================================================================
124 128 try:
125 129 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
126 130 if repo_name.endswith('/'):
127 131 repo_name = repo_name.rstrip('/')
128 132 self.repository = repo_name
129 133 except:
130 134 log.error(traceback.format_exc())
131 135 return HTTPInternalServerError()(environ, start_response)
132 136
133 137 #===================================================================
134 138 # CHECK PERMISSIONS FOR THIS REQUEST
135 139 #===================================================================
136 140 self.action = self.__get_action(environ)
137 141 if self.action:
138 142 username = self.__get_environ_user(environ)
139 143 try:
140 144 user = self.__get_user(username)
141 145 self.username = user.username
142 146 except:
143 147 log.error(traceback.format_exc())
144 148 return HTTPInternalServerError()(environ, start_response)
145 149
146 150 #check permissions for this repository
147 151 if self.action == 'push':
148 152 if not HasPermissionAnyMiddleware('repository.write',
149 153 'repository.admin')\
150 154 (user, repo_name):
151 155 return HTTPForbidden()(environ, start_response)
152 156
153 157 else:
154 158 #any other action need at least read permission
155 159 if not HasPermissionAnyMiddleware('repository.read',
156 160 'repository.write',
157 161 'repository.admin')\
158 162 (user, repo_name):
159 163 return HTTPForbidden()(environ, start_response)
160 164
161 165 self.extras = {'ip':self.ipaddr,
162 166 'username':self.username,
163 167 'action':self.action,
164 168 'repository':self.repository}
165 169
166 170 #===================================================================
167 171 # GIT REQUEST HANDLING
168 172 #===================================================================
169 173 self.basepath = self.config['base_path']
170 174 self.repo_path = os.path.join(self.basepath, self.repo_name)
171 175 #quick check if that dir exists...
172 176 if check_repo_fast(self.repo_name, self.basepath):
173 177 return HTTPNotFound()(environ, start_response)
174 178 try:
175 179 app = self.__make_app()
176 180 except:
177 181 log.error(traceback.format_exc())
178 182 return HTTPInternalServerError()(environ, start_response)
179 183
180 184 #invalidate cache on push
181 185 if self.action == 'push':
182 186 self.__invalidate_cache(self.repo_name)
183 187 messages = []
184 188 messages.append('thank you for using rhodecode')
185 189 return app(environ, start_response)
186 190 else:
187 191 return app(environ, start_response)
188 192
189 193
190 194 def __make_app(self):
191 195 backend = dulserver.DictBackend({'/' + self.repo_name: Repo(self.repo_path)})
192 196 gitserve = HTTPGitApplication(backend)
193 197
194 198 return gitserve
195 199
196 200 def __get_environ_user(self, environ):
197 201 return environ.get('REMOTE_USER')
198 202
199 203 def __get_user(self, username):
200 204 return UserModel().get_by_username(username, cache=True)
201 205
202 206 def __get_action(self, environ):
203 """
204 Maps git request commands into a pull or push command.
207 """Maps git request commands into a pull or push command.
208
205 209 :param environ:
206 210 """
207 211 service = environ['QUERY_STRING'].split('=')
208 212 if len(service) > 1:
209 213 service_cmd = service[1]
210 214 mapping = {'git-receive-pack': 'push',
211 215 'git-upload-pack': 'pull',
212 216 }
213 217
214 218 return mapping.get(service_cmd, service_cmd if service_cmd else 'other')
215 219 else:
216 220 return 'other'
217 221
218 222 def __invalidate_cache(self, repo_name):
219 223 """we know that some change was made to repositories and we should
220 224 invalidate the cache to see the changes right away but only for
221 225 push requests"""
222 226 invalidate_cache('get_repo_cached_%s' % repo_name)
@@ -1,230 +1,216 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # middleware to handle mercurial api calls
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 #
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
8
9 :created_on: Apr 28, 2010
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
13 """
6 14 # This program is free software; you can redistribute it and/or
7 15 # modify it under the terms of the GNU General Public License
8 16 # as published by the Free Software Foundation; version 2
9 17 # of the License or (at your opinion) any later version of the license.
10 18 #
11 19 # This program is distributed in the hope that it will be useful,
12 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 22 # GNU General Public License for more details.
15 23 #
16 24 # You should have received a copy of the GNU General Public License
17 25 # along with this program; if not, write to the Free Software
18 26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 27 # MA 02110-1301, USA.
20 """
21 Created on 2010-04-28
22 28
23 @author: marcink
24 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
25 It's implemented with basic auth function
26 """
27 29 from mercurial.error import RepoError
28 30 from mercurial.hgweb import hgweb
29 31 from mercurial.hgweb.request import wsgiapplication
30 32 from paste.auth.basic import AuthBasicAuthenticator
31 33 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 34 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
33 35 from rhodecode.lib.utils import make_ui, invalidate_cache, \
34 36 check_repo_fast, ui_sections
35 37 from rhodecode.model.user import UserModel
36 38 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
37 39 import logging
38 40 import os
39 41 import traceback
40 42
41 43 log = logging.getLogger(__name__)
42 44
43 45 def is_mercurial(environ):
44 """
45 Returns True if request's target is mercurial server - header
46 """Returns True if request's target is mercurial server - header
46 47 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
47 48 """
48 49 http_accept = environ.get('HTTP_ACCEPT')
49 50 if http_accept and http_accept.startswith('application/mercurial'):
50 51 return True
51 52 return False
52 53
53 54 class SimpleHg(object):
54 55
55 56 def __init__(self, application, config):
56 57 self.application = application
57 58 self.config = config
58 59 #authenticate this mercurial request using authfunc
59 60 self.authenticate = AuthBasicAuthenticator('', authfunc)
60 61 self.ipaddr = '0.0.0.0'
61 62 self.repository = None
62 63 self.username = None
63 64 self.action = None
64 65
65 66 def __call__(self, environ, start_response):
66 67 if not is_mercurial(environ):
67 68 return self.application(environ, start_response)
68 69
69 70 proxy_key = 'HTTP_X_REAL_IP'
70 71 def_key = 'REMOTE_ADDR'
71 72 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
72 73 # skip passing error to error controller
73 74 environ['pylons.status_code_redirect'] = True
74 75 #===================================================================
75 76 # AUTHENTICATE THIS MERCURIAL REQUEST
76 77 #===================================================================
77 78 username = REMOTE_USER(environ)
78 79
79 80 if not username:
80 81 self.authenticate.realm = self.config['rhodecode_realm']
81 82 result = self.authenticate(environ)
82 83 if isinstance(result, str):
83 84 AUTH_TYPE.update(environ, 'basic')
84 85 REMOTE_USER.update(environ, result)
85 86 else:
86 87 return result.wsgi_application(environ, start_response)
87 88
88 89 #=======================================================================
89 90 # GET REPOSITORY
90 91 #=======================================================================
91 92 try:
92 93 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
93 94 if repo_name.endswith('/'):
94 95 repo_name = repo_name.rstrip('/')
95 96 self.repository = repo_name
96 97 except:
97 98 log.error(traceback.format_exc())
98 99 return HTTPInternalServerError()(environ, start_response)
99 100
100 101 #===================================================================
101 102 # CHECK PERMISSIONS FOR THIS REQUEST
102 103 #===================================================================
103 104 self.action = self.__get_action(environ)
104 105 if self.action:
105 106 username = self.__get_environ_user(environ)
106 107 try:
107 108 user = self.__get_user(username)
108 109 self.username = user.username
109 110 except:
110 111 log.error(traceback.format_exc())
111 112 return HTTPInternalServerError()(environ, start_response)
112 113
113 114 #check permissions for this repository
114 115 if self.action == 'push':
115 116 if not HasPermissionAnyMiddleware('repository.write',
116 117 'repository.admin')\
117 118 (user, repo_name):
118 119 return HTTPForbidden()(environ, start_response)
119 120
120 121 else:
121 122 #any other action need at least read permission
122 123 if not HasPermissionAnyMiddleware('repository.read',
123 124 'repository.write',
124 125 'repository.admin')\
125 126 (user, repo_name):
126 127 return HTTPForbidden()(environ, start_response)
127 128
128 129 self.extras = {'ip':self.ipaddr,
129 130 'username':self.username,
130 131 'action':self.action,
131 132 'repository':self.repository}
132 133
133 134 #===================================================================
134 135 # MERCURIAL REQUEST HANDLING
135 136 #===================================================================
136 137 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
137 138 self.baseui = make_ui('db')
138 139 self.basepath = self.config['base_path']
139 140 self.repo_path = os.path.join(self.basepath, repo_name)
140 141
141 142 #quick check if that dir exists...
142 143 if check_repo_fast(repo_name, self.basepath):
143 144 return HTTPNotFound()(environ, start_response)
144 145 try:
145 146 app = wsgiapplication(self.__make_app)
146 147 except RepoError, e:
147 148 if str(e).find('not found') != -1:
148 149 return HTTPNotFound()(environ, start_response)
149 150 except Exception:
150 151 log.error(traceback.format_exc())
151 152 return HTTPInternalServerError()(environ, start_response)
152 153
153 154 #invalidate cache on push
154 155 if self.action == 'push':
155 156 self.__invalidate_cache(repo_name)
156 157
157 158 return app(environ, start_response)
158 159
159 160
160 161 def __make_app(self):
161 162 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
162 163 return self.__load_web_settings(hgserve, self.extras)
163 164
164 165 def __get_environ_user(self, environ):
165 166 return environ.get('REMOTE_USER')
166 167
167 168 def __get_user(self, username):
168 169 return UserModel().get_by_username(username, cache=True)
169 170
170 171 def __get_action(self, environ):
171 """
172 Maps mercurial request commands into a clone,pull or push command.
172 """Maps mercurial request commands into a clone,pull or push command.
173 173 This should always return a valid command string
174 174 :param environ:
175 175 """
176 176 mapping = {'changegroup': 'pull',
177 177 'changegroupsubset': 'pull',
178 178 'stream_out': 'pull',
179 179 'listkeys': 'pull',
180 180 'unbundle': 'push',
181 181 'pushkey': 'push', }
182 182 for qry in environ['QUERY_STRING'].split('&'):
183 183 if qry.startswith('cmd'):
184 184 cmd = qry.split('=')[-1]
185 185 if mapping.has_key(cmd):
186 186 return mapping[cmd]
187 187 else:
188 188 return cmd
189 189
190 190 def __invalidate_cache(self, repo_name):
191 191 """we know that some change was made to repositories and we should
192 192 invalidate the cache to see the changes right away but only for
193 193 push requests"""
194 194 invalidate_cache('get_repo_cached_%s' % repo_name)
195 195
196 196
197 197 def __load_web_settings(self, hgserve, extras={}):
198 198 #set the global ui for hgserve instance passed
199 199 hgserve.repo.ui = self.baseui
200 200
201 201 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
202 202
203 203 #inject some additional parameters that will be available in ui
204 204 #for hooks
205 205 for k, v in extras.items():
206 206 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
207 207
208 208 repoui = make_ui('file', hgrc, False)
209 209
210 210 if repoui:
211 211 #overwrite our ui instance with the section from hgrc file
212 212 for section in ui_sections:
213 213 for k, v in repoui.configitems(section):
214 214 hgserve.repo.ui.setconfig(section, k, v)
215 215
216 216 return hgserve
217
218
219
220
221
222
223
224
225
226
227
228
229
230
General Comments 0
You need to be logged in to leave comments. Login now