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