##// END OF EJS Templates
invalidation: some documentation and refactoring
Mads Kiilerich -
r3606:c8ecfe42 beta
parent child Browse files
Show More
@@ -1,800 +1,794 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 user:%s ip:%s' %
165 log.info('Logging action:%s on %s by user:%s ip:%s' %
166 (action, safe_unicode(repo), user_obj, ipaddr))
166 (action, safe_unicode(repo), user_obj, ipaddr))
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, skip_removed_repos=True):
174 def get_repos(path, recursive=False, skip_removed_repos=True):
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 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
185
185
186 def _get_repos(p):
186 def _get_repos(p):
187 if not os.access(p, os.W_OK):
187 if not os.access(p, os.W_OK):
188 return
188 return
189 for dirpath in os.listdir(p):
189 for dirpath in os.listdir(p):
190 if os.path.isfile(os.path.join(p, dirpath)):
190 if os.path.isfile(os.path.join(p, dirpath)):
191 continue
191 continue
192 cur_path = os.path.join(p, dirpath)
192 cur_path = os.path.join(p, dirpath)
193
193
194 # skip removed repos
194 # skip removed repos
195 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
195 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
196 continue
196 continue
197
197
198 #skip .<somethin> dirs
198 #skip .<somethin> dirs
199 if dirpath.startswith('.'):
199 if dirpath.startswith('.'):
200 continue
200 continue
201
201
202 try:
202 try:
203 scm_info = get_scm(cur_path)
203 scm_info = get_scm(cur_path)
204 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
204 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
205 except VCSError:
205 except VCSError:
206 if not recursive:
206 if not recursive:
207 continue
207 continue
208 #check if this dir containts other repos for recursive scan
208 #check if this dir containts other repos for recursive scan
209 rec_path = os.path.join(p, dirpath)
209 rec_path = os.path.join(p, dirpath)
210 if os.path.isdir(rec_path):
210 if os.path.isdir(rec_path):
211 for inner_scm in _get_repos(rec_path):
211 for inner_scm in _get_repos(rec_path):
212 yield inner_scm
212 yield inner_scm
213
213
214 return _get_repos(path)
214 return _get_repos(path)
215
215
216 #alias for backward compat
216 #alias for backward compat
217 get_filesystem_repos = get_repos
217 get_filesystem_repos = get_repos
218
218
219
219
220 def is_valid_repo(repo_name, base_path, scm=None):
220 def is_valid_repo(repo_name, base_path, scm=None):
221 """
221 """
222 Returns True if given path is a valid repository False otherwise.
222 Returns True if given path is a valid repository False otherwise.
223 If scm param is given also compare if given scm is the same as expected
223 If scm param is given also compare if given scm is the same as expected
224 from scm parameter
224 from scm parameter
225
225
226 :param repo_name:
226 :param repo_name:
227 :param base_path:
227 :param base_path:
228 :param scm:
228 :param scm:
229
229
230 :return True: if given path is a valid repository
230 :return True: if given path is a valid repository
231 """
231 """
232 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
232 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
233
233
234 try:
234 try:
235 scm_ = get_scm(full_path)
235 scm_ = get_scm(full_path)
236 if scm:
236 if scm:
237 return scm_[0] == scm
237 return scm_[0] == scm
238 return True
238 return True
239 except VCSError:
239 except VCSError:
240 return False
240 return False
241
241
242
242
243 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
243 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
244 """
244 """
245 Returns True if given path is a repos group False otherwise
245 Returns True if given path is a repos group False otherwise
246
246
247 :param repo_name:
247 :param repo_name:
248 :param base_path:
248 :param base_path:
249 """
249 """
250 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
250 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
251
251
252 # check if it's not a repo
252 # check if it's not a repo
253 if is_valid_repo(repos_group_name, base_path):
253 if is_valid_repo(repos_group_name, base_path):
254 return False
254 return False
255
255
256 try:
256 try:
257 # we need to check bare git repos at higher level
257 # we need to check bare git repos at higher level
258 # since we might match branches/hooks/info/objects or possible
258 # since we might match branches/hooks/info/objects or possible
259 # other things inside bare git repo
259 # other things inside bare git repo
260 get_scm(os.path.dirname(full_path))
260 get_scm(os.path.dirname(full_path))
261 return False
261 return False
262 except VCSError:
262 except VCSError:
263 pass
263 pass
264
264
265 # check if it's a valid path
265 # check if it's a valid path
266 if skip_path_check or os.path.isdir(full_path):
266 if skip_path_check or os.path.isdir(full_path):
267 return True
267 return True
268
268
269 return False
269 return False
270
270
271
271
272 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
272 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
273 while True:
273 while True:
274 ok = raw_input(prompt)
274 ok = raw_input(prompt)
275 if ok in ('y', 'ye', 'yes'):
275 if ok in ('y', 'ye', 'yes'):
276 return True
276 return True
277 if ok in ('n', 'no', 'nop', 'nope'):
277 if ok in ('n', 'no', 'nop', 'nope'):
278 return False
278 return False
279 retries = retries - 1
279 retries = retries - 1
280 if retries < 0:
280 if retries < 0:
281 raise IOError
281 raise IOError
282 print complaint
282 print complaint
283
283
284 #propagated from mercurial documentation
284 #propagated from mercurial documentation
285 ui_sections = ['alias', 'auth',
285 ui_sections = ['alias', 'auth',
286 'decode/encode', 'defaults',
286 'decode/encode', 'defaults',
287 'diff', 'email',
287 'diff', 'email',
288 'extensions', 'format',
288 'extensions', 'format',
289 'merge-patterns', 'merge-tools',
289 'merge-patterns', 'merge-tools',
290 'hooks', 'http_proxy',
290 'hooks', 'http_proxy',
291 'smtp', 'patch',
291 'smtp', 'patch',
292 'paths', 'profiling',
292 'paths', 'profiling',
293 'server', 'trusted',
293 'server', 'trusted',
294 'ui', 'web', ]
294 'ui', 'web', ]
295
295
296
296
297 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
297 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
298 """
298 """
299 A function that will read python rc files or database
299 A function that will read python rc files or database
300 and make an mercurial ui object from read options
300 and make an mercurial ui object from read options
301
301
302 :param path: path to mercurial config file
302 :param path: path to mercurial config file
303 :param checkpaths: check the path
303 :param checkpaths: check the path
304 :param read_from: read from 'file' or 'db'
304 :param read_from: read from 'file' or 'db'
305 """
305 """
306
306
307 baseui = ui.ui()
307 baseui = ui.ui()
308
308
309 # clean the baseui object
309 # clean the baseui object
310 baseui._ocfg = config.config()
310 baseui._ocfg = config.config()
311 baseui._ucfg = config.config()
311 baseui._ucfg = config.config()
312 baseui._tcfg = config.config()
312 baseui._tcfg = config.config()
313
313
314 if read_from == 'file':
314 if read_from == 'file':
315 if not os.path.isfile(path):
315 if not os.path.isfile(path):
316 log.debug('hgrc file is not present at %s, skipping...' % path)
316 log.debug('hgrc file is not present at %s, skipping...' % path)
317 return False
317 return False
318 log.debug('reading hgrc from %s' % path)
318 log.debug('reading hgrc from %s' % path)
319 cfg = config.config()
319 cfg = config.config()
320 cfg.read(path)
320 cfg.read(path)
321 for section in ui_sections:
321 for section in ui_sections:
322 for k, v in cfg.items(section):
322 for k, v in cfg.items(section):
323 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
323 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
324 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
324 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
325
325
326 elif read_from == 'db':
326 elif read_from == 'db':
327 sa = meta.Session()
327 sa = meta.Session()
328 ret = sa.query(RhodeCodeUi)\
328 ret = sa.query(RhodeCodeUi)\
329 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
329 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
330 .all()
330 .all()
331
331
332 hg_ui = ret
332 hg_ui = ret
333 for ui_ in hg_ui:
333 for ui_ in hg_ui:
334 if ui_.ui_active:
334 if ui_.ui_active:
335 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
335 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
336 ui_.ui_key, ui_.ui_value)
336 ui_.ui_key, ui_.ui_value)
337 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
337 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
338 safe_str(ui_.ui_value))
338 safe_str(ui_.ui_value))
339 if ui_.ui_key == 'push_ssl':
339 if ui_.ui_key == 'push_ssl':
340 # force set push_ssl requirement to False, rhodecode
340 # force set push_ssl requirement to False, rhodecode
341 # handles that
341 # handles that
342 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
342 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
343 False)
343 False)
344 if clear_session:
344 if clear_session:
345 meta.Session.remove()
345 meta.Session.remove()
346 return baseui
346 return baseui
347
347
348
348
349 def set_rhodecode_config(config):
349 def set_rhodecode_config(config):
350 """
350 """
351 Updates pylons config with new settings from database
351 Updates pylons config with new settings from database
352
352
353 :param config:
353 :param config:
354 """
354 """
355 hgsettings = RhodeCodeSetting.get_app_settings()
355 hgsettings = RhodeCodeSetting.get_app_settings()
356
356
357 for k, v in hgsettings.items():
357 for k, v in hgsettings.items():
358 config[k] = v
358 config[k] = v
359
359
360
360
361 def invalidate_cache(cache_key, *args):
361 def invalidate_cache(cache_key, *args):
362 """
362 """
363 Puts cache invalidation task into db for
363 Puts cache invalidation task into db for
364 further global cache invalidation
364 further global cache invalidation
365 """
365 """
366
366
367 from rhodecode.model.scm import ScmModel
367 from rhodecode.model.scm import ScmModel
368
368
369 if cache_key.startswith('get_repo_cached_'):
369 if cache_key.startswith('get_repo_cached_'):
370 name = cache_key.split('get_repo_cached_')[-1]
370 name = cache_key.split('get_repo_cached_')[-1]
371 ScmModel().mark_for_invalidation(name)
371 ScmModel().mark_for_invalidation(name)
372
372
373
373
374 def map_groups(path):
374 def map_groups(path):
375 """
375 """
376 Given a full path to a repository, create all nested groups that this
376 Given a full path to a repository, create all nested groups that this
377 repo is inside. This function creates parent-child relationships between
377 repo is inside. This function creates parent-child relationships between
378 groups and creates default perms for all new groups.
378 groups and creates default perms for all new groups.
379
379
380 :param paths: full path to repository
380 :param paths: full path to repository
381 """
381 """
382 sa = meta.Session()
382 sa = meta.Session()
383 groups = path.split(Repository.url_sep())
383 groups = path.split(Repository.url_sep())
384 parent = None
384 parent = None
385 group = None
385 group = None
386
386
387 # last element is repo in nested groups structure
387 # last element is repo in nested groups structure
388 groups = groups[:-1]
388 groups = groups[:-1]
389 rgm = ReposGroupModel(sa)
389 rgm = ReposGroupModel(sa)
390 for lvl, group_name in enumerate(groups):
390 for lvl, group_name in enumerate(groups):
391 group_name = '/'.join(groups[:lvl] + [group_name])
391 group_name = '/'.join(groups[:lvl] + [group_name])
392 group = RepoGroup.get_by_group_name(group_name)
392 group = RepoGroup.get_by_group_name(group_name)
393 desc = '%s group' % group_name
393 desc = '%s group' % group_name
394
394
395 # skip folders that are now removed repos
395 # skip folders that are now removed repos
396 if REMOVED_REPO_PAT.match(group_name):
396 if REMOVED_REPO_PAT.match(group_name):
397 break
397 break
398
398
399 if group is None:
399 if group is None:
400 log.debug('creating group level: %s group_name: %s' % (lvl,
400 log.debug('creating group level: %s group_name: %s' % (lvl,
401 group_name))
401 group_name))
402 group = RepoGroup(group_name, parent)
402 group = RepoGroup(group_name, parent)
403 group.group_description = desc
403 group.group_description = desc
404 sa.add(group)
404 sa.add(group)
405 rgm._create_default_perms(group)
405 rgm._create_default_perms(group)
406 sa.flush()
406 sa.flush()
407 parent = group
407 parent = group
408 return group
408 return group
409
409
410
410
411 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
411 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
412 install_git_hook=False):
412 install_git_hook=False):
413 """
413 """
414 maps all repos given in initial_repo_list, non existing repositories
414 maps all repos given in initial_repo_list, non existing repositories
415 are created, if remove_obsolete is True it also check for db entries
415 are created, if remove_obsolete is True it also check for db entries
416 that are not in initial_repo_list and removes them.
416 that are not in initial_repo_list and removes them.
417
417
418 :param initial_repo_list: list of repositories found by scanning methods
418 :param initial_repo_list: list of repositories found by scanning methods
419 :param remove_obsolete: check for obsolete entries in database
419 :param remove_obsolete: check for obsolete entries in database
420 :param install_git_hook: if this is True, also check and install githook
420 :param install_git_hook: if this is True, also check and install githook
421 for a repo if missing
421 for a repo if missing
422 """
422 """
423 from rhodecode.model.repo import RepoModel
423 from rhodecode.model.repo import RepoModel
424 from rhodecode.model.scm import ScmModel
424 from rhodecode.model.scm import ScmModel
425 sa = meta.Session()
425 sa = meta.Session()
426 rm = RepoModel()
426 rm = RepoModel()
427 user = sa.query(User).filter(User.admin == True).first()
427 user = sa.query(User).filter(User.admin == True).first()
428 if user is None:
428 if user is None:
429 raise Exception('Missing administrative account!')
429 raise Exception('Missing administrative account!')
430 added = []
430 added = []
431
431
432 # # clear cache keys
433 # log.debug("Clearing cache keys now...")
434 # CacheInvalidation.clear_cache()
435 # sa.commit()
436
437 ##creation defaults
432 ##creation defaults
438 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
433 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
439 enable_statistics = defs.get('repo_enable_statistics')
434 enable_statistics = defs.get('repo_enable_statistics')
440 enable_locking = defs.get('repo_enable_locking')
435 enable_locking = defs.get('repo_enable_locking')
441 enable_downloads = defs.get('repo_enable_downloads')
436 enable_downloads = defs.get('repo_enable_downloads')
442 private = defs.get('repo_private')
437 private = defs.get('repo_private')
443
438
444 for name, repo in initial_repo_list.items():
439 for name, repo in initial_repo_list.items():
445 group = map_groups(name)
440 group = map_groups(name)
446 db_repo = rm.get_by_repo_name(name)
441 db_repo = rm.get_by_repo_name(name)
447 # found repo that is on filesystem not in RhodeCode database
442 # found repo that is on filesystem not in RhodeCode database
448 if not db_repo:
443 if not db_repo:
449 log.info('repository %s not found, creating now' % name)
444 log.info('repository %s not found, creating now' % name)
450 added.append(name)
445 added.append(name)
451 desc = (repo.description
446 desc = (repo.description
452 if repo.description != 'unknown'
447 if repo.description != 'unknown'
453 else '%s repository' % name)
448 else '%s repository' % name)
454
449
455 new_repo = rm.create_repo(
450 new_repo = rm.create_repo(
456 repo_name=name,
451 repo_name=name,
457 repo_type=repo.alias,
452 repo_type=repo.alias,
458 description=desc,
453 description=desc,
459 repos_group=getattr(group, 'group_id', None),
454 repos_group=getattr(group, 'group_id', None),
460 owner=user,
455 owner=user,
461 just_db=True,
456 just_db=True,
462 enable_locking=enable_locking,
457 enable_locking=enable_locking,
463 enable_downloads=enable_downloads,
458 enable_downloads=enable_downloads,
464 enable_statistics=enable_statistics,
459 enable_statistics=enable_statistics,
465 private=private
460 private=private
466 )
461 )
467 # we added that repo just now, and make sure it has githook
462 # we added that repo just now, and make sure it has githook
468 # installed
463 # installed
469 if new_repo.repo_type == 'git':
464 if new_repo.repo_type == 'git':
470 ScmModel().install_git_hook(new_repo.scm_instance)
465 ScmModel().install_git_hook(new_repo.scm_instance)
471 new_repo.update_changeset_cache()
466 new_repo.update_changeset_cache()
472 elif install_git_hook:
467 elif install_git_hook:
473 if db_repo.repo_type == 'git':
468 if db_repo.repo_type == 'git':
474 ScmModel().install_git_hook(db_repo.scm_instance)
469 ScmModel().install_git_hook(db_repo.scm_instance)
475 # during starting install all cache keys for all repositories in the
470 # during starting install all cache keys for all repositories in the
476 # system, this will register all repos and multiple instances
471 # system, this will register all repos and multiple instances
477 key, _prefix, _org_key = CacheInvalidation._get_key(name)
472 cache_key = CacheInvalidation._get_cache_key(name)
473 log.debug("Creating invalidation cache key for %s: %s", name, cache_key)
478 CacheInvalidation.invalidate(name)
474 CacheInvalidation.invalidate(name)
479 log.debug("Creating a cache key for %s, instance_id %s"
480 % (name, _prefix or 'unknown'))
481
475
482 sa.commit()
476 sa.commit()
483 removed = []
477 removed = []
484 if remove_obsolete:
478 if remove_obsolete:
485 # remove from database those repositories that are not in the filesystem
479 # remove from database those repositories that are not in the filesystem
486 for repo in sa.query(Repository).all():
480 for repo in sa.query(Repository).all():
487 if repo.repo_name not in initial_repo_list.keys():
481 if repo.repo_name not in initial_repo_list.keys():
488 log.debug("Removing non-existing repository found in db `%s`" %
482 log.debug("Removing non-existing repository found in db `%s`" %
489 repo.repo_name)
483 repo.repo_name)
490 try:
484 try:
491 sa.delete(repo)
485 sa.delete(repo)
492 sa.commit()
486 sa.commit()
493 removed.append(repo.repo_name)
487 removed.append(repo.repo_name)
494 except:
488 except:
495 #don't hold further removals on error
489 #don't hold further removals on error
496 log.error(traceback.format_exc())
490 log.error(traceback.format_exc())
497 sa.rollback()
491 sa.rollback()
498 return added, removed
492 return added, removed
499
493
500
494
501 # set cache regions for beaker so celery can utilise it
495 # set cache regions for beaker so celery can utilise it
502 def add_cache(settings):
496 def add_cache(settings):
503 cache_settings = {'regions': None}
497 cache_settings = {'regions': None}
504 for key in settings.keys():
498 for key in settings.keys():
505 for prefix in ['beaker.cache.', 'cache.']:
499 for prefix in ['beaker.cache.', 'cache.']:
506 if key.startswith(prefix):
500 if key.startswith(prefix):
507 name = key.split(prefix)[1].strip()
501 name = key.split(prefix)[1].strip()
508 cache_settings[name] = settings[key].strip()
502 cache_settings[name] = settings[key].strip()
509 if cache_settings['regions']:
503 if cache_settings['regions']:
510 for region in cache_settings['regions'].split(','):
504 for region in cache_settings['regions'].split(','):
511 region = region.strip()
505 region = region.strip()
512 region_settings = {}
506 region_settings = {}
513 for key, value in cache_settings.items():
507 for key, value in cache_settings.items():
514 if key.startswith(region):
508 if key.startswith(region):
515 region_settings[key.split('.')[1]] = value
509 region_settings[key.split('.')[1]] = value
516 region_settings['expire'] = int(region_settings.get('expire',
510 region_settings['expire'] = int(region_settings.get('expire',
517 60))
511 60))
518 region_settings.setdefault('lock_dir',
512 region_settings.setdefault('lock_dir',
519 cache_settings.get('lock_dir'))
513 cache_settings.get('lock_dir'))
520 region_settings.setdefault('data_dir',
514 region_settings.setdefault('data_dir',
521 cache_settings.get('data_dir'))
515 cache_settings.get('data_dir'))
522
516
523 if 'type' not in region_settings:
517 if 'type' not in region_settings:
524 region_settings['type'] = cache_settings.get('type',
518 region_settings['type'] = cache_settings.get('type',
525 'memory')
519 'memory')
526 beaker.cache.cache_regions[region] = region_settings
520 beaker.cache.cache_regions[region] = region_settings
527
521
528
522
529 def load_rcextensions(root_path):
523 def load_rcextensions(root_path):
530 import rhodecode
524 import rhodecode
531 from rhodecode.config import conf
525 from rhodecode.config import conf
532
526
533 path = os.path.join(root_path, 'rcextensions', '__init__.py')
527 path = os.path.join(root_path, 'rcextensions', '__init__.py')
534 if os.path.isfile(path):
528 if os.path.isfile(path):
535 rcext = create_module('rc', path)
529 rcext = create_module('rc', path)
536 EXT = rhodecode.EXTENSIONS = rcext
530 EXT = rhodecode.EXTENSIONS = rcext
537 log.debug('Found rcextensions now loading %s...' % rcext)
531 log.debug('Found rcextensions now loading %s...' % rcext)
538
532
539 # Additional mappings that are not present in the pygments lexers
533 # Additional mappings that are not present in the pygments lexers
540 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
534 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
541
535
542 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
536 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
543
537
544 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
538 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
545 log.debug('settings custom INDEX_EXTENSIONS')
539 log.debug('settings custom INDEX_EXTENSIONS')
546 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
540 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
547
541
548 #ADDITIONAL MAPPINGS
542 #ADDITIONAL MAPPINGS
549 log.debug('adding extra into INDEX_EXTENSIONS')
543 log.debug('adding extra into INDEX_EXTENSIONS')
550 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
544 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
551
545
552 # auto check if the module is not missing any data, set to default if is
546 # auto check if the module is not missing any data, set to default if is
553 # this will help autoupdate new feature of rcext module
547 # this will help autoupdate new feature of rcext module
554 from rhodecode.config import rcextensions
548 from rhodecode.config import rcextensions
555 for k in dir(rcextensions):
549 for k in dir(rcextensions):
556 if not k.startswith('_') and not hasattr(EXT, k):
550 if not k.startswith('_') and not hasattr(EXT, k):
557 setattr(EXT, k, getattr(rcextensions, k))
551 setattr(EXT, k, getattr(rcextensions, k))
558
552
559
553
560 def get_custom_lexer(extension):
554 def get_custom_lexer(extension):
561 """
555 """
562 returns a custom lexer if it's defined in rcextensions module, or None
556 returns a custom lexer if it's defined in rcextensions module, or None
563 if there's no custom lexer defined
557 if there's no custom lexer defined
564 """
558 """
565 import rhodecode
559 import rhodecode
566 from pygments import lexers
560 from pygments import lexers
567 #check if we didn't define this extension as other lexer
561 #check if we didn't define this extension as other lexer
568 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
562 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
569 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
563 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
570 return lexers.get_lexer_by_name(_lexer_name)
564 return lexers.get_lexer_by_name(_lexer_name)
571
565
572
566
573 #==============================================================================
567 #==============================================================================
574 # TEST FUNCTIONS AND CREATORS
568 # TEST FUNCTIONS AND CREATORS
575 #==============================================================================
569 #==============================================================================
576 def create_test_index(repo_location, config, full_index):
570 def create_test_index(repo_location, config, full_index):
577 """
571 """
578 Makes default test index
572 Makes default test index
579
573
580 :param config: test config
574 :param config: test config
581 :param full_index:
575 :param full_index:
582 """
576 """
583
577
584 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
578 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
585 from rhodecode.lib.pidlock import DaemonLock, LockHeld
579 from rhodecode.lib.pidlock import DaemonLock, LockHeld
586
580
587 repo_location = repo_location
581 repo_location = repo_location
588
582
589 index_location = os.path.join(config['app_conf']['index_dir'])
583 index_location = os.path.join(config['app_conf']['index_dir'])
590 if not os.path.exists(index_location):
584 if not os.path.exists(index_location):
591 os.makedirs(index_location)
585 os.makedirs(index_location)
592
586
593 try:
587 try:
594 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
588 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
595 WhooshIndexingDaemon(index_location=index_location,
589 WhooshIndexingDaemon(index_location=index_location,
596 repo_location=repo_location)\
590 repo_location=repo_location)\
597 .run(full_index=full_index)
591 .run(full_index=full_index)
598 l.release()
592 l.release()
599 except LockHeld:
593 except LockHeld:
600 pass
594 pass
601
595
602
596
603 def create_test_env(repos_test_path, config):
597 def create_test_env(repos_test_path, config):
604 """
598 """
605 Makes a fresh database and
599 Makes a fresh database and
606 install test repository into tmp dir
600 install test repository into tmp dir
607 """
601 """
608 from rhodecode.lib.db_manage import DbManage
602 from rhodecode.lib.db_manage import DbManage
609 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
603 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
610
604
611 # PART ONE create db
605 # PART ONE create db
612 dbconf = config['sqlalchemy.db1.url']
606 dbconf = config['sqlalchemy.db1.url']
613 log.debug('making test db %s' % dbconf)
607 log.debug('making test db %s' % dbconf)
614
608
615 # create test dir if it doesn't exist
609 # create test dir if it doesn't exist
616 if not os.path.isdir(repos_test_path):
610 if not os.path.isdir(repos_test_path):
617 log.debug('Creating testdir %s' % repos_test_path)
611 log.debug('Creating testdir %s' % repos_test_path)
618 os.makedirs(repos_test_path)
612 os.makedirs(repos_test_path)
619
613
620 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
614 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
621 tests=True)
615 tests=True)
622 dbmanage.create_tables(override=True)
616 dbmanage.create_tables(override=True)
623 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
617 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
624 dbmanage.create_default_user()
618 dbmanage.create_default_user()
625 dbmanage.admin_prompt()
619 dbmanage.admin_prompt()
626 dbmanage.create_permissions()
620 dbmanage.create_permissions()
627 dbmanage.populate_default_permissions()
621 dbmanage.populate_default_permissions()
628 Session().commit()
622 Session().commit()
629 # PART TWO make test repo
623 # PART TWO make test repo
630 log.debug('making test vcs repositories')
624 log.debug('making test vcs repositories')
631
625
632 idx_path = config['app_conf']['index_dir']
626 idx_path = config['app_conf']['index_dir']
633 data_path = config['app_conf']['cache_dir']
627 data_path = config['app_conf']['cache_dir']
634
628
635 #clean index and data
629 #clean index and data
636 if idx_path and os.path.exists(idx_path):
630 if idx_path and os.path.exists(idx_path):
637 log.debug('remove %s' % idx_path)
631 log.debug('remove %s' % idx_path)
638 shutil.rmtree(idx_path)
632 shutil.rmtree(idx_path)
639
633
640 if data_path and os.path.exists(data_path):
634 if data_path and os.path.exists(data_path):
641 log.debug('remove %s' % data_path)
635 log.debug('remove %s' % data_path)
642 shutil.rmtree(data_path)
636 shutil.rmtree(data_path)
643
637
644 #CREATE DEFAULT TEST REPOS
638 #CREATE DEFAULT TEST REPOS
645 cur_dir = dn(dn(abspath(__file__)))
639 cur_dir = dn(dn(abspath(__file__)))
646 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
640 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
647 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
641 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
648 tar.close()
642 tar.close()
649
643
650 cur_dir = dn(dn(abspath(__file__)))
644 cur_dir = dn(dn(abspath(__file__)))
651 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
645 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
652 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
646 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
653 tar.close()
647 tar.close()
654
648
655 #LOAD VCS test stuff
649 #LOAD VCS test stuff
656 from rhodecode.tests.vcs import setup_package
650 from rhodecode.tests.vcs import setup_package
657 setup_package()
651 setup_package()
658
652
659
653
660 #==============================================================================
654 #==============================================================================
661 # PASTER COMMANDS
655 # PASTER COMMANDS
662 #==============================================================================
656 #==============================================================================
663 class BasePasterCommand(Command):
657 class BasePasterCommand(Command):
664 """
658 """
665 Abstract Base Class for paster commands.
659 Abstract Base Class for paster commands.
666
660
667 The celery commands are somewhat aggressive about loading
661 The celery commands are somewhat aggressive about loading
668 celery.conf, and since our module sets the `CELERY_LOADER`
662 celery.conf, and since our module sets the `CELERY_LOADER`
669 environment variable to our loader, we have to bootstrap a bit and
663 environment variable to our loader, we have to bootstrap a bit and
670 make sure we've had a chance to load the pylons config off of the
664 make sure we've had a chance to load the pylons config off of the
671 command line, otherwise everything fails.
665 command line, otherwise everything fails.
672 """
666 """
673 min_args = 1
667 min_args = 1
674 min_args_error = "Please provide a paster config file as an argument."
668 min_args_error = "Please provide a paster config file as an argument."
675 takes_config_file = 1
669 takes_config_file = 1
676 requires_config_file = True
670 requires_config_file = True
677
671
678 def notify_msg(self, msg, log=False):
672 def notify_msg(self, msg, log=False):
679 """Make a notification to user, additionally if logger is passed
673 """Make a notification to user, additionally if logger is passed
680 it logs this action using given logger
674 it logs this action using given logger
681
675
682 :param msg: message that will be printed to user
676 :param msg: message that will be printed to user
683 :param log: logging instance, to use to additionally log this message
677 :param log: logging instance, to use to additionally log this message
684
678
685 """
679 """
686 if log and isinstance(log, logging):
680 if log and isinstance(log, logging):
687 log(msg)
681 log(msg)
688
682
689 def run(self, args):
683 def run(self, args):
690 """
684 """
691 Overrides Command.run
685 Overrides Command.run
692
686
693 Checks for a config file argument and loads it.
687 Checks for a config file argument and loads it.
694 """
688 """
695 if len(args) < self.min_args:
689 if len(args) < self.min_args:
696 raise BadCommand(
690 raise BadCommand(
697 self.min_args_error % {'min_args': self.min_args,
691 self.min_args_error % {'min_args': self.min_args,
698 'actual_args': len(args)})
692 'actual_args': len(args)})
699
693
700 # Decrement because we're going to lob off the first argument.
694 # Decrement because we're going to lob off the first argument.
701 # @@ This is hacky
695 # @@ This is hacky
702 self.min_args -= 1
696 self.min_args -= 1
703 self.bootstrap_config(args[0])
697 self.bootstrap_config(args[0])
704 self.update_parser()
698 self.update_parser()
705 return super(BasePasterCommand, self).run(args[1:])
699 return super(BasePasterCommand, self).run(args[1:])
706
700
707 def update_parser(self):
701 def update_parser(self):
708 """
702 """
709 Abstract method. Allows for the class's parser to be updated
703 Abstract method. Allows for the class's parser to be updated
710 before the superclass's `run` method is called. Necessary to
704 before the superclass's `run` method is called. Necessary to
711 allow options/arguments to be passed through to the underlying
705 allow options/arguments to be passed through to the underlying
712 celery command.
706 celery command.
713 """
707 """
714 raise NotImplementedError("Abstract Method.")
708 raise NotImplementedError("Abstract Method.")
715
709
716 def bootstrap_config(self, conf):
710 def bootstrap_config(self, conf):
717 """
711 """
718 Loads the pylons configuration.
712 Loads the pylons configuration.
719 """
713 """
720 from pylons import config as pylonsconfig
714 from pylons import config as pylonsconfig
721
715
722 self.path_to_ini_file = os.path.realpath(conf)
716 self.path_to_ini_file = os.path.realpath(conf)
723 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
717 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
724 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
718 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
725
719
726 def _init_session(self):
720 def _init_session(self):
727 """
721 """
728 Inits SqlAlchemy Session
722 Inits SqlAlchemy Session
729 """
723 """
730 logging.config.fileConfig(self.path_to_ini_file)
724 logging.config.fileConfig(self.path_to_ini_file)
731 from pylons import config
725 from pylons import config
732 from rhodecode.model import init_model
726 from rhodecode.model import init_model
733 from rhodecode.lib.utils2 import engine_from_config
727 from rhodecode.lib.utils2 import engine_from_config
734
728
735 #get to remove repos !!
729 #get to remove repos !!
736 add_cache(config)
730 add_cache(config)
737 engine = engine_from_config(config, 'sqlalchemy.db1.')
731 engine = engine_from_config(config, 'sqlalchemy.db1.')
738 init_model(engine)
732 init_model(engine)
739
733
740
734
741 def check_git_version():
735 def check_git_version():
742 """
736 """
743 Checks what version of git is installed in system, and issues a warning
737 Checks what version of git is installed in system, and issues a warning
744 if it's too old for RhodeCode to properly work.
738 if it's too old for RhodeCode to properly work.
745 """
739 """
746 from rhodecode import BACKENDS
740 from rhodecode import BACKENDS
747 from rhodecode.lib.vcs.backends.git.repository import GitRepository
741 from rhodecode.lib.vcs.backends.git.repository import GitRepository
748 from distutils.version import StrictVersion
742 from distutils.version import StrictVersion
749
743
750 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
744 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
751 _safe=True)
745 _safe=True)
752
746
753 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
747 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
754 if len(ver.split('.')) > 3:
748 if len(ver.split('.')) > 3:
755 #StrictVersion needs to be only 3 element type
749 #StrictVersion needs to be only 3 element type
756 ver = '.'.join(ver.split('.')[:3])
750 ver = '.'.join(ver.split('.')[:3])
757 try:
751 try:
758 _ver = StrictVersion(ver)
752 _ver = StrictVersion(ver)
759 except:
753 except:
760 _ver = StrictVersion('0.0.0')
754 _ver = StrictVersion('0.0.0')
761 stderr = traceback.format_exc()
755 stderr = traceback.format_exc()
762
756
763 req_ver = '1.7.4'
757 req_ver = '1.7.4'
764 to_old_git = False
758 to_old_git = False
765 if _ver < StrictVersion(req_ver):
759 if _ver < StrictVersion(req_ver):
766 to_old_git = True
760 to_old_git = True
767
761
768 if 'git' in BACKENDS:
762 if 'git' in BACKENDS:
769 log.debug('GIT version detected: %s' % stdout)
763 log.debug('GIT version detected: %s' % stdout)
770 if stderr:
764 if stderr:
771 log.warning('Unable to detect git version, org error was: %r' % stderr)
765 log.warning('Unable to detect git version, org error was: %r' % stderr)
772 elif to_old_git:
766 elif to_old_git:
773 log.warning('RhodeCode detected git version %s, which is too old '
767 log.warning('RhodeCode detected git version %s, which is too old '
774 'for the system to function properly. Make sure '
768 'for the system to function properly. Make sure '
775 'its version is at least %s' % (ver, req_ver))
769 'its version is at least %s' % (ver, req_ver))
776 return _ver
770 return _ver
777
771
778
772
779 @decorator.decorator
773 @decorator.decorator
780 def jsonify(func, *args, **kwargs):
774 def jsonify(func, *args, **kwargs):
781 """Action decorator that formats output for JSON
775 """Action decorator that formats output for JSON
782
776
783 Given a function that will return content, this decorator will turn
777 Given a function that will return content, this decorator will turn
784 the result into JSON, with a content-type of 'application/json' and
778 the result into JSON, with a content-type of 'application/json' and
785 output it.
779 output it.
786
780
787 """
781 """
788 from pylons.decorators.util import get_pylons
782 from pylons.decorators.util import get_pylons
789 from rhodecode.lib.ext_json import json
783 from rhodecode.lib.ext_json import json
790 pylons = get_pylons(args)
784 pylons = get_pylons(args)
791 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
785 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
792 data = func(*args, **kwargs)
786 data = func(*args, **kwargs)
793 if isinstance(data, (list, tuple)):
787 if isinstance(data, (list, tuple)):
794 msg = "JSON responses with Array envelopes are susceptible to " \
788 msg = "JSON responses with Array envelopes are susceptible to " \
795 "cross-site data leak attacks, see " \
789 "cross-site data leak attacks, see " \
796 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
790 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
797 warnings.warn(msg, Warning, 2)
791 warnings.warn(msg, Warning, 2)
798 log.warning(msg)
792 log.warning(msg)
799 log.debug("Returning JSON wrapped action output")
793 log.debug("Returning JSON wrapped action output")
800 return json.dumps(data, encoding='utf-8')
794 return json.dumps(data, encoding='utf-8')
@@ -1,2052 +1,2039 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 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48
48
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 from rhodecode.lib.compat import json
51 from rhodecode.lib.compat import json
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model.meta import Base, Session
54 from rhodecode.model.meta import Base, Session
55
55
56 URL_SEP = '/'
56 URL_SEP = '/'
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59 #==============================================================================
59 #==============================================================================
60 # BASE CLASSES
60 # BASE CLASSES
61 #==============================================================================
61 #==============================================================================
62
62
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64
64
65
65
66 class BaseModel(object):
66 class BaseModel(object):
67 """
67 """
68 Base Model for all classess
68 Base Model for all classess
69 """
69 """
70
70
71 @classmethod
71 @classmethod
72 def _get_keys(cls):
72 def _get_keys(cls):
73 """return column names for this model """
73 """return column names for this model """
74 return class_mapper(cls).c.keys()
74 return class_mapper(cls).c.keys()
75
75
76 def get_dict(self):
76 def get_dict(self):
77 """
77 """
78 return dict with keys and values corresponding
78 return dict with keys and values corresponding
79 to this model data """
79 to this model data """
80
80
81 d = {}
81 d = {}
82 for k in self._get_keys():
82 for k in self._get_keys():
83 d[k] = getattr(self, k)
83 d[k] = getattr(self, k)
84
84
85 # also use __json__() if present to get additional fields
85 # also use __json__() if present to get additional fields
86 _json_attr = getattr(self, '__json__', None)
86 _json_attr = getattr(self, '__json__', None)
87 if _json_attr:
87 if _json_attr:
88 # update with attributes from __json__
88 # update with attributes from __json__
89 if callable(_json_attr):
89 if callable(_json_attr):
90 _json_attr = _json_attr()
90 _json_attr = _json_attr()
91 for k, val in _json_attr.iteritems():
91 for k, val in _json_attr.iteritems():
92 d[k] = val
92 d[k] = val
93 return d
93 return d
94
94
95 def get_appstruct(self):
95 def get_appstruct(self):
96 """return list with keys and values tupples corresponding
96 """return list with keys and values tupples corresponding
97 to this model data """
97 to this model data """
98
98
99 l = []
99 l = []
100 for k in self._get_keys():
100 for k in self._get_keys():
101 l.append((k, getattr(self, k),))
101 l.append((k, getattr(self, k),))
102 return l
102 return l
103
103
104 def populate_obj(self, populate_dict):
104 def populate_obj(self, populate_dict):
105 """populate model with data from given populate_dict"""
105 """populate model with data from given populate_dict"""
106
106
107 for k in self._get_keys():
107 for k in self._get_keys():
108 if k in populate_dict:
108 if k in populate_dict:
109 setattr(self, k, populate_dict[k])
109 setattr(self, k, populate_dict[k])
110
110
111 @classmethod
111 @classmethod
112 def query(cls):
112 def query(cls):
113 return Session().query(cls)
113 return Session().query(cls)
114
114
115 @classmethod
115 @classmethod
116 def get(cls, id_):
116 def get(cls, id_):
117 if id_:
117 if id_:
118 return cls.query().get(id_)
118 return cls.query().get(id_)
119
119
120 @classmethod
120 @classmethod
121 def get_or_404(cls, id_):
121 def get_or_404(cls, id_):
122 try:
122 try:
123 id_ = int(id_)
123 id_ = int(id_)
124 except (TypeError, ValueError):
124 except (TypeError, ValueError):
125 raise HTTPNotFound
125 raise HTTPNotFound
126
126
127 res = cls.query().get(id_)
127 res = cls.query().get(id_)
128 if not res:
128 if not res:
129 raise HTTPNotFound
129 raise HTTPNotFound
130 return res
130 return res
131
131
132 @classmethod
132 @classmethod
133 def getAll(cls):
133 def getAll(cls):
134 return cls.query().all()
134 return cls.query().all()
135
135
136 @classmethod
136 @classmethod
137 def delete(cls, id_):
137 def delete(cls, id_):
138 obj = cls.query().get(id_)
138 obj = cls.query().get(id_)
139 Session().delete(obj)
139 Session().delete(obj)
140
140
141 def __repr__(self):
141 def __repr__(self):
142 if hasattr(self, '__unicode__'):
142 if hasattr(self, '__unicode__'):
143 # python repr needs to return str
143 # python repr needs to return str
144 return safe_str(self.__unicode__())
144 return safe_str(self.__unicode__())
145 return '<DB:%s>' % (self.__class__.__name__)
145 return '<DB:%s>' % (self.__class__.__name__)
146
146
147
147
148 class RhodeCodeSetting(Base, BaseModel):
148 class RhodeCodeSetting(Base, BaseModel):
149 __tablename__ = 'rhodecode_settings'
149 __tablename__ = 'rhodecode_settings'
150 __table_args__ = (
150 __table_args__ = (
151 UniqueConstraint('app_settings_name'),
151 UniqueConstraint('app_settings_name'),
152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 'mysql_charset': 'utf8'}
153 'mysql_charset': 'utf8'}
154 )
154 )
155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
158
158
159 def __init__(self, k='', v=''):
159 def __init__(self, k='', v=''):
160 self.app_settings_name = k
160 self.app_settings_name = k
161 self.app_settings_value = v
161 self.app_settings_value = v
162
162
163 @validates('_app_settings_value')
163 @validates('_app_settings_value')
164 def validate_settings_value(self, key, val):
164 def validate_settings_value(self, key, val):
165 assert type(val) == unicode
165 assert type(val) == unicode
166 return val
166 return val
167
167
168 @hybrid_property
168 @hybrid_property
169 def app_settings_value(self):
169 def app_settings_value(self):
170 v = self._app_settings_value
170 v = self._app_settings_value
171 if self.app_settings_name in ["ldap_active",
171 if self.app_settings_name in ["ldap_active",
172 "default_repo_enable_statistics",
172 "default_repo_enable_statistics",
173 "default_repo_enable_locking",
173 "default_repo_enable_locking",
174 "default_repo_private",
174 "default_repo_private",
175 "default_repo_enable_downloads"]:
175 "default_repo_enable_downloads"]:
176 v = str2bool(v)
176 v = str2bool(v)
177 return v
177 return v
178
178
179 @app_settings_value.setter
179 @app_settings_value.setter
180 def app_settings_value(self, val):
180 def app_settings_value(self, val):
181 """
181 """
182 Setter that will always make sure we use unicode in app_settings_value
182 Setter that will always make sure we use unicode in app_settings_value
183
183
184 :param val:
184 :param val:
185 """
185 """
186 self._app_settings_value = safe_unicode(val)
186 self._app_settings_value = safe_unicode(val)
187
187
188 def __unicode__(self):
188 def __unicode__(self):
189 return u"<%s('%s:%s')>" % (
189 return u"<%s('%s:%s')>" % (
190 self.__class__.__name__,
190 self.__class__.__name__,
191 self.app_settings_name, self.app_settings_value
191 self.app_settings_name, self.app_settings_value
192 )
192 )
193
193
194 @classmethod
194 @classmethod
195 def get_by_name(cls, key):
195 def get_by_name(cls, key):
196 return cls.query()\
196 return cls.query()\
197 .filter(cls.app_settings_name == key).scalar()
197 .filter(cls.app_settings_name == key).scalar()
198
198
199 @classmethod
199 @classmethod
200 def get_by_name_or_create(cls, key):
200 def get_by_name_or_create(cls, key):
201 res = cls.get_by_name(key)
201 res = cls.get_by_name(key)
202 if not res:
202 if not res:
203 res = cls(key)
203 res = cls(key)
204 return res
204 return res
205
205
206 @classmethod
206 @classmethod
207 def get_app_settings(cls, cache=False):
207 def get_app_settings(cls, cache=False):
208
208
209 ret = cls.query()
209 ret = cls.query()
210
210
211 if cache:
211 if cache:
212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213
213
214 if not ret:
214 if not ret:
215 raise Exception('Could not get application settings !')
215 raise Exception('Could not get application settings !')
216 settings = {}
216 settings = {}
217 for each in ret:
217 for each in ret:
218 settings['rhodecode_' + each.app_settings_name] = \
218 settings['rhodecode_' + each.app_settings_name] = \
219 each.app_settings_value
219 each.app_settings_value
220
220
221 return settings
221 return settings
222
222
223 @classmethod
223 @classmethod
224 def get_ldap_settings(cls, cache=False):
224 def get_ldap_settings(cls, cache=False):
225 ret = cls.query()\
225 ret = cls.query()\
226 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 fd = {}
227 fd = {}
228 for row in ret:
228 for row in ret:
229 fd.update({row.app_settings_name: row.app_settings_value})
229 fd.update({row.app_settings_name: row.app_settings_value})
230
230
231 return fd
231 return fd
232
232
233 @classmethod
233 @classmethod
234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
235 ret = cls.query()\
235 ret = cls.query()\
236 .filter(cls.app_settings_name.startswith('default_')).all()
236 .filter(cls.app_settings_name.startswith('default_')).all()
237 fd = {}
237 fd = {}
238 for row in ret:
238 for row in ret:
239 key = row.app_settings_name
239 key = row.app_settings_name
240 if strip_prefix:
240 if strip_prefix:
241 key = remove_prefix(key, prefix='default_')
241 key = remove_prefix(key, prefix='default_')
242 fd.update({key: row.app_settings_value})
242 fd.update({key: row.app_settings_value})
243
243
244 return fd
244 return fd
245
245
246
246
247 class RhodeCodeUi(Base, BaseModel):
247 class RhodeCodeUi(Base, BaseModel):
248 __tablename__ = 'rhodecode_ui'
248 __tablename__ = 'rhodecode_ui'
249 __table_args__ = (
249 __table_args__ = (
250 UniqueConstraint('ui_key'),
250 UniqueConstraint('ui_key'),
251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 'mysql_charset': 'utf8'}
252 'mysql_charset': 'utf8'}
253 )
253 )
254
254
255 HOOK_UPDATE = 'changegroup.update'
255 HOOK_UPDATE = 'changegroup.update'
256 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 HOOK_REPO_SIZE = 'changegroup.repo_size'
257 HOOK_PUSH = 'changegroup.push_logger'
257 HOOK_PUSH = 'changegroup.push_logger'
258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
259 HOOK_PULL = 'outgoing.pull_logger'
259 HOOK_PULL = 'outgoing.pull_logger'
260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
261
261
262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
267
267
268 @classmethod
268 @classmethod
269 def get_by_key(cls, key):
269 def get_by_key(cls, key):
270 return cls.query().filter(cls.ui_key == key).scalar()
270 return cls.query().filter(cls.ui_key == key).scalar()
271
271
272 @classmethod
272 @classmethod
273 def get_builtin_hooks(cls):
273 def get_builtin_hooks(cls):
274 q = cls.query()
274 q = cls.query()
275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
278 return q.all()
278 return q.all()
279
279
280 @classmethod
280 @classmethod
281 def get_custom_hooks(cls):
281 def get_custom_hooks(cls):
282 q = cls.query()
282 q = cls.query()
283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
286 q = q.filter(cls.ui_section == 'hooks')
286 q = q.filter(cls.ui_section == 'hooks')
287 return q.all()
287 return q.all()
288
288
289 @classmethod
289 @classmethod
290 def get_repos_location(cls):
290 def get_repos_location(cls):
291 return cls.get_by_key('/').ui_value
291 return cls.get_by_key('/').ui_value
292
292
293 @classmethod
293 @classmethod
294 def create_or_update_hook(cls, key, val):
294 def create_or_update_hook(cls, key, val):
295 new_ui = cls.get_by_key(key) or cls()
295 new_ui = cls.get_by_key(key) or cls()
296 new_ui.ui_section = 'hooks'
296 new_ui.ui_section = 'hooks'
297 new_ui.ui_active = True
297 new_ui.ui_active = True
298 new_ui.ui_key = key
298 new_ui.ui_key = key
299 new_ui.ui_value = val
299 new_ui.ui_value = val
300
300
301 Session().add(new_ui)
301 Session().add(new_ui)
302
302
303 def __repr__(self):
303 def __repr__(self):
304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
305 self.ui_value)
305 self.ui_value)
306
306
307
307
308 class User(Base, BaseModel):
308 class User(Base, BaseModel):
309 __tablename__ = 'users'
309 __tablename__ = 'users'
310 __table_args__ = (
310 __table_args__ = (
311 UniqueConstraint('username'), UniqueConstraint('email'),
311 UniqueConstraint('username'), UniqueConstraint('email'),
312 Index('u_username_idx', 'username'),
312 Index('u_username_idx', 'username'),
313 Index('u_email_idx', 'email'),
313 Index('u_email_idx', 'email'),
314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
315 'mysql_charset': 'utf8'}
315 'mysql_charset': 'utf8'}
316 )
316 )
317 DEFAULT_USER = 'default'
317 DEFAULT_USER = 'default'
318 DEFAULT_PERMISSIONS = [
318 DEFAULT_PERMISSIONS = [
319 'hg.register.manual_activate', 'hg.create.repository',
319 'hg.register.manual_activate', 'hg.create.repository',
320 'hg.fork.repository', 'repository.read', 'group.read'
320 'hg.fork.repository', 'repository.read', 'group.read'
321 ]
321 ]
322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
334
334
335 user_log = relationship('UserLog')
335 user_log = relationship('UserLog')
336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
337
337
338 repositories = relationship('Repository')
338 repositories = relationship('Repository')
339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
341
341
342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
344
344
345 group_member = relationship('UserGroupMember', cascade='all')
345 group_member = relationship('UserGroupMember', cascade='all')
346
346
347 notifications = relationship('UserNotification', cascade='all')
347 notifications = relationship('UserNotification', cascade='all')
348 # notifications assigned to this user
348 # notifications assigned to this user
349 user_created_notifications = relationship('Notification', cascade='all')
349 user_created_notifications = relationship('Notification', cascade='all')
350 # comments created by this user
350 # comments created by this user
351 user_comments = relationship('ChangesetComment', cascade='all')
351 user_comments = relationship('ChangesetComment', cascade='all')
352 #extra emails for this user
352 #extra emails for this user
353 user_emails = relationship('UserEmailMap', cascade='all')
353 user_emails = relationship('UserEmailMap', cascade='all')
354
354
355 @hybrid_property
355 @hybrid_property
356 def email(self):
356 def email(self):
357 return self._email
357 return self._email
358
358
359 @email.setter
359 @email.setter
360 def email(self, val):
360 def email(self, val):
361 self._email = val.lower() if val else None
361 self._email = val.lower() if val else None
362
362
363 @property
363 @property
364 def firstname(self):
364 def firstname(self):
365 # alias for future
365 # alias for future
366 return self.name
366 return self.name
367
367
368 @property
368 @property
369 def emails(self):
369 def emails(self):
370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
371 return [self.email] + [x.email for x in other]
371 return [self.email] + [x.email for x in other]
372
372
373 @property
373 @property
374 def ip_addresses(self):
374 def ip_addresses(self):
375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
376 return [x.ip_addr for x in ret]
376 return [x.ip_addr for x in ret]
377
377
378 @property
378 @property
379 def username_and_name(self):
379 def username_and_name(self):
380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
381
381
382 @property
382 @property
383 def full_name(self):
383 def full_name(self):
384 return '%s %s' % (self.firstname, self.lastname)
384 return '%s %s' % (self.firstname, self.lastname)
385
385
386 @property
386 @property
387 def full_name_or_username(self):
387 def full_name_or_username(self):
388 return ('%s %s' % (self.firstname, self.lastname)
388 return ('%s %s' % (self.firstname, self.lastname)
389 if (self.firstname and self.lastname) else self.username)
389 if (self.firstname and self.lastname) else self.username)
390
390
391 @property
391 @property
392 def full_contact(self):
392 def full_contact(self):
393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
394
394
395 @property
395 @property
396 def short_contact(self):
396 def short_contact(self):
397 return '%s %s' % (self.firstname, self.lastname)
397 return '%s %s' % (self.firstname, self.lastname)
398
398
399 @property
399 @property
400 def is_admin(self):
400 def is_admin(self):
401 return self.admin
401 return self.admin
402
402
403 @property
403 @property
404 def AuthUser(self):
404 def AuthUser(self):
405 """
405 """
406 Returns instance of AuthUser for this user
406 Returns instance of AuthUser for this user
407 """
407 """
408 from rhodecode.lib.auth import AuthUser
408 from rhodecode.lib.auth import AuthUser
409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
410 username=self.username)
410 username=self.username)
411
411
412 def __unicode__(self):
412 def __unicode__(self):
413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
414 self.user_id, self.username)
414 self.user_id, self.username)
415
415
416 @classmethod
416 @classmethod
417 def get_by_username(cls, username, case_insensitive=False, cache=False):
417 def get_by_username(cls, username, case_insensitive=False, cache=False):
418 if case_insensitive:
418 if case_insensitive:
419 q = cls.query().filter(cls.username.ilike(username))
419 q = cls.query().filter(cls.username.ilike(username))
420 else:
420 else:
421 q = cls.query().filter(cls.username == username)
421 q = cls.query().filter(cls.username == username)
422
422
423 if cache:
423 if cache:
424 q = q.options(FromCache(
424 q = q.options(FromCache(
425 "sql_cache_short",
425 "sql_cache_short",
426 "get_user_%s" % _hash_key(username)
426 "get_user_%s" % _hash_key(username)
427 )
427 )
428 )
428 )
429 return q.scalar()
429 return q.scalar()
430
430
431 @classmethod
431 @classmethod
432 def get_by_api_key(cls, api_key, cache=False):
432 def get_by_api_key(cls, api_key, cache=False):
433 q = cls.query().filter(cls.api_key == api_key)
433 q = cls.query().filter(cls.api_key == api_key)
434
434
435 if cache:
435 if cache:
436 q = q.options(FromCache("sql_cache_short",
436 q = q.options(FromCache("sql_cache_short",
437 "get_api_key_%s" % api_key))
437 "get_api_key_%s" % api_key))
438 return q.scalar()
438 return q.scalar()
439
439
440 @classmethod
440 @classmethod
441 def get_by_email(cls, email, case_insensitive=False, cache=False):
441 def get_by_email(cls, email, case_insensitive=False, cache=False):
442 if case_insensitive:
442 if case_insensitive:
443 q = cls.query().filter(cls.email.ilike(email))
443 q = cls.query().filter(cls.email.ilike(email))
444 else:
444 else:
445 q = cls.query().filter(cls.email == email)
445 q = cls.query().filter(cls.email == email)
446
446
447 if cache:
447 if cache:
448 q = q.options(FromCache("sql_cache_short",
448 q = q.options(FromCache("sql_cache_short",
449 "get_email_key_%s" % email))
449 "get_email_key_%s" % email))
450
450
451 ret = q.scalar()
451 ret = q.scalar()
452 if ret is None:
452 if ret is None:
453 q = UserEmailMap.query()
453 q = UserEmailMap.query()
454 # try fetching in alternate email map
454 # try fetching in alternate email map
455 if case_insensitive:
455 if case_insensitive:
456 q = q.filter(UserEmailMap.email.ilike(email))
456 q = q.filter(UserEmailMap.email.ilike(email))
457 else:
457 else:
458 q = q.filter(UserEmailMap.email == email)
458 q = q.filter(UserEmailMap.email == email)
459 q = q.options(joinedload(UserEmailMap.user))
459 q = q.options(joinedload(UserEmailMap.user))
460 if cache:
460 if cache:
461 q = q.options(FromCache("sql_cache_short",
461 q = q.options(FromCache("sql_cache_short",
462 "get_email_map_key_%s" % email))
462 "get_email_map_key_%s" % email))
463 ret = getattr(q.scalar(), 'user', None)
463 ret = getattr(q.scalar(), 'user', None)
464
464
465 return ret
465 return ret
466
466
467 @classmethod
467 @classmethod
468 def get_from_cs_author(cls, author):
468 def get_from_cs_author(cls, author):
469 """
469 """
470 Tries to get User objects out of commit author string
470 Tries to get User objects out of commit author string
471
471
472 :param author:
472 :param author:
473 """
473 """
474 from rhodecode.lib.helpers import email, author_name
474 from rhodecode.lib.helpers import email, author_name
475 # Valid email in the attribute passed, see if they're in the system
475 # Valid email in the attribute passed, see if they're in the system
476 _email = email(author)
476 _email = email(author)
477 if _email:
477 if _email:
478 user = cls.get_by_email(_email, case_insensitive=True)
478 user = cls.get_by_email(_email, case_insensitive=True)
479 if user:
479 if user:
480 return user
480 return user
481 # Maybe we can match by username?
481 # Maybe we can match by username?
482 _author = author_name(author)
482 _author = author_name(author)
483 user = cls.get_by_username(_author, case_insensitive=True)
483 user = cls.get_by_username(_author, case_insensitive=True)
484 if user:
484 if user:
485 return user
485 return user
486
486
487 def update_lastlogin(self):
487 def update_lastlogin(self):
488 """Update user lastlogin"""
488 """Update user lastlogin"""
489 self.last_login = datetime.datetime.now()
489 self.last_login = datetime.datetime.now()
490 Session().add(self)
490 Session().add(self)
491 log.debug('updated user %s lastlogin' % self.username)
491 log.debug('updated user %s lastlogin' % self.username)
492
492
493 def get_api_data(self):
493 def get_api_data(self):
494 """
494 """
495 Common function for generating user related data for API
495 Common function for generating user related data for API
496 """
496 """
497 user = self
497 user = self
498 data = dict(
498 data = dict(
499 user_id=user.user_id,
499 user_id=user.user_id,
500 username=user.username,
500 username=user.username,
501 firstname=user.name,
501 firstname=user.name,
502 lastname=user.lastname,
502 lastname=user.lastname,
503 email=user.email,
503 email=user.email,
504 emails=user.emails,
504 emails=user.emails,
505 api_key=user.api_key,
505 api_key=user.api_key,
506 active=user.active,
506 active=user.active,
507 admin=user.admin,
507 admin=user.admin,
508 ldap_dn=user.ldap_dn,
508 ldap_dn=user.ldap_dn,
509 last_login=user.last_login,
509 last_login=user.last_login,
510 ip_addresses=user.ip_addresses
510 ip_addresses=user.ip_addresses
511 )
511 )
512 return data
512 return data
513
513
514 def __json__(self):
514 def __json__(self):
515 data = dict(
515 data = dict(
516 full_name=self.full_name,
516 full_name=self.full_name,
517 full_name_or_username=self.full_name_or_username,
517 full_name_or_username=self.full_name_or_username,
518 short_contact=self.short_contact,
518 short_contact=self.short_contact,
519 full_contact=self.full_contact
519 full_contact=self.full_contact
520 )
520 )
521 data.update(self.get_api_data())
521 data.update(self.get_api_data())
522 return data
522 return data
523
523
524
524
525 class UserEmailMap(Base, BaseModel):
525 class UserEmailMap(Base, BaseModel):
526 __tablename__ = 'user_email_map'
526 __tablename__ = 'user_email_map'
527 __table_args__ = (
527 __table_args__ = (
528 Index('uem_email_idx', 'email'),
528 Index('uem_email_idx', 'email'),
529 UniqueConstraint('email'),
529 UniqueConstraint('email'),
530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 'mysql_charset': 'utf8'}
531 'mysql_charset': 'utf8'}
532 )
532 )
533 __mapper_args__ = {}
533 __mapper_args__ = {}
534
534
535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
538 user = relationship('User', lazy='joined')
538 user = relationship('User', lazy='joined')
539
539
540 @validates('_email')
540 @validates('_email')
541 def validate_email(self, key, email):
541 def validate_email(self, key, email):
542 # check if this email is not main one
542 # check if this email is not main one
543 main_email = Session().query(User).filter(User.email == email).scalar()
543 main_email = Session().query(User).filter(User.email == email).scalar()
544 if main_email is not None:
544 if main_email is not None:
545 raise AttributeError('email %s is present is user table' % email)
545 raise AttributeError('email %s is present is user table' % email)
546 return email
546 return email
547
547
548 @hybrid_property
548 @hybrid_property
549 def email(self):
549 def email(self):
550 return self._email
550 return self._email
551
551
552 @email.setter
552 @email.setter
553 def email(self, val):
553 def email(self, val):
554 self._email = val.lower() if val else None
554 self._email = val.lower() if val else None
555
555
556
556
557 class UserIpMap(Base, BaseModel):
557 class UserIpMap(Base, BaseModel):
558 __tablename__ = 'user_ip_map'
558 __tablename__ = 'user_ip_map'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('user_id', 'ip_addr'),
560 UniqueConstraint('user_id', 'ip_addr'),
561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
562 'mysql_charset': 'utf8'}
562 'mysql_charset': 'utf8'}
563 )
563 )
564 __mapper_args__ = {}
564 __mapper_args__ = {}
565
565
566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 user = relationship('User', lazy='joined')
570 user = relationship('User', lazy='joined')
571
571
572 @classmethod
572 @classmethod
573 def _get_ip_range(cls, ip_addr):
573 def _get_ip_range(cls, ip_addr):
574 from rhodecode.lib import ipaddr
574 from rhodecode.lib import ipaddr
575 net = ipaddr.IPNetwork(address=ip_addr)
575 net = ipaddr.IPNetwork(address=ip_addr)
576 return [str(net.network), str(net.broadcast)]
576 return [str(net.network), str(net.broadcast)]
577
577
578 def __json__(self):
578 def __json__(self):
579 return dict(
579 return dict(
580 ip_addr=self.ip_addr,
580 ip_addr=self.ip_addr,
581 ip_range=self._get_ip_range(self.ip_addr)
581 ip_range=self._get_ip_range(self.ip_addr)
582 )
582 )
583
583
584
584
585 class UserLog(Base, BaseModel):
585 class UserLog(Base, BaseModel):
586 __tablename__ = 'user_logs'
586 __tablename__ = 'user_logs'
587 __table_args__ = (
587 __table_args__ = (
588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
589 'mysql_charset': 'utf8'},
589 'mysql_charset': 'utf8'},
590 )
590 )
591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
599
599
600 @property
600 @property
601 def action_as_day(self):
601 def action_as_day(self):
602 return datetime.date(*self.action_date.timetuple()[:3])
602 return datetime.date(*self.action_date.timetuple()[:3])
603
603
604 user = relationship('User')
604 user = relationship('User')
605 repository = relationship('Repository', cascade='')
605 repository = relationship('Repository', cascade='')
606
606
607
607
608 class UserGroup(Base, BaseModel):
608 class UserGroup(Base, BaseModel):
609 __tablename__ = 'users_groups'
609 __tablename__ = 'users_groups'
610 __table_args__ = (
610 __table_args__ = (
611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
612 'mysql_charset': 'utf8'},
612 'mysql_charset': 'utf8'},
613 )
613 )
614
614
615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
619
619
620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
623
623
624 def __unicode__(self):
624 def __unicode__(self):
625 return u'<userGroup(%s)>' % (self.users_group_name)
625 return u'<userGroup(%s)>' % (self.users_group_name)
626
626
627 @classmethod
627 @classmethod
628 def get_by_group_name(cls, group_name, cache=False,
628 def get_by_group_name(cls, group_name, cache=False,
629 case_insensitive=False):
629 case_insensitive=False):
630 if case_insensitive:
630 if case_insensitive:
631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
632 else:
632 else:
633 q = cls.query().filter(cls.users_group_name == group_name)
633 q = cls.query().filter(cls.users_group_name == group_name)
634 if cache:
634 if cache:
635 q = q.options(FromCache(
635 q = q.options(FromCache(
636 "sql_cache_short",
636 "sql_cache_short",
637 "get_user_%s" % _hash_key(group_name)
637 "get_user_%s" % _hash_key(group_name)
638 )
638 )
639 )
639 )
640 return q.scalar()
640 return q.scalar()
641
641
642 @classmethod
642 @classmethod
643 def get(cls, users_group_id, cache=False):
643 def get(cls, users_group_id, cache=False):
644 users_group = cls.query()
644 users_group = cls.query()
645 if cache:
645 if cache:
646 users_group = users_group.options(FromCache("sql_cache_short",
646 users_group = users_group.options(FromCache("sql_cache_short",
647 "get_users_group_%s" % users_group_id))
647 "get_users_group_%s" % users_group_id))
648 return users_group.get(users_group_id)
648 return users_group.get(users_group_id)
649
649
650 def get_api_data(self):
650 def get_api_data(self):
651 users_group = self
651 users_group = self
652
652
653 data = dict(
653 data = dict(
654 users_group_id=users_group.users_group_id,
654 users_group_id=users_group.users_group_id,
655 group_name=users_group.users_group_name,
655 group_name=users_group.users_group_name,
656 active=users_group.users_group_active,
656 active=users_group.users_group_active,
657 )
657 )
658
658
659 return data
659 return data
660
660
661
661
662 class UserGroupMember(Base, BaseModel):
662 class UserGroupMember(Base, BaseModel):
663 __tablename__ = 'users_groups_members'
663 __tablename__ = 'users_groups_members'
664 __table_args__ = (
664 __table_args__ = (
665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
666 'mysql_charset': 'utf8'},
666 'mysql_charset': 'utf8'},
667 )
667 )
668
668
669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
672
672
673 user = relationship('User', lazy='joined')
673 user = relationship('User', lazy='joined')
674 users_group = relationship('UserGroup')
674 users_group = relationship('UserGroup')
675
675
676 def __init__(self, gr_id='', u_id=''):
676 def __init__(self, gr_id='', u_id=''):
677 self.users_group_id = gr_id
677 self.users_group_id = gr_id
678 self.user_id = u_id
678 self.user_id = u_id
679
679
680
680
681 class RepositoryField(Base, BaseModel):
681 class RepositoryField(Base, BaseModel):
682 __tablename__ = 'repositories_fields'
682 __tablename__ = 'repositories_fields'
683 __table_args__ = (
683 __table_args__ = (
684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
686 'mysql_charset': 'utf8'},
686 'mysql_charset': 'utf8'},
687 )
687 )
688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
689
689
690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
696 field_type = Column("field_type", String(256), nullable=False, unique=None)
696 field_type = Column("field_type", String(256), nullable=False, unique=None)
697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
698
698
699 repository = relationship('Repository')
699 repository = relationship('Repository')
700
700
701 @property
701 @property
702 def field_key_prefixed(self):
702 def field_key_prefixed(self):
703 return 'ex_%s' % self.field_key
703 return 'ex_%s' % self.field_key
704
704
705 @classmethod
705 @classmethod
706 def un_prefix_key(cls, key):
706 def un_prefix_key(cls, key):
707 if key.startswith(cls.PREFIX):
707 if key.startswith(cls.PREFIX):
708 return key[len(cls.PREFIX):]
708 return key[len(cls.PREFIX):]
709 return key
709 return key
710
710
711 @classmethod
711 @classmethod
712 def get_by_key_name(cls, key, repo):
712 def get_by_key_name(cls, key, repo):
713 row = cls.query()\
713 row = cls.query()\
714 .filter(cls.repository == repo)\
714 .filter(cls.repository == repo)\
715 .filter(cls.field_key == key).scalar()
715 .filter(cls.field_key == key).scalar()
716 return row
716 return row
717
717
718
718
719 class Repository(Base, BaseModel):
719 class Repository(Base, BaseModel):
720 __tablename__ = 'repositories'
720 __tablename__ = 'repositories'
721 __table_args__ = (
721 __table_args__ = (
722 UniqueConstraint('repo_name'),
722 UniqueConstraint('repo_name'),
723 Index('r_repo_name_idx', 'repo_name'),
723 Index('r_repo_name_idx', 'repo_name'),
724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
725 'mysql_charset': 'utf8'},
725 'mysql_charset': 'utf8'},
726 )
726 )
727
727
728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
743
743
744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
746
746
747 user = relationship('User')
747 user = relationship('User')
748 fork = relationship('Repository', remote_side=repo_id)
748 fork = relationship('Repository', remote_side=repo_id)
749 group = relationship('RepoGroup')
749 group = relationship('RepoGroup')
750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
752 stats = relationship('Statistics', cascade='all', uselist=False)
752 stats = relationship('Statistics', cascade='all', uselist=False)
753
753
754 followers = relationship('UserFollowing',
754 followers = relationship('UserFollowing',
755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
756 cascade='all')
756 cascade='all')
757 extra_fields = relationship('RepositoryField',
757 extra_fields = relationship('RepositoryField',
758 cascade="all, delete, delete-orphan")
758 cascade="all, delete, delete-orphan")
759
759
760 logs = relationship('UserLog')
760 logs = relationship('UserLog')
761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
762
762
763 pull_requests_org = relationship('PullRequest',
763 pull_requests_org = relationship('PullRequest',
764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
765 cascade="all, delete, delete-orphan")
765 cascade="all, delete, delete-orphan")
766
766
767 pull_requests_other = relationship('PullRequest',
767 pull_requests_other = relationship('PullRequest',
768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
769 cascade="all, delete, delete-orphan")
769 cascade="all, delete, delete-orphan")
770
770
771 def __unicode__(self):
771 def __unicode__(self):
772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
773 self.repo_name)
773 self.repo_name)
774
774
775 @hybrid_property
775 @hybrid_property
776 def locked(self):
776 def locked(self):
777 # always should return [user_id, timelocked]
777 # always should return [user_id, timelocked]
778 if self._locked:
778 if self._locked:
779 _lock_info = self._locked.split(':')
779 _lock_info = self._locked.split(':')
780 return int(_lock_info[0]), _lock_info[1]
780 return int(_lock_info[0]), _lock_info[1]
781 return [None, None]
781 return [None, None]
782
782
783 @locked.setter
783 @locked.setter
784 def locked(self, val):
784 def locked(self, val):
785 if val and isinstance(val, (list, tuple)):
785 if val and isinstance(val, (list, tuple)):
786 self._locked = ':'.join(map(str, val))
786 self._locked = ':'.join(map(str, val))
787 else:
787 else:
788 self._locked = None
788 self._locked = None
789
789
790 @hybrid_property
790 @hybrid_property
791 def changeset_cache(self):
791 def changeset_cache(self):
792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
793 dummy = EmptyChangeset().__json__()
793 dummy = EmptyChangeset().__json__()
794 if not self._changeset_cache:
794 if not self._changeset_cache:
795 return dummy
795 return dummy
796 try:
796 try:
797 return json.loads(self._changeset_cache)
797 return json.loads(self._changeset_cache)
798 except TypeError:
798 except TypeError:
799 return dummy
799 return dummy
800
800
801 @changeset_cache.setter
801 @changeset_cache.setter
802 def changeset_cache(self, val):
802 def changeset_cache(self, val):
803 try:
803 try:
804 self._changeset_cache = json.dumps(val)
804 self._changeset_cache = json.dumps(val)
805 except:
805 except:
806 log.error(traceback.format_exc())
806 log.error(traceback.format_exc())
807
807
808 @classmethod
808 @classmethod
809 def url_sep(cls):
809 def url_sep(cls):
810 return URL_SEP
810 return URL_SEP
811
811
812 @classmethod
812 @classmethod
813 def normalize_repo_name(cls, repo_name):
813 def normalize_repo_name(cls, repo_name):
814 """
814 """
815 Normalizes os specific repo_name to the format internally stored inside
815 Normalizes os specific repo_name to the format internally stored inside
816 dabatabase using URL_SEP
816 dabatabase using URL_SEP
817
817
818 :param cls:
818 :param cls:
819 :param repo_name:
819 :param repo_name:
820 """
820 """
821 return cls.url_sep().join(repo_name.split(os.sep))
821 return cls.url_sep().join(repo_name.split(os.sep))
822
822
823 @classmethod
823 @classmethod
824 def get_by_repo_name(cls, repo_name):
824 def get_by_repo_name(cls, repo_name):
825 q = Session().query(cls).filter(cls.repo_name == repo_name)
825 q = Session().query(cls).filter(cls.repo_name == repo_name)
826 q = q.options(joinedload(Repository.fork))\
826 q = q.options(joinedload(Repository.fork))\
827 .options(joinedload(Repository.user))\
827 .options(joinedload(Repository.user))\
828 .options(joinedload(Repository.group))
828 .options(joinedload(Repository.group))
829 return q.scalar()
829 return q.scalar()
830
830
831 @classmethod
831 @classmethod
832 def get_by_full_path(cls, repo_full_path):
832 def get_by_full_path(cls, repo_full_path):
833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
834 repo_name = cls.normalize_repo_name(repo_name)
834 repo_name = cls.normalize_repo_name(repo_name)
835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
836
836
837 @classmethod
837 @classmethod
838 def get_repo_forks(cls, repo_id):
838 def get_repo_forks(cls, repo_id):
839 return cls.query().filter(Repository.fork_id == repo_id)
839 return cls.query().filter(Repository.fork_id == repo_id)
840
840
841 @classmethod
841 @classmethod
842 def base_path(cls):
842 def base_path(cls):
843 """
843 """
844 Returns base path when all repos are stored
844 Returns base path when all repos are stored
845
845
846 :param cls:
846 :param cls:
847 """
847 """
848 q = Session().query(RhodeCodeUi)\
848 q = Session().query(RhodeCodeUi)\
849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 return q.one().ui_value
851 return q.one().ui_value
852
852
853 @property
853 @property
854 def forks(self):
854 def forks(self):
855 """
855 """
856 Return forks of this repo
856 Return forks of this repo
857 """
857 """
858 return Repository.get_repo_forks(self.repo_id)
858 return Repository.get_repo_forks(self.repo_id)
859
859
860 @property
860 @property
861 def parent(self):
861 def parent(self):
862 """
862 """
863 Returns fork parent
863 Returns fork parent
864 """
864 """
865 return self.fork
865 return self.fork
866
866
867 @property
867 @property
868 def just_name(self):
868 def just_name(self):
869 return self.repo_name.split(Repository.url_sep())[-1]
869 return self.repo_name.split(Repository.url_sep())[-1]
870
870
871 @property
871 @property
872 def groups_with_parents(self):
872 def groups_with_parents(self):
873 groups = []
873 groups = []
874 if self.group is None:
874 if self.group is None:
875 return groups
875 return groups
876
876
877 cur_gr = self.group
877 cur_gr = self.group
878 groups.insert(0, cur_gr)
878 groups.insert(0, cur_gr)
879 while 1:
879 while 1:
880 gr = getattr(cur_gr, 'parent_group', None)
880 gr = getattr(cur_gr, 'parent_group', None)
881 cur_gr = cur_gr.parent_group
881 cur_gr = cur_gr.parent_group
882 if gr is None:
882 if gr is None:
883 break
883 break
884 groups.insert(0, gr)
884 groups.insert(0, gr)
885
885
886 return groups
886 return groups
887
887
888 @property
888 @property
889 def groups_and_repo(self):
889 def groups_and_repo(self):
890 return self.groups_with_parents, self.just_name, self.repo_name
890 return self.groups_with_parents, self.just_name, self.repo_name
891
891
892 @LazyProperty
892 @LazyProperty
893 def repo_path(self):
893 def repo_path(self):
894 """
894 """
895 Returns base full path for that repository means where it actually
895 Returns base full path for that repository means where it actually
896 exists on a filesystem
896 exists on a filesystem
897 """
897 """
898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
899 Repository.url_sep())
899 Repository.url_sep())
900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
901 return q.one().ui_value
901 return q.one().ui_value
902
902
903 @property
903 @property
904 def repo_full_path(self):
904 def repo_full_path(self):
905 p = [self.repo_path]
905 p = [self.repo_path]
906 # we need to split the name by / since this is how we store the
906 # we need to split the name by / since this is how we store the
907 # names in the database, but that eventually needs to be converted
907 # names in the database, but that eventually needs to be converted
908 # into a valid system path
908 # into a valid system path
909 p += self.repo_name.split(Repository.url_sep())
909 p += self.repo_name.split(Repository.url_sep())
910 return os.path.join(*p)
910 return os.path.join(*p)
911
911
912 @property
912 @property
913 def cache_keys(self):
913 def cache_keys(self):
914 """
914 """
915 Returns associated cache keys for that repo
915 Returns associated cache keys for that repo
916 """
916 """
917 return CacheInvalidation.query()\
917 return CacheInvalidation.query()\
918 .filter(CacheInvalidation.cache_args == self.repo_name)\
918 .filter(CacheInvalidation.cache_args == self.repo_name)\
919 .order_by(CacheInvalidation.cache_key)\
919 .order_by(CacheInvalidation.cache_key)\
920 .all()
920 .all()
921
921
922 def get_new_name(self, repo_name):
922 def get_new_name(self, repo_name):
923 """
923 """
924 returns new full repository name based on assigned group and new new
924 returns new full repository name based on assigned group and new new
925
925
926 :param group_name:
926 :param group_name:
927 """
927 """
928 path_prefix = self.group.full_path_splitted if self.group else []
928 path_prefix = self.group.full_path_splitted if self.group else []
929 return Repository.url_sep().join(path_prefix + [repo_name])
929 return Repository.url_sep().join(path_prefix + [repo_name])
930
930
931 @property
931 @property
932 def _ui(self):
932 def _ui(self):
933 """
933 """
934 Creates an db based ui object for this repository
934 Creates an db based ui object for this repository
935 """
935 """
936 from rhodecode.lib.utils import make_ui
936 from rhodecode.lib.utils import make_ui
937 return make_ui('db', clear_session=False)
937 return make_ui('db', clear_session=False)
938
938
939 @classmethod
939 @classmethod
940 def is_valid(cls, repo_name):
940 def is_valid(cls, repo_name):
941 """
941 """
942 returns True if given repo name is a valid filesystem repository
942 returns True if given repo name is a valid filesystem repository
943
943
944 :param cls:
944 :param cls:
945 :param repo_name:
945 :param repo_name:
946 """
946 """
947 from rhodecode.lib.utils import is_valid_repo
947 from rhodecode.lib.utils import is_valid_repo
948
948
949 return is_valid_repo(repo_name, cls.base_path())
949 return is_valid_repo(repo_name, cls.base_path())
950
950
951 def get_api_data(self):
951 def get_api_data(self):
952 """
952 """
953 Common function for generating repo api data
953 Common function for generating repo api data
954
954
955 """
955 """
956 repo = self
956 repo = self
957 data = dict(
957 data = dict(
958 repo_id=repo.repo_id,
958 repo_id=repo.repo_id,
959 repo_name=repo.repo_name,
959 repo_name=repo.repo_name,
960 repo_type=repo.repo_type,
960 repo_type=repo.repo_type,
961 clone_uri=repo.clone_uri,
961 clone_uri=repo.clone_uri,
962 private=repo.private,
962 private=repo.private,
963 created_on=repo.created_on,
963 created_on=repo.created_on,
964 description=repo.description,
964 description=repo.description,
965 landing_rev=repo.landing_rev,
965 landing_rev=repo.landing_rev,
966 owner=repo.user.username,
966 owner=repo.user.username,
967 fork_of=repo.fork.repo_name if repo.fork else None,
967 fork_of=repo.fork.repo_name if repo.fork else None,
968 enable_statistics=repo.enable_statistics,
968 enable_statistics=repo.enable_statistics,
969 enable_locking=repo.enable_locking,
969 enable_locking=repo.enable_locking,
970 enable_downloads=repo.enable_downloads,
970 enable_downloads=repo.enable_downloads,
971 last_changeset=repo.changeset_cache,
971 last_changeset=repo.changeset_cache,
972 locked_by=User.get(self.locked[0]).get_api_data() \
972 locked_by=User.get(self.locked[0]).get_api_data() \
973 if self.locked[0] else None,
973 if self.locked[0] else None,
974 locked_date=time_to_datetime(self.locked[1]) \
974 locked_date=time_to_datetime(self.locked[1]) \
975 if self.locked[1] else None
975 if self.locked[1] else None
976 )
976 )
977 rc_config = RhodeCodeSetting.get_app_settings()
977 rc_config = RhodeCodeSetting.get_app_settings()
978 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
978 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
979 if repository_fields:
979 if repository_fields:
980 for f in self.extra_fields:
980 for f in self.extra_fields:
981 data[f.field_key_prefixed] = f.field_value
981 data[f.field_key_prefixed] = f.field_value
982
982
983 return data
983 return data
984
984
985 @classmethod
985 @classmethod
986 def lock(cls, repo, user_id):
986 def lock(cls, repo, user_id):
987 repo.locked = [user_id, time.time()]
987 repo.locked = [user_id, time.time()]
988 Session().add(repo)
988 Session().add(repo)
989 Session().commit()
989 Session().commit()
990
990
991 @classmethod
991 @classmethod
992 def unlock(cls, repo):
992 def unlock(cls, repo):
993 repo.locked = None
993 repo.locked = None
994 Session().add(repo)
994 Session().add(repo)
995 Session().commit()
995 Session().commit()
996
996
997 @classmethod
997 @classmethod
998 def getlock(cls, repo):
998 def getlock(cls, repo):
999 return repo.locked
999 return repo.locked
1000
1000
1001 @property
1001 @property
1002 def last_db_change(self):
1002 def last_db_change(self):
1003 return self.updated_on
1003 return self.updated_on
1004
1004
1005 def clone_url(self, **override):
1005 def clone_url(self, **override):
1006 from pylons import url
1006 from pylons import url
1007 from urlparse import urlparse
1007 from urlparse import urlparse
1008 import urllib
1008 import urllib
1009 parsed_url = urlparse(url('home', qualified=True))
1009 parsed_url = urlparse(url('home', qualified=True))
1010 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1010 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1011 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1011 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1012 args = {
1012 args = {
1013 'user': '',
1013 'user': '',
1014 'pass': '',
1014 'pass': '',
1015 'scheme': parsed_url.scheme,
1015 'scheme': parsed_url.scheme,
1016 'netloc': parsed_url.netloc,
1016 'netloc': parsed_url.netloc,
1017 'prefix': decoded_path,
1017 'prefix': decoded_path,
1018 'path': self.repo_name
1018 'path': self.repo_name
1019 }
1019 }
1020
1020
1021 args.update(override)
1021 args.update(override)
1022 return default_clone_uri % args
1022 return default_clone_uri % args
1023
1023
1024 #==========================================================================
1024 #==========================================================================
1025 # SCM PROPERTIES
1025 # SCM PROPERTIES
1026 #==========================================================================
1026 #==========================================================================
1027
1027
1028 def get_changeset(self, rev=None):
1028 def get_changeset(self, rev=None):
1029 return get_changeset_safe(self.scm_instance, rev)
1029 return get_changeset_safe(self.scm_instance, rev)
1030
1030
1031 def get_landing_changeset(self):
1031 def get_landing_changeset(self):
1032 """
1032 """
1033 Returns landing changeset, or if that doesn't exist returns the tip
1033 Returns landing changeset, or if that doesn't exist returns the tip
1034 """
1034 """
1035 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1035 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1036 return cs
1036 return cs
1037
1037
1038 def update_changeset_cache(self, cs_cache=None):
1038 def update_changeset_cache(self, cs_cache=None):
1039 """
1039 """
1040 Update cache of last changeset for repository, keys should be::
1040 Update cache of last changeset for repository, keys should be::
1041
1041
1042 short_id
1042 short_id
1043 raw_id
1043 raw_id
1044 revision
1044 revision
1045 message
1045 message
1046 date
1046 date
1047 author
1047 author
1048
1048
1049 :param cs_cache:
1049 :param cs_cache:
1050 """
1050 """
1051 from rhodecode.lib.vcs.backends.base import BaseChangeset
1051 from rhodecode.lib.vcs.backends.base import BaseChangeset
1052 if cs_cache is None:
1052 if cs_cache is None:
1053 cs_cache = EmptyChangeset()
1053 cs_cache = EmptyChangeset()
1054 # use no-cache version here
1054 # use no-cache version here
1055 scm_repo = self.scm_instance_no_cache()
1055 scm_repo = self.scm_instance_no_cache()
1056 if scm_repo:
1056 if scm_repo:
1057 cs_cache = scm_repo.get_changeset()
1057 cs_cache = scm_repo.get_changeset()
1058
1058
1059 if isinstance(cs_cache, BaseChangeset):
1059 if isinstance(cs_cache, BaseChangeset):
1060 cs_cache = cs_cache.__json__()
1060 cs_cache = cs_cache.__json__()
1061
1061
1062 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1062 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1063 _default = datetime.datetime.fromtimestamp(0)
1063 _default = datetime.datetime.fromtimestamp(0)
1064 last_change = cs_cache.get('date') or _default
1064 last_change = cs_cache.get('date') or _default
1065 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1065 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1066 self.updated_on = last_change
1066 self.updated_on = last_change
1067 self.changeset_cache = cs_cache
1067 self.changeset_cache = cs_cache
1068 Session().add(self)
1068 Session().add(self)
1069 Session().commit()
1069 Session().commit()
1070 else:
1070 else:
1071 log.debug('Skipping repo:%s already with latest changes' % self)
1071 log.debug('Skipping repo:%s already with latest changes' % self)
1072
1072
1073 @property
1073 @property
1074 def tip(self):
1074 def tip(self):
1075 return self.get_changeset('tip')
1075 return self.get_changeset('tip')
1076
1076
1077 @property
1077 @property
1078 def author(self):
1078 def author(self):
1079 return self.tip.author
1079 return self.tip.author
1080
1080
1081 @property
1081 @property
1082 def last_change(self):
1082 def last_change(self):
1083 return self.scm_instance.last_change
1083 return self.scm_instance.last_change
1084
1084
1085 def get_comments(self, revisions=None):
1085 def get_comments(self, revisions=None):
1086 """
1086 """
1087 Returns comments for this repository grouped by revisions
1087 Returns comments for this repository grouped by revisions
1088
1088
1089 :param revisions: filter query by revisions only
1089 :param revisions: filter query by revisions only
1090 """
1090 """
1091 cmts = ChangesetComment.query()\
1091 cmts = ChangesetComment.query()\
1092 .filter(ChangesetComment.repo == self)
1092 .filter(ChangesetComment.repo == self)
1093 if revisions:
1093 if revisions:
1094 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1094 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1095 grouped = defaultdict(list)
1095 grouped = defaultdict(list)
1096 for cmt in cmts.all():
1096 for cmt in cmts.all():
1097 grouped[cmt.revision].append(cmt)
1097 grouped[cmt.revision].append(cmt)
1098 return grouped
1098 return grouped
1099
1099
1100 def statuses(self, revisions=None):
1100 def statuses(self, revisions=None):
1101 """
1101 """
1102 Returns statuses for this repository
1102 Returns statuses for this repository
1103
1103
1104 :param revisions: list of revisions to get statuses for
1104 :param revisions: list of revisions to get statuses for
1105 :type revisions: list
1105 :type revisions: list
1106 """
1106 """
1107
1107
1108 statuses = ChangesetStatus.query()\
1108 statuses = ChangesetStatus.query()\
1109 .filter(ChangesetStatus.repo == self)\
1109 .filter(ChangesetStatus.repo == self)\
1110 .filter(ChangesetStatus.version == 0)
1110 .filter(ChangesetStatus.version == 0)
1111 if revisions:
1111 if revisions:
1112 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1112 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1113 grouped = {}
1113 grouped = {}
1114
1114
1115 #maybe we have open new pullrequest without a status ?
1115 #maybe we have open new pullrequest without a status ?
1116 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1116 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1117 status_lbl = ChangesetStatus.get_status_lbl(stat)
1117 status_lbl = ChangesetStatus.get_status_lbl(stat)
1118 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1118 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1119 for rev in pr.revisions:
1119 for rev in pr.revisions:
1120 pr_id = pr.pull_request_id
1120 pr_id = pr.pull_request_id
1121 pr_repo = pr.other_repo.repo_name
1121 pr_repo = pr.other_repo.repo_name
1122 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1122 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1123
1123
1124 for stat in statuses.all():
1124 for stat in statuses.all():
1125 pr_id = pr_repo = None
1125 pr_id = pr_repo = None
1126 if stat.pull_request:
1126 if stat.pull_request:
1127 pr_id = stat.pull_request.pull_request_id
1127 pr_id = stat.pull_request.pull_request_id
1128 pr_repo = stat.pull_request.other_repo.repo_name
1128 pr_repo = stat.pull_request.other_repo.repo_name
1129 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1129 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1130 pr_id, pr_repo]
1130 pr_id, pr_repo]
1131 return grouped
1131 return grouped
1132
1132
1133 def _repo_size(self):
1133 def _repo_size(self):
1134 from rhodecode.lib import helpers as h
1134 from rhodecode.lib import helpers as h
1135 log.debug('calculating repository size...')
1135 log.debug('calculating repository size...')
1136 return h.format_byte_size(self.scm_instance.size)
1136 return h.format_byte_size(self.scm_instance.size)
1137
1137
1138 #==========================================================================
1138 #==========================================================================
1139 # SCM CACHE INSTANCE
1139 # SCM CACHE INSTANCE
1140 #==========================================================================
1140 #==========================================================================
1141
1141
1142 @property
1142 @property
1143 def invalidate(self):
1143 def invalidate(self):
1144 return CacheInvalidation.invalidate(self.repo_name)
1144 return CacheInvalidation.invalidate(self.repo_name)
1145
1145
1146 def set_invalidate(self):
1146 def set_invalidate(self):
1147 """
1147 """
1148 set a cache for invalidation for this instance
1148 set a cache for invalidation for this instance
1149 """
1149 """
1150 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1150 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1151
1151
1152 def scm_instance_no_cache(self):
1152 def scm_instance_no_cache(self):
1153 return self.__get_instance()
1153 return self.__get_instance()
1154
1154
1155 @LazyProperty
1155 @LazyProperty
1156 def scm_instance(self):
1156 def scm_instance(self):
1157 import rhodecode
1157 import rhodecode
1158 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1158 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1159 if full_cache:
1159 if full_cache:
1160 return self.scm_instance_cached()
1160 return self.scm_instance_cached()
1161 return self.__get_instance()
1161 return self.__get_instance()
1162
1162
1163 def scm_instance_cached(self, cache_map=None):
1163 def scm_instance_cached(self, cache_map=None):
1164 @cache_region('long_term')
1164 @cache_region('long_term')
1165 def _c(repo_name):
1165 def _c(repo_name):
1166 return self.__get_instance()
1166 return self.__get_instance()
1167 rn = self.repo_name
1167 rn = self.repo_name
1168 log.debug('Getting cached instance of repo')
1168 log.debug('Getting cached instance of repo')
1169
1169
1170 if cache_map:
1170 if cache_map:
1171 # get using prefilled cache_map
1171 # get using prefilled cache_map
1172 invalidate_repo = cache_map[self.repo_name]
1172 invalidate_repo = cache_map[self.repo_name]
1173 if invalidate_repo:
1173 if invalidate_repo:
1174 invalidate_repo = (None if invalidate_repo.cache_active
1174 invalidate_repo = (None if invalidate_repo.cache_active
1175 else invalidate_repo)
1175 else invalidate_repo)
1176 else:
1176 else:
1177 # get from invalidate
1177 # get from invalidate
1178 invalidate_repo = self.invalidate
1178 invalidate_repo = self.invalidate
1179
1179
1180 if invalidate_repo is not None:
1180 if invalidate_repo is not None:
1181 region_invalidate(_c, None, rn)
1181 region_invalidate(_c, None, rn)
1182 # update our cache
1182 # update our cache
1183 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1183 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1184 return _c(rn)
1184 return _c(rn)
1185
1185
1186 def __get_instance(self):
1186 def __get_instance(self):
1187 repo_full_path = self.repo_full_path
1187 repo_full_path = self.repo_full_path
1188 try:
1188 try:
1189 alias = get_scm(repo_full_path)[0]
1189 alias = get_scm(repo_full_path)[0]
1190 log.debug('Creating instance of %s repository from %s'
1190 log.debug('Creating instance of %s repository from %s'
1191 % (alias, repo_full_path))
1191 % (alias, repo_full_path))
1192 backend = get_backend(alias)
1192 backend = get_backend(alias)
1193 except VCSError:
1193 except VCSError:
1194 log.error(traceback.format_exc())
1194 log.error(traceback.format_exc())
1195 log.error('Perhaps this repository is in db and not in '
1195 log.error('Perhaps this repository is in db and not in '
1196 'filesystem run rescan repositories with '
1196 'filesystem run rescan repositories with '
1197 '"destroy old data " option from admin panel')
1197 '"destroy old data " option from admin panel')
1198 return
1198 return
1199
1199
1200 if alias == 'hg':
1200 if alias == 'hg':
1201
1201
1202 repo = backend(safe_str(repo_full_path), create=False,
1202 repo = backend(safe_str(repo_full_path), create=False,
1203 baseui=self._ui)
1203 baseui=self._ui)
1204 # skip hidden web repository
1204 # skip hidden web repository
1205 if repo._get_hidden():
1205 if repo._get_hidden():
1206 return
1206 return
1207 else:
1207 else:
1208 repo = backend(repo_full_path, create=False)
1208 repo = backend(repo_full_path, create=False)
1209
1209
1210 return repo
1210 return repo
1211
1211
1212
1212
1213 class RepoGroup(Base, BaseModel):
1213 class RepoGroup(Base, BaseModel):
1214 __tablename__ = 'groups'
1214 __tablename__ = 'groups'
1215 __table_args__ = (
1215 __table_args__ = (
1216 UniqueConstraint('group_name', 'group_parent_id'),
1216 UniqueConstraint('group_name', 'group_parent_id'),
1217 CheckConstraint('group_id != group_parent_id'),
1217 CheckConstraint('group_id != group_parent_id'),
1218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1219 'mysql_charset': 'utf8'},
1219 'mysql_charset': 'utf8'},
1220 )
1220 )
1221 __mapper_args__ = {'order_by': 'group_name'}
1221 __mapper_args__ = {'order_by': 'group_name'}
1222
1222
1223 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1223 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1224 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1225 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1225 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1226 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1226 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1227 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1227 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1228
1228
1229 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1229 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1230 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1230 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1231
1231
1232 parent_group = relationship('RepoGroup', remote_side=group_id)
1232 parent_group = relationship('RepoGroup', remote_side=group_id)
1233
1233
1234 def __init__(self, group_name='', parent_group=None):
1234 def __init__(self, group_name='', parent_group=None):
1235 self.group_name = group_name
1235 self.group_name = group_name
1236 self.parent_group = parent_group
1236 self.parent_group = parent_group
1237
1237
1238 def __unicode__(self):
1238 def __unicode__(self):
1239 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1239 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1240 self.group_name)
1240 self.group_name)
1241
1241
1242 @classmethod
1242 @classmethod
1243 def groups_choices(cls, groups=None, show_empty_group=True):
1243 def groups_choices(cls, groups=None, show_empty_group=True):
1244 from webhelpers.html import literal as _literal
1244 from webhelpers.html import literal as _literal
1245 if not groups:
1245 if not groups:
1246 groups = cls.query().all()
1246 groups = cls.query().all()
1247
1247
1248 repo_groups = []
1248 repo_groups = []
1249 if show_empty_group:
1249 if show_empty_group:
1250 repo_groups = [('-1', '-- %s --' % _('top level'))]
1250 repo_groups = [('-1', '-- %s --' % _('top level'))]
1251 sep = ' &raquo; '
1251 sep = ' &raquo; '
1252 _name = lambda k: _literal(sep.join(k))
1252 _name = lambda k: _literal(sep.join(k))
1253
1253
1254 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1254 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1255 for x in groups])
1255 for x in groups])
1256
1256
1257 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1257 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1258 return repo_groups
1258 return repo_groups
1259
1259
1260 @classmethod
1260 @classmethod
1261 def url_sep(cls):
1261 def url_sep(cls):
1262 return URL_SEP
1262 return URL_SEP
1263
1263
1264 @classmethod
1264 @classmethod
1265 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1265 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1266 if case_insensitive:
1266 if case_insensitive:
1267 gr = cls.query()\
1267 gr = cls.query()\
1268 .filter(cls.group_name.ilike(group_name))
1268 .filter(cls.group_name.ilike(group_name))
1269 else:
1269 else:
1270 gr = cls.query()\
1270 gr = cls.query()\
1271 .filter(cls.group_name == group_name)
1271 .filter(cls.group_name == group_name)
1272 if cache:
1272 if cache:
1273 gr = gr.options(FromCache(
1273 gr = gr.options(FromCache(
1274 "sql_cache_short",
1274 "sql_cache_short",
1275 "get_group_%s" % _hash_key(group_name)
1275 "get_group_%s" % _hash_key(group_name)
1276 )
1276 )
1277 )
1277 )
1278 return gr.scalar()
1278 return gr.scalar()
1279
1279
1280 @property
1280 @property
1281 def parents(self):
1281 def parents(self):
1282 parents_recursion_limit = 5
1282 parents_recursion_limit = 5
1283 groups = []
1283 groups = []
1284 if self.parent_group is None:
1284 if self.parent_group is None:
1285 return groups
1285 return groups
1286 cur_gr = self.parent_group
1286 cur_gr = self.parent_group
1287 groups.insert(0, cur_gr)
1287 groups.insert(0, cur_gr)
1288 cnt = 0
1288 cnt = 0
1289 while 1:
1289 while 1:
1290 cnt += 1
1290 cnt += 1
1291 gr = getattr(cur_gr, 'parent_group', None)
1291 gr = getattr(cur_gr, 'parent_group', None)
1292 cur_gr = cur_gr.parent_group
1292 cur_gr = cur_gr.parent_group
1293 if gr is None:
1293 if gr is None:
1294 break
1294 break
1295 if cnt == parents_recursion_limit:
1295 if cnt == parents_recursion_limit:
1296 # this will prevent accidental infinit loops
1296 # this will prevent accidental infinit loops
1297 log.error('group nested more than %s' %
1297 log.error('group nested more than %s' %
1298 parents_recursion_limit)
1298 parents_recursion_limit)
1299 break
1299 break
1300
1300
1301 groups.insert(0, gr)
1301 groups.insert(0, gr)
1302 return groups
1302 return groups
1303
1303
1304 @property
1304 @property
1305 def children(self):
1305 def children(self):
1306 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1306 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1307
1307
1308 @property
1308 @property
1309 def name(self):
1309 def name(self):
1310 return self.group_name.split(RepoGroup.url_sep())[-1]
1310 return self.group_name.split(RepoGroup.url_sep())[-1]
1311
1311
1312 @property
1312 @property
1313 def full_path(self):
1313 def full_path(self):
1314 return self.group_name
1314 return self.group_name
1315
1315
1316 @property
1316 @property
1317 def full_path_splitted(self):
1317 def full_path_splitted(self):
1318 return self.group_name.split(RepoGroup.url_sep())
1318 return self.group_name.split(RepoGroup.url_sep())
1319
1319
1320 @property
1320 @property
1321 def repositories(self):
1321 def repositories(self):
1322 return Repository.query()\
1322 return Repository.query()\
1323 .filter(Repository.group == self)\
1323 .filter(Repository.group == self)\
1324 .order_by(Repository.repo_name)
1324 .order_by(Repository.repo_name)
1325
1325
1326 @property
1326 @property
1327 def repositories_recursive_count(self):
1327 def repositories_recursive_count(self):
1328 cnt = self.repositories.count()
1328 cnt = self.repositories.count()
1329
1329
1330 def children_count(group):
1330 def children_count(group):
1331 cnt = 0
1331 cnt = 0
1332 for child in group.children:
1332 for child in group.children:
1333 cnt += child.repositories.count()
1333 cnt += child.repositories.count()
1334 cnt += children_count(child)
1334 cnt += children_count(child)
1335 return cnt
1335 return cnt
1336
1336
1337 return cnt + children_count(self)
1337 return cnt + children_count(self)
1338
1338
1339 def _recursive_objects(self, include_repos=True):
1339 def _recursive_objects(self, include_repos=True):
1340 all_ = []
1340 all_ = []
1341
1341
1342 def _get_members(root_gr):
1342 def _get_members(root_gr):
1343 if include_repos:
1343 if include_repos:
1344 for r in root_gr.repositories:
1344 for r in root_gr.repositories:
1345 all_.append(r)
1345 all_.append(r)
1346 childs = root_gr.children.all()
1346 childs = root_gr.children.all()
1347 if childs:
1347 if childs:
1348 for gr in childs:
1348 for gr in childs:
1349 all_.append(gr)
1349 all_.append(gr)
1350 _get_members(gr)
1350 _get_members(gr)
1351
1351
1352 _get_members(self)
1352 _get_members(self)
1353 return [self] + all_
1353 return [self] + all_
1354
1354
1355 def recursive_groups_and_repos(self):
1355 def recursive_groups_and_repos(self):
1356 """
1356 """
1357 Recursive return all groups, with repositories in those groups
1357 Recursive return all groups, with repositories in those groups
1358 """
1358 """
1359 return self._recursive_objects()
1359 return self._recursive_objects()
1360
1360
1361 def recursive_groups(self):
1361 def recursive_groups(self):
1362 """
1362 """
1363 Returns all children groups for this group including children of children
1363 Returns all children groups for this group including children of children
1364 """
1364 """
1365 return self._recursive_objects(include_repos=False)
1365 return self._recursive_objects(include_repos=False)
1366
1366
1367 def get_new_name(self, group_name):
1367 def get_new_name(self, group_name):
1368 """
1368 """
1369 returns new full group name based on parent and new name
1369 returns new full group name based on parent and new name
1370
1370
1371 :param group_name:
1371 :param group_name:
1372 """
1372 """
1373 path_prefix = (self.parent_group.full_path_splitted if
1373 path_prefix = (self.parent_group.full_path_splitted if
1374 self.parent_group else [])
1374 self.parent_group else [])
1375 return RepoGroup.url_sep().join(path_prefix + [group_name])
1375 return RepoGroup.url_sep().join(path_prefix + [group_name])
1376
1376
1377
1377
1378 class Permission(Base, BaseModel):
1378 class Permission(Base, BaseModel):
1379 __tablename__ = 'permissions'
1379 __tablename__ = 'permissions'
1380 __table_args__ = (
1380 __table_args__ = (
1381 Index('p_perm_name_idx', 'permission_name'),
1381 Index('p_perm_name_idx', 'permission_name'),
1382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1383 'mysql_charset': 'utf8'},
1383 'mysql_charset': 'utf8'},
1384 )
1384 )
1385 PERMS = [
1385 PERMS = [
1386 ('repository.none', _('Repository no access')),
1386 ('repository.none', _('Repository no access')),
1387 ('repository.read', _('Repository read access')),
1387 ('repository.read', _('Repository read access')),
1388 ('repository.write', _('Repository write access')),
1388 ('repository.write', _('Repository write access')),
1389 ('repository.admin', _('Repository admin access')),
1389 ('repository.admin', _('Repository admin access')),
1390
1390
1391 ('group.none', _('Repository group no access')),
1391 ('group.none', _('Repository group no access')),
1392 ('group.read', _('Repository group read access')),
1392 ('group.read', _('Repository group read access')),
1393 ('group.write', _('Repository group write access')),
1393 ('group.write', _('Repository group write access')),
1394 ('group.admin', _('Repository group admin access')),
1394 ('group.admin', _('Repository group admin access')),
1395
1395
1396 ('hg.admin', _('RhodeCode Administrator')),
1396 ('hg.admin', _('RhodeCode Administrator')),
1397 ('hg.create.none', _('Repository creation disabled')),
1397 ('hg.create.none', _('Repository creation disabled')),
1398 ('hg.create.repository', _('Repository creation enabled')),
1398 ('hg.create.repository', _('Repository creation enabled')),
1399 ('hg.fork.none', _('Repository forking disabled')),
1399 ('hg.fork.none', _('Repository forking disabled')),
1400 ('hg.fork.repository', _('Repository forking enabled')),
1400 ('hg.fork.repository', _('Repository forking enabled')),
1401 ('hg.register.none', _('Register disabled')),
1401 ('hg.register.none', _('Register disabled')),
1402 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1402 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1403 'with manual activation')),
1403 'with manual activation')),
1404
1404
1405 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1405 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1406 'with auto activation')),
1406 'with auto activation')),
1407 ]
1407 ]
1408
1408
1409 # defines which permissions are more important higher the more important
1409 # defines which permissions are more important higher the more important
1410 PERM_WEIGHTS = {
1410 PERM_WEIGHTS = {
1411 'repository.none': 0,
1411 'repository.none': 0,
1412 'repository.read': 1,
1412 'repository.read': 1,
1413 'repository.write': 3,
1413 'repository.write': 3,
1414 'repository.admin': 4,
1414 'repository.admin': 4,
1415
1415
1416 'group.none': 0,
1416 'group.none': 0,
1417 'group.read': 1,
1417 'group.read': 1,
1418 'group.write': 3,
1418 'group.write': 3,
1419 'group.admin': 4,
1419 'group.admin': 4,
1420
1420
1421 'hg.fork.none': 0,
1421 'hg.fork.none': 0,
1422 'hg.fork.repository': 1,
1422 'hg.fork.repository': 1,
1423 'hg.create.none': 0,
1423 'hg.create.none': 0,
1424 'hg.create.repository':1
1424 'hg.create.repository':1
1425 }
1425 }
1426
1426
1427 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1427 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1428 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1428 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1429 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1429 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1430
1430
1431 def __unicode__(self):
1431 def __unicode__(self):
1432 return u"<%s('%s:%s')>" % (
1432 return u"<%s('%s:%s')>" % (
1433 self.__class__.__name__, self.permission_id, self.permission_name
1433 self.__class__.__name__, self.permission_id, self.permission_name
1434 )
1434 )
1435
1435
1436 @classmethod
1436 @classmethod
1437 def get_by_key(cls, key):
1437 def get_by_key(cls, key):
1438 return cls.query().filter(cls.permission_name == key).scalar()
1438 return cls.query().filter(cls.permission_name == key).scalar()
1439
1439
1440 @classmethod
1440 @classmethod
1441 def get_default_perms(cls, default_user_id):
1441 def get_default_perms(cls, default_user_id):
1442 q = Session().query(UserRepoToPerm, Repository, cls)\
1442 q = Session().query(UserRepoToPerm, Repository, cls)\
1443 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1443 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1444 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1444 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1445 .filter(UserRepoToPerm.user_id == default_user_id)
1445 .filter(UserRepoToPerm.user_id == default_user_id)
1446
1446
1447 return q.all()
1447 return q.all()
1448
1448
1449 @classmethod
1449 @classmethod
1450 def get_default_group_perms(cls, default_user_id):
1450 def get_default_group_perms(cls, default_user_id):
1451 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1451 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1452 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1452 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1453 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1453 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1454 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1454 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1455
1455
1456 return q.all()
1456 return q.all()
1457
1457
1458
1458
1459 class UserRepoToPerm(Base, BaseModel):
1459 class UserRepoToPerm(Base, BaseModel):
1460 __tablename__ = 'repo_to_perm'
1460 __tablename__ = 'repo_to_perm'
1461 __table_args__ = (
1461 __table_args__ = (
1462 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1462 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 'mysql_charset': 'utf8'}
1464 'mysql_charset': 'utf8'}
1465 )
1465 )
1466 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1466 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1468 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1468 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1469 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1469 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1470
1470
1471 user = relationship('User')
1471 user = relationship('User')
1472 repository = relationship('Repository')
1472 repository = relationship('Repository')
1473 permission = relationship('Permission')
1473 permission = relationship('Permission')
1474
1474
1475 @classmethod
1475 @classmethod
1476 def create(cls, user, repository, permission):
1476 def create(cls, user, repository, permission):
1477 n = cls()
1477 n = cls()
1478 n.user = user
1478 n.user = user
1479 n.repository = repository
1479 n.repository = repository
1480 n.permission = permission
1480 n.permission = permission
1481 Session().add(n)
1481 Session().add(n)
1482 return n
1482 return n
1483
1483
1484 def __unicode__(self):
1484 def __unicode__(self):
1485 return u'<user:%s => %s >' % (self.user, self.repository)
1485 return u'<user:%s => %s >' % (self.user, self.repository)
1486
1486
1487
1487
1488 class UserToPerm(Base, BaseModel):
1488 class UserToPerm(Base, BaseModel):
1489 __tablename__ = 'user_to_perm'
1489 __tablename__ = 'user_to_perm'
1490 __table_args__ = (
1490 __table_args__ = (
1491 UniqueConstraint('user_id', 'permission_id'),
1491 UniqueConstraint('user_id', 'permission_id'),
1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 'mysql_charset': 'utf8'}
1493 'mysql_charset': 'utf8'}
1494 )
1494 )
1495 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1495 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1496 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1496 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1497 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1497 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1498
1498
1499 user = relationship('User')
1499 user = relationship('User')
1500 permission = relationship('Permission', lazy='joined')
1500 permission = relationship('Permission', lazy='joined')
1501
1501
1502
1502
1503 class UserGroupRepoToPerm(Base, BaseModel):
1503 class UserGroupRepoToPerm(Base, BaseModel):
1504 __tablename__ = 'users_group_repo_to_perm'
1504 __tablename__ = 'users_group_repo_to_perm'
1505 __table_args__ = (
1505 __table_args__ = (
1506 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1506 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1508 'mysql_charset': 'utf8'}
1508 'mysql_charset': 'utf8'}
1509 )
1509 )
1510 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1510 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1511 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1511 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1512 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1512 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1513 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1513 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1514
1514
1515 users_group = relationship('UserGroup')
1515 users_group = relationship('UserGroup')
1516 permission = relationship('Permission')
1516 permission = relationship('Permission')
1517 repository = relationship('Repository')
1517 repository = relationship('Repository')
1518
1518
1519 @classmethod
1519 @classmethod
1520 def create(cls, users_group, repository, permission):
1520 def create(cls, users_group, repository, permission):
1521 n = cls()
1521 n = cls()
1522 n.users_group = users_group
1522 n.users_group = users_group
1523 n.repository = repository
1523 n.repository = repository
1524 n.permission = permission
1524 n.permission = permission
1525 Session().add(n)
1525 Session().add(n)
1526 return n
1526 return n
1527
1527
1528 def __unicode__(self):
1528 def __unicode__(self):
1529 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1529 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1530
1530
1531
1531
1532 class UserGroupToPerm(Base, BaseModel):
1532 class UserGroupToPerm(Base, BaseModel):
1533 __tablename__ = 'users_group_to_perm'
1533 __tablename__ = 'users_group_to_perm'
1534 __table_args__ = (
1534 __table_args__ = (
1535 UniqueConstraint('users_group_id', 'permission_id',),
1535 UniqueConstraint('users_group_id', 'permission_id',),
1536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1537 'mysql_charset': 'utf8'}
1537 'mysql_charset': 'utf8'}
1538 )
1538 )
1539 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1539 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1540 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1540 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1541 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1541 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1542
1542
1543 users_group = relationship('UserGroup')
1543 users_group = relationship('UserGroup')
1544 permission = relationship('Permission')
1544 permission = relationship('Permission')
1545
1545
1546
1546
1547 class UserRepoGroupToPerm(Base, BaseModel):
1547 class UserRepoGroupToPerm(Base, BaseModel):
1548 __tablename__ = 'user_repo_group_to_perm'
1548 __tablename__ = 'user_repo_group_to_perm'
1549 __table_args__ = (
1549 __table_args__ = (
1550 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1550 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1552 'mysql_charset': 'utf8'}
1552 'mysql_charset': 'utf8'}
1553 )
1553 )
1554
1554
1555 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1555 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1556 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1556 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1557 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1557 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1558 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1558 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1559
1559
1560 user = relationship('User')
1560 user = relationship('User')
1561 group = relationship('RepoGroup')
1561 group = relationship('RepoGroup')
1562 permission = relationship('Permission')
1562 permission = relationship('Permission')
1563
1563
1564
1564
1565 class UserGroupRepoGroupToPerm(Base, BaseModel):
1565 class UserGroupRepoGroupToPerm(Base, BaseModel):
1566 __tablename__ = 'users_group_repo_group_to_perm'
1566 __tablename__ = 'users_group_repo_group_to_perm'
1567 __table_args__ = (
1567 __table_args__ = (
1568 UniqueConstraint('users_group_id', 'group_id'),
1568 UniqueConstraint('users_group_id', 'group_id'),
1569 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1569 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1570 'mysql_charset': 'utf8'}
1570 'mysql_charset': 'utf8'}
1571 )
1571 )
1572
1572
1573 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)
1573 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)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1575 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1576 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1576 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1577
1577
1578 users_group = relationship('UserGroup')
1578 users_group = relationship('UserGroup')
1579 permission = relationship('Permission')
1579 permission = relationship('Permission')
1580 group = relationship('RepoGroup')
1580 group = relationship('RepoGroup')
1581
1581
1582
1582
1583 class Statistics(Base, BaseModel):
1583 class Statistics(Base, BaseModel):
1584 __tablename__ = 'statistics'
1584 __tablename__ = 'statistics'
1585 __table_args__ = (
1585 __table_args__ = (
1586 UniqueConstraint('repository_id'),
1586 UniqueConstraint('repository_id'),
1587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1588 'mysql_charset': 'utf8'}
1588 'mysql_charset': 'utf8'}
1589 )
1589 )
1590 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1590 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1591 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1592 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1592 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1593 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1593 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1594 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1594 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1595 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1595 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1596
1596
1597 repository = relationship('Repository', single_parent=True)
1597 repository = relationship('Repository', single_parent=True)
1598
1598
1599
1599
1600 class UserFollowing(Base, BaseModel):
1600 class UserFollowing(Base, BaseModel):
1601 __tablename__ = 'user_followings'
1601 __tablename__ = 'user_followings'
1602 __table_args__ = (
1602 __table_args__ = (
1603 UniqueConstraint('user_id', 'follows_repository_id'),
1603 UniqueConstraint('user_id', 'follows_repository_id'),
1604 UniqueConstraint('user_id', 'follows_user_id'),
1604 UniqueConstraint('user_id', 'follows_user_id'),
1605 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1605 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1606 'mysql_charset': 'utf8'}
1606 'mysql_charset': 'utf8'}
1607 )
1607 )
1608
1608
1609 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1609 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1611 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1611 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1612 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1612 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1613 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1613 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1614
1614
1615 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1615 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1616
1616
1617 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1617 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1618 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1618 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1619
1619
1620 @classmethod
1620 @classmethod
1621 def get_repo_followers(cls, repo_id):
1621 def get_repo_followers(cls, repo_id):
1622 return cls.query().filter(cls.follows_repo_id == repo_id)
1622 return cls.query().filter(cls.follows_repo_id == repo_id)
1623
1623
1624
1624
1625 class CacheInvalidation(Base, BaseModel):
1625 class CacheInvalidation(Base, BaseModel):
1626 __tablename__ = 'cache_invalidation'
1626 __tablename__ = 'cache_invalidation'
1627 __table_args__ = (
1627 __table_args__ = (
1628 UniqueConstraint('cache_key'),
1628 UniqueConstraint('cache_key'),
1629 Index('key_idx', 'cache_key'),
1629 Index('key_idx', 'cache_key'),
1630 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1630 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1631 'mysql_charset': 'utf8'},
1631 'mysql_charset': 'utf8'},
1632 )
1632 )
1633 # cache_id, not used
1633 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1634 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1635 # cache_key as created by _get_cache_key
1634 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1636 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1637 # cache_args is usually a repo_name, possibly with _README/_RSS/_ATOM suffix
1635 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1638 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1639 # instance sets cache_active True when it is caching, other instances set cache_active to False to invalidate
1636 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1640 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1637
1641
1638 def __init__(self, cache_key, cache_args=''):
1642 def __init__(self, cache_key, cache_args=''):
1639 self.cache_key = cache_key
1643 self.cache_key = cache_key
1640 self.cache_args = cache_args
1644 self.cache_args = cache_args
1641 self.cache_active = False
1645 self.cache_active = False
1642
1646
1643 def __unicode__(self):
1647 def __unicode__(self):
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1648 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1645 self.cache_id, self.cache_key)
1649 self.cache_id, self.cache_key)
1646
1650
1647 @property
1651 def get_prefix(self):
1648 def prefix(self):
1652 """
1653 Guess prefix that might have been used in _get_cache_key to generate self.cache_key .
1654 Only used for informational purposes in repo_edit.html .
1655 """
1649 _split = self.cache_key.split(self.cache_args, 1)
1656 _split = self.cache_key.split(self.cache_args, 1)
1650 if _split and len(_split) == 2:
1657 if len(_split) == 2:
1651 return _split[0]
1658 return _split[0]
1652 return ''
1659 return ''
1653
1660
1654 @classmethod
1661 @classmethod
1655 def clear_cache(cls):
1662 def _get_cache_key(cls, key):
1656 cls.query().delete()
1657
1658 @classmethod
1659 def _get_key(cls, key):
1660 """
1663 """
1661 Wrapper for generating a key, together with a prefix
1664 Wrapper for generating a unique cache key for this instance and "key".
1662
1663 :param key:
1664 """
1665 """
1665 import rhodecode
1666 import rhodecode
1666 prefix = ''
1667 prefix = rhodecode.CONFIG.get('instance_id', '')
1667 org_key = key
1668 return "%s%s" % (prefix, key)
1668 iid = rhodecode.CONFIG.get('instance_id')
1669 if iid:
1670 prefix = iid
1671
1672 return "%s%s" % (prefix, key), prefix, org_key
1673
1669
1674 @classmethod
1670 @classmethod
1675 def get_by_key(cls, key):
1671 def _get_or_create_inv_obj(cls, key, repo_name, commit=True):
1676 return cls.query().filter(cls.cache_key == key).scalar()
1677
1678 @classmethod
1679 def get_by_repo_name(cls, repo_name):
1680 return cls.query().filter(cls.cache_args == repo_name).all()
1681
1682 @classmethod
1683 def _get_or_create_key(cls, key, repo_name, commit=True):
1684 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1672 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1685 if not inv_obj:
1673 if not inv_obj:
1686 try:
1674 try:
1687 inv_obj = CacheInvalidation(key, repo_name)
1675 inv_obj = CacheInvalidation(key, repo_name)
1688 Session().add(inv_obj)
1676 Session().add(inv_obj)
1689 if commit:
1677 if commit:
1690 Session().commit()
1678 Session().commit()
1691 except Exception:
1679 except Exception:
1692 log.error(traceback.format_exc())
1680 log.error(traceback.format_exc())
1693 Session().rollback()
1681 Session().rollback()
1694 return inv_obj
1682 return inv_obj
1695
1683
1696 @classmethod
1684 @classmethod
1697 def invalidate(cls, key):
1685 def invalidate(cls, key):
1698 """
1686 """
1699 Returns Invalidation object if this given key should be invalidated
1687 Returns Invalidation object if this given key should be invalidated
1700 None otherwise. `cache_active = False` means that this cache
1688 None otherwise. `cache_active = False` means that this cache
1701 state is not valid and needs to be invalidated
1689 state is not valid and needs to be invalidated
1702
1690
1703 :param key:
1691 :param key:
1704 """
1692 """
1705 repo_name = key
1693 repo_name = key
1706 repo_name = remove_suffix(repo_name, '_README')
1694 repo_name = remove_suffix(repo_name, '_README')
1707 repo_name = remove_suffix(repo_name, '_RSS')
1695 repo_name = remove_suffix(repo_name, '_RSS')
1708 repo_name = remove_suffix(repo_name, '_ATOM')
1696 repo_name = remove_suffix(repo_name, '_ATOM')
1709
1697
1710 # adds instance prefix
1698 cache_key = cls._get_cache_key(key)
1711 key, _prefix, _org_key = cls._get_key(key)
1699 inv = cls._get_or_create_inv_obj(cache_key, repo_name)
1712 inv = cls._get_or_create_key(key, repo_name)
1713
1700
1714 if inv and inv.cache_active is False:
1701 if inv and inv.cache_active is False:
1715 return inv
1702 return inv
1716
1703
1717 @classmethod
1704 @classmethod
1718 def set_invalidate(cls, key=None, repo_name=None):
1705 def set_invalidate(cls, key=None, repo_name=None):
1719 """
1706 """
1720 Mark this Cache key for invalidation, either by key or whole
1707 Mark this Cache key for invalidation, either by key or whole
1721 cache sets based on repo_name
1708 cache sets based on repo_name
1722
1709
1723 :param key:
1710 :param key:
1724 """
1711 """
1725 invalidated_keys = []
1712 invalidated_keys = []
1726 if key:
1713 if key:
1727 key, _prefix, _org_key = cls._get_key(key)
1714 assert not repo_name
1728 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1715 cache_key = cls._get_cache_key(key)
1729 elif repo_name:
1716 inv_objs = Session().query(cls).filter(cls.cache_key == cache_key).all()
1717 else:
1718 assert repo_name
1730 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1719 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1731
1720
1732 try:
1721 try:
1733 for inv_obj in inv_objs:
1722 for inv_obj in inv_objs:
1734 inv_obj.cache_active = False
1723 inv_obj.cache_active = False
1735 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1724 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1736 % (inv_obj, key, safe_str(repo_name)))
1725 % (inv_obj, key, safe_str(repo_name)))
1737 invalidated_keys.append(inv_obj.cache_key)
1726 invalidated_keys.append(inv_obj.cache_key)
1738 Session().add(inv_obj)
1727 Session().add(inv_obj)
1739 Session().commit()
1728 Session().commit()
1740 except Exception:
1729 except Exception:
1741 log.error(traceback.format_exc())
1730 log.error(traceback.format_exc())
1742 Session().rollback()
1731 Session().rollback()
1743 return invalidated_keys
1732 return invalidated_keys
1744
1733
1745 @classmethod
1734 @classmethod
1746 def set_valid(cls, key):
1735 def set_valid(cls, key):
1747 """
1736 """
1748 Mark this cache key as active and currently cached
1737 Mark this cache key as active and currently cached
1749
1738
1750 :param key:
1739 :param key:
1751 """
1740 """
1752 inv_obj = cls.get_by_key(key)
1741 inv_obj = cls.query().filter(cls.cache_key == key).scalar()
1753 inv_obj.cache_active = True
1742 inv_obj.cache_active = True
1754 Session().add(inv_obj)
1743 Session().add(inv_obj)
1755 Session().commit()
1744 Session().commit()
1756
1745
1757 @classmethod
1746 @classmethod
1758 def get_cache_map(cls):
1747 def get_cache_map(cls):
1759
1748
1760 class cachemapdict(dict):
1749 class cachemapdict(dict):
1761
1750
1762 def __init__(self, *args, **kwargs):
1751 def __init__(self, *args, **kwargs):
1763 fixkey = kwargs.get('fixkey')
1752 self.fixkey = kwargs.pop('fixkey', False)
1764 if fixkey:
1765 del kwargs['fixkey']
1766 self.fixkey = fixkey
1767 super(cachemapdict, self).__init__(*args, **kwargs)
1753 super(cachemapdict, self).__init__(*args, **kwargs)
1768
1754
1769 def __getattr__(self, name):
1755 def __getattr__(self, name):
1770 key = name
1756 cache_key = name
1771 if self.fixkey:
1757 if self.fixkey:
1772 key, _prefix, _org_key = cls._get_key(key)
1758 cache_key = cls._get_cache_key(name)
1773 if key in self.__dict__:
1759 if cache_key in self.__dict__:
1774 return self.__dict__[key]
1760 return self.__dict__[cache_key]
1775 else:
1761 else:
1776 return self[key]
1762 return self[cache_key]
1777
1763
1778 def __getitem__(self, key):
1764 def __getitem__(self, name):
1765 cache_key = name
1779 if self.fixkey:
1766 if self.fixkey:
1780 key, _prefix, _org_key = cls._get_key(key)
1767 cache_key = cls._get_cache_key(name)
1781 try:
1768 try:
1782 return super(cachemapdict, self).__getitem__(key)
1769 return super(cachemapdict, self).__getitem__(cache_key)
1783 except KeyError:
1770 except KeyError:
1784 return
1771 return None
1785
1772
1786 cache_map = cachemapdict(fixkey=True)
1773 cache_map = cachemapdict(fixkey=True)
1787 for obj in cls.query().all():
1774 for obj in cls.query().all():
1788 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1775 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1789 return cache_map
1776 return cache_map
1790
1777
1791
1778
1792 class ChangesetComment(Base, BaseModel):
1779 class ChangesetComment(Base, BaseModel):
1793 __tablename__ = 'changeset_comments'
1780 __tablename__ = 'changeset_comments'
1794 __table_args__ = (
1781 __table_args__ = (
1795 Index('cc_revision_idx', 'revision'),
1782 Index('cc_revision_idx', 'revision'),
1796 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1783 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1797 'mysql_charset': 'utf8'},
1784 'mysql_charset': 'utf8'},
1798 )
1785 )
1799 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1786 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1800 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1787 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1801 revision = Column('revision', String(40), nullable=True)
1788 revision = Column('revision', String(40), nullable=True)
1802 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1789 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1803 line_no = Column('line_no', Unicode(10), nullable=True)
1790 line_no = Column('line_no', Unicode(10), nullable=True)
1804 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1791 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1805 f_path = Column('f_path', Unicode(1000), nullable=True)
1792 f_path = Column('f_path', Unicode(1000), nullable=True)
1806 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1793 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1807 text = Column('text', UnicodeText(25000), nullable=False)
1794 text = Column('text', UnicodeText(25000), nullable=False)
1808 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1795 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1809 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1796 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1810
1797
1811 author = relationship('User', lazy='joined')
1798 author = relationship('User', lazy='joined')
1812 repo = relationship('Repository')
1799 repo = relationship('Repository')
1813 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1800 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1814 pull_request = relationship('PullRequest', lazy='joined')
1801 pull_request = relationship('PullRequest', lazy='joined')
1815
1802
1816 @classmethod
1803 @classmethod
1817 def get_users(cls, revision=None, pull_request_id=None):
1804 def get_users(cls, revision=None, pull_request_id=None):
1818 """
1805 """
1819 Returns user associated with this ChangesetComment. ie those
1806 Returns user associated with this ChangesetComment. ie those
1820 who actually commented
1807 who actually commented
1821
1808
1822 :param cls:
1809 :param cls:
1823 :param revision:
1810 :param revision:
1824 """
1811 """
1825 q = Session().query(User)\
1812 q = Session().query(User)\
1826 .join(ChangesetComment.author)
1813 .join(ChangesetComment.author)
1827 if revision:
1814 if revision:
1828 q = q.filter(cls.revision == revision)
1815 q = q.filter(cls.revision == revision)
1829 elif pull_request_id:
1816 elif pull_request_id:
1830 q = q.filter(cls.pull_request_id == pull_request_id)
1817 q = q.filter(cls.pull_request_id == pull_request_id)
1831 return q.all()
1818 return q.all()
1832
1819
1833
1820
1834 class ChangesetStatus(Base, BaseModel):
1821 class ChangesetStatus(Base, BaseModel):
1835 __tablename__ = 'changeset_statuses'
1822 __tablename__ = 'changeset_statuses'
1836 __table_args__ = (
1823 __table_args__ = (
1837 Index('cs_revision_idx', 'revision'),
1824 Index('cs_revision_idx', 'revision'),
1838 Index('cs_version_idx', 'version'),
1825 Index('cs_version_idx', 'version'),
1839 UniqueConstraint('repo_id', 'revision', 'version'),
1826 UniqueConstraint('repo_id', 'revision', 'version'),
1840 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1827 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1841 'mysql_charset': 'utf8'}
1828 'mysql_charset': 'utf8'}
1842 )
1829 )
1843 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1830 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1844 STATUS_APPROVED = 'approved'
1831 STATUS_APPROVED = 'approved'
1845 STATUS_REJECTED = 'rejected'
1832 STATUS_REJECTED = 'rejected'
1846 STATUS_UNDER_REVIEW = 'under_review'
1833 STATUS_UNDER_REVIEW = 'under_review'
1847
1834
1848 STATUSES = [
1835 STATUSES = [
1849 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1836 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1850 (STATUS_APPROVED, _("Approved")),
1837 (STATUS_APPROVED, _("Approved")),
1851 (STATUS_REJECTED, _("Rejected")),
1838 (STATUS_REJECTED, _("Rejected")),
1852 (STATUS_UNDER_REVIEW, _("Under Review")),
1839 (STATUS_UNDER_REVIEW, _("Under Review")),
1853 ]
1840 ]
1854
1841
1855 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1842 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1856 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1843 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1857 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1844 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1858 revision = Column('revision', String(40), nullable=False)
1845 revision = Column('revision', String(40), nullable=False)
1859 status = Column('status', String(128), nullable=False, default=DEFAULT)
1846 status = Column('status', String(128), nullable=False, default=DEFAULT)
1860 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1847 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1861 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1848 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1862 version = Column('version', Integer(), nullable=False, default=0)
1849 version = Column('version', Integer(), nullable=False, default=0)
1863 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1850 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1864
1851
1865 author = relationship('User', lazy='joined')
1852 author = relationship('User', lazy='joined')
1866 repo = relationship('Repository')
1853 repo = relationship('Repository')
1867 comment = relationship('ChangesetComment', lazy='joined')
1854 comment = relationship('ChangesetComment', lazy='joined')
1868 pull_request = relationship('PullRequest', lazy='joined')
1855 pull_request = relationship('PullRequest', lazy='joined')
1869
1856
1870 def __unicode__(self):
1857 def __unicode__(self):
1871 return u"<%s('%s:%s')>" % (
1858 return u"<%s('%s:%s')>" % (
1872 self.__class__.__name__,
1859 self.__class__.__name__,
1873 self.status, self.author
1860 self.status, self.author
1874 )
1861 )
1875
1862
1876 @classmethod
1863 @classmethod
1877 def get_status_lbl(cls, value):
1864 def get_status_lbl(cls, value):
1878 return dict(cls.STATUSES).get(value)
1865 return dict(cls.STATUSES).get(value)
1879
1866
1880 @property
1867 @property
1881 def status_lbl(self):
1868 def status_lbl(self):
1882 return ChangesetStatus.get_status_lbl(self.status)
1869 return ChangesetStatus.get_status_lbl(self.status)
1883
1870
1884
1871
1885 class PullRequest(Base, BaseModel):
1872 class PullRequest(Base, BaseModel):
1886 __tablename__ = 'pull_requests'
1873 __tablename__ = 'pull_requests'
1887 __table_args__ = (
1874 __table_args__ = (
1888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1875 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1889 'mysql_charset': 'utf8'},
1876 'mysql_charset': 'utf8'},
1890 )
1877 )
1891
1878
1892 STATUS_NEW = u'new'
1879 STATUS_NEW = u'new'
1893 STATUS_OPEN = u'open'
1880 STATUS_OPEN = u'open'
1894 STATUS_CLOSED = u'closed'
1881 STATUS_CLOSED = u'closed'
1895
1882
1896 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1883 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1897 title = Column('title', Unicode(256), nullable=True)
1884 title = Column('title', Unicode(256), nullable=True)
1898 description = Column('description', UnicodeText(10240), nullable=True)
1885 description = Column('description', UnicodeText(10240), nullable=True)
1899 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1886 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1887 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1901 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1888 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1902 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1889 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1903 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1890 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1904 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1891 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1905 org_ref = Column('org_ref', Unicode(256), nullable=False)
1892 org_ref = Column('org_ref', Unicode(256), nullable=False)
1906 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1893 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1907 other_ref = Column('other_ref', Unicode(256), nullable=False)
1894 other_ref = Column('other_ref', Unicode(256), nullable=False)
1908
1895
1909 @hybrid_property
1896 @hybrid_property
1910 def revisions(self):
1897 def revisions(self):
1911 return self._revisions.split(':')
1898 return self._revisions.split(':')
1912
1899
1913 @revisions.setter
1900 @revisions.setter
1914 def revisions(self, val):
1901 def revisions(self, val):
1915 self._revisions = ':'.join(val)
1902 self._revisions = ':'.join(val)
1916
1903
1917 @property
1904 @property
1918 def org_ref_parts(self):
1905 def org_ref_parts(self):
1919 return self.org_ref.split(':')
1906 return self.org_ref.split(':')
1920
1907
1921 @property
1908 @property
1922 def other_ref_parts(self):
1909 def other_ref_parts(self):
1923 return self.other_ref.split(':')
1910 return self.other_ref.split(':')
1924
1911
1925 author = relationship('User', lazy='joined')
1912 author = relationship('User', lazy='joined')
1926 reviewers = relationship('PullRequestReviewers',
1913 reviewers = relationship('PullRequestReviewers',
1927 cascade="all, delete, delete-orphan")
1914 cascade="all, delete, delete-orphan")
1928 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1915 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1929 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1916 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1930 statuses = relationship('ChangesetStatus')
1917 statuses = relationship('ChangesetStatus')
1931 comments = relationship('ChangesetComment',
1918 comments = relationship('ChangesetComment',
1932 cascade="all, delete, delete-orphan")
1919 cascade="all, delete, delete-orphan")
1933
1920
1934 def is_closed(self):
1921 def is_closed(self):
1935 return self.status == self.STATUS_CLOSED
1922 return self.status == self.STATUS_CLOSED
1936
1923
1937 @property
1924 @property
1938 def last_review_status(self):
1925 def last_review_status(self):
1939 return self.statuses[-1].status if self.statuses else ''
1926 return self.statuses[-1].status if self.statuses else ''
1940
1927
1941 def __json__(self):
1928 def __json__(self):
1942 return dict(
1929 return dict(
1943 revisions=self.revisions
1930 revisions=self.revisions
1944 )
1931 )
1945
1932
1946
1933
1947 class PullRequestReviewers(Base, BaseModel):
1934 class PullRequestReviewers(Base, BaseModel):
1948 __tablename__ = 'pull_request_reviewers'
1935 __tablename__ = 'pull_request_reviewers'
1949 __table_args__ = (
1936 __table_args__ = (
1950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1937 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1951 'mysql_charset': 'utf8'},
1938 'mysql_charset': 'utf8'},
1952 )
1939 )
1953
1940
1954 def __init__(self, user=None, pull_request=None):
1941 def __init__(self, user=None, pull_request=None):
1955 self.user = user
1942 self.user = user
1956 self.pull_request = pull_request
1943 self.pull_request = pull_request
1957
1944
1958 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1945 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1959 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1946 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1960 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1947 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1961
1948
1962 user = relationship('User')
1949 user = relationship('User')
1963 pull_request = relationship('PullRequest')
1950 pull_request = relationship('PullRequest')
1964
1951
1965
1952
1966 class Notification(Base, BaseModel):
1953 class Notification(Base, BaseModel):
1967 __tablename__ = 'notifications'
1954 __tablename__ = 'notifications'
1968 __table_args__ = (
1955 __table_args__ = (
1969 Index('notification_type_idx', 'type'),
1956 Index('notification_type_idx', 'type'),
1970 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1971 'mysql_charset': 'utf8'},
1958 'mysql_charset': 'utf8'},
1972 )
1959 )
1973
1960
1974 TYPE_CHANGESET_COMMENT = u'cs_comment'
1961 TYPE_CHANGESET_COMMENT = u'cs_comment'
1975 TYPE_MESSAGE = u'message'
1962 TYPE_MESSAGE = u'message'
1976 TYPE_MENTION = u'mention'
1963 TYPE_MENTION = u'mention'
1977 TYPE_REGISTRATION = u'registration'
1964 TYPE_REGISTRATION = u'registration'
1978 TYPE_PULL_REQUEST = u'pull_request'
1965 TYPE_PULL_REQUEST = u'pull_request'
1979 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1966 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1980
1967
1981 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1968 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1982 subject = Column('subject', Unicode(512), nullable=True)
1969 subject = Column('subject', Unicode(512), nullable=True)
1983 body = Column('body', UnicodeText(50000), nullable=True)
1970 body = Column('body', UnicodeText(50000), nullable=True)
1984 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1971 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1985 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1972 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1986 type_ = Column('type', Unicode(256))
1973 type_ = Column('type', Unicode(256))
1987
1974
1988 created_by_user = relationship('User')
1975 created_by_user = relationship('User')
1989 notifications_to_users = relationship('UserNotification', lazy='joined',
1976 notifications_to_users = relationship('UserNotification', lazy='joined',
1990 cascade="all, delete, delete-orphan")
1977 cascade="all, delete, delete-orphan")
1991
1978
1992 @property
1979 @property
1993 def recipients(self):
1980 def recipients(self):
1994 return [x.user for x in UserNotification.query()\
1981 return [x.user for x in UserNotification.query()\
1995 .filter(UserNotification.notification == self)\
1982 .filter(UserNotification.notification == self)\
1996 .order_by(UserNotification.user_id.asc()).all()]
1983 .order_by(UserNotification.user_id.asc()).all()]
1997
1984
1998 @classmethod
1985 @classmethod
1999 def create(cls, created_by, subject, body, recipients, type_=None):
1986 def create(cls, created_by, subject, body, recipients, type_=None):
2000 if type_ is None:
1987 if type_ is None:
2001 type_ = Notification.TYPE_MESSAGE
1988 type_ = Notification.TYPE_MESSAGE
2002
1989
2003 notification = cls()
1990 notification = cls()
2004 notification.created_by_user = created_by
1991 notification.created_by_user = created_by
2005 notification.subject = subject
1992 notification.subject = subject
2006 notification.body = body
1993 notification.body = body
2007 notification.type_ = type_
1994 notification.type_ = type_
2008 notification.created_on = datetime.datetime.now()
1995 notification.created_on = datetime.datetime.now()
2009
1996
2010 for u in recipients:
1997 for u in recipients:
2011 assoc = UserNotification()
1998 assoc = UserNotification()
2012 assoc.notification = notification
1999 assoc.notification = notification
2013 u.notifications.append(assoc)
2000 u.notifications.append(assoc)
2014 Session().add(notification)
2001 Session().add(notification)
2015 return notification
2002 return notification
2016
2003
2017 @property
2004 @property
2018 def description(self):
2005 def description(self):
2019 from rhodecode.model.notification import NotificationModel
2006 from rhodecode.model.notification import NotificationModel
2020 return NotificationModel().make_description(self)
2007 return NotificationModel().make_description(self)
2021
2008
2022
2009
2023 class UserNotification(Base, BaseModel):
2010 class UserNotification(Base, BaseModel):
2024 __tablename__ = 'user_to_notification'
2011 __tablename__ = 'user_to_notification'
2025 __table_args__ = (
2012 __table_args__ = (
2026 UniqueConstraint('user_id', 'notification_id'),
2013 UniqueConstraint('user_id', 'notification_id'),
2027 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2014 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2028 'mysql_charset': 'utf8'}
2015 'mysql_charset': 'utf8'}
2029 )
2016 )
2030 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2017 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2031 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2018 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2032 read = Column('read', Boolean, default=False)
2019 read = Column('read', Boolean, default=False)
2033 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2020 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2034
2021
2035 user = relationship('User', lazy="joined")
2022 user = relationship('User', lazy="joined")
2036 notification = relationship('Notification', lazy="joined",
2023 notification = relationship('Notification', lazy="joined",
2037 order_by=lambda: Notification.created_on.desc(),)
2024 order_by=lambda: Notification.created_on.desc(),)
2038
2025
2039 def mark_as_read(self):
2026 def mark_as_read(self):
2040 self.read = True
2027 self.read = True
2041 Session().add(self)
2028 Session().add(self)
2042
2029
2043
2030
2044 class DbMigrateVersion(Base, BaseModel):
2031 class DbMigrateVersion(Base, BaseModel):
2045 __tablename__ = 'db_migrate_version'
2032 __tablename__ = 'db_migrate_version'
2046 __table_args__ = (
2033 __table_args__ = (
2047 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2034 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2048 'mysql_charset': 'utf8'},
2035 'mysql_charset': 'utf8'},
2049 )
2036 )
2050 repository_id = Column('repository_id', String(250), primary_key=True)
2037 repository_id = Column('repository_id', String(250), primary_key=True)
2051 repository_path = Column('repository_path', Text)
2038 repository_path = Column('repository_path', Text)
2052 version = Column('version', Integer)
2039 version = Column('version', Integer)
@@ -1,378 +1,378 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##
2 ##
3 ## See also repo_settings.html
3 ## See also repo_settings.html
4 ##
4 ##
5 <%inherit file="/base/base.html"/>
5 <%inherit file="/base/base.html"/>
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('Edit repository')} ${c.repo_info.repo_name} &middot; ${c.rhodecode_name}
8 ${_('Edit repository')} ${c.repo_info.repo_name} &middot; ${c.rhodecode_name}
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${_('Settings')}
12 ${_('Settings')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 ${self.context_bar('options')}
20 ${self.context_bar('options')}
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="repo_name">${_('Name')}:</label>
32 <label for="repo_name">${_('Name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('repo_name',class_="medium")}
35 ${h.text('repo_name',class_="medium")}
36 </div>
36 </div>
37 </div>
37 </div>
38 <div class="field">
38 <div class="field">
39 <div class="label">
39 <div class="label">
40 <label for="clone_uri">${_('Clone uri')}:</label>
40 <label for="clone_uri">${_('Clone uri')}:</label>
41 </div>
41 </div>
42 <div class="input">
42 <div class="input">
43 ${h.text('clone_uri',class_="medium")}
43 ${h.text('clone_uri',class_="medium")}
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
45 </div>
45 </div>
46 </div>
46 </div>
47 <div class="field">
47 <div class="field">
48 <div class="label">
48 <div class="label">
49 <label for="repo_group">${_('Repository group')}:</label>
49 <label for="repo_group">${_('Repository group')}:</label>
50 </div>
50 </div>
51 <div class="input">
51 <div class="input">
52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
54 </div>
54 </div>
55 </div>
55 </div>
56 <div class="field">
56 <div class="field">
57 <div class="label">
57 <div class="label">
58 <label for="repo_type">${_('Type')}:</label>
58 <label for="repo_type">${_('Type')}:</label>
59 </div>
59 </div>
60 <div class="input">
60 <div class="input">
61 ${h.select('repo_type','hg',c.backends,class_="medium")}
61 ${h.select('repo_type','hg',c.backends,class_="medium")}
62 </div>
62 </div>
63 </div>
63 </div>
64 <div class="field">
64 <div class="field">
65 <div class="label">
65 <div class="label">
66 <label for="repo_landing_rev">${_('Landing revision')}:</label>
66 <label for="repo_landing_rev">${_('Landing revision')}:</label>
67 </div>
67 </div>
68 <div class="input">
68 <div class="input">
69 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
69 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
71 </div>
71 </div>
72 </div>
72 </div>
73 <div class="field">
73 <div class="field">
74 <div class="label label-textarea">
74 <div class="label label-textarea">
75 <label for="repo_description">${_('Description')}:</label>
75 <label for="repo_description">${_('Description')}:</label>
76 </div>
76 </div>
77 <div class="textarea text-area editor">
77 <div class="textarea text-area editor">
78 ${h.textarea('repo_description')}
78 ${h.textarea('repo_description')}
79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
80 </div>
80 </div>
81 </div>
81 </div>
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label label-checkbox">
84 <div class="label label-checkbox">
85 <label for="repo_private">${_('Private repository')}:</label>
85 <label for="repo_private">${_('Private repository')}:</label>
86 </div>
86 </div>
87 <div class="checkboxes">
87 <div class="checkboxes">
88 ${h.checkbox('repo_private',value="True")}
88 ${h.checkbox('repo_private',value="True")}
89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
90 </div>
90 </div>
91 </div>
91 </div>
92 <div class="field">
92 <div class="field">
93 <div class="label label-checkbox">
93 <div class="label label-checkbox">
94 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
94 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
95 </div>
95 </div>
96 <div class="checkboxes">
96 <div class="checkboxes">
97 ${h.checkbox('repo_enable_statistics',value="True")}
97 ${h.checkbox('repo_enable_statistics',value="True")}
98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
99 </div>
99 </div>
100 </div>
100 </div>
101 <div class="field">
101 <div class="field">
102 <div class="label label-checkbox">
102 <div class="label label-checkbox">
103 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
103 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
104 </div>
104 </div>
105 <div class="checkboxes">
105 <div class="checkboxes">
106 ${h.checkbox('repo_enable_downloads',value="True")}
106 ${h.checkbox('repo_enable_downloads',value="True")}
107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
108 </div>
108 </div>
109 </div>
109 </div>
110 <div class="field">
110 <div class="field">
111 <div class="label label-checkbox">
111 <div class="label label-checkbox">
112 <label for="repo_enable_locking">${_('Enable locking')}:</label>
112 <label for="repo_enable_locking">${_('Enable locking')}:</label>
113 </div>
113 </div>
114 <div class="checkboxes">
114 <div class="checkboxes">
115 ${h.checkbox('repo_enable_locking',value="True")}
115 ${h.checkbox('repo_enable_locking',value="True")}
116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
117 </div>
117 </div>
118 </div>
118 </div>
119 <div class="field">
119 <div class="field">
120 <div class="label">
120 <div class="label">
121 <label for="user">${_('Owner')}:</label>
121 <label for="user">${_('Owner')}:</label>
122 </div>
122 </div>
123 <div class="input input-medium ac">
123 <div class="input input-medium ac">
124 <div class="perm_ac">
124 <div class="perm_ac">
125 ${h.text('user',class_='yui-ac-input')}
125 ${h.text('user',class_='yui-ac-input')}
126 <span class="help-block">${_('Change owner of this repository.')}</span>
126 <span class="help-block">${_('Change owner of this repository.')}</span>
127 <div id="owner_container"></div>
127 <div id="owner_container"></div>
128 </div>
128 </div>
129 </div>
129 </div>
130 </div>
130 </div>
131 %if c.visual.repository_fields:
131 %if c.visual.repository_fields:
132 ## EXTRA FIELDS
132 ## EXTRA FIELDS
133 %for field in c.repo_fields:
133 %for field in c.repo_fields:
134 <div class="field">
134 <div class="field">
135 <div class="label">
135 <div class="label">
136 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
136 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
137 </div>
137 </div>
138 <div class="input input-medium">
138 <div class="input input-medium">
139 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
139 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
140 %if field.field_desc:
140 %if field.field_desc:
141 <span class="help-block">${field.field_desc}</span>
141 <span class="help-block">${field.field_desc}</span>
142 %endif
142 %endif
143 </div>
143 </div>
144 </div>
144 </div>
145 %endfor
145 %endfor
146 %endif
146 %endif
147 <div class="field">
147 <div class="field">
148 <div class="label">
148 <div class="label">
149 <label for="input">${_('Permissions')}:</label>
149 <label for="input">${_('Permissions')}:</label>
150 </div>
150 </div>
151 <div class="input">
151 <div class="input">
152 <%include file="repo_edit_perms.html"/>
152 <%include file="repo_edit_perms.html"/>
153 </div>
153 </div>
154 </div>
154 </div>
155
155
156 <div class="buttons">
156 <div class="buttons">
157 ${h.submit('save',_('Save'),class_="ui-btn large")}
157 ${h.submit('save',_('Save'),class_="ui-btn large")}
158 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
158 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
159 </div>
159 </div>
160 </div>
160 </div>
161 </div>
161 </div>
162 ${h.end_form()}
162 ${h.end_form()}
163 </div>
163 </div>
164
164
165 <div class="box box-right">
165 <div class="box box-right">
166 <div class="title">
166 <div class="title">
167 <h5>${_('Advanced settings')}</h5>
167 <h5>${_('Advanced settings')}</h5>
168 </div>
168 </div>
169
169
170 <h3>${_('Statistics')}</h3>
170 <h3>${_('Statistics')}</h3>
171 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
171 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
172 <div class="form">
172 <div class="form">
173 <div class="fields">
173 <div class="fields">
174 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
174 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
175 <div class="field" style="border:none;color:#888">
175 <div class="field" style="border:none;color:#888">
176 <ul>
176 <ul>
177 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
177 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
178 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
178 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
179 </ul>
179 </ul>
180 </div>
180 </div>
181 </div>
181 </div>
182 </div>
182 </div>
183 ${h.end_form()}
183 ${h.end_form()}
184
184
185 %if c.repo_info.clone_uri:
185 %if c.repo_info.clone_uri:
186 <h3>${_('Remote')}</h3>
186 <h3>${_('Remote')}</h3>
187 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
187 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
188 <div class="form">
188 <div class="form">
189 <div class="fields">
189 <div class="fields">
190 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
190 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
191 <div class="field" style="border:none">
191 <div class="field" style="border:none">
192 <ul>
192 <ul>
193 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
193 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
194 </ul>
194 </ul>
195 </div>
195 </div>
196 </div>
196 </div>
197 </div>
197 </div>
198 ${h.end_form()}
198 ${h.end_form()}
199 %endif
199 %endif
200
200
201 <h3>${_('Cache')}</h3>
201 <h3>${_('Cache')}</h3>
202 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
202 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
203 <div class="form">
203 <div class="form">
204 <div class="fields">
204 <div class="fields">
205 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
205 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
206 <div class="field" style="border:none;color:#888">
206 <div class="field" style="border:none;color:#888">
207 <ul>
207 <ul>
208 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
208 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
209 </li>
209 </li>
210 </ul>
210 </ul>
211 </div>
211 </div>
212 <div class="field" style="border:none;">
212 <div class="field" style="border:none;">
213 ${_('List of cached values')}
213 ${_('List of cached values')}
214 <table>
214 <table>
215 <tr>
215 <tr>
216 <th>${_('Prefix')}</th>
216 <th>${_('Prefix')}</th>
217 <th>${_('Key')}</th>
217 <th>${_('Key')}</th>
218 <th>${_('Active')}</th>
218 <th>${_('Active')}</th>
219 </tr>
219 </tr>
220 %for cache in c.repo_info.cache_keys:
220 %for cache in c.repo_info.cache_keys:
221 <tr>
221 <tr>
222 <td>${cache.prefix or '-'}</td>
222 <td>${cache.get_prefix() or '-'}</td>
223 <td>${cache.cache_key}</td>
223 <td>${cache.cache_key}</td>
224 <td>${h.bool2icon(cache.cache_active)}</td>
224 <td>${h.bool2icon(cache.cache_active)}</td>
225 </tr>
225 </tr>
226 %endfor
226 %endfor
227 </table>
227 </table>
228 </div>
228 </div>
229 </div>
229 </div>
230 </div>
230 </div>
231 ${h.end_form()}
231 ${h.end_form()}
232
232
233 <h3>${_('Public journal')}</h3>
233 <h3>${_('Public journal')}</h3>
234 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
234 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
235 <div class="form">
235 <div class="form">
236 ${h.hidden('auth_token',str(h.get_token()))}
236 ${h.hidden('auth_token',str(h.get_token()))}
237 <div class="field">
237 <div class="field">
238 %if c.in_public_journal:
238 %if c.in_public_journal:
239 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
239 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
240 %else:
240 %else:
241 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
241 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
242 %endif
242 %endif
243 </div>
243 </div>
244 <div class="field" style="border:none;color:#888">
244 <div class="field" style="border:none;color:#888">
245 <ul>
245 <ul>
246 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
246 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
247 </li>
247 </li>
248 </ul>
248 </ul>
249 </div>
249 </div>
250 </div>
250 </div>
251 ${h.end_form()}
251 ${h.end_form()}
252
252
253 <h3>${_('Locking')}</h3>
253 <h3>${_('Locking')}</h3>
254 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
254 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
255 <div class="form">
255 <div class="form">
256 <div class="fields">
256 <div class="fields">
257 %if c.repo_info.locked[0]:
257 %if c.repo_info.locked[0]:
258 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
258 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
259 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
259 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
260 %else:
260 %else:
261 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
261 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
262 ${_('Repository is not locked')}
262 ${_('Repository is not locked')}
263 %endif
263 %endif
264 </div>
264 </div>
265 <div class="field" style="border:none;color:#888">
265 <div class="field" style="border:none;color:#888">
266 <ul>
266 <ul>
267 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
267 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
268 </li>
268 </li>
269 </ul>
269 </ul>
270 </div>
270 </div>
271 </div>
271 </div>
272 ${h.end_form()}
272 ${h.end_form()}
273
273
274 <h3>${_('Set as fork of')}</h3>
274 <h3>${_('Set as fork of')}</h3>
275 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
275 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
276 <div class="form">
276 <div class="form">
277 <div class="fields">
277 <div class="fields">
278 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
278 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
279 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
279 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
280 </div>
280 </div>
281 <div class="field" style="border:none;color:#888">
281 <div class="field" style="border:none;color:#888">
282 <ul>
282 <ul>
283 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
283 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
284 </ul>
284 </ul>
285 </div>
285 </div>
286 </div>
286 </div>
287 ${h.end_form()}
287 ${h.end_form()}
288
288
289 <h3>${_('Delete')}</h3>
289 <h3>${_('Delete')}</h3>
290 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
290 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
291 <div class="form">
291 <div class="form">
292 <div class="fields">
292 <div class="fields">
293 <div class="field" style="border:none;color:#888">
293 <div class="field" style="border:none;color:#888">
294 ## <div class="label">
294 ## <div class="label">
295 ## <label for="">${_('Remove repository')}:</label>
295 ## <label for="">${_('Remove repository')}:</label>
296 ## </div>
296 ## </div>
297 <div class="checkboxes">
297 <div class="checkboxes">
298 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
298 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
299 %if c.repo_info.forks.count():
299 %if c.repo_info.forks.count():
300 - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
300 - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
301 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
301 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
302 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
302 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
303 %endif
303 %endif
304 <ul>
304 <ul>
305 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
305 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
306 </ul>
306 </ul>
307 </div>
307 </div>
308 </div>
308 </div>
309 </div>
309 </div>
310 </div>
310 </div>
311 ${h.end_form()}
311 ${h.end_form()}
312 </div>
312 </div>
313
313
314 ##TODO: this should be controlled by the VISUAL setting
314 ##TODO: this should be controlled by the VISUAL setting
315 %if c.visual.repository_fields:
315 %if c.visual.repository_fields:
316 <div class="box box-left" style="clear:left">
316 <div class="box box-left" style="clear:left">
317 <!-- box / title -->
317 <!-- box / title -->
318 <div class="title">
318 <div class="title">
319 <h5>${_('Extra fields')}</h5>
319 <h5>${_('Extra fields')}</h5>
320 </div>
320 </div>
321
321
322 <div class="emails_wrap">
322 <div class="emails_wrap">
323 <table class="noborder">
323 <table class="noborder">
324 %for field in c.repo_fields:
324 %for field in c.repo_fields:
325 <tr>
325 <tr>
326 <td>${field.field_label} (${field.field_key})</td>
326 <td>${field.field_label} (${field.field_key})</td>
327 <td>${field.field_type}</td>
327 <td>${field.field_type}</td>
328 <td>
328 <td>
329 ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
329 ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
330 ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
330 ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
331 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
331 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
332 ${h.end_form()}
332 ${h.end_form()}
333 </td>
333 </td>
334 </tr>
334 </tr>
335 %endfor
335 %endfor
336 </table>
336 </table>
337 </div>
337 </div>
338
338
339 ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')}
339 ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')}
340 <div class="form">
340 <div class="form">
341 <!-- fields -->
341 <!-- fields -->
342 <div class="fields">
342 <div class="fields">
343 <div class="field">
343 <div class="field">
344 <div class="label">
344 <div class="label">
345 <label for="new_field_key">${_('New field key')}:</label>
345 <label for="new_field_key">${_('New field key')}:</label>
346 </div>
346 </div>
347 <div class="input">
347 <div class="input">
348 ${h.text('new_field_key', class_='small')}
348 ${h.text('new_field_key', class_='small')}
349 </div>
349 </div>
350 </div>
350 </div>
351 <div class="field">
351 <div class="field">
352 <div class="label">
352 <div class="label">
353 <label for="new_field_label">${_('New field label')}:</label>
353 <label for="new_field_label">${_('New field label')}:</label>
354 </div>
354 </div>
355 <div class="input">
355 <div class="input">
356 ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
356 ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
357 </div>
357 </div>
358 </div>
358 </div>
359
359
360 <div class="field">
360 <div class="field">
361 <div class="label">
361 <div class="label">
362 <label for="new_field_desc">${_('New field description')}:</label>
362 <label for="new_field_desc">${_('New field description')}:</label>
363 </div>
363 </div>
364 <div class="input">
364 <div class="input">
365 ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
365 ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
366 </div>
366 </div>
367 </div>
367 </div>
368
368
369 <div class="buttons">
369 <div class="buttons">
370 ${h.submit('save',_('Add'),class_="ui-btn large")}
370 ${h.submit('save',_('Add'),class_="ui-btn large")}
371 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
371 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
372 </div>
372 </div>
373 </div>
373 </div>
374 </div>
374 </div>
375 ${h.end_form()}
375 ${h.end_form()}
376 </div>
376 </div>
377 %endif
377 %endif
378 </%def>
378 </%def>
General Comments 0
You need to be logged in to leave comments. Login now