##// END OF EJS Templates
Added full last changeset info to lightweight dashboard
marcink -
r3147:8182ebed beta
parent child Browse files
Show More
@@ -1,125 +1,133 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.home
3 rhodecode.controllers.home
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Home controller for Rhodecode
6 Home controller for Rhodecode
7
7
8 :created_on: Feb 18, 2010
8 :created_on: Feb 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 logging
26 import logging
27
27
28 from pylons import tmpl_context as c, request
28 from pylons import tmpl_context as c, request
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from webob.exc import HTTPBadRequest
30 from webob.exc import HTTPBadRequest
31
31
32 import rhodecode
32 import rhodecode
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.auth import LoginRequired
35 from rhodecode.lib.auth import LoginRequired
36 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.base import BaseController, render
37 from rhodecode.model.db import Repository
37 from rhodecode.model.db import Repository
38 from sqlalchemy.sql.expression import func
38 from sqlalchemy.sql.expression import func
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class HomeController(BaseController):
43 class HomeController(BaseController):
44
44
45 @LoginRequired()
45 @LoginRequired()
46 def __before__(self):
46 def __before__(self):
47 super(HomeController, self).__before__()
47 super(HomeController, self).__before__()
48
48
49 def index(self):
49 def index(self):
50 c.groups = self.scm_model.get_repos_groups()
50 c.groups = self.scm_model.get_repos_groups()
51 c.group = None
51 c.group = None
52
52
53 if c.visual.lightweight_dashboard is False:
53 if c.visual.lightweight_dashboard is False:
54 c.repos_list = self.scm_model.get_repos()
54 c.repos_list = self.scm_model.get_repos()
55 ## lightweight version of dashboard
55 ## lightweight version of dashboard
56 else:
56 else:
57 c.repos_list = Repository.query()\
57 c.repos_list = Repository.query()\
58 .filter(Repository.group_id == None)\
58 .filter(Repository.group_id == None)\
59 .order_by(func.lower(Repository.repo_name))\
59 .order_by(func.lower(Repository.repo_name))\
60 .all()
60 .all()
61 repos_data = []
61 repos_data = []
62 total_records = len(c.repos_list)
62 total_records = len(c.repos_list)
63
63
64 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
64 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
65 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
65 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
66
66
67 quick_menu = lambda repo_name: (template.get_def("quick_menu")
67 quick_menu = lambda repo_name: (template.get_def("quick_menu")
68 .render(repo_name, _=_, h=h, c=c))
68 .render(repo_name, _=_, h=h, c=c))
69 repo_lnk = lambda name, rtype, private, fork_of: (
69 repo_lnk = lambda name, rtype, private, fork_of: (
70 template.get_def("repo_name")
70 template.get_def("repo_name")
71 .render(name, rtype, private, fork_of, short_name=False,
71 .render(name, rtype, private, fork_of, short_name=False,
72 admin=False, _=_, h=h, c=c))
72 admin=False, _=_, h=h, c=c))
73 last_change = lambda last_change: (template.get_def("last_change")
73 last_change = lambda last_change: (template.get_def("last_change")
74 .render(last_change, _=_, h=h, c=c))
74 .render(last_change, _=_, h=h, c=c))
75 rss_lnk = lambda repo_name: (template.get_def("rss")
75 rss_lnk = lambda repo_name: (template.get_def("rss")
76 .render(repo_name, _=_, h=h, c=c))
76 .render(repo_name, _=_, h=h, c=c))
77 atom_lnk = lambda repo_name: (template.get_def("atom")
77 atom_lnk = lambda repo_name: (template.get_def("atom")
78 .render(repo_name, _=_, h=h, c=c))
78 .render(repo_name, _=_, h=h, c=c))
79 tip = lambda repo_name, cs_cache: (template.get_def("revision")
80 .render(repo_name,
81 cs_cache.get('revision'),
82 cs_cache.get('raw_id'),
83 cs_cache.get('author'),
84 cs_cache.get('message'), _=_, h=h,
85 c=c))
79
86
80 def desc(desc):
87 def desc(desc):
81 if c.visual.stylify_metatags:
88 if c.visual.stylify_metatags:
82 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
89 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
83 else:
90 else:
84 return h.urlify_text(h.truncate(desc, 60))
91 return h.urlify_text(h.truncate(desc, 60))
85
92
86 for repo in c.repos_list:
93 for repo in c.repos_list:
87 repos_data.append({
94 repos_data.append({
88 "menu": quick_menu(repo.repo_name),
95 "menu": quick_menu(repo.repo_name),
89 "raw_name": repo.repo_name.lower(),
96 "raw_name": repo.repo_name.lower(),
90 "name": repo_lnk(repo.repo_name, repo.repo_type,
97 "name": repo_lnk(repo.repo_name, repo.repo_type,
91 repo.private, repo.fork),
98 repo.private, repo.fork),
92 "last_change": last_change(repo.last_db_change),
99 "last_change": last_change(repo.last_db_change),
100 "tip": tip(repo.repo_name, repo.changeset_cache),
93 "desc": desc(repo.description),
101 "desc": desc(repo.description),
94 "owner": h.person(repo.user.username),
102 "owner": h.person(repo.user.username),
95 "rss": rss_lnk(repo.repo_name),
103 "rss": rss_lnk(repo.repo_name),
96 "atom": atom_lnk(repo.repo_name),
104 "atom": atom_lnk(repo.repo_name),
97 })
105 })
98
106
99 c.data = json.dumps({
107 c.data = json.dumps({
100 "totalRecords": total_records,
108 "totalRecords": total_records,
101 "startIndex": 0,
109 "startIndex": 0,
102 "sort": "name",
110 "sort": "name",
103 "dir": "asc",
111 "dir": "asc",
104 "records": repos_data
112 "records": repos_data
105 })
113 })
106
114
107 return render('/index.html')
115 return render('/index.html')
108
116
109 def repo_switcher(self):
117 def repo_switcher(self):
110 if request.is_xhr:
118 if request.is_xhr:
111 all_repos = Repository.query().order_by(Repository.repo_name).all()
119 all_repos = Repository.query().order_by(Repository.repo_name).all()
112 c.repos_list = self.scm_model.get_repos(all_repos,
120 c.repos_list = self.scm_model.get_repos(all_repos,
113 sort_key='name_sort',
121 sort_key='name_sort',
114 simple=True)
122 simple=True)
115 return render('/repo_switcher_list.html')
123 return render('/repo_switcher_list.html')
116 else:
124 else:
117 raise HTTPBadRequest()
125 raise HTTPBadRequest()
118
126
119 def branch_tag_switcher(self, repo_name):
127 def branch_tag_switcher(self, repo_name):
120 if request.is_xhr:
128 if request.is_xhr:
121 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
129 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
122 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
130 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
123 return render('/switch_to_list.html')
131 return render('/switch_to_list.html')
124 else:
132 else:
125 raise HTTPBadRequest()
133 raise HTTPBadRequest()
@@ -1,332 +1,332 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 import logging
5 import logging
6 import time
6 import time
7 import traceback
7 import traceback
8
8
9 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
12
12
13 from pylons import config, tmpl_context as c, request, session, url
13 from pylons import config, tmpl_context as c, request, session, url
14 from pylons.controllers import WSGIController
14 from pylons.controllers import WSGIController
15 from pylons.controllers.util import redirect
15 from pylons.controllers.util import redirect
16 from pylons.templating import render_mako as render
16 from pylons.templating import render_mako as render
17
17
18 from rhodecode import __version__, BACKENDS
18 from rhodecode import __version__, BACKENDS
19
19
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
21 safe_str, safe_int
21 safe_str, safe_int
22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 HasPermissionAnyMiddleware, CookieStoreWrapper
23 HasPermissionAnyMiddleware, CookieStoreWrapper
24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
25 from rhodecode.model import meta
25 from rhodecode.model import meta
26
26
27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
28 from rhodecode.model.notification import NotificationModel
28 from rhodecode.model.notification import NotificationModel
29 from rhodecode.model.scm import ScmModel
29 from rhodecode.model.scm import ScmModel
30 from rhodecode.model.meta import Session
30 from rhodecode.model.meta import Session
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 def _get_ip_addr(environ):
35 def _get_ip_addr(environ):
36 proxy_key = 'HTTP_X_REAL_IP'
36 proxy_key = 'HTTP_X_REAL_IP'
37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
38 def_key = 'REMOTE_ADDR'
38 def_key = 'REMOTE_ADDR'
39
39
40 ip = environ.get(proxy_key2)
40 ip = environ.get(proxy_key2)
41 if ip:
41 if ip:
42 return ip
42 return ip
43
43
44 ip = environ.get(proxy_key)
44 ip = environ.get(proxy_key)
45
45
46 if ip:
46 if ip:
47 return ip
47 return ip
48
48
49 ip = environ.get(def_key, '0.0.0.0')
49 ip = environ.get(def_key, '0.0.0.0')
50 return ip
50 return ip
51
51
52
52
53 def _get_access_path(environ):
53 def _get_access_path(environ):
54 path = environ.get('PATH_INFO')
54 path = environ.get('PATH_INFO')
55 org_req = environ.get('pylons.original_request')
55 org_req = environ.get('pylons.original_request')
56 if org_req:
56 if org_req:
57 path = org_req.environ.get('PATH_INFO')
57 path = org_req.environ.get('PATH_INFO')
58 return path
58 return path
59
59
60
60
61 class BasicAuth(AuthBasicAuthenticator):
61 class BasicAuth(AuthBasicAuthenticator):
62
62
63 def __init__(self, realm, authfunc, auth_http_code=None):
63 def __init__(self, realm, authfunc, auth_http_code=None):
64 self.realm = realm
64 self.realm = realm
65 self.authfunc = authfunc
65 self.authfunc = authfunc
66 self._rc_auth_http_code = auth_http_code
66 self._rc_auth_http_code = auth_http_code
67
67
68 def build_authentication(self):
68 def build_authentication(self):
69 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
69 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
70 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
70 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
71 # return 403 if alternative http return code is specified in
71 # return 403 if alternative http return code is specified in
72 # RhodeCode config
72 # RhodeCode config
73 return HTTPForbidden(headers=head)
73 return HTTPForbidden(headers=head)
74 return HTTPUnauthorized(headers=head)
74 return HTTPUnauthorized(headers=head)
75
75
76 def authenticate(self, environ):
76 def authenticate(self, environ):
77 authorization = AUTHORIZATION(environ)
77 authorization = AUTHORIZATION(environ)
78 if not authorization:
78 if not authorization:
79 return self.build_authentication()
79 return self.build_authentication()
80 (authmeth, auth) = authorization.split(' ', 1)
80 (authmeth, auth) = authorization.split(' ', 1)
81 if 'basic' != authmeth.lower():
81 if 'basic' != authmeth.lower():
82 return self.build_authentication()
82 return self.build_authentication()
83 auth = auth.strip().decode('base64')
83 auth = auth.strip().decode('base64')
84 _parts = auth.split(':', 1)
84 _parts = auth.split(':', 1)
85 if len(_parts) == 2:
85 if len(_parts) == 2:
86 username, password = _parts
86 username, password = _parts
87 if self.authfunc(environ, username, password):
87 if self.authfunc(environ, username, password):
88 return username
88 return username
89 return self.build_authentication()
89 return self.build_authentication()
90
90
91 __call__ = authenticate
91 __call__ = authenticate
92
92
93
93
94 class BaseVCSController(object):
94 class BaseVCSController(object):
95
95
96 def __init__(self, application, config):
96 def __init__(self, application, config):
97 self.application = application
97 self.application = application
98 self.config = config
98 self.config = config
99 # base path of repo locations
99 # base path of repo locations
100 self.basepath = self.config['base_path']
100 self.basepath = self.config['base_path']
101 #authenticate this mercurial request using authfunc
101 #authenticate this mercurial request using authfunc
102 self.authenticate = BasicAuth('', authfunc,
102 self.authenticate = BasicAuth('', authfunc,
103 config.get('auth_ret_code'))
103 config.get('auth_ret_code'))
104 self.ip_addr = '0.0.0.0'
104 self.ip_addr = '0.0.0.0'
105
105
106 def _handle_request(self, environ, start_response):
106 def _handle_request(self, environ, start_response):
107 raise NotImplementedError()
107 raise NotImplementedError()
108
108
109 def _get_by_id(self, repo_name):
109 def _get_by_id(self, repo_name):
110 """
110 """
111 Get's a special pattern _<ID> from clone url and tries to replace it
111 Get's a special pattern _<ID> from clone url and tries to replace it
112 with a repository_name for support of _<ID> non changable urls
112 with a repository_name for support of _<ID> non changable urls
113
113
114 :param repo_name:
114 :param repo_name:
115 """
115 """
116 try:
116 try:
117 data = repo_name.split('/')
117 data = repo_name.split('/')
118 if len(data) >= 2:
118 if len(data) >= 2:
119 by_id = data[1].split('_')
119 by_id = data[1].split('_')
120 if len(by_id) == 2 and by_id[1].isdigit():
120 if len(by_id) == 2 and by_id[1].isdigit():
121 _repo_name = Repository.get(by_id[1]).repo_name
121 _repo_name = Repository.get(by_id[1]).repo_name
122 data[1] = _repo_name
122 data[1] = _repo_name
123 except:
123 except:
124 log.debug('Failed to extract repo_name from id %s' % (
124 log.debug('Failed to extract repo_name from id %s' % (
125 traceback.format_exc()
125 traceback.format_exc()
126 )
126 )
127 )
127 )
128
128
129 return '/'.join(data)
129 return '/'.join(data)
130
130
131 def _invalidate_cache(self, repo_name):
131 def _invalidate_cache(self, repo_name):
132 """
132 """
133 Set's cache for this repository for invalidation on next access
133 Set's cache for this repository for invalidation on next access
134
134
135 :param repo_name: full repo name, also a cache key
135 :param repo_name: full repo name, also a cache key
136 """
136 """
137 invalidate_cache('get_repo_cached_%s' % repo_name)
137 invalidate_cache('get_repo_cached_%s' % repo_name)
138
138
139 def _check_permission(self, action, user, repo_name, ip_addr=None):
139 def _check_permission(self, action, user, repo_name, ip_addr=None):
140 """
140 """
141 Checks permissions using action (push/pull) user and repository
141 Checks permissions using action (push/pull) user and repository
142 name
142 name
143
143
144 :param action: push or pull action
144 :param action: push or pull action
145 :param user: user instance
145 :param user: user instance
146 :param repo_name: repository name
146 :param repo_name: repository name
147 """
147 """
148 #check IP
148 #check IP
149 authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
149 authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
150 if not authuser.ip_allowed:
150 if not authuser.ip_allowed:
151 return False
151 return False
152 else:
152 else:
153 log.info('Access for IP:%s allowed' % (ip_addr))
153 log.info('Access for IP:%s allowed' % (ip_addr))
154 if action == 'push':
154 if action == 'push':
155 if not HasPermissionAnyMiddleware('repository.write',
155 if not HasPermissionAnyMiddleware('repository.write',
156 'repository.admin')(user,
156 'repository.admin')(user,
157 repo_name):
157 repo_name):
158 return False
158 return False
159
159
160 else:
160 else:
161 #any other action need at least read permission
161 #any other action need at least read permission
162 if not HasPermissionAnyMiddleware('repository.read',
162 if not HasPermissionAnyMiddleware('repository.read',
163 'repository.write',
163 'repository.write',
164 'repository.admin')(user,
164 'repository.admin')(user,
165 repo_name):
165 repo_name):
166 return False
166 return False
167
167
168 return True
168 return True
169
169
170 def _get_ip_addr(self, environ):
170 def _get_ip_addr(self, environ):
171 return _get_ip_addr(environ)
171 return _get_ip_addr(environ)
172
172
173 def _check_ssl(self, environ, start_response):
173 def _check_ssl(self, environ, start_response):
174 """
174 """
175 Checks the SSL check flag and returns False if SSL is not present
175 Checks the SSL check flag and returns False if SSL is not present
176 and required True otherwise
176 and required True otherwise
177 """
177 """
178 org_proto = environ['wsgi._org_proto']
178 org_proto = environ['wsgi._org_proto']
179 #check if we have SSL required ! if not it's a bad request !
179 #check if we have SSL required ! if not it's a bad request !
180 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
180 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
181 if require_ssl and org_proto == 'http':
181 if require_ssl and org_proto == 'http':
182 log.debug('proto is %s and SSL is required BAD REQUEST !'
182 log.debug('proto is %s and SSL is required BAD REQUEST !'
183 % org_proto)
183 % org_proto)
184 return False
184 return False
185 return True
185 return True
186
186
187 def _check_locking_state(self, environ, action, repo, user_id):
187 def _check_locking_state(self, environ, action, repo, user_id):
188 """
188 """
189 Checks locking on this repository, if locking is enabled and lock is
189 Checks locking on this repository, if locking is enabled and lock is
190 present returns a tuple of make_lock, locked, locked_by.
190 present returns a tuple of make_lock, locked, locked_by.
191 make_lock can have 3 states None (do nothing) True, make lock
191 make_lock can have 3 states None (do nothing) True, make lock
192 False release lock, This value is later propagated to hooks, which
192 False release lock, This value is later propagated to hooks, which
193 do the locking. Think about this as signals passed to hooks what to do.
193 do the locking. Think about this as signals passed to hooks what to do.
194
194
195 """
195 """
196 locked = False # defines that locked error should be thrown to user
196 locked = False # defines that locked error should be thrown to user
197 make_lock = None
197 make_lock = None
198 repo = Repository.get_by_repo_name(repo)
198 repo = Repository.get_by_repo_name(repo)
199 user = User.get(user_id)
199 user = User.get(user_id)
200
200
201 # this is kind of hacky, but due to how mercurial handles client-server
201 # this is kind of hacky, but due to how mercurial handles client-server
202 # server see all operation on changeset; bookmarks, phases and
202 # server see all operation on changeset; bookmarks, phases and
203 # obsolescence marker in different transaction, we don't want to check
203 # obsolescence marker in different transaction, we don't want to check
204 # locking on those
204 # locking on those
205 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
205 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
206 locked_by = repo.locked
206 locked_by = repo.locked
207 if repo and repo.enable_locking and not obsolete_call:
207 if repo and repo.enable_locking and not obsolete_call:
208 if action == 'push':
208 if action == 'push':
209 #check if it's already locked !, if it is compare users
209 #check if it's already locked !, if it is compare users
210 user_id, _date = repo.locked
210 user_id, _date = repo.locked
211 if user.user_id == user_id:
211 if user.user_id == user_id:
212 log.debug('Got push from user %s, now unlocking' % (user))
212 log.debug('Got push from user %s, now unlocking' % (user))
213 # unlock if we have push from user who locked
213 # unlock if we have push from user who locked
214 make_lock = False
214 make_lock = False
215 else:
215 else:
216 # we're not the same user who locked, ban with 423 !
216 # we're not the same user who locked, ban with 423 !
217 locked = True
217 locked = True
218 if action == 'pull':
218 if action == 'pull':
219 if repo.locked[0] and repo.locked[1]:
219 if repo.locked[0] and repo.locked[1]:
220 locked = True
220 locked = True
221 else:
221 else:
222 log.debug('Setting lock on repo %s by %s' % (repo, user))
222 log.debug('Setting lock on repo %s by %s' % (repo, user))
223 make_lock = True
223 make_lock = True
224
224
225 else:
225 else:
226 log.debug('Repository %s do not have locking enabled' % (repo))
226 log.debug('Repository %s do not have locking enabled' % (repo))
227 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
227 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
228 % (make_lock, locked, locked_by))
228 % (make_lock, locked, locked_by))
229 return make_lock, locked, locked_by
229 return make_lock, locked, locked_by
230
230
231 def __call__(self, environ, start_response):
231 def __call__(self, environ, start_response):
232 start = time.time()
232 start = time.time()
233 try:
233 try:
234 return self._handle_request(environ, start_response)
234 return self._handle_request(environ, start_response)
235 finally:
235 finally:
236 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
236 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
237 log.debug('Request time: %.3fs' % (time.time() - start))
237 log.debug('Request time: %.3fs' % (time.time() - start))
238 meta.Session.remove()
238 meta.Session.remove()
239
239
240
240
241 class BaseController(WSGIController):
241 class BaseController(WSGIController):
242
242
243 def __before__(self):
243 def __before__(self):
244 """
244 """
245 __before__ is called before controller methods and after __call__
245 __before__ is called before controller methods and after __call__
246 """
246 """
247 c.rhodecode_version = __version__
247 c.rhodecode_version = __version__
248 c.rhodecode_instanceid = config.get('instance_id')
248 c.rhodecode_instanceid = config.get('instance_id')
249 c.rhodecode_name = config.get('rhodecode_title')
249 c.rhodecode_name = config.get('rhodecode_title')
250 c.use_gravatar = str2bool(config.get('use_gravatar'))
250 c.use_gravatar = str2bool(config.get('use_gravatar'))
251 c.ga_code = config.get('rhodecode_ga_code')
251 c.ga_code = config.get('rhodecode_ga_code')
252 # Visual options
252 # Visual options
253 c.visual = AttributeDict({})
253 c.visual = AttributeDict({})
254 rc_config = RhodeCodeSetting.get_app_settings()
254 rc_config = RhodeCodeSetting.get_app_settings()
255
255
256 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
256 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
257 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
257 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
258 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
258 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
259 c.visual.lightweight_dashboard = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
259 c.visual.lightweight_dashboard = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
260 c.visual.lightweight_dashboard_items = safe_int(config.get('dashboard_items', 100))
260 c.visual.lightweight_dashboard_items = safe_int(config.get('dashboard_items', 100))
261
261
262 c.repo_name = get_repo_slug(request)
262 c.repo_name = get_repo_slug(request)
263 c.backends = BACKENDS.keys()
263 c.backends = BACKENDS.keys()
264 c.unread_notifications = NotificationModel()\
264 c.unread_notifications = NotificationModel()\
265 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
265 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
266 self.cut_off_limit = int(config.get('cut_off_limit'))
266 self.cut_off_limit = int(config.get('cut_off_limit'))
267
267
268 self.sa = meta.Session
268 self.sa = meta.Session
269 self.scm_model = ScmModel(self.sa)
269 self.scm_model = ScmModel(self.sa)
270
270
271 def __call__(self, environ, start_response):
271 def __call__(self, environ, start_response):
272 """Invoke the Controller"""
272 """Invoke the Controller"""
273 # WSGIController.__call__ dispatches to the Controller method
273 # WSGIController.__call__ dispatches to the Controller method
274 # the request is routed to. This routing information is
274 # the request is routed to. This routing information is
275 # available in environ['pylons.routes_dict']
275 # available in environ['pylons.routes_dict']
276 start = time.time()
276 start = time.time()
277 try:
277 try:
278 self.ip_addr = _get_ip_addr(environ)
278 self.ip_addr = _get_ip_addr(environ)
279 # make sure that we update permissions each time we call controller
279 # make sure that we update permissions each time we call controller
280 api_key = request.GET.get('api_key')
280 api_key = request.GET.get('api_key')
281 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
281 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
282 user_id = cookie_store.get('user_id', None)
282 user_id = cookie_store.get('user_id', None)
283 username = get_container_username(environ, config)
283 username = get_container_username(environ, config)
284 auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
284 auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
285 request.user = auth_user
285 request.user = auth_user
286 self.rhodecode_user = c.rhodecode_user = auth_user
286 self.rhodecode_user = c.rhodecode_user = auth_user
287 if not self.rhodecode_user.is_authenticated and \
287 if not self.rhodecode_user.is_authenticated and \
288 self.rhodecode_user.user_id is not None:
288 self.rhodecode_user.user_id is not None:
289 self.rhodecode_user.set_authenticated(
289 self.rhodecode_user.set_authenticated(
290 cookie_store.get('is_authenticated')
290 cookie_store.get('is_authenticated')
291 )
291 )
292 log.info('IP: %s User: %s accessed %s' % (
292 log.info('IP: %s User: %s accessed %s' % (
293 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
293 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
294 )
294 )
295 return WSGIController.__call__(self, environ, start_response)
295 return WSGIController.__call__(self, environ, start_response)
296 finally:
296 finally:
297 log.info('IP: %s Request to %s time: %.3fs' % (
297 log.info('IP: %s Request to %s time: %.3fs' % (
298 _get_ip_addr(environ),
298 _get_ip_addr(environ),
299 safe_unicode(_get_access_path(environ)), time.time() - start)
299 safe_unicode(_get_access_path(environ)), time.time() - start)
300 )
300 )
301 meta.Session.remove()
301 meta.Session.remove()
302
302
303
303
304 class BaseRepoController(BaseController):
304 class BaseRepoController(BaseController):
305 """
305 """
306 Base class for controllers responsible for loading all needed data for
306 Base class for controllers responsible for loading all needed data for
307 repository loaded items are
307 repository loaded items are
308
308
309 c.rhodecode_repo: instance of scm repository
309 c.rhodecode_repo: instance of scm repository
310 c.rhodecode_db_repo: instance of db
310 c.rhodecode_db_repo: instance of db
311 c.repository_followers: number of followers
311 c.repository_followers: number of followers
312 c.repository_forks: number of forks
312 c.repository_forks: number of forks
313 """
313 """
314
314
315 def __before__(self):
315 def __before__(self):
316 super(BaseRepoController, self).__before__()
316 super(BaseRepoController, self).__before__()
317 if c.repo_name:
317 if c.repo_name:
318
318
319 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
319 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
320 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
320 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
321 # update last change according to VCS data
321 # update last change according to VCS data
322 dbr.update_last_change(c.rhodecode_repo.last_change)
322 dbr.update_changeset_cache(dbr.get_changeset())
323 if c.rhodecode_repo is None:
323 if c.rhodecode_repo is None:
324 log.error('%s this repository is present in database but it '
324 log.error('%s this repository is present in database but it '
325 'cannot be created as an scm instance', c.repo_name)
325 'cannot be created as an scm instance', c.repo_name)
326
326
327 redirect(url('home'))
327 redirect(url('home'))
328
328
329 # some globals counter for menu
329 # some globals counter for menu
330 c.repository_followers = self.scm_model.get_followers(dbr)
330 c.repository_followers = self.scm_model.get_followers(dbr)
331 c.repository_forks = self.scm_model.get_forks(dbr)
331 c.repository_forks = self.scm_model.get_forks(dbr)
332 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
332 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
@@ -1,34 +1,51 b''
1 import logging
1 import logging
2 import datetime
2 import datetime
3
3
4 from sqlalchemy import *
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
7 from sqlalchemy.orm.session import Session
7 from sqlalchemy.orm.session import Session
8 from sqlalchemy.ext.declarative import declarative_base
8 from sqlalchemy.ext.declarative import declarative_base
9
9
10 from rhodecode.lib.dbmigrate.migrate import *
10 from rhodecode.lib.dbmigrate.migrate import *
11 from rhodecode.lib.dbmigrate.migrate.changeset import *
11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12
12
13 from rhodecode.model.meta import Base
13 from rhodecode.model.meta import Base
14 from rhodecode.model import meta
14 from rhodecode.model import meta
15
15
16 log = logging.getLogger(__name__)
16 log = logging.getLogger(__name__)
17
17
18
18
19 def upgrade(migrate_engine):
19 def upgrade(migrate_engine):
20 """
20 """
21 Upgrade operations go here.
21 Upgrade operations go here.
22 Don't create your own engine; bind migrate_engine to your metadata
22 Don't create your own engine; bind migrate_engine to your metadata
23 """
23 """
24 #==========================================================================
24 #==========================================================================
25 # USER LOGS
25 # USER LOGS
26 #==========================================================================
26 #==========================================================================
27 from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserIpMap
27 from rhodecode.lib.dbmigrate.schema.db_1_5_0 import UserIpMap
28 tbl = UserIpMap.__table__
28 tbl = UserIpMap.__table__
29 tbl.create()
29 tbl.create()
30
30
31 #==========================================================================
32 # REPOSITORIES
33 #==========================================================================
34 from rhodecode.lib.dbmigrate.schema.db_1_5_0 import Repository
35 tbl = Repository.__table__
36 changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True)
37 # create username column
38 changeset_cache.create(table=tbl)
39
40 #fix cache data
41 _Session = Session()
42 ## after adding that column fix all usernames
43 repositories = _Session.query(Repository).all()
44 for entry in repositories:
45 entry.update_changeset_cache()
46 _Session.commit()
47
31
48
32 def downgrade(migrate_engine):
49 def downgrade(migrate_engine):
33 meta = MetaData()
50 meta = MetaData()
34 meta.bind = migrate_engine
51 meta.bind = migrate_engine
@@ -1,86 +1,87 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 package.rhodecode.lib.cleanup
3 package.rhodecode.lib.cleanup
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 :created_on: Jul 14, 2012
6 :created_on: Jul 14, 2012
7 :author: marcink
7 :author: marcink
8 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
8 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
9 :license: GPLv3, see COPYING for more details.
9 :license: GPLv3, see COPYING for more details.
10 """
10 """
11 # This program is free software: you can redistribute it and/or modify
11 # This program is free software: you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation, either version 3 of the License, or
13 # the Free Software Foundation, either version 3 of the License, or
14 # (at your option) any later version.
14 # (at your option) any later version.
15 #
15 #
16 # This program is distributed in the hope that it will be useful,
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
19 # GNU General Public License for more details.
20 #
20 #
21 # You should have received a copy of the GNU General Public License
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 from __future__ import with_statement
23 from __future__ import with_statement
24
24
25 import os
25 import os
26 import sys
26 import sys
27 import re
27 import re
28 import shutil
28 import shutil
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import string
31 import string
32
32
33 from os.path import dirname as dn, join as jn
33 from os.path import dirname as dn, join as jn
34 from rhodecode.model import init_model
34 from rhodecode.model import init_model
35 from rhodecode.lib.utils2 import engine_from_config, safe_str
35 from rhodecode.lib.utils2 import engine_from_config, safe_str
36 from rhodecode.model.db import RhodeCodeUi, Repository
36 from rhodecode.model.db import RhodeCodeUi, Repository
37 from rhodecode.lib.vcs.backends.base import EmptyChangeset
37
38
38
39
39 #to get the rhodecode import
40 #to get the rhodecode import
40 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
41 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
41
42
42 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
43 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
43
44
44 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
45
46
46
47
47 class UpdateCommand(BasePasterCommand):
48 class UpdateCommand(BasePasterCommand):
48
49
49 max_args = 1
50 max_args = 1
50 min_args = 1
51 min_args = 1
51
52
52 usage = "CONFIG_FILE"
53 usage = "CONFIG_FILE"
53 summary = "Cleanup deleted repos"
54 summary = "Cleanup deleted repos"
54 group_name = "RhodeCode"
55 group_name = "RhodeCode"
55 takes_config_file = -1
56 takes_config_file = -1
56 parser = Command.standard_parser(verbose=True)
57 parser = Command.standard_parser(verbose=True)
57
58
58 def command(self):
59 def command(self):
59 logging.config.fileConfig(self.path_to_ini_file)
60 logging.config.fileConfig(self.path_to_ini_file)
60 from pylons import config
61 from pylons import config
61
62
62 #get to remove repos !!
63 #get to remove repos !!
63 add_cache(config)
64 add_cache(config)
64 engine = engine_from_config(config, 'sqlalchemy.db1.')
65 engine = engine_from_config(config, 'sqlalchemy.db1.')
65 init_model(engine)
66 init_model(engine)
66
67
67 repo_update_list = map(string.strip,
68 repo_update_list = map(string.strip,
68 self.options.repo_update_list.split(',')) \
69 self.options.repo_update_list.split(',')) \
69 if self.options.repo_update_list else None
70 if self.options.repo_update_list else None
70
71
71 if repo_update_list:
72 if repo_update_list:
72 repo_list = Repository.query().filter(Repository.repo_name.in_(repo_update_list))
73 repo_list = Repository.query().filter(Repository.repo_name.in_(repo_update_list))
73 else:
74 else:
74 repo_list = Repository.getAll()
75 repo_list = Repository.getAll()
75 for repo in repo_list:
76 for repo in repo_list:
76 last_change = (repo.scm_instance.last_change if repo.scm_instance
77 last_cs = (repo.scm_instance.get_changeset() if repo.scm_instance
77 else datetime.datetime.utcfromtimestamp(0))
78 else EmptyChangeset())
78 repo.update_last_change(last_change)
79 repo.update_changeset_cache(last_cs)
79
80
80 def update_parser(self):
81 def update_parser(self):
81 self.parser.add_option('--update-only',
82 self.parser.add_option('--update-only',
82 action='store',
83 action='store',
83 dest='repo_update_list',
84 dest='repo_update_list',
84 help="Specifies a comma separated list of repositores "
85 help="Specifies a comma separated list of repositores "
85 "to update last commit info for. OPTIONAL",
86 "to update last commit info for. OPTIONAL",
86 )
87 )
@@ -1,753 +1,754 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 import decorator
35 import decorator
36 import warnings
36 import warnings
37 from os.path import abspath
37 from os.path import abspath
38 from os.path import dirname as dn, join as jn
38 from os.path import dirname as dn, join as jn
39
39
40 from paste.script.command import Command, BadCommand
40 from paste.script.command import Command, BadCommand
41
41
42 from mercurial import ui, config
42 from mercurial import ui, config
43
43
44 from webhelpers.text import collapse, remove_formatting, strip_tags
44 from webhelpers.text import collapse, remove_formatting, strip_tags
45
45
46 from rhodecode.lib.vcs import get_backend
46 from rhodecode.lib.vcs import get_backend
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 from rhodecode.lib.vcs.utils.helpers import get_scm
49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.exceptions import VCSError
51
51
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model import meta
54 from rhodecode.model import meta
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 from rhodecode.model.meta import Session
57 from rhodecode.model.meta import Session
58 from rhodecode.model.repos_group import ReposGroupModel
58 from rhodecode.model.repos_group import ReposGroupModel
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 from rhodecode.lib.vcs.utils.fakemod import create_module
60 from rhodecode.lib.vcs.utils.fakemod import create_module
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65
65
66
66
67 def recursive_replace(str_, replace=' '):
67 def recursive_replace(str_, replace=' '):
68 """
68 """
69 Recursive replace of given sign to just one instance
69 Recursive replace of given sign to just one instance
70
70
71 :param str_: given string
71 :param str_: given string
72 :param replace: char to find and replace multiple instances
72 :param replace: char to find and replace multiple instances
73
73
74 Examples::
74 Examples::
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 'Mighty-Mighty-Bo-sstones'
76 'Mighty-Mighty-Bo-sstones'
77 """
77 """
78
78
79 if str_.find(replace * 2) == -1:
79 if str_.find(replace * 2) == -1:
80 return str_
80 return str_
81 else:
81 else:
82 str_ = str_.replace(replace * 2, replace)
82 str_ = str_.replace(replace * 2, replace)
83 return recursive_replace(str_, replace)
83 return recursive_replace(str_, replace)
84
84
85
85
86 def repo_name_slug(value):
86 def repo_name_slug(value):
87 """
87 """
88 Return slug of name of repository
88 Return slug of name of repository
89 This function is called on each creation/modification
89 This function is called on each creation/modification
90 of repository to prevent bad names in repo
90 of repository to prevent bad names in repo
91 """
91 """
92
92
93 slug = remove_formatting(value)
93 slug = remove_formatting(value)
94 slug = strip_tags(slug)
94 slug = strip_tags(slug)
95
95
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 slug = slug.replace(c, '-')
97 slug = slug.replace(c, '-')
98 slug = recursive_replace(slug, '-')
98 slug = recursive_replace(slug, '-')
99 slug = collapse(slug, '-')
99 slug = collapse(slug, '-')
100 return slug
100 return slug
101
101
102
102
103 def get_repo_slug(request):
103 def get_repo_slug(request):
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 if _repo:
105 if _repo:
106 _repo = _repo.rstrip('/')
106 _repo = _repo.rstrip('/')
107 return _repo
107 return _repo
108
108
109
109
110 def get_repos_group_slug(request):
110 def get_repos_group_slug(request):
111 _group = request.environ['pylons.routes_dict'].get('group_name')
111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 if _group:
112 if _group:
113 _group = _group.rstrip('/')
113 _group = _group.rstrip('/')
114 return _group
114 return _group
115
115
116
116
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 """
118 """
119 Action logger for various actions made by users
119 Action logger for various actions made by users
120
120
121 :param user: user that made this action, can be a unique username string or
121 :param user: user that made this action, can be a unique username string or
122 object containing user_id attribute
122 object containing user_id attribute
123 :param action: action to log, should be on of predefined unique actions for
123 :param action: action to log, should be on of predefined unique actions for
124 easy translations
124 easy translations
125 :param repo: string name of repository or object containing repo_id,
125 :param repo: string name of repository or object containing repo_id,
126 that action was made on
126 that action was made on
127 :param ipaddr: optional ip address from what the action was made
127 :param ipaddr: optional ip address from what the action was made
128 :param sa: optional sqlalchemy session
128 :param sa: optional sqlalchemy session
129
129
130 """
130 """
131
131
132 if not sa:
132 if not sa:
133 sa = meta.Session()
133 sa = meta.Session()
134
134
135 try:
135 try:
136 if hasattr(user, 'user_id'):
136 if hasattr(user, 'user_id'):
137 user_obj = User.get(user.user_id)
137 user_obj = User.get(user.user_id)
138 elif isinstance(user, basestring):
138 elif isinstance(user, basestring):
139 user_obj = User.get_by_username(user)
139 user_obj = User.get_by_username(user)
140 else:
140 else:
141 raise Exception('You have to provide a user object or a username')
141 raise Exception('You have to provide a user object or a username')
142
142
143 if hasattr(repo, 'repo_id'):
143 if hasattr(repo, 'repo_id'):
144 repo_obj = Repository.get(repo.repo_id)
144 repo_obj = Repository.get(repo.repo_id)
145 repo_name = repo_obj.repo_name
145 repo_name = repo_obj.repo_name
146 elif isinstance(repo, basestring):
146 elif isinstance(repo, basestring):
147 repo_name = repo.lstrip('/')
147 repo_name = repo.lstrip('/')
148 repo_obj = Repository.get_by_repo_name(repo_name)
148 repo_obj = Repository.get_by_repo_name(repo_name)
149 else:
149 else:
150 repo_obj = None
150 repo_obj = None
151 repo_name = ''
151 repo_name = ''
152
152
153 user_log = UserLog()
153 user_log = UserLog()
154 user_log.user_id = user_obj.user_id
154 user_log.user_id = user_obj.user_id
155 user_log.username = user_obj.username
155 user_log.username = user_obj.username
156 user_log.action = safe_unicode(action)
156 user_log.action = safe_unicode(action)
157
157
158 user_log.repository = repo_obj
158 user_log.repository = repo_obj
159 user_log.repository_name = repo_name
159 user_log.repository_name = repo_name
160
160
161 user_log.action_date = datetime.datetime.now()
161 user_log.action_date = datetime.datetime.now()
162 user_log.user_ip = ipaddr
162 user_log.user_ip = ipaddr
163 sa.add(user_log)
163 sa.add(user_log)
164
164
165 log.info('Logging action %s on %s by %s' %
165 log.info('Logging action %s on %s by %s' %
166 (action, safe_unicode(repo), user_obj))
166 (action, safe_unicode(repo), user_obj))
167 if commit:
167 if commit:
168 sa.commit()
168 sa.commit()
169 except:
169 except:
170 log.error(traceback.format_exc())
170 log.error(traceback.format_exc())
171 raise
171 raise
172
172
173
173
174 def get_repos(path, recursive=False):
174 def get_repos(path, recursive=False):
175 """
175 """
176 Scans given path for repos and return (name,(type,path)) tuple
176 Scans given path for repos and return (name,(type,path)) tuple
177
177
178 :param path: path to scan for repositories
178 :param path: path to scan for repositories
179 :param recursive: recursive search and return names with subdirs in front
179 :param recursive: recursive search and return names with subdirs in front
180 """
180 """
181
181
182 # remove ending slash for better results
182 # remove ending slash for better results
183 path = path.rstrip(os.sep)
183 path = path.rstrip(os.sep)
184
184
185 def _get_repos(p):
185 def _get_repos(p):
186 if not os.access(p, os.W_OK):
186 if not os.access(p, os.W_OK):
187 return
187 return
188 for dirpath in os.listdir(p):
188 for dirpath in os.listdir(p):
189 if os.path.isfile(os.path.join(p, dirpath)):
189 if os.path.isfile(os.path.join(p, dirpath)):
190 continue
190 continue
191 cur_path = os.path.join(p, dirpath)
191 cur_path = os.path.join(p, dirpath)
192 try:
192 try:
193 scm_info = get_scm(cur_path)
193 scm_info = get_scm(cur_path)
194 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
195 except VCSError:
195 except VCSError:
196 if not recursive:
196 if not recursive:
197 continue
197 continue
198 #check if this dir containts other repos for recursive scan
198 #check if this dir containts other repos for recursive scan
199 rec_path = os.path.join(p, dirpath)
199 rec_path = os.path.join(p, dirpath)
200 if os.path.isdir(rec_path):
200 if os.path.isdir(rec_path):
201 for inner_scm in _get_repos(rec_path):
201 for inner_scm in _get_repos(rec_path):
202 yield inner_scm
202 yield inner_scm
203
203
204 return _get_repos(path)
204 return _get_repos(path)
205
205
206
206
207 def is_valid_repo(repo_name, base_path, scm=None):
207 def is_valid_repo(repo_name, base_path, scm=None):
208 """
208 """
209 Returns True if given path is a valid repository False otherwise.
209 Returns True if given path is a valid repository False otherwise.
210 If scm param is given also compare if given scm is the same as expected
210 If scm param is given also compare if given scm is the same as expected
211 from scm parameter
211 from scm parameter
212
212
213 :param repo_name:
213 :param repo_name:
214 :param base_path:
214 :param base_path:
215 :param scm:
215 :param scm:
216
216
217 :return True: if given path is a valid repository
217 :return True: if given path is a valid repository
218 """
218 """
219 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
219 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
220
220
221 try:
221 try:
222 scm_ = get_scm(full_path)
222 scm_ = get_scm(full_path)
223 if scm:
223 if scm:
224 return scm_[0] == scm
224 return scm_[0] == scm
225 return True
225 return True
226 except VCSError:
226 except VCSError:
227 return False
227 return False
228
228
229
229
230 def is_valid_repos_group(repos_group_name, base_path):
230 def is_valid_repos_group(repos_group_name, base_path):
231 """
231 """
232 Returns True if given path is a repos group False otherwise
232 Returns True if given path is a repos group False otherwise
233
233
234 :param repo_name:
234 :param repo_name:
235 :param base_path:
235 :param base_path:
236 """
236 """
237 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
237 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
238
238
239 # check if it's not a repo
239 # check if it's not a repo
240 if is_valid_repo(repos_group_name, base_path):
240 if is_valid_repo(repos_group_name, base_path):
241 return False
241 return False
242
242
243 try:
243 try:
244 # we need to check bare git repos at higher level
244 # we need to check bare git repos at higher level
245 # since we might match branches/hooks/info/objects or possible
245 # since we might match branches/hooks/info/objects or possible
246 # other things inside bare git repo
246 # other things inside bare git repo
247 get_scm(os.path.dirname(full_path))
247 get_scm(os.path.dirname(full_path))
248 return False
248 return False
249 except VCSError:
249 except VCSError:
250 pass
250 pass
251
251
252 # check if it's a valid path
252 # check if it's a valid path
253 if os.path.isdir(full_path):
253 if os.path.isdir(full_path):
254 return True
254 return True
255
255
256 return False
256 return False
257
257
258
258
259 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
259 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
260 while True:
260 while True:
261 ok = raw_input(prompt)
261 ok = raw_input(prompt)
262 if ok in ('y', 'ye', 'yes'):
262 if ok in ('y', 'ye', 'yes'):
263 return True
263 return True
264 if ok in ('n', 'no', 'nop', 'nope'):
264 if ok in ('n', 'no', 'nop', 'nope'):
265 return False
265 return False
266 retries = retries - 1
266 retries = retries - 1
267 if retries < 0:
267 if retries < 0:
268 raise IOError
268 raise IOError
269 print complaint
269 print complaint
270
270
271 #propagated from mercurial documentation
271 #propagated from mercurial documentation
272 ui_sections = ['alias', 'auth',
272 ui_sections = ['alias', 'auth',
273 'decode/encode', 'defaults',
273 'decode/encode', 'defaults',
274 'diff', 'email',
274 'diff', 'email',
275 'extensions', 'format',
275 'extensions', 'format',
276 'merge-patterns', 'merge-tools',
276 'merge-patterns', 'merge-tools',
277 'hooks', 'http_proxy',
277 'hooks', 'http_proxy',
278 'smtp', 'patch',
278 'smtp', 'patch',
279 'paths', 'profiling',
279 'paths', 'profiling',
280 'server', 'trusted',
280 'server', 'trusted',
281 'ui', 'web', ]
281 'ui', 'web', ]
282
282
283
283
284 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
284 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
285 """
285 """
286 A function that will read python rc files or database
286 A function that will read python rc files or database
287 and make an mercurial ui object from read options
287 and make an mercurial ui object from read options
288
288
289 :param path: path to mercurial config file
289 :param path: path to mercurial config file
290 :param checkpaths: check the path
290 :param checkpaths: check the path
291 :param read_from: read from 'file' or 'db'
291 :param read_from: read from 'file' or 'db'
292 """
292 """
293
293
294 baseui = ui.ui()
294 baseui = ui.ui()
295
295
296 # clean the baseui object
296 # clean the baseui object
297 baseui._ocfg = config.config()
297 baseui._ocfg = config.config()
298 baseui._ucfg = config.config()
298 baseui._ucfg = config.config()
299 baseui._tcfg = config.config()
299 baseui._tcfg = config.config()
300
300
301 if read_from == 'file':
301 if read_from == 'file':
302 if not os.path.isfile(path):
302 if not os.path.isfile(path):
303 log.debug('hgrc file is not present at %s, skipping...' % path)
303 log.debug('hgrc file is not present at %s, skipping...' % path)
304 return False
304 return False
305 log.debug('reading hgrc from %s' % path)
305 log.debug('reading hgrc from %s' % path)
306 cfg = config.config()
306 cfg = config.config()
307 cfg.read(path)
307 cfg.read(path)
308 for section in ui_sections:
308 for section in ui_sections:
309 for k, v in cfg.items(section):
309 for k, v in cfg.items(section):
310 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
310 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
311 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
311 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
312
312
313 elif read_from == 'db':
313 elif read_from == 'db':
314 sa = meta.Session()
314 sa = meta.Session()
315 ret = sa.query(RhodeCodeUi)\
315 ret = sa.query(RhodeCodeUi)\
316 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
316 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
317 .all()
317 .all()
318
318
319 hg_ui = ret
319 hg_ui = ret
320 for ui_ in hg_ui:
320 for ui_ in hg_ui:
321 if ui_.ui_active:
321 if ui_.ui_active:
322 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
322 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
323 ui_.ui_key, ui_.ui_value)
323 ui_.ui_key, ui_.ui_value)
324 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
324 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
325 safe_str(ui_.ui_value))
325 safe_str(ui_.ui_value))
326 if ui_.ui_key == 'push_ssl':
326 if ui_.ui_key == 'push_ssl':
327 # force set push_ssl requirement to False, rhodecode
327 # force set push_ssl requirement to False, rhodecode
328 # handles that
328 # handles that
329 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
329 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
330 False)
330 False)
331 if clear_session:
331 if clear_session:
332 meta.Session.remove()
332 meta.Session.remove()
333 return baseui
333 return baseui
334
334
335
335
336 def set_rhodecode_config(config):
336 def set_rhodecode_config(config):
337 """
337 """
338 Updates pylons config with new settings from database
338 Updates pylons config with new settings from database
339
339
340 :param config:
340 :param config:
341 """
341 """
342 hgsettings = RhodeCodeSetting.get_app_settings()
342 hgsettings = RhodeCodeSetting.get_app_settings()
343
343
344 for k, v in hgsettings.items():
344 for k, v in hgsettings.items():
345 config[k] = v
345 config[k] = v
346
346
347
347
348 def invalidate_cache(cache_key, *args):
348 def invalidate_cache(cache_key, *args):
349 """
349 """
350 Puts cache invalidation task into db for
350 Puts cache invalidation task into db for
351 further global cache invalidation
351 further global cache invalidation
352 """
352 """
353
353
354 from rhodecode.model.scm import ScmModel
354 from rhodecode.model.scm import ScmModel
355
355
356 if cache_key.startswith('get_repo_cached_'):
356 if cache_key.startswith('get_repo_cached_'):
357 name = cache_key.split('get_repo_cached_')[-1]
357 name = cache_key.split('get_repo_cached_')[-1]
358 ScmModel().mark_for_invalidation(name)
358 ScmModel().mark_for_invalidation(name)
359
359
360
360
361 def map_groups(path):
361 def map_groups(path):
362 """
362 """
363 Given a full path to a repository, create all nested groups that this
363 Given a full path to a repository, create all nested groups that this
364 repo is inside. This function creates parent-child relationships between
364 repo is inside. This function creates parent-child relationships between
365 groups and creates default perms for all new groups.
365 groups and creates default perms for all new groups.
366
366
367 :param paths: full path to repository
367 :param paths: full path to repository
368 """
368 """
369 sa = meta.Session()
369 sa = meta.Session()
370 groups = path.split(Repository.url_sep())
370 groups = path.split(Repository.url_sep())
371 parent = None
371 parent = None
372 group = None
372 group = None
373
373
374 # last element is repo in nested groups structure
374 # last element is repo in nested groups structure
375 groups = groups[:-1]
375 groups = groups[:-1]
376 rgm = ReposGroupModel(sa)
376 rgm = ReposGroupModel(sa)
377 for lvl, group_name in enumerate(groups):
377 for lvl, group_name in enumerate(groups):
378 group_name = '/'.join(groups[:lvl] + [group_name])
378 group_name = '/'.join(groups[:lvl] + [group_name])
379 group = RepoGroup.get_by_group_name(group_name)
379 group = RepoGroup.get_by_group_name(group_name)
380 desc = '%s group' % group_name
380 desc = '%s group' % group_name
381
381
382 # skip folders that are now removed repos
382 # skip folders that are now removed repos
383 if REMOVED_REPO_PAT.match(group_name):
383 if REMOVED_REPO_PAT.match(group_name):
384 break
384 break
385
385
386 if group is None:
386 if group is None:
387 log.debug('creating group level: %s group_name: %s' % (lvl,
387 log.debug('creating group level: %s group_name: %s' % (lvl,
388 group_name))
388 group_name))
389 group = RepoGroup(group_name, parent)
389 group = RepoGroup(group_name, parent)
390 group.group_description = desc
390 group.group_description = desc
391 sa.add(group)
391 sa.add(group)
392 rgm._create_default_perms(group)
392 rgm._create_default_perms(group)
393 sa.flush()
393 sa.flush()
394 parent = group
394 parent = group
395 return group
395 return group
396
396
397
397
398 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
398 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
399 install_git_hook=False):
399 install_git_hook=False):
400 """
400 """
401 maps all repos given in initial_repo_list, non existing repositories
401 maps all repos given in initial_repo_list, non existing repositories
402 are created, if remove_obsolete is True it also check for db entries
402 are created, if remove_obsolete is True it also check for db entries
403 that are not in initial_repo_list and removes them.
403 that are not in initial_repo_list and removes them.
404
404
405 :param initial_repo_list: list of repositories found by scanning methods
405 :param initial_repo_list: list of repositories found by scanning methods
406 :param remove_obsolete: check for obsolete entries in database
406 :param remove_obsolete: check for obsolete entries in database
407 :param install_git_hook: if this is True, also check and install githook
407 :param install_git_hook: if this is True, also check and install githook
408 for a repo if missing
408 for a repo if missing
409 """
409 """
410 from rhodecode.model.repo import RepoModel
410 from rhodecode.model.repo import RepoModel
411 from rhodecode.model.scm import ScmModel
411 from rhodecode.model.scm import ScmModel
412 sa = meta.Session()
412 sa = meta.Session()
413 rm = RepoModel()
413 rm = RepoModel()
414 user = sa.query(User).filter(User.admin == True).first()
414 user = sa.query(User).filter(User.admin == True).first()
415 if user is None:
415 if user is None:
416 raise Exception('Missing administrative account!')
416 raise Exception('Missing administrative account!')
417 added = []
417 added = []
418
418
419 # # clear cache keys
419 # # clear cache keys
420 # log.debug("Clearing cache keys now...")
420 # log.debug("Clearing cache keys now...")
421 # CacheInvalidation.clear_cache()
421 # CacheInvalidation.clear_cache()
422 # sa.commit()
422 # sa.commit()
423
423
424 ##creation defaults
424 ##creation defaults
425 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
425 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
426 enable_statistics = defs.get('repo_enable_statistics')
426 enable_statistics = defs.get('repo_enable_statistics')
427 enable_locking = defs.get('repo_enable_locking')
427 enable_locking = defs.get('repo_enable_locking')
428 enable_downloads = defs.get('repo_enable_downloads')
428 enable_downloads = defs.get('repo_enable_downloads')
429 private = defs.get('repo_private')
429 private = defs.get('repo_private')
430
430
431 for name, repo in initial_repo_list.items():
431 for name, repo in initial_repo_list.items():
432 group = map_groups(name)
432 group = map_groups(name)
433 db_repo = rm.get_by_repo_name(name)
433 db_repo = rm.get_by_repo_name(name)
434 # found repo that is on filesystem not in RhodeCode database
434 # found repo that is on filesystem not in RhodeCode database
435 if not db_repo:
435 if not db_repo:
436 log.info('repository %s not found, creating now' % name)
436 log.info('repository %s not found, creating now' % name)
437 added.append(name)
437 added.append(name)
438 desc = (repo.description
438 desc = (repo.description
439 if repo.description != 'unknown'
439 if repo.description != 'unknown'
440 else '%s repository' % name)
440 else '%s repository' % name)
441
441
442 new_repo = rm.create_repo(
442 new_repo = rm.create_repo(
443 repo_name=name,
443 repo_name=name,
444 repo_type=repo.alias,
444 repo_type=repo.alias,
445 description=desc,
445 description=desc,
446 repos_group=getattr(group, 'group_id', None),
446 repos_group=getattr(group, 'group_id', None),
447 owner=user,
447 owner=user,
448 just_db=True,
448 just_db=True,
449 enable_locking=enable_locking,
449 enable_locking=enable_locking,
450 enable_downloads=enable_downloads,
450 enable_downloads=enable_downloads,
451 enable_statistics=enable_statistics,
451 enable_statistics=enable_statistics,
452 private=private
452 private=private
453 )
453 )
454 # we added that repo just now, and make sure it has githook
454 # we added that repo just now, and make sure it has githook
455 # installed
455 # installed
456 if new_repo.repo_type == 'git':
456 if new_repo.repo_type == 'git':
457 ScmModel().install_git_hook(new_repo.scm_instance)
457 ScmModel().install_git_hook(new_repo.scm_instance)
458 new_repo.update_changeset_cache()
458 elif install_git_hook:
459 elif install_git_hook:
459 if db_repo.repo_type == 'git':
460 if db_repo.repo_type == 'git':
460 ScmModel().install_git_hook(db_repo.scm_instance)
461 ScmModel().install_git_hook(db_repo.scm_instance)
461 # during starting install all cache keys for all repositories in the
462 # during starting install all cache keys for all repositories in the
462 # system, this will register all repos and multiple instances
463 # system, this will register all repos and multiple instances
463 key, _prefix, _org_key = CacheInvalidation._get_key(name)
464 key, _prefix, _org_key = CacheInvalidation._get_key(name)
464 CacheInvalidation.invalidate(name)
465 CacheInvalidation.invalidate(name)
465 log.debug("Creating a cache key for %s, instance_id %s"
466 log.debug("Creating a cache key for %s, instance_id %s"
466 % (name, _prefix or 'unknown'))
467 % (name, _prefix or 'unknown'))
467
468
468 sa.commit()
469 sa.commit()
469 removed = []
470 removed = []
470 if remove_obsolete:
471 if remove_obsolete:
471 # remove from database those repositories that are not in the filesystem
472 # remove from database those repositories that are not in the filesystem
472 for repo in sa.query(Repository).all():
473 for repo in sa.query(Repository).all():
473 if repo.repo_name not in initial_repo_list.keys():
474 if repo.repo_name not in initial_repo_list.keys():
474 log.debug("Removing non-existing repository found in db `%s`" %
475 log.debug("Removing non-existing repository found in db `%s`" %
475 repo.repo_name)
476 repo.repo_name)
476 try:
477 try:
477 sa.delete(repo)
478 sa.delete(repo)
478 sa.commit()
479 sa.commit()
479 removed.append(repo.repo_name)
480 removed.append(repo.repo_name)
480 except:
481 except:
481 #don't hold further removals on error
482 #don't hold further removals on error
482 log.error(traceback.format_exc())
483 log.error(traceback.format_exc())
483 sa.rollback()
484 sa.rollback()
484
485
485 return added, removed
486 return added, removed
486
487
487
488
488 # set cache regions for beaker so celery can utilise it
489 # set cache regions for beaker so celery can utilise it
489 def add_cache(settings):
490 def add_cache(settings):
490 cache_settings = {'regions': None}
491 cache_settings = {'regions': None}
491 for key in settings.keys():
492 for key in settings.keys():
492 for prefix in ['beaker.cache.', 'cache.']:
493 for prefix in ['beaker.cache.', 'cache.']:
493 if key.startswith(prefix):
494 if key.startswith(prefix):
494 name = key.split(prefix)[1].strip()
495 name = key.split(prefix)[1].strip()
495 cache_settings[name] = settings[key].strip()
496 cache_settings[name] = settings[key].strip()
496 if cache_settings['regions']:
497 if cache_settings['regions']:
497 for region in cache_settings['regions'].split(','):
498 for region in cache_settings['regions'].split(','):
498 region = region.strip()
499 region = region.strip()
499 region_settings = {}
500 region_settings = {}
500 for key, value in cache_settings.items():
501 for key, value in cache_settings.items():
501 if key.startswith(region):
502 if key.startswith(region):
502 region_settings[key.split('.')[1]] = value
503 region_settings[key.split('.')[1]] = value
503 region_settings['expire'] = int(region_settings.get('expire',
504 region_settings['expire'] = int(region_settings.get('expire',
504 60))
505 60))
505 region_settings.setdefault('lock_dir',
506 region_settings.setdefault('lock_dir',
506 cache_settings.get('lock_dir'))
507 cache_settings.get('lock_dir'))
507 region_settings.setdefault('data_dir',
508 region_settings.setdefault('data_dir',
508 cache_settings.get('data_dir'))
509 cache_settings.get('data_dir'))
509
510
510 if 'type' not in region_settings:
511 if 'type' not in region_settings:
511 region_settings['type'] = cache_settings.get('type',
512 region_settings['type'] = cache_settings.get('type',
512 'memory')
513 'memory')
513 beaker.cache.cache_regions[region] = region_settings
514 beaker.cache.cache_regions[region] = region_settings
514
515
515
516
516 def load_rcextensions(root_path):
517 def load_rcextensions(root_path):
517 import rhodecode
518 import rhodecode
518 from rhodecode.config import conf
519 from rhodecode.config import conf
519
520
520 path = os.path.join(root_path, 'rcextensions', '__init__.py')
521 path = os.path.join(root_path, 'rcextensions', '__init__.py')
521 if os.path.isfile(path):
522 if os.path.isfile(path):
522 rcext = create_module('rc', path)
523 rcext = create_module('rc', path)
523 EXT = rhodecode.EXTENSIONS = rcext
524 EXT = rhodecode.EXTENSIONS = rcext
524 log.debug('Found rcextensions now loading %s...' % rcext)
525 log.debug('Found rcextensions now loading %s...' % rcext)
525
526
526 # Additional mappings that are not present in the pygments lexers
527 # Additional mappings that are not present in the pygments lexers
527 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
528 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
528
529
529 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
530 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
530
531
531 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
532 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
532 log.debug('settings custom INDEX_EXTENSIONS')
533 log.debug('settings custom INDEX_EXTENSIONS')
533 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
534 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
534
535
535 #ADDITIONAL MAPPINGS
536 #ADDITIONAL MAPPINGS
536 log.debug('adding extra into INDEX_EXTENSIONS')
537 log.debug('adding extra into INDEX_EXTENSIONS')
537 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
538 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
538
539
539
540
540 #==============================================================================
541 #==============================================================================
541 # TEST FUNCTIONS AND CREATORS
542 # TEST FUNCTIONS AND CREATORS
542 #==============================================================================
543 #==============================================================================
543 def create_test_index(repo_location, config, full_index):
544 def create_test_index(repo_location, config, full_index):
544 """
545 """
545 Makes default test index
546 Makes default test index
546
547
547 :param config: test config
548 :param config: test config
548 :param full_index:
549 :param full_index:
549 """
550 """
550
551
551 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
552 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
552 from rhodecode.lib.pidlock import DaemonLock, LockHeld
553 from rhodecode.lib.pidlock import DaemonLock, LockHeld
553
554
554 repo_location = repo_location
555 repo_location = repo_location
555
556
556 index_location = os.path.join(config['app_conf']['index_dir'])
557 index_location = os.path.join(config['app_conf']['index_dir'])
557 if not os.path.exists(index_location):
558 if not os.path.exists(index_location):
558 os.makedirs(index_location)
559 os.makedirs(index_location)
559
560
560 try:
561 try:
561 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
562 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
562 WhooshIndexingDaemon(index_location=index_location,
563 WhooshIndexingDaemon(index_location=index_location,
563 repo_location=repo_location)\
564 repo_location=repo_location)\
564 .run(full_index=full_index)
565 .run(full_index=full_index)
565 l.release()
566 l.release()
566 except LockHeld:
567 except LockHeld:
567 pass
568 pass
568
569
569
570
570 def create_test_env(repos_test_path, config):
571 def create_test_env(repos_test_path, config):
571 """
572 """
572 Makes a fresh database and
573 Makes a fresh database and
573 install test repository into tmp dir
574 install test repository into tmp dir
574 """
575 """
575 from rhodecode.lib.db_manage import DbManage
576 from rhodecode.lib.db_manage import DbManage
576 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
577 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
577
578
578 # PART ONE create db
579 # PART ONE create db
579 dbconf = config['sqlalchemy.db1.url']
580 dbconf = config['sqlalchemy.db1.url']
580 log.debug('making test db %s' % dbconf)
581 log.debug('making test db %s' % dbconf)
581
582
582 # create test dir if it doesn't exist
583 # create test dir if it doesn't exist
583 if not os.path.isdir(repos_test_path):
584 if not os.path.isdir(repos_test_path):
584 log.debug('Creating testdir %s' % repos_test_path)
585 log.debug('Creating testdir %s' % repos_test_path)
585 os.makedirs(repos_test_path)
586 os.makedirs(repos_test_path)
586
587
587 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
588 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
588 tests=True)
589 tests=True)
589 dbmanage.create_tables(override=True)
590 dbmanage.create_tables(override=True)
590 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
591 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
591 dbmanage.create_default_user()
592 dbmanage.create_default_user()
592 dbmanage.admin_prompt()
593 dbmanage.admin_prompt()
593 dbmanage.create_permissions()
594 dbmanage.create_permissions()
594 dbmanage.populate_default_permissions()
595 dbmanage.populate_default_permissions()
595 Session().commit()
596 Session().commit()
596 # PART TWO make test repo
597 # PART TWO make test repo
597 log.debug('making test vcs repositories')
598 log.debug('making test vcs repositories')
598
599
599 idx_path = config['app_conf']['index_dir']
600 idx_path = config['app_conf']['index_dir']
600 data_path = config['app_conf']['cache_dir']
601 data_path = config['app_conf']['cache_dir']
601
602
602 #clean index and data
603 #clean index and data
603 if idx_path and os.path.exists(idx_path):
604 if idx_path and os.path.exists(idx_path):
604 log.debug('remove %s' % idx_path)
605 log.debug('remove %s' % idx_path)
605 shutil.rmtree(idx_path)
606 shutil.rmtree(idx_path)
606
607
607 if data_path and os.path.exists(data_path):
608 if data_path and os.path.exists(data_path):
608 log.debug('remove %s' % data_path)
609 log.debug('remove %s' % data_path)
609 shutil.rmtree(data_path)
610 shutil.rmtree(data_path)
610
611
611 #CREATE DEFAULT TEST REPOS
612 #CREATE DEFAULT TEST REPOS
612 cur_dir = dn(dn(abspath(__file__)))
613 cur_dir = dn(dn(abspath(__file__)))
613 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
614 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
614 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
615 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
615 tar.close()
616 tar.close()
616
617
617 cur_dir = dn(dn(abspath(__file__)))
618 cur_dir = dn(dn(abspath(__file__)))
618 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
619 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
619 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
620 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
620 tar.close()
621 tar.close()
621
622
622 #LOAD VCS test stuff
623 #LOAD VCS test stuff
623 from rhodecode.tests.vcs import setup_package
624 from rhodecode.tests.vcs import setup_package
624 setup_package()
625 setup_package()
625
626
626
627
627 #==============================================================================
628 #==============================================================================
628 # PASTER COMMANDS
629 # PASTER COMMANDS
629 #==============================================================================
630 #==============================================================================
630 class BasePasterCommand(Command):
631 class BasePasterCommand(Command):
631 """
632 """
632 Abstract Base Class for paster commands.
633 Abstract Base Class for paster commands.
633
634
634 The celery commands are somewhat aggressive about loading
635 The celery commands are somewhat aggressive about loading
635 celery.conf, and since our module sets the `CELERY_LOADER`
636 celery.conf, and since our module sets the `CELERY_LOADER`
636 environment variable to our loader, we have to bootstrap a bit and
637 environment variable to our loader, we have to bootstrap a bit and
637 make sure we've had a chance to load the pylons config off of the
638 make sure we've had a chance to load the pylons config off of the
638 command line, otherwise everything fails.
639 command line, otherwise everything fails.
639 """
640 """
640 min_args = 1
641 min_args = 1
641 min_args_error = "Please provide a paster config file as an argument."
642 min_args_error = "Please provide a paster config file as an argument."
642 takes_config_file = 1
643 takes_config_file = 1
643 requires_config_file = True
644 requires_config_file = True
644
645
645 def notify_msg(self, msg, log=False):
646 def notify_msg(self, msg, log=False):
646 """Make a notification to user, additionally if logger is passed
647 """Make a notification to user, additionally if logger is passed
647 it logs this action using given logger
648 it logs this action using given logger
648
649
649 :param msg: message that will be printed to user
650 :param msg: message that will be printed to user
650 :param log: logging instance, to use to additionally log this message
651 :param log: logging instance, to use to additionally log this message
651
652
652 """
653 """
653 if log and isinstance(log, logging):
654 if log and isinstance(log, logging):
654 log(msg)
655 log(msg)
655
656
656 def run(self, args):
657 def run(self, args):
657 """
658 """
658 Overrides Command.run
659 Overrides Command.run
659
660
660 Checks for a config file argument and loads it.
661 Checks for a config file argument and loads it.
661 """
662 """
662 if len(args) < self.min_args:
663 if len(args) < self.min_args:
663 raise BadCommand(
664 raise BadCommand(
664 self.min_args_error % {'min_args': self.min_args,
665 self.min_args_error % {'min_args': self.min_args,
665 'actual_args': len(args)})
666 'actual_args': len(args)})
666
667
667 # Decrement because we're going to lob off the first argument.
668 # Decrement because we're going to lob off the first argument.
668 # @@ This is hacky
669 # @@ This is hacky
669 self.min_args -= 1
670 self.min_args -= 1
670 self.bootstrap_config(args[0])
671 self.bootstrap_config(args[0])
671 self.update_parser()
672 self.update_parser()
672 return super(BasePasterCommand, self).run(args[1:])
673 return super(BasePasterCommand, self).run(args[1:])
673
674
674 def update_parser(self):
675 def update_parser(self):
675 """
676 """
676 Abstract method. Allows for the class's parser to be updated
677 Abstract method. Allows for the class's parser to be updated
677 before the superclass's `run` method is called. Necessary to
678 before the superclass's `run` method is called. Necessary to
678 allow options/arguments to be passed through to the underlying
679 allow options/arguments to be passed through to the underlying
679 celery command.
680 celery command.
680 """
681 """
681 raise NotImplementedError("Abstract Method.")
682 raise NotImplementedError("Abstract Method.")
682
683
683 def bootstrap_config(self, conf):
684 def bootstrap_config(self, conf):
684 """
685 """
685 Loads the pylons configuration.
686 Loads the pylons configuration.
686 """
687 """
687 from pylons import config as pylonsconfig
688 from pylons import config as pylonsconfig
688
689
689 self.path_to_ini_file = os.path.realpath(conf)
690 self.path_to_ini_file = os.path.realpath(conf)
690 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
691 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
691 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
692 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
692
693
693
694
694 def check_git_version():
695 def check_git_version():
695 """
696 """
696 Checks what version of git is installed in system, and issues a warning
697 Checks what version of git is installed in system, and issues a warning
697 if it's too old for RhodeCode to properly work.
698 if it's too old for RhodeCode to properly work.
698 """
699 """
699 import subprocess
700 import subprocess
700 from distutils.version import StrictVersion
701 from distutils.version import StrictVersion
701 from rhodecode import BACKENDS
702 from rhodecode import BACKENDS
702
703
703 p = subprocess.Popen('git --version', shell=True,
704 p = subprocess.Popen('git --version', shell=True,
704 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
705 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
705 stdout, stderr = p.communicate()
706 stdout, stderr = p.communicate()
706 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
707 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
707 if len(ver.split('.')) > 3:
708 if len(ver.split('.')) > 3:
708 #StrictVersion needs to be only 3 element type
709 #StrictVersion needs to be only 3 element type
709 ver = '.'.join(ver.split('.')[:3])
710 ver = '.'.join(ver.split('.')[:3])
710 try:
711 try:
711 _ver = StrictVersion(ver)
712 _ver = StrictVersion(ver)
712 except:
713 except:
713 _ver = StrictVersion('0.0.0')
714 _ver = StrictVersion('0.0.0')
714 stderr = traceback.format_exc()
715 stderr = traceback.format_exc()
715
716
716 req_ver = '1.7.4'
717 req_ver = '1.7.4'
717 to_old_git = False
718 to_old_git = False
718 if _ver < StrictVersion(req_ver):
719 if _ver < StrictVersion(req_ver):
719 to_old_git = True
720 to_old_git = True
720
721
721 if 'git' in BACKENDS:
722 if 'git' in BACKENDS:
722 log.debug('GIT version detected: %s' % stdout)
723 log.debug('GIT version detected: %s' % stdout)
723 if stderr:
724 if stderr:
724 log.warning('Unable to detect git version org error was:%r' % stderr)
725 log.warning('Unable to detect git version org error was:%r' % stderr)
725 elif to_old_git:
726 elif to_old_git:
726 log.warning('RhodeCode detected git version %s, which is too old '
727 log.warning('RhodeCode detected git version %s, which is too old '
727 'for the system to function properly. Make sure '
728 'for the system to function properly. Make sure '
728 'its version is at least %s' % (ver, req_ver))
729 'its version is at least %s' % (ver, req_ver))
729 return _ver
730 return _ver
730
731
731
732
732 @decorator.decorator
733 @decorator.decorator
733 def jsonify(func, *args, **kwargs):
734 def jsonify(func, *args, **kwargs):
734 """Action decorator that formats output for JSON
735 """Action decorator that formats output for JSON
735
736
736 Given a function that will return content, this decorator will turn
737 Given a function that will return content, this decorator will turn
737 the result into JSON, with a content-type of 'application/json' and
738 the result into JSON, with a content-type of 'application/json' and
738 output it.
739 output it.
739
740
740 """
741 """
741 from pylons.decorators.util import get_pylons
742 from pylons.decorators.util import get_pylons
742 from rhodecode.lib.ext_json import json
743 from rhodecode.lib.ext_json import json
743 pylons = get_pylons(args)
744 pylons = get_pylons(args)
744 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
745 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
745 data = func(*args, **kwargs)
746 data = func(*args, **kwargs)
746 if isinstance(data, (list, tuple)):
747 if isinstance(data, (list, tuple)):
747 msg = "JSON responses with Array envelopes are susceptible to " \
748 msg = "JSON responses with Array envelopes are susceptible to " \
748 "cross-site data leak attacks, see " \
749 "cross-site data leak attacks, see " \
749 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
750 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
750 warnings.warn(msg, Warning, 2)
751 warnings.warn(msg, Warning, 2)
751 log.warning(msg)
752 log.warning(msg)
752 log.debug("Returning JSON wrapped action output")
753 log.debug("Returning JSON wrapped action output")
753 return json.dumps(data, encoding='utf-8') No newline at end of file
754 return json.dumps(data, encoding='utf-8')
@@ -1,996 +1,997 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.base
3 vcs.backends.base
4 ~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~
5
5
6 Base for all available scm backends
6 Base for all available scm backends
7
7
8 :created_on: Apr 8, 2010
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
10 """
11
11
12
12
13 from itertools import chain
13 from itertools import chain
14 from rhodecode.lib.vcs.utils import author_name, author_email
14 from rhodecode.lib.vcs.utils import author_name, author_email
15 from rhodecode.lib.vcs.utils.lazy import LazyProperty
15 from rhodecode.lib.vcs.utils.lazy import LazyProperty
16 from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
16 from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
17 from rhodecode.lib.vcs.conf import settings
17 from rhodecode.lib.vcs.conf import settings
18
18
19 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
19 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
20 NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, \
20 NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, \
21 NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, \
21 NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, \
22 RepositoryError
22 RepositoryError
23
23
24
24
25 class BaseRepository(object):
25 class BaseRepository(object):
26 """
26 """
27 Base Repository for final backends
27 Base Repository for final backends
28
28
29 **Attributes**
29 **Attributes**
30
30
31 ``DEFAULT_BRANCH_NAME``
31 ``DEFAULT_BRANCH_NAME``
32 name of default branch (i.e. "trunk" for svn, "master" for git etc.
32 name of default branch (i.e. "trunk" for svn, "master" for git etc.
33
33
34 ``scm``
34 ``scm``
35 alias of scm, i.e. *git* or *hg*
35 alias of scm, i.e. *git* or *hg*
36
36
37 ``repo``
37 ``repo``
38 object from external api
38 object from external api
39
39
40 ``revisions``
40 ``revisions``
41 list of all available revisions' ids, in ascending order
41 list of all available revisions' ids, in ascending order
42
42
43 ``changesets``
43 ``changesets``
44 storage dict caching returned changesets
44 storage dict caching returned changesets
45
45
46 ``path``
46 ``path``
47 absolute path to the repository
47 absolute path to the repository
48
48
49 ``branches``
49 ``branches``
50 branches as list of changesets
50 branches as list of changesets
51
51
52 ``tags``
52 ``tags``
53 tags as list of changesets
53 tags as list of changesets
54 """
54 """
55 scm = None
55 scm = None
56 DEFAULT_BRANCH_NAME = None
56 DEFAULT_BRANCH_NAME = None
57 EMPTY_CHANGESET = '0' * 40
57 EMPTY_CHANGESET = '0' * 40
58
58
59 def __init__(self, repo_path, create=False, **kwargs):
59 def __init__(self, repo_path, create=False, **kwargs):
60 """
60 """
61 Initializes repository. Raises RepositoryError if repository could
61 Initializes repository. Raises RepositoryError if repository could
62 not be find at the given ``repo_path`` or directory at ``repo_path``
62 not be find at the given ``repo_path`` or directory at ``repo_path``
63 exists and ``create`` is set to True.
63 exists and ``create`` is set to True.
64
64
65 :param repo_path: local path of the repository
65 :param repo_path: local path of the repository
66 :param create=False: if set to True, would try to craete repository.
66 :param create=False: if set to True, would try to craete repository.
67 :param src_url=None: if set, should be proper url from which repository
67 :param src_url=None: if set, should be proper url from which repository
68 would be cloned; requires ``create`` parameter to be set to True -
68 would be cloned; requires ``create`` parameter to be set to True -
69 raises RepositoryError if src_url is set and create evaluates to
69 raises RepositoryError if src_url is set and create evaluates to
70 False
70 False
71 """
71 """
72 raise NotImplementedError
72 raise NotImplementedError
73
73
74 def __str__(self):
74 def __str__(self):
75 return '<%s at %s>' % (self.__class__.__name__, self.path)
75 return '<%s at %s>' % (self.__class__.__name__, self.path)
76
76
77 def __repr__(self):
77 def __repr__(self):
78 return self.__str__()
78 return self.__str__()
79
79
80 def __len__(self):
80 def __len__(self):
81 return self.count()
81 return self.count()
82
82
83 @LazyProperty
83 @LazyProperty
84 def alias(self):
84 def alias(self):
85 for k, v in settings.BACKENDS.items():
85 for k, v in settings.BACKENDS.items():
86 if v.split('.')[-1] == str(self.__class__.__name__):
86 if v.split('.')[-1] == str(self.__class__.__name__):
87 return k
87 return k
88
88
89 @LazyProperty
89 @LazyProperty
90 def name(self):
90 def name(self):
91 raise NotImplementedError
91 raise NotImplementedError
92
92
93 @LazyProperty
93 @LazyProperty
94 def owner(self):
94 def owner(self):
95 raise NotImplementedError
95 raise NotImplementedError
96
96
97 @LazyProperty
97 @LazyProperty
98 def description(self):
98 def description(self):
99 raise NotImplementedError
99 raise NotImplementedError
100
100
101 @LazyProperty
101 @LazyProperty
102 def size(self):
102 def size(self):
103 """
103 """
104 Returns combined size in bytes for all repository files
104 Returns combined size in bytes for all repository files
105 """
105 """
106
106
107 size = 0
107 size = 0
108 try:
108 try:
109 tip = self.get_changeset()
109 tip = self.get_changeset()
110 for topnode, dirs, files in tip.walk('/'):
110 for topnode, dirs, files in tip.walk('/'):
111 for f in files:
111 for f in files:
112 size += tip.get_file_size(f.path)
112 size += tip.get_file_size(f.path)
113 for dir in dirs:
113 for dir in dirs:
114 for f in files:
114 for f in files:
115 size += tip.get_file_size(f.path)
115 size += tip.get_file_size(f.path)
116
116
117 except RepositoryError, e:
117 except RepositoryError, e:
118 pass
118 pass
119 return size
119 return size
120
120
121 def is_valid(self):
121 def is_valid(self):
122 """
122 """
123 Validates repository.
123 Validates repository.
124 """
124 """
125 raise NotImplementedError
125 raise NotImplementedError
126
126
127 def get_last_change(self):
127 def get_last_change(self):
128 self.get_changesets()
128 self.get_changesets()
129
129
130 #==========================================================================
130 #==========================================================================
131 # CHANGESETS
131 # CHANGESETS
132 #==========================================================================
132 #==========================================================================
133
133
134 def get_changeset(self, revision=None):
134 def get_changeset(self, revision=None):
135 """
135 """
136 Returns instance of ``Changeset`` class. If ``revision`` is None, most
136 Returns instance of ``Changeset`` class. If ``revision`` is None, most
137 recent changeset is returned.
137 recent changeset is returned.
138
138
139 :raises ``EmptyRepositoryError``: if there are no revisions
139 :raises ``EmptyRepositoryError``: if there are no revisions
140 """
140 """
141 raise NotImplementedError
141 raise NotImplementedError
142
142
143 def __iter__(self):
143 def __iter__(self):
144 """
144 """
145 Allows Repository objects to be iterated.
145 Allows Repository objects to be iterated.
146
146
147 *Requires* implementation of ``__getitem__`` method.
147 *Requires* implementation of ``__getitem__`` method.
148 """
148 """
149 for revision in self.revisions:
149 for revision in self.revisions:
150 yield self.get_changeset(revision)
150 yield self.get_changeset(revision)
151
151
152 def get_changesets(self, start=None, end=None, start_date=None,
152 def get_changesets(self, start=None, end=None, start_date=None,
153 end_date=None, branch_name=None, reverse=False):
153 end_date=None, branch_name=None, reverse=False):
154 """
154 """
155 Returns iterator of ``MercurialChangeset`` objects from start to end
155 Returns iterator of ``MercurialChangeset`` objects from start to end
156 not inclusive This should behave just like a list, ie. end is not
156 not inclusive This should behave just like a list, ie. end is not
157 inclusive
157 inclusive
158
158
159 :param start: None or str
159 :param start: None or str
160 :param end: None or str
160 :param end: None or str
161 :param start_date:
161 :param start_date:
162 :param end_date:
162 :param end_date:
163 :param branch_name:
163 :param branch_name:
164 :param reversed:
164 :param reversed:
165 """
165 """
166 raise NotImplementedError
166 raise NotImplementedError
167
167
168 def __getslice__(self, i, j):
168 def __getslice__(self, i, j):
169 """
169 """
170 Returns a iterator of sliced repository
170 Returns a iterator of sliced repository
171 """
171 """
172 for rev in self.revisions[i:j]:
172 for rev in self.revisions[i:j]:
173 yield self.get_changeset(rev)
173 yield self.get_changeset(rev)
174
174
175 def __getitem__(self, key):
175 def __getitem__(self, key):
176 return self.get_changeset(key)
176 return self.get_changeset(key)
177
177
178 def count(self):
178 def count(self):
179 return len(self.revisions)
179 return len(self.revisions)
180
180
181 def tag(self, name, user, revision=None, message=None, date=None, **opts):
181 def tag(self, name, user, revision=None, message=None, date=None, **opts):
182 """
182 """
183 Creates and returns a tag for the given ``revision``.
183 Creates and returns a tag for the given ``revision``.
184
184
185 :param name: name for new tag
185 :param name: name for new tag
186 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
186 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
187 :param revision: changeset id for which new tag would be created
187 :param revision: changeset id for which new tag would be created
188 :param message: message of the tag's commit
188 :param message: message of the tag's commit
189 :param date: date of tag's commit
189 :param date: date of tag's commit
190
190
191 :raises TagAlreadyExistError: if tag with same name already exists
191 :raises TagAlreadyExistError: if tag with same name already exists
192 """
192 """
193 raise NotImplementedError
193 raise NotImplementedError
194
194
195 def remove_tag(self, name, user, message=None, date=None):
195 def remove_tag(self, name, user, message=None, date=None):
196 """
196 """
197 Removes tag with the given ``name``.
197 Removes tag with the given ``name``.
198
198
199 :param name: name of the tag to be removed
199 :param name: name of the tag to be removed
200 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
200 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
201 :param message: message of the tag's removal commit
201 :param message: message of the tag's removal commit
202 :param date: date of tag's removal commit
202 :param date: date of tag's removal commit
203
203
204 :raises TagDoesNotExistError: if tag with given name does not exists
204 :raises TagDoesNotExistError: if tag with given name does not exists
205 """
205 """
206 raise NotImplementedError
206 raise NotImplementedError
207
207
208 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
208 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
209 context=3):
209 context=3):
210 """
210 """
211 Returns (git like) *diff*, as plain text. Shows changes introduced by
211 Returns (git like) *diff*, as plain text. Shows changes introduced by
212 ``rev2`` since ``rev1``.
212 ``rev2`` since ``rev1``.
213
213
214 :param rev1: Entry point from which diff is shown. Can be
214 :param rev1: Entry point from which diff is shown. Can be
215 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
215 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
216 the changes since empty state of the repository until ``rev2``
216 the changes since empty state of the repository until ``rev2``
217 :param rev2: Until which revision changes should be shown.
217 :param rev2: Until which revision changes should be shown.
218 :param ignore_whitespace: If set to ``True``, would not show whitespace
218 :param ignore_whitespace: If set to ``True``, would not show whitespace
219 changes. Defaults to ``False``.
219 changes. Defaults to ``False``.
220 :param context: How many lines before/after changed lines should be
220 :param context: How many lines before/after changed lines should be
221 shown. Defaults to ``3``.
221 shown. Defaults to ``3``.
222 """
222 """
223 raise NotImplementedError
223 raise NotImplementedError
224
224
225 # ========== #
225 # ========== #
226 # COMMIT API #
226 # COMMIT API #
227 # ========== #
227 # ========== #
228
228
229 @LazyProperty
229 @LazyProperty
230 def in_memory_changeset(self):
230 def in_memory_changeset(self):
231 """
231 """
232 Returns ``InMemoryChangeset`` object for this repository.
232 Returns ``InMemoryChangeset`` object for this repository.
233 """
233 """
234 raise NotImplementedError
234 raise NotImplementedError
235
235
236 def add(self, filenode, **kwargs):
236 def add(self, filenode, **kwargs):
237 """
237 """
238 Commit api function that will add given ``FileNode`` into this
238 Commit api function that will add given ``FileNode`` into this
239 repository.
239 repository.
240
240
241 :raises ``NodeAlreadyExistsError``: if there is a file with same path
241 :raises ``NodeAlreadyExistsError``: if there is a file with same path
242 already in repository
242 already in repository
243 :raises ``NodeAlreadyAddedError``: if given node is already marked as
243 :raises ``NodeAlreadyAddedError``: if given node is already marked as
244 *added*
244 *added*
245 """
245 """
246 raise NotImplementedError
246 raise NotImplementedError
247
247
248 def remove(self, filenode, **kwargs):
248 def remove(self, filenode, **kwargs):
249 """
249 """
250 Commit api function that will remove given ``FileNode`` into this
250 Commit api function that will remove given ``FileNode`` into this
251 repository.
251 repository.
252
252
253 :raises ``EmptyRepositoryError``: if there are no changesets yet
253 :raises ``EmptyRepositoryError``: if there are no changesets yet
254 :raises ``NodeDoesNotExistError``: if there is no file with given path
254 :raises ``NodeDoesNotExistError``: if there is no file with given path
255 """
255 """
256 raise NotImplementedError
256 raise NotImplementedError
257
257
258 def commit(self, message, **kwargs):
258 def commit(self, message, **kwargs):
259 """
259 """
260 Persists current changes made on this repository and returns newly
260 Persists current changes made on this repository and returns newly
261 created changeset.
261 created changeset.
262
262
263 :raises ``NothingChangedError``: if no changes has been made
263 :raises ``NothingChangedError``: if no changes has been made
264 """
264 """
265 raise NotImplementedError
265 raise NotImplementedError
266
266
267 def get_state(self):
267 def get_state(self):
268 """
268 """
269 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
269 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
270 containing ``FileNode`` objects.
270 containing ``FileNode`` objects.
271 """
271 """
272 raise NotImplementedError
272 raise NotImplementedError
273
273
274 def get_config_value(self, section, name, config_file=None):
274 def get_config_value(self, section, name, config_file=None):
275 """
275 """
276 Returns configuration value for a given [``section``] and ``name``.
276 Returns configuration value for a given [``section``] and ``name``.
277
277
278 :param section: Section we want to retrieve value from
278 :param section: Section we want to retrieve value from
279 :param name: Name of configuration we want to retrieve
279 :param name: Name of configuration we want to retrieve
280 :param config_file: A path to file which should be used to retrieve
280 :param config_file: A path to file which should be used to retrieve
281 configuration from (might also be a list of file paths)
281 configuration from (might also be a list of file paths)
282 """
282 """
283 raise NotImplementedError
283 raise NotImplementedError
284
284
285 def get_user_name(self, config_file=None):
285 def get_user_name(self, config_file=None):
286 """
286 """
287 Returns user's name from global configuration file.
287 Returns user's name from global configuration file.
288
288
289 :param config_file: A path to file which should be used to retrieve
289 :param config_file: A path to file which should be used to retrieve
290 configuration from (might also be a list of file paths)
290 configuration from (might also be a list of file paths)
291 """
291 """
292 raise NotImplementedError
292 raise NotImplementedError
293
293
294 def get_user_email(self, config_file=None):
294 def get_user_email(self, config_file=None):
295 """
295 """
296 Returns user's email from global configuration file.
296 Returns user's email from global configuration file.
297
297
298 :param config_file: A path to file which should be used to retrieve
298 :param config_file: A path to file which should be used to retrieve
299 configuration from (might also be a list of file paths)
299 configuration from (might also be a list of file paths)
300 """
300 """
301 raise NotImplementedError
301 raise NotImplementedError
302
302
303 # =========== #
303 # =========== #
304 # WORKDIR API #
304 # WORKDIR API #
305 # =========== #
305 # =========== #
306
306
307 @LazyProperty
307 @LazyProperty
308 def workdir(self):
308 def workdir(self):
309 """
309 """
310 Returns ``Workdir`` instance for this repository.
310 Returns ``Workdir`` instance for this repository.
311 """
311 """
312 raise NotImplementedError
312 raise NotImplementedError
313
313
314
314
315 class BaseChangeset(object):
315 class BaseChangeset(object):
316 """
316 """
317 Each backend should implement it's changeset representation.
317 Each backend should implement it's changeset representation.
318
318
319 **Attributes**
319 **Attributes**
320
320
321 ``repository``
321 ``repository``
322 repository object within which changeset exists
322 repository object within which changeset exists
323
323
324 ``id``
324 ``id``
325 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
325 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
326
326
327 ``raw_id``
327 ``raw_id``
328 raw changeset representation (i.e. full 40 length sha for git
328 raw changeset representation (i.e. full 40 length sha for git
329 backend)
329 backend)
330
330
331 ``short_id``
331 ``short_id``
332 shortened (if apply) version of ``raw_id``; it would be simple
332 shortened (if apply) version of ``raw_id``; it would be simple
333 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
333 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
334 as ``raw_id`` for subversion
334 as ``raw_id`` for subversion
335
335
336 ``revision``
336 ``revision``
337 revision number as integer
337 revision number as integer
338
338
339 ``files``
339 ``files``
340 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
340 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
341
341
342 ``dirs``
342 ``dirs``
343 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
343 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
344
344
345 ``nodes``
345 ``nodes``
346 combined list of ``Node`` objects
346 combined list of ``Node`` objects
347
347
348 ``author``
348 ``author``
349 author of the changeset, as unicode
349 author of the changeset, as unicode
350
350
351 ``message``
351 ``message``
352 message of the changeset, as unicode
352 message of the changeset, as unicode
353
353
354 ``parents``
354 ``parents``
355 list of parent changesets
355 list of parent changesets
356
356
357 ``last``
357 ``last``
358 ``True`` if this is last changeset in repository, ``False``
358 ``True`` if this is last changeset in repository, ``False``
359 otherwise; trying to access this attribute while there is no
359 otherwise; trying to access this attribute while there is no
360 changesets would raise ``EmptyRepositoryError``
360 changesets would raise ``EmptyRepositoryError``
361 """
361 """
362 def __str__(self):
362 def __str__(self):
363 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
363 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
364 self.short_id)
364 self.short_id)
365
365
366 def __repr__(self):
366 def __repr__(self):
367 return self.__str__()
367 return self.__str__()
368
368
369 def __unicode__(self):
369 def __unicode__(self):
370 return u'%s:%s' % (self.revision, self.short_id)
370 return u'%s:%s' % (self.revision, self.short_id)
371
371
372 def __eq__(self, other):
372 def __eq__(self, other):
373 return self.raw_id == other.raw_id
373 return self.raw_id == other.raw_id
374
374
375 def __json__(self):
375 def __json__(self):
376 return dict(
376 return dict(
377 short_id=self.short_id,
377 short_id=self.short_id,
378 raw_id=self.raw_id,
378 raw_id=self.raw_id,
379 revision=self.revision,
379 message=self.message,
380 message=self.message,
380 date=self.date,
381 date=self.date,
381 author=self.author,
382 author=self.author,
382 )
383 )
383
384
384 @LazyProperty
385 @LazyProperty
385 def last(self):
386 def last(self):
386 if self.repository is None:
387 if self.repository is None:
387 raise ChangesetError("Cannot check if it's most recent revision")
388 raise ChangesetError("Cannot check if it's most recent revision")
388 return self.raw_id == self.repository.revisions[-1]
389 return self.raw_id == self.repository.revisions[-1]
389
390
390 @LazyProperty
391 @LazyProperty
391 def parents(self):
392 def parents(self):
392 """
393 """
393 Returns list of parents changesets.
394 Returns list of parents changesets.
394 """
395 """
395 raise NotImplementedError
396 raise NotImplementedError
396
397
397 @LazyProperty
398 @LazyProperty
398 def children(self):
399 def children(self):
399 """
400 """
400 Returns list of children changesets.
401 Returns list of children changesets.
401 """
402 """
402 raise NotImplementedError
403 raise NotImplementedError
403
404
404 @LazyProperty
405 @LazyProperty
405 def id(self):
406 def id(self):
406 """
407 """
407 Returns string identifying this changeset.
408 Returns string identifying this changeset.
408 """
409 """
409 raise NotImplementedError
410 raise NotImplementedError
410
411
411 @LazyProperty
412 @LazyProperty
412 def raw_id(self):
413 def raw_id(self):
413 """
414 """
414 Returns raw string identifying this changeset.
415 Returns raw string identifying this changeset.
415 """
416 """
416 raise NotImplementedError
417 raise NotImplementedError
417
418
418 @LazyProperty
419 @LazyProperty
419 def short_id(self):
420 def short_id(self):
420 """
421 """
421 Returns shortened version of ``raw_id`` attribute, as string,
422 Returns shortened version of ``raw_id`` attribute, as string,
422 identifying this changeset, useful for web representation.
423 identifying this changeset, useful for web representation.
423 """
424 """
424 raise NotImplementedError
425 raise NotImplementedError
425
426
426 @LazyProperty
427 @LazyProperty
427 def revision(self):
428 def revision(self):
428 """
429 """
429 Returns integer identifying this changeset.
430 Returns integer identifying this changeset.
430
431
431 """
432 """
432 raise NotImplementedError
433 raise NotImplementedError
433
434
434 @LazyProperty
435 @LazyProperty
435 def commiter(self):
436 def commiter(self):
436 """
437 """
437 Returns Commiter for given commit
438 Returns Commiter for given commit
438 """
439 """
439
440
440 raise NotImplementedError
441 raise NotImplementedError
441
442
442 @LazyProperty
443 @LazyProperty
443 def commiter_name(self):
444 def commiter_name(self):
444 """
445 """
445 Returns Author name for given commit
446 Returns Author name for given commit
446 """
447 """
447
448
448 return author_name(self.commiter)
449 return author_name(self.commiter)
449
450
450 @LazyProperty
451 @LazyProperty
451 def commiter_email(self):
452 def commiter_email(self):
452 """
453 """
453 Returns Author email address for given commit
454 Returns Author email address for given commit
454 """
455 """
455
456
456 return author_email(self.commiter)
457 return author_email(self.commiter)
457
458
458 @LazyProperty
459 @LazyProperty
459 def author(self):
460 def author(self):
460 """
461 """
461 Returns Author for given commit
462 Returns Author for given commit
462 """
463 """
463
464
464 raise NotImplementedError
465 raise NotImplementedError
465
466
466 @LazyProperty
467 @LazyProperty
467 def author_name(self):
468 def author_name(self):
468 """
469 """
469 Returns Author name for given commit
470 Returns Author name for given commit
470 """
471 """
471
472
472 return author_name(self.author)
473 return author_name(self.author)
473
474
474 @LazyProperty
475 @LazyProperty
475 def author_email(self):
476 def author_email(self):
476 """
477 """
477 Returns Author email address for given commit
478 Returns Author email address for given commit
478 """
479 """
479
480
480 return author_email(self.author)
481 return author_email(self.author)
481
482
482 def get_file_mode(self, path):
483 def get_file_mode(self, path):
483 """
484 """
484 Returns stat mode of the file at the given ``path``.
485 Returns stat mode of the file at the given ``path``.
485 """
486 """
486 raise NotImplementedError
487 raise NotImplementedError
487
488
488 def get_file_content(self, path):
489 def get_file_content(self, path):
489 """
490 """
490 Returns content of the file at the given ``path``.
491 Returns content of the file at the given ``path``.
491 """
492 """
492 raise NotImplementedError
493 raise NotImplementedError
493
494
494 def get_file_size(self, path):
495 def get_file_size(self, path):
495 """
496 """
496 Returns size of the file at the given ``path``.
497 Returns size of the file at the given ``path``.
497 """
498 """
498 raise NotImplementedError
499 raise NotImplementedError
499
500
500 def get_file_changeset(self, path):
501 def get_file_changeset(self, path):
501 """
502 """
502 Returns last commit of the file at the given ``path``.
503 Returns last commit of the file at the given ``path``.
503 """
504 """
504 raise NotImplementedError
505 raise NotImplementedError
505
506
506 def get_file_history(self, path):
507 def get_file_history(self, path):
507 """
508 """
508 Returns history of file as reversed list of ``Changeset`` objects for
509 Returns history of file as reversed list of ``Changeset`` objects for
509 which file at given ``path`` has been modified.
510 which file at given ``path`` has been modified.
510 """
511 """
511 raise NotImplementedError
512 raise NotImplementedError
512
513
513 def get_nodes(self, path):
514 def get_nodes(self, path):
514 """
515 """
515 Returns combined ``DirNode`` and ``FileNode`` objects list representing
516 Returns combined ``DirNode`` and ``FileNode`` objects list representing
516 state of changeset at the given ``path``.
517 state of changeset at the given ``path``.
517
518
518 :raises ``ChangesetError``: if node at the given ``path`` is not
519 :raises ``ChangesetError``: if node at the given ``path`` is not
519 instance of ``DirNode``
520 instance of ``DirNode``
520 """
521 """
521 raise NotImplementedError
522 raise NotImplementedError
522
523
523 def get_node(self, path):
524 def get_node(self, path):
524 """
525 """
525 Returns ``Node`` object from the given ``path``.
526 Returns ``Node`` object from the given ``path``.
526
527
527 :raises ``NodeDoesNotExistError``: if there is no node at the given
528 :raises ``NodeDoesNotExistError``: if there is no node at the given
528 ``path``
529 ``path``
529 """
530 """
530 raise NotImplementedError
531 raise NotImplementedError
531
532
532 def fill_archive(self, stream=None, kind='tgz', prefix=None):
533 def fill_archive(self, stream=None, kind='tgz', prefix=None):
533 """
534 """
534 Fills up given stream.
535 Fills up given stream.
535
536
536 :param stream: file like object.
537 :param stream: file like object.
537 :param kind: one of following: ``zip``, ``tar``, ``tgz``
538 :param kind: one of following: ``zip``, ``tar``, ``tgz``
538 or ``tbz2``. Default: ``tgz``.
539 or ``tbz2``. Default: ``tgz``.
539 :param prefix: name of root directory in archive.
540 :param prefix: name of root directory in archive.
540 Default is repository name and changeset's raw_id joined with dash.
541 Default is repository name and changeset's raw_id joined with dash.
541
542
542 repo-tip.<kind>
543 repo-tip.<kind>
543 """
544 """
544
545
545 raise NotImplementedError
546 raise NotImplementedError
546
547
547 def get_chunked_archive(self, **kwargs):
548 def get_chunked_archive(self, **kwargs):
548 """
549 """
549 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
550 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
550
551
551 :param chunk_size: extra parameter which controls size of returned
552 :param chunk_size: extra parameter which controls size of returned
552 chunks. Default:8k.
553 chunks. Default:8k.
553 """
554 """
554
555
555 chunk_size = kwargs.pop('chunk_size', 8192)
556 chunk_size = kwargs.pop('chunk_size', 8192)
556 stream = kwargs.get('stream')
557 stream = kwargs.get('stream')
557 self.fill_archive(**kwargs)
558 self.fill_archive(**kwargs)
558 while True:
559 while True:
559 data = stream.read(chunk_size)
560 data = stream.read(chunk_size)
560 if not data:
561 if not data:
561 break
562 break
562 yield data
563 yield data
563
564
564 @LazyProperty
565 @LazyProperty
565 def root(self):
566 def root(self):
566 """
567 """
567 Returns ``RootNode`` object for this changeset.
568 Returns ``RootNode`` object for this changeset.
568 """
569 """
569 return self.get_node('')
570 return self.get_node('')
570
571
571 def next(self, branch=None):
572 def next(self, branch=None):
572 """
573 """
573 Returns next changeset from current, if branch is gives it will return
574 Returns next changeset from current, if branch is gives it will return
574 next changeset belonging to this branch
575 next changeset belonging to this branch
575
576
576 :param branch: show changesets within the given named branch
577 :param branch: show changesets within the given named branch
577 """
578 """
578 raise NotImplementedError
579 raise NotImplementedError
579
580
580 def prev(self, branch=None):
581 def prev(self, branch=None):
581 """
582 """
582 Returns previous changeset from current, if branch is gives it will
583 Returns previous changeset from current, if branch is gives it will
583 return previous changeset belonging to this branch
584 return previous changeset belonging to this branch
584
585
585 :param branch: show changesets within the given named branch
586 :param branch: show changesets within the given named branch
586 """
587 """
587 raise NotImplementedError
588 raise NotImplementedError
588
589
589 @LazyProperty
590 @LazyProperty
590 def added(self):
591 def added(self):
591 """
592 """
592 Returns list of added ``FileNode`` objects.
593 Returns list of added ``FileNode`` objects.
593 """
594 """
594 raise NotImplementedError
595 raise NotImplementedError
595
596
596 @LazyProperty
597 @LazyProperty
597 def changed(self):
598 def changed(self):
598 """
599 """
599 Returns list of modified ``FileNode`` objects.
600 Returns list of modified ``FileNode`` objects.
600 """
601 """
601 raise NotImplementedError
602 raise NotImplementedError
602
603
603 @LazyProperty
604 @LazyProperty
604 def removed(self):
605 def removed(self):
605 """
606 """
606 Returns list of removed ``FileNode`` objects.
607 Returns list of removed ``FileNode`` objects.
607 """
608 """
608 raise NotImplementedError
609 raise NotImplementedError
609
610
610 @LazyProperty
611 @LazyProperty
611 def size(self):
612 def size(self):
612 """
613 """
613 Returns total number of bytes from contents of all filenodes.
614 Returns total number of bytes from contents of all filenodes.
614 """
615 """
615 return sum((node.size for node in self.get_filenodes_generator()))
616 return sum((node.size for node in self.get_filenodes_generator()))
616
617
617 def walk(self, topurl=''):
618 def walk(self, topurl=''):
618 """
619 """
619 Similar to os.walk method. Insted of filesystem it walks through
620 Similar to os.walk method. Insted of filesystem it walks through
620 changeset starting at given ``topurl``. Returns generator of tuples
621 changeset starting at given ``topurl``. Returns generator of tuples
621 (topnode, dirnodes, filenodes).
622 (topnode, dirnodes, filenodes).
622 """
623 """
623 topnode = self.get_node(topurl)
624 topnode = self.get_node(topurl)
624 yield (topnode, topnode.dirs, topnode.files)
625 yield (topnode, topnode.dirs, topnode.files)
625 for dirnode in topnode.dirs:
626 for dirnode in topnode.dirs:
626 for tup in self.walk(dirnode.path):
627 for tup in self.walk(dirnode.path):
627 yield tup
628 yield tup
628
629
629 def get_filenodes_generator(self):
630 def get_filenodes_generator(self):
630 """
631 """
631 Returns generator that yields *all* file nodes.
632 Returns generator that yields *all* file nodes.
632 """
633 """
633 for topnode, dirs, files in self.walk():
634 for topnode, dirs, files in self.walk():
634 for node in files:
635 for node in files:
635 yield node
636 yield node
636
637
637 def as_dict(self):
638 def as_dict(self):
638 """
639 """
639 Returns dictionary with changeset's attributes and their values.
640 Returns dictionary with changeset's attributes and their values.
640 """
641 """
641 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
642 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
642 'revision', 'date', 'message'])
643 'revision', 'date', 'message'])
643 data['author'] = {'name': self.author_name, 'email': self.author_email}
644 data['author'] = {'name': self.author_name, 'email': self.author_email}
644 data['added'] = [node.path for node in self.added]
645 data['added'] = [node.path for node in self.added]
645 data['changed'] = [node.path for node in self.changed]
646 data['changed'] = [node.path for node in self.changed]
646 data['removed'] = [node.path for node in self.removed]
647 data['removed'] = [node.path for node in self.removed]
647 return data
648 return data
648
649
649
650
650 class BaseWorkdir(object):
651 class BaseWorkdir(object):
651 """
652 """
652 Working directory representation of single repository.
653 Working directory representation of single repository.
653
654
654 :attribute: repository: repository object of working directory
655 :attribute: repository: repository object of working directory
655 """
656 """
656
657
657 def __init__(self, repository):
658 def __init__(self, repository):
658 self.repository = repository
659 self.repository = repository
659
660
660 def get_branch(self):
661 def get_branch(self):
661 """
662 """
662 Returns name of current branch.
663 Returns name of current branch.
663 """
664 """
664 raise NotImplementedError
665 raise NotImplementedError
665
666
666 def get_changeset(self):
667 def get_changeset(self):
667 """
668 """
668 Returns current changeset.
669 Returns current changeset.
669 """
670 """
670 raise NotImplementedError
671 raise NotImplementedError
671
672
672 def get_added(self):
673 def get_added(self):
673 """
674 """
674 Returns list of ``FileNode`` objects marked as *new* in working
675 Returns list of ``FileNode`` objects marked as *new* in working
675 directory.
676 directory.
676 """
677 """
677 raise NotImplementedError
678 raise NotImplementedError
678
679
679 def get_changed(self):
680 def get_changed(self):
680 """
681 """
681 Returns list of ``FileNode`` objects *changed* in working directory.
682 Returns list of ``FileNode`` objects *changed* in working directory.
682 """
683 """
683 raise NotImplementedError
684 raise NotImplementedError
684
685
685 def get_removed(self):
686 def get_removed(self):
686 """
687 """
687 Returns list of ``RemovedFileNode`` objects marked as *removed* in
688 Returns list of ``RemovedFileNode`` objects marked as *removed* in
688 working directory.
689 working directory.
689 """
690 """
690 raise NotImplementedError
691 raise NotImplementedError
691
692
692 def get_untracked(self):
693 def get_untracked(self):
693 """
694 """
694 Returns list of ``FileNode`` objects which are present within working
695 Returns list of ``FileNode`` objects which are present within working
695 directory however are not tracked by repository.
696 directory however are not tracked by repository.
696 """
697 """
697 raise NotImplementedError
698 raise NotImplementedError
698
699
699 def get_status(self):
700 def get_status(self):
700 """
701 """
701 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
702 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
702 lists.
703 lists.
703 """
704 """
704 raise NotImplementedError
705 raise NotImplementedError
705
706
706 def commit(self, message, **kwargs):
707 def commit(self, message, **kwargs):
707 """
708 """
708 Commits local (from working directory) changes and returns newly
709 Commits local (from working directory) changes and returns newly
709 created
710 created
710 ``Changeset``. Updates repository's ``revisions`` list.
711 ``Changeset``. Updates repository's ``revisions`` list.
711
712
712 :raises ``CommitError``: if any error occurs while committing
713 :raises ``CommitError``: if any error occurs while committing
713 """
714 """
714 raise NotImplementedError
715 raise NotImplementedError
715
716
716 def update(self, revision=None):
717 def update(self, revision=None):
717 """
718 """
718 Fetches content of the given revision and populates it within working
719 Fetches content of the given revision and populates it within working
719 directory.
720 directory.
720 """
721 """
721 raise NotImplementedError
722 raise NotImplementedError
722
723
723 def checkout_branch(self, branch=None):
724 def checkout_branch(self, branch=None):
724 """
725 """
725 Checks out ``branch`` or the backend's default branch.
726 Checks out ``branch`` or the backend's default branch.
726
727
727 Raises ``BranchDoesNotExistError`` if the branch does not exist.
728 Raises ``BranchDoesNotExistError`` if the branch does not exist.
728 """
729 """
729 raise NotImplementedError
730 raise NotImplementedError
730
731
731
732
732 class BaseInMemoryChangeset(object):
733 class BaseInMemoryChangeset(object):
733 """
734 """
734 Represents differences between repository's state (most recent head) and
735 Represents differences between repository's state (most recent head) and
735 changes made *in place*.
736 changes made *in place*.
736
737
737 **Attributes**
738 **Attributes**
738
739
739 ``repository``
740 ``repository``
740 repository object for this in-memory-changeset
741 repository object for this in-memory-changeset
741
742
742 ``added``
743 ``added``
743 list of ``FileNode`` objects marked as *added*
744 list of ``FileNode`` objects marked as *added*
744
745
745 ``changed``
746 ``changed``
746 list of ``FileNode`` objects marked as *changed*
747 list of ``FileNode`` objects marked as *changed*
747
748
748 ``removed``
749 ``removed``
749 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
750 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
750 *removed*
751 *removed*
751
752
752 ``parents``
753 ``parents``
753 list of ``Changeset`` representing parents of in-memory changeset.
754 list of ``Changeset`` representing parents of in-memory changeset.
754 Should always be 2-element sequence.
755 Should always be 2-element sequence.
755
756
756 """
757 """
757
758
758 def __init__(self, repository):
759 def __init__(self, repository):
759 self.repository = repository
760 self.repository = repository
760 self.added = []
761 self.added = []
761 self.changed = []
762 self.changed = []
762 self.removed = []
763 self.removed = []
763 self.parents = []
764 self.parents = []
764
765
765 def add(self, *filenodes):
766 def add(self, *filenodes):
766 """
767 """
767 Marks given ``FileNode`` objects as *to be committed*.
768 Marks given ``FileNode`` objects as *to be committed*.
768
769
769 :raises ``NodeAlreadyExistsError``: if node with same path exists at
770 :raises ``NodeAlreadyExistsError``: if node with same path exists at
770 latest changeset
771 latest changeset
771 :raises ``NodeAlreadyAddedError``: if node with same path is already
772 :raises ``NodeAlreadyAddedError``: if node with same path is already
772 marked as *added*
773 marked as *added*
773 """
774 """
774 # Check if not already marked as *added* first
775 # Check if not already marked as *added* first
775 for node in filenodes:
776 for node in filenodes:
776 if node.path in (n.path for n in self.added):
777 if node.path in (n.path for n in self.added):
777 raise NodeAlreadyAddedError("Such FileNode %s is already "
778 raise NodeAlreadyAddedError("Such FileNode %s is already "
778 "marked for addition" % node.path)
779 "marked for addition" % node.path)
779 for node in filenodes:
780 for node in filenodes:
780 self.added.append(node)
781 self.added.append(node)
781
782
782 def change(self, *filenodes):
783 def change(self, *filenodes):
783 """
784 """
784 Marks given ``FileNode`` objects to be *changed* in next commit.
785 Marks given ``FileNode`` objects to be *changed* in next commit.
785
786
786 :raises ``EmptyRepositoryError``: if there are no changesets yet
787 :raises ``EmptyRepositoryError``: if there are no changesets yet
787 :raises ``NodeAlreadyExistsError``: if node with same path is already
788 :raises ``NodeAlreadyExistsError``: if node with same path is already
788 marked to be *changed*
789 marked to be *changed*
789 :raises ``NodeAlreadyRemovedError``: if node with same path is already
790 :raises ``NodeAlreadyRemovedError``: if node with same path is already
790 marked to be *removed*
791 marked to be *removed*
791 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
792 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
792 changeset
793 changeset
793 :raises ``NodeNotChangedError``: if node hasn't really be changed
794 :raises ``NodeNotChangedError``: if node hasn't really be changed
794 """
795 """
795 for node in filenodes:
796 for node in filenodes:
796 if node.path in (n.path for n in self.removed):
797 if node.path in (n.path for n in self.removed):
797 raise NodeAlreadyRemovedError("Node at %s is already marked "
798 raise NodeAlreadyRemovedError("Node at %s is already marked "
798 "as removed" % node.path)
799 "as removed" % node.path)
799 try:
800 try:
800 self.repository.get_changeset()
801 self.repository.get_changeset()
801 except EmptyRepositoryError:
802 except EmptyRepositoryError:
802 raise EmptyRepositoryError("Nothing to change - try to *add* new "
803 raise EmptyRepositoryError("Nothing to change - try to *add* new "
803 "nodes rather than changing them")
804 "nodes rather than changing them")
804 for node in filenodes:
805 for node in filenodes:
805 if node.path in (n.path for n in self.changed):
806 if node.path in (n.path for n in self.changed):
806 raise NodeAlreadyChangedError("Node at '%s' is already "
807 raise NodeAlreadyChangedError("Node at '%s' is already "
807 "marked as changed" % node.path)
808 "marked as changed" % node.path)
808 self.changed.append(node)
809 self.changed.append(node)
809
810
810 def remove(self, *filenodes):
811 def remove(self, *filenodes):
811 """
812 """
812 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
813 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
813 *removed* in next commit.
814 *removed* in next commit.
814
815
815 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
816 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
816 be *removed*
817 be *removed*
817 :raises ``NodeAlreadyChangedError``: if node has been already marked to
818 :raises ``NodeAlreadyChangedError``: if node has been already marked to
818 be *changed*
819 be *changed*
819 """
820 """
820 for node in filenodes:
821 for node in filenodes:
821 if node.path in (n.path for n in self.removed):
822 if node.path in (n.path for n in self.removed):
822 raise NodeAlreadyRemovedError("Node is already marked to "
823 raise NodeAlreadyRemovedError("Node is already marked to "
823 "for removal at %s" % node.path)
824 "for removal at %s" % node.path)
824 if node.path in (n.path for n in self.changed):
825 if node.path in (n.path for n in self.changed):
825 raise NodeAlreadyChangedError("Node is already marked to "
826 raise NodeAlreadyChangedError("Node is already marked to "
826 "be changed at %s" % node.path)
827 "be changed at %s" % node.path)
827 # We only mark node as *removed* - real removal is done by
828 # We only mark node as *removed* - real removal is done by
828 # commit method
829 # commit method
829 self.removed.append(node)
830 self.removed.append(node)
830
831
831 def reset(self):
832 def reset(self):
832 """
833 """
833 Resets this instance to initial state (cleans ``added``, ``changed``
834 Resets this instance to initial state (cleans ``added``, ``changed``
834 and ``removed`` lists).
835 and ``removed`` lists).
835 """
836 """
836 self.added = []
837 self.added = []
837 self.changed = []
838 self.changed = []
838 self.removed = []
839 self.removed = []
839 self.parents = []
840 self.parents = []
840
841
841 def get_ipaths(self):
842 def get_ipaths(self):
842 """
843 """
843 Returns generator of paths from nodes marked as added, changed or
844 Returns generator of paths from nodes marked as added, changed or
844 removed.
845 removed.
845 """
846 """
846 for node in chain(self.added, self.changed, self.removed):
847 for node in chain(self.added, self.changed, self.removed):
847 yield node.path
848 yield node.path
848
849
849 def get_paths(self):
850 def get_paths(self):
850 """
851 """
851 Returns list of paths from nodes marked as added, changed or removed.
852 Returns list of paths from nodes marked as added, changed or removed.
852 """
853 """
853 return list(self.get_ipaths())
854 return list(self.get_ipaths())
854
855
855 def check_integrity(self, parents=None):
856 def check_integrity(self, parents=None):
856 """
857 """
857 Checks in-memory changeset's integrity. Also, sets parents if not
858 Checks in-memory changeset's integrity. Also, sets parents if not
858 already set.
859 already set.
859
860
860 :raises CommitError: if any error occurs (i.e.
861 :raises CommitError: if any error occurs (i.e.
861 ``NodeDoesNotExistError``).
862 ``NodeDoesNotExistError``).
862 """
863 """
863 if not self.parents:
864 if not self.parents:
864 parents = parents or []
865 parents = parents or []
865 if len(parents) == 0:
866 if len(parents) == 0:
866 try:
867 try:
867 parents = [self.repository.get_changeset(), None]
868 parents = [self.repository.get_changeset(), None]
868 except EmptyRepositoryError:
869 except EmptyRepositoryError:
869 parents = [None, None]
870 parents = [None, None]
870 elif len(parents) == 1:
871 elif len(parents) == 1:
871 parents += [None]
872 parents += [None]
872 self.parents = parents
873 self.parents = parents
873
874
874 # Local parents, only if not None
875 # Local parents, only if not None
875 parents = [p for p in self.parents if p]
876 parents = [p for p in self.parents if p]
876
877
877 # Check nodes marked as added
878 # Check nodes marked as added
878 for p in parents:
879 for p in parents:
879 for node in self.added:
880 for node in self.added:
880 try:
881 try:
881 p.get_node(node.path)
882 p.get_node(node.path)
882 except NodeDoesNotExistError:
883 except NodeDoesNotExistError:
883 pass
884 pass
884 else:
885 else:
885 raise NodeAlreadyExistsError("Node at %s already exists "
886 raise NodeAlreadyExistsError("Node at %s already exists "
886 "at %s" % (node.path, p))
887 "at %s" % (node.path, p))
887
888
888 # Check nodes marked as changed
889 # Check nodes marked as changed
889 missing = set(self.changed)
890 missing = set(self.changed)
890 not_changed = set(self.changed)
891 not_changed = set(self.changed)
891 if self.changed and not parents:
892 if self.changed and not parents:
892 raise NodeDoesNotExistError(str(self.changed[0].path))
893 raise NodeDoesNotExistError(str(self.changed[0].path))
893 for p in parents:
894 for p in parents:
894 for node in self.changed:
895 for node in self.changed:
895 try:
896 try:
896 old = p.get_node(node.path)
897 old = p.get_node(node.path)
897 missing.remove(node)
898 missing.remove(node)
898 if old.content != node.content:
899 if old.content != node.content:
899 not_changed.remove(node)
900 not_changed.remove(node)
900 except NodeDoesNotExistError:
901 except NodeDoesNotExistError:
901 pass
902 pass
902 if self.changed and missing:
903 if self.changed and missing:
903 raise NodeDoesNotExistError("Node at %s is missing "
904 raise NodeDoesNotExistError("Node at %s is missing "
904 "(parents: %s)" % (node.path, parents))
905 "(parents: %s)" % (node.path, parents))
905
906
906 if self.changed and not_changed:
907 if self.changed and not_changed:
907 raise NodeNotChangedError("Node at %s wasn't actually changed "
908 raise NodeNotChangedError("Node at %s wasn't actually changed "
908 "since parents' changesets: %s" % (not_changed.pop().path,
909 "since parents' changesets: %s" % (not_changed.pop().path,
909 parents)
910 parents)
910 )
911 )
911
912
912 # Check nodes marked as removed
913 # Check nodes marked as removed
913 if self.removed and not parents:
914 if self.removed and not parents:
914 raise NodeDoesNotExistError("Cannot remove node at %s as there "
915 raise NodeDoesNotExistError("Cannot remove node at %s as there "
915 "were no parents specified" % self.removed[0].path)
916 "were no parents specified" % self.removed[0].path)
916 really_removed = set()
917 really_removed = set()
917 for p in parents:
918 for p in parents:
918 for node in self.removed:
919 for node in self.removed:
919 try:
920 try:
920 p.get_node(node.path)
921 p.get_node(node.path)
921 really_removed.add(node)
922 really_removed.add(node)
922 except ChangesetError:
923 except ChangesetError:
923 pass
924 pass
924 not_removed = set(self.removed) - really_removed
925 not_removed = set(self.removed) - really_removed
925 if not_removed:
926 if not_removed:
926 raise NodeDoesNotExistError("Cannot remove node at %s from "
927 raise NodeDoesNotExistError("Cannot remove node at %s from "
927 "following parents: %s" % (not_removed[0], parents))
928 "following parents: %s" % (not_removed[0], parents))
928
929
929 def commit(self, message, author, parents=None, branch=None, date=None,
930 def commit(self, message, author, parents=None, branch=None, date=None,
930 **kwargs):
931 **kwargs):
931 """
932 """
932 Performs in-memory commit (doesn't check workdir in any way) and
933 Performs in-memory commit (doesn't check workdir in any way) and
933 returns newly created ``Changeset``. Updates repository's
934 returns newly created ``Changeset``. Updates repository's
934 ``revisions``.
935 ``revisions``.
935
936
936 .. note::
937 .. note::
937 While overriding this method each backend's should call
938 While overriding this method each backend's should call
938 ``self.check_integrity(parents)`` in the first place.
939 ``self.check_integrity(parents)`` in the first place.
939
940
940 :param message: message of the commit
941 :param message: message of the commit
941 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
942 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
942 :param parents: single parent or sequence of parents from which commit
943 :param parents: single parent or sequence of parents from which commit
943 would be derieved
944 would be derieved
944 :param date: ``datetime.datetime`` instance. Defaults to
945 :param date: ``datetime.datetime`` instance. Defaults to
945 ``datetime.datetime.now()``.
946 ``datetime.datetime.now()``.
946 :param branch: branch name, as string. If none given, default backend's
947 :param branch: branch name, as string. If none given, default backend's
947 branch would be used.
948 branch would be used.
948
949
949 :raises ``CommitError``: if any error occurs while committing
950 :raises ``CommitError``: if any error occurs while committing
950 """
951 """
951 raise NotImplementedError
952 raise NotImplementedError
952
953
953
954
954 class EmptyChangeset(BaseChangeset):
955 class EmptyChangeset(BaseChangeset):
955 """
956 """
956 An dummy empty changeset. It's possible to pass hash when creating
957 An dummy empty changeset. It's possible to pass hash when creating
957 an EmptyChangeset
958 an EmptyChangeset
958 """
959 """
959
960
960 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
961 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
961 alias=None, revision=-1, message='', author='', date=''):
962 alias=None, revision=-1, message='', author='', date=''):
962 self._empty_cs = cs
963 self._empty_cs = cs
963 self.revision = revision
964 self.revision = revision
964 self.message = message
965 self.message = message
965 self.author = author
966 self.author = author
966 self.date = date
967 self.date = date
967 self.repository = repo
968 self.repository = repo
968 self.requested_revision = requested_revision
969 self.requested_revision = requested_revision
969 self.alias = alias
970 self.alias = alias
970
971
971 @LazyProperty
972 @LazyProperty
972 def raw_id(self):
973 def raw_id(self):
973 """
974 """
974 Returns raw string identifying this changeset, useful for web
975 Returns raw string identifying this changeset, useful for web
975 representation.
976 representation.
976 """
977 """
977
978
978 return self._empty_cs
979 return self._empty_cs
979
980
980 @LazyProperty
981 @LazyProperty
981 def branch(self):
982 def branch(self):
982 from rhodecode.lib.vcs.backends import get_backend
983 from rhodecode.lib.vcs.backends import get_backend
983 return get_backend(self.alias).DEFAULT_BRANCH_NAME
984 return get_backend(self.alias).DEFAULT_BRANCH_NAME
984
985
985 @LazyProperty
986 @LazyProperty
986 def short_id(self):
987 def short_id(self):
987 return self.raw_id[:12]
988 return self.raw_id[:12]
988
989
989 def get_file_changeset(self, path):
990 def get_file_changeset(self, path):
990 return self
991 return self
991
992
992 def get_file_content(self, path):
993 def get_file_content(self, path):
993 return u''
994 return u''
994
995
995 def get_file_size(self, path):
996 def get_file_size(self, path):
996 return 0
997 return 0
@@ -1,1872 +1,1908 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47
47
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 safe_unicode, remove_suffix, remove_prefix
49 safe_unicode, remove_suffix, remove_prefix
50 from rhodecode.lib.compat import json
50 from rhodecode.lib.compat import json
51 from rhodecode.lib.caching_query import FromCache
51 from rhodecode.lib.caching_query import FromCache
52
52
53 from rhodecode.model.meta import Base, Session
53 from rhodecode.model.meta import Base, Session
54
54
55 URL_SEP = '/'
55 URL_SEP = '/'
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58 #==============================================================================
58 #==============================================================================
59 # BASE CLASSES
59 # BASE CLASSES
60 #==============================================================================
60 #==============================================================================
61
61
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63
63
64
64
65 class BaseModel(object):
65 class BaseModel(object):
66 """
66 """
67 Base Model for all classess
67 Base Model for all classess
68 """
68 """
69
69
70 @classmethod
70 @classmethod
71 def _get_keys(cls):
71 def _get_keys(cls):
72 """return column names for this model """
72 """return column names for this model """
73 return class_mapper(cls).c.keys()
73 return class_mapper(cls).c.keys()
74
74
75 def get_dict(self):
75 def get_dict(self):
76 """
76 """
77 return dict with keys and values corresponding
77 return dict with keys and values corresponding
78 to this model data """
78 to this model data """
79
79
80 d = {}
80 d = {}
81 for k in self._get_keys():
81 for k in self._get_keys():
82 d[k] = getattr(self, k)
82 d[k] = getattr(self, k)
83
83
84 # also use __json__() if present to get additional fields
84 # also use __json__() if present to get additional fields
85 _json_attr = getattr(self, '__json__', None)
85 _json_attr = getattr(self, '__json__', None)
86 if _json_attr:
86 if _json_attr:
87 # update with attributes from __json__
87 # update with attributes from __json__
88 if callable(_json_attr):
88 if callable(_json_attr):
89 _json_attr = _json_attr()
89 _json_attr = _json_attr()
90 for k, val in _json_attr.iteritems():
90 for k, val in _json_attr.iteritems():
91 d[k] = val
91 d[k] = val
92 return d
92 return d
93
93
94 def get_appstruct(self):
94 def get_appstruct(self):
95 """return list with keys and values tupples corresponding
95 """return list with keys and values tupples corresponding
96 to this model data """
96 to this model data """
97
97
98 l = []
98 l = []
99 for k in self._get_keys():
99 for k in self._get_keys():
100 l.append((k, getattr(self, k),))
100 l.append((k, getattr(self, k),))
101 return l
101 return l
102
102
103 def populate_obj(self, populate_dict):
103 def populate_obj(self, populate_dict):
104 """populate model with data from given populate_dict"""
104 """populate model with data from given populate_dict"""
105
105
106 for k in self._get_keys():
106 for k in self._get_keys():
107 if k in populate_dict:
107 if k in populate_dict:
108 setattr(self, k, populate_dict[k])
108 setattr(self, k, populate_dict[k])
109
109
110 @classmethod
110 @classmethod
111 def query(cls):
111 def query(cls):
112 return Session().query(cls)
112 return Session().query(cls)
113
113
114 @classmethod
114 @classmethod
115 def get(cls, id_):
115 def get(cls, id_):
116 if id_:
116 if id_:
117 return cls.query().get(id_)
117 return cls.query().get(id_)
118
118
119 @classmethod
119 @classmethod
120 def get_or_404(cls, id_):
120 def get_or_404(cls, id_):
121 try:
121 try:
122 id_ = int(id_)
122 id_ = int(id_)
123 except (TypeError, ValueError):
123 except (TypeError, ValueError):
124 raise HTTPNotFound
124 raise HTTPNotFound
125
125
126 res = cls.query().get(id_)
126 res = cls.query().get(id_)
127 if not res:
127 if not res:
128 raise HTTPNotFound
128 raise HTTPNotFound
129 return res
129 return res
130
130
131 @classmethod
131 @classmethod
132 def getAll(cls):
132 def getAll(cls):
133 return cls.query().all()
133 return cls.query().all()
134
134
135 @classmethod
135 @classmethod
136 def delete(cls, id_):
136 def delete(cls, id_):
137 obj = cls.query().get(id_)
137 obj = cls.query().get(id_)
138 Session().delete(obj)
138 Session().delete(obj)
139
139
140 def __repr__(self):
140 def __repr__(self):
141 if hasattr(self, '__unicode__'):
141 if hasattr(self, '__unicode__'):
142 # python repr needs to return str
142 # python repr needs to return str
143 return safe_str(self.__unicode__())
143 return safe_str(self.__unicode__())
144 return '<DB:%s>' % (self.__class__.__name__)
144 return '<DB:%s>' % (self.__class__.__name__)
145
145
146
146
147 class RhodeCodeSetting(Base, BaseModel):
147 class RhodeCodeSetting(Base, BaseModel):
148 __tablename__ = 'rhodecode_settings'
148 __tablename__ = 'rhodecode_settings'
149 __table_args__ = (
149 __table_args__ = (
150 UniqueConstraint('app_settings_name'),
150 UniqueConstraint('app_settings_name'),
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 'mysql_charset': 'utf8'}
152 'mysql_charset': 'utf8'}
153 )
153 )
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157
157
158 def __init__(self, k='', v=''):
158 def __init__(self, k='', v=''):
159 self.app_settings_name = k
159 self.app_settings_name = k
160 self.app_settings_value = v
160 self.app_settings_value = v
161
161
162 @validates('_app_settings_value')
162 @validates('_app_settings_value')
163 def validate_settings_value(self, key, val):
163 def validate_settings_value(self, key, val):
164 assert type(val) == unicode
164 assert type(val) == unicode
165 return val
165 return val
166
166
167 @hybrid_property
167 @hybrid_property
168 def app_settings_value(self):
168 def app_settings_value(self):
169 v = self._app_settings_value
169 v = self._app_settings_value
170 if self.app_settings_name in ["ldap_active",
170 if self.app_settings_name in ["ldap_active",
171 "default_repo_enable_statistics",
171 "default_repo_enable_statistics",
172 "default_repo_enable_locking",
172 "default_repo_enable_locking",
173 "default_repo_private",
173 "default_repo_private",
174 "default_repo_enable_downloads"]:
174 "default_repo_enable_downloads"]:
175 v = str2bool(v)
175 v = str2bool(v)
176 return v
176 return v
177
177
178 @app_settings_value.setter
178 @app_settings_value.setter
179 def app_settings_value(self, val):
179 def app_settings_value(self, val):
180 """
180 """
181 Setter that will always make sure we use unicode in app_settings_value
181 Setter that will always make sure we use unicode in app_settings_value
182
182
183 :param val:
183 :param val:
184 """
184 """
185 self._app_settings_value = safe_unicode(val)
185 self._app_settings_value = safe_unicode(val)
186
186
187 def __unicode__(self):
187 def __unicode__(self):
188 return u"<%s('%s:%s')>" % (
188 return u"<%s('%s:%s')>" % (
189 self.__class__.__name__,
189 self.__class__.__name__,
190 self.app_settings_name, self.app_settings_value
190 self.app_settings_name, self.app_settings_value
191 )
191 )
192
192
193 @classmethod
193 @classmethod
194 def get_by_name(cls, key):
194 def get_by_name(cls, key):
195 return cls.query()\
195 return cls.query()\
196 .filter(cls.app_settings_name == key).scalar()
196 .filter(cls.app_settings_name == key).scalar()
197
197
198 @classmethod
198 @classmethod
199 def get_by_name_or_create(cls, key):
199 def get_by_name_or_create(cls, key):
200 res = cls.get_by_name(key)
200 res = cls.get_by_name(key)
201 if not res:
201 if not res:
202 res = cls(key)
202 res = cls(key)
203 return res
203 return res
204
204
205 @classmethod
205 @classmethod
206 def get_app_settings(cls, cache=False):
206 def get_app_settings(cls, cache=False):
207
207
208 ret = cls.query()
208 ret = cls.query()
209
209
210 if cache:
210 if cache:
211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212
212
213 if not ret:
213 if not ret:
214 raise Exception('Could not get application settings !')
214 raise Exception('Could not get application settings !')
215 settings = {}
215 settings = {}
216 for each in ret:
216 for each in ret:
217 settings['rhodecode_' + each.app_settings_name] = \
217 settings['rhodecode_' + each.app_settings_name] = \
218 each.app_settings_value
218 each.app_settings_value
219
219
220 return settings
220 return settings
221
221
222 @classmethod
222 @classmethod
223 def get_ldap_settings(cls, cache=False):
223 def get_ldap_settings(cls, cache=False):
224 ret = cls.query()\
224 ret = cls.query()\
225 .filter(cls.app_settings_name.startswith('ldap_')).all()
225 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 fd = {}
226 fd = {}
227 for row in ret:
227 for row in ret:
228 fd.update({row.app_settings_name: row.app_settings_value})
228 fd.update({row.app_settings_name: row.app_settings_value})
229
229
230 return fd
230 return fd
231
231
232 @classmethod
232 @classmethod
233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 ret = cls.query()\
234 ret = cls.query()\
235 .filter(cls.app_settings_name.startswith('default_')).all()
235 .filter(cls.app_settings_name.startswith('default_')).all()
236 fd = {}
236 fd = {}
237 for row in ret:
237 for row in ret:
238 key = row.app_settings_name
238 key = row.app_settings_name
239 if strip_prefix:
239 if strip_prefix:
240 key = remove_prefix(key, prefix='default_')
240 key = remove_prefix(key, prefix='default_')
241 fd.update({key: row.app_settings_value})
241 fd.update({key: row.app_settings_value})
242
242
243 return fd
243 return fd
244
244
245
245
246 class RhodeCodeUi(Base, BaseModel):
246 class RhodeCodeUi(Base, BaseModel):
247 __tablename__ = 'rhodecode_ui'
247 __tablename__ = 'rhodecode_ui'
248 __table_args__ = (
248 __table_args__ = (
249 UniqueConstraint('ui_key'),
249 UniqueConstraint('ui_key'),
250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 'mysql_charset': 'utf8'}
251 'mysql_charset': 'utf8'}
252 )
252 )
253
253
254 HOOK_UPDATE = 'changegroup.update'
254 HOOK_UPDATE = 'changegroup.update'
255 HOOK_REPO_SIZE = 'changegroup.repo_size'
255 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 HOOK_PUSH = 'changegroup.push_logger'
256 HOOK_PUSH = 'changegroup.push_logger'
257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 HOOK_PULL = 'outgoing.pull_logger'
258 HOOK_PULL = 'outgoing.pull_logger'
259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260
260
261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266
266
267 @classmethod
267 @classmethod
268 def get_by_key(cls, key):
268 def get_by_key(cls, key):
269 return cls.query().filter(cls.ui_key == key).scalar()
269 return cls.query().filter(cls.ui_key == key).scalar()
270
270
271 @classmethod
271 @classmethod
272 def get_builtin_hooks(cls):
272 def get_builtin_hooks(cls):
273 q = cls.query()
273 q = cls.query()
274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 return q.all()
277 return q.all()
278
278
279 @classmethod
279 @classmethod
280 def get_custom_hooks(cls):
280 def get_custom_hooks(cls):
281 q = cls.query()
281 q = cls.query()
282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 q = q.filter(cls.ui_section == 'hooks')
285 q = q.filter(cls.ui_section == 'hooks')
286 return q.all()
286 return q.all()
287
287
288 @classmethod
288 @classmethod
289 def get_repos_location(cls):
289 def get_repos_location(cls):
290 return cls.get_by_key('/').ui_value
290 return cls.get_by_key('/').ui_value
291
291
292 @classmethod
292 @classmethod
293 def create_or_update_hook(cls, key, val):
293 def create_or_update_hook(cls, key, val):
294 new_ui = cls.get_by_key(key) or cls()
294 new_ui = cls.get_by_key(key) or cls()
295 new_ui.ui_section = 'hooks'
295 new_ui.ui_section = 'hooks'
296 new_ui.ui_active = True
296 new_ui.ui_active = True
297 new_ui.ui_key = key
297 new_ui.ui_key = key
298 new_ui.ui_value = val
298 new_ui.ui_value = val
299
299
300 Session().add(new_ui)
300 Session().add(new_ui)
301
301
302 def __repr__(self):
302 def __repr__(self):
303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 self.ui_value)
304 self.ui_value)
305
305
306
306
307 class User(Base, BaseModel):
307 class User(Base, BaseModel):
308 __tablename__ = 'users'
308 __tablename__ = 'users'
309 __table_args__ = (
309 __table_args__ = (
310 UniqueConstraint('username'), UniqueConstraint('email'),
310 UniqueConstraint('username'), UniqueConstraint('email'),
311 Index('u_username_idx', 'username'),
311 Index('u_username_idx', 'username'),
312 Index('u_email_idx', 'email'),
312 Index('u_email_idx', 'email'),
313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 'mysql_charset': 'utf8'}
314 'mysql_charset': 'utf8'}
315 )
315 )
316 DEFAULT_USER = 'default'
316 DEFAULT_USER = 'default'
317 DEFAULT_PERMISSIONS = [
317 DEFAULT_PERMISSIONS = [
318 'hg.register.manual_activate', 'hg.create.repository',
318 'hg.register.manual_activate', 'hg.create.repository',
319 'hg.fork.repository', 'repository.read', 'group.read'
319 'hg.fork.repository', 'repository.read', 'group.read'
320 ]
320 ]
321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333
333
334 user_log = relationship('UserLog')
334 user_log = relationship('UserLog')
335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336
336
337 repositories = relationship('Repository')
337 repositories = relationship('Repository')
338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340
340
341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343
343
344 group_member = relationship('UsersGroupMember', cascade='all')
344 group_member = relationship('UsersGroupMember', cascade='all')
345
345
346 notifications = relationship('UserNotification', cascade='all')
346 notifications = relationship('UserNotification', cascade='all')
347 # notifications assigned to this user
347 # notifications assigned to this user
348 user_created_notifications = relationship('Notification', cascade='all')
348 user_created_notifications = relationship('Notification', cascade='all')
349 # comments created by this user
349 # comments created by this user
350 user_comments = relationship('ChangesetComment', cascade='all')
350 user_comments = relationship('ChangesetComment', cascade='all')
351 #extra emails for this user
351 #extra emails for this user
352 user_emails = relationship('UserEmailMap', cascade='all')
352 user_emails = relationship('UserEmailMap', cascade='all')
353
353
354 @hybrid_property
354 @hybrid_property
355 def email(self):
355 def email(self):
356 return self._email
356 return self._email
357
357
358 @email.setter
358 @email.setter
359 def email(self, val):
359 def email(self, val):
360 self._email = val.lower() if val else None
360 self._email = val.lower() if val else None
361
361
362 @property
362 @property
363 def firstname(self):
363 def firstname(self):
364 # alias for future
364 # alias for future
365 return self.name
365 return self.name
366
366
367 @property
367 @property
368 def emails(self):
368 def emails(self):
369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 return [self.email] + [x.email for x in other]
370 return [self.email] + [x.email for x in other]
371
371
372 @property
372 @property
373 def ip_addresses(self):
373 def ip_addresses(self):
374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 return [x.ip_addr for x in ret]
375 return [x.ip_addr for x in ret]
376
376
377 @property
377 @property
378 def username_and_name(self):
378 def username_and_name(self):
379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380
380
381 @property
381 @property
382 def full_name(self):
382 def full_name(self):
383 return '%s %s' % (self.firstname, self.lastname)
383 return '%s %s' % (self.firstname, self.lastname)
384
384
385 @property
385 @property
386 def full_name_or_username(self):
386 def full_name_or_username(self):
387 return ('%s %s' % (self.firstname, self.lastname)
387 return ('%s %s' % (self.firstname, self.lastname)
388 if (self.firstname and self.lastname) else self.username)
388 if (self.firstname and self.lastname) else self.username)
389
389
390 @property
390 @property
391 def full_contact(self):
391 def full_contact(self):
392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393
393
394 @property
394 @property
395 def short_contact(self):
395 def short_contact(self):
396 return '%s %s' % (self.firstname, self.lastname)
396 return '%s %s' % (self.firstname, self.lastname)
397
397
398 @property
398 @property
399 def is_admin(self):
399 def is_admin(self):
400 return self.admin
400 return self.admin
401
401
402 def __unicode__(self):
402 def __unicode__(self):
403 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
403 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
404 self.user_id, self.username)
404 self.user_id, self.username)
405
405
406 @classmethod
406 @classmethod
407 def get_by_username(cls, username, case_insensitive=False, cache=False):
407 def get_by_username(cls, username, case_insensitive=False, cache=False):
408 if case_insensitive:
408 if case_insensitive:
409 q = cls.query().filter(cls.username.ilike(username))
409 q = cls.query().filter(cls.username.ilike(username))
410 else:
410 else:
411 q = cls.query().filter(cls.username == username)
411 q = cls.query().filter(cls.username == username)
412
412
413 if cache:
413 if cache:
414 q = q.options(FromCache(
414 q = q.options(FromCache(
415 "sql_cache_short",
415 "sql_cache_short",
416 "get_user_%s" % _hash_key(username)
416 "get_user_%s" % _hash_key(username)
417 )
417 )
418 )
418 )
419 return q.scalar()
419 return q.scalar()
420
420
421 @classmethod
421 @classmethod
422 def get_by_api_key(cls, api_key, cache=False):
422 def get_by_api_key(cls, api_key, cache=False):
423 q = cls.query().filter(cls.api_key == api_key)
423 q = cls.query().filter(cls.api_key == api_key)
424
424
425 if cache:
425 if cache:
426 q = q.options(FromCache("sql_cache_short",
426 q = q.options(FromCache("sql_cache_short",
427 "get_api_key_%s" % api_key))
427 "get_api_key_%s" % api_key))
428 return q.scalar()
428 return q.scalar()
429
429
430 @classmethod
430 @classmethod
431 def get_by_email(cls, email, case_insensitive=False, cache=False):
431 def get_by_email(cls, email, case_insensitive=False, cache=False):
432 if case_insensitive:
432 if case_insensitive:
433 q = cls.query().filter(cls.email.ilike(email))
433 q = cls.query().filter(cls.email.ilike(email))
434 else:
434 else:
435 q = cls.query().filter(cls.email == email)
435 q = cls.query().filter(cls.email == email)
436
436
437 if cache:
437 if cache:
438 q = q.options(FromCache("sql_cache_short",
438 q = q.options(FromCache("sql_cache_short",
439 "get_email_key_%s" % email))
439 "get_email_key_%s" % email))
440
440
441 ret = q.scalar()
441 ret = q.scalar()
442 if ret is None:
442 if ret is None:
443 q = UserEmailMap.query()
443 q = UserEmailMap.query()
444 # try fetching in alternate email map
444 # try fetching in alternate email map
445 if case_insensitive:
445 if case_insensitive:
446 q = q.filter(UserEmailMap.email.ilike(email))
446 q = q.filter(UserEmailMap.email.ilike(email))
447 else:
447 else:
448 q = q.filter(UserEmailMap.email == email)
448 q = q.filter(UserEmailMap.email == email)
449 q = q.options(joinedload(UserEmailMap.user))
449 q = q.options(joinedload(UserEmailMap.user))
450 if cache:
450 if cache:
451 q = q.options(FromCache("sql_cache_short",
451 q = q.options(FromCache("sql_cache_short",
452 "get_email_map_key_%s" % email))
452 "get_email_map_key_%s" % email))
453 ret = getattr(q.scalar(), 'user', None)
453 ret = getattr(q.scalar(), 'user', None)
454
454
455 return ret
455 return ret
456
456
457 def update_lastlogin(self):
457 def update_lastlogin(self):
458 """Update user lastlogin"""
458 """Update user lastlogin"""
459 self.last_login = datetime.datetime.now()
459 self.last_login = datetime.datetime.now()
460 Session().add(self)
460 Session().add(self)
461 log.debug('updated user %s lastlogin' % self.username)
461 log.debug('updated user %s lastlogin' % self.username)
462
462
463 def get_api_data(self):
463 def get_api_data(self):
464 """
464 """
465 Common function for generating user related data for API
465 Common function for generating user related data for API
466 """
466 """
467 user = self
467 user = self
468 data = dict(
468 data = dict(
469 user_id=user.user_id,
469 user_id=user.user_id,
470 username=user.username,
470 username=user.username,
471 firstname=user.name,
471 firstname=user.name,
472 lastname=user.lastname,
472 lastname=user.lastname,
473 email=user.email,
473 email=user.email,
474 emails=user.emails,
474 emails=user.emails,
475 api_key=user.api_key,
475 api_key=user.api_key,
476 active=user.active,
476 active=user.active,
477 admin=user.admin,
477 admin=user.admin,
478 ldap_dn=user.ldap_dn,
478 ldap_dn=user.ldap_dn,
479 last_login=user.last_login,
479 last_login=user.last_login,
480 ip_addresses=user.ip_addresses
480 ip_addresses=user.ip_addresses
481 )
481 )
482 return data
482 return data
483
483
484 def __json__(self):
484 def __json__(self):
485 data = dict(
485 data = dict(
486 full_name=self.full_name,
486 full_name=self.full_name,
487 full_name_or_username=self.full_name_or_username,
487 full_name_or_username=self.full_name_or_username,
488 short_contact=self.short_contact,
488 short_contact=self.short_contact,
489 full_contact=self.full_contact
489 full_contact=self.full_contact
490 )
490 )
491 data.update(self.get_api_data())
491 data.update(self.get_api_data())
492 return data
492 return data
493
493
494
494
495 class UserEmailMap(Base, BaseModel):
495 class UserEmailMap(Base, BaseModel):
496 __tablename__ = 'user_email_map'
496 __tablename__ = 'user_email_map'
497 __table_args__ = (
497 __table_args__ = (
498 Index('uem_email_idx', 'email'),
498 Index('uem_email_idx', 'email'),
499 UniqueConstraint('email'),
499 UniqueConstraint('email'),
500 {'extend_existing': True, 'mysql_engine': 'InnoDB',
500 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 'mysql_charset': 'utf8'}
501 'mysql_charset': 'utf8'}
502 )
502 )
503 __mapper_args__ = {}
503 __mapper_args__ = {}
504
504
505 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
505 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
506 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
506 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
507 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
507 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
508 user = relationship('User', lazy='joined')
508 user = relationship('User', lazy='joined')
509
509
510 @validates('_email')
510 @validates('_email')
511 def validate_email(self, key, email):
511 def validate_email(self, key, email):
512 # check if this email is not main one
512 # check if this email is not main one
513 main_email = Session().query(User).filter(User.email == email).scalar()
513 main_email = Session().query(User).filter(User.email == email).scalar()
514 if main_email is not None:
514 if main_email is not None:
515 raise AttributeError('email %s is present is user table' % email)
515 raise AttributeError('email %s is present is user table' % email)
516 return email
516 return email
517
517
518 @hybrid_property
518 @hybrid_property
519 def email(self):
519 def email(self):
520 return self._email
520 return self._email
521
521
522 @email.setter
522 @email.setter
523 def email(self, val):
523 def email(self, val):
524 self._email = val.lower() if val else None
524 self._email = val.lower() if val else None
525
525
526
526
527 class UserIpMap(Base, BaseModel):
527 class UserIpMap(Base, BaseModel):
528 __tablename__ = 'user_ip_map'
528 __tablename__ = 'user_ip_map'
529 __table_args__ = (
529 __table_args__ = (
530 UniqueConstraint('user_id', 'ip_addr'),
530 UniqueConstraint('user_id', 'ip_addr'),
531 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 {'extend_existing': True, 'mysql_engine': 'InnoDB',
532 'mysql_charset': 'utf8'}
532 'mysql_charset': 'utf8'}
533 )
533 )
534 __mapper_args__ = {}
534 __mapper_args__ = {}
535
535
536 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
538 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
538 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
539 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
539 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
540 user = relationship('User', lazy='joined')
540 user = relationship('User', lazy='joined')
541
541
542 @classmethod
542 @classmethod
543 def _get_ip_range(cls, ip_addr):
543 def _get_ip_range(cls, ip_addr):
544 from rhodecode.lib import ipaddr
544 from rhodecode.lib import ipaddr
545 net = ipaddr.IPv4Network(ip_addr)
545 net = ipaddr.IPv4Network(ip_addr)
546 return [str(net.network), str(net.broadcast)]
546 return [str(net.network), str(net.broadcast)]
547
547
548 def __json__(self):
548 def __json__(self):
549 return dict(
549 return dict(
550 ip_addr=self.ip_addr,
550 ip_addr=self.ip_addr,
551 ip_range=self._get_ip_range(self.ip_addr)
551 ip_range=self._get_ip_range(self.ip_addr)
552 )
552 )
553
553
554
554
555 class UserLog(Base, BaseModel):
555 class UserLog(Base, BaseModel):
556 __tablename__ = 'user_logs'
556 __tablename__ = 'user_logs'
557 __table_args__ = (
557 __table_args__ = (
558 {'extend_existing': True, 'mysql_engine': 'InnoDB',
558 {'extend_existing': True, 'mysql_engine': 'InnoDB',
559 'mysql_charset': 'utf8'},
559 'mysql_charset': 'utf8'},
560 )
560 )
561 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
561 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
562 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
562 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
563 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
563 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
564 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
564 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
565 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
565 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
566 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
566 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
567 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
567 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
568 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
568 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
569
569
570 @property
570 @property
571 def action_as_day(self):
571 def action_as_day(self):
572 return datetime.date(*self.action_date.timetuple()[:3])
572 return datetime.date(*self.action_date.timetuple()[:3])
573
573
574 user = relationship('User')
574 user = relationship('User')
575 repository = relationship('Repository', cascade='')
575 repository = relationship('Repository', cascade='')
576
576
577
577
578 class UsersGroup(Base, BaseModel):
578 class UsersGroup(Base, BaseModel):
579 __tablename__ = 'users_groups'
579 __tablename__ = 'users_groups'
580 __table_args__ = (
580 __table_args__ = (
581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
582 'mysql_charset': 'utf8'},
582 'mysql_charset': 'utf8'},
583 )
583 )
584
584
585 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
585 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
586 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
586 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
587 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
587 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
588 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
588 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
589
589
590 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
590 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
591 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
591 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
592 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
592 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
593
593
594 def __unicode__(self):
594 def __unicode__(self):
595 return u'<userGroup(%s)>' % (self.users_group_name)
595 return u'<userGroup(%s)>' % (self.users_group_name)
596
596
597 @classmethod
597 @classmethod
598 def get_by_group_name(cls, group_name, cache=False,
598 def get_by_group_name(cls, group_name, cache=False,
599 case_insensitive=False):
599 case_insensitive=False):
600 if case_insensitive:
600 if case_insensitive:
601 q = cls.query().filter(cls.users_group_name.ilike(group_name))
601 q = cls.query().filter(cls.users_group_name.ilike(group_name))
602 else:
602 else:
603 q = cls.query().filter(cls.users_group_name == group_name)
603 q = cls.query().filter(cls.users_group_name == group_name)
604 if cache:
604 if cache:
605 q = q.options(FromCache(
605 q = q.options(FromCache(
606 "sql_cache_short",
606 "sql_cache_short",
607 "get_user_%s" % _hash_key(group_name)
607 "get_user_%s" % _hash_key(group_name)
608 )
608 )
609 )
609 )
610 return q.scalar()
610 return q.scalar()
611
611
612 @classmethod
612 @classmethod
613 def get(cls, users_group_id, cache=False):
613 def get(cls, users_group_id, cache=False):
614 users_group = cls.query()
614 users_group = cls.query()
615 if cache:
615 if cache:
616 users_group = users_group.options(FromCache("sql_cache_short",
616 users_group = users_group.options(FromCache("sql_cache_short",
617 "get_users_group_%s" % users_group_id))
617 "get_users_group_%s" % users_group_id))
618 return users_group.get(users_group_id)
618 return users_group.get(users_group_id)
619
619
620 def get_api_data(self):
620 def get_api_data(self):
621 users_group = self
621 users_group = self
622
622
623 data = dict(
623 data = dict(
624 users_group_id=users_group.users_group_id,
624 users_group_id=users_group.users_group_id,
625 group_name=users_group.users_group_name,
625 group_name=users_group.users_group_name,
626 active=users_group.users_group_active,
626 active=users_group.users_group_active,
627 )
627 )
628
628
629 return data
629 return data
630
630
631
631
632 class UsersGroupMember(Base, BaseModel):
632 class UsersGroupMember(Base, BaseModel):
633 __tablename__ = 'users_groups_members'
633 __tablename__ = 'users_groups_members'
634 __table_args__ = (
634 __table_args__ = (
635 {'extend_existing': True, 'mysql_engine': 'InnoDB',
635 {'extend_existing': True, 'mysql_engine': 'InnoDB',
636 'mysql_charset': 'utf8'},
636 'mysql_charset': 'utf8'},
637 )
637 )
638
638
639 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
639 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
641 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
641 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
642
642
643 user = relationship('User', lazy='joined')
643 user = relationship('User', lazy='joined')
644 users_group = relationship('UsersGroup')
644 users_group = relationship('UsersGroup')
645
645
646 def __init__(self, gr_id='', u_id=''):
646 def __init__(self, gr_id='', u_id=''):
647 self.users_group_id = gr_id
647 self.users_group_id = gr_id
648 self.user_id = u_id
648 self.user_id = u_id
649
649
650
650
651 class Repository(Base, BaseModel):
651 class Repository(Base, BaseModel):
652 __tablename__ = 'repositories'
652 __tablename__ = 'repositories'
653 __table_args__ = (
653 __table_args__ = (
654 UniqueConstraint('repo_name'),
654 UniqueConstraint('repo_name'),
655 Index('r_repo_name_idx', 'repo_name'),
655 Index('r_repo_name_idx', 'repo_name'),
656 {'extend_existing': True, 'mysql_engine': 'InnoDB',
656 {'extend_existing': True, 'mysql_engine': 'InnoDB',
657 'mysql_charset': 'utf8'},
657 'mysql_charset': 'utf8'},
658 )
658 )
659
659
660 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
660 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
661 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
661 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
662 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
662 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
663 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
663 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
664 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
664 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
665 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
665 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
666 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
666 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
667 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
667 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
668 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
668 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
669 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
669 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
670 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
670 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
671 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
671 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
672 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
672 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
673 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
673 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
674 #changeset_cache = Column("changeset_cache", LargeBinary(), nullable=False) #JSON data
674 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
675
675
676 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
676 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
677 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
677 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
678
678
679 user = relationship('User')
679 user = relationship('User')
680 fork = relationship('Repository', remote_side=repo_id)
680 fork = relationship('Repository', remote_side=repo_id)
681 group = relationship('RepoGroup')
681 group = relationship('RepoGroup')
682 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
682 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
683 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
683 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
684 stats = relationship('Statistics', cascade='all', uselist=False)
684 stats = relationship('Statistics', cascade='all', uselist=False)
685
685
686 followers = relationship('UserFollowing',
686 followers = relationship('UserFollowing',
687 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
687 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
688 cascade='all')
688 cascade='all')
689
689
690 logs = relationship('UserLog')
690 logs = relationship('UserLog')
691 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
691 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
692
692
693 pull_requests_org = relationship('PullRequest',
693 pull_requests_org = relationship('PullRequest',
694 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
694 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
695 cascade="all, delete, delete-orphan")
695 cascade="all, delete, delete-orphan")
696
696
697 pull_requests_other = relationship('PullRequest',
697 pull_requests_other = relationship('PullRequest',
698 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
698 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
699 cascade="all, delete, delete-orphan")
699 cascade="all, delete, delete-orphan")
700
700
701 def __unicode__(self):
701 def __unicode__(self):
702 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
702 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
703 self.repo_name)
703 self.repo_name)
704
704
705 @hybrid_property
705 @hybrid_property
706 def locked(self):
706 def locked(self):
707 # always should return [user_id, timelocked]
707 # always should return [user_id, timelocked]
708 if self._locked:
708 if self._locked:
709 _lock_info = self._locked.split(':')
709 _lock_info = self._locked.split(':')
710 return int(_lock_info[0]), _lock_info[1]
710 return int(_lock_info[0]), _lock_info[1]
711 return [None, None]
711 return [None, None]
712
712
713 @locked.setter
713 @locked.setter
714 def locked(self, val):
714 def locked(self, val):
715 if val and isinstance(val, (list, tuple)):
715 if val and isinstance(val, (list, tuple)):
716 self._locked = ':'.join(map(str, val))
716 self._locked = ':'.join(map(str, val))
717 else:
717 else:
718 self._locked = None
718 self._locked = None
719
719
720 @hybrid_property
721 def changeset_cache(self):
722 from rhodecode.lib.vcs.backends.base import EmptyChangeset
723 dummy = EmptyChangeset().__json__()
724 if not self._changeset_cache:
725 return dummy
726 try:
727 return json.loads(self._changeset_cache)
728 except TypeError:
729 return dummy
730
731 @changeset_cache.setter
732 def changeset_cache(self, val):
733 try:
734 self._changeset_cache = json.dumps(val)
735 except:
736 log.error(traceback.format_exc())
737
720 @classmethod
738 @classmethod
721 def url_sep(cls):
739 def url_sep(cls):
722 return URL_SEP
740 return URL_SEP
723
741
724 @classmethod
742 @classmethod
725 def get_by_repo_name(cls, repo_name):
743 def get_by_repo_name(cls, repo_name):
726 q = Session().query(cls).filter(cls.repo_name == repo_name)
744 q = Session().query(cls).filter(cls.repo_name == repo_name)
727 q = q.options(joinedload(Repository.fork))\
745 q = q.options(joinedload(Repository.fork))\
728 .options(joinedload(Repository.user))\
746 .options(joinedload(Repository.user))\
729 .options(joinedload(Repository.group))
747 .options(joinedload(Repository.group))
730 return q.scalar()
748 return q.scalar()
731
749
732 @classmethod
750 @classmethod
733 def get_by_full_path(cls, repo_full_path):
751 def get_by_full_path(cls, repo_full_path):
734 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
752 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
735 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
753 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
736
754
737 @classmethod
755 @classmethod
738 def get_repo_forks(cls, repo_id):
756 def get_repo_forks(cls, repo_id):
739 return cls.query().filter(Repository.fork_id == repo_id)
757 return cls.query().filter(Repository.fork_id == repo_id)
740
758
741 @classmethod
759 @classmethod
742 def base_path(cls):
760 def base_path(cls):
743 """
761 """
744 Returns base path when all repos are stored
762 Returns base path when all repos are stored
745
763
746 :param cls:
764 :param cls:
747 """
765 """
748 q = Session().query(RhodeCodeUi)\
766 q = Session().query(RhodeCodeUi)\
749 .filter(RhodeCodeUi.ui_key == cls.url_sep())
767 .filter(RhodeCodeUi.ui_key == cls.url_sep())
750 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
768 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
751 return q.one().ui_value
769 return q.one().ui_value
752
770
753 @property
771 @property
754 def forks(self):
772 def forks(self):
755 """
773 """
756 Return forks of this repo
774 Return forks of this repo
757 """
775 """
758 return Repository.get_repo_forks(self.repo_id)
776 return Repository.get_repo_forks(self.repo_id)
759
777
760 @property
778 @property
761 def parent(self):
779 def parent(self):
762 """
780 """
763 Returns fork parent
781 Returns fork parent
764 """
782 """
765 return self.fork
783 return self.fork
766
784
767 @property
785 @property
768 def just_name(self):
786 def just_name(self):
769 return self.repo_name.split(Repository.url_sep())[-1]
787 return self.repo_name.split(Repository.url_sep())[-1]
770
788
771 @property
789 @property
772 def groups_with_parents(self):
790 def groups_with_parents(self):
773 groups = []
791 groups = []
774 if self.group is None:
792 if self.group is None:
775 return groups
793 return groups
776
794
777 cur_gr = self.group
795 cur_gr = self.group
778 groups.insert(0, cur_gr)
796 groups.insert(0, cur_gr)
779 while 1:
797 while 1:
780 gr = getattr(cur_gr, 'parent_group', None)
798 gr = getattr(cur_gr, 'parent_group', None)
781 cur_gr = cur_gr.parent_group
799 cur_gr = cur_gr.parent_group
782 if gr is None:
800 if gr is None:
783 break
801 break
784 groups.insert(0, gr)
802 groups.insert(0, gr)
785
803
786 return groups
804 return groups
787
805
788 @property
806 @property
789 def groups_and_repo(self):
807 def groups_and_repo(self):
790 return self.groups_with_parents, self.just_name
808 return self.groups_with_parents, self.just_name
791
809
792 @LazyProperty
810 @LazyProperty
793 def repo_path(self):
811 def repo_path(self):
794 """
812 """
795 Returns base full path for that repository means where it actually
813 Returns base full path for that repository means where it actually
796 exists on a filesystem
814 exists on a filesystem
797 """
815 """
798 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
816 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
799 Repository.url_sep())
817 Repository.url_sep())
800 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
818 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
801 return q.one().ui_value
819 return q.one().ui_value
802
820
803 @property
821 @property
804 def repo_full_path(self):
822 def repo_full_path(self):
805 p = [self.repo_path]
823 p = [self.repo_path]
806 # we need to split the name by / since this is how we store the
824 # we need to split the name by / since this is how we store the
807 # names in the database, but that eventually needs to be converted
825 # names in the database, but that eventually needs to be converted
808 # into a valid system path
826 # into a valid system path
809 p += self.repo_name.split(Repository.url_sep())
827 p += self.repo_name.split(Repository.url_sep())
810 return os.path.join(*p)
828 return os.path.join(*p)
811
829
812 @property
830 @property
813 def cache_keys(self):
831 def cache_keys(self):
814 """
832 """
815 Returns associated cache keys for that repo
833 Returns associated cache keys for that repo
816 """
834 """
817 return CacheInvalidation.query()\
835 return CacheInvalidation.query()\
818 .filter(CacheInvalidation.cache_args == self.repo_name)\
836 .filter(CacheInvalidation.cache_args == self.repo_name)\
819 .order_by(CacheInvalidation.cache_key)\
837 .order_by(CacheInvalidation.cache_key)\
820 .all()
838 .all()
821
839
822 def get_new_name(self, repo_name):
840 def get_new_name(self, repo_name):
823 """
841 """
824 returns new full repository name based on assigned group and new new
842 returns new full repository name based on assigned group and new new
825
843
826 :param group_name:
844 :param group_name:
827 """
845 """
828 path_prefix = self.group.full_path_splitted if self.group else []
846 path_prefix = self.group.full_path_splitted if self.group else []
829 return Repository.url_sep().join(path_prefix + [repo_name])
847 return Repository.url_sep().join(path_prefix + [repo_name])
830
848
831 @property
849 @property
832 def _ui(self):
850 def _ui(self):
833 """
851 """
834 Creates an db based ui object for this repository
852 Creates an db based ui object for this repository
835 """
853 """
836 from rhodecode.lib.utils import make_ui
854 from rhodecode.lib.utils import make_ui
837 return make_ui('db', clear_session=False)
855 return make_ui('db', clear_session=False)
838
856
839 @classmethod
857 @classmethod
840 def inject_ui(cls, repo, extras={}):
858 def inject_ui(cls, repo, extras={}):
841 from rhodecode.lib.vcs.backends.hg import MercurialRepository
859 from rhodecode.lib.vcs.backends.hg import MercurialRepository
842 from rhodecode.lib.vcs.backends.git import GitRepository
860 from rhodecode.lib.vcs.backends.git import GitRepository
843 required = (MercurialRepository, GitRepository)
861 required = (MercurialRepository, GitRepository)
844 if not isinstance(repo, required):
862 if not isinstance(repo, required):
845 raise Exception('repo must be instance of %s' % required)
863 raise Exception('repo must be instance of %s' % required)
846
864
847 # inject ui extra param to log this action via push logger
865 # inject ui extra param to log this action via push logger
848 for k, v in extras.items():
866 for k, v in extras.items():
849 repo._repo.ui.setconfig('rhodecode_extras', k, v)
867 repo._repo.ui.setconfig('rhodecode_extras', k, v)
850
868
851 @classmethod
869 @classmethod
852 def is_valid(cls, repo_name):
870 def is_valid(cls, repo_name):
853 """
871 """
854 returns True if given repo name is a valid filesystem repository
872 returns True if given repo name is a valid filesystem repository
855
873
856 :param cls:
874 :param cls:
857 :param repo_name:
875 :param repo_name:
858 """
876 """
859 from rhodecode.lib.utils import is_valid_repo
877 from rhodecode.lib.utils import is_valid_repo
860
878
861 return is_valid_repo(repo_name, cls.base_path())
879 return is_valid_repo(repo_name, cls.base_path())
862
880
863 def get_api_data(self):
881 def get_api_data(self):
864 """
882 """
865 Common function for generating repo api data
883 Common function for generating repo api data
866
884
867 """
885 """
868 repo = self
886 repo = self
869 data = dict(
887 data = dict(
870 repo_id=repo.repo_id,
888 repo_id=repo.repo_id,
871 repo_name=repo.repo_name,
889 repo_name=repo.repo_name,
872 repo_type=repo.repo_type,
890 repo_type=repo.repo_type,
873 clone_uri=repo.clone_uri,
891 clone_uri=repo.clone_uri,
874 private=repo.private,
892 private=repo.private,
875 created_on=repo.created_on,
893 created_on=repo.created_on,
876 description=repo.description,
894 description=repo.description,
877 landing_rev=repo.landing_rev,
895 landing_rev=repo.landing_rev,
878 owner=repo.user.username,
896 owner=repo.user.username,
879 fork_of=repo.fork.repo_name if repo.fork else None,
897 fork_of=repo.fork.repo_name if repo.fork else None,
880 enable_statistics=repo.enable_statistics,
898 enable_statistics=repo.enable_statistics,
881 enable_locking=repo.enable_locking,
899 enable_locking=repo.enable_locking,
882 enable_downloads=repo.enable_downloads
900 enable_downloads=repo.enable_downloads
883 )
901 )
884
902
885 return data
903 return data
886
904
887 @classmethod
905 @classmethod
888 def lock(cls, repo, user_id):
906 def lock(cls, repo, user_id):
889 repo.locked = [user_id, time.time()]
907 repo.locked = [user_id, time.time()]
890 Session().add(repo)
908 Session().add(repo)
891 Session().commit()
909 Session().commit()
892
910
893 @classmethod
911 @classmethod
894 def unlock(cls, repo):
912 def unlock(cls, repo):
895 repo.locked = None
913 repo.locked = None
896 Session().add(repo)
914 Session().add(repo)
897 Session().commit()
915 Session().commit()
898
916
899 @property
917 @property
900 def last_db_change(self):
918 def last_db_change(self):
901 return self.updated_on
919 return self.updated_on
902
920
903 #==========================================================================
921 #==========================================================================
904 # SCM PROPERTIES
922 # SCM PROPERTIES
905 #==========================================================================
923 #==========================================================================
906
924
907 def get_changeset(self, rev=None):
925 def get_changeset(self, rev=None):
908 return get_changeset_safe(self.scm_instance, rev)
926 return get_changeset_safe(self.scm_instance, rev)
909
927
910 def get_landing_changeset(self):
928 def get_landing_changeset(self):
911 """
929 """
912 Returns landing changeset, or if that doesn't exist returns the tip
930 Returns landing changeset, or if that doesn't exist returns the tip
913 """
931 """
914 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
932 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
915 return cs
933 return cs
916
934
917 def update_last_change(self, last_change=None):
935 def update_changeset_cache(self, cs_cache=None):
918 if last_change is None:
936 """
919 last_change = datetime.datetime.now()
937 Update cache of last changeset for repository, keys should be::
920 if self.updated_on is None or self.updated_on != last_change:
938
921 log.debug('updated repo %s with new date %s' % (self, last_change))
939 short_id
940 raw_id
941 revision
942 message
943 date
944 author
945
946 :param cs_cache:
947 """
948 from rhodecode.lib.vcs.backends.base import BaseChangeset
949 if cs_cache is None:
950 cs_cache = self.get_changeset()
951 if isinstance(cs_cache, BaseChangeset):
952 cs_cache = cs_cache.__json__()
953
954 if cs_cache != self.changeset_cache:
955 last_change = cs_cache.get('date') or self.last_change
956 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
922 self.updated_on = last_change
957 self.updated_on = last_change
958 self.changeset_cache = cs_cache
923 Session().add(self)
959 Session().add(self)
924 Session().commit()
960 Session().commit()
925
961
926 @property
962 @property
927 def tip(self):
963 def tip(self):
928 return self.get_changeset('tip')
964 return self.get_changeset('tip')
929
965
930 @property
966 @property
931 def author(self):
967 def author(self):
932 return self.tip.author
968 return self.tip.author
933
969
934 @property
970 @property
935 def last_change(self):
971 def last_change(self):
936 return self.scm_instance.last_change
972 return self.scm_instance.last_change
937
973
938 def get_comments(self, revisions=None):
974 def get_comments(self, revisions=None):
939 """
975 """
940 Returns comments for this repository grouped by revisions
976 Returns comments for this repository grouped by revisions
941
977
942 :param revisions: filter query by revisions only
978 :param revisions: filter query by revisions only
943 """
979 """
944 cmts = ChangesetComment.query()\
980 cmts = ChangesetComment.query()\
945 .filter(ChangesetComment.repo == self)
981 .filter(ChangesetComment.repo == self)
946 if revisions:
982 if revisions:
947 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
983 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
948 grouped = defaultdict(list)
984 grouped = defaultdict(list)
949 for cmt in cmts.all():
985 for cmt in cmts.all():
950 grouped[cmt.revision].append(cmt)
986 grouped[cmt.revision].append(cmt)
951 return grouped
987 return grouped
952
988
953 def statuses(self, revisions=None):
989 def statuses(self, revisions=None):
954 """
990 """
955 Returns statuses for this repository
991 Returns statuses for this repository
956
992
957 :param revisions: list of revisions to get statuses for
993 :param revisions: list of revisions to get statuses for
958 :type revisions: list
994 :type revisions: list
959 """
995 """
960
996
961 statuses = ChangesetStatus.query()\
997 statuses = ChangesetStatus.query()\
962 .filter(ChangesetStatus.repo == self)\
998 .filter(ChangesetStatus.repo == self)\
963 .filter(ChangesetStatus.version == 0)
999 .filter(ChangesetStatus.version == 0)
964 if revisions:
1000 if revisions:
965 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1001 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
966 grouped = {}
1002 grouped = {}
967
1003
968 #maybe we have open new pullrequest without a status ?
1004 #maybe we have open new pullrequest without a status ?
969 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1005 stat = ChangesetStatus.STATUS_UNDER_REVIEW
970 status_lbl = ChangesetStatus.get_status_lbl(stat)
1006 status_lbl = ChangesetStatus.get_status_lbl(stat)
971 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1007 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
972 for rev in pr.revisions:
1008 for rev in pr.revisions:
973 pr_id = pr.pull_request_id
1009 pr_id = pr.pull_request_id
974 pr_repo = pr.other_repo.repo_name
1010 pr_repo = pr.other_repo.repo_name
975 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1011 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
976
1012
977 for stat in statuses.all():
1013 for stat in statuses.all():
978 pr_id = pr_repo = None
1014 pr_id = pr_repo = None
979 if stat.pull_request:
1015 if stat.pull_request:
980 pr_id = stat.pull_request.pull_request_id
1016 pr_id = stat.pull_request.pull_request_id
981 pr_repo = stat.pull_request.other_repo.repo_name
1017 pr_repo = stat.pull_request.other_repo.repo_name
982 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1018 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
983 pr_id, pr_repo]
1019 pr_id, pr_repo]
984 return grouped
1020 return grouped
985
1021
986 #==========================================================================
1022 #==========================================================================
987 # SCM CACHE INSTANCE
1023 # SCM CACHE INSTANCE
988 #==========================================================================
1024 #==========================================================================
989
1025
990 @property
1026 @property
991 def invalidate(self):
1027 def invalidate(self):
992 return CacheInvalidation.invalidate(self.repo_name)
1028 return CacheInvalidation.invalidate(self.repo_name)
993
1029
994 def set_invalidate(self):
1030 def set_invalidate(self):
995 """
1031 """
996 set a cache for invalidation for this instance
1032 set a cache for invalidation for this instance
997 """
1033 """
998 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1034 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
999
1035
1000 @LazyProperty
1036 @LazyProperty
1001 def scm_instance(self):
1037 def scm_instance(self):
1002 import rhodecode
1038 import rhodecode
1003 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1039 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1004 if full_cache:
1040 if full_cache:
1005 return self.scm_instance_cached()
1041 return self.scm_instance_cached()
1006 return self.__get_instance()
1042 return self.__get_instance()
1007
1043
1008 def scm_instance_cached(self, cache_map=None):
1044 def scm_instance_cached(self, cache_map=None):
1009 @cache_region('long_term')
1045 @cache_region('long_term')
1010 def _c(repo_name):
1046 def _c(repo_name):
1011 return self.__get_instance()
1047 return self.__get_instance()
1012 rn = self.repo_name
1048 rn = self.repo_name
1013 log.debug('Getting cached instance of repo')
1049 log.debug('Getting cached instance of repo')
1014
1050
1015 if cache_map:
1051 if cache_map:
1016 # get using prefilled cache_map
1052 # get using prefilled cache_map
1017 invalidate_repo = cache_map[self.repo_name]
1053 invalidate_repo = cache_map[self.repo_name]
1018 if invalidate_repo:
1054 if invalidate_repo:
1019 invalidate_repo = (None if invalidate_repo.cache_active
1055 invalidate_repo = (None if invalidate_repo.cache_active
1020 else invalidate_repo)
1056 else invalidate_repo)
1021 else:
1057 else:
1022 # get from invalidate
1058 # get from invalidate
1023 invalidate_repo = self.invalidate
1059 invalidate_repo = self.invalidate
1024
1060
1025 if invalidate_repo is not None:
1061 if invalidate_repo is not None:
1026 region_invalidate(_c, None, rn)
1062 region_invalidate(_c, None, rn)
1027 # update our cache
1063 # update our cache
1028 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1064 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1029 return _c(rn)
1065 return _c(rn)
1030
1066
1031 def __get_instance(self):
1067 def __get_instance(self):
1032 repo_full_path = self.repo_full_path
1068 repo_full_path = self.repo_full_path
1033 try:
1069 try:
1034 alias = get_scm(repo_full_path)[0]
1070 alias = get_scm(repo_full_path)[0]
1035 log.debug('Creating instance of %s repository' % alias)
1071 log.debug('Creating instance of %s repository' % alias)
1036 backend = get_backend(alias)
1072 backend = get_backend(alias)
1037 except VCSError:
1073 except VCSError:
1038 log.error(traceback.format_exc())
1074 log.error(traceback.format_exc())
1039 log.error('Perhaps this repository is in db and not in '
1075 log.error('Perhaps this repository is in db and not in '
1040 'filesystem run rescan repositories with '
1076 'filesystem run rescan repositories with '
1041 '"destroy old data " option from admin panel')
1077 '"destroy old data " option from admin panel')
1042 return
1078 return
1043
1079
1044 if alias == 'hg':
1080 if alias == 'hg':
1045
1081
1046 repo = backend(safe_str(repo_full_path), create=False,
1082 repo = backend(safe_str(repo_full_path), create=False,
1047 baseui=self._ui)
1083 baseui=self._ui)
1048 # skip hidden web repository
1084 # skip hidden web repository
1049 if repo._get_hidden():
1085 if repo._get_hidden():
1050 return
1086 return
1051 else:
1087 else:
1052 repo = backend(repo_full_path, create=False)
1088 repo = backend(repo_full_path, create=False)
1053
1089
1054 return repo
1090 return repo
1055
1091
1056
1092
1057 class RepoGroup(Base, BaseModel):
1093 class RepoGroup(Base, BaseModel):
1058 __tablename__ = 'groups'
1094 __tablename__ = 'groups'
1059 __table_args__ = (
1095 __table_args__ = (
1060 UniqueConstraint('group_name', 'group_parent_id'),
1096 UniqueConstraint('group_name', 'group_parent_id'),
1061 CheckConstraint('group_id != group_parent_id'),
1097 CheckConstraint('group_id != group_parent_id'),
1062 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1063 'mysql_charset': 'utf8'},
1099 'mysql_charset': 'utf8'},
1064 )
1100 )
1065 __mapper_args__ = {'order_by': 'group_name'}
1101 __mapper_args__ = {'order_by': 'group_name'}
1066
1102
1067 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1103 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1068 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1104 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1069 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1105 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1070 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1106 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1071 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1107 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1072
1108
1073 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1109 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1074 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1110 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1075
1111
1076 parent_group = relationship('RepoGroup', remote_side=group_id)
1112 parent_group = relationship('RepoGroup', remote_side=group_id)
1077
1113
1078 def __init__(self, group_name='', parent_group=None):
1114 def __init__(self, group_name='', parent_group=None):
1079 self.group_name = group_name
1115 self.group_name = group_name
1080 self.parent_group = parent_group
1116 self.parent_group = parent_group
1081
1117
1082 def __unicode__(self):
1118 def __unicode__(self):
1083 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1119 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1084 self.group_name)
1120 self.group_name)
1085
1121
1086 @classmethod
1122 @classmethod
1087 def groups_choices(cls, check_perms=False):
1123 def groups_choices(cls, check_perms=False):
1088 from webhelpers.html import literal as _literal
1124 from webhelpers.html import literal as _literal
1089 from rhodecode.model.scm import ScmModel
1125 from rhodecode.model.scm import ScmModel
1090 groups = cls.query().all()
1126 groups = cls.query().all()
1091 if check_perms:
1127 if check_perms:
1092 #filter group user have access to, it's done
1128 #filter group user have access to, it's done
1093 #magically inside ScmModel based on current user
1129 #magically inside ScmModel based on current user
1094 groups = ScmModel().get_repos_groups(groups)
1130 groups = ScmModel().get_repos_groups(groups)
1095 repo_groups = [('', '')]
1131 repo_groups = [('', '')]
1096 sep = ' &raquo; '
1132 sep = ' &raquo; '
1097 _name = lambda k: _literal(sep.join(k))
1133 _name = lambda k: _literal(sep.join(k))
1098
1134
1099 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1135 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1100 for x in groups])
1136 for x in groups])
1101
1137
1102 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1138 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1103 return repo_groups
1139 return repo_groups
1104
1140
1105 @classmethod
1141 @classmethod
1106 def url_sep(cls):
1142 def url_sep(cls):
1107 return URL_SEP
1143 return URL_SEP
1108
1144
1109 @classmethod
1145 @classmethod
1110 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1146 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1111 if case_insensitive:
1147 if case_insensitive:
1112 gr = cls.query()\
1148 gr = cls.query()\
1113 .filter(cls.group_name.ilike(group_name))
1149 .filter(cls.group_name.ilike(group_name))
1114 else:
1150 else:
1115 gr = cls.query()\
1151 gr = cls.query()\
1116 .filter(cls.group_name == group_name)
1152 .filter(cls.group_name == group_name)
1117 if cache:
1153 if cache:
1118 gr = gr.options(FromCache(
1154 gr = gr.options(FromCache(
1119 "sql_cache_short",
1155 "sql_cache_short",
1120 "get_group_%s" % _hash_key(group_name)
1156 "get_group_%s" % _hash_key(group_name)
1121 )
1157 )
1122 )
1158 )
1123 return gr.scalar()
1159 return gr.scalar()
1124
1160
1125 @property
1161 @property
1126 def parents(self):
1162 def parents(self):
1127 parents_recursion_limit = 5
1163 parents_recursion_limit = 5
1128 groups = []
1164 groups = []
1129 if self.parent_group is None:
1165 if self.parent_group is None:
1130 return groups
1166 return groups
1131 cur_gr = self.parent_group
1167 cur_gr = self.parent_group
1132 groups.insert(0, cur_gr)
1168 groups.insert(0, cur_gr)
1133 cnt = 0
1169 cnt = 0
1134 while 1:
1170 while 1:
1135 cnt += 1
1171 cnt += 1
1136 gr = getattr(cur_gr, 'parent_group', None)
1172 gr = getattr(cur_gr, 'parent_group', None)
1137 cur_gr = cur_gr.parent_group
1173 cur_gr = cur_gr.parent_group
1138 if gr is None:
1174 if gr is None:
1139 break
1175 break
1140 if cnt == parents_recursion_limit:
1176 if cnt == parents_recursion_limit:
1141 # this will prevent accidental infinit loops
1177 # this will prevent accidental infinit loops
1142 log.error('group nested more than %s' %
1178 log.error('group nested more than %s' %
1143 parents_recursion_limit)
1179 parents_recursion_limit)
1144 break
1180 break
1145
1181
1146 groups.insert(0, gr)
1182 groups.insert(0, gr)
1147 return groups
1183 return groups
1148
1184
1149 @property
1185 @property
1150 def children(self):
1186 def children(self):
1151 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1187 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1152
1188
1153 @property
1189 @property
1154 def name(self):
1190 def name(self):
1155 return self.group_name.split(RepoGroup.url_sep())[-1]
1191 return self.group_name.split(RepoGroup.url_sep())[-1]
1156
1192
1157 @property
1193 @property
1158 def full_path(self):
1194 def full_path(self):
1159 return self.group_name
1195 return self.group_name
1160
1196
1161 @property
1197 @property
1162 def full_path_splitted(self):
1198 def full_path_splitted(self):
1163 return self.group_name.split(RepoGroup.url_sep())
1199 return self.group_name.split(RepoGroup.url_sep())
1164
1200
1165 @property
1201 @property
1166 def repositories(self):
1202 def repositories(self):
1167 return Repository.query()\
1203 return Repository.query()\
1168 .filter(Repository.group == self)\
1204 .filter(Repository.group == self)\
1169 .order_by(Repository.repo_name)
1205 .order_by(Repository.repo_name)
1170
1206
1171 @property
1207 @property
1172 def repositories_recursive_count(self):
1208 def repositories_recursive_count(self):
1173 cnt = self.repositories.count()
1209 cnt = self.repositories.count()
1174
1210
1175 def children_count(group):
1211 def children_count(group):
1176 cnt = 0
1212 cnt = 0
1177 for child in group.children:
1213 for child in group.children:
1178 cnt += child.repositories.count()
1214 cnt += child.repositories.count()
1179 cnt += children_count(child)
1215 cnt += children_count(child)
1180 return cnt
1216 return cnt
1181
1217
1182 return cnt + children_count(self)
1218 return cnt + children_count(self)
1183
1219
1184 def recursive_groups_and_repos(self):
1220 def recursive_groups_and_repos(self):
1185 """
1221 """
1186 Recursive return all groups, with repositories in those groups
1222 Recursive return all groups, with repositories in those groups
1187 """
1223 """
1188 all_ = []
1224 all_ = []
1189
1225
1190 def _get_members(root_gr):
1226 def _get_members(root_gr):
1191 for r in root_gr.repositories:
1227 for r in root_gr.repositories:
1192 all_.append(r)
1228 all_.append(r)
1193 childs = root_gr.children.all()
1229 childs = root_gr.children.all()
1194 if childs:
1230 if childs:
1195 for gr in childs:
1231 for gr in childs:
1196 all_.append(gr)
1232 all_.append(gr)
1197 _get_members(gr)
1233 _get_members(gr)
1198
1234
1199 _get_members(self)
1235 _get_members(self)
1200 return [self] + all_
1236 return [self] + all_
1201
1237
1202 def get_new_name(self, group_name):
1238 def get_new_name(self, group_name):
1203 """
1239 """
1204 returns new full group name based on parent and new name
1240 returns new full group name based on parent and new name
1205
1241
1206 :param group_name:
1242 :param group_name:
1207 """
1243 """
1208 path_prefix = (self.parent_group.full_path_splitted if
1244 path_prefix = (self.parent_group.full_path_splitted if
1209 self.parent_group else [])
1245 self.parent_group else [])
1210 return RepoGroup.url_sep().join(path_prefix + [group_name])
1246 return RepoGroup.url_sep().join(path_prefix + [group_name])
1211
1247
1212
1248
1213 class Permission(Base, BaseModel):
1249 class Permission(Base, BaseModel):
1214 __tablename__ = 'permissions'
1250 __tablename__ = 'permissions'
1215 __table_args__ = (
1251 __table_args__ = (
1216 Index('p_perm_name_idx', 'permission_name'),
1252 Index('p_perm_name_idx', 'permission_name'),
1217 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1253 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1218 'mysql_charset': 'utf8'},
1254 'mysql_charset': 'utf8'},
1219 )
1255 )
1220 PERMS = [
1256 PERMS = [
1221 ('repository.none', _('Repository no access')),
1257 ('repository.none', _('Repository no access')),
1222 ('repository.read', _('Repository read access')),
1258 ('repository.read', _('Repository read access')),
1223 ('repository.write', _('Repository write access')),
1259 ('repository.write', _('Repository write access')),
1224 ('repository.admin', _('Repository admin access')),
1260 ('repository.admin', _('Repository admin access')),
1225
1261
1226 ('group.none', _('Repositories Group no access')),
1262 ('group.none', _('Repositories Group no access')),
1227 ('group.read', _('Repositories Group read access')),
1263 ('group.read', _('Repositories Group read access')),
1228 ('group.write', _('Repositories Group write access')),
1264 ('group.write', _('Repositories Group write access')),
1229 ('group.admin', _('Repositories Group admin access')),
1265 ('group.admin', _('Repositories Group admin access')),
1230
1266
1231 ('hg.admin', _('RhodeCode Administrator')),
1267 ('hg.admin', _('RhodeCode Administrator')),
1232 ('hg.create.none', _('Repository creation disabled')),
1268 ('hg.create.none', _('Repository creation disabled')),
1233 ('hg.create.repository', _('Repository creation enabled')),
1269 ('hg.create.repository', _('Repository creation enabled')),
1234 ('hg.fork.none', _('Repository forking disabled')),
1270 ('hg.fork.none', _('Repository forking disabled')),
1235 ('hg.fork.repository', _('Repository forking enabled')),
1271 ('hg.fork.repository', _('Repository forking enabled')),
1236 ('hg.register.none', _('Register disabled')),
1272 ('hg.register.none', _('Register disabled')),
1237 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1273 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1238 'with manual activation')),
1274 'with manual activation')),
1239
1275
1240 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1276 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1241 'with auto activation')),
1277 'with auto activation')),
1242 ]
1278 ]
1243
1279
1244 # defines which permissions are more important higher the more important
1280 # defines which permissions are more important higher the more important
1245 PERM_WEIGHTS = {
1281 PERM_WEIGHTS = {
1246 'repository.none': 0,
1282 'repository.none': 0,
1247 'repository.read': 1,
1283 'repository.read': 1,
1248 'repository.write': 3,
1284 'repository.write': 3,
1249 'repository.admin': 4,
1285 'repository.admin': 4,
1250
1286
1251 'group.none': 0,
1287 'group.none': 0,
1252 'group.read': 1,
1288 'group.read': 1,
1253 'group.write': 3,
1289 'group.write': 3,
1254 'group.admin': 4,
1290 'group.admin': 4,
1255
1291
1256 'hg.fork.none': 0,
1292 'hg.fork.none': 0,
1257 'hg.fork.repository': 1,
1293 'hg.fork.repository': 1,
1258 'hg.create.none': 0,
1294 'hg.create.none': 0,
1259 'hg.create.repository':1
1295 'hg.create.repository':1
1260 }
1296 }
1261
1297
1262 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1298 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1263 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1299 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1264 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1300 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1265
1301
1266 def __unicode__(self):
1302 def __unicode__(self):
1267 return u"<%s('%s:%s')>" % (
1303 return u"<%s('%s:%s')>" % (
1268 self.__class__.__name__, self.permission_id, self.permission_name
1304 self.__class__.__name__, self.permission_id, self.permission_name
1269 )
1305 )
1270
1306
1271 @classmethod
1307 @classmethod
1272 def get_by_key(cls, key):
1308 def get_by_key(cls, key):
1273 return cls.query().filter(cls.permission_name == key).scalar()
1309 return cls.query().filter(cls.permission_name == key).scalar()
1274
1310
1275 @classmethod
1311 @classmethod
1276 def get_default_perms(cls, default_user_id):
1312 def get_default_perms(cls, default_user_id):
1277 q = Session().query(UserRepoToPerm, Repository, cls)\
1313 q = Session().query(UserRepoToPerm, Repository, cls)\
1278 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1314 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1279 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1315 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1280 .filter(UserRepoToPerm.user_id == default_user_id)
1316 .filter(UserRepoToPerm.user_id == default_user_id)
1281
1317
1282 return q.all()
1318 return q.all()
1283
1319
1284 @classmethod
1320 @classmethod
1285 def get_default_group_perms(cls, default_user_id):
1321 def get_default_group_perms(cls, default_user_id):
1286 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1322 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1287 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1323 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1288 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1324 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1289 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1325 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1290
1326
1291 return q.all()
1327 return q.all()
1292
1328
1293
1329
1294 class UserRepoToPerm(Base, BaseModel):
1330 class UserRepoToPerm(Base, BaseModel):
1295 __tablename__ = 'repo_to_perm'
1331 __tablename__ = 'repo_to_perm'
1296 __table_args__ = (
1332 __table_args__ = (
1297 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1333 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1298 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1334 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1299 'mysql_charset': 'utf8'}
1335 'mysql_charset': 'utf8'}
1300 )
1336 )
1301 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1337 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1302 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1338 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1303 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1339 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1304 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1340 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1305
1341
1306 user = relationship('User')
1342 user = relationship('User')
1307 repository = relationship('Repository')
1343 repository = relationship('Repository')
1308 permission = relationship('Permission')
1344 permission = relationship('Permission')
1309
1345
1310 @classmethod
1346 @classmethod
1311 def create(cls, user, repository, permission):
1347 def create(cls, user, repository, permission):
1312 n = cls()
1348 n = cls()
1313 n.user = user
1349 n.user = user
1314 n.repository = repository
1350 n.repository = repository
1315 n.permission = permission
1351 n.permission = permission
1316 Session().add(n)
1352 Session().add(n)
1317 return n
1353 return n
1318
1354
1319 def __unicode__(self):
1355 def __unicode__(self):
1320 return u'<user:%s => %s >' % (self.user, self.repository)
1356 return u'<user:%s => %s >' % (self.user, self.repository)
1321
1357
1322
1358
1323 class UserToPerm(Base, BaseModel):
1359 class UserToPerm(Base, BaseModel):
1324 __tablename__ = 'user_to_perm'
1360 __tablename__ = 'user_to_perm'
1325 __table_args__ = (
1361 __table_args__ = (
1326 UniqueConstraint('user_id', 'permission_id'),
1362 UniqueConstraint('user_id', 'permission_id'),
1327 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1363 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1328 'mysql_charset': 'utf8'}
1364 'mysql_charset': 'utf8'}
1329 )
1365 )
1330 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1366 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1331 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1367 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1332 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1368 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1333
1369
1334 user = relationship('User')
1370 user = relationship('User')
1335 permission = relationship('Permission', lazy='joined')
1371 permission = relationship('Permission', lazy='joined')
1336
1372
1337
1373
1338 class UsersGroupRepoToPerm(Base, BaseModel):
1374 class UsersGroupRepoToPerm(Base, BaseModel):
1339 __tablename__ = 'users_group_repo_to_perm'
1375 __tablename__ = 'users_group_repo_to_perm'
1340 __table_args__ = (
1376 __table_args__ = (
1341 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1377 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1342 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1378 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1343 'mysql_charset': 'utf8'}
1379 'mysql_charset': 'utf8'}
1344 )
1380 )
1345 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1381 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1346 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1382 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1347 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1383 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1348 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1384 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1349
1385
1350 users_group = relationship('UsersGroup')
1386 users_group = relationship('UsersGroup')
1351 permission = relationship('Permission')
1387 permission = relationship('Permission')
1352 repository = relationship('Repository')
1388 repository = relationship('Repository')
1353
1389
1354 @classmethod
1390 @classmethod
1355 def create(cls, users_group, repository, permission):
1391 def create(cls, users_group, repository, permission):
1356 n = cls()
1392 n = cls()
1357 n.users_group = users_group
1393 n.users_group = users_group
1358 n.repository = repository
1394 n.repository = repository
1359 n.permission = permission
1395 n.permission = permission
1360 Session().add(n)
1396 Session().add(n)
1361 return n
1397 return n
1362
1398
1363 def __unicode__(self):
1399 def __unicode__(self):
1364 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1400 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1365
1401
1366
1402
1367 class UsersGroupToPerm(Base, BaseModel):
1403 class UsersGroupToPerm(Base, BaseModel):
1368 __tablename__ = 'users_group_to_perm'
1404 __tablename__ = 'users_group_to_perm'
1369 __table_args__ = (
1405 __table_args__ = (
1370 UniqueConstraint('users_group_id', 'permission_id',),
1406 UniqueConstraint('users_group_id', 'permission_id',),
1371 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1372 'mysql_charset': 'utf8'}
1408 'mysql_charset': 'utf8'}
1373 )
1409 )
1374 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1410 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1375 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1411 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1376 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1412 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1377
1413
1378 users_group = relationship('UsersGroup')
1414 users_group = relationship('UsersGroup')
1379 permission = relationship('Permission')
1415 permission = relationship('Permission')
1380
1416
1381
1417
1382 class UserRepoGroupToPerm(Base, BaseModel):
1418 class UserRepoGroupToPerm(Base, BaseModel):
1383 __tablename__ = 'user_repo_group_to_perm'
1419 __tablename__ = 'user_repo_group_to_perm'
1384 __table_args__ = (
1420 __table_args__ = (
1385 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1421 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1422 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1387 'mysql_charset': 'utf8'}
1423 'mysql_charset': 'utf8'}
1388 )
1424 )
1389
1425
1390 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1426 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1391 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1427 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1392 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1428 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1393 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1429 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1394
1430
1395 user = relationship('User')
1431 user = relationship('User')
1396 group = relationship('RepoGroup')
1432 group = relationship('RepoGroup')
1397 permission = relationship('Permission')
1433 permission = relationship('Permission')
1398
1434
1399
1435
1400 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1436 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1401 __tablename__ = 'users_group_repo_group_to_perm'
1437 __tablename__ = 'users_group_repo_group_to_perm'
1402 __table_args__ = (
1438 __table_args__ = (
1403 UniqueConstraint('users_group_id', 'group_id'),
1439 UniqueConstraint('users_group_id', 'group_id'),
1404 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1405 'mysql_charset': 'utf8'}
1441 'mysql_charset': 'utf8'}
1406 )
1442 )
1407
1443
1408 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1444 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1409 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1445 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1410 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1446 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1411 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1447 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1412
1448
1413 users_group = relationship('UsersGroup')
1449 users_group = relationship('UsersGroup')
1414 permission = relationship('Permission')
1450 permission = relationship('Permission')
1415 group = relationship('RepoGroup')
1451 group = relationship('RepoGroup')
1416
1452
1417
1453
1418 class Statistics(Base, BaseModel):
1454 class Statistics(Base, BaseModel):
1419 __tablename__ = 'statistics'
1455 __tablename__ = 'statistics'
1420 __table_args__ = (
1456 __table_args__ = (
1421 UniqueConstraint('repository_id'),
1457 UniqueConstraint('repository_id'),
1422 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1458 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1423 'mysql_charset': 'utf8'}
1459 'mysql_charset': 'utf8'}
1424 )
1460 )
1425 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1461 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1426 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1462 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1427 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1463 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1428 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1464 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1429 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1465 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1430 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1466 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1431
1467
1432 repository = relationship('Repository', single_parent=True)
1468 repository = relationship('Repository', single_parent=True)
1433
1469
1434
1470
1435 class UserFollowing(Base, BaseModel):
1471 class UserFollowing(Base, BaseModel):
1436 __tablename__ = 'user_followings'
1472 __tablename__ = 'user_followings'
1437 __table_args__ = (
1473 __table_args__ = (
1438 UniqueConstraint('user_id', 'follows_repository_id'),
1474 UniqueConstraint('user_id', 'follows_repository_id'),
1439 UniqueConstraint('user_id', 'follows_user_id'),
1475 UniqueConstraint('user_id', 'follows_user_id'),
1440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1476 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1441 'mysql_charset': 'utf8'}
1477 'mysql_charset': 'utf8'}
1442 )
1478 )
1443
1479
1444 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1480 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1445 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1481 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1446 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1482 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1447 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1483 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1448 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1484 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1449
1485
1450 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1486 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1451
1487
1452 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1488 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1453 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1489 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1454
1490
1455 @classmethod
1491 @classmethod
1456 def get_repo_followers(cls, repo_id):
1492 def get_repo_followers(cls, repo_id):
1457 return cls.query().filter(cls.follows_repo_id == repo_id)
1493 return cls.query().filter(cls.follows_repo_id == repo_id)
1458
1494
1459
1495
1460 class CacheInvalidation(Base, BaseModel):
1496 class CacheInvalidation(Base, BaseModel):
1461 __tablename__ = 'cache_invalidation'
1497 __tablename__ = 'cache_invalidation'
1462 __table_args__ = (
1498 __table_args__ = (
1463 UniqueConstraint('cache_key'),
1499 UniqueConstraint('cache_key'),
1464 Index('key_idx', 'cache_key'),
1500 Index('key_idx', 'cache_key'),
1465 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1466 'mysql_charset': 'utf8'},
1502 'mysql_charset': 'utf8'},
1467 )
1503 )
1468 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1504 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1469 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1505 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1470 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1506 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1471 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1507 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1472
1508
1473 def __init__(self, cache_key, cache_args=''):
1509 def __init__(self, cache_key, cache_args=''):
1474 self.cache_key = cache_key
1510 self.cache_key = cache_key
1475 self.cache_args = cache_args
1511 self.cache_args = cache_args
1476 self.cache_active = False
1512 self.cache_active = False
1477
1513
1478 def __unicode__(self):
1514 def __unicode__(self):
1479 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1515 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1480 self.cache_id, self.cache_key)
1516 self.cache_id, self.cache_key)
1481
1517
1482 @property
1518 @property
1483 def prefix(self):
1519 def prefix(self):
1484 _split = self.cache_key.split(self.cache_args, 1)
1520 _split = self.cache_key.split(self.cache_args, 1)
1485 if _split and len(_split) == 2:
1521 if _split and len(_split) == 2:
1486 return _split[0]
1522 return _split[0]
1487 return ''
1523 return ''
1488
1524
1489 @classmethod
1525 @classmethod
1490 def clear_cache(cls):
1526 def clear_cache(cls):
1491 cls.query().delete()
1527 cls.query().delete()
1492
1528
1493 @classmethod
1529 @classmethod
1494 def _get_key(cls, key):
1530 def _get_key(cls, key):
1495 """
1531 """
1496 Wrapper for generating a key, together with a prefix
1532 Wrapper for generating a key, together with a prefix
1497
1533
1498 :param key:
1534 :param key:
1499 """
1535 """
1500 import rhodecode
1536 import rhodecode
1501 prefix = ''
1537 prefix = ''
1502 org_key = key
1538 org_key = key
1503 iid = rhodecode.CONFIG.get('instance_id')
1539 iid = rhodecode.CONFIG.get('instance_id')
1504 if iid:
1540 if iid:
1505 prefix = iid
1541 prefix = iid
1506
1542
1507 return "%s%s" % (prefix, key), prefix, org_key
1543 return "%s%s" % (prefix, key), prefix, org_key
1508
1544
1509 @classmethod
1545 @classmethod
1510 def get_by_key(cls, key):
1546 def get_by_key(cls, key):
1511 return cls.query().filter(cls.cache_key == key).scalar()
1547 return cls.query().filter(cls.cache_key == key).scalar()
1512
1548
1513 @classmethod
1549 @classmethod
1514 def get_by_repo_name(cls, repo_name):
1550 def get_by_repo_name(cls, repo_name):
1515 return cls.query().filter(cls.cache_args == repo_name).all()
1551 return cls.query().filter(cls.cache_args == repo_name).all()
1516
1552
1517 @classmethod
1553 @classmethod
1518 def _get_or_create_key(cls, key, repo_name, commit=True):
1554 def _get_or_create_key(cls, key, repo_name, commit=True):
1519 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1555 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1520 if not inv_obj:
1556 if not inv_obj:
1521 try:
1557 try:
1522 inv_obj = CacheInvalidation(key, repo_name)
1558 inv_obj = CacheInvalidation(key, repo_name)
1523 Session().add(inv_obj)
1559 Session().add(inv_obj)
1524 if commit:
1560 if commit:
1525 Session().commit()
1561 Session().commit()
1526 except Exception:
1562 except Exception:
1527 log.error(traceback.format_exc())
1563 log.error(traceback.format_exc())
1528 Session().rollback()
1564 Session().rollback()
1529 return inv_obj
1565 return inv_obj
1530
1566
1531 @classmethod
1567 @classmethod
1532 def invalidate(cls, key):
1568 def invalidate(cls, key):
1533 """
1569 """
1534 Returns Invalidation object if this given key should be invalidated
1570 Returns Invalidation object if this given key should be invalidated
1535 None otherwise. `cache_active = False` means that this cache
1571 None otherwise. `cache_active = False` means that this cache
1536 state is not valid and needs to be invalidated
1572 state is not valid and needs to be invalidated
1537
1573
1538 :param key:
1574 :param key:
1539 """
1575 """
1540 repo_name = key
1576 repo_name = key
1541 repo_name = remove_suffix(repo_name, '_README')
1577 repo_name = remove_suffix(repo_name, '_README')
1542 repo_name = remove_suffix(repo_name, '_RSS')
1578 repo_name = remove_suffix(repo_name, '_RSS')
1543 repo_name = remove_suffix(repo_name, '_ATOM')
1579 repo_name = remove_suffix(repo_name, '_ATOM')
1544
1580
1545 # adds instance prefix
1581 # adds instance prefix
1546 key, _prefix, _org_key = cls._get_key(key)
1582 key, _prefix, _org_key = cls._get_key(key)
1547 inv = cls._get_or_create_key(key, repo_name)
1583 inv = cls._get_or_create_key(key, repo_name)
1548
1584
1549 if inv and inv.cache_active is False:
1585 if inv and inv.cache_active is False:
1550 return inv
1586 return inv
1551
1587
1552 @classmethod
1588 @classmethod
1553 def set_invalidate(cls, key=None, repo_name=None):
1589 def set_invalidate(cls, key=None, repo_name=None):
1554 """
1590 """
1555 Mark this Cache key for invalidation, either by key or whole
1591 Mark this Cache key for invalidation, either by key or whole
1556 cache sets based on repo_name
1592 cache sets based on repo_name
1557
1593
1558 :param key:
1594 :param key:
1559 """
1595 """
1560 if key:
1596 if key:
1561 key, _prefix, _org_key = cls._get_key(key)
1597 key, _prefix, _org_key = cls._get_key(key)
1562 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1598 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1563 elif repo_name:
1599 elif repo_name:
1564 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1600 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1565
1601
1566 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1602 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1567 % (len(inv_objs), key, repo_name))
1603 % (len(inv_objs), key, repo_name))
1568 try:
1604 try:
1569 for inv_obj in inv_objs:
1605 for inv_obj in inv_objs:
1570 inv_obj.cache_active = False
1606 inv_obj.cache_active = False
1571 Session().add(inv_obj)
1607 Session().add(inv_obj)
1572 Session().commit()
1608 Session().commit()
1573 except Exception:
1609 except Exception:
1574 log.error(traceback.format_exc())
1610 log.error(traceback.format_exc())
1575 Session().rollback()
1611 Session().rollback()
1576
1612
1577 @classmethod
1613 @classmethod
1578 def set_valid(cls, key):
1614 def set_valid(cls, key):
1579 """
1615 """
1580 Mark this cache key as active and currently cached
1616 Mark this cache key as active and currently cached
1581
1617
1582 :param key:
1618 :param key:
1583 """
1619 """
1584 inv_obj = cls.get_by_key(key)
1620 inv_obj = cls.get_by_key(key)
1585 inv_obj.cache_active = True
1621 inv_obj.cache_active = True
1586 Session().add(inv_obj)
1622 Session().add(inv_obj)
1587 Session().commit()
1623 Session().commit()
1588
1624
1589 @classmethod
1625 @classmethod
1590 def get_cache_map(cls):
1626 def get_cache_map(cls):
1591
1627
1592 class cachemapdict(dict):
1628 class cachemapdict(dict):
1593
1629
1594 def __init__(self, *args, **kwargs):
1630 def __init__(self, *args, **kwargs):
1595 fixkey = kwargs.get('fixkey')
1631 fixkey = kwargs.get('fixkey')
1596 if fixkey:
1632 if fixkey:
1597 del kwargs['fixkey']
1633 del kwargs['fixkey']
1598 self.fixkey = fixkey
1634 self.fixkey = fixkey
1599 super(cachemapdict, self).__init__(*args, **kwargs)
1635 super(cachemapdict, self).__init__(*args, **kwargs)
1600
1636
1601 def __getattr__(self, name):
1637 def __getattr__(self, name):
1602 key = name
1638 key = name
1603 if self.fixkey:
1639 if self.fixkey:
1604 key, _prefix, _org_key = cls._get_key(key)
1640 key, _prefix, _org_key = cls._get_key(key)
1605 if key in self.__dict__:
1641 if key in self.__dict__:
1606 return self.__dict__[key]
1642 return self.__dict__[key]
1607 else:
1643 else:
1608 return self[key]
1644 return self[key]
1609
1645
1610 def __getitem__(self, key):
1646 def __getitem__(self, key):
1611 if self.fixkey:
1647 if self.fixkey:
1612 key, _prefix, _org_key = cls._get_key(key)
1648 key, _prefix, _org_key = cls._get_key(key)
1613 try:
1649 try:
1614 return super(cachemapdict, self).__getitem__(key)
1650 return super(cachemapdict, self).__getitem__(key)
1615 except KeyError:
1651 except KeyError:
1616 return
1652 return
1617
1653
1618 cache_map = cachemapdict(fixkey=True)
1654 cache_map = cachemapdict(fixkey=True)
1619 for obj in cls.query().all():
1655 for obj in cls.query().all():
1620 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1656 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1621 return cache_map
1657 return cache_map
1622
1658
1623
1659
1624 class ChangesetComment(Base, BaseModel):
1660 class ChangesetComment(Base, BaseModel):
1625 __tablename__ = 'changeset_comments'
1661 __tablename__ = 'changeset_comments'
1626 __table_args__ = (
1662 __table_args__ = (
1627 Index('cc_revision_idx', 'revision'),
1663 Index('cc_revision_idx', 'revision'),
1628 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1629 'mysql_charset': 'utf8'},
1665 'mysql_charset': 'utf8'},
1630 )
1666 )
1631 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1667 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1632 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1668 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1633 revision = Column('revision', String(40), nullable=True)
1669 revision = Column('revision', String(40), nullable=True)
1634 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1670 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1635 line_no = Column('line_no', Unicode(10), nullable=True)
1671 line_no = Column('line_no', Unicode(10), nullable=True)
1636 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1672 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1637 f_path = Column('f_path', Unicode(1000), nullable=True)
1673 f_path = Column('f_path', Unicode(1000), nullable=True)
1638 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1674 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1639 text = Column('text', UnicodeText(25000), nullable=False)
1675 text = Column('text', UnicodeText(25000), nullable=False)
1640 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1676 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1641 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1677 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1642
1678
1643 author = relationship('User', lazy='joined')
1679 author = relationship('User', lazy='joined')
1644 repo = relationship('Repository')
1680 repo = relationship('Repository')
1645 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1681 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1646 pull_request = relationship('PullRequest', lazy='joined')
1682 pull_request = relationship('PullRequest', lazy='joined')
1647
1683
1648 @classmethod
1684 @classmethod
1649 def get_users(cls, revision=None, pull_request_id=None):
1685 def get_users(cls, revision=None, pull_request_id=None):
1650 """
1686 """
1651 Returns user associated with this ChangesetComment. ie those
1687 Returns user associated with this ChangesetComment. ie those
1652 who actually commented
1688 who actually commented
1653
1689
1654 :param cls:
1690 :param cls:
1655 :param revision:
1691 :param revision:
1656 """
1692 """
1657 q = Session().query(User)\
1693 q = Session().query(User)\
1658 .join(ChangesetComment.author)
1694 .join(ChangesetComment.author)
1659 if revision:
1695 if revision:
1660 q = q.filter(cls.revision == revision)
1696 q = q.filter(cls.revision == revision)
1661 elif pull_request_id:
1697 elif pull_request_id:
1662 q = q.filter(cls.pull_request_id == pull_request_id)
1698 q = q.filter(cls.pull_request_id == pull_request_id)
1663 return q.all()
1699 return q.all()
1664
1700
1665
1701
1666 class ChangesetStatus(Base, BaseModel):
1702 class ChangesetStatus(Base, BaseModel):
1667 __tablename__ = 'changeset_statuses'
1703 __tablename__ = 'changeset_statuses'
1668 __table_args__ = (
1704 __table_args__ = (
1669 Index('cs_revision_idx', 'revision'),
1705 Index('cs_revision_idx', 'revision'),
1670 Index('cs_version_idx', 'version'),
1706 Index('cs_version_idx', 'version'),
1671 UniqueConstraint('repo_id', 'revision', 'version'),
1707 UniqueConstraint('repo_id', 'revision', 'version'),
1672 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1708 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1673 'mysql_charset': 'utf8'}
1709 'mysql_charset': 'utf8'}
1674 )
1710 )
1675 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1711 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1676 STATUS_APPROVED = 'approved'
1712 STATUS_APPROVED = 'approved'
1677 STATUS_REJECTED = 'rejected'
1713 STATUS_REJECTED = 'rejected'
1678 STATUS_UNDER_REVIEW = 'under_review'
1714 STATUS_UNDER_REVIEW = 'under_review'
1679
1715
1680 STATUSES = [
1716 STATUSES = [
1681 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1717 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1682 (STATUS_APPROVED, _("Approved")),
1718 (STATUS_APPROVED, _("Approved")),
1683 (STATUS_REJECTED, _("Rejected")),
1719 (STATUS_REJECTED, _("Rejected")),
1684 (STATUS_UNDER_REVIEW, _("Under Review")),
1720 (STATUS_UNDER_REVIEW, _("Under Review")),
1685 ]
1721 ]
1686
1722
1687 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1723 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1688 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1724 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1689 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1725 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1690 revision = Column('revision', String(40), nullable=False)
1726 revision = Column('revision', String(40), nullable=False)
1691 status = Column('status', String(128), nullable=False, default=DEFAULT)
1727 status = Column('status', String(128), nullable=False, default=DEFAULT)
1692 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1728 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1693 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1729 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1694 version = Column('version', Integer(), nullable=False, default=0)
1730 version = Column('version', Integer(), nullable=False, default=0)
1695 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1731 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1696
1732
1697 author = relationship('User', lazy='joined')
1733 author = relationship('User', lazy='joined')
1698 repo = relationship('Repository')
1734 repo = relationship('Repository')
1699 comment = relationship('ChangesetComment', lazy='joined')
1735 comment = relationship('ChangesetComment', lazy='joined')
1700 pull_request = relationship('PullRequest', lazy='joined')
1736 pull_request = relationship('PullRequest', lazy='joined')
1701
1737
1702 def __unicode__(self):
1738 def __unicode__(self):
1703 return u"<%s('%s:%s')>" % (
1739 return u"<%s('%s:%s')>" % (
1704 self.__class__.__name__,
1740 self.__class__.__name__,
1705 self.status, self.author
1741 self.status, self.author
1706 )
1742 )
1707
1743
1708 @classmethod
1744 @classmethod
1709 def get_status_lbl(cls, value):
1745 def get_status_lbl(cls, value):
1710 return dict(cls.STATUSES).get(value)
1746 return dict(cls.STATUSES).get(value)
1711
1747
1712 @property
1748 @property
1713 def status_lbl(self):
1749 def status_lbl(self):
1714 return ChangesetStatus.get_status_lbl(self.status)
1750 return ChangesetStatus.get_status_lbl(self.status)
1715
1751
1716
1752
1717 class PullRequest(Base, BaseModel):
1753 class PullRequest(Base, BaseModel):
1718 __tablename__ = 'pull_requests'
1754 __tablename__ = 'pull_requests'
1719 __table_args__ = (
1755 __table_args__ = (
1720 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1756 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1721 'mysql_charset': 'utf8'},
1757 'mysql_charset': 'utf8'},
1722 )
1758 )
1723
1759
1724 STATUS_NEW = u'new'
1760 STATUS_NEW = u'new'
1725 STATUS_OPEN = u'open'
1761 STATUS_OPEN = u'open'
1726 STATUS_CLOSED = u'closed'
1762 STATUS_CLOSED = u'closed'
1727
1763
1728 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1764 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1729 title = Column('title', Unicode(256), nullable=True)
1765 title = Column('title', Unicode(256), nullable=True)
1730 description = Column('description', UnicodeText(10240), nullable=True)
1766 description = Column('description', UnicodeText(10240), nullable=True)
1731 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1767 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1732 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1768 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1733 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1769 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1734 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1770 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1735 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1771 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1736 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1772 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1737 org_ref = Column('org_ref', Unicode(256), nullable=False)
1773 org_ref = Column('org_ref', Unicode(256), nullable=False)
1738 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1774 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1739 other_ref = Column('other_ref', Unicode(256), nullable=False)
1775 other_ref = Column('other_ref', Unicode(256), nullable=False)
1740
1776
1741 @hybrid_property
1777 @hybrid_property
1742 def revisions(self):
1778 def revisions(self):
1743 return self._revisions.split(':')
1779 return self._revisions.split(':')
1744
1780
1745 @revisions.setter
1781 @revisions.setter
1746 def revisions(self, val):
1782 def revisions(self, val):
1747 self._revisions = ':'.join(val)
1783 self._revisions = ':'.join(val)
1748
1784
1749 author = relationship('User', lazy='joined')
1785 author = relationship('User', lazy='joined')
1750 reviewers = relationship('PullRequestReviewers',
1786 reviewers = relationship('PullRequestReviewers',
1751 cascade="all, delete, delete-orphan")
1787 cascade="all, delete, delete-orphan")
1752 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1788 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1753 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1789 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1754 statuses = relationship('ChangesetStatus')
1790 statuses = relationship('ChangesetStatus')
1755 comments = relationship('ChangesetComment',
1791 comments = relationship('ChangesetComment',
1756 cascade="all, delete, delete-orphan")
1792 cascade="all, delete, delete-orphan")
1757
1793
1758 def is_closed(self):
1794 def is_closed(self):
1759 return self.status == self.STATUS_CLOSED
1795 return self.status == self.STATUS_CLOSED
1760
1796
1761 def __json__(self):
1797 def __json__(self):
1762 return dict(
1798 return dict(
1763 revisions=self.revisions
1799 revisions=self.revisions
1764 )
1800 )
1765
1801
1766
1802
1767 class PullRequestReviewers(Base, BaseModel):
1803 class PullRequestReviewers(Base, BaseModel):
1768 __tablename__ = 'pull_request_reviewers'
1804 __tablename__ = 'pull_request_reviewers'
1769 __table_args__ = (
1805 __table_args__ = (
1770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1806 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1771 'mysql_charset': 'utf8'},
1807 'mysql_charset': 'utf8'},
1772 )
1808 )
1773
1809
1774 def __init__(self, user=None, pull_request=None):
1810 def __init__(self, user=None, pull_request=None):
1775 self.user = user
1811 self.user = user
1776 self.pull_request = pull_request
1812 self.pull_request = pull_request
1777
1813
1778 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1814 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1779 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1815 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1780 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1816 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1781
1817
1782 user = relationship('User')
1818 user = relationship('User')
1783 pull_request = relationship('PullRequest')
1819 pull_request = relationship('PullRequest')
1784
1820
1785
1821
1786 class Notification(Base, BaseModel):
1822 class Notification(Base, BaseModel):
1787 __tablename__ = 'notifications'
1823 __tablename__ = 'notifications'
1788 __table_args__ = (
1824 __table_args__ = (
1789 Index('notification_type_idx', 'type'),
1825 Index('notification_type_idx', 'type'),
1790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1826 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1791 'mysql_charset': 'utf8'},
1827 'mysql_charset': 'utf8'},
1792 )
1828 )
1793
1829
1794 TYPE_CHANGESET_COMMENT = u'cs_comment'
1830 TYPE_CHANGESET_COMMENT = u'cs_comment'
1795 TYPE_MESSAGE = u'message'
1831 TYPE_MESSAGE = u'message'
1796 TYPE_MENTION = u'mention'
1832 TYPE_MENTION = u'mention'
1797 TYPE_REGISTRATION = u'registration'
1833 TYPE_REGISTRATION = u'registration'
1798 TYPE_PULL_REQUEST = u'pull_request'
1834 TYPE_PULL_REQUEST = u'pull_request'
1799 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1835 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1800
1836
1801 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1837 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1802 subject = Column('subject', Unicode(512), nullable=True)
1838 subject = Column('subject', Unicode(512), nullable=True)
1803 body = Column('body', UnicodeText(50000), nullable=True)
1839 body = Column('body', UnicodeText(50000), nullable=True)
1804 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1840 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1805 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1841 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1806 type_ = Column('type', Unicode(256))
1842 type_ = Column('type', Unicode(256))
1807
1843
1808 created_by_user = relationship('User')
1844 created_by_user = relationship('User')
1809 notifications_to_users = relationship('UserNotification', lazy='joined',
1845 notifications_to_users = relationship('UserNotification', lazy='joined',
1810 cascade="all, delete, delete-orphan")
1846 cascade="all, delete, delete-orphan")
1811
1847
1812 @property
1848 @property
1813 def recipients(self):
1849 def recipients(self):
1814 return [x.user for x in UserNotification.query()\
1850 return [x.user for x in UserNotification.query()\
1815 .filter(UserNotification.notification == self)\
1851 .filter(UserNotification.notification == self)\
1816 .order_by(UserNotification.user_id.asc()).all()]
1852 .order_by(UserNotification.user_id.asc()).all()]
1817
1853
1818 @classmethod
1854 @classmethod
1819 def create(cls, created_by, subject, body, recipients, type_=None):
1855 def create(cls, created_by, subject, body, recipients, type_=None):
1820 if type_ is None:
1856 if type_ is None:
1821 type_ = Notification.TYPE_MESSAGE
1857 type_ = Notification.TYPE_MESSAGE
1822
1858
1823 notification = cls()
1859 notification = cls()
1824 notification.created_by_user = created_by
1860 notification.created_by_user = created_by
1825 notification.subject = subject
1861 notification.subject = subject
1826 notification.body = body
1862 notification.body = body
1827 notification.type_ = type_
1863 notification.type_ = type_
1828 notification.created_on = datetime.datetime.now()
1864 notification.created_on = datetime.datetime.now()
1829
1865
1830 for u in recipients:
1866 for u in recipients:
1831 assoc = UserNotification()
1867 assoc = UserNotification()
1832 assoc.notification = notification
1868 assoc.notification = notification
1833 u.notifications.append(assoc)
1869 u.notifications.append(assoc)
1834 Session().add(notification)
1870 Session().add(notification)
1835 return notification
1871 return notification
1836
1872
1837 @property
1873 @property
1838 def description(self):
1874 def description(self):
1839 from rhodecode.model.notification import NotificationModel
1875 from rhodecode.model.notification import NotificationModel
1840 return NotificationModel().make_description(self)
1876 return NotificationModel().make_description(self)
1841
1877
1842
1878
1843 class UserNotification(Base, BaseModel):
1879 class UserNotification(Base, BaseModel):
1844 __tablename__ = 'user_to_notification'
1880 __tablename__ = 'user_to_notification'
1845 __table_args__ = (
1881 __table_args__ = (
1846 UniqueConstraint('user_id', 'notification_id'),
1882 UniqueConstraint('user_id', 'notification_id'),
1847 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1883 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1848 'mysql_charset': 'utf8'}
1884 'mysql_charset': 'utf8'}
1849 )
1885 )
1850 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1886 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1851 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1887 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1852 read = Column('read', Boolean, default=False)
1888 read = Column('read', Boolean, default=False)
1853 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1889 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1854
1890
1855 user = relationship('User', lazy="joined")
1891 user = relationship('User', lazy="joined")
1856 notification = relationship('Notification', lazy="joined",
1892 notification = relationship('Notification', lazy="joined",
1857 order_by=lambda: Notification.created_on.desc(),)
1893 order_by=lambda: Notification.created_on.desc(),)
1858
1894
1859 def mark_as_read(self):
1895 def mark_as_read(self):
1860 self.read = True
1896 self.read = True
1861 Session().add(self)
1897 Session().add(self)
1862
1898
1863
1899
1864 class DbMigrateVersion(Base, BaseModel):
1900 class DbMigrateVersion(Base, BaseModel):
1865 __tablename__ = 'db_migrate_version'
1901 __tablename__ = 'db_migrate_version'
1866 __table_args__ = (
1902 __table_args__ = (
1867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1903 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1868 'mysql_charset': 'utf8'},
1904 'mysql_charset': 'utf8'},
1869 )
1905 )
1870 repository_id = Column('repository_id', String(250), primary_key=True)
1906 repository_id = Column('repository_id', String(250), primary_key=True)
1871 repository_path = Column('repository_path', Text)
1907 repository_path = Column('repository_path', Text)
1872 version = Column('version', Integer)
1908 version = Column('version', Integer)
@@ -1,331 +1,334 b''
1 <%page args="parent" />
1 <%page args="parent" />
2 <div class="box">
2 <div class="box">
3 <!-- box / title -->
3 <!-- box / title -->
4 <div class="title">
4 <div class="title">
5 <h5>
5 <h5>
6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
7 </h5>
7 </h5>
8 %if c.rhodecode_user.username != 'default':
8 %if c.rhodecode_user.username != 'default':
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 <ul class="links">
10 <ul class="links">
11 <li>
11 <li>
12 %if c.group:
12 %if c.group:
13 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span>
13 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span>
14 %else:
14 %else:
15 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
15 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
16 %endif
16 %endif
17 </li>
17 </li>
18 </ul>
18 </ul>
19 %endif
19 %endif
20 %endif
20 %endif
21 </div>
21 </div>
22 <!-- end box / title -->
22 <!-- end box / title -->
23 <div class="table">
23 <div class="table">
24 % if c.groups:
24 % if c.groups:
25 <div id='groups_list_wrap' class="yui-skin-sam">
25 <div id='groups_list_wrap' class="yui-skin-sam">
26 <table id="groups_list">
26 <table id="groups_list">
27 <thead>
27 <thead>
28 <tr>
28 <tr>
29 <th class="left"><a href="#">${_('Group name')}</a></th>
29 <th class="left"><a href="#">${_('Group name')}</a></th>
30 <th class="left"><a href="#">${_('Description')}</a></th>
30 <th class="left"><a href="#">${_('Description')}</a></th>
31 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
31 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
32 </tr>
32 </tr>
33 </thead>
33 </thead>
34
34
35 ## REPO GROUPS
35 ## REPO GROUPS
36 % for gr in c.groups:
36 % for gr in c.groups:
37 <tr>
37 <tr>
38 <td>
38 <td>
39 <div style="white-space: nowrap">
39 <div style="white-space: nowrap">
40 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
40 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
41 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
41 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
42 </div>
42 </div>
43 </td>
43 </td>
44 %if c.visual.stylify_metatags:
44 %if c.visual.stylify_metatags:
45 <td>${h.urlify_text(h.desc_stylize(gr.group_description))}</td>
45 <td>${h.urlify_text(h.desc_stylize(gr.group_description))}</td>
46 %else:
46 %else:
47 <td>${gr.group_description}</td>
47 <td>${gr.group_description}</td>
48 %endif
48 %endif
49 ## this is commented out since for multi nested repos can be HEAVY!
49 ## this is commented out since for multi nested repos can be HEAVY!
50 ## in number of executed queries during traversing uncomment at will
50 ## in number of executed queries during traversing uncomment at will
51 ##<td><b>${gr.repositories_recursive_count}</b></td>
51 ##<td><b>${gr.repositories_recursive_count}</b></td>
52 </tr>
52 </tr>
53 % endfor
53 % endfor
54
54
55 </table>
55 </table>
56 </div>
56 </div>
57 <div style="height: 20px"></div>
57 <div style="height: 20px"></div>
58 % endif
58 % endif
59 <div id="welcome" style="display:none;text-align:center">
59 <div id="welcome" style="display:none;text-align:center">
60 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
60 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
61 </div>
61 </div>
62 <%cnt=0%>
62 <%cnt=0%>
63 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
63 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
64 % if c.visual.lightweight_dashboard is False:
64 % if c.visual.lightweight_dashboard is False:
65 ## old full detailed version
65 ## old full detailed version
66 <div id='repos_list_wrap' class="yui-skin-sam">
66 <div id='repos_list_wrap' class="yui-skin-sam">
67 <table id="repos_list">
67 <table id="repos_list">
68 <thead>
68 <thead>
69 <tr>
69 <tr>
70 <th class="left"></th>
70 <th class="left"></th>
71 <th class="left">${_('Name')}</th>
71 <th class="left">${_('Name')}</th>
72 <th class="left">${_('Description')}</th>
72 <th class="left">${_('Description')}</th>
73 <th class="left">${_('Last change')}</th>
73 <th class="left">${_('Last change')}</th>
74 <th class="left">${_('Tip')}</th>
74 <th class="left">${_('Tip')}</th>
75 <th class="left">${_('Owner')}</th>
75 <th class="left">${_('Owner')}</th>
76 <th class="left">${_('RSS')}</th>
76 <th class="left">${_('RSS')}</th>
77 <th class="left">${_('Atom')}</th>
77 <th class="left">${_('Atom')}</th>
78 </tr>
78 </tr>
79 </thead>
79 </thead>
80 <tbody>
80 <tbody>
81 %for cnt,repo in enumerate(c.repos_list):
81 %for cnt,repo in enumerate(c.repos_list):
82 <tr class="parity${(cnt+1)%2}">
82 <tr class="parity${(cnt+1)%2}">
83 ##QUICK MENU
83 ##QUICK MENU
84 <td class="quick_repo_menu">
84 <td class="quick_repo_menu">
85 ${dt.quick_menu(repo['name'])}
85 ${dt.quick_menu(repo['name'])}
86 </td>
86 </td>
87 ##REPO NAME AND ICONS
87 ##REPO NAME AND ICONS
88 <td class="reponame">
88 <td class="reponame">
89 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']),pageargs.get('short_repo_names'))}
89 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']),pageargs.get('short_repo_names'))}
90 </td>
90 </td>
91 ##DESCRIPTION
91 ##DESCRIPTION
92 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
92 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
93 %if c.visual.stylify_metatags:
93 %if c.visual.stylify_metatags:
94 ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span>
94 ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span>
95 %else:
95 %else:
96 ${h.truncate(repo['description'],60)}</span>
96 ${h.truncate(repo['description'],60)}</span>
97 %endif
97 %endif
98 </td>
98 </td>
99 ##LAST CHANGE DATE
99 ##LAST CHANGE DATE
100 <td>
100 <td>
101 ${dt.last_change(repo['last_change'])}
101 ${dt.last_change(repo['last_change'])}
102 </td>
102 </td>
103 ##LAST REVISION
103 ##LAST REVISION
104 <td>
104 <td>
105 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
105 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
106 </td>
106 </td>
107 ##
107 ##
108 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
108 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
109 <td>
109 <td>
110 ${dt.rss(repo['name'])}
110 ${dt.rss(repo['name'])}
111 </td>
111 </td>
112 <td>
112 <td>
113 ${dt.atom(repo['name'])}
113 ${dt.atom(repo['name'])}
114 </td>
114 </td>
115 </tr>
115 </tr>
116 %endfor
116 %endfor
117 </tbody>
117 </tbody>
118 </table>
118 </table>
119 </div>
119 </div>
120 % else:
120 % else:
121 ## lightweight version
121 ## lightweight version
122 <div class="yui-skin-sam" id="repos_list_wrap"></div>
122 <div class="yui-skin-sam" id="repos_list_wrap"></div>
123 <div id="user-paginator" style="padding: 0px 0px 0px 0px"></div>
123 <div id="user-paginator" style="padding: 0px 0px 0px 0px"></div>
124 % endif
124 % endif
125 </div>
125 </div>
126 </div>
126 </div>
127 % if c.visual.lightweight_dashboard is False:
127 % if c.visual.lightweight_dashboard is False:
128 <script>
128 <script>
129 YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0};
129 YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0};
130 var func = function(node){
130 var func = function(node){
131 return node.parentNode.parentNode.parentNode.parentNode;
131 return node.parentNode.parentNode.parentNode.parentNode;
132 }
132 }
133
133
134 // groups table sorting
134 // groups table sorting
135 var myColumnDefs = [
135 var myColumnDefs = [
136 {key:"name",label:"${_('Group name')}",sortable:true,
136 {key:"name",label:"${_('Group name')}",sortable:true,
137 sortOptions: { sortFunction: groupNameSort }},
137 sortOptions: { sortFunction: groupNameSort }},
138 {key:"desc",label:"${_('Description')}",sortable:true},
138 {key:"desc",label:"${_('Description')}",sortable:true},
139 ];
139 ];
140
140
141 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
141 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
142
142
143 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
143 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
144 myDataSource.responseSchema = {
144 myDataSource.responseSchema = {
145 fields: [
145 fields: [
146 {key:"name"},
146 {key:"name"},
147 {key:"desc"},
147 {key:"desc"},
148 ]
148 ]
149 };
149 };
150
150
151 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{
151 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{
152 sortedBy:{key:"name",dir:"asc"},
152 sortedBy:{key:"name",dir:"asc"},
153 paginator: new YAHOO.widget.Paginator({
153 paginator: new YAHOO.widget.Paginator({
154 rowsPerPage: 5,
154 rowsPerPage: 5,
155 alwaysVisible: false,
155 alwaysVisible: false,
156 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
156 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
157 pageLinks: 5,
157 pageLinks: 5,
158 containerClass: 'pagination-wh',
158 containerClass: 'pagination-wh',
159 currentPageClass: 'pager_curpage',
159 currentPageClass: 'pager_curpage',
160 pageLinkClass: 'pager_link',
160 pageLinkClass: 'pager_link',
161 nextPageLinkLabel: '&gt;',
161 nextPageLinkLabel: '&gt;',
162 previousPageLinkLabel: '&lt;',
162 previousPageLinkLabel: '&lt;',
163 firstPageLinkLabel: '&lt;&lt;',
163 firstPageLinkLabel: '&lt;&lt;',
164 lastPageLinkLabel: '&gt;&gt;',
164 lastPageLinkLabel: '&gt;&gt;',
165 containers:['user-paginator']
165 containers:['user-paginator']
166 }),
166 }),
167 MSG_SORTASC:"${_('Click to sort ascending')}",
167 MSG_SORTASC:"${_('Click to sort ascending')}",
168 MSG_SORTDESC:"${_('Click to sort descending')}"
168 MSG_SORTDESC:"${_('Click to sort descending')}"
169 });
169 });
170
170
171 // main table sorting
171 // main table sorting
172 var myColumnDefs = [
172 var myColumnDefs = [
173 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
173 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
174 {key:"name",label:"${_('Name')}",sortable:true,
174 {key:"name",label:"${_('Name')}",sortable:true,
175 sortOptions: { sortFunction: nameSort }},
175 sortOptions: { sortFunction: nameSort }},
176 {key:"desc",label:"${_('Description')}",sortable:true},
176 {key:"desc",label:"${_('Description')}",sortable:true},
177 {key:"last_change",label:"${_('Last Change')}",sortable:true,
177 {key:"last_change",label:"${_('Last Change')}",sortable:true,
178 sortOptions: { sortFunction: ageSort }},
178 sortOptions: { sortFunction: ageSort }},
179 {key:"tip",label:"${_('Tip')}",sortable:true,
179 {key:"tip",label:"${_('Tip')}",sortable:true,
180 sortOptions: { sortFunction: revisionSort }},
180 sortOptions: { sortFunction: revisionSort }},
181 {key:"owner",label:"${_('Owner')}",sortable:true},
181 {key:"owner",label:"${_('Owner')}",sortable:true},
182 {key:"rss",label:"",sortable:false},
182 {key:"rss",label:"",sortable:false},
183 {key:"atom",label:"",sortable:false},
183 {key:"atom",label:"",sortable:false},
184 ];
184 ];
185
185
186 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
186 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
187
187
188 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
188 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
189
189
190 myDataSource.responseSchema = {
190 myDataSource.responseSchema = {
191 fields: [
191 fields: [
192 {key:"menu"},
192 {key:"menu"},
193 //{key:"raw_name"},
193 //{key:"raw_name"},
194 {key:"name"},
194 {key:"name"},
195 {key:"desc"},
195 {key:"desc"},
196 {key:"last_change"},
196 {key:"last_change"},
197 {key:"tip"},
197 {key:"tip"},
198 {key:"owner"},
198 {key:"owner"},
199 {key:"rss"},
199 {key:"rss"},
200 {key:"atom"},
200 {key:"atom"},
201 ]
201 ]
202 };
202 };
203
203
204 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
204 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
205 {
205 {
206 sortedBy:{key:"name",dir:"asc"},
206 sortedBy:{key:"name",dir:"asc"},
207 MSG_SORTASC:"${_('Click to sort ascending')}",
207 MSG_SORTASC:"${_('Click to sort ascending')}",
208 MSG_SORTDESC:"${_('Click to sort descending')}",
208 MSG_SORTDESC:"${_('Click to sort descending')}",
209 MSG_EMPTY:"${_('No records found.')}",
209 MSG_EMPTY:"${_('No records found.')}",
210 MSG_ERROR:"${_('Data error.')}",
210 MSG_ERROR:"${_('Data error.')}",
211 MSG_LOADING:"${_('Loading...')}",
211 MSG_LOADING:"${_('Loading...')}",
212 }
212 }
213 );
213 );
214 myDataTable.subscribe('postRenderEvent',function(oArgs) {
214 myDataTable.subscribe('postRenderEvent',function(oArgs) {
215 tooltip_activate();
215 tooltip_activate();
216 quick_repo_menu();
216 quick_repo_menu();
217 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
217 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
218 });
218 });
219
219
220 </script>
220 </script>
221 % else:
221 % else:
222 <script>
222 <script>
223 //var url = "${h.url('formatted_users', format='json')}";
223 //var url = "${h.url('formatted_users', format='json')}";
224 var data = ${c.data|n};
224 var data = ${c.data|n};
225 var myDataSource = new YAHOO.util.DataSource(data);
225 var myDataSource = new YAHOO.util.DataSource(data);
226 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
226 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
227
227
228 myDataSource.responseSchema = {
228 myDataSource.responseSchema = {
229 resultsList: "records",
229 resultsList: "records",
230 fields: [
230 fields: [
231 {key:"menu"},
231 {key:"menu"},
232 {key:"raw_name"},
232 {key:"raw_name"},
233 {key:"name"},
233 {key:"name"},
234 {key:"desc"},
234 {key:"desc"},
235 {key:"last_change"},
235 {key:"last_change"},
236 {key: "tip"},
236 {key:"owner"},
237 {key:"owner"},
237 {key:"rss"},
238 {key:"rss"},
238 {key:"atom"},
239 {key:"atom"},
239 ]
240 ]
240 };
241 };
241 myDataSource.doBeforeCallback = function(req,raw,res,cb) {
242 myDataSource.doBeforeCallback = function(req,raw,res,cb) {
242 // This is the filter function
243 // This is the filter function
243 var data = res.results || [],
244 var data = res.results || [],
244 filtered = [],
245 filtered = [],
245 i,l;
246 i,l;
246
247
247 if (req) {
248 if (req) {
248 req = req.toLowerCase();
249 req = req.toLowerCase();
249 for (i = 0; i<data.length; i++) {
250 for (i = 0; i<data.length; i++) {
250 var pos = data[i].raw_name.toLowerCase().indexOf(req)
251 var pos = data[i].raw_name.toLowerCase().indexOf(req)
251 if (pos != -1) {
252 if (pos != -1) {
252 filtered.push(data[i]);
253 filtered.push(data[i]);
253 }
254 }
254 }
255 }
255 res.results = filtered;
256 res.results = filtered;
256 }
257 }
257 YUD.get('repo_count').innerHTML = res.results.length;
258 YUD.get('repo_count').innerHTML = res.results.length;
258 return res;
259 return res;
259 }
260 }
260
261
261 // main table sorting
262 // main table sorting
262 var myColumnDefs = [
263 var myColumnDefs = [
263 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
264 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
264 {key:"name",label:"${_('Name')}",sortable:true,
265 {key:"name",label:"${_('Name')}",sortable:true,
265 sortOptions: { sortFunction: nameSort }},
266 sortOptions: { sortFunction: nameSort }},
266 {key:"desc",label:"${_('Description')}",sortable:true},
267 {key:"desc",label:"${_('Description')}",sortable:true},
267 {key:"last_change",label:"${_('Last Change')}",sortable:true,
268 {key:"last_change",label:"${_('Last Change')}",sortable:true,
268 sortOptions: { sortFunction: ageSort }},
269 sortOptions: { sortFunction: ageSort }},
270 {key:"tip",label:"${_('Tip')}",sortable:true,
271 sortOptions: { sortFunction: revisionSort }},
269 {key:"owner",label:"${_('Owner')}",sortable:true},
272 {key:"owner",label:"${_('Owner')}",sortable:true},
270 {key:"rss",label:"",sortable:false},
273 {key:"rss",label:"",sortable:false},
271 {key:"atom",label:"",sortable:false},
274 {key:"atom",label:"",sortable:false},
272 ];
275 ];
273
276
274 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
277 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
275 sortedBy:{key:"name",dir:"asc"},
278 sortedBy:{key:"name",dir:"asc"},
276 paginator: new YAHOO.widget.Paginator({
279 paginator: new YAHOO.widget.Paginator({
277 rowsPerPage: ${c.visual.lightweight_dashboard_items},
280 rowsPerPage: ${c.visual.lightweight_dashboard_items},
278 alwaysVisible: false,
281 alwaysVisible: false,
279 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
282 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
280 pageLinks: 5,
283 pageLinks: 5,
281 containerClass: 'pagination-wh',
284 containerClass: 'pagination-wh',
282 currentPageClass: 'pager_curpage',
285 currentPageClass: 'pager_curpage',
283 pageLinkClass: 'pager_link',
286 pageLinkClass: 'pager_link',
284 nextPageLinkLabel: '&gt;',
287 nextPageLinkLabel: '&gt;',
285 previousPageLinkLabel: '&lt;',
288 previousPageLinkLabel: '&lt;',
286 firstPageLinkLabel: '&lt;&lt;',
289 firstPageLinkLabel: '&lt;&lt;',
287 lastPageLinkLabel: '&gt;&gt;',
290 lastPageLinkLabel: '&gt;&gt;',
288 containers:['user-paginator']
291 containers:['user-paginator']
289 }),
292 }),
290
293
291 MSG_SORTASC:"${_('Click to sort ascending')}",
294 MSG_SORTASC:"${_('Click to sort ascending')}",
292 MSG_SORTDESC:"${_('Click to sort descending')}",
295 MSG_SORTDESC:"${_('Click to sort descending')}",
293 MSG_EMPTY:"${_('No records found.')}",
296 MSG_EMPTY:"${_('No records found.')}",
294 MSG_ERROR:"${_('Data error.')}",
297 MSG_ERROR:"${_('Data error.')}",
295 MSG_LOADING:"${_('Loading...')}",
298 MSG_LOADING:"${_('Loading...')}",
296 }
299 }
297 );
300 );
298 myDataTable.subscribe('postRenderEvent',function(oArgs) {
301 myDataTable.subscribe('postRenderEvent',function(oArgs) {
299 tooltip_activate();
302 tooltip_activate();
300 quick_repo_menu();
303 quick_repo_menu();
301 });
304 });
302
305
303 var filterTimeout = null;
306 var filterTimeout = null;
304
307
305 updateFilter = function () {
308 updateFilter = function () {
306 // Reset timeout
309 // Reset timeout
307 filterTimeout = null;
310 filterTimeout = null;
308
311
309 // Reset sort
312 // Reset sort
310 var state = myDataTable.getState();
313 var state = myDataTable.getState();
311 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
314 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
312
315
313 // Get filtered data
316 // Get filtered data
314 myDataSource.sendRequest(YUD.get('q_filter').value,{
317 myDataSource.sendRequest(YUD.get('q_filter').value,{
315 success : myDataTable.onDataReturnInitializeTable,
318 success : myDataTable.onDataReturnInitializeTable,
316 failure : myDataTable.onDataReturnInitializeTable,
319 failure : myDataTable.onDataReturnInitializeTable,
317 scope : myDataTable,
320 scope : myDataTable,
318 argument: state
321 argument: state
319 });
322 });
320
323
321 };
324 };
322 YUE.on('q_filter','click',function(){
325 YUE.on('q_filter','click',function(){
323 YUD.get('q_filter').value = '';
326 YUD.get('q_filter').value = '';
324 });
327 });
325
328
326 YUE.on('q_filter','keyup',function (e) {
329 YUE.on('q_filter','keyup',function (e) {
327 clearTimeout(filterTimeout);
330 clearTimeout(filterTimeout);
328 filterTimeout = setTimeout(updateFilter,600);
331 filterTimeout = setTimeout(updateFilter,600);
329 });
332 });
330 </script>
333 </script>
331 % endif
334 % endif
@@ -1,45 +1,45 b''
1 [egg_info]
1 [egg_info]
2 tag_build =
2 tag_build =
3 tag_svn_revision = true
3 tag_svn_revision = true
4
4
5 [easy_install]
5 [easy_install]
6 find_links = http://www.pylonshq.com/download/
6 find_links = http://www.pylonshq.com/download/
7
7
8 [nosetests]
8 [nosetests]
9 verbose=True
9 verbose=True
10 verbosity=2
10 verbosity=2
11 with-pylons=test.ini
11 with-pylons=test.ini
12 detailed-errors=1
12 detailed-errors=1
13 nologcapture=1
13 nologcapture=1
14 #pdb=1
14 pdb=1
15 #pdb-failures=1
15 pdb-failures=1
16
16
17 # Babel configuration
17 # Babel configuration
18 [compile_catalog]
18 [compile_catalog]
19 domain = rhodecode
19 domain = rhodecode
20 directory = rhodecode/i18n
20 directory = rhodecode/i18n
21 statistics = true
21 statistics = true
22
22
23 [extract_messages]
23 [extract_messages]
24 add_comments = TRANSLATORS:
24 add_comments = TRANSLATORS:
25 output_file = rhodecode/i18n/rhodecode.pot
25 output_file = rhodecode/i18n/rhodecode.pot
26 width = 80
26 width = 80
27
27
28 [init_catalog]
28 [init_catalog]
29 domain = rhodecode
29 domain = rhodecode
30 input_file = rhodecode/i18n/rhodecode.pot
30 input_file = rhodecode/i18n/rhodecode.pot
31 output_dir = rhodecode/i18n
31 output_dir = rhodecode/i18n
32
32
33 [update_catalog]
33 [update_catalog]
34 domain = rhodecode
34 domain = rhodecode
35 input_file = rhodecode/i18n/rhodecode.pot
35 input_file = rhodecode/i18n/rhodecode.pot
36 output_dir = rhodecode/i18n
36 output_dir = rhodecode/i18n
37 previous = true
37 previous = true
38
38
39 [build_sphinx]
39 [build_sphinx]
40 source-dir = docs/
40 source-dir = docs/
41 build-dir = docs/_build
41 build-dir = docs/_build
42 all_files = 1
42 all_files = 1
43
43
44 [upload_sphinx]
44 [upload_sphinx]
45 upload-dir = docs/_build/html No newline at end of file
45 upload-dir = docs/_build/html
General Comments 0
You need to be logged in to leave comments. Login now