##// END OF EJS Templates
Implemented basic locking functionality....
marcink -
r2726:aa17c7a1 beta
parent child Browse files
Show More
@@ -0,0 +1,41 b''
1 .. _locking:
2
3 ===================================
4 RhodeCode repository locking system
5 ===================================
6
7
8 | Repos with **locking function=disabled** is the default, that's how repos work
9 today.
10 | Repos with **locking function=enabled** behaves like follows:
11
12 Repos have a state called `locked` that can be true or false.
13 The hg/git commands `hg/git clone`, `hg/git pull`, and `hg/git push`
14 influence this state:
15
16 - The command `hg/git pull <repo>` will lock that repo (locked=true)
17 if the user has write/admin permissions on this repo
18
19 - The command `hg/git clone <repo>` will lock that repo (locked=true) if the
20 user has write/admin permissions on this repo
21
22
23 RhodeCode will remember the user id who locked the repo
24 only this specific user can unlock the repo (locked=false) by calling
25
26 - `hg/git push <repo>`
27
28 every other command on that repo from this user and
29 every command from any other user will result in http return code 423 (locked)
30
31
32 additionally the http error includes the <user> that locked the repo
33 (e.g. “repository <repo> locked by user <user>”)
34
35
36 So the scenario of use for repos with `locking function` enabled is that
37 every initial clone and every pull gives users (with write permission)
38 the exclusive right to do a push.
39
40
41 Each repo can be manually unlocked by admin from the repo settings menu. No newline at end of file
@@ -0,0 +1,31 b''
1 #!/usr/bin/env python
2 import os
3 import sys
4
5 try:
6 import rhodecode
7 RC_HOOK_VER = '_TMPL_'
8 os.environ['RC_HOOK_VER'] = RC_HOOK_VER
9 from rhodecode.lib.hooks import handle_git_pre_receive
10 except ImportError:
11 rhodecode = None
12
13
14 def main():
15 if rhodecode is None:
16 # exit with success if we cannot import rhodecode !!
17 # this allows simply push to this repo even without
18 # rhodecode
19 sys.exit(0)
20
21 repo_path = os.path.abspath('.')
22 push_data = sys.stdin.readlines()
23 # os.environ is modified here by a subprocess call that
24 # runs git and later git executes this hook.
25 # Environ get's some additional info from rhodecode system
26 # like IP or username from basic-auth
27 handle_git_pre_receive(repo_path, push_data, os.environ)
28 sys.exit(0)
29
30 if __name__ == '__main__':
31 main()
@@ -22,6 +22,7 b' Users Guide'
22 usage/general
22 usage/general
23 usage/git_support
23 usage/git_support
24 usage/performance
24 usage/performance
25 usage/locking
25 usage/statistics
26 usage/statistics
26 usage/backup
27 usage/backup
27 usage/debugging
28 usage/debugging
@@ -138,7 +138,9 b' def make_map(config):'
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
139 action="repo_as_fork", conditions=dict(method=["PUT"],
139 action="repo_as_fork", conditions=dict(method=["PUT"],
140 function=check_repo))
140 function=check_repo))
141
141 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
142 action="repo_locking", conditions=dict(method=["PUT"],
143 function=check_repo))
142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
144 with rmap.submapper(path_prefix=ADMIN_PREFIX,
143 controller='admin/repos_groups') as m:
145 controller='admin/repos_groups') as m:
144 m.connect("repos_groups", "/repos_groups",
146 m.connect("repos_groups", "/repos_groups",
@@ -381,6 +381,7 b' class ReposController(BaseController):'
381 RepoModel().delete_stats(repo_name)
381 RepoModel().delete_stats(repo_name)
382 Session().commit()
382 Session().commit()
383 except Exception, e:
383 except Exception, e:
384 log.error(traceback.format_exc())
384 h.flash(_('An error occurred during deletion of repository stats'),
385 h.flash(_('An error occurred during deletion of repository stats'),
385 category='error')
386 category='error')
386 return redirect(url('edit_repo', repo_name=repo_name))
387 return redirect(url('edit_repo', repo_name=repo_name))
@@ -397,11 +398,32 b' class ReposController(BaseController):'
397 ScmModel().mark_for_invalidation(repo_name)
398 ScmModel().mark_for_invalidation(repo_name)
398 Session().commit()
399 Session().commit()
399 except Exception, e:
400 except Exception, e:
401 log.error(traceback.format_exc())
400 h.flash(_('An error occurred during cache invalidation'),
402 h.flash(_('An error occurred during cache invalidation'),
401 category='error')
403 category='error')
402 return redirect(url('edit_repo', repo_name=repo_name))
404 return redirect(url('edit_repo', repo_name=repo_name))
403
405
404 @HasPermissionAllDecorator('hg.admin')
406 @HasPermissionAllDecorator('hg.admin')
407 def repo_locking(self, repo_name):
408 """
409 Unlock repository when it is locked !
410
411 :param repo_name:
412 """
413
414 try:
415 repo = Repository.get_by_repo_name(repo_name)
416 if request.POST.get('set_lock'):
417 Repository.lock(repo, c.rhodecode_user.user_id)
418 elif request.POST.get('set_unlock'):
419 Repository.unlock(repo)
420 except Exception, e:
421 log.error(traceback.format_exc())
422 h.flash(_('An error occurred during unlocking'),
423 category='error')
424 return redirect(url('edit_repo', repo_name=repo_name))
425
426 @HasPermissionAllDecorator('hg.admin')
405 def repo_public_journal(self, repo_name):
427 def repo_public_journal(self, repo_name):
406 """
428 """
407 Set's this repository to be visible in public journal,
429 Set's this repository to be visible in public journal,
@@ -807,7 +807,7 b' class HasPermissionAnyMiddleware(object)'
807 return self.check_permissions()
807 return self.check_permissions()
808
808
809 def check_permissions(self):
809 def check_permissions(self):
810 log.debug('checking mercurial protocol '
810 log.debug('checking VCS protocol '
811 'permissions %s for user:%s repository:%s', self.user_perms,
811 'permissions %s for user:%s repository:%s', self.user_perms,
812 self.username, self.repo_name)
812 self.username, self.repo_name)
813 if self.required_perms.intersection(self.user_perms):
813 if self.required_perms.intersection(self.user_perms):
@@ -8,6 +8,7 b' import traceback'
8
8
9 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from webob.exc import HTTPClientError
11 from paste.httpheaders import WWW_AUTHENTICATE
12 from paste.httpheaders import WWW_AUTHENTICATE
12
13
13 from pylons import config, tmpl_context as c, request, session, url
14 from pylons import config, tmpl_context as c, request, session, url
@@ -17,15 +18,17 b' from pylons.templating import render_mak'
17
18
18 from rhodecode import __version__, BACKENDS
19 from rhodecode import __version__, BACKENDS
19
20
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
21 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
22 safe_str
21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 HasPermissionAnyMiddleware, CookieStoreWrapper
24 HasPermissionAnyMiddleware, CookieStoreWrapper
23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
25 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.model import meta
26 from rhodecode.model import meta
25
27
26 from rhodecode.model.db import Repository, RhodeCodeUi
28 from rhodecode.model.db import Repository, RhodeCodeUi, User
27 from rhodecode.model.notification import NotificationModel
29 from rhodecode.model.notification import NotificationModel
28 from rhodecode.model.scm import ScmModel
30 from rhodecode.model.scm import ScmModel
31 from rhodecode.model.meta import Session
29
32
30 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
31
34
@@ -159,6 +162,49 b' class BaseVCSController(object):'
159 return False
162 return False
160 return True
163 return True
161
164
165 def _check_locking_state(self, environ, action, repo, user_id):
166 """
167 Checks locking on this repository, if locking is enabled and lock is
168 present returns a tuple of make_lock, locked, locked_by.
169 make_lock can have 3 states None (do nothing) True, make lock
170 False release lock, This value is later propagated to hooks, which
171 do the locking. Think about this as signals passed to hooks what to do.
172
173 """
174 locked = False
175 make_lock = None
176 repo = Repository.get_by_repo_name(repo)
177 user = User.get(user_id)
178
179 # this is kind of hacky, but due to how mercurial handles client-server
180 # server see all operation on changeset; bookmarks, phases and
181 # obsolescence marker in different transaction, we don't want to check
182 # locking on those
183 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
184 locked_by = repo.locked
185 if repo and repo.enable_locking and not obsolete_call:
186 if action == 'push':
187 #check if it's already locked !, if it is compare users
188 user_id, _date = repo.locked
189 if user.user_id == user_id:
190 log.debug('Got push from user, now unlocking' % (user))
191 # unlock if we have push from user who locked
192 make_lock = False
193 else:
194 # we're not the same user who locked, ban with 423 !
195 locked = True
196 if action == 'pull':
197 if repo.locked[0] and repo.locked[1]:
198 locked = True
199 else:
200 log.debug('Setting lock on repo %s by %s' % (repo, user))
201 make_lock = True
202
203 else:
204 log.debug('Repository %s do not have locking enabled' % (repo))
205
206 return make_lock, locked, locked_by
207
162 def __call__(self, environ, start_response):
208 def __call__(self, environ, start_response):
163 start = time.time()
209 start = time.time()
164 try:
210 try:
@@ -307,37 +307,47 b' class DbManage(object):'
307 hooks1.ui_key = hooks1_key
307 hooks1.ui_key = hooks1_key
308 hooks1.ui_value = 'hg update >&2'
308 hooks1.ui_value = 'hg update >&2'
309 hooks1.ui_active = False
309 hooks1.ui_active = False
310 self.sa.add(hooks1)
310
311
311 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
312 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
312 hooks2_ = self.sa.query(RhodeCodeUi)\
313 hooks2_ = self.sa.query(RhodeCodeUi)\
313 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
314 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
314
315 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
315 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
316 hooks2.ui_section = 'hooks'
316 hooks2.ui_section = 'hooks'
317 hooks2.ui_key = hooks2_key
317 hooks2.ui_key = hooks2_key
318 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
318 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
319 self.sa.add(hooks2)
319
320
320 hooks3 = RhodeCodeUi()
321 hooks3 = RhodeCodeUi()
321 hooks3.ui_section = 'hooks'
322 hooks3.ui_section = 'hooks'
322 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
323 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
323 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
324 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
325 self.sa.add(hooks3)
324
326
325 hooks4 = RhodeCodeUi()
327 hooks4 = RhodeCodeUi()
326 hooks4.ui_section = 'hooks'
328 hooks4.ui_section = 'hooks'
327 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
329 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
328 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
330 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
331 self.sa.add(hooks4)
329
332
330 # For mercurial 1.7 set backward comapatibility with format
333 hooks5 = RhodeCodeUi()
331 dotencode_disable = RhodeCodeUi()
334 hooks5.ui_section = 'hooks'
332 dotencode_disable.ui_section = 'format'
335 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
333 dotencode_disable.ui_key = 'dotencode'
336 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
334 dotencode_disable.ui_value = 'false'
337 self.sa.add(hooks5)
338
339 hooks6 = RhodeCodeUi()
340 hooks6.ui_section = 'hooks'
341 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
342 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
343 self.sa.add(hooks6)
335
344
336 # enable largefiles
345 # enable largefiles
337 largefiles = RhodeCodeUi()
346 largefiles = RhodeCodeUi()
338 largefiles.ui_section = 'extensions'
347 largefiles.ui_section = 'extensions'
339 largefiles.ui_key = 'largefiles'
348 largefiles.ui_key = 'largefiles'
340 largefiles.ui_value = ''
349 largefiles.ui_value = ''
350 self.sa.add(largefiles)
341
351
342 # enable hgsubversion disabled by default
352 # enable hgsubversion disabled by default
343 hgsubversion = RhodeCodeUi()
353 hgsubversion = RhodeCodeUi()
@@ -345,6 +355,7 b' class DbManage(object):'
345 hgsubversion.ui_key = 'hgsubversion'
355 hgsubversion.ui_key = 'hgsubversion'
346 hgsubversion.ui_value = ''
356 hgsubversion.ui_value = ''
347 hgsubversion.ui_active = False
357 hgsubversion.ui_active = False
358 self.sa.add(hgsubversion)
348
359
349 # enable hggit disabled by default
360 # enable hggit disabled by default
350 hggit = RhodeCodeUi()
361 hggit = RhodeCodeUi()
@@ -352,13 +363,6 b' class DbManage(object):'
352 hggit.ui_key = 'hggit'
363 hggit.ui_key = 'hggit'
353 hggit.ui_value = ''
364 hggit.ui_value = ''
354 hggit.ui_active = False
365 hggit.ui_active = False
355
356 self.sa.add(hooks1)
357 self.sa.add(hooks2)
358 self.sa.add(hooks3)
359 self.sa.add(hooks4)
360 self.sa.add(largefiles)
361 self.sa.add(hgsubversion)
362 self.sa.add(hggit)
366 self.sa.add(hggit)
363
367
364 def create_ldap_options(self, skip_existing=False):
368 def create_ldap_options(self, skip_existing=False):
@@ -461,6 +465,11 b' class DbManage(object):'
461 paths.ui_key = '/'
465 paths.ui_key = '/'
462 paths.ui_value = path
466 paths.ui_value = path
463
467
468 phases = RhodeCodeUi()
469 phases.ui_section = 'phases'
470 phases.ui_key = 'publish'
471 phases.ui_value = False
472
464 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
473 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
465 sett2 = RhodeCodeSetting('title', 'RhodeCode')
474 sett2 = RhodeCodeSetting('title', 'RhodeCode')
466 sett3 = RhodeCodeSetting('ga_code', '')
475 sett3 = RhodeCodeSetting('ga_code', '')
@@ -23,6 +23,8 b''
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 from webob.exc import HTTPClientError
27
26
28
27 class LdapUsernameError(Exception):
29 class LdapUsernameError(Exception):
28 pass
30 pass
@@ -53,4 +55,17 b' class UsersGroupsAssignedException(Excep'
53
55
54
56
55 class StatusChangeOnClosedPullRequestError(Exception):
57 class StatusChangeOnClosedPullRequestError(Exception):
56 pass No newline at end of file
58 pass
59
60
61 class HTTPLockedRC(HTTPClientError):
62 """
63 Special Exception For locked Repos in RhodeCode
64 """
65 code = 423
66 title = explanation = 'Repository Locked'
67
68 def __init__(self, reponame, username, *args, **kwargs):
69 self.title = self.explanation = ('Repository `%s` locked by '
70 'user `%s`' % (reponame, username))
71 super(HTTPLockedRC, self).__init__(*args, **kwargs)
@@ -41,7 +41,7 b' from webhelpers.html.tags import _set_in'
41 from rhodecode.lib.annotate import annotate_highlight
41 from rhodecode.lib.annotate import annotate_highlight
42 from rhodecode.lib.utils import repo_name_slug
42 from rhodecode.lib.utils import repo_name_slug
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 get_changeset_safe
44 get_changeset_safe, datetime_to_time, time_to_datetime
45 from rhodecode.lib.markup_renderer import MarkupRenderer
45 from rhodecode.lib.markup_renderer import MarkupRenderer
46 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
46 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
@@ -439,6 +439,19 b' def person(author):'
439 return _author
439 return _author
440
440
441
441
442 def person_by_id(id_):
443 # attr to return from fetched user
444 person_getter = lambda usr: usr.username
445
446 #maybe it's an ID ?
447 if str(id_).isdigit() or isinstance(id_, int):
448 id_ = int(id_)
449 user = User.get(id_)
450 if user is not None:
451 return person_getter(user)
452 return id_
453
454
442 def desc_stylize(value):
455 def desc_stylize(value):
443 """
456 """
444 converts tags from value into html equivalent
457 converts tags from value into html equivalent
@@ -34,6 +34,9 b' from rhodecode.lib import helpers as h'
34 from rhodecode.lib.utils import action_logger
34 from rhodecode.lib.utils import action_logger
35 from rhodecode.lib.vcs.backends.base import EmptyChangeset
35 from rhodecode.lib.vcs.backends.base import EmptyChangeset
36 from rhodecode.lib.compat import json
36 from rhodecode.lib.compat import json
37 from rhodecode.model.db import Repository, User
38 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.lib.exceptions import HTTPLockedRC
37
40
38
41
39 def _get_scm_size(alias, root_path):
42 def _get_scm_size(alias, root_path):
@@ -84,6 +87,59 b' def repo_size(ui, repo, hooktype=None, *'
84 sys.stdout.write(msg)
87 sys.stdout.write(msg)
85
88
86
89
90 def pre_push(ui, repo, **kwargs):
91 # pre push function, currently used to ban pushing when
92 # repository is locked
93 try:
94 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
95 except:
96 rc_extras = {}
97 extras = dict(repo.ui.configitems('rhodecode_extras'))
98
99 if 'username' in extras:
100 username = extras['username']
101 repository = extras['repository']
102 scm = extras['scm']
103 locked_by = extras['locked_by']
104 elif 'username' in rc_extras:
105 username = rc_extras['username']
106 repository = rc_extras['repository']
107 scm = rc_extras['scm']
108 locked_by = rc_extras['locked_by']
109 else:
110 raise Exception('Missing data in repo.ui and os.environ')
111
112 usr = User.get_by_username(username)
113
114 if locked_by[0] and usr.user_id != int(locked_by[0]):
115 raise HTTPLockedRC(username, repository)
116
117
118 def pre_pull(ui, repo, **kwargs):
119 # pre push function, currently used to ban pushing when
120 # repository is locked
121 try:
122 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
123 except:
124 rc_extras = {}
125 extras = dict(repo.ui.configitems('rhodecode_extras'))
126 if 'username' in extras:
127 username = extras['username']
128 repository = extras['repository']
129 scm = extras['scm']
130 locked_by = extras['locked_by']
131 elif 'username' in rc_extras:
132 username = rc_extras['username']
133 repository = rc_extras['repository']
134 scm = rc_extras['scm']
135 locked_by = rc_extras['locked_by']
136 else:
137 raise Exception('Missing data in repo.ui and os.environ')
138
139 if locked_by[0]:
140 raise HTTPLockedRC(username, repository)
141
142
87 def log_pull_action(ui, repo, **kwargs):
143 def log_pull_action(ui, repo, **kwargs):
88 """
144 """
89 Logs user last pull action
145 Logs user last pull action
@@ -100,15 +156,17 b' def log_pull_action(ui, repo, **kwargs):'
100 username = extras['username']
156 username = extras['username']
101 repository = extras['repository']
157 repository = extras['repository']
102 scm = extras['scm']
158 scm = extras['scm']
159 make_lock = extras['make_lock']
103 elif 'username' in rc_extras:
160 elif 'username' in rc_extras:
104 username = rc_extras['username']
161 username = rc_extras['username']
105 repository = rc_extras['repository']
162 repository = rc_extras['repository']
106 scm = rc_extras['scm']
163 scm = rc_extras['scm']
164 make_lock = rc_extras['make_lock']
107 else:
165 else:
108 raise Exception('Missing data in repo.ui and os.environ')
166 raise Exception('Missing data in repo.ui and os.environ')
109
167 user = User.get_by_username(username)
110 action = 'pull'
168 action = 'pull'
111 action_logger(username, action, repository, extras['ip'], commit=True)
169 action_logger(user, action, repository, extras['ip'], commit=True)
112 # extension hook call
170 # extension hook call
113 from rhodecode import EXTENSIONS
171 from rhodecode import EXTENSIONS
114 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
172 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
@@ -117,6 +175,12 b' def log_pull_action(ui, repo, **kwargs):'
117 kw = {}
175 kw = {}
118 kw.update(extras)
176 kw.update(extras)
119 callback(**kw)
177 callback(**kw)
178
179 if make_lock is True:
180 Repository.lock(Repository.get_by_repo_name(repository), user.user_id)
181 #msg = 'Made lock on repo `%s`' % repository
182 #sys.stdout.write(msg)
183
120 return 0
184 return 0
121
185
122
186
@@ -138,10 +202,12 b' def log_push_action(ui, repo, **kwargs):'
138 username = extras['username']
202 username = extras['username']
139 repository = extras['repository']
203 repository = extras['repository']
140 scm = extras['scm']
204 scm = extras['scm']
205 make_lock = extras['make_lock']
141 elif 'username' in rc_extras:
206 elif 'username' in rc_extras:
142 username = rc_extras['username']
207 username = rc_extras['username']
143 repository = rc_extras['repository']
208 repository = rc_extras['repository']
144 scm = rc_extras['scm']
209 scm = rc_extras['scm']
210 make_lock = rc_extras['make_lock']
145 else:
211 else:
146 raise Exception('Missing data in repo.ui and os.environ')
212 raise Exception('Missing data in repo.ui and os.environ')
147
213
@@ -179,6 +245,12 b' def log_push_action(ui, repo, **kwargs):'
179 kw = {'pushed_revs': revs}
245 kw = {'pushed_revs': revs}
180 kw.update(extras)
246 kw.update(extras)
181 callback(**kw)
247 callback(**kw)
248
249 if make_lock is False:
250 Repository.unlock(Repository.get_by_repo_name(repository))
251 msg = 'Released lock on repo `%s`\n' % repository
252 sys.stdout.write(msg)
253
182 return 0
254 return 0
183
255
184
256
@@ -219,8 +291,13 b' def log_create_repository(repository_dic'
219
291
220 return 0
292 return 0
221
293
294 handle_git_pre_receive = (lambda repo_path, revs, env:
295 handle_git_receive(repo_path, revs, env, hook_type='pre'))
296 handle_git_post_receive = (lambda repo_path, revs, env:
297 handle_git_receive(repo_path, revs, env, hook_type='post'))
222
298
223 def handle_git_post_receive(repo_path, revs, env):
299
300 def handle_git_receive(repo_path, revs, env, hook_type='post'):
224 """
301 """
225 A really hacky method that is runned by git post-receive hook and logs
302 A really hacky method that is runned by git post-receive hook and logs
226 an push action together with pushed revisions. It's executed by subprocess
303 an push action together with pushed revisions. It's executed by subprocess
@@ -240,7 +317,6 b' def handle_git_post_receive(repo_path, r'
240 from rhodecode.model import init_model
317 from rhodecode.model import init_model
241 from rhodecode.model.db import RhodeCodeUi
318 from rhodecode.model.db import RhodeCodeUi
242 from rhodecode.lib.utils import make_ui
319 from rhodecode.lib.utils import make_ui
243 from rhodecode.model.db import Repository
244
320
245 path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE'])
321 path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE'])
246 conf = appconfig('config:%s' % ini_name, relative_to=path)
322 conf = appconfig('config:%s' % ini_name, relative_to=path)
@@ -255,20 +331,18 b' def handle_git_post_receive(repo_path, r'
255 repo_path = repo_path[:-4]
331 repo_path = repo_path[:-4]
256 repo = Repository.get_by_full_path(repo_path)
332 repo = Repository.get_by_full_path(repo_path)
257 _hooks = dict(baseui.configitems('hooks')) or {}
333 _hooks = dict(baseui.configitems('hooks')) or {}
258 # if push hook is enabled via web interface
259 if repo and _hooks.get(RhodeCodeUi.HOOK_PUSH):
260
334
261 extras = {
335 extras = json.loads(env['RHODECODE_EXTRAS'])
262 'username': env['RHODECODE_USER'],
336 for k, v in extras.items():
263 'repository': repo.repo_name,
337 baseui.setconfig('rhodecode_extras', k, v)
264 'scm': 'git',
338 repo = repo.scm_instance
265 'action': 'push',
339 repo.ui = baseui
266 'ip': env['RHODECODE_CONFIG_IP'],
340
267 }
341 if hook_type == 'pre':
268 for k, v in extras.items():
342 pre_push(baseui, repo)
269 baseui.setconfig('rhodecode_extras', k, v)
343
270 repo = repo.scm_instance
344 # if push hook is enabled via web interface
271 repo.ui = baseui
345 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
272
346
273 rev_data = []
347 rev_data = []
274 for l in revs:
348 for l in revs:
@@ -41,7 +41,7 b' class GitRepository(object):'
41 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
41 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
42 commands = ['git-upload-pack', 'git-receive-pack']
42 commands = ['git-upload-pack', 'git-receive-pack']
43
43
44 def __init__(self, repo_name, content_path, username):
44 def __init__(self, repo_name, content_path, extras):
45 files = set([f.lower() for f in os.listdir(content_path)])
45 files = set([f.lower() for f in os.listdir(content_path)])
46 if not (self.git_folder_signature.intersection(files)
46 if not (self.git_folder_signature.intersection(files)
47 == self.git_folder_signature):
47 == self.git_folder_signature):
@@ -50,7 +50,7 b' class GitRepository(object):'
50 self.valid_accepts = ['application/x-%s-result' %
50 self.valid_accepts = ['application/x-%s-result' %
51 c for c in self.commands]
51 c for c in self.commands]
52 self.repo_name = repo_name
52 self.repo_name = repo_name
53 self.username = username
53 self.extras = extras
54
54
55 def _get_fixedpath(self, path):
55 def _get_fixedpath(self, path):
56 """
56 """
@@ -67,7 +67,7 b' class GitRepository(object):'
67 HTTP /info/refs request.
67 HTTP /info/refs request.
68 """
68 """
69
69
70 git_command = request.GET['service']
70 git_command = request.GET.get('service')
71 if git_command not in self.commands:
71 if git_command not in self.commands:
72 log.debug('command %s not allowed' % git_command)
72 log.debug('command %s not allowed' % git_command)
73 return exc.HTTPMethodNotAllowed()
73 return exc.HTTPMethodNotAllowed()
@@ -119,9 +119,8 b' class GitRepository(object):'
119 try:
119 try:
120 gitenv = os.environ
120 gitenv = os.environ
121 from rhodecode import CONFIG
121 from rhodecode import CONFIG
122 from rhodecode.lib.base import _get_ip_addr
122 from rhodecode.lib.compat import json
123 gitenv['RHODECODE_USER'] = self.username
123 gitenv['RHODECODE_EXTRAS'] = json.dumps(self.extras)
124 gitenv['RHODECODE_CONFIG_IP'] = _get_ip_addr(environ)
125 # forget all configs
124 # forget all configs
126 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
125 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
127 # we need current .ini file used to later initialize rhodecode
126 # we need current .ini file used to later initialize rhodecode
@@ -174,7 +173,7 b' class GitRepository(object):'
174
173
175 class GitDirectory(object):
174 class GitDirectory(object):
176
175
177 def __init__(self, repo_root, repo_name, username):
176 def __init__(self, repo_root, repo_name, extras):
178 repo_location = os.path.join(repo_root, repo_name)
177 repo_location = os.path.join(repo_root, repo_name)
179 if not os.path.isdir(repo_location):
178 if not os.path.isdir(repo_location):
180 raise OSError(repo_location)
179 raise OSError(repo_location)
@@ -182,12 +181,12 b' class GitDirectory(object):'
182 self.content_path = repo_location
181 self.content_path = repo_location
183 self.repo_name = repo_name
182 self.repo_name = repo_name
184 self.repo_location = repo_location
183 self.repo_location = repo_location
185 self.username = username
184 self.extras = extras
186
185
187 def __call__(self, environ, start_response):
186 def __call__(self, environ, start_response):
188 content_path = self.content_path
187 content_path = self.content_path
189 try:
188 try:
190 app = GitRepository(self.repo_name, content_path, self.username)
189 app = GitRepository(self.repo_name, content_path, self.extras)
191 except (AssertionError, OSError):
190 except (AssertionError, OSError):
192 if os.path.isdir(os.path.join(content_path, '.git')):
191 if os.path.isdir(os.path.join(content_path, '.git')):
193 app = GitRepository(self.repo_name,
192 app = GitRepository(self.repo_name,
@@ -198,5 +197,5 b' class GitDirectory(object):'
198 return app(environ, start_response)
197 return app(environ, start_response)
199
198
200
199
201 def make_wsgi_app(repo_name, repo_root, username):
200 def make_wsgi_app(repo_name, repo_root, extras):
202 return GitDirectory(repo_root, repo_name, username)
201 return GitDirectory(repo_root, repo_name, extras)
@@ -31,6 +31,8 b' import traceback'
31
31
32 from dulwich import server as dulserver
32 from dulwich import server as dulserver
33 from dulwich.web import LimitedInputFilter, GunzipFilter
33 from dulwich.web import LimitedInputFilter, GunzipFilter
34 from rhodecode.lib.exceptions import HTTPLockedRC
35 from rhodecode.lib.hooks import pre_pull
34
36
35
37
36 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
38 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
@@ -102,11 +104,11 b' def is_git(environ):'
102 class SimpleGit(BaseVCSController):
104 class SimpleGit(BaseVCSController):
103
105
104 def _handle_request(self, environ, start_response):
106 def _handle_request(self, environ, start_response):
105
106 if not is_git(environ):
107 if not is_git(environ):
107 return self.application(environ, start_response)
108 return self.application(environ, start_response)
108 if not self._check_ssl(environ, start_response):
109 if not self._check_ssl(environ, start_response):
109 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
110 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
111
110 ipaddr = self._get_ip_addr(environ)
112 ipaddr = self._get_ip_addr(environ)
111 username = None
113 username = None
112 self._git_first_op = False
114 self._git_first_op = False
@@ -184,21 +186,39 b' class SimpleGit(BaseVCSController):'
184 if perm is not True:
186 if perm is not True:
185 return HTTPForbidden()(environ, start_response)
187 return HTTPForbidden()(environ, start_response)
186
188
189 # extras are injected into UI object and later available
190 # in hooks executed by rhodecode
187 extras = {
191 extras = {
188 'ip': ipaddr,
192 'ip': ipaddr,
189 'username': username,
193 'username': username,
190 'action': action,
194 'action': action,
191 'repository': repo_name,
195 'repository': repo_name,
192 'scm': 'git',
196 'scm': 'git',
197 'make_lock': None,
198 'locked_by': [None, None]
193 }
199 }
194 # set the environ variables for this request
200
195 os.environ['RC_SCM_DATA'] = json.dumps(extras)
196 #===================================================================
201 #===================================================================
197 # GIT REQUEST HANDLING
202 # GIT REQUEST HANDLING
198 #===================================================================
203 #===================================================================
199 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
204 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
200 log.debug('Repository path is %s' % repo_path)
205 log.debug('Repository path is %s' % repo_path)
201
206
207 # CHECK LOCKING only if it's not ANONYMOUS USER
208 if username != User.DEFAULT_USER:
209 log.debug('Checking locking on repository')
210 (make_lock,
211 locked,
212 locked_by) = self._check_locking_state(
213 environ=environ, action=action,
214 repo=repo_name, user_id=user.user_id
215 )
216 # store the make_lock for later evaluation in hooks
217 extras.update({'make_lock': make_lock,
218 'locked_by': locked_by})
219 # set the environ variables for this request
220 os.environ['RC_SCM_DATA'] = json.dumps(extras)
221 log.debug('HOOKS extras is %s' % extras)
202 baseui = make_ui('db')
222 baseui = make_ui('db')
203 self.__inject_extras(repo_path, baseui, extras)
223 self.__inject_extras(repo_path, baseui, extras)
204
224
@@ -209,13 +229,16 b' class SimpleGit(BaseVCSController):'
209 self._handle_githooks(repo_name, action, baseui, environ)
229 self._handle_githooks(repo_name, action, baseui, environ)
210
230
211 log.info('%s action on GIT repo "%s"' % (action, repo_name))
231 log.info('%s action on GIT repo "%s"' % (action, repo_name))
212 app = self.__make_app(repo_name, repo_path, username)
232 app = self.__make_app(repo_name, repo_path, extras)
213 return app(environ, start_response)
233 return app(environ, start_response)
234 except HTTPLockedRC, e:
235 log.debug('Repositry LOCKED ret code 423!')
236 return e(environ, start_response)
214 except Exception:
237 except Exception:
215 log.error(traceback.format_exc())
238 log.error(traceback.format_exc())
216 return HTTPInternalServerError()(environ, start_response)
239 return HTTPInternalServerError()(environ, start_response)
217
240
218 def __make_app(self, repo_name, repo_path, username):
241 def __make_app(self, repo_name, repo_path, extras):
219 """
242 """
220 Make an wsgi application using dulserver
243 Make an wsgi application using dulserver
221
244
@@ -227,7 +250,7 b' class SimpleGit(BaseVCSController):'
227 app = make_wsgi_app(
250 app = make_wsgi_app(
228 repo_root=safe_str(self.basepath),
251 repo_root=safe_str(self.basepath),
229 repo_name=repo_name,
252 repo_name=repo_name,
230 username=username,
253 extras=extras,
231 )
254 )
232 app = GunzipFilter(LimitedInputFilter(app))
255 app = GunzipFilter(LimitedInputFilter(app))
233 return app
256 return app
@@ -279,6 +302,7 b' class SimpleGit(BaseVCSController):'
279 """
302 """
280 from rhodecode.lib.hooks import log_pull_action
303 from rhodecode.lib.hooks import log_pull_action
281 service = environ['QUERY_STRING'].split('=')
304 service = environ['QUERY_STRING'].split('=')
305
282 if len(service) < 2:
306 if len(service) < 2:
283 return
307 return
284
308
@@ -288,6 +312,9 b' class SimpleGit(BaseVCSController):'
288 _repo._repo.ui = baseui
312 _repo._repo.ui = baseui
289
313
290 _hooks = dict(baseui.configitems('hooks')) or {}
314 _hooks = dict(baseui.configitems('hooks')) or {}
315 if action == 'pull':
316 # stupid git, emulate pre-pull hook !
317 pre_pull(ui=baseui, repo=_repo._repo)
291 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
318 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
292 log_pull_action(ui=baseui, repo=_repo._repo)
319 log_pull_action(ui=baseui, repo=_repo._repo)
293
320
@@ -42,6 +42,7 b' from rhodecode.lib.auth import get_conta'
42 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
42 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
43 from rhodecode.lib.compat import json
43 from rhodecode.lib.compat import json
44 from rhodecode.model.db import User
44 from rhodecode.model.db import User
45 from rhodecode.lib.exceptions import HTTPLockedRC
45
46
46
47
47 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
@@ -157,15 +158,31 b' class SimpleHg(BaseVCSController):'
157 'action': action,
158 'action': action,
158 'repository': repo_name,
159 'repository': repo_name,
159 'scm': 'hg',
160 'scm': 'hg',
161 'make_lock': None,
162 'locked_by': [None, None]
160 }
163 }
161 # set the environ variables for this request
162 os.environ['RC_SCM_DATA'] = json.dumps(extras)
163 #======================================================================
164 #======================================================================
164 # MERCURIAL REQUEST HANDLING
165 # MERCURIAL REQUEST HANDLING
165 #======================================================================
166 #======================================================================
166 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
167 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
167 log.debug('Repository path is %s' % repo_path)
168 log.debug('Repository path is %s' % repo_path)
168
169
170 # CHECK LOCKING only if it's not ANONYMOUS USER
171 if username != User.DEFAULT_USER:
172 log.debug('Checking locking on repository')
173 (make_lock,
174 locked,
175 locked_by) = self._check_locking_state(
176 environ=environ, action=action,
177 repo=repo_name, user_id=user.user_id
178 )
179 # store the make_lock for later evaluation in hooks
180 extras.update({'make_lock': make_lock,
181 'locked_by': locked_by})
182
183 # set the environ variables for this request
184 os.environ['RC_SCM_DATA'] = json.dumps(extras)
185 log.debug('HOOKS extras is %s' % extras)
169 baseui = make_ui('db')
186 baseui = make_ui('db')
170 self.__inject_extras(repo_path, baseui, extras)
187 self.__inject_extras(repo_path, baseui, extras)
171
188
@@ -179,6 +196,9 b' class SimpleHg(BaseVCSController):'
179 except RepoError, e:
196 except RepoError, e:
180 if str(e).find('not found') != -1:
197 if str(e).find('not found') != -1:
181 return HTTPNotFound()(environ, start_response)
198 return HTTPNotFound()(environ, start_response)
199 except HTTPLockedRC, e:
200 log.debug('Repositry LOCKED ret code 423!')
201 return e(environ, start_response)
182 except Exception:
202 except Exception:
183 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
184 return HTTPInternalServerError()(environ, start_response)
204 return HTTPInternalServerError()(environ, start_response)
@@ -25,7 +25,7 b''
25
25
26 import re
26 import re
27 import time
27 import time
28 from datetime import datetime
28 import datetime
29 from pylons.i18n.translation import _, ungettext
29 from pylons.i18n.translation import _, ungettext
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31
31
@@ -300,7 +300,7 b' def age(prevdate):'
300 deltas = {}
300 deltas = {}
301
301
302 # Get date parts deltas
302 # Get date parts deltas
303 now = datetime.now()
303 now = datetime.datetime.now()
304 for part in order:
304 for part in order:
305 deltas[part] = getattr(now, part) - getattr(prevdate, part)
305 deltas[part] = getattr(now, part) - getattr(prevdate, part)
306
306
@@ -435,6 +435,15 b' def datetime_to_time(dt):'
435 return time.mktime(dt.timetuple())
435 return time.mktime(dt.timetuple())
436
436
437
437
438 def time_to_datetime(tm):
439 if tm:
440 if isinstance(tm, basestring):
441 try:
442 tm = float(tm)
443 except ValueError:
444 return
445 return datetime.datetime.fromtimestamp(tm)
446
438 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
447 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
439
448
440
449
@@ -28,6 +28,7 b' import logging'
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 from collections import defaultdict
32 from collections import defaultdict
32
33
33 from sqlalchemy import *
34 from sqlalchemy import *
@@ -232,7 +233,9 b' class RhodeCodeUi(Base, BaseModel):'
232 HOOK_UPDATE = 'changegroup.update'
233 HOOK_UPDATE = 'changegroup.update'
233 HOOK_REPO_SIZE = 'changegroup.repo_size'
234 HOOK_REPO_SIZE = 'changegroup.repo_size'
234 HOOK_PUSH = 'changegroup.push_logger'
235 HOOK_PUSH = 'changegroup.push_logger'
235 HOOK_PULL = 'preoutgoing.pull_logger'
236 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
237 HOOK_PULL = 'outgoing.pull_logger'
238 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
236
239
237 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
240 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
238 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
241 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -247,17 +250,17 b' class RhodeCodeUi(Base, BaseModel):'
247 @classmethod
250 @classmethod
248 def get_builtin_hooks(cls):
251 def get_builtin_hooks(cls):
249 q = cls.query()
252 q = cls.query()
250 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
253 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
251 cls.HOOK_REPO_SIZE,
254 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
252 cls.HOOK_PUSH, cls.HOOK_PULL]))
255 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
253 return q.all()
256 return q.all()
254
257
255 @classmethod
258 @classmethod
256 def get_custom_hooks(cls):
259 def get_custom_hooks(cls):
257 q = cls.query()
260 q = cls.query()
258 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
261 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
259 cls.HOOK_REPO_SIZE,
262 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
260 cls.HOOK_PUSH, cls.HOOK_PULL]))
263 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
261 q = q.filter(cls.ui_section == 'hooks')
264 q = q.filter(cls.ui_section == 'hooks')
262 return q.all()
265 return q.all()
263
266
@@ -280,9 +283,13 b' class User(Base, BaseModel):'
280 __tablename__ = 'users'
283 __tablename__ = 'users'
281 __table_args__ = (
284 __table_args__ = (
282 UniqueConstraint('username'), UniqueConstraint('email'),
285 UniqueConstraint('username'), UniqueConstraint('email'),
286 Index('u_username_idx', 'username'),
287 Index('u_email_idx', 'email'),
283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
288 {'extend_existing': True, 'mysql_engine': 'InnoDB',
284 'mysql_charset': 'utf8'}
289 'mysql_charset': 'utf8'}
285 )
290 )
291 DEFAULT_USER = 'default'
292
286 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
293 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
287 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
288 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
@@ -572,6 +579,7 b' class Repository(Base, BaseModel):'
572 __tablename__ = 'repositories'
579 __tablename__ = 'repositories'
573 __table_args__ = (
580 __table_args__ = (
574 UniqueConstraint('repo_name'),
581 UniqueConstraint('repo_name'),
582 Index('r_repo_name_idx', 'repo_name'),
575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
576 'mysql_charset': 'utf8'},
584 'mysql_charset': 'utf8'},
577 )
585 )
@@ -587,6 +595,8 b' class Repository(Base, BaseModel):'
587 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
595 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
588 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
596 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
589 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
597 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
598 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
599 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
590
600
591 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
601 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
592 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
602 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
@@ -617,6 +627,21 b' class Repository(Base, BaseModel):'
617 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
627 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
618 self.repo_name)
628 self.repo_name)
619
629
630 @hybrid_property
631 def locked(self):
632 # always should return [user_id, timelocked]
633 if self._locked:
634 _lock_info = self._locked.split(':')
635 return int(_lock_info[0]), _lock_info[1]
636 return [None, None]
637
638 @locked.setter
639 def locked(self, val):
640 if val and isinstance(val, (list, tuple)):
641 self._locked = ':'.join(map(str, val))
642 else:
643 self._locked = None
644
620 @classmethod
645 @classmethod
621 def url_sep(cls):
646 def url_sep(cls):
622 return URL_SEP
647 return URL_SEP
@@ -744,7 +769,7 b' class Repository(Base, BaseModel):'
744 if ui_.ui_key == 'push_ssl':
769 if ui_.ui_key == 'push_ssl':
745 # force set push_ssl requirement to False, rhodecode
770 # force set push_ssl requirement to False, rhodecode
746 # handles that
771 # handles that
747 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
772 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
748
773
749 return baseui
774 return baseui
750
775
@@ -793,6 +818,18 b' class Repository(Base, BaseModel):'
793
818
794 return data
819 return data
795
820
821 @classmethod
822 def lock(cls, repo, user_id):
823 repo.locked = [user_id, time.time()]
824 Session().add(repo)
825 Session().commit()
826
827 @classmethod
828 def unlock(cls, repo):
829 repo.locked = None
830 Session().add(repo)
831 Session().commit()
832
796 #==========================================================================
833 #==========================================================================
797 # SCM PROPERTIES
834 # SCM PROPERTIES
798 #==========================================================================
835 #==========================================================================
@@ -182,6 +182,7 b' def RepoForm(edit=False, old_data={}, su'
182 private = v.StringBoolean(if_missing=False)
182 private = v.StringBoolean(if_missing=False)
183 enable_statistics = v.StringBoolean(if_missing=False)
183 enable_statistics = v.StringBoolean(if_missing=False)
184 enable_downloads = v.StringBoolean(if_missing=False)
184 enable_downloads = v.StringBoolean(if_missing=False)
185 enable_locking = v.StringBoolean(if_missing=False)
185 landing_rev = v.OneOf(landing_revs, hideList=True)
186 landing_rev = v.OneOf(landing_revs, hideList=True)
186
187
187 if edit:
188 if edit:
@@ -265,7 +266,7 b' def ApplicationUiSettingsForm():'
265 hooks_changegroup_update = v.StringBoolean(if_missing=False)
266 hooks_changegroup_update = v.StringBoolean(if_missing=False)
266 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
267 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
267 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
268 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
268 hooks_preoutgoing_pull_logger = v.StringBoolean(if_missing=False)
269 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
269
270
270 extensions_largefiles = v.StringBoolean(if_missing=False)
271 extensions_largefiles = v.StringBoolean(if_missing=False)
271 extensions_hgsubversion = v.StringBoolean(if_missing=False)
272 extensions_hgsubversion = v.StringBoolean(if_missing=False)
@@ -571,34 +571,41 b' class ScmModel(BaseModel):'
571 if not os.path.isdir(loc):
571 if not os.path.isdir(loc):
572 os.makedirs(loc)
572 os.makedirs(loc)
573
573
574 tmpl = pkg_resources.resource_string(
574 tmpl_post = pkg_resources.resource_string(
575 'rhodecode', jn('config', 'post_receive_tmpl.py')
575 'rhodecode', jn('config', 'post_receive_tmpl.py')
576 )
576 )
577 tmpl_pre = pkg_resources.resource_string(
578 'rhodecode', jn('config', 'pre_receive_tmpl.py')
579 )
577
580
578 _hook_file = jn(loc, 'post-receive')
581 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
579 _rhodecode_hook = False
582 _hook_file = jn(loc, '%s-receive' % h_type)
580 log.debug('Installing git hook in repo %s' % repo)
583 _rhodecode_hook = False
581 if os.path.exists(_hook_file):
584 log.debug('Installing git hook in repo %s' % repo)
582 # let's take a look at this hook, maybe it's rhodecode ?
585 if os.path.exists(_hook_file):
583 log.debug('hook exists, checking if it is from rhodecode')
586 # let's take a look at this hook, maybe it's rhodecode ?
584 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
587 log.debug('hook exists, checking if it is from rhodecode')
585 with open(_hook_file, 'rb') as f:
588 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
586 data = f.read()
589 with open(_hook_file, 'rb') as f:
587 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
590 data = f.read()
588 % 'RC_HOOK_VER').search(data)
591 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
589 if matches:
592 % 'RC_HOOK_VER').search(data)
590 try:
593 if matches:
591 ver = matches.groups()[0]
594 try:
592 log.debug('got %s it is rhodecode' % (ver))
595 ver = matches.groups()[0]
593 _rhodecode_hook = True
596 log.debug('got %s it is rhodecode' % (ver))
594 except:
597 _rhodecode_hook = True
595 log.error(traceback.format_exc())
598 except:
599 log.error(traceback.format_exc())
600 else:
601 # there is no hook in this dir, so we want to create one
602 _rhodecode_hook = True
596
603
597 if _rhodecode_hook or force_create:
604 if _rhodecode_hook or force_create:
598 log.debug('writing hook file !')
605 log.debug('writing %s hook file !' % h_type)
599 with open(_hook_file, 'wb') as f:
606 with open(_hook_file, 'wb') as f:
600 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
607 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
601 f.write(tmpl)
608 f.write(tmpl)
602 os.chmod(_hook_file, 0755)
609 os.chmod(_hook_file, 0755)
603 else:
610 else:
604 log.debug('skipping writing hook file')
611 log.debug('skipping writing hook file')
@@ -108,6 +108,15 b''
108 </div>
108 </div>
109 </div>
109 </div>
110 <div class="field">
110 <div class="field">
111 <div class="label label-checkbox">
112 <label for="enable_locking">${_('Enable locking')}:</label>
113 </div>
114 <div class="checkboxes">
115 ${h.checkbox('enable_locking',value="True")}
116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
117 </div>
118 </div>
119 <div class="field">
111 <div class="label">
120 <div class="label">
112 <label for="user">${_('Owner')}:</label>
121 <label for="user">${_('Owner')}:</label>
113 </div>
122 </div>
@@ -196,26 +205,31 b''
196 </div>
205 </div>
197 <div class="field" style="border:none;color:#888">
206 <div class="field" style="border:none;color:#888">
198 <ul>
207 <ul>
199 <li>${_('''All actions made on this repository will be accessible to everyone in public journal''')}
208 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
200 </li>
209 </li>
201 </ul>
210 </ul>
202 </div>
211 </div>
203 </div>
212 </div>
204 ${h.end_form()}
213 ${h.end_form()}
205
214
206 <h3>${_('Delete')}</h3>
215 <h3>${_('Locking')}</h3>
207 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
216 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
208 <div class="form">
217 <div class="form">
209 <div class="fields">
218 <div class="fields">
210 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
219 %if c.repo_info.locked[0]:
220 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
221 ${'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])))}
222 %else:
223 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
224 ${_('Repository is not locked')}
225 %endif
211 </div>
226 </div>
212 <div class="field" style="border:none;color:#888">
227 <div class="field" style="border:none;color:#888">
213 <ul>
228 <ul>
214 <li>${_('''This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems.
229 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
215 If you need fully delete it from filesystem please do it manually''')}
216 </li>
230 </li>
217 </ul>
231 </ul>
218 </div>
232 </div>
219 </div>
233 </div>
220 ${h.end_form()}
234 ${h.end_form()}
221
235
@@ -231,10 +245,24 b''
231 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
245 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
232 </ul>
246 </ul>
233 </div>
247 </div>
234 </div>
248 </div>
235 ${h.end_form()}
249 ${h.end_form()}
236
250
251 <h3>${_('Delete')}</h3>
252 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
253 <div class="form">
254 <div class="fields">
255 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
256 </div>
257 <div class="field" style="border:none;color:#888">
258 <ul>
259 <li>${_('''This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems.
260 If you need fully delete it from filesystem please do it manually''')}
261 </li>
262 </ul>
263 </div>
264 </div>
265 ${h.end_form()}
237 </div>
266 </div>
238
267
239
240 </%def>
268 </%def>
@@ -211,8 +211,8 b''
211 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
211 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
212 </div>
212 </div>
213 <div class="checkbox">
213 <div class="checkbox">
214 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
214 ${h.checkbox('hooks_outgoing_pull_logger','True')}
215 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
215 <label for="hooks_outgoing_pull_logger">${_('Log user pull commands')}</label>
216 </div>
216 </div>
217 </div>
217 </div>
218 <div class="input" style="margin-top:10px">
218 <div class="input" style="margin-top:10px">
General Comments 0
You need to be logged in to leave comments. Login now