##// END OF EJS Templates
updated setup for all newest versions...
marcink -
r643:9dc1d92d beta
parent child Browse files
Show More
@@ -1,104 +1,103 b''
1 1
2 2 RhodeCode (RhodiumCode)
3 3 =======================
4 4
5 5 ``RhodeCode`` (formerly hg-app) is Pylons based repository management and
6 serving for mercurial_. It's similar to github or bitbucket, but it's suppose to run
7 as standalone app, it's open source and focuses more on restricted access to repositories
8 There's no default free access to RhodeCode You have to create an account in order
9 to use the application. It's powered by vcs_ library that we created to handle
10 many various version control systems.
6 serving for mercurial_ and git_. It's similar to github or bitbucket, but
7 it's suppose to run as standalone app, it's open source and focuses more on
8 restricted access to repositories. There's no default free access to RhodeCode
9 You have to create an account in order to use the application. It's powered
10 by vcs_ library that we created to handle many various version control systems.
11 11
12 12 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
13 13
14
15 14 RhodeCode demo
16 15 --------------
17 16
18 17 http://hg.python-works.com
19 18
20 19 The default access is
21 20
22 21 - username: demo
23 22 - password: demo
24 23
25 24 Source code
26 25 -----------
27 26
28 27 Source code is along with issue tracker is available at
29 28 http://bitbucket.org/marcinkuzminski/rhodecode
30 29
31 30 Also a source codes can be obtained from demo rhodecode instance
32 31 http://hg.python-works.com/rhodecode/summary
33 32
34 33 Instalation
35 34 -----------
36 35
37 36 Please visit http://packages.python.org/RhodeCode/installation.html
38 37
39 38
40 39 Features
41 40 --------
42 41
43 42 - Has it's own middleware to handle mercurial_ protocol request. Each request
44 43 can be logged and authenticated. Runs on threads unlikely to hgweb You can
45 44 make multiple pulls/pushes simultaneous. Supports http/https
46 45 - Full permissions and authentication per project private/read/write/admin.
47 46 One account for web interface and mercurial_ push/pull/clone.
48 47 - Mako templates let's you customize look and feel of application.
49 48 - Beautiful diffs, annotations and source codes all colored by pygments.
50 49 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
51 50 - Admin interface with user/permission management. User activity journal logs
52 51 pulls, pushes, forks,registrations. Possible to disable built in hooks
53 52 - Server side forks, it's possible to fork a project and hack it free without
54 53 breaking the main.
55 54 - Full text search on source codes, search on file names. All powered by whoosh
56 55 and build in indexing daemons
57 56 (no external search servers required all in one application)
58 57 - Rss / atom feeds, gravatar support, download sources as zip/tarballs
59 58 - Async tasks for speed and performance using celery_ (works without them too)
60 59 - Backup scripts can do backup of whole app and send it over scp to desired
61 60 location
62 61 - Setup project descriptions and info inside built in db for easy, non
63 62 file-system operations
64 63 - Added cache with invalidation on push/repo management for high performance and
65 64 always up to date data.
66 65 - Based on pylons 1.0 / sqlalchemy 0.6 / sqlite
67 66
68 67
69 68 Incoming
70 69 --------
71 70
72 71 - code review (probably based on hg-review)
73 72 - full git_ support, with push/pull server
74 73 - commit based build in wiki system
75 74 - clone points and cloning from remote repositories into rhodecode
76 75 (git_ and mercurial_)
77 76 - some cache optimizations
78 77 - other cools stuff that i can figure out (or You can help me figure out)
79 78
80 79 License
81 80 -------
82 81
83 82 ``rhodecode`` is released under GPL_ license.
84 83
85 84
86 85 Documentation
87 86 -------------
88 87
89 88 Online documentation for current version is available at
90 89 http://packages.python.org/RhodeCode/.
91 90 You may also build documentation for yourself - go into ``docs/`` and run::
92 91
93 92 make html
94 93
95 94 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
96 95 .. _python: http://www.python.org/
97 96 .. _django: http://www.djangoproject.com/
98 97 .. _mercurial: http://mercurial.selenic.com/
99 98 .. _subversion: http://subversion.tigris.org/
100 99 .. _git: http://git-scm.com/
101 100 .. _celery: http://celeryproject.org/
102 101 .. _Sphinx: http://sphinx.pocoo.org/
103 102 .. _GPL: http://www.gnu.org/licenses/gpl.html
104 103 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
@@ -1,113 +1,120 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # summary controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 18, 2010
22 22 summary controller for pylons
23 23 @author: marcink
24 24 """
25 25 from pylons import tmpl_context as c, request, url
26 from vcs.exceptions import ChangesetError
26 27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 28 from rhodecode.lib.base import BaseController, render
28 from rhodecode.lib.utils import OrderedDict
29 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
29 30 from rhodecode.model.hg import HgModel
30 31 from rhodecode.model.db import Statistics
31 32 from webhelpers.paginate import Page
32 33 from rhodecode.lib.celerylib import run_task
33 34 from rhodecode.lib.celerylib.tasks import get_commits_stats
34 35 from datetime import datetime, timedelta
35 36 from time import mktime
36 37 import calendar
37 38 import logging
38 39 try:
39 40 import json
40 41 except ImportError:
41 42 #python 2.5 compatibility
42 43 import simplejson as json
43 44 log = logging.getLogger(__name__)
44 45
45 46 class SummaryController(BaseController):
46 47
47 48 @LoginRequired()
48 49 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
49 50 'repository.admin')
50 51 def __before__(self):
51 52 super(SummaryController, self).__before__()
52 53
53 54 def index(self):
54 55 hg_model = HgModel()
55 56 c.repo_info = hg_model.get_repo(c.repo_name)
56 57 def url_generator(**kw):
57 58 return url('shortlog_home', repo_name=c.repo_name, **kw)
58 59
59 60 c.repo_changesets = Page(c.repo_info, page=1, items_per_page=10,
60 61 url=url_generator)
61 62
62 63 e = request.environ
63 64
64 65 uri = u'%(protocol)s://%(user)s@%(host)s%(prefix)s/%(repo_name)s' % {
65 66 'protocol': e.get('wsgi.url_scheme'),
66 67 'user':str(c.rhodecode_user.username),
67 68 'host':e.get('HTTP_HOST'),
68 69 'prefix':e.get('SCRIPT_NAME'),
69 70 'repo_name':c.repo_name, }
70 71 c.clone_repo_url = uri
71 72 c.repo_tags = OrderedDict()
72 73 for name, hash in c.repo_info.tags.items()[:10]:
73 c.repo_tags[name] = c.repo_info.get_changeset(hash)
74 try:
75 c.repo_tags[name] = c.repo_info.get_changeset(hash)
76 except ChangesetError:
77 c.repo_tags[name] = EmptyChangeset(hash)
74 78
75 79 c.repo_branches = OrderedDict()
76 80 for name, hash in c.repo_info.branches.items()[:10]:
77 c.repo_branches[name] = c.repo_info.get_changeset(hash)
81 try:
82 c.repo_branches[name] = c.repo_info.get_changeset(hash)
83 except ChangesetError:
84 c.repo_branches[name] = EmptyChangeset(hash)
78 85
79 86 td = datetime.today() + timedelta(days=1)
80 87 y, m, d = td.year, td.month, td.day
81 88
82 89 ts_min_y = mktime((y - 1, (td - timedelta(days=calendar.mdays[m])).month,
83 90 d, 0, 0, 0, 0, 0, 0,))
84 91 ts_min_m = mktime((y, (td - timedelta(days=calendar.mdays[m])).month,
85 92 d, 0, 0, 0, 0, 0, 0,))
86 93
87 94 ts_max_y = mktime((y, m, d, 0, 0, 0, 0, 0, 0,))
88 95
89 96 run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y)
90 97 c.ts_min = ts_min_m
91 98 c.ts_max = ts_max_y
92 99
93 100 stats = self.sa.query(Statistics)\
94 101 .filter(Statistics.repository == c.repo_info.dbrepo)\
95 102 .scalar()
96 103
97 104
98 105 if stats and stats.languages:
99 106 lang_stats = json.loads(stats.languages)
100 107 c.commit_data = stats.commit_activity
101 108 c.overview_data = stats.commit_activity_combined
102 109 c.trending_languages = json.dumps(OrderedDict(
103 110 sorted(lang_stats.items(), reverse=True,
104 111 key=lambda k: k[1])[:2]
105 112 )
106 113 )
107 114 else:
108 115 c.commit_data = json.dumps({})
109 116 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 0] ])
110 117 c.trending_languages = json.dumps({})
111 118
112 119 return render('summary/summary.html')
113 120
@@ -1,398 +1,401 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 from pygments.formatters import HtmlFormatter
7 7 from pygments import highlight as code_highlight
8 8 from pylons import url, app_globals as g
9 9 from pylons.i18n.translation import _, ungettext
10 10 from vcs.utils.annotate import annotate_highlight
11 11 from webhelpers.html import literal, HTML, escape
12 12 from webhelpers.html.tools import *
13 13 from webhelpers.html.builder import make_tag
14 14 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
15 15 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
16 16 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
17 17 password, textarea, title, ul, xml_declaration, radio
18 18 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
19 19 mail_to, strip_links, strip_tags, tag_re
20 20 from webhelpers.number import format_byte_size, format_bit_size
21 21 from webhelpers.pylonslib import Flash as _Flash
22 22 from webhelpers.pylonslib.secure_form import secure_form
23 23 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
24 24 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
25 25 replace_whitespace, urlify, truncate, wrap_paragraphs
26 26 from webhelpers.date import time_ago_in_words
27 27
28 28 #Custom helpers here :)
29 29 class _Link(object):
30 30 '''
31 31 Make a url based on label and url with help of url_for
32 32 :param label:name of link if not defined url is used
33 33 :param url: the url for link
34 34 '''
35 35
36 36 def __call__(self, label='', *url_, **urlargs):
37 37 if label is None or '':
38 38 label = url
39 39 link_fn = link_to(label, url(*url_, **urlargs))
40 40 return link_fn
41 41
42 42 link = _Link()
43 43
44 44 class _GetError(object):
45 45
46 46 def __call__(self, field_name, form_errors):
47 47 tmpl = """<span class="error_msg">%s</span>"""
48 48 if form_errors and form_errors.has_key(field_name):
49 49 return literal(tmpl % form_errors.get(field_name))
50 50
51 51 get_error = _GetError()
52 52
53 53 def recursive_replace(str, replace=' '):
54 54 """
55 55 Recursive replace of given sign to just one instance
56 56 :param str: given string
57 57 :param replace:char to find and replace multiple instances
58 58
59 59 Examples::
60 60 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
61 61 'Mighty-Mighty-Bo-sstones'
62 62 """
63 63
64 64 if str.find(replace * 2) == -1:
65 65 return str
66 66 else:
67 67 str = str.replace(replace * 2, replace)
68 68 return recursive_replace(str, replace)
69 69
70 70 class _ToolTip(object):
71 71
72 72 def __call__(self, tooltip_title, trim_at=50):
73 73 """
74 74 Special function just to wrap our text into nice formatted autowrapped
75 75 text
76 76 :param tooltip_title:
77 77 """
78 78
79 79 return wrap_paragraphs(escape(tooltip_title), trim_at)\
80 80 .replace('\n', '<br/>')
81 81
82 82 def activate(self):
83 83 """
84 84 Adds tooltip mechanism to the given Html all tooltips have to have
85 85 set class tooltip and set attribute tooltip_title.
86 86 Then a tooltip will be generated based on that
87 87 All with yui js tooltip
88 88 """
89 89
90 90 js = '''
91 91 YAHOO.util.Event.onDOMReady(function(){
92 92 function toolTipsId(){
93 93 var ids = [];
94 94 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
95 95
96 96 for (var i = 0; i < tts.length; i++) {
97 97 //if element doesn not have and id autgenerate one for tooltip
98 98
99 99 if (!tts[i].id){
100 100 tts[i].id='tt'+i*100;
101 101 }
102 102 ids.push(tts[i].id);
103 103 }
104 104 return ids
105 105 };
106 106 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
107 107 context: toolTipsId(),
108 108 monitorresize:false,
109 109 xyoffset :[0,0],
110 110 autodismissdelay:300000,
111 111 hidedelay:5,
112 112 showdelay:20,
113 113 });
114 114
115 115 //Mouse Over event disabled for new repositories since they dont
116 116 //have last commit message
117 117 myToolTips.contextMouseOverEvent.subscribe(
118 118 function(type, args) {
119 119 var context = args[0];
120 120 var txt = context.getAttribute('tooltip_title');
121 121 if(txt){
122 122 return true;
123 123 }
124 124 else{
125 125 return false;
126 126 }
127 127 });
128 128
129 129
130 130 // Set the text for the tooltip just before we display it. Lazy method
131 131 myToolTips.contextTriggerEvent.subscribe(
132 132 function(type, args) {
133 133
134 134
135 135 var context = args[0];
136 136
137 137 var txt = context.getAttribute('tooltip_title');
138 138 this.cfg.setProperty("text", txt);
139 139
140 140
141 141 // positioning of tooltip
142 142 var tt_w = this.element.clientWidth;
143 143 var tt_h = this.element.clientHeight;
144 144
145 145 var context_w = context.offsetWidth;
146 146 var context_h = context.offsetHeight;
147 147
148 148 var pos_x = YAHOO.util.Dom.getX(context);
149 149 var pos_y = YAHOO.util.Dom.getY(context);
150 150
151 151 var display_strategy = 'top';
152 152 var xy_pos = [0,0];
153 153 switch (display_strategy){
154 154
155 155 case 'top':
156 156 var cur_x = (pos_x+context_w/2)-(tt_w/2);
157 157 var cur_y = pos_y-tt_h-4;
158 158 xy_pos = [cur_x,cur_y];
159 159 break;
160 160 case 'bottom':
161 161 var cur_x = (pos_x+context_w/2)-(tt_w/2);
162 162 var cur_y = pos_y+context_h+4;
163 163 xy_pos = [cur_x,cur_y];
164 164 break;
165 165 case 'left':
166 166 var cur_x = (pos_x-tt_w-4);
167 167 var cur_y = pos_y-((tt_h/2)-context_h/2);
168 168 xy_pos = [cur_x,cur_y];
169 169 break;
170 170 case 'right':
171 171 var cur_x = (pos_x+context_w+4);
172 172 var cur_y = pos_y-((tt_h/2)-context_h/2);
173 173 xy_pos = [cur_x,cur_y];
174 174 break;
175 175 default:
176 176 var cur_x = (pos_x+context_w/2)-(tt_w/2);
177 177 var cur_y = pos_y-tt_h-4;
178 178 xy_pos = [cur_x,cur_y];
179 179 break;
180 180
181 181 }
182 182
183 183 this.cfg.setProperty("xy",xy_pos);
184 184
185 185 });
186 186
187 187 //Mouse out
188 188 myToolTips.contextMouseOutEvent.subscribe(
189 189 function(type, args) {
190 190 var context = args[0];
191 191
192 192 });
193 193 });
194 194 '''
195 195 return literal(js)
196 196
197 197 tooltip = _ToolTip()
198 198
199 199 class _FilesBreadCrumbs(object):
200 200
201 201 def __call__(self, repo_name, rev, paths):
202 202 url_l = [link_to(repo_name, url('files_home',
203 203 repo_name=repo_name,
204 204 revision=rev, f_path=''))]
205 205 paths_l = paths.split('/')
206 206
207 207 for cnt, p in enumerate(paths_l, 1):
208 208 if p != '':
209 209 url_l.append(link_to(p, url('files_home',
210 210 repo_name=repo_name,
211 211 revision=rev,
212 212 f_path='/'.join(paths_l[:cnt]))))
213 213
214 214 return literal('/'.join(url_l))
215 215
216 216 files_breadcrumbs = _FilesBreadCrumbs()
217 217 class CodeHtmlFormatter(HtmlFormatter):
218 218
219 219 def wrap(self, source, outfile):
220 220 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
221 221
222 222 def _wrap_code(self, source):
223 223 for cnt, it in enumerate(source, 1):
224 224 i, t = it
225 225 t = '<div id="#S-%s">%s</div>' % (cnt, t)
226 226 yield i, t
227 227 def pygmentize(filenode, **kwargs):
228 228 """
229 229 pygmentize function using pygments
230 230 :param filenode:
231 231 """
232 232 return literal(code_highlight(filenode.content,
233 233 filenode.lexer, CodeHtmlFormatter(**kwargs)))
234 234
235 235 def pygmentize_annotation(filenode, **kwargs):
236 236 """
237 237 pygmentize function for annotation
238 238 :param filenode:
239 239 """
240 240
241 241 color_dict = {}
242 242 def gen_color():
243 243 """generator for getting 10k of evenly distibuted colors using hsv color
244 244 and golden ratio.
245 245 """
246 246 import colorsys
247 247 n = 10000
248 248 golden_ratio = 0.618033988749895
249 249 h = 0.22717784590367374
250 250 #generate 10k nice web friendly colors in the same order
251 251 for c in xrange(n):
252 252 h += golden_ratio
253 253 h %= 1
254 254 HSV_tuple = [h, 0.95, 0.95]
255 255 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
256 256 yield map(lambda x:str(int(x * 256)), RGB_tuple)
257 257
258 258 cgenerator = gen_color()
259 259
260 260 def get_color_string(cs):
261 261 if color_dict.has_key(cs):
262 262 col = color_dict[cs]
263 263 else:
264 264 col = color_dict[cs] = cgenerator.next()
265 265 return "color: rgb(%s)! important;" % (', '.join(col))
266 266
267 267 def url_func(changeset):
268 268 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
269 269 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
270 270
271 271 tooltip_html = tooltip_html % (changeset.author,
272 272 changeset.date,
273 273 tooltip(changeset.message))
274 274 lnk_format = 'r%-5s:%s' % (changeset.revision,
275 275 changeset.raw_id)
276 276 uri = link_to(
277 277 lnk_format,
278 278 url('changeset_home', repo_name=changeset.repository.name,
279 279 revision=changeset.raw_id),
280 280 style=get_color_string(changeset.raw_id),
281 281 class_='tooltip',
282 282 tooltip_title=tooltip_html
283 283 )
284 284
285 285 uri += '\n'
286 286 return uri
287 287 return literal(annotate_highlight(filenode, url_func, **kwargs))
288 288
289 289 def repo_name_slug(value):
290 290 """Return slug of name of repository
291 291 This function is called on each creation/modification
292 292 of repository to prevent bad names in repo
293 293 """
294 294 slug = remove_formatting(value)
295 295 slug = strip_tags(slug)
296 296
297 297 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
298 298 slug = slug.replace(c, '-')
299 299 slug = recursive_replace(slug, '-')
300 300 slug = collapse(slug, '-')
301 301 return slug
302 302
303 303 def get_changeset_safe(repo, rev):
304 304 from vcs.backends.base import BaseRepository
305 305 from vcs.exceptions import RepositoryError
306 306 if not isinstance(repo, BaseRepository):
307 307 raise Exception('You must pass an Repository '
308 308 'object as first argument got %s', type(repo))
309 309
310 310 try:
311 311 cs = repo.get_changeset(rev)
312 312 except RepositoryError:
313 313 from rhodecode.lib.utils import EmptyChangeset
314 314 cs = EmptyChangeset()
315 315 return cs
316 316
317 317
318 318 flash = _Flash()
319 319
320 320
321 321 #==============================================================================
322 322 # MERCURIAL FILTERS available via h.
323 323 #==============================================================================
324 324 from mercurial import util
325 325 from mercurial.templatefilters import person as _person
326 326
327 327
328 328
329 329 def _age(curdate):
330 330 """turns a datetime into an age string."""
331 if not curdate:
332 return ''
331 333
332 334 from datetime import timedelta, datetime
335
333 336 agescales = [("year", 3600 * 24 * 365),
334 337 ("month", 3600 * 24 * 30),
335 338 #("week", 3600 * 24 * 7),
336 339 ("day", 3600 * 24),
337 340 ("hour", 3600),
338 341 ("minute", 60),
339 342 ("second", 1)]
340 343
341 344 age = datetime.now() - curdate
342 345 age_seconds = (age.days * agescales[2][1]) + age.seconds
343 346
344 347 pos = 1
345 348 for scale in agescales:
346 349 if scale[1] <= age_seconds:
347 350 return time_ago_in_words(curdate, agescales[pos][0])
348 351 pos += 1
349 352
350 353 age = lambda x:_age(x)
351 354 capitalize = lambda x: x.capitalize()
352 355 email = util.email
353 356 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
354 357 person = lambda x: _person(x)
355 358 short_id = lambda x: x[:12]
356 359
357 360 #==============================================================================
358 361 # PERMS
359 362 #==============================================================================
360 363 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
361 364 HasRepoPermissionAny, HasRepoPermissionAll
362 365
363 366 #==============================================================================
364 367 # GRAVATAR URL
365 368 #==============================================================================
366 369 import hashlib
367 370 import urllib
368 371 from pylons import request
369 372
370 373 def gravatar_url(email_address, size=30):
371 374 ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME')
372 375 default = 'identicon'
373 376 baseurl_nossl = "http://www.gravatar.com/avatar/"
374 377 baseurl_ssl = "https://secure.gravatar.com/avatar/"
375 378 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
376 379
377 380
378 381 # construct the url
379 382 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
380 383 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
381 384
382 385 return gravatar_url
383 386
384 387 def safe_unicode(str):
385 388 """safe unicode function. In case of UnicodeDecode error we try to return
386 389 unicode with errors replace, if this failes we return unicode with
387 390 string_escape decoding """
388 391
389 392 try:
390 393 u_str = unicode(str)
391 394 except UnicodeDecodeError:
392 395 try:
393 396 u_str = unicode(str, 'utf-8', 'replace')
394 397 except UnicodeDecodeError:
395 398 #incase we have a decode error just represent as byte string
396 399 u_str = unicode(str(str).encode('string_escape'))
397 400
398 401 return u_str
@@ -1,204 +1,204 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # middleware to handle git api calls
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on 2010-04-28
22 22
23 23 @author: marcink
24 24 SimpleGit middleware for handling git protocol request (push/clone etc.)
25 25 It's implemented with basic auth function
26 26 """
27 27
28 28 from dulwich import server as dulserver
29 29
30 30 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
31 31
32 32 def handle(self):
33 33 write = lambda x: self.proto.write_sideband(1, x)
34 34
35 35 graph_walker = dulserver.ProtocolGraphWalker(self, self.repo.object_store,
36 36 self.repo.get_peeled)
37 37 objects_iter = self.repo.fetch_objects(
38 38 graph_walker.determine_wants, graph_walker, self.progress,
39 39 get_tagged=self.get_tagged)
40 40
41 41 # Do they want any objects?
42 42 if len(objects_iter) == 0:
43 43 return
44 44
45 45 self.progress("counting objects: %d, done.\n" % len(objects_iter))
46 46 dulserver.write_pack_data(dulserver.ProtocolFile(None, write), objects_iter,
47 47 len(objects_iter))
48 48 messages = []
49 49 messages.append('thank you for using rhodecode')
50 50
51 51 for msg in messages:
52 52 self.progress(msg + "\n")
53 53 # we are done
54 54 self.proto.write("0000")
55 55
56 56 dulserver.DEFAULT_HANDLERS = {
57 57 'git-upload-pack': SimpleGitUploadPackHandler,
58 58 'git-receive-pack': dulserver.ReceivePackHandler,
59 59 }
60 60
61 61 from dulwich.repo import Repo
62 62 from dulwich.web import HTTPGitApplication
63 63 from paste.auth.basic import AuthBasicAuthenticator
64 64 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
65 65 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
66 66 from rhodecode.lib.utils import action_logger, is_git, invalidate_cache, \
67 67 check_repo_fast
68 68 from rhodecode.model.user import UserModel
69 69 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
70 70 import logging
71 71 import os
72 72 import traceback
73 73
74 74
75 75 log = logging.getLogger(__name__)
76 76
77 77 class SimpleGit(object):
78 78
79 79 def __init__(self, application, config):
80 80 self.application = application
81 81 self.config = config
82 82 #authenticate this git request using
83 83 self.authenticate = AuthBasicAuthenticator('', authfunc)
84 84
85 85 def __call__(self, environ, start_response):
86 86 if not is_git(environ):
87 87 return self.application(environ, start_response)
88 88
89 89 #===================================================================
90 90 # AUTHENTICATE THIS GIT REQUEST
91 91 #===================================================================
92 92 username = REMOTE_USER(environ)
93 93 if not username:
94 94 self.authenticate.realm = self.config['rhodecode_realm']
95 95 result = self.authenticate(environ)
96 96 if isinstance(result, str):
97 97 AUTH_TYPE.update(environ, 'basic')
98 98 REMOTE_USER.update(environ, result)
99 99 else:
100 100 return result.wsgi_application(environ, start_response)
101 101
102 102 try:
103 103 self.repo_name = environ['PATH_INFO'].split('/')[1]
104 104 if self.repo_name.endswith('/'):
105 105 self.repo_name = self.repo_name.rstrip('/')
106 106 except:
107 107 log.error(traceback.format_exc())
108 108 return HTTPInternalServerError()(environ, start_response)
109 109
110 110 #===================================================================
111 111 # CHECK PERMISSIONS FOR THIS REQUEST
112 112 #===================================================================
113 113 action = self.__get_action(environ)
114 114 if action:
115 115 username = self.__get_environ_user(environ)
116 116 try:
117 117 user = self.__get_user(username)
118 118 except:
119 119 log.error(traceback.format_exc())
120 120 return HTTPInternalServerError()(environ, start_response)
121 121
122 122 #check permissions for this repository
123 123 if action == 'push':
124 124 if not HasPermissionAnyMiddleware('repository.write',
125 125 'repository.admin')\
126 126 (user, self.repo_name):
127 127 return HTTPForbidden()(environ, start_response)
128 128
129 129 else:
130 130 #any other action need at least read permission
131 131 if not HasPermissionAnyMiddleware('repository.read',
132 132 'repository.write',
133 133 'repository.admin')\
134 134 (user, self.repo_name):
135 135 return HTTPForbidden()(environ, start_response)
136 136
137 137 #log action
138 138 if action in ('push', 'pull', 'clone'):
139 139 proxy_key = 'HTTP_X_REAL_IP'
140 140 def_key = 'REMOTE_ADDR'
141 141 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
142 142 self.__log_user_action(user, action, self.repo_name, ipaddr)
143 143
144 144 #===================================================================
145 145 # GIT REQUEST HANDLING
146 146 #===================================================================
147 147 self.basepath = self.config['base_path']
148 148 self.repo_path = os.path.join(self.basepath, self.repo_name)
149 149 #quick check if that dir exists...
150 150 if check_repo_fast(self.repo_name, self.basepath):
151 151 return HTTPNotFound()(environ, start_response)
152 152 try:
153 153 app = self.__make_app()
154 except Exception:
154 except:
155 155 log.error(traceback.format_exc())
156 156 return HTTPInternalServerError()(environ, start_response)
157 157
158 158 #invalidate cache on push
159 159 if action == 'push':
160 160 self.__invalidate_cache(self.repo_name)
161 161 messages = []
162 162 messages.append('thank you for using rhodecode')
163 163 return app(environ, start_response)
164 164 else:
165 165 return app(environ, start_response)
166 166
167 167
168 168 def __make_app(self):
169 169 backend = dulserver.DictBackend({'/' + self.repo_name: Repo(self.repo_path)})
170 170 gitserve = HTTPGitApplication(backend)
171 171
172 172 return gitserve
173 173
174 174 def __get_environ_user(self, environ):
175 175 return environ.get('REMOTE_USER')
176 176
177 177 def __get_user(self, username):
178 178 return UserModel().get_by_username(username, cache=True)
179 179
180 180 def __get_action(self, environ):
181 181 """
182 182 Maps git request commands into a pull or push command.
183 183 :param environ:
184 184 """
185 185 service = environ['QUERY_STRING'].split('=')
186 186 if len(service) > 1:
187 187 service_cmd = service[1]
188 188 mapping = {'git-receive-pack': 'push',
189 189 'git-upload-pack': 'pull',
190 190 }
191 191
192 192 return mapping.get(service_cmd, service_cmd if service_cmd else 'other')
193 193 else:
194 194 return 'other'
195 195
196 196 def __log_user_action(self, user, action, repo, ipaddr):
197 197 action_logger(user, action, repo, ipaddr)
198 198
199 199 def __invalidate_cache(self, repo_name):
200 200 """we know that some change was made to repositories and we should
201 201 invalidate the cache to see the changes right away but only for
202 202 push requests"""
203 203 invalidate_cache('cached_repo_list')
204 204 invalidate_cache('full_changelog', repo_name)
@@ -1,538 +1,541 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Utilities for RhodeCode
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19 """
20 20 Created on April 18, 2010
21 21 Utilities for RhodeCode
22 22 @author: marcink
23 23 """
24 24
25 25 from UserDict import DictMixin
26 26 from mercurial import ui, config, hg
27 27 from mercurial.error import RepoError
28 28 from rhodecode.model import meta
29 29 from rhodecode.model.caching_query import FromCache
30 30 from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, \
31 31 UserLog
32 32 from rhodecode.model.repo import RepoModel
33 33 from rhodecode.model.user import UserModel
34 34 from vcs.backends.base import BaseChangeset
35 35 from vcs.backends.git import GitRepository
36 36 from vcs.backends.hg import MercurialRepository
37 37 from vcs.utils.lazy import LazyProperty
38 38 import datetime
39 39 import logging
40 40 import os
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 def get_repo_slug(request):
46 46 return request.environ['pylons.routes_dict'].get('repo_name')
47 47
48 48 def is_mercurial(environ):
49 49 """
50 50 Returns True if request's target is mercurial server - header
51 51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 52 """
53 53 http_accept = environ.get('HTTP_ACCEPT')
54 54 if http_accept and http_accept.startswith('application/mercurial'):
55 55 return True
56 56 return False
57 57
58 58 def is_git(environ):
59 59 """
60 60 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
61 61 then have git client version given.
62 62
63 63 :param environ:
64 64 """
65 65 http_user_agent = environ.get('HTTP_USER_AGENT')
66 66 if http_user_agent.startswith('git'):
67 67 return True
68 68 return False
69 69
70 70 def action_logger(user, action, repo, ipaddr, sa=None):
71 71 """
72 72 Action logger for various action made by users
73 73 """
74 74
75 75 if not sa:
76 76 sa = meta.Session()
77 77
78 78 try:
79 79 if hasattr(user, 'user_id'):
80 80 user_id = user.user_id
81 81 elif isinstance(user, basestring):
82 82 user_id = UserModel(sa).get_by_username(user, cache=False).user_id
83 83 else:
84 84 raise Exception('You have to provide user object or username')
85 85
86 86 repo_name = repo.lstrip('/')
87 87 user_log = UserLog()
88 88 user_log.user_id = user_id
89 89 user_log.action = action
90 90 user_log.repository_name = repo_name
91 91 user_log.repository = RepoModel(sa).get(repo_name, cache=False)
92 92 user_log.action_date = datetime.datetime.now()
93 93 user_log.user_ip = ipaddr
94 94 sa.add(user_log)
95 95 sa.commit()
96 96
97 97 log.info('Adding user %s, action %s on %s',
98 98 user.username, action, repo)
99 99 except Exception, e:
100 100 sa.rollback()
101 101 log.error('could not log user action:%s', str(e))
102 102
103 103 def get_repos(path, recursive=False, initial=False):
104 104 """
105 105 Scans given path for repos and return (name,(type,path)) tuple
106 106 :param prefix:
107 107 :param path:
108 108 :param recursive:
109 109 :param initial:
110 110 """
111 111 from vcs.utils.helpers import get_scm
112 112 from vcs.exceptions import VCSError
113 113
114 114 try:
115 115 scm = get_scm(path)
116 116 except:
117 117 pass
118 118 else:
119 119 raise Exception('The given path %s should not be a repository got %s',
120 120 path, scm)
121 121
122 122 for dirpath in os.listdir(path):
123 123 try:
124 124 yield dirpath, get_scm(os.path.join(path, dirpath))
125 125 except VCSError:
126 126 pass
127 127
128 128 if __name__ == '__main__':
129 129 get_repos('', '/home/marcink/workspace-python')
130 130
131 131
132 132 def check_repo_fast(repo_name, base_path):
133 133 if os.path.isdir(os.path.join(base_path, repo_name)):return False
134 134 return True
135 135
136 136 def check_repo(repo_name, base_path, verify=True):
137 137
138 138 repo_path = os.path.join(base_path, repo_name)
139 139
140 140 try:
141 141 if not check_repo_fast(repo_name, base_path):
142 142 return False
143 143 r = hg.repository(ui.ui(), repo_path)
144 144 if verify:
145 145 hg.verify(r)
146 146 #here we hnow that repo exists it was verified
147 147 log.info('%s repo is already created', repo_name)
148 148 return False
149 149 except RepoError:
150 150 #it means that there is no valid repo there...
151 151 log.info('%s repo is free for creation', repo_name)
152 152 return True
153 153
154 154 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
155 155 while True:
156 156 ok = raw_input(prompt)
157 157 if ok in ('y', 'ye', 'yes'): return True
158 158 if ok in ('n', 'no', 'nop', 'nope'): return False
159 159 retries = retries - 1
160 160 if retries < 0: raise IOError
161 161 print complaint
162 162
163 163 def get_hg_ui_cached():
164 164 try:
165 165 sa = meta.Session
166 166 ret = sa.query(RhodeCodeUi)\
167 167 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
168 168 .all()
169 169 except:
170 170 pass
171 171 finally:
172 172 meta.Session.remove()
173 173 return ret
174 174
175 175
176 176 def get_hg_settings():
177 177 try:
178 178 sa = meta.Session()
179 179 ret = sa.query(RhodeCodeSettings)\
180 180 .options(FromCache("sql_cache_short", "get_hg_settings"))\
181 181 .all()
182 182 except:
183 183 pass
184 184 finally:
185 185 meta.Session.remove()
186 186
187 187 if not ret:
188 188 raise Exception('Could not get application settings !')
189 189 settings = {}
190 190 for each in ret:
191 191 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
192 192
193 193 return settings
194 194
195 195 def get_hg_ui_settings():
196 196 try:
197 197 sa = meta.Session()
198 198 ret = sa.query(RhodeCodeUi).all()
199 199 except:
200 200 pass
201 201 finally:
202 202 meta.Session.remove()
203 203
204 204 if not ret:
205 205 raise Exception('Could not get application ui settings !')
206 206 settings = {}
207 207 for each in ret:
208 208 k = each.ui_key
209 209 v = each.ui_value
210 210 if k == '/':
211 211 k = 'root_path'
212 212
213 213 if k.find('.') != -1:
214 214 k = k.replace('.', '_')
215 215
216 216 if each.ui_section == 'hooks':
217 217 v = each.ui_active
218 218
219 219 settings[each.ui_section + '_' + k] = v
220 220
221 221 return settings
222 222
223 223 #propagated from mercurial documentation
224 224 ui_sections = ['alias', 'auth',
225 225 'decode/encode', 'defaults',
226 226 'diff', 'email',
227 227 'extensions', 'format',
228 228 'merge-patterns', 'merge-tools',
229 229 'hooks', 'http_proxy',
230 230 'smtp', 'patch',
231 231 'paths', 'profiling',
232 232 'server', 'trusted',
233 233 'ui', 'web', ]
234 234
235 235 def make_ui(read_from='file', path=None, checkpaths=True):
236 236 """
237 237 A function that will read python rc files or database
238 238 and make an mercurial ui object from read options
239 239
240 240 :param path: path to mercurial config file
241 241 :param checkpaths: check the path
242 242 :param read_from: read from 'file' or 'db'
243 243 """
244 244
245 245 baseui = ui.ui()
246 246
247 247 if read_from == 'file':
248 248 if not os.path.isfile(path):
249 249 log.warning('Unable to read config file %s' % path)
250 250 return False
251 251 log.debug('reading hgrc from %s', path)
252 252 cfg = config.config()
253 253 cfg.read(path)
254 254 for section in ui_sections:
255 255 for k, v in cfg.items(section):
256 256 baseui.setconfig(section, k, v)
257 257 log.debug('settings ui from file[%s]%s:%s', section, k, v)
258 258
259 259 elif read_from == 'db':
260 260 hg_ui = get_hg_ui_cached()
261 261 for ui_ in hg_ui:
262 262 if ui_.ui_active:
263 263 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
264 264 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
265 265
266 266
267 267 return baseui
268 268
269 269
270 270 def set_rhodecode_config(config):
271 271 hgsettings = get_hg_settings()
272 272
273 273 for k, v in hgsettings.items():
274 274 config[k] = v
275 275
276 276 def invalidate_cache(name, *args):
277 277 """Invalidates given name cache"""
278 278
279 279 from beaker.cache import region_invalidate
280 280 log.info('INVALIDATING CACHE FOR %s', name)
281 281
282 282 """propagate our arguments to make sure invalidation works. First
283 283 argument has to be the name of cached func name give to cache decorator
284 284 without that the invalidation would not work"""
285 285 tmp = [name]
286 286 tmp.extend(args)
287 287 args = tuple(tmp)
288 288
289 289 if name == 'cached_repo_list':
290 290 from rhodecode.model.hg import _get_repos_cached
291 291 region_invalidate(_get_repos_cached, None, *args)
292 292
293 293 if name == 'full_changelog':
294 294 from rhodecode.model.hg import _full_changelog_cached
295 295 region_invalidate(_full_changelog_cached, None, *args)
296 296
297 297 class EmptyChangeset(BaseChangeset):
298 298 """
299 An dummy empty changeset.
299 An dummy empty changeset. It's possible to pass hash when creating
300 an EmptyChangeset
300 301 """
301 302
302 revision = -1
303 message = ''
304 author = ''
305 date = ''
303 def __init__(self, cs='0' * 40):
304 self._empty_cs = cs
305 self.revision = -1
306 self.message = ''
307 self.author = ''
308 self.date = ''
306 309
307 310 @LazyProperty
308 311 def raw_id(self):
309 312 """
310 313 Returns raw string identifying this changeset, useful for web
311 314 representation.
312 315 """
313 return '0' * 40
316 return self._empty_cs
314 317
315 318 @LazyProperty
316 319 def short_id(self):
317 320 return self.raw_id[:12]
318 321
319 322 def get_file_changeset(self, path):
320 323 return self
321 324
322 325 def get_file_content(self, path):
323 326 return u''
324 327
325 328 def get_file_size(self, path):
326 329 return 0
327 330
328 331 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
329 332 """
330 333 maps all found repositories into db
331 334 """
332 335
333 336 sa = meta.Session()
334 337 rm = RepoModel(sa)
335 338 user = sa.query(User).filter(User.admin == True).first()
336 339
337 340 for name, repo in initial_repo_list.items():
338 341 if not rm.get(name, cache=False):
339 342 log.info('repository %s not found creating default', name)
340 343
341 344 if isinstance(repo, MercurialRepository):
342 345 repo_type = 'hg'
343 346 if isinstance(repo, GitRepository):
344 347 repo_type = 'git'
345 348
346 349 form_data = {
347 350 'repo_name':name,
348 351 'repo_type':repo_type,
349 352 'description':repo.description if repo.description != 'unknown' else \
350 353 'auto description for %s' % name,
351 354 'private':False
352 355 }
353 356 rm.create(form_data, user, just_db=True)
354 357
355 358
356 359 if remove_obsolete:
357 360 #remove from database those repositories that are not in the filesystem
358 361 for repo in sa.query(Repository).all():
359 362 if repo.repo_name not in initial_repo_list.keys():
360 363 sa.delete(repo)
361 364 sa.commit()
362 365
363 366
364 367 meta.Session.remove()
365 368
366 369
367 370 class OrderedDict(dict, DictMixin):
368 371
369 372 def __init__(self, *args, **kwds):
370 373 if len(args) > 1:
371 374 raise TypeError('expected at most 1 arguments, got %d' % len(args))
372 375 try:
373 376 self.__end
374 377 except AttributeError:
375 378 self.clear()
376 379 self.update(*args, **kwds)
377 380
378 381 def clear(self):
379 382 self.__end = end = []
380 383 end += [None, end, end] # sentinel node for doubly linked list
381 384 self.__map = {} # key --> [key, prev, next]
382 385 dict.clear(self)
383 386
384 387 def __setitem__(self, key, value):
385 388 if key not in self:
386 389 end = self.__end
387 390 curr = end[1]
388 391 curr[2] = end[1] = self.__map[key] = [key, curr, end]
389 392 dict.__setitem__(self, key, value)
390 393
391 394 def __delitem__(self, key):
392 395 dict.__delitem__(self, key)
393 396 key, prev, next = self.__map.pop(key)
394 397 prev[2] = next
395 398 next[1] = prev
396 399
397 400 def __iter__(self):
398 401 end = self.__end
399 402 curr = end[2]
400 403 while curr is not end:
401 404 yield curr[0]
402 405 curr = curr[2]
403 406
404 407 def __reversed__(self):
405 408 end = self.__end
406 409 curr = end[1]
407 410 while curr is not end:
408 411 yield curr[0]
409 412 curr = curr[1]
410 413
411 414 def popitem(self, last=True):
412 415 if not self:
413 416 raise KeyError('dictionary is empty')
414 417 if last:
415 418 key = reversed(self).next()
416 419 else:
417 420 key = iter(self).next()
418 421 value = self.pop(key)
419 422 return key, value
420 423
421 424 def __reduce__(self):
422 425 items = [[k, self[k]] for k in self]
423 426 tmp = self.__map, self.__end
424 427 del self.__map, self.__end
425 428 inst_dict = vars(self).copy()
426 429 self.__map, self.__end = tmp
427 430 if inst_dict:
428 431 return (self.__class__, (items,), inst_dict)
429 432 return self.__class__, (items,)
430 433
431 434 def keys(self):
432 435 return list(self)
433 436
434 437 setdefault = DictMixin.setdefault
435 438 update = DictMixin.update
436 439 pop = DictMixin.pop
437 440 values = DictMixin.values
438 441 items = DictMixin.items
439 442 iterkeys = DictMixin.iterkeys
440 443 itervalues = DictMixin.itervalues
441 444 iteritems = DictMixin.iteritems
442 445
443 446 def __repr__(self):
444 447 if not self:
445 448 return '%s()' % (self.__class__.__name__,)
446 449 return '%s(%r)' % (self.__class__.__name__, self.items())
447 450
448 451 def copy(self):
449 452 return self.__class__(self)
450 453
451 454 @classmethod
452 455 def fromkeys(cls, iterable, value=None):
453 456 d = cls()
454 457 for key in iterable:
455 458 d[key] = value
456 459 return d
457 460
458 461 def __eq__(self, other):
459 462 if isinstance(other, OrderedDict):
460 463 return len(self) == len(other) and self.items() == other.items()
461 464 return dict.__eq__(self, other)
462 465
463 466 def __ne__(self, other):
464 467 return not self == other
465 468
466 469
467 470 #===============================================================================
468 471 # TEST FUNCTIONS AND CREATORS
469 472 #===============================================================================
470 473 def create_test_index(repo_location, full_index):
471 474 """Makes default test index
472 475 :param repo_location:
473 476 :param full_index:
474 477 """
475 478 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
476 479 from rhodecode.lib.pidlock import DaemonLock, LockHeld
477 480 from rhodecode.lib.indexers import IDX_LOCATION
478 481 import shutil
479 482
480 483 if os.path.exists(IDX_LOCATION):
481 484 shutil.rmtree(IDX_LOCATION)
482 485
483 486 try:
484 487 l = DaemonLock()
485 488 WhooshIndexingDaemon(repo_location=repo_location)\
486 489 .run(full_index=full_index)
487 490 l.release()
488 491 except LockHeld:
489 492 pass
490 493
491 494 def create_test_env(repos_test_path, config):
492 495 """Makes a fresh database and
493 496 install test repository into tmp dir
494 497 """
495 498 from rhodecode.lib.db_manage import DbManage
496 499 import tarfile
497 500 import shutil
498 501 from os.path import dirname as dn, join as jn, abspath
499 502
500 503 log = logging.getLogger('TestEnvCreator')
501 504 # create logger
502 505 log.setLevel(logging.DEBUG)
503 506 log.propagate = True
504 507 # create console handler and set level to debug
505 508 ch = logging.StreamHandler()
506 509 ch.setLevel(logging.DEBUG)
507 510
508 511 # create formatter
509 512 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
510 513
511 514 # add formatter to ch
512 515 ch.setFormatter(formatter)
513 516
514 517 # add ch to logger
515 518 log.addHandler(ch)
516 519
517 520 #PART ONE create db
518 521 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
519 522 log.debug('making test db %s', dbname)
520 523
521 524 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
522 525 tests=True)
523 526 dbmanage.create_tables(override=True)
524 527 dbmanage.config_prompt(repos_test_path)
525 528 dbmanage.create_default_user()
526 529 dbmanage.admin_prompt()
527 530 dbmanage.create_permissions()
528 531 dbmanage.populate_default_permissions()
529 532
530 533 #PART TWO make test repo
531 534 log.debug('making test vcs repo')
532 535 if os.path.isdir('/tmp/vcs_test'):
533 536 shutil.rmtree('/tmp/vcs_test')
534 537
535 538 cur_dir = dn(dn(abspath(__file__)))
536 539 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz"))
537 540 tar.extractall('/tmp')
538 541 tar.close()
@@ -1,88 +1,88 b''
1 1 from rhodecode import get_version
2 2 import sys
3 3 py_version = sys.version_info
4 4
5 5 requirements = [
6 6 "Pylons>=1.0.0",
7 7 "SQLAlchemy>=0.6.4",
8 8 "Mako>=0.3.5",
9 "vcs==0.1.10",
9 "vcs>=0.1.10",
10 10 "pygments>=1.3.0",
11 11 "mercurial==1.6.4",
12 "whoosh==1.1.1",
13 "celery==2.1.1",
12 "whoosh>=1.2.5",
13 "celery>=2.1.2",
14 14 "py-bcrypt",
15 15 "babel",
16 16 ]
17 17
18 18 classifiers = ['Development Status :: 4 - Beta',
19 19 'Environment :: Web Environment',
20 20 'Framework :: Pylons',
21 21 'Intended Audience :: Developers',
22 22 'License :: OSI Approved :: BSD License',
23 23 'Operating System :: OS Independent',
24 24 'Programming Language :: Python', ]
25 25
26 26 if sys.version_info < (2, 6):
27 27 requirements.append("simplejson")
28 28 requirements.append("pysqlite")
29 29
30 30 #additional files from project that goes somewhere in the filesystem
31 31 #relative to sys.prefix
32 32 data_files = []
33 33
34 34 #additional files that goes into package itself
35 35 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
36 36
37 37 description = 'Mercurial repository serving and browsing app'
38 38 #long description
39 39 try:
40 40 readme_file = 'README.rst'
41 41 long_description = open(readme_file).read()
42 42 except IOError, err:
43 43 sys.stderr.write("[WARNING] Cannot find file specified as "
44 44 "long_description (%s)\n skipping that file" % readme_file)
45 45 long_description = description
46 46
47 47
48 48 try:
49 49 from setuptools import setup, find_packages
50 50 except ImportError:
51 51 from ez_setup import use_setuptools
52 52 use_setuptools()
53 53 from setuptools import setup, find_packages
54 54 #packages
55 55 packages = find_packages(exclude=['ez_setup'])
56 56
57 57 setup(
58 58 name='RhodeCode',
59 59 version=get_version(),
60 60 description=description,
61 61 long_description=long_description,
62 62 keywords='rhodiumcode mercurial web hgwebdir replacement serving hgweb rhodecode',
63 63 license='BSD',
64 64 author='Marcin Kuzminski',
65 65 author_email='marcin@python-works.com',
66 66 url='http://hg.python-works.com',
67 67 install_requires=requirements,
68 68 classifiers=classifiers,
69 69 setup_requires=["PasteScript>=1.6.3"],
70 70 data_files=data_files,
71 71 packages=packages,
72 72 include_package_data=True,
73 73 test_suite='nose.collector',
74 74 package_data=package_data,
75 75 message_extractors={'rhodecode': [
76 76 ('**.py', 'python', None),
77 77 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
78 78 ('public/**', 'ignore', None)]},
79 79 zip_safe=False,
80 80 paster_plugins=['PasteScript', 'Pylons'],
81 81 entry_points="""
82 82 [paste.app_factory]
83 83 main = rhodecode.config.middleware:make_app
84 84
85 85 [paste.app_install]
86 86 main = pylons.util:PylonsInstaller
87 87 """,
88 88 )
General Comments 0
You need to be logged in to leave comments. Login now