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