##// END OF EJS Templates
fixed @repo into :repo for docs...
marcink -
r604:5cc96df7 default
parent child Browse files
Show More
@@ -1,181 +1,189 b''
1 """Routes configuration
1 """
2 Routes configuration
2 3
3 4 The more specific and detailed routes should be defined first so they
4 5 may take precedent over the more generic routes. For more information
5 6 refer to the routes manual at http://routes.groovie.org/docs/
6 7 """
7 8 from __future__ import with_statement
8 9 from routes import Mapper
9 10 from rhodecode.lib.utils import check_repo_fast as cr
10 11
11 12 def make_map(config):
12 13 """Create, configure and return the routes Mapper"""
13 14 map = Mapper(directory=config['pylons.paths']['controllers'],
14 15 always_scan=config['debug'])
15 16 map.minimization = False
16 17 map.explicit = False
17 18
19 def check_repo(environ, match_dict):
20 """
21 check for valid repository for proper 404 handling
22 :param environ:
23 :param match_dict:
24 """
25 repo_name = match_dict.get('repo_name')
26 return not cr(repo_name, config['base_path'])
27
18 28 # The ErrorController route (handles 404/500 error pages); it should
19 29 # likely stay at the top, ensuring it can always be resolved
20 30 map.connect('/error/{action}', controller='error')
21 31 map.connect('/error/{action}/{id}', controller='error')
22 32
33 #==========================================================================
23 34 # CUSTOM ROUTES HERE
35 #==========================================================================
36
37 #MAIN PAGE
24 38 map.connect('hg_home', '/', controller='hg', action='index')
25 39
26 def check_repo(environ, match_dict):
27 """
28 check for valid repository for proper 404 handling
29 @param environ:
30 @param match_dict:
31 """
32 repo_name = match_dict.get('repo_name')
33 return not cr(repo_name, config['base_path'])
34
35 #REST REPO MAP
40 #ADMIN REPOSITORY REST ROUTES
36 41 with map.submapper(path_prefix='/_admin', controller='admin/repos') as m:
37 42 m.connect("repos", "/repos",
38 43 action="create", conditions=dict(method=["POST"]))
39 44 m.connect("repos", "/repos",
40 45 action="index", conditions=dict(method=["GET"]))
41 46 m.connect("formatted_repos", "/repos.{format}",
42 47 action="index",
43 48 conditions=dict(method=["GET"]))
44 49 m.connect("new_repo", "/repos/new",
45 50 action="new", conditions=dict(method=["GET"]))
46 51 m.connect("formatted_new_repo", "/repos/new.{format}",
47 52 action="new", conditions=dict(method=["GET"]))
48 53 m.connect("/repos/{repo_name:.*}",
49 54 action="update", conditions=dict(method=["PUT"],
50 55 function=check_repo))
51 56 m.connect("/repos/{repo_name:.*}",
52 57 action="delete", conditions=dict(method=["DELETE"],
53 58 function=check_repo))
54 59 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
55 60 action="edit", conditions=dict(method=["GET"],
56 61 function=check_repo))
57 62 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
58 63 action="edit", conditions=dict(method=["GET"],
59 64 function=check_repo))
60 65 m.connect("repo", "/repos/{repo_name:.*}",
61 66 action="show", conditions=dict(method=["GET"],
62 67 function=check_repo))
63 68 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
64 69 action="show", conditions=dict(method=["GET"],
65 70 function=check_repo))
66 71 #ajax delete repo perm user
67 72 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
68 73 action="delete_perm_user", conditions=dict(method=["DELETE"],
69 74 function=check_repo))
70 75
76 #ADMIN USER REST ROUTES
71 77 map.resource('user', 'users', controller='admin/users', path_prefix='/_admin')
78
79 #ADMIN PERMISSIONS REST ROUTES
72 80 map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
73 81
74 #REST SETTINGS MAP
82 #ADMIN SETTINGS REST ROUTES
75 83 with map.submapper(path_prefix='/_admin', controller='admin/settings') as m:
76 84 m.connect("admin_settings", "/settings",
77 85 action="create", conditions=dict(method=["POST"]))
78 86 m.connect("admin_settings", "/settings",
79 87 action="index", conditions=dict(method=["GET"]))
80 88 m.connect("formatted_admin_settings", "/settings.{format}",
81 89 action="index", conditions=dict(method=["GET"]))
82 90 m.connect("admin_new_setting", "/settings/new",
83 91 action="new", conditions=dict(method=["GET"]))
84 92 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
85 93 action="new", conditions=dict(method=["GET"]))
86 94 m.connect("/settings/{setting_id}",
87 95 action="update", conditions=dict(method=["PUT"]))
88 96 m.connect("/settings/{setting_id}",
89 97 action="delete", conditions=dict(method=["DELETE"]))
90 98 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
91 99 action="edit", conditions=dict(method=["GET"]))
92 100 m.connect("formatted_admin_edit_setting", "/settings/{setting_id}.{format}/edit",
93 101 action="edit", conditions=dict(method=["GET"]))
94 102 m.connect("admin_setting", "/settings/{setting_id}",
95 103 action="show", conditions=dict(method=["GET"]))
96 104 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
97 105 action="show", conditions=dict(method=["GET"]))
98 106 m.connect("admin_settings_my_account", "/my_account",
99 107 action="my_account", conditions=dict(method=["GET"]))
100 108 m.connect("admin_settings_my_account_update", "/my_account_update",
101 109 action="my_account_update", conditions=dict(method=["PUT"]))
102 110 m.connect("admin_settings_create_repository", "/create_repository",
103 111 action="create_repository", conditions=dict(method=["GET"]))
104 112
105 #ADMIN
113 #ADMIN MAIN PAGES
106 114 with map.submapper(path_prefix='/_admin', controller='admin/admin') as m:
107 115 m.connect('admin_home', '', action='index')#main page
108 116 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
109 117 action='add_repo')
110 118 #SEARCH
111 119 map.connect('search', '/_admin/search', controller='search',)
112 120 map.connect('search_repo', '/_admin/search/{search_repo:.*}', controller='search')
113 121
114 122 #LOGIN/LOGOUT/REGISTER/SIGN IN
115 123 map.connect('login_home', '/_admin/login', controller='login')
116 124 map.connect('logout_home', '/_admin/logout', controller='login', action='logout')
117 125 map.connect('register', '/_admin/register', controller='login', action='register')
118 126 map.connect('reset_password', '/_admin/password_reset', controller='login', action='password_reset')
119 127
120 128 #FEEDS
121 129 map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
122 130 controller='feed', action='rss',
123 131 conditions=dict(function=check_repo))
124 132 map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
125 133 controller='feed', action='atom',
126 134 conditions=dict(function=check_repo))
127 135
128 136
129 #OTHERS
137 #REPOSITORY ROUTES
130 138 map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
131 139 controller='changeset', revision='tip',
132 140 conditions=dict(function=check_repo))
133 141 map.connect('raw_changeset_home', '/{repo_name:.*}/raw-changeset/{revision}',
134 142 controller='changeset', action='raw_changeset', revision='tip',
135 143 conditions=dict(function=check_repo))
136 144 map.connect('summary_home', '/{repo_name:.*}/summary',
137 145 controller='summary', conditions=dict(function=check_repo))
138 146 map.connect('shortlog_home', '/{repo_name:.*}/shortlog',
139 147 controller='shortlog', conditions=dict(function=check_repo))
140 148 map.connect('branches_home', '/{repo_name:.*}/branches',
141 149 controller='branches', conditions=dict(function=check_repo))
142 150 map.connect('tags_home', '/{repo_name:.*}/tags',
143 151 controller='tags', conditions=dict(function=check_repo))
144 152 map.connect('changelog_home', '/{repo_name:.*}/changelog',
145 153 controller='changelog', conditions=dict(function=check_repo))
146 154 map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
147 155 controller='files', revision='tip', f_path='',
148 156 conditions=dict(function=check_repo))
149 157 map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
150 158 controller='files', action='diff', revision='tip', f_path='',
151 159 conditions=dict(function=check_repo))
152 160 map.connect('files_rawfile_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
153 161 controller='files', action='rawfile', revision='tip', f_path='',
154 162 conditions=dict(function=check_repo))
155 163 map.connect('files_raw_home', '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
156 164 controller='files', action='raw', revision='tip', f_path='',
157 165 conditions=dict(function=check_repo))
158 166 map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
159 167 controller='files', action='annotate', revision='tip', f_path='',
160 168 conditions=dict(function=check_repo))
161 169 map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}',
162 170 controller='files', action='archivefile', revision='tip',
163 171 conditions=dict(function=check_repo))
164 172 map.connect('repo_settings_delete', '/{repo_name:.*}/settings',
165 173 controller='settings', action="delete",
166 174 conditions=dict(method=["DELETE"], function=check_repo))
167 175 map.connect('repo_settings_update', '/{repo_name:.*}/settings',
168 176 controller='settings', action="update",
169 177 conditions=dict(method=["PUT"], function=check_repo))
170 178 map.connect('repo_settings_home', '/{repo_name:.*}/settings',
171 179 controller='settings', action='index',
172 180 conditions=dict(function=check_repo))
173 181
174 182 map.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
175 183 controller='settings', action='fork_create',
176 184 conditions=dict(function=check_repo, method=["POST"]))
177 185 map.connect('repo_fork_home', '/{repo_name:.*}/fork',
178 186 controller='settings', action='fork',
179 187 conditions=dict(function=check_repo))
180 188
181 189 return map
@@ -1,245 +1,245 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # repos controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 7, 2010
22 22 admin controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from operator import itemgetter
27 27 from paste.httpexceptions import HTTPInternalServerError
28 28 from pylons import request, response, session, tmpl_context as c, url
29 29 from pylons.controllers.util import abort, redirect
30 30 from pylons.i18n.translation import _
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
33 33 HasPermissionAnyDecorator
34 34 from rhodecode.lib.base import BaseController, render
35 35 from rhodecode.lib.utils import invalidate_cache, action_logger
36 36 from rhodecode.model.db import User
37 37 from rhodecode.model.forms import RepoForm
38 38 from rhodecode.model.hg_model import HgModel
39 39 from rhodecode.model.repo_model import RepoModel
40 40 import formencode
41 41 import logging
42 42 import traceback
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46 class ReposController(BaseController):
47 47 """REST Controller styled on the Atom Publishing Protocol"""
48 48 # To properly map this controller, ensure your config/routing.py
49 49 # file has a resource setup:
50 50 # map.resource('repo', 'repos')
51 51
52 52 @LoginRequired()
53 53 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
54 54 def __before__(self):
55 55 c.admin_user = session.get('admin_user')
56 56 c.admin_username = session.get('admin_username')
57 57 super(ReposController, self).__before__()
58 58
59 59 @HasPermissionAllDecorator('hg.admin')
60 60 def index(self, format='html'):
61 61 """GET /repos: All items in the collection"""
62 62 # url('repos')
63 63 cached_repo_list = HgModel().get_repos()
64 64 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
65 65 return render('admin/repos/repos.html')
66 66
67 67 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
68 68 def create(self):
69 69 """POST /repos: Create a new item"""
70 70 # url('repos')
71 71 repo_model = RepoModel()
72 72 _form = RepoForm()()
73 73 form_result = {}
74 74 try:
75 75 form_result = _form.to_python(dict(request.POST))
76 76 repo_model.create(form_result, c.rhodecode_user)
77 77 invalidate_cache('cached_repo_list')
78 78 h.flash(_('created repository %s') % form_result['repo_name'],
79 79 category='success')
80 80
81 81 if request.POST.get('user_created'):
82 82 action_logger(self.rhodecode_user, 'user_created_repo',
83 83 form_result['repo_name'], '', self.sa)
84 84 else:
85 85 action_logger(self.rhodecode_user, 'admin_created_repo',
86 86 form_result['repo_name'], '', self.sa)
87 87
88 88 except formencode.Invalid, errors:
89 89 c.new_repo = errors.value['repo_name']
90 90
91 91 if request.POST.get('user_created'):
92 92 r = render('admin/repos/repo_add_create_repository.html')
93 93 else:
94 94 r = render('admin/repos/repo_add.html')
95 95
96 96 return htmlfill.render(
97 97 r,
98 98 defaults=errors.value,
99 99 errors=errors.error_dict or {},
100 100 prefix_error=False,
101 101 encoding="UTF-8")
102 102
103 103 except Exception:
104 104 log.error(traceback.format_exc())
105 105 msg = _('error occured during creation of repository %s') \
106 106 % form_result.get('repo_name')
107 107 h.flash(msg, category='error')
108 108 if request.POST.get('user_created'):
109 109 return redirect(url('hg_home'))
110 110 return redirect(url('repos'))
111 111
112 112 @HasPermissionAllDecorator('hg.admin')
113 113 def new(self, format='html'):
114 114 """GET /repos/new: Form to create a new item"""
115 115 new_repo = request.GET.get('repo', '')
116 116 c.new_repo = h.repo_name_slug(new_repo)
117 117
118 118 return render('admin/repos/repo_add.html')
119 119
120 120 @HasPermissionAllDecorator('hg.admin')
121 121 def update(self, repo_name):
122 122 """PUT /repos/repo_name: Update an existing item"""
123 123 # Forms posted to this method should contain a hidden field:
124 124 # <input type="hidden" name="_method" value="PUT" />
125 125 # Or using helpers:
126 126 # h.form(url('repo', repo_name=ID),
127 127 # method='put')
128 128 # url('repo', repo_name=ID)
129 129 repo_model = RepoModel()
130 130 changed_name = repo_name
131 131 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
132 132
133 133 try:
134 134 form_result = _form.to_python(dict(request.POST))
135 135 repo_model.update(repo_name, form_result)
136 136 invalidate_cache('cached_repo_list')
137 137 h.flash(_('Repository %s updated succesfully' % repo_name),
138 138 category='success')
139 139 changed_name = form_result['repo_name']
140 140 except formencode.Invalid, errors:
141 141 c.repo_info = repo_model.get(repo_name)
142 142 c.users_array = repo_model.get_users_js()
143 143 errors.value.update({'user':c.repo_info.user.username})
144 144 return htmlfill.render(
145 145 render('admin/repos/repo_edit.html'),
146 146 defaults=errors.value,
147 147 errors=errors.error_dict or {},
148 148 prefix_error=False,
149 149 encoding="UTF-8")
150 150
151 151 except Exception:
152 152 log.error(traceback.format_exc())
153 153 h.flash(_('error occured during update of repository %s') \
154 154 % repo_name, category='error')
155 155
156 156 return redirect(url('edit_repo', repo_name=changed_name))
157 157
158 158 @HasPermissionAllDecorator('hg.admin')
159 159 def delete(self, repo_name):
160 160 """DELETE /repos/repo_name: Delete an existing item"""
161 161 # Forms posted to this method should contain a hidden field:
162 162 # <input type="hidden" name="_method" value="DELETE" />
163 163 # Or using helpers:
164 164 # h.form(url('repo', repo_name=ID),
165 165 # method='delete')
166 166 # url('repo', repo_name=ID)
167 167
168 168 repo_model = RepoModel()
169 169 repo = repo_model.get(repo_name)
170 170 if not repo:
171 171 h.flash(_('%s repository is not mapped to db perhaps'
172 172 ' it was moved or renamed from the filesystem'
173 173 ' please run the application again'
174 174 ' in order to rescan repositories') % repo_name,
175 175 category='error')
176 176
177 177 return redirect(url('repos'))
178 178 try:
179 179 action_logger(self.rhodecode_user, 'admin_deleted_repo',
180 180 repo_name, '', self.sa)
181 181 repo_model.delete(repo)
182 182 invalidate_cache('cached_repo_list')
183 183 h.flash(_('deleted repository %s') % repo_name, category='success')
184 184
185 185 except Exception, e:
186 186 log.error(traceback.format_exc())
187 187 h.flash(_('An error occured during deletion of %s') % repo_name,
188 188 category='error')
189 189
190 190 return redirect(url('repos'))
191 191
192 192 @HasPermissionAllDecorator('hg.admin')
193 193 def delete_perm_user(self, repo_name):
194 194 """
195 195 DELETE an existing repository permission user
196 @param repo_name:
196 :param repo_name:
197 197 """
198 198
199 199 try:
200 200 repo_model = RepoModel()
201 201 repo_model.delete_perm_user(request.POST, repo_name)
202 202 except Exception, e:
203 203 h.flash(_('An error occured during deletion of repository user'),
204 204 category='error')
205 205 raise HTTPInternalServerError()
206 206
207 207 @HasPermissionAllDecorator('hg.admin')
208 208 def show(self, repo_name, format='html'):
209 209 """GET /repos/repo_name: Show a specific item"""
210 210 # url('repo', repo_name=ID)
211 211
212 212 @HasPermissionAllDecorator('hg.admin')
213 213 def edit(self, repo_name, format='html'):
214 214 """GET /repos/repo_name/edit: Form to edit an existing item"""
215 215 # url('edit_repo', repo_name=ID)
216 216 repo_model = RepoModel()
217 217 c.repo_info = repo = repo_model.get(repo_name)
218 218 if not repo:
219 219 h.flash(_('%s repository is not mapped to db perhaps'
220 220 ' it was created or renamed from the filesystem'
221 221 ' please run the application again'
222 222 ' in order to rescan repositories') % repo_name,
223 223 category='error')
224 224
225 225 return redirect(url('repos'))
226 226 defaults = c.repo_info.__dict__
227 227 if c.repo_info.user:
228 228 defaults.update({'user':c.repo_info.user.username})
229 229 else:
230 230 replacement_user = self.sa.query(User)\
231 231 .filter(User.admin == True).first().username
232 232 defaults.update({'user':replacement_user})
233 233
234 234 c.users_array = repo_model.get_users_js()
235 235
236 236 for p in c.repo_info.repo_to_perm:
237 237 defaults.update({'perm_%s' % p.user.username:
238 238 p.permission.permission_name})
239 239
240 240 return htmlfill.render(
241 241 render('admin/repos/repo_edit.html'),
242 242 defaults=defaults,
243 243 encoding="UTF-8",
244 244 force_defaults=False
245 245 )
@@ -1,486 +1,486 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # authentication and permission libraries
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 4, 2010
22 22
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from pylons import config, session, url, request
27 27 from pylons.controllers.util import abort, redirect
28 28 from rhodecode.lib.utils import get_repo_slug
29 29 from rhodecode.model import meta
30 30 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
31 31 UserToPerm
32 32 from sqlalchemy.exc import OperationalError
33 33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
34 34 import bcrypt
35 35 from decorator import decorator
36 36 import logging
37 37 import random
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41 class PasswordGenerator(object):
42 42 """This is a simple class for generating password from
43 43 different sets of characters
44 44 usage:
45 45 passwd_gen = PasswordGenerator()
46 46 #print 8-letter password containing only big and small letters of alphabet
47 47 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
48 48 """
49 49 ALPHABETS_NUM = r'''1234567890'''#[0]
50 50 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
51 51 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
52 52 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
53 53 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
54 54 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
55 55 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
56 56 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
57 57 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
58 58
59 59 def __init__(self, passwd=''):
60 60 self.passwd = passwd
61 61
62 62 def gen_password(self, len, type):
63 63 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
64 64 return self.passwd
65 65
66 66
67 67 def get_crypt_password(password):
68 68 """Cryptographic function used for password hashing based on sha1
69 @param password: password to hash
69 :param password: password to hash
70 70 """
71 71 return bcrypt.hashpw(password, bcrypt.gensalt(10))
72 72
73 73 def check_password(password, hashed):
74 74 return bcrypt.hashpw(password, hashed) == hashed
75 75
76 76 @cache_region('super_short_term', 'cached_user')
77 77 def get_user_cached(username):
78 78 sa = meta.Session
79 79 try:
80 80 user = sa.query(User).filter(User.username == username).one()
81 81 finally:
82 82 meta.Session.remove()
83 83 return user
84 84
85 85 def authfunc(environ, username, password):
86 86 try:
87 87 user = get_user_cached(username)
88 88 except (NoResultFound, MultipleResultsFound, OperationalError), e:
89 89 log.error(e)
90 90 user = None
91 91
92 92 if user:
93 93 if user.active:
94 94 if user.username == username and check_password(password, user.password):
95 95 log.info('user %s authenticated correctly', username)
96 96 return True
97 97 else:
98 98 log.error('user %s is disabled', username)
99 99
100 100 return False
101 101
102 102 class AuthUser(object):
103 103 """
104 104 A simple object that handles a mercurial username for authentication
105 105 """
106 106 def __init__(self):
107 107 self.username = 'None'
108 108 self.name = ''
109 109 self.lastname = ''
110 110 self.email = ''
111 111 self.user_id = None
112 112 self.is_authenticated = False
113 113 self.is_admin = False
114 114 self.permissions = {}
115 115
116 116
117 117 def set_available_permissions(config):
118 118 """
119 119 This function will propagate pylons globals with all available defined
120 120 permission given in db. We don't wannt to check each time from db for new
121 121 permissions since adding a new permission also requires application restart
122 122 ie. to decorate new views with the newly created permission
123 @param config:
123 :param config:
124 124 """
125 125 log.info('getting information about all available permissions')
126 126 try:
127 127 sa = meta.Session
128 128 all_perms = sa.query(Permission).all()
129 129 finally:
130 130 meta.Session.remove()
131 131
132 132 config['available_permissions'] = [x.permission_name for x in all_perms]
133 133
134 134 def set_base_path(config):
135 135 config['base_path'] = config['pylons.app_globals'].base_path
136 136
137 137 def fill_data(user):
138 138 """
139 139 Fills user data with those from database and log out user if not present
140 140 in database
141 @param user:
141 :param user:
142 142 """
143 143 sa = meta.Session
144 144 dbuser = sa.query(User).get(user.user_id)
145 145 if dbuser:
146 146 user.username = dbuser.username
147 147 user.is_admin = dbuser.admin
148 148 user.name = dbuser.name
149 149 user.lastname = dbuser.lastname
150 150 user.email = dbuser.email
151 151 else:
152 152 user.is_authenticated = False
153 153 meta.Session.remove()
154 154 return user
155 155
156 156 def fill_perms(user):
157 157 """
158 158 Fills user permission attribute with permissions taken from database
159 @param user:
159 :param user:
160 160 """
161 161
162 162 sa = meta.Session
163 163 user.permissions['repositories'] = {}
164 164 user.permissions['global'] = set()
165 165
166 166 #===========================================================================
167 167 # fetch default permissions
168 168 #===========================================================================
169 169 default_perms = sa.query(RepoToPerm, Repository, Permission)\
170 170 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
171 171 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
172 172 .filter(RepoToPerm.user == sa.query(User).filter(User.username ==
173 173 'default').scalar()).all()
174 174
175 175 if user.is_admin:
176 176 #=======================================================================
177 177 # #admin have all default rights set to admin
178 178 #=======================================================================
179 179 user.permissions['global'].add('hg.admin')
180 180
181 181 for perm in default_perms:
182 182 p = 'repository.admin'
183 183 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
184 184
185 185 else:
186 186 #=======================================================================
187 187 # set default permissions
188 188 #=======================================================================
189 189
190 190 #default global
191 191 default_global_perms = sa.query(UserToPerm)\
192 192 .filter(UserToPerm.user == sa.query(User).filter(User.username ==
193 193 'default').one())
194 194
195 195 for perm in default_global_perms:
196 196 user.permissions['global'].add(perm.permission.permission_name)
197 197
198 198 #default repositories
199 199 for perm in default_perms:
200 200 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
201 201 #disable defaults for private repos,
202 202 p = 'repository.none'
203 203 elif perm.Repository.user_id == user.user_id:
204 204 #set admin if owner
205 205 p = 'repository.admin'
206 206 else:
207 207 p = perm.Permission.permission_name
208 208
209 209 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
210 210
211 211 #=======================================================================
212 212 # #overwrite default with user permissions if any
213 213 #=======================================================================
214 214 user_perms = sa.query(RepoToPerm, Permission, Repository)\
215 215 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
216 216 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
217 217 .filter(RepoToPerm.user_id == user.user_id).all()
218 218
219 219 for perm in user_perms:
220 220 if perm.Repository.user_id == user.user_id:#set admin if owner
221 221 p = 'repository.admin'
222 222 else:
223 223 p = perm.Permission.permission_name
224 224 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
225 225 meta.Session.remove()
226 226 return user
227 227
228 228 def get_user(session):
229 229 """
230 230 Gets user from session, and wraps permissions into user
231 @param session:
231 :param session:
232 232 """
233 233 user = session.get('rhodecode_user', AuthUser())
234 234 if user.is_authenticated:
235 235 user = fill_data(user)
236 236 user = fill_perms(user)
237 237 session['rhodecode_user'] = user
238 238 session.save()
239 239 return user
240 240
241 241 #===============================================================================
242 242 # CHECK DECORATORS
243 243 #===============================================================================
244 244 class LoginRequired(object):
245 245 """Must be logged in to execute this function else redirect to login page"""
246 246
247 247 def __call__(self, func):
248 248 return decorator(self.__wrapper, func)
249 249
250 250 def __wrapper(self, func, *fargs, **fkwargs):
251 251 user = session.get('rhodecode_user', AuthUser())
252 252 log.debug('Checking login required for user:%s', user.username)
253 253 if user.is_authenticated:
254 254 log.debug('user %s is authenticated', user.username)
255 255 return func(*fargs, **fkwargs)
256 256 else:
257 257 log.warn('user %s not authenticated', user.username)
258 258
259 259 p = ''
260 260 if request.environ.get('SCRIPT_NAME') != '/':
261 261 p += request.environ.get('SCRIPT_NAME')
262 262
263 263 p += request.environ.get('PATH_INFO')
264 264 if request.environ.get('QUERY_STRING'):
265 265 p += '?' + request.environ.get('QUERY_STRING')
266 266
267 267 log.debug('redirecting to login page with %s', p)
268 268 return redirect(url('login_home', came_from=p))
269 269
270 270 class PermsDecorator(object):
271 271 """Base class for decorators"""
272 272
273 273 def __init__(self, *required_perms):
274 274 available_perms = config['available_permissions']
275 275 for perm in required_perms:
276 276 if perm not in available_perms:
277 277 raise Exception("'%s' permission is not defined" % perm)
278 278 self.required_perms = set(required_perms)
279 279 self.user_perms = None
280 280
281 281 def __call__(self, func):
282 282 return decorator(self.__wrapper, func)
283 283
284 284
285 285 def __wrapper(self, func, *fargs, **fkwargs):
286 286 # _wrapper.__name__ = func.__name__
287 287 # _wrapper.__dict__.update(func.__dict__)
288 288 # _wrapper.__doc__ = func.__doc__
289 289
290 290 self.user_perms = session.get('rhodecode_user', AuthUser()).permissions
291 291 log.debug('checking %s permissions %s for %s',
292 292 self.__class__.__name__, self.required_perms, func.__name__)
293 293
294 294 if self.check_permissions():
295 295 log.debug('Permission granted for %s', func.__name__)
296 296
297 297 return func(*fargs, **fkwargs)
298 298
299 299 else:
300 300 log.warning('Permission denied for %s', func.__name__)
301 301 #redirect with forbidden ret code
302 302 return abort(403)
303 303
304 304
305 305
306 306 def check_permissions(self):
307 307 """Dummy function for overriding"""
308 308 raise Exception('You have to write this function in child class')
309 309
310 310 class HasPermissionAllDecorator(PermsDecorator):
311 311 """Checks for access permission for all given predicates. All of them
312 312 have to be meet in order to fulfill the request
313 313 """
314 314
315 315 def check_permissions(self):
316 316 if self.required_perms.issubset(self.user_perms.get('global')):
317 317 return True
318 318 return False
319 319
320 320
321 321 class HasPermissionAnyDecorator(PermsDecorator):
322 322 """Checks for access permission for any of given predicates. In order to
323 323 fulfill the request any of predicates must be meet
324 324 """
325 325
326 326 def check_permissions(self):
327 327 if self.required_perms.intersection(self.user_perms.get('global')):
328 328 return True
329 329 return False
330 330
331 331 class HasRepoPermissionAllDecorator(PermsDecorator):
332 332 """Checks for access permission for all given predicates for specific
333 333 repository. All of them have to be meet in order to fulfill the request
334 334 """
335 335
336 336 def check_permissions(self):
337 337 repo_name = get_repo_slug(request)
338 338 try:
339 339 user_perms = set([self.user_perms['repositories'][repo_name]])
340 340 except KeyError:
341 341 return False
342 342 if self.required_perms.issubset(user_perms):
343 343 return True
344 344 return False
345 345
346 346
347 347 class HasRepoPermissionAnyDecorator(PermsDecorator):
348 348 """Checks for access permission for any of given predicates for specific
349 349 repository. In order to fulfill the request any of predicates must be meet
350 350 """
351 351
352 352 def check_permissions(self):
353 353 repo_name = get_repo_slug(request)
354 354
355 355 try:
356 356 user_perms = set([self.user_perms['repositories'][repo_name]])
357 357 except KeyError:
358 358 return False
359 359 if self.required_perms.intersection(user_perms):
360 360 return True
361 361 return False
362 362 #===============================================================================
363 363 # CHECK FUNCTIONS
364 364 #===============================================================================
365 365
366 366 class PermsFunction(object):
367 367 """Base function for other check functions"""
368 368
369 369 def __init__(self, *perms):
370 370 available_perms = config['available_permissions']
371 371
372 372 for perm in perms:
373 373 if perm not in available_perms:
374 374 raise Exception("'%s' permission in not defined" % perm)
375 375 self.required_perms = set(perms)
376 376 self.user_perms = None
377 377 self.granted_for = ''
378 378 self.repo_name = None
379 379
380 380 def __call__(self, check_Location=''):
381 381 user = session.get('rhodecode_user', False)
382 382 if not user:
383 383 return False
384 384 self.user_perms = user.permissions
385 385 self.granted_for = user.username
386 386 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
387 387
388 388 if self.check_permissions():
389 389 log.debug('Permission granted for %s @%s', self.granted_for,
390 390 check_Location)
391 391 return True
392 392
393 393 else:
394 394 log.warning('Permission denied for %s @%s', self.granted_for,
395 395 check_Location)
396 396 return False
397 397
398 398 def check_permissions(self):
399 399 """Dummy function for overriding"""
400 400 raise Exception('You have to write this function in child class')
401 401
402 402 class HasPermissionAll(PermsFunction):
403 403 def check_permissions(self):
404 404 if self.required_perms.issubset(self.user_perms.get('global')):
405 405 return True
406 406 return False
407 407
408 408 class HasPermissionAny(PermsFunction):
409 409 def check_permissions(self):
410 410 if self.required_perms.intersection(self.user_perms.get('global')):
411 411 return True
412 412 return False
413 413
414 414 class HasRepoPermissionAll(PermsFunction):
415 415
416 416 def __call__(self, repo_name=None, check_Location=''):
417 417 self.repo_name = repo_name
418 418 return super(HasRepoPermissionAll, self).__call__(check_Location)
419 419
420 420 def check_permissions(self):
421 421 if not self.repo_name:
422 422 self.repo_name = get_repo_slug(request)
423 423
424 424 try:
425 425 self.user_perms = set([self.user_perms['repositories']\
426 426 [self.repo_name]])
427 427 except KeyError:
428 428 return False
429 429 self.granted_for = self.repo_name
430 430 if self.required_perms.issubset(self.user_perms):
431 431 return True
432 432 return False
433 433
434 434 class HasRepoPermissionAny(PermsFunction):
435 435
436 436 def __call__(self, repo_name=None, check_Location=''):
437 437 self.repo_name = repo_name
438 438 return super(HasRepoPermissionAny, self).__call__(check_Location)
439 439
440 440 def check_permissions(self):
441 441 if not self.repo_name:
442 442 self.repo_name = get_repo_slug(request)
443 443
444 444 try:
445 445 self.user_perms = set([self.user_perms['repositories']\
446 446 [self.repo_name]])
447 447 except KeyError:
448 448 return False
449 449 self.granted_for = self.repo_name
450 450 if self.required_perms.intersection(self.user_perms):
451 451 return True
452 452 return False
453 453
454 454 #===============================================================================
455 455 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
456 456 #===============================================================================
457 457
458 458 class HasPermissionAnyMiddleware(object):
459 459 def __init__(self, *perms):
460 460 self.required_perms = set(perms)
461 461
462 462 def __call__(self, user, repo_name):
463 463 usr = AuthUser()
464 464 usr.user_id = user.user_id
465 465 usr.username = user.username
466 466 usr.is_admin = user.admin
467 467
468 468 try:
469 469 self.user_perms = set([fill_perms(usr)\
470 470 .permissions['repositories'][repo_name]])
471 471 except:
472 472 self.user_perms = set()
473 473 self.granted_for = ''
474 474 self.username = user.username
475 475 self.repo_name = repo_name
476 476 return self.check_permissions()
477 477
478 478 def check_permissions(self):
479 479 log.debug('checking mercurial protocol '
480 480 'permissions for user:%s repository:%s',
481 481 self.username, self.repo_name)
482 482 if self.required_perms.intersection(self.user_perms):
483 483 log.debug('permission granted')
484 484 return True
485 485 log.debug('permission denied')
486 486 return False
@@ -1,383 +1,383 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 from pygments.formatters import HtmlFormatter
7 7 from pygments import highlight as code_highlight
8 8 from pylons import url, app_globals as g
9 9 from pylons.i18n.translation import _, ungettext
10 10 from vcs.utils.annotate import annotate_highlight
11 11 from webhelpers.html import literal, HTML, escape
12 12 from webhelpers.html.tools import *
13 13 from webhelpers.html.builder import make_tag
14 14 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
15 15 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
16 16 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
17 17 password, textarea, title, ul, xml_declaration, radio
18 18 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
19 19 mail_to, strip_links, strip_tags, tag_re
20 20 from webhelpers.number import format_byte_size, format_bit_size
21 21 from webhelpers.pylonslib import Flash as _Flash
22 22 from webhelpers.pylonslib.secure_form import secure_form
23 23 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
24 24 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
25 25 replace_whitespace, urlify, truncate, wrap_paragraphs
26 26
27 27 #Custom helpers here :)
28 28 class _Link(object):
29 29 '''
30 30 Make a url based on label and url with help of url_for
31 @param label:name of link if not defined url is used
32 @param url: the url for link
31 :param label:name of link if not defined url is used
32 :param url: the url for link
33 33 '''
34 34
35 35 def __call__(self, label='', *url_, **urlargs):
36 36 if label is None or '':
37 37 label = url
38 38 link_fn = link_to(label, url(*url_, **urlargs))
39 39 return link_fn
40 40
41 41 link = _Link()
42 42
43 43 class _GetError(object):
44 44
45 45 def __call__(self, field_name, form_errors):
46 46 tmpl = """<span class="error_msg">%s</span>"""
47 47 if form_errors and form_errors.has_key(field_name):
48 48 return literal(tmpl % form_errors.get(field_name))
49 49
50 50 get_error = _GetError()
51 51
52 52 def recursive_replace(str, replace=' '):
53 53 """
54 54 Recursive replace of given sign to just one instance
55 @param str: given string
56 @param replace:char to find and replace multiple instances
55 :param str: given string
56 :param replace:char to find and replace multiple instances
57 57
58 58 Examples::
59 59 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
60 60 'Mighty-Mighty-Bo-sstones'
61 61 """
62 62
63 63 if str.find(replace * 2) == -1:
64 64 return str
65 65 else:
66 66 str = str.replace(replace * 2, replace)
67 67 return recursive_replace(str, replace)
68 68
69 69 class _ToolTip(object):
70 70
71 71 def __call__(self, tooltip_title, trim_at=50):
72 72 """
73 73 Special function just to wrap our text into nice formatted autowrapped
74 74 text
75 @param tooltip_title:
75 :param tooltip_title:
76 76 """
77 77
78 78 return wrap_paragraphs(escape(tooltip_title), trim_at)\
79 79 .replace('\n', '<br/>')
80 80
81 81 def activate(self):
82 82 """
83 83 Adds tooltip mechanism to the given Html all tooltips have to have
84 84 set class tooltip and set attribute tooltip_title.
85 85 Then a tooltip will be generated based on that
86 86 All with yui js tooltip
87 87 """
88 88
89 89 js = '''
90 90 YAHOO.util.Event.onDOMReady(function(){
91 91 function toolTipsId(){
92 92 var ids = [];
93 93 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
94 94
95 95 for (var i = 0; i < tts.length; i++) {
96 96 //if element doesn not have and id autgenerate one for tooltip
97 97
98 98 if (!tts[i].id){
99 99 tts[i].id='tt'+i*100;
100 100 }
101 101 ids.push(tts[i].id);
102 102 }
103 103 return ids
104 104 };
105 105 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
106 106 context: toolTipsId(),
107 107 monitorresize:false,
108 108 xyoffset :[0,0],
109 109 autodismissdelay:300000,
110 110 hidedelay:5,
111 111 showdelay:20,
112 112 });
113 113
114 114 //Mouse Over event disabled for new repositories since they dont
115 115 //have last commit message
116 116 myToolTips.contextMouseOverEvent.subscribe(
117 117 function(type, args) {
118 118 var context = args[0];
119 119 var txt = context.getAttribute('tooltip_title');
120 120 if(txt){
121 121 return true;
122 122 }
123 123 else{
124 124 return false;
125 125 }
126 126 });
127 127
128 128
129 129 // Set the text for the tooltip just before we display it. Lazy method
130 130 myToolTips.contextTriggerEvent.subscribe(
131 131 function(type, args) {
132 132
133 133
134 134 var context = args[0];
135 135
136 136 var txt = context.getAttribute('tooltip_title');
137 137 this.cfg.setProperty("text", txt);
138 138
139 139
140 140 // positioning of tooltip
141 141 var tt_w = this.element.clientWidth;
142 142 var tt_h = this.element.clientHeight;
143 143
144 144 var context_w = context.offsetWidth;
145 145 var context_h = context.offsetHeight;
146 146
147 147 var pos_x = YAHOO.util.Dom.getX(context);
148 148 var pos_y = YAHOO.util.Dom.getY(context);
149 149
150 150 var display_strategy = 'top';
151 151 var xy_pos = [0,0];
152 152 switch (display_strategy){
153 153
154 154 case 'top':
155 155 var cur_x = (pos_x+context_w/2)-(tt_w/2);
156 156 var cur_y = pos_y-tt_h-4;
157 157 xy_pos = [cur_x,cur_y];
158 158 break;
159 159 case 'bottom':
160 160 var cur_x = (pos_x+context_w/2)-(tt_w/2);
161 161 var cur_y = pos_y+context_h+4;
162 162 xy_pos = [cur_x,cur_y];
163 163 break;
164 164 case 'left':
165 165 var cur_x = (pos_x-tt_w-4);
166 166 var cur_y = pos_y-((tt_h/2)-context_h/2);
167 167 xy_pos = [cur_x,cur_y];
168 168 break;
169 169 case 'right':
170 170 var cur_x = (pos_x+context_w+4);
171 171 var cur_y = pos_y-((tt_h/2)-context_h/2);
172 172 xy_pos = [cur_x,cur_y];
173 173 break;
174 174 default:
175 175 var cur_x = (pos_x+context_w/2)-(tt_w/2);
176 176 var cur_y = pos_y-tt_h-4;
177 177 xy_pos = [cur_x,cur_y];
178 178 break;
179 179
180 180 }
181 181
182 182 this.cfg.setProperty("xy",xy_pos);
183 183
184 184 });
185 185
186 186 //Mouse out
187 187 myToolTips.contextMouseOutEvent.subscribe(
188 188 function(type, args) {
189 189 var context = args[0];
190 190
191 191 });
192 192 });
193 193 '''
194 194 return literal(js)
195 195
196 196 tooltip = _ToolTip()
197 197
198 198 class _FilesBreadCrumbs(object):
199 199
200 200 def __call__(self, repo_name, rev, paths):
201 201 url_l = [link_to(repo_name, url('files_home',
202 202 repo_name=repo_name,
203 203 revision=rev, f_path=''))]
204 204 paths_l = paths.split('/')
205 205
206 206 for cnt, p in enumerate(paths_l, 1):
207 207 if p != '':
208 208 url_l.append(link_to(p, url('files_home',
209 209 repo_name=repo_name,
210 210 revision=rev,
211 211 f_path='/'.join(paths_l[:cnt]))))
212 212
213 213 return literal('/'.join(url_l))
214 214
215 215 files_breadcrumbs = _FilesBreadCrumbs()
216 216 class CodeHtmlFormatter(HtmlFormatter):
217 217
218 218 def wrap(self, source, outfile):
219 219 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
220 220
221 221 def _wrap_code(self, source):
222 222 for cnt, it in enumerate(source, 1):
223 223 i, t = it
224 224 t = '<div id="#S-%s">%s</div>' % (cnt, t)
225 225 yield i, t
226 226 def pygmentize(filenode, **kwargs):
227 227 """
228 228 pygmentize function using pygments
229 @param filenode:
229 :param filenode:
230 230 """
231 231 return literal(code_highlight(filenode.content,
232 232 filenode.lexer, CodeHtmlFormatter(**kwargs)))
233 233
234 234 def pygmentize_annotation(filenode, **kwargs):
235 235 """
236 236 pygmentize function for annotation
237 @param filenode:
237 :param filenode:
238 238 """
239 239
240 240 color_dict = {}
241 241 def gen_color():
242 242 """generator for getting 10k of evenly distibuted colors using hsv color
243 243 and golden ratio.
244 244 """
245 245 import colorsys
246 246 n = 10000
247 247 golden_ratio = 0.618033988749895
248 248 h = 0.22717784590367374
249 249 #generate 10k nice web friendly colors in the same order
250 250 for c in xrange(n):
251 251 h += golden_ratio
252 252 h %= 1
253 253 HSV_tuple = [h, 0.95, 0.95]
254 254 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
255 255 yield map(lambda x:str(int(x * 256)), RGB_tuple)
256 256
257 257 cgenerator = gen_color()
258 258
259 259 def get_color_string(cs):
260 260 if color_dict.has_key(cs):
261 261 col = color_dict[cs]
262 262 else:
263 263 col = color_dict[cs] = cgenerator.next()
264 264 return "color: rgb(%s)! important;" % (', '.join(col))
265 265
266 266 def url_func(changeset):
267 267 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
268 268 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
269 269
270 270 tooltip_html = tooltip_html % (changeset.author,
271 271 changeset.date,
272 272 tooltip(changeset.message))
273 273 lnk_format = 'r%-5s:%s' % (changeset.revision,
274 274 changeset.short_id)
275 275 uri = link_to(
276 276 lnk_format,
277 277 url('changeset_home', repo_name=changeset.repository.name,
278 278 revision=changeset.short_id),
279 279 style=get_color_string(changeset.short_id),
280 280 class_='tooltip',
281 281 tooltip_title=tooltip_html
282 282 )
283 283
284 284 uri += '\n'
285 285 return uri
286 286 return literal(annotate_highlight(filenode, url_func, **kwargs))
287 287
288 288 def repo_name_slug(value):
289 289 """Return slug of name of repository
290 290 This function is called on each creation/modification
291 291 of repository to prevent bad names in repo
292 292 """
293 293 slug = remove_formatting(value)
294 294 slug = strip_tags(slug)
295 295
296 296 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
297 297 slug = slug.replace(c, '-')
298 298 slug = recursive_replace(slug, '-')
299 299 slug = collapse(slug, '-')
300 300 return slug
301 301
302 302 def get_changeset_safe(repo, rev):
303 303 from vcs.backends.base import BaseRepository
304 304 from vcs.exceptions import RepositoryError
305 305 if not isinstance(repo, BaseRepository):
306 306 raise Exception('You must pass an Repository '
307 307 'object as first argument got %s', type(repo))
308 308
309 309 try:
310 310 cs = repo.get_changeset(rev)
311 311 except RepositoryError:
312 312 from rhodecode.lib.utils import EmptyChangeset
313 313 cs = EmptyChangeset()
314 314 return cs
315 315
316 316
317 317 flash = _Flash()
318 318
319 319
320 320 #===============================================================================
321 321 # MERCURIAL FILTERS available via h.
322 322 #===============================================================================
323 323 from mercurial import util
324 324 from mercurial.templatefilters import age as _age, person as _person
325 325
326 326 age = lambda x:_age(x)
327 327 capitalize = lambda x: x.capitalize()
328 328 date = lambda x: util.datestr(x)
329 329 email = util.email
330 330 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
331 331 person = lambda x: _person(x)
332 332 hgdate = lambda x: "%d %d" % x
333 333 isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2')
334 334 isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2')
335 335 localdate = lambda x: (x[0], util.makedate()[1])
336 336 rfc822date = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2")
337 337 rfc822date_notz = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S")
338 338 rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2")
339 339 time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2")
340 340
341 341
342 342 #===============================================================================
343 343 # PERMS
344 344 #===============================================================================
345 345 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
346 346 HasRepoPermissionAny, HasRepoPermissionAll
347 347
348 348 #===============================================================================
349 349 # GRAVATAR URL
350 350 #===============================================================================
351 351 import hashlib
352 352 import urllib
353 353 from pylons import request
354 354
355 355 def gravatar_url(email_address, size=30):
356 356 ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME')
357 357 default = 'identicon'
358 358 baseurl_nossl = "http://www.gravatar.com/avatar/"
359 359 baseurl_ssl = "https://secure.gravatar.com/avatar/"
360 360 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
361 361
362 362
363 363 # construct the url
364 364 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
365 365 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
366 366
367 367 return gravatar_url
368 368
369 369 def safe_unicode(str):
370 370 """safe unicode function. In case of UnicodeDecode error we try to return
371 371 unicode with errors replace, if this failes we return unicode with
372 372 string_escape decoding """
373 373
374 374 try:
375 375 u_str = unicode(str)
376 376 except UnicodeDecodeError:
377 377 try:
378 378 u_str = unicode(str, 'utf-8', 'replace')
379 379 except UnicodeDecodeError:
380 380 #incase we have a decode error just represent as byte string
381 381 u_str = unicode(str(str).encode('string_escape'))
382 382
383 383 return u_str
@@ -1,78 +1,78 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # custom hooks for application
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on Aug 6, 2010
22 22
23 23 @author: marcink
24 24 """
25 25
26 26 import sys
27 27 import os
28 28 from rhodecode.lib import helpers as h
29 29 from rhodecode.model import meta
30 30 from rhodecode.model.db import UserLog, User
31 31
32 32 def repo_size(ui, repo, hooktype=None, **kwargs):
33 33
34 34 if hooktype != 'changegroup':
35 35 return False
36 36 size_hg, size_root = 0, 0
37 37 for path, dirs, files in os.walk(repo.root):
38 38 if path.find('.hg') != -1:
39 39 for f in files:
40 40 size_hg += os.path.getsize(os.path.join(path, f))
41 41 else:
42 42 for f in files:
43 43 size_root += os.path.getsize(os.path.join(path, f))
44 44
45 45 size_hg_f = h.format_byte_size(size_hg)
46 46 size_root_f = h.format_byte_size(size_root)
47 47 size_total_f = h.format_byte_size(size_root + size_hg)
48 48 sys.stdout.write('Repository size .hg:%s repo:%s total:%s\n' \
49 49 % (size_hg_f, size_root_f, size_total_f))
50 50
51 51 user_action_mapper(ui, repo, hooktype, **kwargs)
52 52
53 53 def user_action_mapper(ui, repo, hooktype=None, **kwargs):
54 54 """
55 55 Maps user last push action to new changeset id, from mercurial
56 @param ui:
57 @param repo:
58 @param hooktype:
56 :param ui:
57 :param repo:
58 :param hooktype:
59 59 """
60 60
61 61 try:
62 62 sa = meta.Session
63 63 username = kwargs['url'].split(':')[-1]
64 64 user_log = sa.query(UserLog)\
65 65 .filter(UserLog.user == sa.query(User)\
66 66 .filter(User.username == username).one())\
67 67 .order_by(UserLog.user_log_id.desc()).first()
68 68
69 69 if user_log and not user_log.revision:
70 70 user_log.revision = str(repo['tip'])
71 71 sa.add(user_log)
72 72 sa.commit()
73 73
74 74 except Exception, e:
75 75 sa.rollback()
76 76 raise
77 77 finally:
78 78 meta.Session.remove()
@@ -1,142 +1,142 b''
1 1 from os.path import dirname as dn, join as jn
2 2 from rhodecode.config.environment import load_environment
3 3 from rhodecode.model.hg_model import HgModel
4 4 from shutil import rmtree
5 5 from webhelpers.html.builder import escape
6 6 from vcs.utils.lazy import LazyProperty
7 7
8 8 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
9 9 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
10 10 from whoosh.index import create_in, open_dir
11 11 from whoosh.formats import Characters
12 12 from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter
13 13
14 14 import os
15 15 import sys
16 16 import traceback
17 17
18 18 #to get the rhodecode import
19 19 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
20 20
21 21
22 22 #LOCATION WE KEEP THE INDEX
23 23 IDX_LOCATION = jn(dn(dn(dn(dn(os.path.abspath(__file__))))), 'data', 'index')
24 24
25 25 #EXTENSIONS WE WANT TO INDEX CONTENT OFF
26 26 INDEX_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c',
27 27 'cfg', 'cfm', 'cpp', 'cs', 'css', 'diff', 'do', 'el', 'erl',
28 28 'h', 'htm', 'html', 'ini', 'java', 'js', 'jsp', 'jspx', 'lisp',
29 29 'lua', 'm', 'mako', 'ml', 'pas', 'patch', 'php', 'php3',
30 30 'php4', 'phtml', 'pm', 'py', 'rb', 'rst', 's', 'sh', 'sql',
31 31 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt',
32 32 'yaws']
33 33
34 34 #CUSTOM ANALYZER wordsplit + lowercase filter
35 35 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
36 36
37 37
38 38 #INDEX SCHEMA DEFINITION
39 39 SCHEMA = Schema(owner=TEXT(),
40 40 repository=TEXT(stored=True),
41 41 path=TEXT(stored=True),
42 42 content=FieldType(format=Characters(ANALYZER),
43 43 scorable=True, stored=True),
44 44 modtime=STORED(), extension=TEXT(stored=True))
45 45
46 46
47 47 IDX_NAME = 'HG_INDEX'
48 48 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
49 49 FRAGMENTER = SimpleFragmenter(200)
50 50
51 51 class ResultWrapper(object):
52 52 def __init__(self, search_type, searcher, matcher, highlight_items):
53 53 self.search_type = search_type
54 54 self.searcher = searcher
55 55 self.matcher = matcher
56 56 self.highlight_items = highlight_items
57 57 self.fragment_size = 200 / 2
58 58
59 59 @LazyProperty
60 60 def doc_ids(self):
61 61 docs_id = []
62 62 while self.matcher.is_active():
63 63 docnum = self.matcher.id()
64 64 chunks = [offsets for offsets in self.get_chunks()]
65 65 docs_id.append([docnum, chunks])
66 66 self.matcher.next()
67 67 return docs_id
68 68
69 69 def __str__(self):
70 70 return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
71 71
72 72 def __repr__(self):
73 73 return self.__str__()
74 74
75 75 def __len__(self):
76 76 return len(self.doc_ids)
77 77
78 78 def __iter__(self):
79 79 """
80 80 Allows Iteration over results,and lazy generate content
81 81
82 82 *Requires* implementation of ``__getitem__`` method.
83 83 """
84 84 for docid in self.doc_ids:
85 85 yield self.get_full_content(docid)
86 86
87 87 def __getslice__(self, i, j):
88 88 """
89 89 Slicing of resultWrapper
90 90 """
91 91 slice = []
92 92 for docid in self.doc_ids[i:j]:
93 93 slice.append(self.get_full_content(docid))
94 94 return slice
95 95
96 96
97 97 def get_full_content(self, docid):
98 98 res = self.searcher.stored_fields(docid[0])
99 99 f_path = res['path'][res['path'].find(res['repository']) \
100 100 + len(res['repository']):].lstrip('/')
101 101
102 102 content_short = self.get_short_content(res, docid[1])
103 103 res.update({'content_short':content_short,
104 104 'content_short_hl':self.highlight(content_short),
105 105 'f_path':f_path})
106 106
107 107 return res
108 108
109 109 def get_short_content(self, res, chunks):
110 110
111 111 return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
112 112
113 113 def get_chunks(self):
114 114 """
115 115 Smart function that implements chunking the content
116 116 but not overlap chunks so it doesn't highlight the same
117 117 close occurrences twice.
118 @param matcher:
119 @param size:
118 :param matcher:
119 :param size:
120 120 """
121 121 memory = [(0, 0)]
122 122 for span in self.matcher.spans():
123 123 start = span.startchar or 0
124 124 end = span.endchar or 0
125 125 start_offseted = max(0, start - self.fragment_size)
126 126 end_offseted = end + self.fragment_size
127 127
128 128 if start_offseted < memory[-1][1]:
129 129 start_offseted = memory[-1][1]
130 130 memory.append((start_offseted, end_offseted,))
131 131 yield (start_offseted, end_offseted,)
132 132
133 133 def highlight(self, content, top=5):
134 134 if self.search_type != 'content':
135 135 return ''
136 136 hl = highlight(escape(content),
137 137 self.highlight_items,
138 138 analyzer=ANALYZER,
139 139 fragmenter=FRAGMENTER,
140 140 formatter=FORMATTER,
141 141 top=top)
142 142 return hl
@@ -1,120 +1,120 b''
1 1 import os, time
2 2 import sys
3 3 from warnings import warn
4 4 from multiprocessing.util import Finalize
5 5 import errno
6 6
7 7 class LockHeld(Exception):pass
8 8
9 9
10 10 class DaemonLock(object):
11 11 """daemon locking
12 12 USAGE:
13 13 try:
14 14 l = DaemonLock(desc='test lock')
15 15 main()
16 16 l.release()
17 17 except LockHeld:
18 18 sys.exit(1)
19 19 """
20 20
21 21 def __init__(self, file=None, callbackfn=None,
22 22 desc='daemon lock', debug=False):
23 23
24 24 self.pidfile = file if file else os.path.join(os.path.dirname(__file__),
25 25 'running.lock')
26 26 self.callbackfn = callbackfn
27 27 self.desc = desc
28 28 self.debug = debug
29 29 self.held = False
30 30 #run the lock automatically !
31 31 self.lock()
32 32 self._finalize = Finalize(self, DaemonLock._on_finalize,
33 33 args=(self, debug), exitpriority=10)
34 34
35 35 @staticmethod
36 36 def _on_finalize(lock, debug):
37 37 if lock.held:
38 38 if debug:
39 39 print 'leck held finilazing and running lock.release()'
40 40 lock.release()
41 41
42 42
43 43 def lock(self):
44 44 """locking function, if lock is present it will raise LockHeld exception
45 45 """
46 46 lockname = '%s' % (os.getpid())
47 47 if self.debug:
48 48 print 'running lock'
49 49 self.trylock()
50 50 self.makelock(lockname, self.pidfile)
51 51 return True
52 52
53 53 def trylock(self):
54 54 running_pid = False
55 55 if self.debug:
56 56 print 'checking for already running process'
57 57 try:
58 58 pidfile = open(self.pidfile, "r")
59 59 pidfile.seek(0)
60 60 running_pid = int(pidfile.readline())
61 61
62 62 pidfile.close()
63 63
64 64 if self.debug:
65 65 print 'lock file present running_pid: %s, checking for execution'\
66 66 % running_pid
67 67 # Now we check the PID from lock file matches to the current
68 68 # process PID
69 69 if running_pid:
70 70 try:
71 71 os.kill(running_pid, 0)
72 72 except OSError, exc:
73 73 if exc.errno in (errno.ESRCH, errno.EPERM):
74 74 print "Lock File is there but the program is not running"
75 75 print "Removing lock file for the: %s" % running_pid
76 76 self.release()
77 77 else:
78 78 raise
79 79 else:
80 80 print "You already have an instance of the program running"
81 81 print "It is running as process %s" % running_pid
82 82 raise LockHeld()
83 83
84 84 except IOError, e:
85 85 if e.errno != 2:
86 86 raise
87 87
88 88 def release(self):
89 89 """releases the pid by removing the pidfile
90 90 """
91 91 if self.debug:
92 92 print 'trying to release the pidlock'
93 93
94 94 if self.callbackfn:
95 95 #execute callback function on release
96 96 if self.debug:
97 97 print 'executing callback function %s' % self.callbackfn
98 98 self.callbackfn()
99 99 try:
100 100 if self.debug:
101 101 print 'removing pidfile %s' % self.pidfile
102 102 os.remove(self.pidfile)
103 103 self.held = False
104 104 except OSError, e:
105 105 if self.debug:
106 106 print 'removing pidfile failed %s' % e
107 107 pass
108 108
109 109 def makelock(self, lockname, pidfile):
110 110 """
111 111 this function will make an actual lock
112 @param lockname: acctual pid of file
113 @param pidfile: the file to write the pid in
112 :param lockname: acctual pid of file
113 :param pidfile: the file to write the pid in
114 114 """
115 115 if self.debug:
116 116 print 'creating a file %s and pid: %s' % (pidfile, lockname)
117 117 pidfile = open(self.pidfile, "wb")
118 118 pidfile.write(lockname)
119 119 pidfile.close
120 120 self.held = True
@@ -1,118 +1,118 b''
1 1 import logging
2 2 import smtplib
3 3 import mimetypes
4 4 from email.mime.multipart import MIMEMultipart
5 5 from email.mime.image import MIMEImage
6 6 from email.mime.audio import MIMEAudio
7 7 from email.mime.base import MIMEBase
8 8 from email.mime.text import MIMEText
9 9 from email.utils import formatdate
10 10 from email import encoders
11 11
12 12 class SmtpMailer(object):
13 13 """simple smtp mailer class
14 14
15 15 mailer = SmtpMailer(mail_from, user, passwd, mail_server, mail_port, ssl, tls)
16 16 mailer.send(recipients, subject, body, attachment_files)
17 17
18 18 :param recipients might be a list of string or single string
19 19 :param attachment_files is a dict of {filename:location}
20 20 it tries to guess the mimetype and attach the file
21 21 """
22 22
23 23 def __init__(self, mail_from, user, passwd, mail_server,
24 24 mail_port=None, ssl=False, tls=False):
25 25
26 26 self.mail_from = mail_from
27 27 self.mail_server = mail_server
28 28 self.mail_port = mail_port
29 29 self.user = user
30 30 self.passwd = passwd
31 31 self.ssl = ssl
32 32 self.tls = tls
33 33 self.debug = False
34 34
35 35 def send(self, recipients=[], subject='', body='', attachment_files={}):
36 36
37 37 if isinstance(recipients, basestring):
38 38 recipients = [recipients]
39 39 if self.ssl:
40 40 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
41 41 else:
42 42 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
43 43
44 44 if self.tls:
45 45 smtp_serv.starttls()
46 46
47 47 if self.debug:
48 48 smtp_serv.set_debuglevel(1)
49 49
50 50 smtp_serv.ehlo("mailer")
51 51
52 52 #if server requires authorization you must provide login and password
53 53 smtp_serv.login(self.user, self.passwd)
54 54
55 55 date_ = formatdate(localtime=True)
56 56 msg = MIMEMultipart()
57 57 msg['From'] = self.mail_from
58 58 msg['To'] = ','.join(recipients)
59 59 msg['Date'] = date_
60 60 msg['Subject'] = subject
61 61 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
62 62
63 63 msg.attach(MIMEText(body))
64 64
65 65 if attachment_files:
66 66 self.__atach_files(msg, attachment_files)
67 67
68 68 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
69 69 logging.info('MAIL SEND TO: %s' % recipients)
70 70 smtp_serv.quit()
71 71
72 72
73 73 def __atach_files(self, msg, attachment_files):
74 74 if isinstance(attachment_files, dict):
75 75 for f_name, msg_file in attachment_files.items():
76 76 ctype, encoding = mimetypes.guess_type(f_name)
77 77 logging.info("guessing file %s type based on %s" , ctype, f_name)
78 78 if ctype is None or encoding is not None:
79 79 # No guess could be made, or the file is encoded (compressed), so
80 80 # use a generic bag-of-bits type.
81 81 ctype = 'application/octet-stream'
82 82 maintype, subtype = ctype.split('/', 1)
83 83 if maintype == 'text':
84 84 # Note: we should handle calculating the charset
85 85 file_part = MIMEText(self.get_content(msg_file),
86 86 _subtype=subtype)
87 87 elif maintype == 'image':
88 88 file_part = MIMEImage(self.get_content(msg_file),
89 89 _subtype=subtype)
90 90 elif maintype == 'audio':
91 91 file_part = MIMEAudio(self.get_content(msg_file),
92 92 _subtype=subtype)
93 93 else:
94 94 file_part = MIMEBase(maintype, subtype)
95 95 file_part.set_payload(self.get_content(msg_file))
96 96 # Encode the payload using Base64
97 97 encoders.encode_base64(msg)
98 98 # Set the filename parameter
99 99 file_part.add_header('Content-Disposition', 'attachment',
100 100 filename=f_name)
101 101 file_part.add_header('Content-Type', ctype, name=f_name)
102 102 msg.attach(file_part)
103 103 else:
104 104 raise Exception('Attachment files should be'
105 105 'a dict in format {"filename":"filepath"}')
106 106
107 107 def get_content(self, msg_file):
108 108 '''
109 109 Get content based on type, if content is a string do open first
110 110 else just read because it's a probably open file object
111 @param msg_file:
111 :param msg_file:
112 112 '''
113 113 if isinstance(msg_file, str):
114 114 return open(msg_file, "rb").read()
115 115 else:
116 116 #just for safe seek to 0
117 117 msg_file.seek(0)
118 118 return msg_file.read()
@@ -1,490 +1,502 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Utilities for RhodeCode
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19
20 20 """
21 21 Created on April 18, 2010
22 22 Utilities for RhodeCode
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui, config, hg
27 27 from mercurial.error import RepoError
28 28 from rhodecode.model import meta
29 29 from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, UserLog
30 30 from vcs.backends.base import BaseChangeset
31 31 from vcs.utils.lazy import LazyProperty
32 32 import logging
33 33 import datetime
34 34 import os
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 def get_repo_slug(request):
40 40 return request.environ['pylons.routes_dict'].get('repo_name')
41 41
42 42 def is_mercurial(environ):
43 43 """
44 44 Returns True if request's target is mercurial server - header
45 45 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
46 46 """
47 47 http_accept = environ.get('HTTP_ACCEPT')
48 48 if http_accept and http_accept.startswith('application/mercurial'):
49 49 return True
50 50 return False
51 51
52 def is_git(environ):
53 """
54 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
55 then have git client version given.
56
57 :param environ:
58 """
59 http_user_agent = environ.get('HTTP_USER_AGENT')
60 if http_user_agent.startswith('git'):
61 return True
62 return False
63
52 64 def action_logger(user, action, repo, ipaddr, sa=None):
53 65 """
54 66 Action logger for various action made by users
55 67 """
56 68
57 69 if not sa:
58 70 sa = meta.Session
59 71
60 72 try:
61 73 if hasattr(user, 'user_id'):
62 74 user_id = user.user_id
63 75 elif isinstance(user, basestring):
64 76 user_id = sa.query(User).filter(User.username == user).one()
65 77 else:
66 78 raise Exception('You have to provide user object or username')
67 79
68 80 repo_name = repo.lstrip('/')
69 81 user_log = UserLog()
70 82 user_log.user_id = user_id
71 83 user_log.action = action
72 84 user_log.repository_name = repo_name
73 85 user_log.repository = sa.query(Repository)\
74 86 .filter(Repository.repo_name == repo_name).one()
75 87 user_log.action_date = datetime.datetime.now()
76 88 user_log.user_ip = ipaddr
77 89 sa.add(user_log)
78 90 sa.commit()
79 91 log.info('Adding user %s, action %s on %s',
80 92 user.username, action, repo)
81 93 except Exception, e:
82 94 raise
83 95 sa.rollback()
84 96 log.error('could not log user action:%s', str(e))
85 97
86 98 def check_repo_dir(paths):
87 99 repos_path = paths[0][1].split('/')
88 100 if repos_path[-1] in ['*', '**']:
89 101 repos_path = repos_path[:-1]
90 102 if repos_path[0] != '/':
91 103 repos_path[0] = '/'
92 104 if not os.path.isdir(os.path.join(*repos_path)):
93 105 raise Exception('Not a valid repository in %s' % paths[0][1])
94 106
95 107 def check_repo_fast(repo_name, base_path):
96 108 if os.path.isdir(os.path.join(base_path, repo_name)):return False
97 109 return True
98 110
99 111 def check_repo(repo_name, base_path, verify=True):
100 112
101 113 repo_path = os.path.join(base_path, repo_name)
102 114
103 115 try:
104 116 if not check_repo_fast(repo_name, base_path):
105 117 return False
106 118 r = hg.repository(ui.ui(), repo_path)
107 119 if verify:
108 120 hg.verify(r)
109 121 #here we hnow that repo exists it was verified
110 122 log.info('%s repo is already created', repo_name)
111 123 return False
112 124 except RepoError:
113 125 #it means that there is no valid repo there...
114 126 log.info('%s repo is free for creation', repo_name)
115 127 return True
116 128
117 129 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
118 130 while True:
119 131 ok = raw_input(prompt)
120 132 if ok in ('y', 'ye', 'yes'): return True
121 133 if ok in ('n', 'no', 'nop', 'nope'): return False
122 134 retries = retries - 1
123 135 if retries < 0: raise IOError
124 136 print complaint
125 137
126 138 @cache_region('super_short_term', 'cached_hg_ui')
127 139 def get_hg_ui_cached():
128 140 try:
129 141 sa = meta.Session
130 142 ret = sa.query(RhodeCodeUi).all()
131 143 finally:
132 144 meta.Session.remove()
133 145 return ret
134 146
135 147
136 148 def get_hg_settings():
137 149 try:
138 150 sa = meta.Session
139 151 ret = sa.query(RhodeCodeSettings).all()
140 152 finally:
141 153 meta.Session.remove()
142 154
143 155 if not ret:
144 156 raise Exception('Could not get application settings !')
145 157 settings = {}
146 158 for each in ret:
147 159 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
148 160
149 161 return settings
150 162
151 163 def get_hg_ui_settings():
152 164 try:
153 165 sa = meta.Session
154 166 ret = sa.query(RhodeCodeUi).all()
155 167 finally:
156 168 meta.Session.remove()
157 169
158 170 if not ret:
159 171 raise Exception('Could not get application ui settings !')
160 172 settings = {}
161 173 for each in ret:
162 174 k = each.ui_key
163 175 v = each.ui_value
164 176 if k == '/':
165 177 k = 'root_path'
166 178
167 179 if k.find('.') != -1:
168 180 k = k.replace('.', '_')
169 181
170 182 if each.ui_section == 'hooks':
171 183 v = each.ui_active
172 184
173 185 settings[each.ui_section + '_' + k] = v
174 186
175 187 return settings
176 188
177 189 #propagated from mercurial documentation
178 190 ui_sections = ['alias', 'auth',
179 191 'decode/encode', 'defaults',
180 192 'diff', 'email',
181 193 'extensions', 'format',
182 194 'merge-patterns', 'merge-tools',
183 195 'hooks', 'http_proxy',
184 196 'smtp', 'patch',
185 197 'paths', 'profiling',
186 198 'server', 'trusted',
187 199 'ui', 'web', ]
188 200
189 201 def make_ui(read_from='file', path=None, checkpaths=True):
190 202 """
191 203 A function that will read python rc files or database
192 204 and make an mercurial ui object from read options
193 205
194 @param path: path to mercurial config file
195 @param checkpaths: check the path
196 @param read_from: read from 'file' or 'db'
206 :param path: path to mercurial config file
207 :param checkpaths: check the path
208 :param read_from: read from 'file' or 'db'
197 209 """
198 210
199 211 baseui = ui.ui()
200 212
201 213 if read_from == 'file':
202 214 if not os.path.isfile(path):
203 215 log.warning('Unable to read config file %s' % path)
204 216 return False
205 217 log.debug('reading hgrc from %s', path)
206 218 cfg = config.config()
207 219 cfg.read(path)
208 220 for section in ui_sections:
209 221 for k, v in cfg.items(section):
210 222 baseui.setconfig(section, k, v)
211 223 log.debug('settings ui from file[%s]%s:%s', section, k, v)
212 224 if checkpaths:check_repo_dir(cfg.items('paths'))
213 225
214 226
215 227 elif read_from == 'db':
216 228 hg_ui = get_hg_ui_cached()
217 229 for ui_ in hg_ui:
218 230 if ui_.ui_active:
219 231 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
220 232 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
221 233
222 234
223 235 return baseui
224 236
225 237
226 238 def set_rhodecode_config(config):
227 239 hgsettings = get_hg_settings()
228 240
229 241 for k, v in hgsettings.items():
230 242 config[k] = v
231 243
232 244 def invalidate_cache(name, *args):
233 245 """Invalidates given name cache"""
234 246
235 247 from beaker.cache import region_invalidate
236 248 log.info('INVALIDATING CACHE FOR %s', name)
237 249
238 250 """propagate our arguments to make sure invalidation works. First
239 251 argument has to be the name of cached func name give to cache decorator
240 252 without that the invalidation would not work"""
241 253 tmp = [name]
242 254 tmp.extend(args)
243 255 args = tuple(tmp)
244 256
245 257 if name == 'cached_repo_list':
246 258 from rhodecode.model.hg_model import _get_repos_cached
247 259 region_invalidate(_get_repos_cached, None, *args)
248 260
249 261 if name == 'full_changelog':
250 262 from rhodecode.model.hg_model import _full_changelog_cached
251 263 region_invalidate(_full_changelog_cached, None, *args)
252 264
253 265 class EmptyChangeset(BaseChangeset):
254 266 """
255 267 An dummy empty changeset.
256 268 """
257 269
258 270 revision = -1
259 271 message = ''
260 272 author = ''
261 273 date = ''
262 274 @LazyProperty
263 275 def raw_id(self):
264 276 """
265 277 Returns raw string identifing this changeset, useful for web
266 278 representation.
267 279 """
268 280 return '0' * 40
269 281
270 282 @LazyProperty
271 283 def short_id(self):
272 284 return self.raw_id[:12]
273 285
274 286 def get_file_changeset(self, path):
275 287 return self
276 288
277 289 def get_file_content(self, path):
278 290 return u''
279 291
280 292 def get_file_size(self, path):
281 293 return 0
282 294
283 295 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
284 296 """
285 297 maps all found repositories into db
286 298 """
287 299 from rhodecode.model.repo_model import RepoModel
288 300
289 301 sa = meta.Session
290 302 user = sa.query(User).filter(User.admin == True).first()
291 303
292 304 rm = RepoModel()
293 305
294 306 for name, repo in initial_repo_list.items():
295 307 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
296 308 log.info('repository %s not found creating default', name)
297 309
298 310 form_data = {
299 311 'repo_name':name,
300 312 'description':repo.description if repo.description != 'unknown' else \
301 313 'auto description for %s' % name,
302 314 'private':False
303 315 }
304 316 rm.create(form_data, user, just_db=True)
305 317
306 318
307 319 if remove_obsolete:
308 320 #remove from database those repositories that are not in the filesystem
309 321 for repo in sa.query(Repository).all():
310 322 if repo.repo_name not in initial_repo_list.keys():
311 323 sa.delete(repo)
312 324 sa.commit()
313 325
314 326
315 327 meta.Session.remove()
316 328
317 329 from UserDict import DictMixin
318 330
319 331 class OrderedDict(dict, DictMixin):
320 332
321 333 def __init__(self, *args, **kwds):
322 334 if len(args) > 1:
323 335 raise TypeError('expected at most 1 arguments, got %d' % len(args))
324 336 try:
325 337 self.__end
326 338 except AttributeError:
327 339 self.clear()
328 340 self.update(*args, **kwds)
329 341
330 342 def clear(self):
331 343 self.__end = end = []
332 344 end += [None, end, end] # sentinel node for doubly linked list
333 345 self.__map = {} # key --> [key, prev, next]
334 346 dict.clear(self)
335 347
336 348 def __setitem__(self, key, value):
337 349 if key not in self:
338 350 end = self.__end
339 351 curr = end[1]
340 352 curr[2] = end[1] = self.__map[key] = [key, curr, end]
341 353 dict.__setitem__(self, key, value)
342 354
343 355 def __delitem__(self, key):
344 356 dict.__delitem__(self, key)
345 357 key, prev, next = self.__map.pop(key)
346 358 prev[2] = next
347 359 next[1] = prev
348 360
349 361 def __iter__(self):
350 362 end = self.__end
351 363 curr = end[2]
352 364 while curr is not end:
353 365 yield curr[0]
354 366 curr = curr[2]
355 367
356 368 def __reversed__(self):
357 369 end = self.__end
358 370 curr = end[1]
359 371 while curr is not end:
360 372 yield curr[0]
361 373 curr = curr[1]
362 374
363 375 def popitem(self, last=True):
364 376 if not self:
365 377 raise KeyError('dictionary is empty')
366 378 if last:
367 379 key = reversed(self).next()
368 380 else:
369 381 key = iter(self).next()
370 382 value = self.pop(key)
371 383 return key, value
372 384
373 385 def __reduce__(self):
374 386 items = [[k, self[k]] for k in self]
375 387 tmp = self.__map, self.__end
376 388 del self.__map, self.__end
377 389 inst_dict = vars(self).copy()
378 390 self.__map, self.__end = tmp
379 391 if inst_dict:
380 392 return (self.__class__, (items,), inst_dict)
381 393 return self.__class__, (items,)
382 394
383 395 def keys(self):
384 396 return list(self)
385 397
386 398 setdefault = DictMixin.setdefault
387 399 update = DictMixin.update
388 400 pop = DictMixin.pop
389 401 values = DictMixin.values
390 402 items = DictMixin.items
391 403 iterkeys = DictMixin.iterkeys
392 404 itervalues = DictMixin.itervalues
393 405 iteritems = DictMixin.iteritems
394 406
395 407 def __repr__(self):
396 408 if not self:
397 409 return '%s()' % (self.__class__.__name__,)
398 410 return '%s(%r)' % (self.__class__.__name__, self.items())
399 411
400 412 def copy(self):
401 413 return self.__class__(self)
402 414
403 415 @classmethod
404 416 def fromkeys(cls, iterable, value=None):
405 417 d = cls()
406 418 for key in iterable:
407 419 d[key] = value
408 420 return d
409 421
410 422 def __eq__(self, other):
411 423 if isinstance(other, OrderedDict):
412 424 return len(self) == len(other) and self.items() == other.items()
413 425 return dict.__eq__(self, other)
414 426
415 427 def __ne__(self, other):
416 428 return not self == other
417 429
418 430
419 431 #===============================================================================
420 432 # TEST FUNCTIONS
421 433 #===============================================================================
422 434 def create_test_index(repo_location, full_index):
423 435 """Makes default test index
424 @param repo_location:
425 @param full_index:
436 :param repo_location:
437 :param full_index:
426 438 """
427 439 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
428 440 from rhodecode.lib.pidlock import DaemonLock, LockHeld
429 441 from rhodecode.lib.indexers import IDX_LOCATION
430 442 import shutil
431 443
432 444 if os.path.exists(IDX_LOCATION):
433 445 shutil.rmtree(IDX_LOCATION)
434 446
435 447 try:
436 448 l = DaemonLock()
437 449 WhooshIndexingDaemon(repo_location=repo_location)\
438 450 .run(full_index=full_index)
439 451 l.release()
440 452 except LockHeld:
441 453 pass
442 454
443 455 def create_test_env(repos_test_path, config):
444 456 """Makes a fresh database and
445 457 install test repository into tmp dir
446 458 """
447 459 from rhodecode.lib.db_manage import DbManage
448 460 import tarfile
449 461 import shutil
450 462 from os.path import dirname as dn, join as jn, abspath
451 463
452 464 log = logging.getLogger('TestEnvCreator')
453 465 # create logger
454 466 log.setLevel(logging.DEBUG)
455 467 log.propagate = True
456 468 # create console handler and set level to debug
457 469 ch = logging.StreamHandler()
458 470 ch.setLevel(logging.DEBUG)
459 471
460 472 # create formatter
461 473 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
462 474
463 475 # add formatter to ch
464 476 ch.setFormatter(formatter)
465 477
466 478 # add ch to logger
467 479 log.addHandler(ch)
468 480
469 481 #PART ONE create db
470 482 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
471 483 log.debug('making test db %s', dbname)
472 484
473 485 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
474 486 tests=True)
475 487 dbmanage.create_tables(override=True)
476 488 dbmanage.config_prompt(repos_test_path)
477 489 dbmanage.create_default_user()
478 490 dbmanage.admin_prompt()
479 491 dbmanage.create_permissions()
480 492 dbmanage.populate_default_permissions()
481 493
482 494 #PART TWO make test repo
483 495 log.debug('making test vcs repo')
484 496 if os.path.isdir('/tmp/vcs_test'):
485 497 shutil.rmtree('/tmp/vcs_test')
486 498
487 499 cur_dir = dn(dn(abspath(__file__)))
488 500 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz"))
489 501 tar.extractall('/tmp')
490 502 tar.close()
General Comments 0
You need to be logged in to leave comments. Login now