##// END OF EJS Templates
Fixes issue #201...
marcink -
r1373:66f03a87 beta
parent child Browse files
Show More
@@ -1,188 +1,188 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.summary
3 rhodecode.controllers.summary
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Summary controller for Rhodecode
6 Summary controller for Rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import calendar
26 import calendar
27 import logging
27 import logging
28 from time import mktime
28 from time import mktime
29 from datetime import datetime, timedelta, date
29 from datetime import datetime, timedelta, date
30
30
31 from vcs.exceptions import ChangesetError
31 from vcs.exceptions import ChangesetError
32
32
33 from pylons import tmpl_context as c, request, url
33 from pylons import tmpl_context as c, request, url
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from rhodecode.model.db import Statistics, Repository
36 from rhodecode.model.db import Statistics, Repository
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38
38
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.utils import EmptyChangeset
41 from rhodecode.lib.utils import EmptyChangeset
42 from rhodecode.lib.odict import OrderedDict
42 from rhodecode.lib.odict import OrderedDict
43
43
44 from rhodecode.lib.celerylib import run_task
44 from rhodecode.lib.celerylib import run_task
45 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
45 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
46 LANGUAGES_EXTENSIONS_MAP
46 LANGUAGES_EXTENSIONS_MAP
47 from rhodecode.lib.helpers import RepoPage
47 from rhodecode.lib.helpers import RepoPage
48
48
49 try:
49 try:
50 import json
50 import json
51 except ImportError:
51 except ImportError:
52 #python 2.5 compatibility
52 #python 2.5 compatibility
53 import simplejson as json
53 import simplejson as json
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class SummaryController(BaseRepoController):
57 class SummaryController(BaseRepoController):
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
61 'repository.admin')
61 'repository.admin')
62 def __before__(self):
62 def __before__(self):
63 super(SummaryController, self).__before__()
63 super(SummaryController, self).__before__()
64
64
65 def index(self, repo_name):
65 def index(self, repo_name):
66
66
67 e = request.environ
67 e = request.environ
68 c.dbrepo = dbrepo = Repository.by_repo_name(repo_name)
68 c.dbrepo = dbrepo = c.rhodecode_db_repo
69
69
70 c.following = self.scm_model.is_following_repo(repo_name,
70 c.following = self.scm_model.is_following_repo(repo_name,
71 self.rhodecode_user.user_id)
71 self.rhodecode_user.user_id)
72
72
73 def url_generator(**kw):
73 def url_generator(**kw):
74 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
74 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
75
75
76 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
76 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
77 items_per_page=10, url=url_generator)
77 items_per_page=10, url=url_generator)
78
78
79 if self.rhodecode_user.username == 'default':
79 if self.rhodecode_user.username == 'default':
80 #for default(anonymous) user we don't need to pass credentials
80 #for default(anonymous) user we don't need to pass credentials
81 username = ''
81 username = ''
82 password = ''
82 password = ''
83 else:
83 else:
84 username = str(self.rhodecode_user.username)
84 username = str(self.rhodecode_user.username)
85 password = '@'
85 password = '@'
86
86
87 if e.get('wsgi.url_scheme') == 'https':
87 if e.get('wsgi.url_scheme') == 'https':
88 split_s = 'https://'
88 split_s = 'https://'
89 else:
89 else:
90 split_s = 'http://'
90 split_s = 'http://'
91
91
92 qualified_uri = [split_s] + [url.current(qualified=True)\
92 qualified_uri = [split_s] + [url.current(qualified=True)\
93 .split(split_s)[-1]]
93 .split(split_s)[-1]]
94 uri = u'%(proto)s%(user)s%(pass)s%(rest)s' \
94 uri = u'%(proto)s%(user)s%(pass)s%(rest)s' \
95 % {'user': username,
95 % {'user': username,
96 'pass': password,
96 'pass': password,
97 'proto': qualified_uri[0],
97 'proto': qualified_uri[0],
98 'rest': qualified_uri[1]}
98 'rest': qualified_uri[1]}
99 c.clone_repo_url = uri
99 c.clone_repo_url = uri
100 c.repo_tags = OrderedDict()
100 c.repo_tags = OrderedDict()
101 for name, hash in c.rhodecode_repo.tags.items()[:10]:
101 for name, hash in c.rhodecode_repo.tags.items()[:10]:
102 try:
102 try:
103 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
103 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
104 except ChangesetError:
104 except ChangesetError:
105 c.repo_tags[name] = EmptyChangeset(hash)
105 c.repo_tags[name] = EmptyChangeset(hash)
106
106
107 c.repo_branches = OrderedDict()
107 c.repo_branches = OrderedDict()
108 for name, hash in c.rhodecode_repo.branches.items()[:10]:
108 for name, hash in c.rhodecode_repo.branches.items()[:10]:
109 try:
109 try:
110 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
110 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
111 except ChangesetError:
111 except ChangesetError:
112 c.repo_branches[name] = EmptyChangeset(hash)
112 c.repo_branches[name] = EmptyChangeset(hash)
113
113
114 td = date.today() + timedelta(days=1)
114 td = date.today() + timedelta(days=1)
115 td_1m = td - timedelta(days=calendar.mdays[td.month])
115 td_1m = td - timedelta(days=calendar.mdays[td.month])
116 td_1y = td - timedelta(days=365)
116 td_1y = td - timedelta(days=365)
117
117
118 ts_min_m = mktime(td_1m.timetuple())
118 ts_min_m = mktime(td_1m.timetuple())
119 ts_min_y = mktime(td_1y.timetuple())
119 ts_min_y = mktime(td_1y.timetuple())
120 ts_max_y = mktime(td.timetuple())
120 ts_max_y = mktime(td.timetuple())
121
121
122 if dbrepo.enable_statistics:
122 if dbrepo.enable_statistics:
123 c.no_data_msg = _('No data loaded yet')
123 c.no_data_msg = _('No data loaded yet')
124 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
124 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
125 else:
125 else:
126 c.no_data_msg = _('Statistics are disabled for this repository')
126 c.no_data_msg = _('Statistics are disabled for this repository')
127 c.ts_min = ts_min_m
127 c.ts_min = ts_min_m
128 c.ts_max = ts_max_y
128 c.ts_max = ts_max_y
129
129
130 stats = self.sa.query(Statistics)\
130 stats = self.sa.query(Statistics)\
131 .filter(Statistics.repository == dbrepo)\
131 .filter(Statistics.repository == dbrepo)\
132 .scalar()
132 .scalar()
133
133
134 c.stats_percentage = 0
134 c.stats_percentage = 0
135
135
136 if stats and stats.languages:
136 if stats and stats.languages:
137 c.no_data = False is dbrepo.enable_statistics
137 c.no_data = False is dbrepo.enable_statistics
138 lang_stats_d = json.loads(stats.languages)
138 lang_stats_d = json.loads(stats.languages)
139 c.commit_data = stats.commit_activity
139 c.commit_data = stats.commit_activity
140 c.overview_data = stats.commit_activity_combined
140 c.overview_data = stats.commit_activity_combined
141
141
142 lang_stats = [(x, {"count": y,
142 lang_stats = [(x, {"count": y,
143 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
143 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
144 for x, y in lang_stats_d.items()]
144 for x, y in lang_stats_d.items()]
145
145
146 c.trending_languages = json.dumps(OrderedDict(
146 c.trending_languages = json.dumps(OrderedDict(
147 sorted(lang_stats, reverse=True,
147 sorted(lang_stats, reverse=True,
148 key=lambda k: k[1])[:10]
148 key=lambda k: k[1])[:10]
149 )
149 )
150 )
150 )
151 last_rev = stats.stat_on_revision
151 last_rev = stats.stat_on_revision
152 c.repo_last_rev = c.rhodecode_repo.count() - 1 \
152 c.repo_last_rev = c.rhodecode_repo.count() - 1 \
153 if c.rhodecode_repo.revisions else 0
153 if c.rhodecode_repo.revisions else 0
154 if last_rev == 0 or c.repo_last_rev == 0:
154 if last_rev == 0 or c.repo_last_rev == 0:
155 pass
155 pass
156 else:
156 else:
157 c.stats_percentage = '%.2f' % ((float((last_rev)) /
157 c.stats_percentage = '%.2f' % ((float((last_rev)) /
158 c.repo_last_rev) * 100)
158 c.repo_last_rev) * 100)
159 else:
159 else:
160 c.commit_data = json.dumps({})
160 c.commit_data = json.dumps({})
161 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
161 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
162 c.trending_languages = json.dumps({})
162 c.trending_languages = json.dumps({})
163 c.no_data = True
163 c.no_data = True
164
164
165 c.enable_downloads = dbrepo.enable_downloads
165 c.enable_downloads = dbrepo.enable_downloads
166 if c.enable_downloads:
166 if c.enable_downloads:
167 c.download_options = self._get_download_links(c.rhodecode_repo)
167 c.download_options = self._get_download_links(c.rhodecode_repo)
168
168
169 return render('summary/summary.html')
169 return render('summary/summary.html')
170
170
171 def _get_download_links(self, repo):
171 def _get_download_links(self, repo):
172
172
173 download_l = []
173 download_l = []
174
174
175 branches_group = ([], _("Branches"))
175 branches_group = ([], _("Branches"))
176 tags_group = ([], _("Tags"))
176 tags_group = ([], _("Tags"))
177
177
178 for name, chs in c.rhodecode_repo.branches.items():
178 for name, chs in c.rhodecode_repo.branches.items():
179 #chs = chs.split(':')[-1]
179 #chs = chs.split(':')[-1]
180 branches_group[0].append((chs, name),)
180 branches_group[0].append((chs, name),)
181 download_l.append(branches_group)
181 download_l.append(branches_group)
182
182
183 for name, chs in c.rhodecode_repo.tags.items():
183 for name, chs in c.rhodecode_repo.tags.items():
184 #chs = chs.split(':')[-1]
184 #chs = chs.split(':')[-1]
185 tags_group[0].append((chs, name),)
185 tags_group[0].append((chs, name),)
186 download_l.append(tags_group)
186 download_l.append(tags_group)
187
187
188 return download_l
188 return download_l
@@ -1,286 +1,315 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.__init__
3 rhodecode.lib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26
26
27 def __get_lem():
27 def __get_lem():
28 from pygments import lexers
28 from pygments import lexers
29 from string import lower
29 from string import lower
30 from collections import defaultdict
30 from collections import defaultdict
31
31
32 d = defaultdict(lambda: [])
32 d = defaultdict(lambda: [])
33
33
34 def __clean(s):
34 def __clean(s):
35 s = s.lstrip('*')
35 s = s.lstrip('*')
36 s = s.lstrip('.')
36 s = s.lstrip('.')
37
37
38 if s.find('[') != -1:
38 if s.find('[') != -1:
39 exts = []
39 exts = []
40 start, stop = s.find('['), s.find(']')
40 start, stop = s.find('['), s.find(']')
41
41
42 for suffix in s[start + 1:stop]:
42 for suffix in s[start + 1:stop]:
43 exts.append(s[:s.find('[')] + suffix)
43 exts.append(s[:s.find('[')] + suffix)
44 return map(lower, exts)
44 return map(lower, exts)
45 else:
45 else:
46 return map(lower, [s])
46 return map(lower, [s])
47
47
48 for lx, t in sorted(lexers.LEXERS.items()):
48 for lx, t in sorted(lexers.LEXERS.items()):
49 m = map(__clean, t[-2])
49 m = map(__clean, t[-2])
50 if m:
50 if m:
51 m = reduce(lambda x, y: x + y, m)
51 m = reduce(lambda x, y: x + y, m)
52 for ext in m:
52 for ext in m:
53 desc = lx.replace('Lexer', '')
53 desc = lx.replace('Lexer', '')
54 d[ext].append(desc)
54 d[ext].append(desc)
55
55
56 return dict(d)
56 return dict(d)
57
57
58 # language map is also used by whoosh indexer, which for those specified
58 # language map is also used by whoosh indexer, which for those specified
59 # extensions will index it's content
59 # extensions will index it's content
60 LANGUAGES_EXTENSIONS_MAP = __get_lem()
60 LANGUAGES_EXTENSIONS_MAP = __get_lem()
61
61
62 # Additional mappings that are not present in the pygments lexers
62 # Additional mappings that are not present in the pygments lexers
63 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
63 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
64 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
64 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
65
65
66 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
66 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
67
67
68
68
69 def str2bool(_str):
69 def str2bool(_str):
70 """
70 """
71 returs True/False value from given string, it tries to translate the
71 returs True/False value from given string, it tries to translate the
72 string into boolean
72 string into boolean
73
73
74 :param _str: string value to translate into boolean
74 :param _str: string value to translate into boolean
75 :rtype: boolean
75 :rtype: boolean
76 :returns: boolean from given string
76 :returns: boolean from given string
77 """
77 """
78 if _str is None:
78 if _str is None:
79 return False
79 return False
80 if _str in (True, False):
80 if _str in (True, False):
81 return _str
81 return _str
82 _str = str(_str).strip().lower()
82 _str = str(_str).strip().lower()
83 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
83 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
84
84
85
85
86 def convert_line_endings(temp, mode):
86 def convert_line_endings(line, mode):
87 """
88 Converts a given line "line end" accordingly to given mode
89
90 Available modes are::
91 0 - Unix
92 1 - Mac
93 2 - DOS
94
95 :param line: given line to convert
96 :param mode: mode to convert to
97 :rtype: str
98 :return: converted line according to mode
99 """
87 from string import replace
100 from string import replace
88 #modes: 0 - Unix, 1 - Mac, 2 - DOS
101
89 if mode == 0:
102 if mode == 0:
90 temp = replace(temp, '\r\n', '\n')
103 line = replace(line, '\r\n', '\n')
91 temp = replace(temp, '\r', '\n')
104 line = replace(line, '\r', '\n')
92 elif mode == 1:
105 elif mode == 1:
93 temp = replace(temp, '\r\n', '\r')
106 line = replace(line, '\r\n', '\r')
94 temp = replace(temp, '\n', '\r')
107 line = replace(line, '\n', '\r')
95 elif mode == 2:
108 elif mode == 2:
96 import re
109 import re
97 temp = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", temp)
110 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
98 return temp
111 return line
99
112
100
113
101 def detect_mode(line, default):
114 def detect_mode(line, default):
102 """
115 """
103 Detects line break for given line, if line break couldn't be found
116 Detects line break for given line, if line break couldn't be found
104 given default value is returned
117 given default value is returned
105
118
106 :param line: str line
119 :param line: str line
107 :param default: default
120 :param default: default
108 :rtype: int
121 :rtype: int
109 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
122 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
110 """
123 """
111 if line.endswith('\r\n'):
124 if line.endswith('\r\n'):
112 return 2
125 return 2
113 elif line.endswith('\n'):
126 elif line.endswith('\n'):
114 return 0
127 return 0
115 elif line.endswith('\r'):
128 elif line.endswith('\r'):
116 return 1
129 return 1
117 else:
130 else:
118 return default
131 return default
119
132
120
133
121 def generate_api_key(username, salt=None):
134 def generate_api_key(username, salt=None):
122 """
135 """
123 Generates unique API key for given username,if salt is not given
136 Generates unique API key for given username,if salt is not given
124 it'll be generated from some random string
137 it'll be generated from some random string
125
138
126 :param username: username as string
139 :param username: username as string
127 :param salt: salt to hash generate KEY
140 :param salt: salt to hash generate KEY
128 :rtype: str
141 :rtype: str
129 :returns: sha1 hash from username+salt
142 :returns: sha1 hash from username+salt
130 """
143 """
131 from tempfile import _RandomNameSequence
144 from tempfile import _RandomNameSequence
132 import hashlib
145 import hashlib
133
146
134 if salt is None:
147 if salt is None:
135 salt = _RandomNameSequence().next()
148 salt = _RandomNameSequence().next()
136
149
137 return hashlib.sha1(username + salt).hexdigest()
150 return hashlib.sha1(username + salt).hexdigest()
138
151
139
152
140 def safe_unicode(_str, from_encoding='utf8'):
153 def safe_unicode(_str, from_encoding='utf8'):
141 """
154 """
142 safe unicode function. In case of UnicodeDecode error we try to return
155 safe unicode function. In case of UnicodeDecode error we try to return
143 unicode with errors replace
156 unicode with errors replace
144
157
145 :param _str: string to decode
158 :param _str: string to decode
146 :rtype: unicode
159 :rtype: unicode
147 :returns: unicode object
160 :returns: unicode object
148 """
161 """
149
162
150 if isinstance(_str, unicode):
163 if isinstance(_str, unicode):
151 return _str
164 return _str
152
165
153 try:
166 try:
154 u_str = unicode(_str, from_encoding)
167 u_str = unicode(_str, from_encoding)
155 except UnicodeDecodeError:
168 except UnicodeDecodeError:
156 u_str = unicode(_str, from_encoding, 'replace')
169 u_str = unicode(_str, from_encoding, 'replace')
157
170
158 return u_str
171 return u_str
159
172
160
173
161 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
174 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
162 """
175 """
163 Custom engine_from_config functions that makes sure we use NullPool for
176 Custom engine_from_config functions that makes sure we use NullPool for
164 file based sqlite databases. This prevents errors on sqlite. This only
177 file based sqlite databases. This prevents errors on sqlite. This only
165 applies to sqlalchemy versions < 0.7.0
178 applies to sqlalchemy versions < 0.7.0
166
179
167 """
180 """
168 import sqlalchemy
181 import sqlalchemy
169 from sqlalchemy import engine_from_config as efc
182 from sqlalchemy import engine_from_config as efc
170 import logging
183 import logging
171
184
172 if int(sqlalchemy.__version__.split('.')[1]) < 7:
185 if int(sqlalchemy.__version__.split('.')[1]) < 7:
173
186
174 # This solution should work for sqlalchemy < 0.7.0, and should use
187 # This solution should work for sqlalchemy < 0.7.0, and should use
175 # proxy=TimerProxy() for execution time profiling
188 # proxy=TimerProxy() for execution time profiling
176
189
177 from sqlalchemy.pool import NullPool
190 from sqlalchemy.pool import NullPool
178 url = configuration[prefix + 'url']
191 url = configuration[prefix + 'url']
179
192
180 if url.startswith('sqlite'):
193 if url.startswith('sqlite'):
181 kwargs.update({'poolclass': NullPool})
194 kwargs.update({'poolclass': NullPool})
182 return efc(configuration, prefix, **kwargs)
195 return efc(configuration, prefix, **kwargs)
183 else:
196 else:
184 import time
197 import time
185 from sqlalchemy import event
198 from sqlalchemy import event
186 from sqlalchemy.engine import Engine
199 from sqlalchemy.engine import Engine
187
200
188 log = logging.getLogger('sqlalchemy.engine')
201 log = logging.getLogger('sqlalchemy.engine')
189 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
202 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
190 engine = efc(configuration, prefix, **kwargs)
203 engine = efc(configuration, prefix, **kwargs)
191
204
192 def color_sql(sql):
205 def color_sql(sql):
193 COLOR_SEQ = "\033[1;%dm"
206 COLOR_SEQ = "\033[1;%dm"
194 COLOR_SQL = YELLOW
207 COLOR_SQL = YELLOW
195 normal = '\x1b[0m'
208 normal = '\x1b[0m'
196 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
209 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
197
210
198 if configuration['debug']:
211 if configuration['debug']:
199 #attach events only for debug configuration
212 #attach events only for debug configuration
200
213
201 def before_cursor_execute(conn, cursor, statement,
214 def before_cursor_execute(conn, cursor, statement,
202 parameters, context, executemany):
215 parameters, context, executemany):
203 context._query_start_time = time.time()
216 context._query_start_time = time.time()
204 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
217 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
205
218
206
219
207 def after_cursor_execute(conn, cursor, statement,
220 def after_cursor_execute(conn, cursor, statement,
208 parameters, context, executemany):
221 parameters, context, executemany):
209 total = time.time() - context._query_start_time
222 total = time.time() - context._query_start_time
210 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
223 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
211
224
212 event.listen(engine, "before_cursor_execute",
225 event.listen(engine, "before_cursor_execute",
213 before_cursor_execute)
226 before_cursor_execute)
214 event.listen(engine, "after_cursor_execute",
227 event.listen(engine, "after_cursor_execute",
215 after_cursor_execute)
228 after_cursor_execute)
216
229
217 return engine
230 return engine
218
231
219
232
220 def age(curdate):
233 def age(curdate):
221 """
234 """
222 turns a datetime into an age string.
235 turns a datetime into an age string.
223
236
224 :param curdate: datetime object
237 :param curdate: datetime object
225 :rtype: unicode
238 :rtype: unicode
226 :returns: unicode words describing age
239 :returns: unicode words describing age
227 """
240 """
228
241
229 from datetime import datetime
242 from datetime import datetime
230 from webhelpers.date import time_ago_in_words
243 from webhelpers.date import time_ago_in_words
231
244
232 _ = lambda s:s
245 _ = lambda s:s
233
246
234 if not curdate:
247 if not curdate:
235 return ''
248 return ''
236
249
237 agescales = [(_(u"year"), 3600 * 24 * 365),
250 agescales = [(_(u"year"), 3600 * 24 * 365),
238 (_(u"month"), 3600 * 24 * 30),
251 (_(u"month"), 3600 * 24 * 30),
239 (_(u"day"), 3600 * 24),
252 (_(u"day"), 3600 * 24),
240 (_(u"hour"), 3600),
253 (_(u"hour"), 3600),
241 (_(u"minute"), 60),
254 (_(u"minute"), 60),
242 (_(u"second"), 1), ]
255 (_(u"second"), 1), ]
243
256
244 age = datetime.now() - curdate
257 age = datetime.now() - curdate
245 age_seconds = (age.days * agescales[2][1]) + age.seconds
258 age_seconds = (age.days * agescales[2][1]) + age.seconds
246 pos = 1
259 pos = 1
247 for scale in agescales:
260 for scale in agescales:
248 if scale[1] <= age_seconds:
261 if scale[1] <= age_seconds:
249 if pos == 6:pos = 5
262 if pos == 6:pos = 5
250 return '%s %s' % (time_ago_in_words(curdate,
263 return '%s %s' % (time_ago_in_words(curdate,
251 agescales[pos][0]), _('ago'))
264 agescales[pos][0]), _('ago'))
252 pos += 1
265 pos += 1
253
266
254 return _(u'just now')
267 return _(u'just now')
255
268
256
269
257 def credentials_hidder(uri):
270 def uri_filter(uri):
258 """
271 """
259 Removes user:password from given url string
272 Removes user:password from given url string
260
273
261 :param uri:
274 :param uri:
262 :rtype: unicode
275 :rtype: unicode
263 :returns: filtered list of strings
276 :returns: filtered list of strings
264 """
277 """
265 if not uri:
278 if not uri:
266 return ''
279 return ''
267
280
268 proto = ''
281 proto = ''
269
282
270 for pat in ('https://', 'http://'):
283 for pat in ('https://', 'http://'):
271 if uri.startswith(pat):
284 if uri.startswith(pat):
272 uri = uri[len(pat):]
285 uri = uri[len(pat):]
273 proto = pat
286 proto = pat
274 break
287 break
275
288
276 # remove passwords and username
289 # remove passwords and username
277 uri = uri[uri.find('@') + 1:]
290 uri = uri[uri.find('@') + 1:]
278
291
279 # get the port
292 # get the port
280 cred_pos = uri.find(':')
293 cred_pos = uri.find(':')
281 if cred_pos == -1:
294 if cred_pos == -1:
282 host, port = uri, None
295 host, port = uri, None
283 else:
296 else:
284 host, port = uri[:cred_pos], uri[cred_pos + 1:]
297 host, port = uri[:cred_pos], uri[cred_pos + 1:]
285
298
286 return filter(None, [proto, host, port])
299 return filter(None, [proto, host, port])
300
301
302 def credentials_filter(uri):
303 """
304 Returns a url with removed credentials
305
306 :param uri:
307 """
308
309 uri = uri_filter(uri)
310 #check if we have port
311 if len(uri) > 2 and uri[2]:
312 uri[2] = ':' + uri[2]
313
314 return ''.join(uri)
315
@@ -1,75 +1,82 b''
1 """The base Controller API
1 """The base Controller API
2
2
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 from pylons import config, tmpl_context as c, request, session
5 import logging
6
7 from pylons import config, tmpl_context as c, request, session, url
6 from pylons.controllers import WSGIController
8 from pylons.controllers import WSGIController
9 from pylons.controllers.util import redirect
7 from pylons.templating import render_mako as render
10 from pylons.templating import render_mako as render
8
11
9 from rhodecode import __version__
12 from rhodecode import __version__
10 from rhodecode.lib.auth import AuthUser
13 from rhodecode.lib.auth import AuthUser
11 from rhodecode.lib.utils import get_repo_slug
14 from rhodecode.lib.utils import get_repo_slug
12 from rhodecode.model import meta
15 from rhodecode.model import meta
13 from rhodecode.model.scm import ScmModel
16 from rhodecode.model.scm import ScmModel
14 from rhodecode import BACKENDS
17 from rhodecode import BACKENDS
15 from rhodecode.model.db import Repository
18 from rhodecode.model.db import Repository
16
19
20 log = logging.getLogger(__name__)
17
21
18 class BaseController(WSGIController):
22 class BaseController(WSGIController):
19
23
20 def __before__(self):
24 def __before__(self):
21 c.rhodecode_version = __version__
25 c.rhodecode_version = __version__
22 c.rhodecode_name = config.get('rhodecode_title')
26 c.rhodecode_name = config.get('rhodecode_title')
23 c.ga_code = config.get('rhodecode_ga_code')
27 c.ga_code = config.get('rhodecode_ga_code')
24 c.repo_name = get_repo_slug(request)
28 c.repo_name = get_repo_slug(request)
25 c.backends = BACKENDS.keys()
29 c.backends = BACKENDS.keys()
26 self.cut_off_limit = int(config.get('cut_off_limit'))
30 self.cut_off_limit = int(config.get('cut_off_limit'))
27
31
28 self.sa = meta.Session()
32 self.sa = meta.Session()
29 self.scm_model = ScmModel(self.sa)
33 self.scm_model = ScmModel(self.sa)
30
34
31 #c.unread_journal = scm_model.get_unread_journal()
35 #c.unread_journal = scm_model.get_unread_journal()
32
36
33 def __call__(self, environ, start_response):
37 def __call__(self, environ, start_response):
34 """Invoke the Controller"""
38 """Invoke the Controller"""
35 # WSGIController.__call__ dispatches to the Controller method
39 # WSGIController.__call__ dispatches to the Controller method
36 # the request is routed to. This routing information is
40 # the request is routed to. This routing information is
37 # available in environ['pylons.routes_dict']
41 # available in environ['pylons.routes_dict']
38 try:
42 try:
39 # putting this here makes sure that we update permissions each time
43 # putting this here makes sure that we update permissions each time
40 api_key = request.GET.get('api_key')
44 api_key = request.GET.get('api_key')
41 user_id = getattr(session.get('rhodecode_user'), 'user_id', None)
45 user_id = getattr(session.get('rhodecode_user'), 'user_id', None)
42 self.rhodecode_user = c.rhodecode_user = AuthUser(user_id, api_key)
46 self.rhodecode_user = c.rhodecode_user = AuthUser(user_id, api_key)
43 self.rhodecode_user.set_authenticated(
47 self.rhodecode_user.set_authenticated(
44 getattr(session.get('rhodecode_user'),
48 getattr(session.get('rhodecode_user'),
45 'is_authenticated', False))
49 'is_authenticated', False))
46 session['rhodecode_user'] = self.rhodecode_user
50 session['rhodecode_user'] = self.rhodecode_user
47 session.save()
51 session.save()
48 return WSGIController.__call__(self, environ, start_response)
52 return WSGIController.__call__(self, environ, start_response)
49 finally:
53 finally:
50 meta.Session.remove()
54 meta.Session.remove()
51
55
52
56
53 class BaseRepoController(BaseController):
57 class BaseRepoController(BaseController):
54 """
58 """
55 Base class for controllers responsible for loading all needed data
59 Base class for controllers responsible for loading all needed data
56 for those controllers, loaded items are
60 for those controllers, loaded items are
57
61
58 c.rhodecode_repo: instance of scm repository (taken from cache)
62 c.rhodecode_repo: instance of scm repository (taken from cache)
59
63
60 """
64 """
61
65
62 def __before__(self):
66 def __before__(self):
63 super(BaseRepoController, self).__before__()
67 super(BaseRepoController, self).__before__()
64 if c.repo_name:
68 if c.repo_name:
65
69
66 c.rhodecode_repo = Repository.by_repo_name(c.repo_name).scm_instance
70 c.rhodecode_db_repo = Repository.by_repo_name(c.repo_name)
71 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
72
73 if c.rhodecode_repo is None:
74 log.error('%s this repository is present in database but it '
75 'cannot be created as an scm instance', c.repo_name)
67
76
68 if c.rhodecode_repo is not None:
77 redirect(url('home'))
69 c.repository_followers = \
70 self.scm_model.get_followers(c.repo_name)
71 c.repository_forks = self.scm_model.get_forks(c.repo_name)
72 else:
73 c.repository_followers = 0
74 c.repository_forks = 0
75
78
79 c.repository_followers = \
80 self.scm_model.get_followers(c.repo_name)
81 c.repository_forks = self.scm_model.get_forks(c.repo_name)
82
@@ -1,694 +1,694 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 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10
10
11 from datetime import datetime
11 from datetime import datetime
12 from pygments.formatters import HtmlFormatter
12 from pygments.formatters import HtmlFormatter
13 from pygments import highlight as code_highlight
13 from pygments import highlight as code_highlight
14 from pylons import url, request, config
14 from pylons import url, request, config
15 from pylons.i18n.translation import _, ungettext
15 from pylons.i18n.translation import _, ungettext
16
16
17 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html.tools import *
18 from webhelpers.html.tools import *
19 from webhelpers.html.builder import make_tag
19 from webhelpers.html.builder import make_tag
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 password, textarea, title, ul, xml_declaration, radio
23 password, textarea, title, ul, xml_declaration, radio
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 mail_to, strip_links, strip_tags, tag_re
25 mail_to, strip_links, strip_tags, tag_re
26 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 replace_whitespace, urlify, truncate, wrap_paragraphs
31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 from webhelpers.date import time_ago_in_words
32 from webhelpers.date import time_ago_in_words
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 convert_boolean_attrs, NotGiven
35 convert_boolean_attrs, NotGiven
36
36
37 from vcs.utils.annotate import annotate_highlight
37 from vcs.utils.annotate import annotate_highlight
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib import str2bool, safe_unicode
39 from rhodecode.lib import str2bool, safe_unicode
40
40
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 """
42 """
43 Reset button
43 Reset button
44 """
44 """
45 _set_input_attrs(attrs, type, name, value)
45 _set_input_attrs(attrs, type, name, value)
46 _set_id_attr(attrs, id, name)
46 _set_id_attr(attrs, id, name)
47 convert_boolean_attrs(attrs, ["disabled"])
47 convert_boolean_attrs(attrs, ["disabled"])
48 return HTML.input(**attrs)
48 return HTML.input(**attrs)
49
49
50 reset = _reset
50 reset = _reset
51
51
52
52
53 def get_token():
53 def get_token():
54 """Return the current authentication token, creating one if one doesn't
54 """Return the current authentication token, creating one if one doesn't
55 already exist.
55 already exist.
56 """
56 """
57 token_key = "_authentication_token"
57 token_key = "_authentication_token"
58 from pylons import session
58 from pylons import session
59 if not token_key in session:
59 if not token_key in session:
60 try:
60 try:
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 except AttributeError: # Python < 2.4
62 except AttributeError: # Python < 2.4
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 session[token_key] = token
64 session[token_key] = token
65 if hasattr(session, 'save'):
65 if hasattr(session, 'save'):
66 session.save()
66 session.save()
67 return session[token_key]
67 return session[token_key]
68
68
69 class _GetError(object):
69 class _GetError(object):
70 """Get error from form_errors, and represent it as span wrapped error
70 """Get error from form_errors, and represent it as span wrapped error
71 message
71 message
72
72
73 :param field_name: field to fetch errors for
73 :param field_name: field to fetch errors for
74 :param form_errors: form errors dict
74 :param form_errors: form errors dict
75 """
75 """
76
76
77 def __call__(self, field_name, form_errors):
77 def __call__(self, field_name, form_errors):
78 tmpl = """<span class="error_msg">%s</span>"""
78 tmpl = """<span class="error_msg">%s</span>"""
79 if form_errors and form_errors.has_key(field_name):
79 if form_errors and form_errors.has_key(field_name):
80 return literal(tmpl % form_errors.get(field_name))
80 return literal(tmpl % form_errors.get(field_name))
81
81
82 get_error = _GetError()
82 get_error = _GetError()
83
83
84 class _ToolTip(object):
84 class _ToolTip(object):
85
85
86 def __call__(self, tooltip_title, trim_at=50):
86 def __call__(self, tooltip_title, trim_at=50):
87 """Special function just to wrap our text into nice formatted
87 """Special function just to wrap our text into nice formatted
88 autowrapped text
88 autowrapped text
89
89
90 :param tooltip_title:
90 :param tooltip_title:
91 """
91 """
92
92
93 return escape(tooltip_title)
93 return escape(tooltip_title)
94
94
95 def activate(self):
95 def activate(self):
96 """Adds tooltip mechanism to the given Html all tooltips have to have
96 """Adds tooltip mechanism to the given Html all tooltips have to have
97 set class `tooltip` and set attribute `tooltip_title`.
97 set class `tooltip` and set attribute `tooltip_title`.
98 Then a tooltip will be generated based on that. All with yui js tooltip
98 Then a tooltip will be generated based on that. All with yui js tooltip
99 """
99 """
100
100
101 js = '''
101 js = '''
102 YAHOO.util.Event.onDOMReady(function(){
102 YAHOO.util.Event.onDOMReady(function(){
103 function toolTipsId(){
103 function toolTipsId(){
104 var ids = [];
104 var ids = [];
105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106
106
107 for (var i = 0; i < tts.length; i++) {
107 for (var i = 0; i < tts.length; i++) {
108 //if element doesn't not have and id autogenerate one for tooltip
108 //if element doesn't not have and id autogenerate one for tooltip
109
109
110 if (!tts[i].id){
110 if (!tts[i].id){
111 tts[i].id='tt'+i*100;
111 tts[i].id='tt'+i*100;
112 }
112 }
113 ids.push(tts[i].id);
113 ids.push(tts[i].id);
114 }
114 }
115 return ids
115 return ids
116 };
116 };
117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
119 monitorresize:false,
119 monitorresize:false,
120 xyoffset :[0,0],
120 xyoffset :[0,0],
121 autodismissdelay:300000,
121 autodismissdelay:300000,
122 hidedelay:5,
122 hidedelay:5,
123 showdelay:20,
123 showdelay:20,
124 });
124 });
125
125
126 });
126 });
127 '''
127 '''
128 return literal(js)
128 return literal(js)
129
129
130 tooltip = _ToolTip()
130 tooltip = _ToolTip()
131
131
132 class _FilesBreadCrumbs(object):
132 class _FilesBreadCrumbs(object):
133
133
134 def __call__(self, repo_name, rev, paths):
134 def __call__(self, repo_name, rev, paths):
135 if isinstance(paths, str):
135 if isinstance(paths, str):
136 paths = safe_unicode(paths)
136 paths = safe_unicode(paths)
137 url_l = [link_to(repo_name, url('files_home',
137 url_l = [link_to(repo_name, url('files_home',
138 repo_name=repo_name,
138 repo_name=repo_name,
139 revision=rev, f_path=''))]
139 revision=rev, f_path=''))]
140 paths_l = paths.split('/')
140 paths_l = paths.split('/')
141 for cnt, p in enumerate(paths_l):
141 for cnt, p in enumerate(paths_l):
142 if p != '':
142 if p != '':
143 url_l.append(link_to(p, url('files_home',
143 url_l.append(link_to(p, url('files_home',
144 repo_name=repo_name,
144 repo_name=repo_name,
145 revision=rev,
145 revision=rev,
146 f_path='/'.join(paths_l[:cnt + 1]))))
146 f_path='/'.join(paths_l[:cnt + 1]))))
147
147
148 return literal('/'.join(url_l))
148 return literal('/'.join(url_l))
149
149
150 files_breadcrumbs = _FilesBreadCrumbs()
150 files_breadcrumbs = _FilesBreadCrumbs()
151
151
152 class CodeHtmlFormatter(HtmlFormatter):
152 class CodeHtmlFormatter(HtmlFormatter):
153 """My code Html Formatter for source codes
153 """My code Html Formatter for source codes
154 """
154 """
155
155
156 def wrap(self, source, outfile):
156 def wrap(self, source, outfile):
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
158
158
159 def _wrap_code(self, source):
159 def _wrap_code(self, source):
160 for cnt, it in enumerate(source):
160 for cnt, it in enumerate(source):
161 i, t = it
161 i, t = it
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
163 yield i, t
163 yield i, t
164
164
165 def _wrap_tablelinenos(self, inner):
165 def _wrap_tablelinenos(self, inner):
166 dummyoutfile = StringIO.StringIO()
166 dummyoutfile = StringIO.StringIO()
167 lncount = 0
167 lncount = 0
168 for t, line in inner:
168 for t, line in inner:
169 if t:
169 if t:
170 lncount += 1
170 lncount += 1
171 dummyoutfile.write(line)
171 dummyoutfile.write(line)
172
172
173 fl = self.linenostart
173 fl = self.linenostart
174 mw = len(str(lncount + fl - 1))
174 mw = len(str(lncount + fl - 1))
175 sp = self.linenospecial
175 sp = self.linenospecial
176 st = self.linenostep
176 st = self.linenostep
177 la = self.lineanchors
177 la = self.lineanchors
178 aln = self.anchorlinenos
178 aln = self.anchorlinenos
179 nocls = self.noclasses
179 nocls = self.noclasses
180 if sp:
180 if sp:
181 lines = []
181 lines = []
182
182
183 for i in range(fl, fl + lncount):
183 for i in range(fl, fl + lncount):
184 if i % st == 0:
184 if i % st == 0:
185 if i % sp == 0:
185 if i % sp == 0:
186 if aln:
186 if aln:
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
188 (la, i, mw, i))
188 (la, i, mw, i))
189 else:
189 else:
190 lines.append('<span class="special">%*d</span>' % (mw, i))
190 lines.append('<span class="special">%*d</span>' % (mw, i))
191 else:
191 else:
192 if aln:
192 if aln:
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
194 else:
194 else:
195 lines.append('%*d' % (mw, i))
195 lines.append('%*d' % (mw, i))
196 else:
196 else:
197 lines.append('')
197 lines.append('')
198 ls = '\n'.join(lines)
198 ls = '\n'.join(lines)
199 else:
199 else:
200 lines = []
200 lines = []
201 for i in range(fl, fl + lncount):
201 for i in range(fl, fl + lncount):
202 if i % st == 0:
202 if i % st == 0:
203 if aln:
203 if aln:
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
205 else:
205 else:
206 lines.append('%*d' % (mw, i))
206 lines.append('%*d' % (mw, i))
207 else:
207 else:
208 lines.append('')
208 lines.append('')
209 ls = '\n'.join(lines)
209 ls = '\n'.join(lines)
210
210
211 # in case you wonder about the seemingly redundant <div> here: since the
211 # in case you wonder about the seemingly redundant <div> here: since the
212 # content in the other cell also is wrapped in a div, some browsers in
212 # content in the other cell also is wrapped in a div, some browsers in
213 # some configurations seem to mess up the formatting...
213 # some configurations seem to mess up the formatting...
214 if nocls:
214 if nocls:
215 yield 0, ('<table class="%stable">' % self.cssclass +
215 yield 0, ('<table class="%stable">' % self.cssclass +
216 '<tr><td><div class="linenodiv" '
216 '<tr><td><div class="linenodiv" '
217 'style="background-color: #f0f0f0; padding-right: 10px">'
217 'style="background-color: #f0f0f0; padding-right: 10px">'
218 '<pre style="line-height: 125%">' +
218 '<pre style="line-height: 125%">' +
219 ls + '</pre></div></td><td id="hlcode" class="code">')
219 ls + '</pre></div></td><td id="hlcode" class="code">')
220 else:
220 else:
221 yield 0, ('<table class="%stable">' % self.cssclass +
221 yield 0, ('<table class="%stable">' % self.cssclass +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
223 ls + '</pre></div></td><td id="hlcode" class="code">')
223 ls + '</pre></div></td><td id="hlcode" class="code">')
224 yield 0, dummyoutfile.getvalue()
224 yield 0, dummyoutfile.getvalue()
225 yield 0, '</td></tr></table>'
225 yield 0, '</td></tr></table>'
226
226
227
227
228 def pygmentize(filenode, **kwargs):
228 def pygmentize(filenode, **kwargs):
229 """pygmentize function using pygments
229 """pygmentize function using pygments
230
230
231 :param filenode:
231 :param filenode:
232 """
232 """
233
233
234 return literal(code_highlight(filenode.content,
234 return literal(code_highlight(filenode.content,
235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
236
236
237 def pygmentize_annotation(repo_name, filenode, **kwargs):
237 def pygmentize_annotation(repo_name, filenode, **kwargs):
238 """pygmentize function for annotation
238 """pygmentize function for annotation
239
239
240 :param filenode:
240 :param filenode:
241 """
241 """
242
242
243 color_dict = {}
243 color_dict = {}
244 def gen_color(n=10000):
244 def gen_color(n=10000):
245 """generator for getting n of evenly distributed colors using
245 """generator for getting n of evenly distributed colors using
246 hsv color and golden ratio. It always return same order of colors
246 hsv color and golden ratio. It always return same order of colors
247
247
248 :returns: RGB tuple
248 :returns: RGB tuple
249 """
249 """
250 import colorsys
250 import colorsys
251 golden_ratio = 0.618033988749895
251 golden_ratio = 0.618033988749895
252 h = 0.22717784590367374
252 h = 0.22717784590367374
253
253
254 for _ in xrange(n):
254 for _ in xrange(n):
255 h += golden_ratio
255 h += golden_ratio
256 h %= 1
256 h %= 1
257 HSV_tuple = [h, 0.95, 0.95]
257 HSV_tuple = [h, 0.95, 0.95]
258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
260
260
261 cgenerator = gen_color()
261 cgenerator = gen_color()
262
262
263 def get_color_string(cs):
263 def get_color_string(cs):
264 if color_dict.has_key(cs):
264 if color_dict.has_key(cs):
265 col = color_dict[cs]
265 col = color_dict[cs]
266 else:
266 else:
267 col = color_dict[cs] = cgenerator.next()
267 col = color_dict[cs] = cgenerator.next()
268 return "color: rgb(%s)! important;" % (', '.join(col))
268 return "color: rgb(%s)! important;" % (', '.join(col))
269
269
270 def url_func(repo_name):
270 def url_func(repo_name):
271
271
272 def _url_func(changeset):
272 def _url_func(changeset):
273 author = changeset.author
273 author = changeset.author
274 date = changeset.date
274 date = changeset.date
275 message = tooltip(changeset.message)
275 message = tooltip(changeset.message)
276
276
277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
279 "</b> %s<br/></div>")
279 "</b> %s<br/></div>")
280
280
281 tooltip_html = tooltip_html % (author, date, message)
281 tooltip_html = tooltip_html % (author, date, message)
282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
283 short_id(changeset.raw_id))
283 short_id(changeset.raw_id))
284 uri = link_to(
284 uri = link_to(
285 lnk_format,
285 lnk_format,
286 url('changeset_home', repo_name=repo_name,
286 url('changeset_home', repo_name=repo_name,
287 revision=changeset.raw_id),
287 revision=changeset.raw_id),
288 style=get_color_string(changeset.raw_id),
288 style=get_color_string(changeset.raw_id),
289 class_='tooltip',
289 class_='tooltip',
290 title=tooltip_html
290 title=tooltip_html
291 )
291 )
292
292
293 uri += '\n'
293 uri += '\n'
294 return uri
294 return uri
295 return _url_func
295 return _url_func
296
296
297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
298
298
299 def get_changeset_safe(repo, rev):
299 def get_changeset_safe(repo, rev):
300 from vcs.backends.base import BaseRepository
300 from vcs.backends.base import BaseRepository
301 from vcs.exceptions import RepositoryError
301 from vcs.exceptions import RepositoryError
302 if not isinstance(repo, BaseRepository):
302 if not isinstance(repo, BaseRepository):
303 raise Exception('You must pass an Repository '
303 raise Exception('You must pass an Repository '
304 'object as first argument got %s', type(repo))
304 'object as first argument got %s', type(repo))
305
305
306 try:
306 try:
307 cs = repo.get_changeset(rev)
307 cs = repo.get_changeset(rev)
308 except RepositoryError:
308 except RepositoryError:
309 from rhodecode.lib.utils import EmptyChangeset
309 from rhodecode.lib.utils import EmptyChangeset
310 cs = EmptyChangeset()
310 cs = EmptyChangeset()
311 return cs
311 return cs
312
312
313
313
314 def is_following_repo(repo_name, user_id):
314 def is_following_repo(repo_name, user_id):
315 from rhodecode.model.scm import ScmModel
315 from rhodecode.model.scm import ScmModel
316 return ScmModel().is_following_repo(repo_name, user_id)
316 return ScmModel().is_following_repo(repo_name, user_id)
317
317
318 flash = _Flash()
318 flash = _Flash()
319
319
320 #==============================================================================
320 #==============================================================================
321 # SCM FILTERS available via h.
321 # SCM FILTERS available via h.
322 #==============================================================================
322 #==============================================================================
323 from vcs.utils import author_name, author_email
323 from vcs.utils import author_name, author_email
324 from rhodecode.lib import credentials_hidder, age as _age
324 from rhodecode.lib import credentials_filter, age as _age
325
325
326 age = lambda x:_age(x)
326 age = lambda x:_age(x)
327 capitalize = lambda x: x.capitalize()
327 capitalize = lambda x: x.capitalize()
328 email = author_email
328 email = author_email
329 email_or_none = lambda x: email(x) if email(x) != x else None
329 email_or_none = lambda x: email(x) if email(x) != x else None
330 person = lambda x: author_name(x)
330 person = lambda x: author_name(x)
331 short_id = lambda x: x[:12]
331 short_id = lambda x: x[:12]
332 hide_credentials = lambda x: ''.join(credentials_hidder(x))
332 hide_credentials = lambda x: ''.join(credentials_filter(x))
333
333
334 def bool2icon(value):
334 def bool2icon(value):
335 """Returns True/False values represented as small html image of true/false
335 """Returns True/False values represented as small html image of true/false
336 icons
336 icons
337
337
338 :param value: bool value
338 :param value: bool value
339 """
339 """
340
340
341 if value is True:
341 if value is True:
342 return HTML.tag('img', src=url("/images/icons/accept.png"),
342 return HTML.tag('img', src=url("/images/icons/accept.png"),
343 alt=_('True'))
343 alt=_('True'))
344
344
345 if value is False:
345 if value is False:
346 return HTML.tag('img', src=url("/images/icons/cancel.png"),
346 return HTML.tag('img', src=url("/images/icons/cancel.png"),
347 alt=_('False'))
347 alt=_('False'))
348
348
349 return value
349 return value
350
350
351
351
352 def action_parser(user_log, feed=False):
352 def action_parser(user_log, feed=False):
353 """This helper will action_map the specified string action into translated
353 """This helper will action_map the specified string action into translated
354 fancy names with icons and links
354 fancy names with icons and links
355
355
356 :param user_log: user log instance
356 :param user_log: user log instance
357 :param feed: use output for feeds (no html and fancy icons)
357 :param feed: use output for feeds (no html and fancy icons)
358 """
358 """
359
359
360 action = user_log.action
360 action = user_log.action
361 action_params = ' '
361 action_params = ' '
362
362
363 x = action.split(':')
363 x = action.split(':')
364
364
365 if len(x) > 1:
365 if len(x) > 1:
366 action, action_params = x
366 action, action_params = x
367
367
368 def get_cs_links():
368 def get_cs_links():
369 revs_limit = 5 #display this amount always
369 revs_limit = 5 #display this amount always
370 revs_top_limit = 50 #show upto this amount of changesets hidden
370 revs_top_limit = 50 #show upto this amount of changesets hidden
371 revs = action_params.split(',')
371 revs = action_params.split(',')
372 repo_name = user_log.repository.repo_name
372 repo_name = user_log.repository.repo_name
373
373
374 from rhodecode.model.scm import ScmModel
374 from rhodecode.model.scm import ScmModel
375 repo = user_log.repository.scm_instance
375 repo = user_log.repository.scm_instance
376
376
377 message = lambda rev: get_changeset_safe(repo, rev).message
377 message = lambda rev: get_changeset_safe(repo, rev).message
378 cs_links = []
378 cs_links = []
379 cs_links.append(" " + ', '.join ([link_to(rev,
379 cs_links.append(" " + ', '.join ([link_to(rev,
380 url('changeset_home',
380 url('changeset_home',
381 repo_name=repo_name,
381 repo_name=repo_name,
382 revision=rev), title=tooltip(message(rev)),
382 revision=rev), title=tooltip(message(rev)),
383 class_='tooltip') for rev in revs[:revs_limit] ]))
383 class_='tooltip') for rev in revs[:revs_limit] ]))
384
384
385 compare_view = (' <div class="compare_view tooltip" title="%s">'
385 compare_view = (' <div class="compare_view tooltip" title="%s">'
386 '<a href="%s">%s</a> '
386 '<a href="%s">%s</a> '
387 '</div>' % (_('Show all combined changesets %s->%s' \
387 '</div>' % (_('Show all combined changesets %s->%s' \
388 % (revs[0], revs[-1])),
388 % (revs[0], revs[-1])),
389 url('changeset_home', repo_name=repo_name,
389 url('changeset_home', repo_name=repo_name,
390 revision='%s...%s' % (revs[0], revs[-1])
390 revision='%s...%s' % (revs[0], revs[-1])
391 ),
391 ),
392 _('compare view'))
392 _('compare view'))
393 )
393 )
394
394
395 if len(revs) > revs_limit:
395 if len(revs) > revs_limit:
396 uniq_id = revs[0]
396 uniq_id = revs[0]
397 html_tmpl = ('<span> %s '
397 html_tmpl = ('<span> %s '
398 '<a class="show_more" id="_%s" href="#more">%s</a> '
398 '<a class="show_more" id="_%s" href="#more">%s</a> '
399 '%s</span>')
399 '%s</span>')
400 if not feed:
400 if not feed:
401 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
401 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
402 % (len(revs) - revs_limit),
402 % (len(revs) - revs_limit),
403 _('revisions')))
403 _('revisions')))
404
404
405 if not feed:
405 if not feed:
406 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
406 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
407 else:
407 else:
408 html_tmpl = '<span id="%s"> %s </span>'
408 html_tmpl = '<span id="%s"> %s </span>'
409
409
410 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
410 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
411 url('changeset_home',
411 url('changeset_home',
412 repo_name=repo_name, revision=rev),
412 repo_name=repo_name, revision=rev),
413 title=message(rev), class_='tooltip')
413 title=message(rev), class_='tooltip')
414 for rev in revs[revs_limit:revs_top_limit]])))
414 for rev in revs[revs_limit:revs_top_limit]])))
415 if len(revs) > 1:
415 if len(revs) > 1:
416 cs_links.append(compare_view)
416 cs_links.append(compare_view)
417 return ''.join(cs_links)
417 return ''.join(cs_links)
418
418
419 def get_fork_name():
419 def get_fork_name():
420 repo_name = action_params
420 repo_name = action_params
421 return _('fork name ') + str(link_to(action_params, url('summary_home',
421 return _('fork name ') + str(link_to(action_params, url('summary_home',
422 repo_name=repo_name,)))
422 repo_name=repo_name,)))
423
423
424 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
424 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
425 'user_created_repo':(_('[created] repository'), None),
425 'user_created_repo':(_('[created] repository'), None),
426 'user_forked_repo':(_('[forked] repository'), get_fork_name),
426 'user_forked_repo':(_('[forked] repository'), get_fork_name),
427 'user_updated_repo':(_('[updated] repository'), None),
427 'user_updated_repo':(_('[updated] repository'), None),
428 'admin_deleted_repo':(_('[delete] repository'), None),
428 'admin_deleted_repo':(_('[delete] repository'), None),
429 'admin_created_repo':(_('[created] repository'), None),
429 'admin_created_repo':(_('[created] repository'), None),
430 'admin_forked_repo':(_('[forked] repository'), None),
430 'admin_forked_repo':(_('[forked] repository'), None),
431 'admin_updated_repo':(_('[updated] repository'), None),
431 'admin_updated_repo':(_('[updated] repository'), None),
432 'push':(_('[pushed] into'), get_cs_links),
432 'push':(_('[pushed] into'), get_cs_links),
433 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
433 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
434 'push_remote':(_('[pulled from remote] into'), get_cs_links),
434 'push_remote':(_('[pulled from remote] into'), get_cs_links),
435 'pull':(_('[pulled] from'), None),
435 'pull':(_('[pulled] from'), None),
436 'started_following_repo':(_('[started following] repository'), None),
436 'started_following_repo':(_('[started following] repository'), None),
437 'stopped_following_repo':(_('[stopped following] repository'), None),
437 'stopped_following_repo':(_('[stopped following] repository'), None),
438 }
438 }
439
439
440 action_str = action_map.get(action, action)
440 action_str = action_map.get(action, action)
441 if feed:
441 if feed:
442 action = action_str[0].replace('[', '').replace(']', '')
442 action = action_str[0].replace('[', '').replace(']', '')
443 else:
443 else:
444 action = action_str[0].replace('[', '<span class="journal_highlight">')\
444 action = action_str[0].replace('[', '<span class="journal_highlight">')\
445 .replace(']', '</span>')
445 .replace(']', '</span>')
446
446
447 action_params_func = lambda :""
447 action_params_func = lambda :""
448
448
449 if callable(action_str[1]):
449 if callable(action_str[1]):
450 action_params_func = action_str[1]
450 action_params_func = action_str[1]
451
451
452 return [literal(action), action_params_func]
452 return [literal(action), action_params_func]
453
453
454 def action_parser_icon(user_log):
454 def action_parser_icon(user_log):
455 action = user_log.action
455 action = user_log.action
456 action_params = None
456 action_params = None
457 x = action.split(':')
457 x = action.split(':')
458
458
459 if len(x) > 1:
459 if len(x) > 1:
460 action, action_params = x
460 action, action_params = x
461
461
462 tmpl = """<img src="%s%s" alt="%s"/>"""
462 tmpl = """<img src="%s%s" alt="%s"/>"""
463 map = {'user_deleted_repo':'database_delete.png',
463 map = {'user_deleted_repo':'database_delete.png',
464 'user_created_repo':'database_add.png',
464 'user_created_repo':'database_add.png',
465 'user_forked_repo':'arrow_divide.png',
465 'user_forked_repo':'arrow_divide.png',
466 'user_updated_repo':'database_edit.png',
466 'user_updated_repo':'database_edit.png',
467 'admin_deleted_repo':'database_delete.png',
467 'admin_deleted_repo':'database_delete.png',
468 'admin_created_repo':'database_add.png',
468 'admin_created_repo':'database_add.png',
469 'admin_forked_repo':'arrow_divide.png',
469 'admin_forked_repo':'arrow_divide.png',
470 'admin_updated_repo':'database_edit.png',
470 'admin_updated_repo':'database_edit.png',
471 'push':'script_add.png',
471 'push':'script_add.png',
472 'push_local':'script_edit.png',
472 'push_local':'script_edit.png',
473 'push_remote':'connect.png',
473 'push_remote':'connect.png',
474 'pull':'down_16.png',
474 'pull':'down_16.png',
475 'started_following_repo':'heart_add.png',
475 'started_following_repo':'heart_add.png',
476 'stopped_following_repo':'heart_delete.png',
476 'stopped_following_repo':'heart_delete.png',
477 }
477 }
478 return literal(tmpl % ((url('/images/icons/')),
478 return literal(tmpl % ((url('/images/icons/')),
479 map.get(action, action), action))
479 map.get(action, action), action))
480
480
481
481
482 #==============================================================================
482 #==============================================================================
483 # PERMS
483 # PERMS
484 #==============================================================================
484 #==============================================================================
485 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
485 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
486 HasRepoPermissionAny, HasRepoPermissionAll
486 HasRepoPermissionAny, HasRepoPermissionAll
487
487
488 #==============================================================================
488 #==============================================================================
489 # GRAVATAR URL
489 # GRAVATAR URL
490 #==============================================================================
490 #==============================================================================
491
491
492 def gravatar_url(email_address, size=30):
492 def gravatar_url(email_address, size=30):
493 if not str2bool(config['app_conf'].get('use_gravatar')) or \
493 if not str2bool(config['app_conf'].get('use_gravatar')) or \
494 email_address == 'anonymous@rhodecode.org':
494 email_address == 'anonymous@rhodecode.org':
495 return "/images/user%s.png" % size
495 return "/images/user%s.png" % size
496
496
497 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
497 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
498 default = 'identicon'
498 default = 'identicon'
499 baseurl_nossl = "http://www.gravatar.com/avatar/"
499 baseurl_nossl = "http://www.gravatar.com/avatar/"
500 baseurl_ssl = "https://secure.gravatar.com/avatar/"
500 baseurl_ssl = "https://secure.gravatar.com/avatar/"
501 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
501 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
502
502
503 if isinstance(email_address, unicode):
503 if isinstance(email_address, unicode):
504 #hashlib crashes on unicode items
504 #hashlib crashes on unicode items
505 email_address = email_address.encode('utf8', 'replace')
505 email_address = email_address.encode('utf8', 'replace')
506 # construct the url
506 # construct the url
507 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
507 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
508 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
508 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
509
509
510 return gravatar_url
510 return gravatar_url
511
511
512
512
513 #==============================================================================
513 #==============================================================================
514 # REPO PAGER, PAGER FOR REPOSITORY
514 # REPO PAGER, PAGER FOR REPOSITORY
515 #==============================================================================
515 #==============================================================================
516 class RepoPage(Page):
516 class RepoPage(Page):
517
517
518 def __init__(self, collection, page=1, items_per_page=20,
518 def __init__(self, collection, page=1, items_per_page=20,
519 item_count=None, url=None, branch_name=None, **kwargs):
519 item_count=None, url=None, branch_name=None, **kwargs):
520
520
521 """Create a "RepoPage" instance. special pager for paging
521 """Create a "RepoPage" instance. special pager for paging
522 repository
522 repository
523 """
523 """
524 self._url_generator = url
524 self._url_generator = url
525
525
526 # Safe the kwargs class-wide so they can be used in the pager() method
526 # Safe the kwargs class-wide so they can be used in the pager() method
527 self.kwargs = kwargs
527 self.kwargs = kwargs
528
528
529 # Save a reference to the collection
529 # Save a reference to the collection
530 self.original_collection = collection
530 self.original_collection = collection
531
531
532 self.collection = collection
532 self.collection = collection
533
533
534 # The self.page is the number of the current page.
534 # The self.page is the number of the current page.
535 # The first page has the number 1!
535 # The first page has the number 1!
536 try:
536 try:
537 self.page = int(page) # make it int() if we get it as a string
537 self.page = int(page) # make it int() if we get it as a string
538 except (ValueError, TypeError):
538 except (ValueError, TypeError):
539 self.page = 1
539 self.page = 1
540
540
541 self.items_per_page = items_per_page
541 self.items_per_page = items_per_page
542
542
543 # Unless the user tells us how many items the collections has
543 # Unless the user tells us how many items the collections has
544 # we calculate that ourselves.
544 # we calculate that ourselves.
545 if item_count is not None:
545 if item_count is not None:
546 self.item_count = item_count
546 self.item_count = item_count
547 else:
547 else:
548 self.item_count = len(self.collection)
548 self.item_count = len(self.collection)
549
549
550 # Compute the number of the first and last available page
550 # Compute the number of the first and last available page
551 if self.item_count > 0:
551 if self.item_count > 0:
552 self.first_page = 1
552 self.first_page = 1
553 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
553 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
554 self.last_page = self.first_page + self.page_count - 1
554 self.last_page = self.first_page + self.page_count - 1
555
555
556 # Make sure that the requested page number is the range of valid pages
556 # Make sure that the requested page number is the range of valid pages
557 if self.page > self.last_page:
557 if self.page > self.last_page:
558 self.page = self.last_page
558 self.page = self.last_page
559 elif self.page < self.first_page:
559 elif self.page < self.first_page:
560 self.page = self.first_page
560 self.page = self.first_page
561
561
562 # Note: the number of items on this page can be less than
562 # Note: the number of items on this page can be less than
563 # items_per_page if the last page is not full
563 # items_per_page if the last page is not full
564 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
564 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
565 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
565 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
566
566
567 iterator = self.collection.get_changesets(start=self.first_item,
567 iterator = self.collection.get_changesets(start=self.first_item,
568 end=self.last_item,
568 end=self.last_item,
569 reverse=True,
569 reverse=True,
570 branch_name=branch_name)
570 branch_name=branch_name)
571 self.items = list(iterator)
571 self.items = list(iterator)
572
572
573 # Links to previous and next page
573 # Links to previous and next page
574 if self.page > self.first_page:
574 if self.page > self.first_page:
575 self.previous_page = self.page - 1
575 self.previous_page = self.page - 1
576 else:
576 else:
577 self.previous_page = None
577 self.previous_page = None
578
578
579 if self.page < self.last_page:
579 if self.page < self.last_page:
580 self.next_page = self.page + 1
580 self.next_page = self.page + 1
581 else:
581 else:
582 self.next_page = None
582 self.next_page = None
583
583
584 # No items available
584 # No items available
585 else:
585 else:
586 self.first_page = None
586 self.first_page = None
587 self.page_count = 0
587 self.page_count = 0
588 self.last_page = None
588 self.last_page = None
589 self.first_item = None
589 self.first_item = None
590 self.last_item = None
590 self.last_item = None
591 self.previous_page = None
591 self.previous_page = None
592 self.next_page = None
592 self.next_page = None
593 self.items = []
593 self.items = []
594
594
595 # This is a subclass of the 'list' type. Initialise the list now.
595 # This is a subclass of the 'list' type. Initialise the list now.
596 list.__init__(self, self.items)
596 list.__init__(self, self.items)
597
597
598
598
599 def changed_tooltip(nodes):
599 def changed_tooltip(nodes):
600 """
600 """
601 Generates a html string for changed nodes in changeset page.
601 Generates a html string for changed nodes in changeset page.
602 It limits the output to 30 entries
602 It limits the output to 30 entries
603
603
604 :param nodes: LazyNodesGenerator
604 :param nodes: LazyNodesGenerator
605 """
605 """
606 if nodes:
606 if nodes:
607 pref = ': <br/> '
607 pref = ': <br/> '
608 suf = ''
608 suf = ''
609 if len(nodes) > 30:
609 if len(nodes) > 30:
610 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
610 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
611 return literal(pref + '<br/> '.join([safe_unicode(x.path)
611 return literal(pref + '<br/> '.join([safe_unicode(x.path)
612 for x in nodes[:30]]) + suf)
612 for x in nodes[:30]]) + suf)
613 else:
613 else:
614 return ': ' + _('No Files')
614 return ': ' + _('No Files')
615
615
616
616
617
617
618 def repo_link(groups_and_repos):
618 def repo_link(groups_and_repos):
619 """
619 """
620 Makes a breadcrumbs link to repo within a group
620 Makes a breadcrumbs link to repo within a group
621 joins &raquo; on each group to create a fancy link
621 joins &raquo; on each group to create a fancy link
622
622
623 ex::
623 ex::
624 group >> subgroup >> repo
624 group >> subgroup >> repo
625
625
626 :param groups_and_repos:
626 :param groups_and_repos:
627 """
627 """
628 groups, repo_name = groups_and_repos
628 groups, repo_name = groups_and_repos
629
629
630 if not groups:
630 if not groups:
631 return repo_name
631 return repo_name
632 else:
632 else:
633 def make_link(group):
633 def make_link(group):
634 return link_to(group.group_name, url('repos_group',
634 return link_to(group.group_name, url('repos_group',
635 id=group.group_id))
635 id=group.group_id))
636 return literal(' &raquo; '.join(map(make_link, groups)) + \
636 return literal(' &raquo; '.join(map(make_link, groups)) + \
637 " &raquo; " + repo_name)
637 " &raquo; " + repo_name)
638
638
639
639
640 def fancy_file_stats(stats):
640 def fancy_file_stats(stats):
641 """
641 """
642 Displays a fancy two colored bar for number of added/deleted
642 Displays a fancy two colored bar for number of added/deleted
643 lines of code on file
643 lines of code on file
644
644
645 :param stats: two element list of added/deleted lines of code
645 :param stats: two element list of added/deleted lines of code
646 """
646 """
647
647
648 a, d, t = stats[0], stats[1], stats[0] + stats[1]
648 a, d, t = stats[0], stats[1], stats[0] + stats[1]
649 width = 100
649 width = 100
650 unit = float(width) / (t or 1)
650 unit = float(width) / (t or 1)
651
651
652 # needs > 9% of width to be visible or 0 to be hidden
652 # needs > 9% of width to be visible or 0 to be hidden
653 a_p = max(9, unit * a) if a > 0 else 0
653 a_p = max(9, unit * a) if a > 0 else 0
654 d_p = max(9, unit * d) if d > 0 else 0
654 d_p = max(9, unit * d) if d > 0 else 0
655 p_sum = a_p + d_p
655 p_sum = a_p + d_p
656
656
657 if p_sum > width:
657 if p_sum > width:
658 #adjust the percentage to be == 100% since we adjusted to 9
658 #adjust the percentage to be == 100% since we adjusted to 9
659 if a_p > d_p:
659 if a_p > d_p:
660 a_p = a_p - (p_sum - width)
660 a_p = a_p - (p_sum - width)
661 else:
661 else:
662 d_p = d_p - (p_sum - width)
662 d_p = d_p - (p_sum - width)
663
663
664 a_v = a if a > 0 else ''
664 a_v = a if a > 0 else ''
665 d_v = d if d > 0 else ''
665 d_v = d if d > 0 else ''
666
666
667
667
668 def cgen(l_type):
668 def cgen(l_type):
669 mapping = {'tr':'top-right-rounded-corner',
669 mapping = {'tr':'top-right-rounded-corner',
670 'tl':'top-left-rounded-corner',
670 'tl':'top-left-rounded-corner',
671 'br':'bottom-right-rounded-corner',
671 'br':'bottom-right-rounded-corner',
672 'bl':'bottom-left-rounded-corner'}
672 'bl':'bottom-left-rounded-corner'}
673 map_getter = lambda x:mapping[x]
673 map_getter = lambda x:mapping[x]
674
674
675 if l_type == 'a' and d_v:
675 if l_type == 'a' and d_v:
676 #case when added and deleted are present
676 #case when added and deleted are present
677 return ' '.join(map(map_getter, ['tl', 'bl']))
677 return ' '.join(map(map_getter, ['tl', 'bl']))
678
678
679 if l_type == 'a' and not d_v:
679 if l_type == 'a' and not d_v:
680 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
680 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
681
681
682 if l_type == 'd' and a_v:
682 if l_type == 'd' and a_v:
683 return ' '.join(map(map_getter, ['tr', 'br']))
683 return ' '.join(map(map_getter, ['tr', 'br']))
684
684
685 if l_type == 'd' and not a_v:
685 if l_type == 'd' and not a_v:
686 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
686 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
687
687
688
688
689
689
690 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
690 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
691 a_p, a_v)
691 a_p, a_v)
692 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
692 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
693 d_p, d_v)
693 d_p, d_v)
694 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
694 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
@@ -1,412 +1,416 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29
29
30 from mercurial import ui
30 from mercurial import ui
31
31
32 from sqlalchemy.exc import DatabaseError
32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.orm import make_transient
33 from sqlalchemy.orm import make_transient
34
34
35 from beaker.cache import cache_region, region_invalidate
35 from beaker.cache import cache_region, region_invalidate
36
36
37 from vcs import get_backend
37 from vcs import get_backend
38 from vcs.utils.helpers import get_scm
38 from vcs.utils.helpers import get_scm
39 from vcs.exceptions import RepositoryError, VCSError
39 from vcs.exceptions import RepositoryError, VCSError
40 from vcs.utils.lazy import LazyProperty
40 from vcs.utils.lazy import LazyProperty
41 from vcs.nodes import FileNode
41 from vcs.nodes import FileNode
42
42
43 from rhodecode import BACKENDS
43 from rhodecode import BACKENDS
44 from rhodecode.lib import helpers as h
44 from rhodecode.lib import helpers as h
45 from rhodecode.lib.auth import HasRepoPermissionAny
45 from rhodecode.lib.auth import HasRepoPermissionAny
46 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
46 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
47 action_logger
47 action_logger
48 from rhodecode.model import BaseModel
48 from rhodecode.model import BaseModel
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
51 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
52 UserFollowing, UserLog
52 UserFollowing, UserLog
53 from rhodecode.model.caching_query import FromCache
53 from rhodecode.model.caching_query import FromCache
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UserTemp(object):
58 class UserTemp(object):
59 def __init__(self, user_id):
59 def __init__(self, user_id):
60 self.user_id = user_id
60 self.user_id = user_id
61
61
62 def __repr__(self):
62 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64
64
65
65
66 class RepoTemp(object):
66 class RepoTemp(object):
67 def __init__(self, repo_id):
67 def __init__(self, repo_id):
68 self.repo_id = repo_id
68 self.repo_id = repo_id
69
69
70 def __repr__(self):
70 def __repr__(self):
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72
72
73 class CachedRepoList(object):
73 class CachedRepoList(object):
74
74
75 def __init__(self, db_repo_list, invalidation_list, repos_path,
75 def __init__(self, db_repo_list, invalidation_list, repos_path,
76 order_by=None):
76 order_by=None):
77 self.db_repo_list = db_repo_list
77 self.db_repo_list = db_repo_list
78 self.invalidation_list = invalidation_list
78 self.invalidation_list = invalidation_list
79 self.repos_path = repos_path
79 self.repos_path = repos_path
80 self.order_by = order_by
80 self.order_by = order_by
81 self.reversed = (order_by or '').startswith('-')
81 self.reversed = (order_by or '').startswith('-')
82
82
83 def __len__(self):
83 def __len__(self):
84 return len(self.db_repo_list)
84 return len(self.db_repo_list)
85
85
86 def __repr__(self):
86 def __repr__(self):
87 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
87 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
88
88
89 def __iter__(self):
89 def __iter__(self):
90 for db_repo in self.db_repo_list:
90 for db_repo in self.db_repo_list:
91 dbr = db_repo
91 dbr = db_repo
92
92
93 # invalidate the repo cache if needed before getting the
93 # invalidate the repo cache if needed before getting the
94 # scm instance
94 # scm instance
95
95
96 scm_invalidate = False
96 scm_invalidate = False
97 if self.invalidation_list is not None:
97 if self.invalidation_list is not None:
98 scm_invalidate = dbr.repo_name in self.invalidation_list
98 scm_invalidate = dbr.repo_name in self.invalidation_list
99
99
100 if scm_invalidate:
100 if scm_invalidate:
101 log.info('invalidating cache for repository %s',
101 log.info('invalidating cache for repository %s',
102 dbr.repo_name)
102 dbr.repo_name)
103 db_repo.set_invalidate
103 db_repo.set_invalidate
104
104
105 scmr = db_repo.scm_instance_cached
105 scmr = db_repo.scm_instance_cached
106
106
107 #check permission at this level
107 #check permission at this level
108 if not HasRepoPermissionAny('repository.read',
108 if not HasRepoPermissionAny('repository.read',
109 'repository.write',
109 'repository.write',
110 'repository.admin')(dbr.repo_name,
110 'repository.admin')(dbr.repo_name,
111 'get repo check'):
111 'get repo check'):
112 continue
112 continue
113
113
114
114
115
115 if not scmr:
116 log.error('%s this repository is present in database but it '
117 'cannot be created as an scm instance',
118 dbr.repo_name)
119 continue
116
120
117
121
118 last_change = scmr.last_change
122 last_change = scmr.last_change
119 tip = h.get_changeset_safe(scmr, 'tip')
123 tip = h.get_changeset_safe(scmr, 'tip')
120
124
121 tmp_d = {}
125 tmp_d = {}
122 tmp_d['name'] = dbr.repo_name
126 tmp_d['name'] = dbr.repo_name
123 tmp_d['name_sort'] = tmp_d['name'].lower()
127 tmp_d['name_sort'] = tmp_d['name'].lower()
124 tmp_d['description'] = dbr.description
128 tmp_d['description'] = dbr.description
125 tmp_d['description_sort'] = tmp_d['description']
129 tmp_d['description_sort'] = tmp_d['description']
126 tmp_d['last_change'] = last_change
130 tmp_d['last_change'] = last_change
127 tmp_d['last_change_sort'] = time.mktime(last_change \
131 tmp_d['last_change_sort'] = time.mktime(last_change \
128 .timetuple())
132 .timetuple())
129 tmp_d['tip'] = tip.raw_id
133 tmp_d['tip'] = tip.raw_id
130 tmp_d['tip_sort'] = tip.revision
134 tmp_d['tip_sort'] = tip.revision
131 tmp_d['rev'] = tip.revision
135 tmp_d['rev'] = tip.revision
132 tmp_d['contact'] = dbr.user.full_contact
136 tmp_d['contact'] = dbr.user.full_contact
133 tmp_d['contact_sort'] = tmp_d['contact']
137 tmp_d['contact_sort'] = tmp_d['contact']
134 tmp_d['owner_sort'] = tmp_d['contact']
138 tmp_d['owner_sort'] = tmp_d['contact']
135 tmp_d['repo_archives'] = list(scmr._get_archives())
139 tmp_d['repo_archives'] = list(scmr._get_archives())
136 tmp_d['last_msg'] = tip.message
140 tmp_d['last_msg'] = tip.message
137 tmp_d['repo'] = scmr
141 tmp_d['repo'] = scmr
138 tmp_d['dbrepo'] = dbr.get_dict()
142 tmp_d['dbrepo'] = dbr.get_dict()
139 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
143 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
140 else {}
144 else {}
141 yield tmp_d
145 yield tmp_d
142
146
143 class ScmModel(BaseModel):
147 class ScmModel(BaseModel):
144 """Generic Scm Model
148 """Generic Scm Model
145 """
149 """
146
150
147 @LazyProperty
151 @LazyProperty
148 def repos_path(self):
152 def repos_path(self):
149 """Get's the repositories root path from database
153 """Get's the repositories root path from database
150 """
154 """
151
155
152 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
156 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
153
157
154 return q.ui_value
158 return q.ui_value
155
159
156 def repo_scan(self, repos_path=None):
160 def repo_scan(self, repos_path=None):
157 """Listing of repositories in given path. This path should not be a
161 """Listing of repositories in given path. This path should not be a
158 repository itself. Return a dictionary of repository objects
162 repository itself. Return a dictionary of repository objects
159
163
160 :param repos_path: path to directory containing repositories
164 :param repos_path: path to directory containing repositories
161 """
165 """
162
166
163 log.info('scanning for repositories in %s', repos_path)
167 log.info('scanning for repositories in %s', repos_path)
164
168
165 if repos_path is None:
169 if repos_path is None:
166 repos_path = self.repos_path
170 repos_path = self.repos_path
167
171
168 baseui = make_ui('db')
172 baseui = make_ui('db')
169 repos_list = {}
173 repos_list = {}
170
174
171 for name, path in get_filesystem_repos(repos_path, recursive=True):
175 for name, path in get_filesystem_repos(repos_path, recursive=True):
172 try:
176 try:
173 if name in repos_list:
177 if name in repos_list:
174 raise RepositoryError('Duplicate repository name %s '
178 raise RepositoryError('Duplicate repository name %s '
175 'found in %s' % (name, path))
179 'found in %s' % (name, path))
176 else:
180 else:
177
181
178 klass = get_backend(path[0])
182 klass = get_backend(path[0])
179
183
180 if path[0] == 'hg' and path[0] in BACKENDS.keys():
184 if path[0] == 'hg' and path[0] in BACKENDS.keys():
181 repos_list[name] = klass(path[1], baseui=baseui)
185 repos_list[name] = klass(path[1], baseui=baseui)
182
186
183 if path[0] == 'git' and path[0] in BACKENDS.keys():
187 if path[0] == 'git' and path[0] in BACKENDS.keys():
184 repos_list[name] = klass(path[1])
188 repos_list[name] = klass(path[1])
185 except OSError:
189 except OSError:
186 continue
190 continue
187
191
188 return repos_list
192 return repos_list
189
193
190 def get_repos(self, all_repos=None, sort_key=None):
194 def get_repos(self, all_repos=None, sort_key=None):
191 """
195 """
192 Get all repos from db and for each repo create it's
196 Get all repos from db and for each repo create it's
193 backend instance and fill that backed with information from database
197 backend instance and fill that backed with information from database
194
198
195 :param all_repos: list of repository names as strings
199 :param all_repos: list of repository names as strings
196 give specific repositories list, good for filtering
200 give specific repositories list, good for filtering
197 """
201 """
198 if all_repos is None:
202 if all_repos is None:
199 all_repos = self.sa.query(Repository)\
203 all_repos = self.sa.query(Repository)\
200 .filter(Repository.group_id == None)\
204 .filter(Repository.group_id == None)\
201 .order_by(Repository.repo_name).all()
205 .order_by(Repository.repo_name).all()
202
206
203 #get the repositories that should be invalidated
207 #get the repositories that should be invalidated
204 invalidation_list = [str(x.cache_key) for x in \
208 invalidation_list = [str(x.cache_key) for x in \
205 self.sa.query(CacheInvalidation.cache_key)\
209 self.sa.query(CacheInvalidation.cache_key)\
206 .filter(CacheInvalidation.cache_active == False)\
210 .filter(CacheInvalidation.cache_active == False)\
207 .all()]
211 .all()]
208
212
209 repo_iter = CachedRepoList(all_repos, invalidation_list,
213 repo_iter = CachedRepoList(all_repos, invalidation_list,
210 repos_path=self.repos_path,
214 repos_path=self.repos_path,
211 order_by=sort_key)
215 order_by=sort_key)
212
216
213 return repo_iter
217 return repo_iter
214
218
215 def mark_for_invalidation(self, repo_name):
219 def mark_for_invalidation(self, repo_name):
216 """Puts cache invalidation task into db for
220 """Puts cache invalidation task into db for
217 further global cache invalidation
221 further global cache invalidation
218
222
219 :param repo_name: this repo that should invalidation take place
223 :param repo_name: this repo that should invalidation take place
220 """
224 """
221
225
222 log.debug('marking %s for invalidation', repo_name)
226 log.debug('marking %s for invalidation', repo_name)
223 cache = self.sa.query(CacheInvalidation)\
227 cache = self.sa.query(CacheInvalidation)\
224 .filter(CacheInvalidation.cache_key == repo_name).scalar()
228 .filter(CacheInvalidation.cache_key == repo_name).scalar()
225
229
226 if cache:
230 if cache:
227 #mark this cache as inactive
231 #mark this cache as inactive
228 cache.cache_active = False
232 cache.cache_active = False
229 else:
233 else:
230 log.debug('cache key not found in invalidation db -> creating one')
234 log.debug('cache key not found in invalidation db -> creating one')
231 cache = CacheInvalidation(repo_name)
235 cache = CacheInvalidation(repo_name)
232
236
233 try:
237 try:
234 self.sa.add(cache)
238 self.sa.add(cache)
235 self.sa.commit()
239 self.sa.commit()
236 except (DatabaseError,):
240 except (DatabaseError,):
237 log.error(traceback.format_exc())
241 log.error(traceback.format_exc())
238 self.sa.rollback()
242 self.sa.rollback()
239
243
240 def toggle_following_repo(self, follow_repo_id, user_id):
244 def toggle_following_repo(self, follow_repo_id, user_id):
241
245
242 f = self.sa.query(UserFollowing)\
246 f = self.sa.query(UserFollowing)\
243 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
247 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
244 .filter(UserFollowing.user_id == user_id).scalar()
248 .filter(UserFollowing.user_id == user_id).scalar()
245
249
246 if f is not None:
250 if f is not None:
247
251
248 try:
252 try:
249 self.sa.delete(f)
253 self.sa.delete(f)
250 self.sa.commit()
254 self.sa.commit()
251 action_logger(UserTemp(user_id),
255 action_logger(UserTemp(user_id),
252 'stopped_following_repo',
256 'stopped_following_repo',
253 RepoTemp(follow_repo_id))
257 RepoTemp(follow_repo_id))
254 return
258 return
255 except:
259 except:
256 log.error(traceback.format_exc())
260 log.error(traceback.format_exc())
257 self.sa.rollback()
261 self.sa.rollback()
258 raise
262 raise
259
263
260 try:
264 try:
261 f = UserFollowing()
265 f = UserFollowing()
262 f.user_id = user_id
266 f.user_id = user_id
263 f.follows_repo_id = follow_repo_id
267 f.follows_repo_id = follow_repo_id
264 self.sa.add(f)
268 self.sa.add(f)
265 self.sa.commit()
269 self.sa.commit()
266 action_logger(UserTemp(user_id),
270 action_logger(UserTemp(user_id),
267 'started_following_repo',
271 'started_following_repo',
268 RepoTemp(follow_repo_id))
272 RepoTemp(follow_repo_id))
269 except:
273 except:
270 log.error(traceback.format_exc())
274 log.error(traceback.format_exc())
271 self.sa.rollback()
275 self.sa.rollback()
272 raise
276 raise
273
277
274 def toggle_following_user(self, follow_user_id, user_id):
278 def toggle_following_user(self, follow_user_id, user_id):
275 f = self.sa.query(UserFollowing)\
279 f = self.sa.query(UserFollowing)\
276 .filter(UserFollowing.follows_user_id == follow_user_id)\
280 .filter(UserFollowing.follows_user_id == follow_user_id)\
277 .filter(UserFollowing.user_id == user_id).scalar()
281 .filter(UserFollowing.user_id == user_id).scalar()
278
282
279 if f is not None:
283 if f is not None:
280 try:
284 try:
281 self.sa.delete(f)
285 self.sa.delete(f)
282 self.sa.commit()
286 self.sa.commit()
283 return
287 return
284 except:
288 except:
285 log.error(traceback.format_exc())
289 log.error(traceback.format_exc())
286 self.sa.rollback()
290 self.sa.rollback()
287 raise
291 raise
288
292
289 try:
293 try:
290 f = UserFollowing()
294 f = UserFollowing()
291 f.user_id = user_id
295 f.user_id = user_id
292 f.follows_user_id = follow_user_id
296 f.follows_user_id = follow_user_id
293 self.sa.add(f)
297 self.sa.add(f)
294 self.sa.commit()
298 self.sa.commit()
295 except:
299 except:
296 log.error(traceback.format_exc())
300 log.error(traceback.format_exc())
297 self.sa.rollback()
301 self.sa.rollback()
298 raise
302 raise
299
303
300 def is_following_repo(self, repo_name, user_id, cache=False):
304 def is_following_repo(self, repo_name, user_id, cache=False):
301 r = self.sa.query(Repository)\
305 r = self.sa.query(Repository)\
302 .filter(Repository.repo_name == repo_name).scalar()
306 .filter(Repository.repo_name == repo_name).scalar()
303
307
304 f = self.sa.query(UserFollowing)\
308 f = self.sa.query(UserFollowing)\
305 .filter(UserFollowing.follows_repository == r)\
309 .filter(UserFollowing.follows_repository == r)\
306 .filter(UserFollowing.user_id == user_id).scalar()
310 .filter(UserFollowing.user_id == user_id).scalar()
307
311
308 return f is not None
312 return f is not None
309
313
310 def is_following_user(self, username, user_id, cache=False):
314 def is_following_user(self, username, user_id, cache=False):
311 u = UserModel(self.sa).get_by_username(username)
315 u = UserModel(self.sa).get_by_username(username)
312
316
313 f = self.sa.query(UserFollowing)\
317 f = self.sa.query(UserFollowing)\
314 .filter(UserFollowing.follows_user == u)\
318 .filter(UserFollowing.follows_user == u)\
315 .filter(UserFollowing.user_id == user_id).scalar()
319 .filter(UserFollowing.user_id == user_id).scalar()
316
320
317 return f is not None
321 return f is not None
318
322
319 def get_followers(self, repo_id):
323 def get_followers(self, repo_id):
320 if not isinstance(repo_id, int):
324 if not isinstance(repo_id, int):
321 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
325 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
322
326
323 return self.sa.query(UserFollowing)\
327 return self.sa.query(UserFollowing)\
324 .filter(UserFollowing.follows_repo_id == repo_id).count()
328 .filter(UserFollowing.follows_repo_id == repo_id).count()
325
329
326 def get_forks(self, repo_id):
330 def get_forks(self, repo_id):
327 if not isinstance(repo_id, int):
331 if not isinstance(repo_id, int):
328 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
332 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
329
333
330 return self.sa.query(Repository)\
334 return self.sa.query(Repository)\
331 .filter(Repository.fork_id == repo_id).count()
335 .filter(Repository.fork_id == repo_id).count()
332
336
333 def pull_changes(self, repo_name, username):
337 def pull_changes(self, repo_name, username):
334 dbrepo = Repository.by_repo_name(repo_name)
338 dbrepo = Repository.by_repo_name(repo_name)
335 repo = dbrepo.scm_instance
339 repo = dbrepo.scm_instance
336 try:
340 try:
337 extras = {'ip': '',
341 extras = {'ip': '',
338 'username': username,
342 'username': username,
339 'action': 'push_remote',
343 'action': 'push_remote',
340 'repository': repo_name}
344 'repository': repo_name}
341
345
342 #inject ui extra param to log this action via push logger
346 #inject ui extra param to log this action via push logger
343 for k, v in extras.items():
347 for k, v in extras.items():
344 repo._repo.ui.setconfig('rhodecode_extras', k, v)
348 repo._repo.ui.setconfig('rhodecode_extras', k, v)
345
349
346 repo.pull(dbrepo.clone_uri)
350 repo.pull(dbrepo.clone_uri)
347 self.mark_for_invalidation(repo_name)
351 self.mark_for_invalidation(repo_name)
348 except:
352 except:
349 log.error(traceback.format_exc())
353 log.error(traceback.format_exc())
350 raise
354 raise
351
355
352
356
353 def commit_change(self, repo, repo_name, cs, user, author, message, content,
357 def commit_change(self, repo, repo_name, cs, user, author, message, content,
354 f_path):
358 f_path):
355
359
356 if repo.alias == 'hg':
360 if repo.alias == 'hg':
357 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
361 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
358 elif repo.alias == 'git':
362 elif repo.alias == 'git':
359 from vcs.backends.git import GitInMemoryChangeset as IMC
363 from vcs.backends.git import GitInMemoryChangeset as IMC
360
364
361 # decoding here will force that we have proper encoded values
365 # decoding here will force that we have proper encoded values
362 # in any other case this will throw exceptions and deny commit
366 # in any other case this will throw exceptions and deny commit
363 content = content.encode('utf8')
367 content = content.encode('utf8')
364 message = message.encode('utf8')
368 message = message.encode('utf8')
365 path = f_path.encode('utf8')
369 path = f_path.encode('utf8')
366 author = author.encode('utf8')
370 author = author.encode('utf8')
367 m = IMC(repo)
371 m = IMC(repo)
368 m.change(FileNode(path, content))
372 m.change(FileNode(path, content))
369 tip = m.commit(message=message,
373 tip = m.commit(message=message,
370 author=author,
374 author=author,
371 parents=[cs], branch=cs.branch)
375 parents=[cs], branch=cs.branch)
372
376
373 new_cs = tip.short_id
377 new_cs = tip.short_id
374 action = 'push_local:%s' % new_cs
378 action = 'push_local:%s' % new_cs
375
379
376 action_logger(user, action, repo_name)
380 action_logger(user, action, repo_name)
377
381
378 self.mark_for_invalidation(repo_name)
382 self.mark_for_invalidation(repo_name)
379
383
380
384
381 def get_unread_journal(self):
385 def get_unread_journal(self):
382 return self.sa.query(UserLog).count()
386 return self.sa.query(UserLog).count()
383
387
384 def _should_invalidate(self, repo_name):
388 def _should_invalidate(self, repo_name):
385 """Looks up database for invalidation signals for this repo_name
389 """Looks up database for invalidation signals for this repo_name
386
390
387 :param repo_name:
391 :param repo_name:
388 """
392 """
389
393
390 ret = self.sa.query(CacheInvalidation)\
394 ret = self.sa.query(CacheInvalidation)\
391 .filter(CacheInvalidation.cache_key == repo_name)\
395 .filter(CacheInvalidation.cache_key == repo_name)\
392 .filter(CacheInvalidation.cache_active == False)\
396 .filter(CacheInvalidation.cache_active == False)\
393 .scalar()
397 .scalar()
394
398
395 return ret
399 return ret
396
400
397 def _mark_invalidated(self, cache_key):
401 def _mark_invalidated(self, cache_key):
398 """ Marks all occurrences of cache to invalidation as already
402 """ Marks all occurrences of cache to invalidation as already
399 invalidated
403 invalidated
400
404
401 :param cache_key:
405 :param cache_key:
402 """
406 """
403
407
404 if cache_key:
408 if cache_key:
405 log.debug('marking %s as already invalidated', cache_key)
409 log.debug('marking %s as already invalidated', cache_key)
406 try:
410 try:
407 cache_key.cache_active = True
411 cache_key.cache_active = True
408 self.sa.add(cache_key)
412 self.sa.add(cache_key)
409 self.sa.commit()
413 self.sa.commit()
410 except (DatabaseError,):
414 except (DatabaseError,):
411 log.error(traceback.format_exc())
415 log.error(traceback.format_exc())
412 self.sa.rollback()
416 self.sa.rollback()
@@ -0,0 +1,2 b''
1 import unittest
2 from rhodecode.tests import *
@@ -1,207 +1,219 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20
20
21 #smtp_server = mail.server.com
21 #smtp_server = mail.server.com
22 #smtp_username =
22 #smtp_username =
23 #smtp_password =
23 #smtp_password =
24 #smtp_port =
24 #smtp_port =
25 #smtp_use_tls = false
25 #smtp_use_tls = false
26
26
27 [server:main]
27 [server:main]
28 ##nr of threads to spawn
28 ##nr of threads to spawn
29 threadpool_workers = 5
29 threadpool_workers = 5
30
30
31 ##max request before thread respawn
31 ##max request before thread respawn
32 threadpool_max_requests = 2
32 threadpool_max_requests = 2
33
33
34 ##option to use threads of process
34 ##option to use threads of process
35 use_threadpool = true
35 use_threadpool = true
36
36
37 use = egg:Paste#http
37 use = egg:Paste#http
38 host = 127.0.0.1
38 host = 127.0.0.1
39 port = 5000
39 port = 5000
40
40
41 [app:main]
41 [app:main]
42 use = egg:rhodecode
42 use = egg:rhodecode
43 full_stack = true
43 full_stack = true
44 static_files = true
44 static_files = true
45 lang=en
45 lang=en
46 cache_dir = /tmp/data
46 cache_dir = /tmp/data
47 index_dir = /tmp/index
47 index_dir = /tmp/index
48 app_instance_uuid = develop-test
48 app_instance_uuid = develop-test
49 cut_off_limit = 256000
49 cut_off_limit = 256000
50 force_https = false
50 force_https = false
51 commit_parse_limit = 25
51 commit_parse_limit = 25
52
52
53 ####################################
53 ####################################
54 ### CELERY CONFIG ####
54 ### CELERY CONFIG ####
55 ####################################
55 ####################################
56 use_celery = false
56 use_celery = false
57 broker.host = localhost
57 broker.host = localhost
58 broker.vhost = rabbitmqhost
58 broker.vhost = rabbitmqhost
59 broker.port = 5672
59 broker.port = 5672
60 broker.user = rabbitmq
60 broker.user = rabbitmq
61 broker.password = qweqwe
61 broker.password = qweqwe
62
62
63 celery.imports = rhodecode.lib.celerylib.tasks
63 celery.imports = rhodecode.lib.celerylib.tasks
64
64
65 celery.result.backend = amqp
65 celery.result.backend = amqp
66 celery.result.dburi = amqp://
66 celery.result.dburi = amqp://
67 celery.result.serialier = json
67 celery.result.serialier = json
68
68
69 #celery.send.task.error.emails = true
69 #celery.send.task.error.emails = true
70 #celery.amqp.task.result.expires = 18000
70 #celery.amqp.task.result.expires = 18000
71
71
72 celeryd.concurrency = 2
72 celeryd.concurrency = 2
73 #celeryd.log.file = celeryd.log
73 #celeryd.log.file = celeryd.log
74 celeryd.log.level = debug
74 celeryd.log.level = debug
75 celeryd.max.tasks.per.child = 1
75 celeryd.max.tasks.per.child = 1
76
76
77 #tasks will never be sent to the queue, but executed locally instead.
77 #tasks will never be sent to the queue, but executed locally instead.
78 celery.always.eager = false
78 celery.always.eager = false
79
79
80 ####################################
80 ####################################
81 ### BEAKER CACHE ####
81 ### BEAKER CACHE ####
82 ####################################
82 ####################################
83 beaker.cache.data_dir=/tmp/data/cache/data
83 beaker.cache.data_dir=/tmp/data/cache/data
84 beaker.cache.lock_dir=/tmp/data/cache/lock
84 beaker.cache.lock_dir=/tmp/data/cache/lock
85 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
85 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
86
86
87 beaker.cache.super_short_term.type=memory
87 beaker.cache.super_short_term.type=memory
88 beaker.cache.super_short_term.expire=10
88 beaker.cache.super_short_term.expire=10
89
89
90 beaker.cache.short_term.type=memory
90 beaker.cache.short_term.type=memory
91 beaker.cache.short_term.expire=60
91 beaker.cache.short_term.expire=60
92
92
93 beaker.cache.long_term.type=memory
93 beaker.cache.long_term.type=memory
94 beaker.cache.long_term.expire=36000
94 beaker.cache.long_term.expire=36000
95
95
96
96
97 beaker.cache.sql_cache_short.type=memory
97 beaker.cache.sql_cache_short.type=memory
98 beaker.cache.sql_cache_short.expire=10
98 beaker.cache.sql_cache_short.expire=10
99
99
100 beaker.cache.sql_cache_med.type=memory
100 beaker.cache.sql_cache_med.type=memory
101 beaker.cache.sql_cache_med.expire=360
101 beaker.cache.sql_cache_med.expire=360
102
102
103 beaker.cache.sql_cache_long.type=file
103 beaker.cache.sql_cache_long.type=file
104 beaker.cache.sql_cache_long.expire=3600
104 beaker.cache.sql_cache_long.expire=3600
105
105
106 ####################################
106 ####################################
107 ### BEAKER SESSION ####
107 ### BEAKER SESSION ####
108 ####################################
108 ####################################
109 ## Type of storage used for the session, current types are
109 ## Type of storage used for the session, current types are
110 ## dbm, file, memcached, database, and memory.
110 ## dbm, file, memcached, database, and memory.
111 ## The storage uses the Container API
111 ## The storage uses the Container API
112 ##that is also used by the cache system.
112 ##that is also used by the cache system.
113 beaker.session.type = file
113 beaker.session.type = file
114
114
115 beaker.session.key = rhodecode
115 beaker.session.key = rhodecode
116 beaker.session.secret = g654dcno0-9873jhgfreyu
116 beaker.session.secret = g654dcno0-9873jhgfreyu
117 beaker.session.timeout = 36000
117 beaker.session.timeout = 36000
118
118
119 ##auto save the session to not to use .save()
119 ##auto save the session to not to use .save()
120 beaker.session.auto = False
120 beaker.session.auto = False
121
121
122 ##true exire at browser close
122 ##true exire at browser close
123 #beaker.session.cookie_expires = 3600
123 #beaker.session.cookie_expires = 3600
124
124
125
125
126 ################################################################################
126 ################################################################################
127 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
127 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
128 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
128 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
129 ## execute malicious code after an exception is raised. ##
129 ## execute malicious code after an exception is raised. ##
130 ################################################################################
130 ################################################################################
131 #set debug = false
131 #set debug = false
132
132
133 ##################################
133 ##################################
134 ### LOGVIEW CONFIG ###
134 ### LOGVIEW CONFIG ###
135 ##################################
135 ##################################
136 logview.sqlalchemy = #faa
136 logview.sqlalchemy = #faa
137 logview.pylons.templating = #bfb
137 logview.pylons.templating = #bfb
138 logview.pylons.util = #eee
138 logview.pylons.util = #eee
139
139
140 #########################################################
140 #########################################################
141 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
141 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
142 #########################################################
142 #########################################################
143 #sqlalchemy.db1.url = sqlite:///%(here)s/test.db
143 sqlalchemy.db1.url = sqlite:///%(here)s/test.db
144 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
144 #sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode_tests
145 #sqlalchemy.db1.echo = False
145 #sqlalchemy.db1.echo = False
146 #sqlalchemy.db1.pool_recycle = 3600
146 #sqlalchemy.db1.pool_recycle = 3600
147 sqlalchemy.convert_unicode = true
147 sqlalchemy.convert_unicode = true
148
148
149 ################################
149 ################################
150 ### LOGGING CONFIGURATION ####
150 ### LOGGING CONFIGURATION ####
151 ################################
151 ################################
152 [loggers]
152 [loggers]
153 keys = root, routes, rhodecode, sqlalchemy,beaker,templates
153 keys = root, routes, rhodecode, sqlalchemy,beaker,templates
154
154
155 [handlers]
155 [handlers]
156 keys = console
156 keys = console
157
157
158 [formatters]
158 [formatters]
159 keys = generic,color_formatter
159 keys = generic,color_formatter
160
160
161 #############
161 #############
162 ## LOGGERS ##
162 ## LOGGERS ##
163 #############
163 #############
164 [logger_root]
164 [logger_root]
165 level = ERROR
165 level = ERROR
166 handlers = console
166 handlers = console
167
167
168 [logger_routes]
168 [logger_routes]
169 level = ERROR
169 level = ERROR
170 handlers = console
170 handlers = console
171 qualname = routes.middleware
171 qualname = routes.middleware
172 # "level = DEBUG" logs the route matched and routing variables.
172 # "level = DEBUG" logs the route matched and routing variables.
173
173
174 [logger_beaker]
175 level = DEBUG
176 handlers =
177 qualname = beaker.container
178 propagate = 1
179
180 [logger_templates]
181 level = INFO
182 handlers =
183 qualname = pylons.templating
184 propagate = 1
185
174 [logger_rhodecode]
186 [logger_rhodecode]
175 level = ERROR
187 level = ERROR
176 handlers = console
188 handlers = console
177 qualname = rhodecode
189 qualname = rhodecode
178 propagate = 0
190 propagate = 0
179
191
180 [logger_sqlalchemy]
192 [logger_sqlalchemy]
181 level = ERROR
193 level = ERROR
182 handlers = console
194 handlers = console
183 qualname = sqlalchemy.engine
195 qualname = sqlalchemy.engine
184 propagate = 0
196 propagate = 0
185
197
186 ##############
198 ##############
187 ## HANDLERS ##
199 ## HANDLERS ##
188 ##############
200 ##############
189
201
190 [handler_console]
202 [handler_console]
191 class = StreamHandler
203 class = StreamHandler
192 args = (sys.stderr,)
204 args = (sys.stderr,)
193 level = NOTSET
205 level = NOTSET
194 formatter = color_formatter
206 formatter = color_formatter
195
207
196 ################
208 ################
197 ## FORMATTERS ##
209 ## FORMATTERS ##
198 ################
210 ################
199
211
200 [formatter_generic]
212 [formatter_generic]
201 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
213 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
202 datefmt = %Y-%m-%d %H:%M:%S
214 datefmt = %Y-%m-%d %H:%M:%S
203
215
204 [formatter_color_formatter]
216 [formatter_color_formatter]
205 class=rhodecode.lib.colored_formatter.ColorFormatter
217 class=rhodecode.lib.colored_formatter.ColorFormatter
206 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
218 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
207 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
219 datefmt = %Y-%m-%d %H:%M:%S
General Comments 0
You need to be logged in to leave comments. Login now