##// END OF EJS Templates
hooks: expose refs on push....
marcink -
r1755:329954e2 default
parent child Browse files
Show More
@@ -1,619 +1,620 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
22 Database creation, and setup module for RhodeCode Enterprise. Used for creation
23 of database as well as for migration operations
23 of database as well as for migration operations
24 """
24 """
25
25
26 import os
26 import os
27 import sys
27 import sys
28 import time
28 import time
29 import uuid
29 import uuid
30 import logging
30 import logging
31 import getpass
31 import getpass
32 from os.path import dirname as dn, join as jn
32 from os.path import dirname as dn, join as jn
33
33
34 from sqlalchemy.engine import create_engine
34 from sqlalchemy.engine import create_engine
35
35
36 from rhodecode import __dbversion__
36 from rhodecode import __dbversion__
37 from rhodecode.model import init_model
37 from rhodecode.model import init_model
38 from rhodecode.model.user import UserModel
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.db import (
39 from rhodecode.model.db import (
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
40 User, Permission, RhodeCodeUi, RhodeCodeSetting, UserToPerm,
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
41 DbMigrateVersion, RepoGroup, UserRepoGroupToPerm, CacheKey, Repository)
42 from rhodecode.model.meta import Session, Base
42 from rhodecode.model.meta import Session, Base
43 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.permission import PermissionModel
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.repo_group import RepoGroupModel
45 from rhodecode.model.repo_group import RepoGroupModel
46 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 def notify(msg):
52 def notify(msg):
53 """
53 """
54 Notification for migrations messages
54 Notification for migrations messages
55 """
55 """
56 ml = len(msg) + (4 * 2)
56 ml = len(msg) + (4 * 2)
57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
57 print('\n%s\n*** %s ***\n%s' % ('*' * ml, msg, '*' * ml)).upper()
58
58
59
59
60 class DbManage(object):
60 class DbManage(object):
61
61
62 def __init__(self, log_sql, dbconf, root, tests=False,
62 def __init__(self, log_sql, dbconf, root, tests=False,
63 SESSION=None, cli_args={}):
63 SESSION=None, cli_args={}):
64 self.dbname = dbconf.split('/')[-1]
64 self.dbname = dbconf.split('/')[-1]
65 self.tests = tests
65 self.tests = tests
66 self.root = root
66 self.root = root
67 self.dburi = dbconf
67 self.dburi = dbconf
68 self.log_sql = log_sql
68 self.log_sql = log_sql
69 self.db_exists = False
69 self.db_exists = False
70 self.cli_args = cli_args
70 self.cli_args = cli_args
71 self.init_db(SESSION=SESSION)
71 self.init_db(SESSION=SESSION)
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
72 self.ask_ok = self.get_ask_ok_func(self.cli_args.get('force_ask'))
73
73
74 def get_ask_ok_func(self, param):
74 def get_ask_ok_func(self, param):
75 if param not in [None]:
75 if param not in [None]:
76 # return a function lambda that has a default set to param
76 # return a function lambda that has a default set to param
77 return lambda *args, **kwargs: param
77 return lambda *args, **kwargs: param
78 else:
78 else:
79 from rhodecode.lib.utils import ask_ok
79 from rhodecode.lib.utils import ask_ok
80 return ask_ok
80 return ask_ok
81
81
82 def init_db(self, SESSION=None):
82 def init_db(self, SESSION=None):
83 if SESSION:
83 if SESSION:
84 self.sa = SESSION
84 self.sa = SESSION
85 else:
85 else:
86 # init new sessions
86 # init new sessions
87 engine = create_engine(self.dburi, echo=self.log_sql)
87 engine = create_engine(self.dburi, echo=self.log_sql)
88 init_model(engine)
88 init_model(engine)
89 self.sa = Session()
89 self.sa = Session()
90
90
91 def create_tables(self, override=False):
91 def create_tables(self, override=False):
92 """
92 """
93 Create a auth database
93 Create a auth database
94 """
94 """
95
95
96 log.info("Existing database with the same name is going to be destroyed.")
96 log.info("Existing database with the same name is going to be destroyed.")
97 log.info("Setup command will run DROP ALL command on that database.")
97 log.info("Setup command will run DROP ALL command on that database.")
98 if self.tests:
98 if self.tests:
99 destroy = True
99 destroy = True
100 else:
100 else:
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
101 destroy = self.ask_ok('Are you sure that you want to destroy the old database? [y/n]')
102 if not destroy:
102 if not destroy:
103 log.info('Nothing done.')
103 log.info('Nothing done.')
104 sys.exit(0)
104 sys.exit(0)
105 if destroy:
105 if destroy:
106 Base.metadata.drop_all()
106 Base.metadata.drop_all()
107
107
108 checkfirst = not override
108 checkfirst = not override
109 Base.metadata.create_all(checkfirst=checkfirst)
109 Base.metadata.create_all(checkfirst=checkfirst)
110 log.info('Created tables for %s' % self.dbname)
110 log.info('Created tables for %s' % self.dbname)
111
111
112 def set_db_version(self):
112 def set_db_version(self):
113 ver = DbMigrateVersion()
113 ver = DbMigrateVersion()
114 ver.version = __dbversion__
114 ver.version = __dbversion__
115 ver.repository_id = 'rhodecode_db_migrations'
115 ver.repository_id = 'rhodecode_db_migrations'
116 ver.repository_path = 'versions'
116 ver.repository_path = 'versions'
117 self.sa.add(ver)
117 self.sa.add(ver)
118 log.info('db version set to: %s' % __dbversion__)
118 log.info('db version set to: %s' % __dbversion__)
119
119
120 def run_pre_migration_tasks(self):
120 def run_pre_migration_tasks(self):
121 """
121 """
122 Run various tasks before actually doing migrations
122 Run various tasks before actually doing migrations
123 """
123 """
124 # delete cache keys on each upgrade
124 # delete cache keys on each upgrade
125 total = CacheKey.query().count()
125 total = CacheKey.query().count()
126 log.info("Deleting (%s) cache keys now...", total)
126 log.info("Deleting (%s) cache keys now...", total)
127 CacheKey.delete_all_cache()
127 CacheKey.delete_all_cache()
128
128
129 def upgrade(self):
129 def upgrade(self):
130 """
130 """
131 Upgrades given database schema to given revision following
131 Upgrades given database schema to given revision following
132 all needed steps, to perform the upgrade
132 all needed steps, to perform the upgrade
133
133
134 """
134 """
135
135
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
136 from rhodecode.lib.dbmigrate.migrate.versioning import api
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
137 from rhodecode.lib.dbmigrate.migrate.exceptions import \
138 DatabaseNotControlledError
138 DatabaseNotControlledError
139
139
140 if 'sqlite' in self.dburi:
140 if 'sqlite' in self.dburi:
141 print (
141 print (
142 '********************** WARNING **********************\n'
142 '********************** WARNING **********************\n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
143 'Make sure your version of sqlite is at least 3.7.X. \n'
144 'Earlier versions are known to fail on some migrations\n'
144 'Earlier versions are known to fail on some migrations\n'
145 '*****************************************************\n')
145 '*****************************************************\n')
146
146
147 upgrade = self.ask_ok(
147 upgrade = self.ask_ok(
148 'You are about to perform a database upgrade. Make '
148 'You are about to perform a database upgrade. Make '
149 'sure you have backed up your database. '
149 'sure you have backed up your database. '
150 'Continue ? [y/n]')
150 'Continue ? [y/n]')
151 if not upgrade:
151 if not upgrade:
152 log.info('No upgrade performed')
152 log.info('No upgrade performed')
153 sys.exit(0)
153 sys.exit(0)
154
154
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
155 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
156 'rhodecode/lib/dbmigrate')
156 'rhodecode/lib/dbmigrate')
157 db_uri = self.dburi
157 db_uri = self.dburi
158
158
159 try:
159 try:
160 curr_version = api.db_version(db_uri, repository_path)
160 curr_version = api.db_version(db_uri, repository_path)
161 msg = ('Found current database under version '
161 msg = ('Found current database under version '
162 'control with version %s' % curr_version)
162 'control with version %s' % curr_version)
163
163
164 except (RuntimeError, DatabaseNotControlledError):
164 except (RuntimeError, DatabaseNotControlledError):
165 curr_version = 1
165 curr_version = 1
166 msg = ('Current database is not under version control. Setting '
166 msg = ('Current database is not under version control. Setting '
167 'as version %s' % curr_version)
167 'as version %s' % curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
168 api.version_control(db_uri, repository_path, curr_version)
169
169
170 notify(msg)
170 notify(msg)
171
171
172 self.run_pre_migration_tasks()
172 self.run_pre_migration_tasks()
173
173
174 if curr_version == __dbversion__:
174 if curr_version == __dbversion__:
175 log.info('This database is already at the newest version')
175 log.info('This database is already at the newest version')
176 sys.exit(0)
176 sys.exit(0)
177
177
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
178 upgrade_steps = range(curr_version + 1, __dbversion__ + 1)
179 notify('attempting to upgrade database from '
179 notify('attempting to upgrade database from '
180 'version %s to version %s' % (curr_version, __dbversion__))
180 'version %s to version %s' % (curr_version, __dbversion__))
181
181
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
182 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
183 _step = None
183 _step = None
184 for step in upgrade_steps:
184 for step in upgrade_steps:
185 notify('performing upgrade step %s' % step)
185 notify('performing upgrade step %s' % step)
186 time.sleep(0.5)
186 time.sleep(0.5)
187
187
188 api.upgrade(db_uri, repository_path, step)
188 api.upgrade(db_uri, repository_path, step)
189 self.sa.rollback()
189 self.sa.rollback()
190 notify('schema upgrade for step %s completed' % (step,))
190 notify('schema upgrade for step %s completed' % (step,))
191
191
192 _step = step
192 _step = step
193
193
194 notify('upgrade to version %s successful' % _step)
194 notify('upgrade to version %s successful' % _step)
195
195
196 def fix_repo_paths(self):
196 def fix_repo_paths(self):
197 """
197 """
198 Fixes an old RhodeCode version path into new one without a '*'
198 Fixes an old RhodeCode version path into new one without a '*'
199 """
199 """
200
200
201 paths = self.sa.query(RhodeCodeUi)\
201 paths = self.sa.query(RhodeCodeUi)\
202 .filter(RhodeCodeUi.ui_key == '/')\
202 .filter(RhodeCodeUi.ui_key == '/')\
203 .scalar()
203 .scalar()
204
204
205 paths.ui_value = paths.ui_value.replace('*', '')
205 paths.ui_value = paths.ui_value.replace('*', '')
206
206
207 try:
207 try:
208 self.sa.add(paths)
208 self.sa.add(paths)
209 self.sa.commit()
209 self.sa.commit()
210 except Exception:
210 except Exception:
211 self.sa.rollback()
211 self.sa.rollback()
212 raise
212 raise
213
213
214 def fix_default_user(self):
214 def fix_default_user(self):
215 """
215 """
216 Fixes an old default user with some 'nicer' default values,
216 Fixes an old default user with some 'nicer' default values,
217 used mostly for anonymous access
217 used mostly for anonymous access
218 """
218 """
219 def_user = self.sa.query(User)\
219 def_user = self.sa.query(User)\
220 .filter(User.username == User.DEFAULT_USER)\
220 .filter(User.username == User.DEFAULT_USER)\
221 .one()
221 .one()
222
222
223 def_user.name = 'Anonymous'
223 def_user.name = 'Anonymous'
224 def_user.lastname = 'User'
224 def_user.lastname = 'User'
225 def_user.email = User.DEFAULT_USER_EMAIL
225 def_user.email = User.DEFAULT_USER_EMAIL
226
226
227 try:
227 try:
228 self.sa.add(def_user)
228 self.sa.add(def_user)
229 self.sa.commit()
229 self.sa.commit()
230 except Exception:
230 except Exception:
231 self.sa.rollback()
231 self.sa.rollback()
232 raise
232 raise
233
233
234 def fix_settings(self):
234 def fix_settings(self):
235 """
235 """
236 Fixes rhodecode settings and adds ga_code key for google analytics
236 Fixes rhodecode settings and adds ga_code key for google analytics
237 """
237 """
238
238
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
239 hgsettings3 = RhodeCodeSetting('ga_code', '')
240
240
241 try:
241 try:
242 self.sa.add(hgsettings3)
242 self.sa.add(hgsettings3)
243 self.sa.commit()
243 self.sa.commit()
244 except Exception:
244 except Exception:
245 self.sa.rollback()
245 self.sa.rollback()
246 raise
246 raise
247
247
248 def create_admin_and_prompt(self):
248 def create_admin_and_prompt(self):
249
249
250 # defaults
250 # defaults
251 defaults = self.cli_args
251 defaults = self.cli_args
252 username = defaults.get('username')
252 username = defaults.get('username')
253 password = defaults.get('password')
253 password = defaults.get('password')
254 email = defaults.get('email')
254 email = defaults.get('email')
255
255
256 if username is None:
256 if username is None:
257 username = raw_input('Specify admin username:')
257 username = raw_input('Specify admin username:')
258 if password is None:
258 if password is None:
259 password = self._get_admin_password()
259 password = self._get_admin_password()
260 if not password:
260 if not password:
261 # second try
261 # second try
262 password = self._get_admin_password()
262 password = self._get_admin_password()
263 if not password:
263 if not password:
264 sys.exit()
264 sys.exit()
265 if email is None:
265 if email is None:
266 email = raw_input('Specify admin email:')
266 email = raw_input('Specify admin email:')
267 api_key = self.cli_args.get('api_key')
267 api_key = self.cli_args.get('api_key')
268 self.create_user(username, password, email, True,
268 self.create_user(username, password, email, True,
269 strict_creation_check=False,
269 strict_creation_check=False,
270 api_key=api_key)
270 api_key=api_key)
271
271
272 def _get_admin_password(self):
272 def _get_admin_password(self):
273 password = getpass.getpass('Specify admin password '
273 password = getpass.getpass('Specify admin password '
274 '(min 6 chars):')
274 '(min 6 chars):')
275 confirm = getpass.getpass('Confirm password:')
275 confirm = getpass.getpass('Confirm password:')
276
276
277 if password != confirm:
277 if password != confirm:
278 log.error('passwords mismatch')
278 log.error('passwords mismatch')
279 return False
279 return False
280 if len(password) < 6:
280 if len(password) < 6:
281 log.error('password is too short - use at least 6 characters')
281 log.error('password is too short - use at least 6 characters')
282 return False
282 return False
283
283
284 return password
284 return password
285
285
286 def create_test_admin_and_users(self):
286 def create_test_admin_and_users(self):
287 log.info('creating admin and regular test users')
287 log.info('creating admin and regular test users')
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
288 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
289 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
290 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
291 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293
293
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296
296
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299
299
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302
302
303 def create_ui_settings(self, repo_store_path):
303 def create_ui_settings(self, repo_store_path):
304 """
304 """
305 Creates ui settings, fills out hooks
305 Creates ui settings, fills out hooks
306 and disables dotencode
306 and disables dotencode
307 """
307 """
308 settings_model = SettingsModel(sa=self.sa)
308 settings_model = SettingsModel(sa=self.sa)
309 from rhodecode.lib.vcs.backends.hg import largefiles_store
309 from rhodecode.lib.vcs.backends.hg import largefiles_store
310 from rhodecode.lib.vcs.backends.git import lfs_store
310 from rhodecode.lib.vcs.backends.git import lfs_store
311
311
312 # Build HOOKS
312 # Build HOOKS
313 hooks = [
313 hooks = [
314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
314 (RhodeCodeUi.HOOK_REPO_SIZE, 'python:vcsserver.hooks.repo_size'),
315
315
316 # HG
316 # HG
317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
322 (RhodeCodeUi.HOOK_PUSH_KEY, 'python:vcsserver.hooks.key_push'),
322
323
323 ]
324 ]
324
325
325 for key, value in hooks:
326 for key, value in hooks:
326 hook_obj = settings_model.get_ui_by_key(key)
327 hook_obj = settings_model.get_ui_by_key(key)
327 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
328 hooks2 = hook_obj if hook_obj else RhodeCodeUi()
328 hooks2.ui_section = 'hooks'
329 hooks2.ui_section = 'hooks'
329 hooks2.ui_key = key
330 hooks2.ui_key = key
330 hooks2.ui_value = value
331 hooks2.ui_value = value
331 self.sa.add(hooks2)
332 self.sa.add(hooks2)
332
333
333 # enable largefiles
334 # enable largefiles
334 largefiles = RhodeCodeUi()
335 largefiles = RhodeCodeUi()
335 largefiles.ui_section = 'extensions'
336 largefiles.ui_section = 'extensions'
336 largefiles.ui_key = 'largefiles'
337 largefiles.ui_key = 'largefiles'
337 largefiles.ui_value = ''
338 largefiles.ui_value = ''
338 self.sa.add(largefiles)
339 self.sa.add(largefiles)
339
340
340 # set default largefiles cache dir, defaults to
341 # set default largefiles cache dir, defaults to
341 # /repo_store_location/.cache/largefiles
342 # /repo_store_location/.cache/largefiles
342 largefiles = RhodeCodeUi()
343 largefiles = RhodeCodeUi()
343 largefiles.ui_section = 'largefiles'
344 largefiles.ui_section = 'largefiles'
344 largefiles.ui_key = 'usercache'
345 largefiles.ui_key = 'usercache'
345 largefiles.ui_value = largefiles_store(repo_store_path)
346 largefiles.ui_value = largefiles_store(repo_store_path)
346
347
347 self.sa.add(largefiles)
348 self.sa.add(largefiles)
348
349
349 # set default lfs cache dir, defaults to
350 # set default lfs cache dir, defaults to
350 # /repo_store_location/.cache/lfs_store
351 # /repo_store_location/.cache/lfs_store
351 lfsstore = RhodeCodeUi()
352 lfsstore = RhodeCodeUi()
352 lfsstore.ui_section = 'vcs_git_lfs'
353 lfsstore.ui_section = 'vcs_git_lfs'
353 lfsstore.ui_key = 'store_location'
354 lfsstore.ui_key = 'store_location'
354 lfsstore.ui_value = lfs_store(repo_store_path)
355 lfsstore.ui_value = lfs_store(repo_store_path)
355
356
356 self.sa.add(lfsstore)
357 self.sa.add(lfsstore)
357
358
358 # enable hgsubversion disabled by default
359 # enable hgsubversion disabled by default
359 hgsubversion = RhodeCodeUi()
360 hgsubversion = RhodeCodeUi()
360 hgsubversion.ui_section = 'extensions'
361 hgsubversion.ui_section = 'extensions'
361 hgsubversion.ui_key = 'hgsubversion'
362 hgsubversion.ui_key = 'hgsubversion'
362 hgsubversion.ui_value = ''
363 hgsubversion.ui_value = ''
363 hgsubversion.ui_active = False
364 hgsubversion.ui_active = False
364 self.sa.add(hgsubversion)
365 self.sa.add(hgsubversion)
365
366
366 # enable hgevolve disabled by default
367 # enable hgevolve disabled by default
367 hgevolve = RhodeCodeUi()
368 hgevolve = RhodeCodeUi()
368 hgevolve.ui_section = 'extensions'
369 hgevolve.ui_section = 'extensions'
369 hgevolve.ui_key = 'evolve'
370 hgevolve.ui_key = 'evolve'
370 hgevolve.ui_value = ''
371 hgevolve.ui_value = ''
371 hgevolve.ui_active = False
372 hgevolve.ui_active = False
372 self.sa.add(hgevolve)
373 self.sa.add(hgevolve)
373
374
374 # enable hggit disabled by default
375 # enable hggit disabled by default
375 hggit = RhodeCodeUi()
376 hggit = RhodeCodeUi()
376 hggit.ui_section = 'extensions'
377 hggit.ui_section = 'extensions'
377 hggit.ui_key = 'hggit'
378 hggit.ui_key = 'hggit'
378 hggit.ui_value = ''
379 hggit.ui_value = ''
379 hggit.ui_active = False
380 hggit.ui_active = False
380 self.sa.add(hggit)
381 self.sa.add(hggit)
381
382
382 # set svn branch defaults
383 # set svn branch defaults
383 branches = ["/branches/*", "/trunk"]
384 branches = ["/branches/*", "/trunk"]
384 tags = ["/tags/*"]
385 tags = ["/tags/*"]
385
386
386 for branch in branches:
387 for branch in branches:
387 settings_model.create_ui_section_value(
388 settings_model.create_ui_section_value(
388 RhodeCodeUi.SVN_BRANCH_ID, branch)
389 RhodeCodeUi.SVN_BRANCH_ID, branch)
389
390
390 for tag in tags:
391 for tag in tags:
391 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
392 settings_model.create_ui_section_value(RhodeCodeUi.SVN_TAG_ID, tag)
392
393
393 def create_auth_plugin_options(self, skip_existing=False):
394 def create_auth_plugin_options(self, skip_existing=False):
394 """
395 """
395 Create default auth plugin settings, and make it active
396 Create default auth plugin settings, and make it active
396
397
397 :param skip_existing:
398 :param skip_existing:
398 """
399 """
399
400
400 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
401 for k, v, t in [('auth_plugins', 'egg:rhodecode-enterprise-ce#rhodecode', 'list'),
401 ('auth_rhodecode_enabled', 'True', 'bool')]:
402 ('auth_rhodecode_enabled', 'True', 'bool')]:
402 if (skip_existing and
403 if (skip_existing and
403 SettingsModel().get_setting_by_name(k) is not None):
404 SettingsModel().get_setting_by_name(k) is not None):
404 log.debug('Skipping option %s' % k)
405 log.debug('Skipping option %s' % k)
405 continue
406 continue
406 setting = RhodeCodeSetting(k, v, t)
407 setting = RhodeCodeSetting(k, v, t)
407 self.sa.add(setting)
408 self.sa.add(setting)
408
409
409 def create_default_options(self, skip_existing=False):
410 def create_default_options(self, skip_existing=False):
410 """Creates default settings"""
411 """Creates default settings"""
411
412
412 for k, v, t in [
413 for k, v, t in [
413 ('default_repo_enable_locking', False, 'bool'),
414 ('default_repo_enable_locking', False, 'bool'),
414 ('default_repo_enable_downloads', False, 'bool'),
415 ('default_repo_enable_downloads', False, 'bool'),
415 ('default_repo_enable_statistics', False, 'bool'),
416 ('default_repo_enable_statistics', False, 'bool'),
416 ('default_repo_private', False, 'bool'),
417 ('default_repo_private', False, 'bool'),
417 ('default_repo_type', 'hg', 'unicode')]:
418 ('default_repo_type', 'hg', 'unicode')]:
418
419
419 if (skip_existing and
420 if (skip_existing and
420 SettingsModel().get_setting_by_name(k) is not None):
421 SettingsModel().get_setting_by_name(k) is not None):
421 log.debug('Skipping option %s' % k)
422 log.debug('Skipping option %s' % k)
422 continue
423 continue
423 setting = RhodeCodeSetting(k, v, t)
424 setting = RhodeCodeSetting(k, v, t)
424 self.sa.add(setting)
425 self.sa.add(setting)
425
426
426 def fixup_groups(self):
427 def fixup_groups(self):
427 def_usr = User.get_default_user()
428 def_usr = User.get_default_user()
428 for g in RepoGroup.query().all():
429 for g in RepoGroup.query().all():
429 g.group_name = g.get_new_name(g.name)
430 g.group_name = g.get_new_name(g.name)
430 self.sa.add(g)
431 self.sa.add(g)
431 # get default perm
432 # get default perm
432 default = UserRepoGroupToPerm.query()\
433 default = UserRepoGroupToPerm.query()\
433 .filter(UserRepoGroupToPerm.group == g)\
434 .filter(UserRepoGroupToPerm.group == g)\
434 .filter(UserRepoGroupToPerm.user == def_usr)\
435 .filter(UserRepoGroupToPerm.user == def_usr)\
435 .scalar()
436 .scalar()
436
437
437 if default is None:
438 if default is None:
438 log.debug('missing default permission for group %s adding' % g)
439 log.debug('missing default permission for group %s adding' % g)
439 perm_obj = RepoGroupModel()._create_default_perms(g)
440 perm_obj = RepoGroupModel()._create_default_perms(g)
440 self.sa.add(perm_obj)
441 self.sa.add(perm_obj)
441
442
442 def reset_permissions(self, username):
443 def reset_permissions(self, username):
443 """
444 """
444 Resets permissions to default state, useful when old systems had
445 Resets permissions to default state, useful when old systems had
445 bad permissions, we must clean them up
446 bad permissions, we must clean them up
446
447
447 :param username:
448 :param username:
448 """
449 """
449 default_user = User.get_by_username(username)
450 default_user = User.get_by_username(username)
450 if not default_user:
451 if not default_user:
451 return
452 return
452
453
453 u2p = UserToPerm.query()\
454 u2p = UserToPerm.query()\
454 .filter(UserToPerm.user == default_user).all()
455 .filter(UserToPerm.user == default_user).all()
455 fixed = False
456 fixed = False
456 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
457 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
457 for p in u2p:
458 for p in u2p:
458 Session().delete(p)
459 Session().delete(p)
459 fixed = True
460 fixed = True
460 self.populate_default_permissions()
461 self.populate_default_permissions()
461 return fixed
462 return fixed
462
463
463 def update_repo_info(self):
464 def update_repo_info(self):
464 RepoModel.update_repoinfo()
465 RepoModel.update_repoinfo()
465
466
466 def config_prompt(self, test_repo_path='', retries=3):
467 def config_prompt(self, test_repo_path='', retries=3):
467 defaults = self.cli_args
468 defaults = self.cli_args
468 _path = defaults.get('repos_location')
469 _path = defaults.get('repos_location')
469 if retries == 3:
470 if retries == 3:
470 log.info('Setting up repositories config')
471 log.info('Setting up repositories config')
471
472
472 if _path is not None:
473 if _path is not None:
473 path = _path
474 path = _path
474 elif not self.tests and not test_repo_path:
475 elif not self.tests and not test_repo_path:
475 path = raw_input(
476 path = raw_input(
476 'Enter a valid absolute path to store repositories. '
477 'Enter a valid absolute path to store repositories. '
477 'All repositories in that path will be added automatically:'
478 'All repositories in that path will be added automatically:'
478 )
479 )
479 else:
480 else:
480 path = test_repo_path
481 path = test_repo_path
481 path_ok = True
482 path_ok = True
482
483
483 # check proper dir
484 # check proper dir
484 if not os.path.isdir(path):
485 if not os.path.isdir(path):
485 path_ok = False
486 path_ok = False
486 log.error('Given path %s is not a valid directory' % (path,))
487 log.error('Given path %s is not a valid directory' % (path,))
487
488
488 elif not os.path.isabs(path):
489 elif not os.path.isabs(path):
489 path_ok = False
490 path_ok = False
490 log.error('Given path %s is not an absolute path' % (path,))
491 log.error('Given path %s is not an absolute path' % (path,))
491
492
492 # check if path is at least readable.
493 # check if path is at least readable.
493 if not os.access(path, os.R_OK):
494 if not os.access(path, os.R_OK):
494 path_ok = False
495 path_ok = False
495 log.error('Given path %s is not readable' % (path,))
496 log.error('Given path %s is not readable' % (path,))
496
497
497 # check write access, warn user about non writeable paths
498 # check write access, warn user about non writeable paths
498 elif not os.access(path, os.W_OK) and path_ok:
499 elif not os.access(path, os.W_OK) and path_ok:
499 log.warning('No write permission to given path %s' % (path,))
500 log.warning('No write permission to given path %s' % (path,))
500
501
501 q = ('Given path %s is not writeable, do you want to '
502 q = ('Given path %s is not writeable, do you want to '
502 'continue with read only mode ? [y/n]' % (path,))
503 'continue with read only mode ? [y/n]' % (path,))
503 if not self.ask_ok(q):
504 if not self.ask_ok(q):
504 log.error('Canceled by user')
505 log.error('Canceled by user')
505 sys.exit(-1)
506 sys.exit(-1)
506
507
507 if retries == 0:
508 if retries == 0:
508 sys.exit('max retries reached')
509 sys.exit('max retries reached')
509 if not path_ok:
510 if not path_ok:
510 retries -= 1
511 retries -= 1
511 return self.config_prompt(test_repo_path, retries)
512 return self.config_prompt(test_repo_path, retries)
512
513
513 real_path = os.path.normpath(os.path.realpath(path))
514 real_path = os.path.normpath(os.path.realpath(path))
514
515
515 if real_path != os.path.normpath(path):
516 if real_path != os.path.normpath(path):
516 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
517 q = ('Path looks like a symlink, RhodeCode Enterprise will store '
517 'given path as %s ? [y/n]') % (real_path,)
518 'given path as %s ? [y/n]') % (real_path,)
518 if not self.ask_ok(q):
519 if not self.ask_ok(q):
519 log.error('Canceled by user')
520 log.error('Canceled by user')
520 sys.exit(-1)
521 sys.exit(-1)
521
522
522 return real_path
523 return real_path
523
524
524 def create_settings(self, path):
525 def create_settings(self, path):
525
526
526 self.create_ui_settings(path)
527 self.create_ui_settings(path)
527
528
528 ui_config = [
529 ui_config = [
529 ('web', 'push_ssl', 'False'),
530 ('web', 'push_ssl', 'False'),
530 ('web', 'allow_archive', 'gz zip bz2'),
531 ('web', 'allow_archive', 'gz zip bz2'),
531 ('web', 'allow_push', '*'),
532 ('web', 'allow_push', '*'),
532 ('web', 'baseurl', '/'),
533 ('web', 'baseurl', '/'),
533 ('paths', '/', path),
534 ('paths', '/', path),
534 ('phases', 'publish', 'True')
535 ('phases', 'publish', 'True')
535 ]
536 ]
536 for section, key, value in ui_config:
537 for section, key, value in ui_config:
537 ui_conf = RhodeCodeUi()
538 ui_conf = RhodeCodeUi()
538 setattr(ui_conf, 'ui_section', section)
539 setattr(ui_conf, 'ui_section', section)
539 setattr(ui_conf, 'ui_key', key)
540 setattr(ui_conf, 'ui_key', key)
540 setattr(ui_conf, 'ui_value', value)
541 setattr(ui_conf, 'ui_value', value)
541 self.sa.add(ui_conf)
542 self.sa.add(ui_conf)
542
543
543 # rhodecode app settings
544 # rhodecode app settings
544 settings = [
545 settings = [
545 ('realm', 'RhodeCode', 'unicode'),
546 ('realm', 'RhodeCode', 'unicode'),
546 ('title', '', 'unicode'),
547 ('title', '', 'unicode'),
547 ('pre_code', '', 'unicode'),
548 ('pre_code', '', 'unicode'),
548 ('post_code', '', 'unicode'),
549 ('post_code', '', 'unicode'),
549 ('show_public_icon', True, 'bool'),
550 ('show_public_icon', True, 'bool'),
550 ('show_private_icon', True, 'bool'),
551 ('show_private_icon', True, 'bool'),
551 ('stylify_metatags', False, 'bool'),
552 ('stylify_metatags', False, 'bool'),
552 ('dashboard_items', 100, 'int'),
553 ('dashboard_items', 100, 'int'),
553 ('admin_grid_items', 25, 'int'),
554 ('admin_grid_items', 25, 'int'),
554 ('show_version', True, 'bool'),
555 ('show_version', True, 'bool'),
555 ('use_gravatar', False, 'bool'),
556 ('use_gravatar', False, 'bool'),
556 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
557 ('gravatar_url', User.DEFAULT_GRAVATAR_URL, 'unicode'),
557 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
558 ('clone_uri_tmpl', Repository.DEFAULT_CLONE_URI, 'unicode'),
558 ('support_url', '', 'unicode'),
559 ('support_url', '', 'unicode'),
559 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
560 ('update_url', RhodeCodeSetting.DEFAULT_UPDATE_URL, 'unicode'),
560 ('show_revision_number', True, 'bool'),
561 ('show_revision_number', True, 'bool'),
561 ('show_sha_length', 12, 'int'),
562 ('show_sha_length', 12, 'int'),
562 ]
563 ]
563
564
564 for key, val, type_ in settings:
565 for key, val, type_ in settings:
565 sett = RhodeCodeSetting(key, val, type_)
566 sett = RhodeCodeSetting(key, val, type_)
566 self.sa.add(sett)
567 self.sa.add(sett)
567
568
568 self.create_auth_plugin_options()
569 self.create_auth_plugin_options()
569 self.create_default_options()
570 self.create_default_options()
570
571
571 log.info('created ui config')
572 log.info('created ui config')
572
573
573 def create_user(self, username, password, email='', admin=False,
574 def create_user(self, username, password, email='', admin=False,
574 strict_creation_check=True, api_key=None):
575 strict_creation_check=True, api_key=None):
575 log.info('creating user %s' % username)
576 log.info('creating user %s' % username)
576 user = UserModel().create_or_update(
577 user = UserModel().create_or_update(
577 username, password, email, firstname='RhodeCode', lastname='Admin',
578 username, password, email, firstname='RhodeCode', lastname='Admin',
578 active=True, admin=admin, extern_type="rhodecode",
579 active=True, admin=admin, extern_type="rhodecode",
579 strict_creation_check=strict_creation_check)
580 strict_creation_check=strict_creation_check)
580
581
581 if api_key:
582 if api_key:
582 log.info('setting a provided api key for the user %s', username)
583 log.info('setting a provided api key for the user %s', username)
583 from rhodecode.model.auth_token import AuthTokenModel
584 from rhodecode.model.auth_token import AuthTokenModel
584 AuthTokenModel().create(
585 AuthTokenModel().create(
585 user=user, description='BUILTIN TOKEN')
586 user=user, description='BUILTIN TOKEN')
586
587
587 def create_default_user(self):
588 def create_default_user(self):
588 log.info('creating default user')
589 log.info('creating default user')
589 # create default user for handling default permissions.
590 # create default user for handling default permissions.
590 user = UserModel().create_or_update(username=User.DEFAULT_USER,
591 user = UserModel().create_or_update(username=User.DEFAULT_USER,
591 password=str(uuid.uuid1())[:20],
592 password=str(uuid.uuid1())[:20],
592 email=User.DEFAULT_USER_EMAIL,
593 email=User.DEFAULT_USER_EMAIL,
593 firstname='Anonymous',
594 firstname='Anonymous',
594 lastname='User',
595 lastname='User',
595 strict_creation_check=False)
596 strict_creation_check=False)
596 # based on configuration options activate/deactive this user which
597 # based on configuration options activate/deactive this user which
597 # controlls anonymous access
598 # controlls anonymous access
598 if self.cli_args.get('public_access') is False:
599 if self.cli_args.get('public_access') is False:
599 log.info('Public access disabled')
600 log.info('Public access disabled')
600 user.active = False
601 user.active = False
601 Session().add(user)
602 Session().add(user)
602 Session().commit()
603 Session().commit()
603
604
604 def create_permissions(self):
605 def create_permissions(self):
605 """
606 """
606 Creates all permissions defined in the system
607 Creates all permissions defined in the system
607 """
608 """
608 # module.(access|create|change|delete)_[name]
609 # module.(access|create|change|delete)_[name]
609 # module.(none|read|write|admin)
610 # module.(none|read|write|admin)
610 log.info('creating permissions')
611 log.info('creating permissions')
611 PermissionModel(self.sa).create_permissions()
612 PermissionModel(self.sa).create_permissions()
612
613
613 def populate_default_permissions(self):
614 def populate_default_permissions(self):
614 """
615 """
615 Populate default permissions. It will create only the default
616 Populate default permissions. It will create only the default
616 permissions that are missing, and not alter already defined ones
617 permissions that are missing, and not alter already defined ones
617 """
618 """
618 log.info('creating default user permissions')
619 log.info('creating default user permissions')
619 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
620 PermissionModel(self.sa).create_default_user_permissions(user=User.DEFAULT_USER)
@@ -1,413 +1,425 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Set of hooks run by RhodeCode Enterprise
23 Set of hooks run by RhodeCode Enterprise
24 """
24 """
25
25
26 import os
26 import os
27 import collections
27 import collections
28 import logging
28 import logging
29
29
30 import rhodecode
30 import rhodecode
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib import audit_logger
33 from rhodecode.lib import audit_logger
34 from rhodecode.lib.utils2 import safe_str
34 from rhodecode.lib.utils2 import safe_str
35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
35 from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
36 from rhodecode.model.db import Repository, User
36 from rhodecode.model.db import Repository, User
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
41 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
42
42
43
43
44 def is_shadow_repo(extras):
44 def is_shadow_repo(extras):
45 """
45 """
46 Returns ``True`` if this is an action executed against a shadow repository.
46 Returns ``True`` if this is an action executed against a shadow repository.
47 """
47 """
48 return extras['is_shadow_repo']
48 return extras['is_shadow_repo']
49
49
50
50
51 def _get_scm_size(alias, root_path):
51 def _get_scm_size(alias, root_path):
52
52
53 if not alias.startswith('.'):
53 if not alias.startswith('.'):
54 alias += '.'
54 alias += '.'
55
55
56 size_scm, size_root = 0, 0
56 size_scm, size_root = 0, 0
57 for path, unused_dirs, files in os.walk(safe_str(root_path)):
57 for path, unused_dirs, files in os.walk(safe_str(root_path)):
58 if path.find(alias) != -1:
58 if path.find(alias) != -1:
59 for f in files:
59 for f in files:
60 try:
60 try:
61 size_scm += os.path.getsize(os.path.join(path, f))
61 size_scm += os.path.getsize(os.path.join(path, f))
62 except OSError:
62 except OSError:
63 pass
63 pass
64 else:
64 else:
65 for f in files:
65 for f in files:
66 try:
66 try:
67 size_root += os.path.getsize(os.path.join(path, f))
67 size_root += os.path.getsize(os.path.join(path, f))
68 except OSError:
68 except OSError:
69 pass
69 pass
70
70
71 size_scm_f = h.format_byte_size_binary(size_scm)
71 size_scm_f = h.format_byte_size_binary(size_scm)
72 size_root_f = h.format_byte_size_binary(size_root)
72 size_root_f = h.format_byte_size_binary(size_root)
73 size_total_f = h.format_byte_size_binary(size_root + size_scm)
73 size_total_f = h.format_byte_size_binary(size_root + size_scm)
74
74
75 return size_scm_f, size_root_f, size_total_f
75 return size_scm_f, size_root_f, size_total_f
76
76
77
77
78 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
78 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
79 def repo_size(extras):
79 def repo_size(extras):
80 """Present size of repository after push."""
80 """Present size of repository after push."""
81 repo = Repository.get_by_repo_name(extras.repository)
81 repo = Repository.get_by_repo_name(extras.repository)
82 vcs_part = safe_str(u'.%s' % repo.repo_type)
82 vcs_part = safe_str(u'.%s' % repo.repo_type)
83 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
83 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
84 repo.repo_full_path)
84 repo.repo_full_path)
85 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
85 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
86 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
86 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
87 return HookResponse(0, msg)
87 return HookResponse(0, msg)
88
88
89
89
90 def pre_push(extras):
90 def pre_push(extras):
91 """
91 """
92 Hook executed before pushing code.
92 Hook executed before pushing code.
93
93
94 It bans pushing when the repository is locked.
94 It bans pushing when the repository is locked.
95 """
95 """
96
96
97 usr = User.get_by_username(extras.username)
97 usr = User.get_by_username(extras.username)
98 output = ''
98 output = ''
99 if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
99 if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
100 locked_by = User.get(extras.locked_by[0]).username
100 locked_by = User.get(extras.locked_by[0]).username
101 reason = extras.locked_by[2]
101 reason = extras.locked_by[2]
102 # this exception is interpreted in git/hg middlewares and based
102 # this exception is interpreted in git/hg middlewares and based
103 # on that proper return code is server to client
103 # on that proper return code is server to client
104 _http_ret = HTTPLockedRC(
104 _http_ret = HTTPLockedRC(
105 _locked_by_explanation(extras.repository, locked_by, reason))
105 _locked_by_explanation(extras.repository, locked_by, reason))
106 if str(_http_ret.code).startswith('2'):
106 if str(_http_ret.code).startswith('2'):
107 # 2xx Codes don't raise exceptions
107 # 2xx Codes don't raise exceptions
108 output = _http_ret.title
108 output = _http_ret.title
109 else:
109 else:
110 raise _http_ret
110 raise _http_ret
111
111
112 # Propagate to external components. This is done after checking the
112 # Propagate to external components. This is done after checking the
113 # lock, for consistent behavior.
113 # lock, for consistent behavior.
114 if not is_shadow_repo(extras):
114 if not is_shadow_repo(extras):
115 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
115 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
116 events.trigger(events.RepoPrePushEvent(
116 events.trigger(events.RepoPrePushEvent(
117 repo_name=extras.repository, extras=extras))
117 repo_name=extras.repository, extras=extras))
118
118
119 return HookResponse(0, output)
119 return HookResponse(0, output)
120
120
121
121
122 def pre_pull(extras):
122 def pre_pull(extras):
123 """
123 """
124 Hook executed before pulling the code.
124 Hook executed before pulling the code.
125
125
126 It bans pulling when the repository is locked.
126 It bans pulling when the repository is locked.
127 """
127 """
128
128
129 output = ''
129 output = ''
130 if extras.locked_by[0]:
130 if extras.locked_by[0]:
131 locked_by = User.get(extras.locked_by[0]).username
131 locked_by = User.get(extras.locked_by[0]).username
132 reason = extras.locked_by[2]
132 reason = extras.locked_by[2]
133 # this exception is interpreted in git/hg middlewares and based
133 # this exception is interpreted in git/hg middlewares and based
134 # on that proper return code is server to client
134 # on that proper return code is server to client
135 _http_ret = HTTPLockedRC(
135 _http_ret = HTTPLockedRC(
136 _locked_by_explanation(extras.repository, locked_by, reason))
136 _locked_by_explanation(extras.repository, locked_by, reason))
137 if str(_http_ret.code).startswith('2'):
137 if str(_http_ret.code).startswith('2'):
138 # 2xx Codes don't raise exceptions
138 # 2xx Codes don't raise exceptions
139 output = _http_ret.title
139 output = _http_ret.title
140 else:
140 else:
141 raise _http_ret
141 raise _http_ret
142
142
143 # Propagate to external components. This is done after checking the
143 # Propagate to external components. This is done after checking the
144 # lock, for consistent behavior.
144 # lock, for consistent behavior.
145 if not is_shadow_repo(extras):
145 if not is_shadow_repo(extras):
146 pre_pull_extension(**extras)
146 pre_pull_extension(**extras)
147 events.trigger(events.RepoPrePullEvent(
147 events.trigger(events.RepoPrePullEvent(
148 repo_name=extras.repository, extras=extras))
148 repo_name=extras.repository, extras=extras))
149
149
150 return HookResponse(0, output)
150 return HookResponse(0, output)
151
151
152
152
153 def post_pull(extras):
153 def post_pull(extras):
154 """Hook executed after client pulls the code."""
154 """Hook executed after client pulls the code."""
155
155
156 audit_user = audit_logger.UserWrap(
156 audit_user = audit_logger.UserWrap(
157 username=extras.username,
157 username=extras.username,
158 ip_addr=extras.ip)
158 ip_addr=extras.ip)
159 repo = audit_logger.RepoWrap(repo_name=extras.repository)
159 repo = audit_logger.RepoWrap(repo_name=extras.repository)
160 audit_logger.store(
160 audit_logger.store(
161 action='user.pull', action_data={
161 action='user.pull', action_data={
162 'user_agent': extras.user_agent},
162 'user_agent': extras.user_agent},
163 user=audit_user, repo=repo, commit=True)
163 user=audit_user, repo=repo, commit=True)
164
164
165 # Propagate to external components.
165 # Propagate to external components.
166 if not is_shadow_repo(extras):
166 if not is_shadow_repo(extras):
167 post_pull_extension(**extras)
167 post_pull_extension(**extras)
168 events.trigger(events.RepoPullEvent(
168 events.trigger(events.RepoPullEvent(
169 repo_name=extras.repository, extras=extras))
169 repo_name=extras.repository, extras=extras))
170
170
171 output = ''
171 output = ''
172 # make lock is a tri state False, True, None. We only make lock on True
172 # make lock is a tri state False, True, None. We only make lock on True
173 if extras.make_lock is True and not is_shadow_repo(extras):
173 if extras.make_lock is True and not is_shadow_repo(extras):
174 user = User.get_by_username(extras.username)
174 user = User.get_by_username(extras.username)
175 Repository.lock(Repository.get_by_repo_name(extras.repository),
175 Repository.lock(Repository.get_by_repo_name(extras.repository),
176 user.user_id,
176 user.user_id,
177 lock_reason=Repository.LOCK_PULL)
177 lock_reason=Repository.LOCK_PULL)
178 msg = 'Made lock on repo `%s`' % (extras.repository,)
178 msg = 'Made lock on repo `%s`' % (extras.repository,)
179 output += msg
179 output += msg
180
180
181 if extras.locked_by[0]:
181 if extras.locked_by[0]:
182 locked_by = User.get(extras.locked_by[0]).username
182 locked_by = User.get(extras.locked_by[0]).username
183 reason = extras.locked_by[2]
183 reason = extras.locked_by[2]
184 _http_ret = HTTPLockedRC(
184 _http_ret = HTTPLockedRC(
185 _locked_by_explanation(extras.repository, locked_by, reason))
185 _locked_by_explanation(extras.repository, locked_by, reason))
186 if str(_http_ret.code).startswith('2'):
186 if str(_http_ret.code).startswith('2'):
187 # 2xx Codes don't raise exceptions
187 # 2xx Codes don't raise exceptions
188 output += _http_ret.title
188 output += _http_ret.title
189
189
190 return HookResponse(0, output)
190 return HookResponse(0, output)
191
191
192
192
193 def post_push(extras):
193 def post_push(extras):
194 """Hook executed after user pushes to the repository."""
194 """Hook executed after user pushes to the repository."""
195 commit_ids = extras.commit_ids
195 commit_ids = extras.commit_ids
196
196
197 # log the push call
197 # log the push call
198 audit_user = audit_logger.UserWrap(
198 audit_user = audit_logger.UserWrap(
199 username=extras.username, ip_addr=extras.ip)
199 username=extras.username, ip_addr=extras.ip)
200 repo = audit_logger.RepoWrap(repo_name=extras.repository)
200 repo = audit_logger.RepoWrap(repo_name=extras.repository)
201 audit_logger.store(
201 audit_logger.store(
202 action='user.push', action_data={
202 action='user.push', action_data={
203 'user_agent': extras.user_agent,
203 'user_agent': extras.user_agent,
204 'commit_ids': commit_ids[:10000]},
204 'commit_ids': commit_ids[:10000]},
205 user=audit_user, repo=repo, commit=True)
205 user=audit_user, repo=repo, commit=True)
206
206
207 # Propagate to external components.
207 # Propagate to external components.
208 if not is_shadow_repo(extras):
208 if not is_shadow_repo(extras):
209 post_push_extension(
209 post_push_extension(
210 repo_store_path=Repository.base_path(),
210 repo_store_path=Repository.base_path(),
211 pushed_revs=commit_ids,
211 pushed_revs=commit_ids,
212 **extras)
212 **extras)
213 events.trigger(events.RepoPushEvent(
213 events.trigger(events.RepoPushEvent(
214 repo_name=extras.repository,
214 repo_name=extras.repository,
215 pushed_commit_ids=commit_ids,
215 pushed_commit_ids=commit_ids,
216 extras=extras))
216 extras=extras))
217
217
218 output = ''
218 output = ''
219 # make lock is a tri state False, True, None. We only release lock on False
219 # make lock is a tri state False, True, None. We only release lock on False
220 if extras.make_lock is False and not is_shadow_repo(extras):
220 if extras.make_lock is False and not is_shadow_repo(extras):
221 Repository.unlock(Repository.get_by_repo_name(extras.repository))
221 Repository.unlock(Repository.get_by_repo_name(extras.repository))
222 msg = 'Released lock on repo `%s`\n' % extras.repository
222 msg = 'Released lock on repo `%s`\n' % extras.repository
223 output += msg
223 output += msg
224
224
225 if extras.locked_by[0]:
225 if extras.locked_by[0]:
226 locked_by = User.get(extras.locked_by[0]).username
226 locked_by = User.get(extras.locked_by[0]).username
227 reason = extras.locked_by[2]
227 reason = extras.locked_by[2]
228 _http_ret = HTTPLockedRC(
228 _http_ret = HTTPLockedRC(
229 _locked_by_explanation(extras.repository, locked_by, reason))
229 _locked_by_explanation(extras.repository, locked_by, reason))
230 # TODO: johbo: if not?
230 # TODO: johbo: if not?
231 if str(_http_ret.code).startswith('2'):
231 if str(_http_ret.code).startswith('2'):
232 # 2xx Codes don't raise exceptions
232 # 2xx Codes don't raise exceptions
233 output += _http_ret.title
233 output += _http_ret.title
234
234
235 if extras.new_refs:
236 tmpl = \
237 extras.server_url + '/' + \
238 extras.repository + \
239 "/pull-request/new?{ref_type}={ref_name}"
240 for branch_name in extras.new_refs['branches']:
241 output += 'RhodeCode: open pull request link: {}\n'.format(
242 tmpl.format(ref_type='branch', ref_name=branch_name))
243
244 for book_name in extras.new_refs['bookmarks']:
245 output += 'RhodeCode: open pull request link: {}\n'.format(
246 tmpl.format(ref_type='bookmark', ref_name=book_name))
247
235 output += 'RhodeCode: push completed\n'
248 output += 'RhodeCode: push completed\n'
236
237 return HookResponse(0, output)
249 return HookResponse(0, output)
238
250
239
251
240 def _locked_by_explanation(repo_name, user_name, reason):
252 def _locked_by_explanation(repo_name, user_name, reason):
241 message = (
253 message = (
242 'Repository `%s` locked by user `%s`. Reason:`%s`'
254 'Repository `%s` locked by user `%s`. Reason:`%s`'
243 % (repo_name, user_name, reason))
255 % (repo_name, user_name, reason))
244 return message
256 return message
245
257
246
258
247 def check_allowed_create_user(user_dict, created_by, **kwargs):
259 def check_allowed_create_user(user_dict, created_by, **kwargs):
248 # pre create hooks
260 # pre create hooks
249 if pre_create_user.is_active():
261 if pre_create_user.is_active():
250 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
262 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
251 if not allowed:
263 if not allowed:
252 raise UserCreationError(reason)
264 raise UserCreationError(reason)
253
265
254
266
255 class ExtensionCallback(object):
267 class ExtensionCallback(object):
256 """
268 """
257 Forwards a given call to rcextensions, sanitizes keyword arguments.
269 Forwards a given call to rcextensions, sanitizes keyword arguments.
258
270
259 Does check if there is an extension active for that hook. If it is
271 Does check if there is an extension active for that hook. If it is
260 there, it will forward all `kwargs_keys` keyword arguments to the
272 there, it will forward all `kwargs_keys` keyword arguments to the
261 extension callback.
273 extension callback.
262 """
274 """
263
275
264 def __init__(self, hook_name, kwargs_keys):
276 def __init__(self, hook_name, kwargs_keys):
265 self._hook_name = hook_name
277 self._hook_name = hook_name
266 self._kwargs_keys = set(kwargs_keys)
278 self._kwargs_keys = set(kwargs_keys)
267
279
268 def __call__(self, *args, **kwargs):
280 def __call__(self, *args, **kwargs):
269 log.debug('Calling extension callback for %s', self._hook_name)
281 log.debug('Calling extension callback for %s', self._hook_name)
270
282
271 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
283 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
272 # backward compat for removed api_key for old hooks. THis was it works
284 # backward compat for removed api_key for old hooks. THis was it works
273 # with older rcextensions that require api_key present
285 # with older rcextensions that require api_key present
274 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
286 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
275 kwargs_to_pass['api_key'] = '_DEPRECATED_'
287 kwargs_to_pass['api_key'] = '_DEPRECATED_'
276
288
277 callback = self._get_callback()
289 callback = self._get_callback()
278 if callback:
290 if callback:
279 return callback(**kwargs_to_pass)
291 return callback(**kwargs_to_pass)
280 else:
292 else:
281 log.debug('extensions callback not found skipping...')
293 log.debug('extensions callback not found skipping...')
282
294
283 def is_active(self):
295 def is_active(self):
284 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
296 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
285
297
286 def _get_callback(self):
298 def _get_callback(self):
287 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
299 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
288
300
289
301
290 pre_pull_extension = ExtensionCallback(
302 pre_pull_extension = ExtensionCallback(
291 hook_name='PRE_PULL_HOOK',
303 hook_name='PRE_PULL_HOOK',
292 kwargs_keys=(
304 kwargs_keys=(
293 'server_url', 'config', 'scm', 'username', 'ip', 'action',
305 'server_url', 'config', 'scm', 'username', 'ip', 'action',
294 'repository'))
306 'repository'))
295
307
296
308
297 post_pull_extension = ExtensionCallback(
309 post_pull_extension = ExtensionCallback(
298 hook_name='PULL_HOOK',
310 hook_name='PULL_HOOK',
299 kwargs_keys=(
311 kwargs_keys=(
300 'server_url', 'config', 'scm', 'username', 'ip', 'action',
312 'server_url', 'config', 'scm', 'username', 'ip', 'action',
301 'repository'))
313 'repository'))
302
314
303
315
304 pre_push_extension = ExtensionCallback(
316 pre_push_extension = ExtensionCallback(
305 hook_name='PRE_PUSH_HOOK',
317 hook_name='PRE_PUSH_HOOK',
306 kwargs_keys=(
318 kwargs_keys=(
307 'server_url', 'config', 'scm', 'username', 'ip', 'action',
319 'server_url', 'config', 'scm', 'username', 'ip', 'action',
308 'repository', 'repo_store_path', 'commit_ids'))
320 'repository', 'repo_store_path', 'commit_ids'))
309
321
310
322
311 post_push_extension = ExtensionCallback(
323 post_push_extension = ExtensionCallback(
312 hook_name='PUSH_HOOK',
324 hook_name='PUSH_HOOK',
313 kwargs_keys=(
325 kwargs_keys=(
314 'server_url', 'config', 'scm', 'username', 'ip', 'action',
326 'server_url', 'config', 'scm', 'username', 'ip', 'action',
315 'repository', 'repo_store_path', 'pushed_revs'))
327 'repository', 'repo_store_path', 'pushed_revs'))
316
328
317
329
318 pre_create_user = ExtensionCallback(
330 pre_create_user = ExtensionCallback(
319 hook_name='PRE_CREATE_USER_HOOK',
331 hook_name='PRE_CREATE_USER_HOOK',
320 kwargs_keys=(
332 kwargs_keys=(
321 'username', 'password', 'email', 'firstname', 'lastname', 'active',
333 'username', 'password', 'email', 'firstname', 'lastname', 'active',
322 'admin', 'created_by'))
334 'admin', 'created_by'))
323
335
324
336
325 log_create_pull_request = ExtensionCallback(
337 log_create_pull_request = ExtensionCallback(
326 hook_name='CREATE_PULL_REQUEST',
338 hook_name='CREATE_PULL_REQUEST',
327 kwargs_keys=(
339 kwargs_keys=(
328 'server_url', 'config', 'scm', 'username', 'ip', 'action',
340 'server_url', 'config', 'scm', 'username', 'ip', 'action',
329 'repository', 'pull_request_id', 'url', 'title', 'description',
341 'repository', 'pull_request_id', 'url', 'title', 'description',
330 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
342 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
331 'mergeable', 'source', 'target', 'author', 'reviewers'))
343 'mergeable', 'source', 'target', 'author', 'reviewers'))
332
344
333
345
334 log_merge_pull_request = ExtensionCallback(
346 log_merge_pull_request = ExtensionCallback(
335 hook_name='MERGE_PULL_REQUEST',
347 hook_name='MERGE_PULL_REQUEST',
336 kwargs_keys=(
348 kwargs_keys=(
337 'server_url', 'config', 'scm', 'username', 'ip', 'action',
349 'server_url', 'config', 'scm', 'username', 'ip', 'action',
338 'repository', 'pull_request_id', 'url', 'title', 'description',
350 'repository', 'pull_request_id', 'url', 'title', 'description',
339 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
351 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
340 'mergeable', 'source', 'target', 'author', 'reviewers'))
352 'mergeable', 'source', 'target', 'author', 'reviewers'))
341
353
342
354
343 log_close_pull_request = ExtensionCallback(
355 log_close_pull_request = ExtensionCallback(
344 hook_name='CLOSE_PULL_REQUEST',
356 hook_name='CLOSE_PULL_REQUEST',
345 kwargs_keys=(
357 kwargs_keys=(
346 'server_url', 'config', 'scm', 'username', 'ip', 'action',
358 'server_url', 'config', 'scm', 'username', 'ip', 'action',
347 'repository', 'pull_request_id', 'url', 'title', 'description',
359 'repository', 'pull_request_id', 'url', 'title', 'description',
348 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
360 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
349 'mergeable', 'source', 'target', 'author', 'reviewers'))
361 'mergeable', 'source', 'target', 'author', 'reviewers'))
350
362
351
363
352 log_review_pull_request = ExtensionCallback(
364 log_review_pull_request = ExtensionCallback(
353 hook_name='REVIEW_PULL_REQUEST',
365 hook_name='REVIEW_PULL_REQUEST',
354 kwargs_keys=(
366 kwargs_keys=(
355 'server_url', 'config', 'scm', 'username', 'ip', 'action',
367 'server_url', 'config', 'scm', 'username', 'ip', 'action',
356 'repository', 'pull_request_id', 'url', 'title', 'description',
368 'repository', 'pull_request_id', 'url', 'title', 'description',
357 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
369 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
358 'mergeable', 'source', 'target', 'author', 'reviewers'))
370 'mergeable', 'source', 'target', 'author', 'reviewers'))
359
371
360
372
361 log_update_pull_request = ExtensionCallback(
373 log_update_pull_request = ExtensionCallback(
362 hook_name='UPDATE_PULL_REQUEST',
374 hook_name='UPDATE_PULL_REQUEST',
363 kwargs_keys=(
375 kwargs_keys=(
364 'server_url', 'config', 'scm', 'username', 'ip', 'action',
376 'server_url', 'config', 'scm', 'username', 'ip', 'action',
365 'repository', 'pull_request_id', 'url', 'title', 'description',
377 'repository', 'pull_request_id', 'url', 'title', 'description',
366 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
378 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
367 'mergeable', 'source', 'target', 'author', 'reviewers'))
379 'mergeable', 'source', 'target', 'author', 'reviewers'))
368
380
369
381
370 log_create_user = ExtensionCallback(
382 log_create_user = ExtensionCallback(
371 hook_name='CREATE_USER_HOOK',
383 hook_name='CREATE_USER_HOOK',
372 kwargs_keys=(
384 kwargs_keys=(
373 'username', 'full_name_or_username', 'full_contact', 'user_id',
385 'username', 'full_name_or_username', 'full_contact', 'user_id',
374 'name', 'firstname', 'short_contact', 'admin', 'lastname',
386 'name', 'firstname', 'short_contact', 'admin', 'lastname',
375 'ip_addresses', 'extern_type', 'extern_name',
387 'ip_addresses', 'extern_type', 'extern_name',
376 'email', 'api_keys', 'last_login',
388 'email', 'api_keys', 'last_login',
377 'full_name', 'active', 'password', 'emails',
389 'full_name', 'active', 'password', 'emails',
378 'inherit_default_permissions', 'created_by', 'created_on'))
390 'inherit_default_permissions', 'created_by', 'created_on'))
379
391
380
392
381 log_delete_user = ExtensionCallback(
393 log_delete_user = ExtensionCallback(
382 hook_name='DELETE_USER_HOOK',
394 hook_name='DELETE_USER_HOOK',
383 kwargs_keys=(
395 kwargs_keys=(
384 'username', 'full_name_or_username', 'full_contact', 'user_id',
396 'username', 'full_name_or_username', 'full_contact', 'user_id',
385 'name', 'firstname', 'short_contact', 'admin', 'lastname',
397 'name', 'firstname', 'short_contact', 'admin', 'lastname',
386 'ip_addresses',
398 'ip_addresses',
387 'email', 'last_login',
399 'email', 'last_login',
388 'full_name', 'active', 'password', 'emails',
400 'full_name', 'active', 'password', 'emails',
389 'inherit_default_permissions', 'deleted_by'))
401 'inherit_default_permissions', 'deleted_by'))
390
402
391
403
392 log_create_repository = ExtensionCallback(
404 log_create_repository = ExtensionCallback(
393 hook_name='CREATE_REPO_HOOK',
405 hook_name='CREATE_REPO_HOOK',
394 kwargs_keys=(
406 kwargs_keys=(
395 'repo_name', 'repo_type', 'description', 'private', 'created_on',
407 'repo_name', 'repo_type', 'description', 'private', 'created_on',
396 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
408 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
397 'clone_uri', 'fork_id', 'group_id', 'created_by'))
409 'clone_uri', 'fork_id', 'group_id', 'created_by'))
398
410
399
411
400 log_delete_repository = ExtensionCallback(
412 log_delete_repository = ExtensionCallback(
401 hook_name='DELETE_REPO_HOOK',
413 hook_name='DELETE_REPO_HOOK',
402 kwargs_keys=(
414 kwargs_keys=(
403 'repo_name', 'repo_type', 'description', 'private', 'created_on',
415 'repo_name', 'repo_type', 'description', 'private', 'created_on',
404 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
416 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
405 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
417 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
406
418
407
419
408 log_create_repository_group = ExtensionCallback(
420 log_create_repository_group = ExtensionCallback(
409 hook_name='CREATE_REPO_GROUP_HOOK',
421 hook_name='CREATE_REPO_GROUP_HOOK',
410 kwargs_keys=(
422 kwargs_keys=(
411 'group_name', 'group_parent_id', 'group_description',
423 'group_name', 'group_parent_id', 'group_description',
412 'group_id', 'user_id', 'created_by', 'created_on',
424 'group_id', 'user_id', 'created_by', 'created_on',
413 'enable_locking'))
425 'enable_locking'))
@@ -1,1042 +1,1043 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Utilities library for RhodeCode
22 Utilities library for RhodeCode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import decorator
26 import decorator
27 import json
27 import json
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import shutil
31 import shutil
32 import tempfile
32 import tempfile
33 import traceback
33 import traceback
34 import tarfile
34 import tarfile
35 import warnings
35 import warnings
36 import hashlib
36 import hashlib
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 import paste
39 import paste
40 import pkg_resources
40 import pkg_resources
41 from paste.script.command import Command, BadCommand
41 from paste.script.command import Command, BadCommand
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43 from mako import exceptions
43 from mako import exceptions
44 from pyramid.threadlocal import get_current_registry
44 from pyramid.threadlocal import get_current_registry
45 from pyramid.request import Request
45 from pyramid.request import Request
46
46
47 from rhodecode.lib.fakemod import create_module
47 from rhodecode.lib.fakemod import create_module
48 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.backends.base import Config
49 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
50 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
51 from rhodecode.lib.utils2 import (
51 from rhodecode.lib.utils2 import (
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
52 safe_str, safe_unicode, get_current_rhodecode_user, md5)
53 from rhodecode.model import meta
53 from rhodecode.model import meta
54 from rhodecode.model.db import (
54 from rhodecode.model.db import (
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
55 Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
61 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62
62
63 # String which contains characters that are not allowed in slug names for
63 # String which contains characters that are not allowed in slug names for
64 # repositories or repository groups. It is properly escaped to use it in
64 # repositories or repository groups. It is properly escaped to use it in
65 # regular expressions.
65 # regular expressions.
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
66 SLUG_BAD_CHARS = re.escape('`?=[]\;\'"<>,/~!@#$%^&*()+{}|:')
67
67
68 # Regex that matches forbidden characters in repo/group slugs.
68 # Regex that matches forbidden characters in repo/group slugs.
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
69 SLUG_BAD_CHAR_RE = re.compile('[{}]'.format(SLUG_BAD_CHARS))
70
70
71 # Regex that matches allowed characters in repo/group slugs.
71 # Regex that matches allowed characters in repo/group slugs.
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
72 SLUG_GOOD_CHAR_RE = re.compile('[^{}]'.format(SLUG_BAD_CHARS))
73
73
74 # Regex that matches whole repo/group slugs.
74 # Regex that matches whole repo/group slugs.
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
75 SLUG_RE = re.compile('[^{}]+'.format(SLUG_BAD_CHARS))
76
76
77 _license_cache = None
77 _license_cache = None
78
78
79
79
80 def repo_name_slug(value):
80 def repo_name_slug(value):
81 """
81 """
82 Return slug of name of repository
82 Return slug of name of repository
83 This function is called on each creation/modification
83 This function is called on each creation/modification
84 of repository to prevent bad names in repo
84 of repository to prevent bad names in repo
85 """
85 """
86 replacement_char = '-'
86 replacement_char = '-'
87
87
88 slug = remove_formatting(value)
88 slug = remove_formatting(value)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
89 slug = SLUG_BAD_CHAR_RE.sub('', slug)
90 slug = re.sub('[\s]+', '-', slug)
90 slug = re.sub('[\s]+', '-', slug)
91 slug = collapse(slug, replacement_char)
91 slug = collapse(slug, replacement_char)
92 return slug
92 return slug
93
93
94
94
95 #==============================================================================
95 #==============================================================================
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
96 # PERM DECORATOR HELPERS FOR EXTRACTING NAMES FOR PERM CHECKS
97 #==============================================================================
97 #==============================================================================
98 def get_repo_slug(request):
98 def get_repo_slug(request):
99 if isinstance(request, Request) and getattr(request, 'matchdict', None):
99 if isinstance(request, Request) and getattr(request, 'matchdict', None):
100 # pyramid
100 # pyramid
101 _repo = request.matchdict.get('repo_name')
101 _repo = request.matchdict.get('repo_name')
102 else:
102 else:
103 _repo = request.environ['pylons.routes_dict'].get('repo_name')
103 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104
104
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_repo_group_slug(request):
110 def get_repo_group_slug(request):
111 if isinstance(request, Request) and getattr(request, 'matchdict', None):
111 if isinstance(request, Request) and getattr(request, 'matchdict', None):
112 # pyramid
112 # pyramid
113 _group = request.matchdict.get('group_name')
113 _group = request.matchdict.get('group_name')
114 else:
114 else:
115 _group = request.environ['pylons.routes_dict'].get('group_name')
115 _group = request.environ['pylons.routes_dict'].get('group_name')
116
116
117 if _group:
117 if _group:
118 _group = _group.rstrip('/')
118 _group = _group.rstrip('/')
119 return _group
119 return _group
120
120
121
121
122 def get_user_group_slug(request):
122 def get_user_group_slug(request):
123 if isinstance(request, Request) and getattr(request, 'matchdict', None):
123 if isinstance(request, Request) and getattr(request, 'matchdict', None):
124 # pyramid
124 # pyramid
125 _group = request.matchdict.get('user_group_id')
125 _group = request.matchdict.get('user_group_id')
126 else:
126 else:
127 _group = request.environ['pylons.routes_dict'].get('user_group_id')
127 _group = request.environ['pylons.routes_dict'].get('user_group_id')
128
128
129 try:
129 try:
130 _group = UserGroup.get(_group)
130 _group = UserGroup.get(_group)
131 if _group:
131 if _group:
132 _group = _group.users_group_name
132 _group = _group.users_group_name
133 except Exception:
133 except Exception:
134 log.debug(traceback.format_exc())
134 log.debug(traceback.format_exc())
135 # catch all failures here
135 # catch all failures here
136 pass
136 pass
137
137
138 return _group
138 return _group
139
139
140
140
141 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
141 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
142 """
142 """
143 Action logger for various actions made by users
143 Action logger for various actions made by users
144
144
145 :param user: user that made this action, can be a unique username string or
145 :param user: user that made this action, can be a unique username string or
146 object containing user_id attribute
146 object containing user_id attribute
147 :param action: action to log, should be on of predefined unique actions for
147 :param action: action to log, should be on of predefined unique actions for
148 easy translations
148 easy translations
149 :param repo: string name of repository or object containing repo_id,
149 :param repo: string name of repository or object containing repo_id,
150 that action was made on
150 that action was made on
151 :param ipaddr: optional ip address from what the action was made
151 :param ipaddr: optional ip address from what the action was made
152 :param sa: optional sqlalchemy session
152 :param sa: optional sqlalchemy session
153
153
154 """
154 """
155
155
156 if not sa:
156 if not sa:
157 sa = meta.Session()
157 sa = meta.Session()
158 # if we don't get explicit IP address try to get one from registered user
158 # if we don't get explicit IP address try to get one from registered user
159 # in tmpl context var
159 # in tmpl context var
160 if not ipaddr:
160 if not ipaddr:
161 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
161 ipaddr = getattr(get_current_rhodecode_user(), 'ip_addr', '')
162
162
163 try:
163 try:
164 if getattr(user, 'user_id', None):
164 if getattr(user, 'user_id', None):
165 user_obj = User.get(user.user_id)
165 user_obj = User.get(user.user_id)
166 elif isinstance(user, basestring):
166 elif isinstance(user, basestring):
167 user_obj = User.get_by_username(user)
167 user_obj = User.get_by_username(user)
168 else:
168 else:
169 raise Exception('You have to provide a user object or a username')
169 raise Exception('You have to provide a user object or a username')
170
170
171 if getattr(repo, 'repo_id', None):
171 if getattr(repo, 'repo_id', None):
172 repo_obj = Repository.get(repo.repo_id)
172 repo_obj = Repository.get(repo.repo_id)
173 repo_name = repo_obj.repo_name
173 repo_name = repo_obj.repo_name
174 elif isinstance(repo, basestring):
174 elif isinstance(repo, basestring):
175 repo_name = repo.lstrip('/')
175 repo_name = repo.lstrip('/')
176 repo_obj = Repository.get_by_repo_name(repo_name)
176 repo_obj = Repository.get_by_repo_name(repo_name)
177 else:
177 else:
178 repo_obj = None
178 repo_obj = None
179 repo_name = ''
179 repo_name = ''
180
180
181 user_log = UserLog()
181 user_log = UserLog()
182 user_log.user_id = user_obj.user_id
182 user_log.user_id = user_obj.user_id
183 user_log.username = user_obj.username
183 user_log.username = user_obj.username
184 action = safe_unicode(action)
184 action = safe_unicode(action)
185 user_log.action = action[:1200000]
185 user_log.action = action[:1200000]
186
186
187 user_log.repository = repo_obj
187 user_log.repository = repo_obj
188 user_log.repository_name = repo_name
188 user_log.repository_name = repo_name
189
189
190 user_log.action_date = datetime.datetime.now()
190 user_log.action_date = datetime.datetime.now()
191 user_log.user_ip = ipaddr
191 user_log.user_ip = ipaddr
192 sa.add(user_log)
192 sa.add(user_log)
193
193
194 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
194 log.info('Logging action:`%s` on repo:`%s` by user:%s ip:%s',
195 action, safe_unicode(repo), user_obj, ipaddr)
195 action, safe_unicode(repo), user_obj, ipaddr)
196 if commit:
196 if commit:
197 sa.commit()
197 sa.commit()
198 except Exception:
198 except Exception:
199 log.error(traceback.format_exc())
199 log.error(traceback.format_exc())
200 raise
200 raise
201
201
202
202
203 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
203 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
204 """
204 """
205 Scans given path for repos and return (name,(type,path)) tuple
205 Scans given path for repos and return (name,(type,path)) tuple
206
206
207 :param path: path to scan for repositories
207 :param path: path to scan for repositories
208 :param recursive: recursive search and return names with subdirs in front
208 :param recursive: recursive search and return names with subdirs in front
209 """
209 """
210
210
211 # remove ending slash for better results
211 # remove ending slash for better results
212 path = path.rstrip(os.sep)
212 path = path.rstrip(os.sep)
213 log.debug('now scanning in %s location recursive:%s...', path, recursive)
213 log.debug('now scanning in %s location recursive:%s...', path, recursive)
214
214
215 def _get_repos(p):
215 def _get_repos(p):
216 dirpaths = _get_dirpaths(p)
216 dirpaths = _get_dirpaths(p)
217 if not _is_dir_writable(p):
217 if not _is_dir_writable(p):
218 log.warning('repo path without write access: %s', p)
218 log.warning('repo path without write access: %s', p)
219
219
220 for dirpath in dirpaths:
220 for dirpath in dirpaths:
221 if os.path.isfile(os.path.join(p, dirpath)):
221 if os.path.isfile(os.path.join(p, dirpath)):
222 continue
222 continue
223 cur_path = os.path.join(p, dirpath)
223 cur_path = os.path.join(p, dirpath)
224
224
225 # skip removed repos
225 # skip removed repos
226 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
226 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
227 continue
227 continue
228
228
229 #skip .<somethin> dirs
229 #skip .<somethin> dirs
230 if dirpath.startswith('.'):
230 if dirpath.startswith('.'):
231 continue
231 continue
232
232
233 try:
233 try:
234 scm_info = get_scm(cur_path)
234 scm_info = get_scm(cur_path)
235 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
235 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
236 except VCSError:
236 except VCSError:
237 if not recursive:
237 if not recursive:
238 continue
238 continue
239 #check if this dir containts other repos for recursive scan
239 #check if this dir containts other repos for recursive scan
240 rec_path = os.path.join(p, dirpath)
240 rec_path = os.path.join(p, dirpath)
241 if os.path.isdir(rec_path):
241 if os.path.isdir(rec_path):
242 for inner_scm in _get_repos(rec_path):
242 for inner_scm in _get_repos(rec_path):
243 yield inner_scm
243 yield inner_scm
244
244
245 return _get_repos(path)
245 return _get_repos(path)
246
246
247
247
248 def _get_dirpaths(p):
248 def _get_dirpaths(p):
249 try:
249 try:
250 # OS-independable way of checking if we have at least read-only
250 # OS-independable way of checking if we have at least read-only
251 # access or not.
251 # access or not.
252 dirpaths = os.listdir(p)
252 dirpaths = os.listdir(p)
253 except OSError:
253 except OSError:
254 log.warning('ignoring repo path without read access: %s', p)
254 log.warning('ignoring repo path without read access: %s', p)
255 return []
255 return []
256
256
257 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
257 # os.listpath has a tweak: If a unicode is passed into it, then it tries to
258 # decode paths and suddenly returns unicode objects itself. The items it
258 # decode paths and suddenly returns unicode objects itself. The items it
259 # cannot decode are returned as strings and cause issues.
259 # cannot decode are returned as strings and cause issues.
260 #
260 #
261 # Those paths are ignored here until a solid solution for path handling has
261 # Those paths are ignored here until a solid solution for path handling has
262 # been built.
262 # been built.
263 expected_type = type(p)
263 expected_type = type(p)
264
264
265 def _has_correct_type(item):
265 def _has_correct_type(item):
266 if type(item) is not expected_type:
266 if type(item) is not expected_type:
267 log.error(
267 log.error(
268 u"Ignoring path %s since it cannot be decoded into unicode.",
268 u"Ignoring path %s since it cannot be decoded into unicode.",
269 # Using "repr" to make sure that we see the byte value in case
269 # Using "repr" to make sure that we see the byte value in case
270 # of support.
270 # of support.
271 repr(item))
271 repr(item))
272 return False
272 return False
273 return True
273 return True
274
274
275 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
275 dirpaths = [item for item in dirpaths if _has_correct_type(item)]
276
276
277 return dirpaths
277 return dirpaths
278
278
279
279
280 def _is_dir_writable(path):
280 def _is_dir_writable(path):
281 """
281 """
282 Probe if `path` is writable.
282 Probe if `path` is writable.
283
283
284 Due to trouble on Cygwin / Windows, this is actually probing if it is
284 Due to trouble on Cygwin / Windows, this is actually probing if it is
285 possible to create a file inside of `path`, stat does not produce reliable
285 possible to create a file inside of `path`, stat does not produce reliable
286 results in this case.
286 results in this case.
287 """
287 """
288 try:
288 try:
289 with tempfile.TemporaryFile(dir=path):
289 with tempfile.TemporaryFile(dir=path):
290 pass
290 pass
291 except OSError:
291 except OSError:
292 return False
292 return False
293 return True
293 return True
294
294
295
295
296 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
296 def is_valid_repo(repo_name, base_path, expect_scm=None, explicit_scm=None):
297 """
297 """
298 Returns True if given path is a valid repository False otherwise.
298 Returns True if given path is a valid repository False otherwise.
299 If expect_scm param is given also, compare if given scm is the same
299 If expect_scm param is given also, compare if given scm is the same
300 as expected from scm parameter. If explicit_scm is given don't try to
300 as expected from scm parameter. If explicit_scm is given don't try to
301 detect the scm, just use the given one to check if repo is valid
301 detect the scm, just use the given one to check if repo is valid
302
302
303 :param repo_name:
303 :param repo_name:
304 :param base_path:
304 :param base_path:
305 :param expect_scm:
305 :param expect_scm:
306 :param explicit_scm:
306 :param explicit_scm:
307
307
308 :return True: if given path is a valid repository
308 :return True: if given path is a valid repository
309 """
309 """
310 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
310 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
311 log.debug('Checking if `%s` is a valid path for repository. '
311 log.debug('Checking if `%s` is a valid path for repository. '
312 'Explicit type: %s', repo_name, explicit_scm)
312 'Explicit type: %s', repo_name, explicit_scm)
313
313
314 try:
314 try:
315 if explicit_scm:
315 if explicit_scm:
316 detected_scms = [get_scm_backend(explicit_scm)]
316 detected_scms = [get_scm_backend(explicit_scm)]
317 else:
317 else:
318 detected_scms = get_scm(full_path)
318 detected_scms = get_scm(full_path)
319
319
320 if expect_scm:
320 if expect_scm:
321 return detected_scms[0] == expect_scm
321 return detected_scms[0] == expect_scm
322 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
322 log.debug('path: %s is an vcs object:%s', full_path, detected_scms)
323 return True
323 return True
324 except VCSError:
324 except VCSError:
325 log.debug('path: %s is not a valid repo !', full_path)
325 log.debug('path: %s is not a valid repo !', full_path)
326 return False
326 return False
327
327
328
328
329 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
329 def is_valid_repo_group(repo_group_name, base_path, skip_path_check=False):
330 """
330 """
331 Returns True if given path is a repository group, False otherwise
331 Returns True if given path is a repository group, False otherwise
332
332
333 :param repo_name:
333 :param repo_name:
334 :param base_path:
334 :param base_path:
335 """
335 """
336 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
336 full_path = os.path.join(safe_str(base_path), safe_str(repo_group_name))
337 log.debug('Checking if `%s` is a valid path for repository group',
337 log.debug('Checking if `%s` is a valid path for repository group',
338 repo_group_name)
338 repo_group_name)
339
339
340 # check if it's not a repo
340 # check if it's not a repo
341 if is_valid_repo(repo_group_name, base_path):
341 if is_valid_repo(repo_group_name, base_path):
342 log.debug('Repo called %s exist, it is not a valid '
342 log.debug('Repo called %s exist, it is not a valid '
343 'repo group' % repo_group_name)
343 'repo group' % repo_group_name)
344 return False
344 return False
345
345
346 try:
346 try:
347 # we need to check bare git repos at higher level
347 # we need to check bare git repos at higher level
348 # since we might match branches/hooks/info/objects or possible
348 # since we might match branches/hooks/info/objects or possible
349 # other things inside bare git repo
349 # other things inside bare git repo
350 scm_ = get_scm(os.path.dirname(full_path))
350 scm_ = get_scm(os.path.dirname(full_path))
351 log.debug('path: %s is a vcs object:%s, not valid '
351 log.debug('path: %s is a vcs object:%s, not valid '
352 'repo group' % (full_path, scm_))
352 'repo group' % (full_path, scm_))
353 return False
353 return False
354 except VCSError:
354 except VCSError:
355 pass
355 pass
356
356
357 # check if it's a valid path
357 # check if it's a valid path
358 if skip_path_check or os.path.isdir(full_path):
358 if skip_path_check or os.path.isdir(full_path):
359 log.debug('path: %s is a valid repo group !', full_path)
359 log.debug('path: %s is a valid repo group !', full_path)
360 return True
360 return True
361
361
362 log.debug('path: %s is not a valid repo group !', full_path)
362 log.debug('path: %s is not a valid repo group !', full_path)
363 return False
363 return False
364
364
365
365
366 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
366 def ask_ok(prompt, retries=4, complaint='[y]es or [n]o please!'):
367 while True:
367 while True:
368 ok = raw_input(prompt)
368 ok = raw_input(prompt)
369 if ok.lower() in ('y', 'ye', 'yes'):
369 if ok.lower() in ('y', 'ye', 'yes'):
370 return True
370 return True
371 if ok.lower() in ('n', 'no', 'nop', 'nope'):
371 if ok.lower() in ('n', 'no', 'nop', 'nope'):
372 return False
372 return False
373 retries = retries - 1
373 retries = retries - 1
374 if retries < 0:
374 if retries < 0:
375 raise IOError
375 raise IOError
376 print(complaint)
376 print(complaint)
377
377
378 # propagated from mercurial documentation
378 # propagated from mercurial documentation
379 ui_sections = [
379 ui_sections = [
380 'alias', 'auth',
380 'alias', 'auth',
381 'decode/encode', 'defaults',
381 'decode/encode', 'defaults',
382 'diff', 'email',
382 'diff', 'email',
383 'extensions', 'format',
383 'extensions', 'format',
384 'merge-patterns', 'merge-tools',
384 'merge-patterns', 'merge-tools',
385 'hooks', 'http_proxy',
385 'hooks', 'http_proxy',
386 'smtp', 'patch',
386 'smtp', 'patch',
387 'paths', 'profiling',
387 'paths', 'profiling',
388 'server', 'trusted',
388 'server', 'trusted',
389 'ui', 'web', ]
389 'ui', 'web', ]
390
390
391
391
392 def config_data_from_db(clear_session=True, repo=None):
392 def config_data_from_db(clear_session=True, repo=None):
393 """
393 """
394 Read the configuration data from the database and return configuration
394 Read the configuration data from the database and return configuration
395 tuples.
395 tuples.
396 """
396 """
397 from rhodecode.model.settings import VcsSettingsModel
397 from rhodecode.model.settings import VcsSettingsModel
398
398
399 config = []
399 config = []
400
400
401 sa = meta.Session()
401 sa = meta.Session()
402 settings_model = VcsSettingsModel(repo=repo, sa=sa)
402 settings_model = VcsSettingsModel(repo=repo, sa=sa)
403
403
404 ui_settings = settings_model.get_ui_settings()
404 ui_settings = settings_model.get_ui_settings()
405
405
406 for setting in ui_settings:
406 for setting in ui_settings:
407 if setting.active:
407 if setting.active:
408 log.debug(
408 log.debug(
409 'settings ui from db: [%s] %s=%s',
409 'settings ui from db: [%s] %s=%s',
410 setting.section, setting.key, setting.value)
410 setting.section, setting.key, setting.value)
411 config.append((
411 config.append((
412 safe_str(setting.section), safe_str(setting.key),
412 safe_str(setting.section), safe_str(setting.key),
413 safe_str(setting.value)))
413 safe_str(setting.value)))
414 if setting.key == 'push_ssl':
414 if setting.key == 'push_ssl':
415 # force set push_ssl requirement to False, rhodecode
415 # force set push_ssl requirement to False, rhodecode
416 # handles that
416 # handles that
417 config.append((
417 config.append((
418 safe_str(setting.section), safe_str(setting.key), False))
418 safe_str(setting.section), safe_str(setting.key), False))
419 if clear_session:
419 if clear_session:
420 meta.Session.remove()
420 meta.Session.remove()
421
421
422 # TODO: mikhail: probably it makes no sense to re-read hooks information.
422 # TODO: mikhail: probably it makes no sense to re-read hooks information.
423 # It's already there and activated/deactivated
423 # It's already there and activated/deactivated
424 skip_entries = []
424 skip_entries = []
425 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
425 enabled_hook_classes = get_enabled_hook_classes(ui_settings)
426 if 'pull' not in enabled_hook_classes:
426 if 'pull' not in enabled_hook_classes:
427 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
427 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PULL))
428 if 'push' not in enabled_hook_classes:
428 if 'push' not in enabled_hook_classes:
429 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
429 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRE_PUSH))
430 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
430 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PRETX_PUSH))
431 skip_entries.append(('hooks', RhodeCodeUi.HOOK_PUSH_KEY))
431
432
432 config = [entry for entry in config if entry[:2] not in skip_entries]
433 config = [entry for entry in config if entry[:2] not in skip_entries]
433
434
434 return config
435 return config
435
436
436
437
437 def make_db_config(clear_session=True, repo=None):
438 def make_db_config(clear_session=True, repo=None):
438 """
439 """
439 Create a :class:`Config` instance based on the values in the database.
440 Create a :class:`Config` instance based on the values in the database.
440 """
441 """
441 config = Config()
442 config = Config()
442 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
443 config_data = config_data_from_db(clear_session=clear_session, repo=repo)
443 for section, option, value in config_data:
444 for section, option, value in config_data:
444 config.set(section, option, value)
445 config.set(section, option, value)
445 return config
446 return config
446
447
447
448
448 def get_enabled_hook_classes(ui_settings):
449 def get_enabled_hook_classes(ui_settings):
449 """
450 """
450 Return the enabled hook classes.
451 Return the enabled hook classes.
451
452
452 :param ui_settings: List of ui_settings as returned
453 :param ui_settings: List of ui_settings as returned
453 by :meth:`VcsSettingsModel.get_ui_settings`
454 by :meth:`VcsSettingsModel.get_ui_settings`
454
455
455 :return: a list with the enabled hook classes. The order is not guaranteed.
456 :return: a list with the enabled hook classes. The order is not guaranteed.
456 :rtype: list
457 :rtype: list
457 """
458 """
458 enabled_hooks = []
459 enabled_hooks = []
459 active_hook_keys = [
460 active_hook_keys = [
460 key for section, key, value, active in ui_settings
461 key for section, key, value, active in ui_settings
461 if section == 'hooks' and active]
462 if section == 'hooks' and active]
462
463
463 hook_names = {
464 hook_names = {
464 RhodeCodeUi.HOOK_PUSH: 'push',
465 RhodeCodeUi.HOOK_PUSH: 'push',
465 RhodeCodeUi.HOOK_PULL: 'pull',
466 RhodeCodeUi.HOOK_PULL: 'pull',
466 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
467 RhodeCodeUi.HOOK_REPO_SIZE: 'repo_size'
467 }
468 }
468
469
469 for key in active_hook_keys:
470 for key in active_hook_keys:
470 hook = hook_names.get(key)
471 hook = hook_names.get(key)
471 if hook:
472 if hook:
472 enabled_hooks.append(hook)
473 enabled_hooks.append(hook)
473
474
474 return enabled_hooks
475 return enabled_hooks
475
476
476
477
477 def set_rhodecode_config(config):
478 def set_rhodecode_config(config):
478 """
479 """
479 Updates pylons config with new settings from database
480 Updates pylons config with new settings from database
480
481
481 :param config:
482 :param config:
482 """
483 """
483 from rhodecode.model.settings import SettingsModel
484 from rhodecode.model.settings import SettingsModel
484 app_settings = SettingsModel().get_all_settings()
485 app_settings = SettingsModel().get_all_settings()
485
486
486 for k, v in app_settings.items():
487 for k, v in app_settings.items():
487 config[k] = v
488 config[k] = v
488
489
489
490
490 def get_rhodecode_realm():
491 def get_rhodecode_realm():
491 """
492 """
492 Return the rhodecode realm from database.
493 Return the rhodecode realm from database.
493 """
494 """
494 from rhodecode.model.settings import SettingsModel
495 from rhodecode.model.settings import SettingsModel
495 realm = SettingsModel().get_setting_by_name('realm')
496 realm = SettingsModel().get_setting_by_name('realm')
496 return safe_str(realm.app_settings_value)
497 return safe_str(realm.app_settings_value)
497
498
498
499
499 def get_rhodecode_base_path():
500 def get_rhodecode_base_path():
500 """
501 """
501 Returns the base path. The base path is the filesystem path which points
502 Returns the base path. The base path is the filesystem path which points
502 to the repository store.
503 to the repository store.
503 """
504 """
504 from rhodecode.model.settings import SettingsModel
505 from rhodecode.model.settings import SettingsModel
505 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
506 paths_ui = SettingsModel().get_ui_by_section_and_key('paths', '/')
506 return safe_str(paths_ui.ui_value)
507 return safe_str(paths_ui.ui_value)
507
508
508
509
509 def map_groups(path):
510 def map_groups(path):
510 """
511 """
511 Given a full path to a repository, create all nested groups that this
512 Given a full path to a repository, create all nested groups that this
512 repo is inside. This function creates parent-child relationships between
513 repo is inside. This function creates parent-child relationships between
513 groups and creates default perms for all new groups.
514 groups and creates default perms for all new groups.
514
515
515 :param paths: full path to repository
516 :param paths: full path to repository
516 """
517 """
517 from rhodecode.model.repo_group import RepoGroupModel
518 from rhodecode.model.repo_group import RepoGroupModel
518 sa = meta.Session()
519 sa = meta.Session()
519 groups = path.split(Repository.NAME_SEP)
520 groups = path.split(Repository.NAME_SEP)
520 parent = None
521 parent = None
521 group = None
522 group = None
522
523
523 # last element is repo in nested groups structure
524 # last element is repo in nested groups structure
524 groups = groups[:-1]
525 groups = groups[:-1]
525 rgm = RepoGroupModel(sa)
526 rgm = RepoGroupModel(sa)
526 owner = User.get_first_super_admin()
527 owner = User.get_first_super_admin()
527 for lvl, group_name in enumerate(groups):
528 for lvl, group_name in enumerate(groups):
528 group_name = '/'.join(groups[:lvl] + [group_name])
529 group_name = '/'.join(groups[:lvl] + [group_name])
529 group = RepoGroup.get_by_group_name(group_name)
530 group = RepoGroup.get_by_group_name(group_name)
530 desc = '%s group' % group_name
531 desc = '%s group' % group_name
531
532
532 # skip folders that are now removed repos
533 # skip folders that are now removed repos
533 if REMOVED_REPO_PAT.match(group_name):
534 if REMOVED_REPO_PAT.match(group_name):
534 break
535 break
535
536
536 if group is None:
537 if group is None:
537 log.debug('creating group level: %s group_name: %s',
538 log.debug('creating group level: %s group_name: %s',
538 lvl, group_name)
539 lvl, group_name)
539 group = RepoGroup(group_name, parent)
540 group = RepoGroup(group_name, parent)
540 group.group_description = desc
541 group.group_description = desc
541 group.user = owner
542 group.user = owner
542 sa.add(group)
543 sa.add(group)
543 perm_obj = rgm._create_default_perms(group)
544 perm_obj = rgm._create_default_perms(group)
544 sa.add(perm_obj)
545 sa.add(perm_obj)
545 sa.flush()
546 sa.flush()
546
547
547 parent = group
548 parent = group
548 return group
549 return group
549
550
550
551
551 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
552 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
552 """
553 """
553 maps all repos given in initial_repo_list, non existing repositories
554 maps all repos given in initial_repo_list, non existing repositories
554 are created, if remove_obsolete is True it also checks for db entries
555 are created, if remove_obsolete is True it also checks for db entries
555 that are not in initial_repo_list and removes them.
556 that are not in initial_repo_list and removes them.
556
557
557 :param initial_repo_list: list of repositories found by scanning methods
558 :param initial_repo_list: list of repositories found by scanning methods
558 :param remove_obsolete: check for obsolete entries in database
559 :param remove_obsolete: check for obsolete entries in database
559 """
560 """
560 from rhodecode.model.repo import RepoModel
561 from rhodecode.model.repo import RepoModel
561 from rhodecode.model.scm import ScmModel
562 from rhodecode.model.scm import ScmModel
562 from rhodecode.model.repo_group import RepoGroupModel
563 from rhodecode.model.repo_group import RepoGroupModel
563 from rhodecode.model.settings import SettingsModel
564 from rhodecode.model.settings import SettingsModel
564
565
565 sa = meta.Session()
566 sa = meta.Session()
566 repo_model = RepoModel()
567 repo_model = RepoModel()
567 user = User.get_first_super_admin()
568 user = User.get_first_super_admin()
568 added = []
569 added = []
569
570
570 # creation defaults
571 # creation defaults
571 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
572 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
572 enable_statistics = defs.get('repo_enable_statistics')
573 enable_statistics = defs.get('repo_enable_statistics')
573 enable_locking = defs.get('repo_enable_locking')
574 enable_locking = defs.get('repo_enable_locking')
574 enable_downloads = defs.get('repo_enable_downloads')
575 enable_downloads = defs.get('repo_enable_downloads')
575 private = defs.get('repo_private')
576 private = defs.get('repo_private')
576
577
577 for name, repo in initial_repo_list.items():
578 for name, repo in initial_repo_list.items():
578 group = map_groups(name)
579 group = map_groups(name)
579 unicode_name = safe_unicode(name)
580 unicode_name = safe_unicode(name)
580 db_repo = repo_model.get_by_repo_name(unicode_name)
581 db_repo = repo_model.get_by_repo_name(unicode_name)
581 # found repo that is on filesystem not in RhodeCode database
582 # found repo that is on filesystem not in RhodeCode database
582 if not db_repo:
583 if not db_repo:
583 log.info('repository %s not found, creating now', name)
584 log.info('repository %s not found, creating now', name)
584 added.append(name)
585 added.append(name)
585 desc = (repo.description
586 desc = (repo.description
586 if repo.description != 'unknown'
587 if repo.description != 'unknown'
587 else '%s repository' % name)
588 else '%s repository' % name)
588
589
589 db_repo = repo_model._create_repo(
590 db_repo = repo_model._create_repo(
590 repo_name=name,
591 repo_name=name,
591 repo_type=repo.alias,
592 repo_type=repo.alias,
592 description=desc,
593 description=desc,
593 repo_group=getattr(group, 'group_id', None),
594 repo_group=getattr(group, 'group_id', None),
594 owner=user,
595 owner=user,
595 enable_locking=enable_locking,
596 enable_locking=enable_locking,
596 enable_downloads=enable_downloads,
597 enable_downloads=enable_downloads,
597 enable_statistics=enable_statistics,
598 enable_statistics=enable_statistics,
598 private=private,
599 private=private,
599 state=Repository.STATE_CREATED
600 state=Repository.STATE_CREATED
600 )
601 )
601 sa.commit()
602 sa.commit()
602 # we added that repo just now, and make sure we updated server info
603 # we added that repo just now, and make sure we updated server info
603 if db_repo.repo_type == 'git':
604 if db_repo.repo_type == 'git':
604 git_repo = db_repo.scm_instance()
605 git_repo = db_repo.scm_instance()
605 # update repository server-info
606 # update repository server-info
606 log.debug('Running update server info')
607 log.debug('Running update server info')
607 git_repo._update_server_info()
608 git_repo._update_server_info()
608
609
609 db_repo.update_commit_cache()
610 db_repo.update_commit_cache()
610
611
611 config = db_repo._config
612 config = db_repo._config
612 config.set('extensions', 'largefiles', '')
613 config.set('extensions', 'largefiles', '')
613 ScmModel().install_hooks(
614 ScmModel().install_hooks(
614 db_repo.scm_instance(config=config),
615 db_repo.scm_instance(config=config),
615 repo_type=db_repo.repo_type)
616 repo_type=db_repo.repo_type)
616
617
617 removed = []
618 removed = []
618 if remove_obsolete:
619 if remove_obsolete:
619 # remove from database those repositories that are not in the filesystem
620 # remove from database those repositories that are not in the filesystem
620 for repo in sa.query(Repository).all():
621 for repo in sa.query(Repository).all():
621 if repo.repo_name not in initial_repo_list.keys():
622 if repo.repo_name not in initial_repo_list.keys():
622 log.debug("Removing non-existing repository found in db `%s`",
623 log.debug("Removing non-existing repository found in db `%s`",
623 repo.repo_name)
624 repo.repo_name)
624 try:
625 try:
625 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
626 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
626 sa.commit()
627 sa.commit()
627 removed.append(repo.repo_name)
628 removed.append(repo.repo_name)
628 except Exception:
629 except Exception:
629 # don't hold further removals on error
630 # don't hold further removals on error
630 log.error(traceback.format_exc())
631 log.error(traceback.format_exc())
631 sa.rollback()
632 sa.rollback()
632
633
633 def splitter(full_repo_name):
634 def splitter(full_repo_name):
634 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
635 _parts = full_repo_name.rsplit(RepoGroup.url_sep(), 1)
635 gr_name = None
636 gr_name = None
636 if len(_parts) == 2:
637 if len(_parts) == 2:
637 gr_name = _parts[0]
638 gr_name = _parts[0]
638 return gr_name
639 return gr_name
639
640
640 initial_repo_group_list = [splitter(x) for x in
641 initial_repo_group_list = [splitter(x) for x in
641 initial_repo_list.keys() if splitter(x)]
642 initial_repo_list.keys() if splitter(x)]
642
643
643 # remove from database those repository groups that are not in the
644 # remove from database those repository groups that are not in the
644 # filesystem due to parent child relationships we need to delete them
645 # filesystem due to parent child relationships we need to delete them
645 # in a specific order of most nested first
646 # in a specific order of most nested first
646 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
647 all_groups = [x.group_name for x in sa.query(RepoGroup).all()]
647 nested_sort = lambda gr: len(gr.split('/'))
648 nested_sort = lambda gr: len(gr.split('/'))
648 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
649 for group_name in sorted(all_groups, key=nested_sort, reverse=True):
649 if group_name not in initial_repo_group_list:
650 if group_name not in initial_repo_group_list:
650 repo_group = RepoGroup.get_by_group_name(group_name)
651 repo_group = RepoGroup.get_by_group_name(group_name)
651 if (repo_group.children.all() or
652 if (repo_group.children.all() or
652 not RepoGroupModel().check_exist_filesystem(
653 not RepoGroupModel().check_exist_filesystem(
653 group_name=group_name, exc_on_failure=False)):
654 group_name=group_name, exc_on_failure=False)):
654 continue
655 continue
655
656
656 log.info(
657 log.info(
657 'Removing non-existing repository group found in db `%s`',
658 'Removing non-existing repository group found in db `%s`',
658 group_name)
659 group_name)
659 try:
660 try:
660 RepoGroupModel(sa).delete(group_name, fs_remove=False)
661 RepoGroupModel(sa).delete(group_name, fs_remove=False)
661 sa.commit()
662 sa.commit()
662 removed.append(group_name)
663 removed.append(group_name)
663 except Exception:
664 except Exception:
664 # don't hold further removals on error
665 # don't hold further removals on error
665 log.exception(
666 log.exception(
666 'Unable to remove repository group `%s`',
667 'Unable to remove repository group `%s`',
667 group_name)
668 group_name)
668 sa.rollback()
669 sa.rollback()
669 raise
670 raise
670
671
671 return added, removed
672 return added, removed
672
673
673
674
674 def get_default_cache_settings(settings):
675 def get_default_cache_settings(settings):
675 cache_settings = {}
676 cache_settings = {}
676 for key in settings.keys():
677 for key in settings.keys():
677 for prefix in ['beaker.cache.', 'cache.']:
678 for prefix in ['beaker.cache.', 'cache.']:
678 if key.startswith(prefix):
679 if key.startswith(prefix):
679 name = key.split(prefix)[1].strip()
680 name = key.split(prefix)[1].strip()
680 cache_settings[name] = settings[key].strip()
681 cache_settings[name] = settings[key].strip()
681 return cache_settings
682 return cache_settings
682
683
683
684
684 # set cache regions for beaker so celery can utilise it
685 # set cache regions for beaker so celery can utilise it
685 def add_cache(settings):
686 def add_cache(settings):
686 from rhodecode.lib import caches
687 from rhodecode.lib import caches
687 cache_settings = {'regions': None}
688 cache_settings = {'regions': None}
688 # main cache settings used as default ...
689 # main cache settings used as default ...
689 cache_settings.update(get_default_cache_settings(settings))
690 cache_settings.update(get_default_cache_settings(settings))
690
691
691 if cache_settings['regions']:
692 if cache_settings['regions']:
692 for region in cache_settings['regions'].split(','):
693 for region in cache_settings['regions'].split(','):
693 region = region.strip()
694 region = region.strip()
694 region_settings = {}
695 region_settings = {}
695 for key, value in cache_settings.items():
696 for key, value in cache_settings.items():
696 if key.startswith(region):
697 if key.startswith(region):
697 region_settings[key.split('.')[1]] = value
698 region_settings[key.split('.')[1]] = value
698
699
699 caches.configure_cache_region(
700 caches.configure_cache_region(
700 region, region_settings, cache_settings)
701 region, region_settings, cache_settings)
701
702
702
703
703 def load_rcextensions(root_path):
704 def load_rcextensions(root_path):
704 import rhodecode
705 import rhodecode
705 from rhodecode.config import conf
706 from rhodecode.config import conf
706
707
707 path = os.path.join(root_path, 'rcextensions', '__init__.py')
708 path = os.path.join(root_path, 'rcextensions', '__init__.py')
708 if os.path.isfile(path):
709 if os.path.isfile(path):
709 rcext = create_module('rc', path)
710 rcext = create_module('rc', path)
710 EXT = rhodecode.EXTENSIONS = rcext
711 EXT = rhodecode.EXTENSIONS = rcext
711 log.debug('Found rcextensions now loading %s...', rcext)
712 log.debug('Found rcextensions now loading %s...', rcext)
712
713
713 # Additional mappings that are not present in the pygments lexers
714 # Additional mappings that are not present in the pygments lexers
714 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
715 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
715
716
716 # auto check if the module is not missing any data, set to default if is
717 # auto check if the module is not missing any data, set to default if is
717 # this will help autoupdate new feature of rcext module
718 # this will help autoupdate new feature of rcext module
718 #from rhodecode.config import rcextensions
719 #from rhodecode.config import rcextensions
719 #for k in dir(rcextensions):
720 #for k in dir(rcextensions):
720 # if not k.startswith('_') and not hasattr(EXT, k):
721 # if not k.startswith('_') and not hasattr(EXT, k):
721 # setattr(EXT, k, getattr(rcextensions, k))
722 # setattr(EXT, k, getattr(rcextensions, k))
722
723
723
724
724 def get_custom_lexer(extension):
725 def get_custom_lexer(extension):
725 """
726 """
726 returns a custom lexer if it is defined in rcextensions module, or None
727 returns a custom lexer if it is defined in rcextensions module, or None
727 if there's no custom lexer defined
728 if there's no custom lexer defined
728 """
729 """
729 import rhodecode
730 import rhodecode
730 from pygments import lexers
731 from pygments import lexers
731
732
732 # custom override made by RhodeCode
733 # custom override made by RhodeCode
733 if extension in ['mako']:
734 if extension in ['mako']:
734 return lexers.get_lexer_by_name('html+mako')
735 return lexers.get_lexer_by_name('html+mako')
735
736
736 # check if we didn't define this extension as other lexer
737 # check if we didn't define this extension as other lexer
737 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
738 extensions = rhodecode.EXTENSIONS and getattr(rhodecode.EXTENSIONS, 'EXTRA_LEXERS', None)
738 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
739 if extensions and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
739 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
740 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
740 return lexers.get_lexer_by_name(_lexer_name)
741 return lexers.get_lexer_by_name(_lexer_name)
741
742
742
743
743 #==============================================================================
744 #==============================================================================
744 # TEST FUNCTIONS AND CREATORS
745 # TEST FUNCTIONS AND CREATORS
745 #==============================================================================
746 #==============================================================================
746 def create_test_index(repo_location, config):
747 def create_test_index(repo_location, config):
747 """
748 """
748 Makes default test index.
749 Makes default test index.
749 """
750 """
750 import rc_testdata
751 import rc_testdata
751
752
752 rc_testdata.extract_search_index(
753 rc_testdata.extract_search_index(
753 'vcs_search_index', os.path.dirname(config['search.location']))
754 'vcs_search_index', os.path.dirname(config['search.location']))
754
755
755
756
756 def create_test_directory(test_path):
757 def create_test_directory(test_path):
757 """
758 """
758 Create test directory if it doesn't exist.
759 Create test directory if it doesn't exist.
759 """
760 """
760 if not os.path.isdir(test_path):
761 if not os.path.isdir(test_path):
761 log.debug('Creating testdir %s', test_path)
762 log.debug('Creating testdir %s', test_path)
762 os.makedirs(test_path)
763 os.makedirs(test_path)
763
764
764
765
765 def create_test_database(test_path, config):
766 def create_test_database(test_path, config):
766 """
767 """
767 Makes a fresh database.
768 Makes a fresh database.
768 """
769 """
769 from rhodecode.lib.db_manage import DbManage
770 from rhodecode.lib.db_manage import DbManage
770
771
771 # PART ONE create db
772 # PART ONE create db
772 dbconf = config['sqlalchemy.db1.url']
773 dbconf = config['sqlalchemy.db1.url']
773 log.debug('making test db %s', dbconf)
774 log.debug('making test db %s', dbconf)
774
775
775 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
776 dbmanage = DbManage(log_sql=False, dbconf=dbconf, root=config['here'],
776 tests=True, cli_args={'force_ask': True})
777 tests=True, cli_args={'force_ask': True})
777 dbmanage.create_tables(override=True)
778 dbmanage.create_tables(override=True)
778 dbmanage.set_db_version()
779 dbmanage.set_db_version()
779 # for tests dynamically set new root paths based on generated content
780 # for tests dynamically set new root paths based on generated content
780 dbmanage.create_settings(dbmanage.config_prompt(test_path))
781 dbmanage.create_settings(dbmanage.config_prompt(test_path))
781 dbmanage.create_default_user()
782 dbmanage.create_default_user()
782 dbmanage.create_test_admin_and_users()
783 dbmanage.create_test_admin_and_users()
783 dbmanage.create_permissions()
784 dbmanage.create_permissions()
784 dbmanage.populate_default_permissions()
785 dbmanage.populate_default_permissions()
785 Session().commit()
786 Session().commit()
786
787
787
788
788 def create_test_repositories(test_path, config):
789 def create_test_repositories(test_path, config):
789 """
790 """
790 Creates test repositories in the temporary directory. Repositories are
791 Creates test repositories in the temporary directory. Repositories are
791 extracted from archives within the rc_testdata package.
792 extracted from archives within the rc_testdata package.
792 """
793 """
793 import rc_testdata
794 import rc_testdata
794 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
795 from rhodecode.tests import HG_REPO, GIT_REPO, SVN_REPO
795
796
796 log.debug('making test vcs repositories')
797 log.debug('making test vcs repositories')
797
798
798 idx_path = config['search.location']
799 idx_path = config['search.location']
799 data_path = config['cache_dir']
800 data_path = config['cache_dir']
800
801
801 # clean index and data
802 # clean index and data
802 if idx_path and os.path.exists(idx_path):
803 if idx_path and os.path.exists(idx_path):
803 log.debug('remove %s', idx_path)
804 log.debug('remove %s', idx_path)
804 shutil.rmtree(idx_path)
805 shutil.rmtree(idx_path)
805
806
806 if data_path and os.path.exists(data_path):
807 if data_path and os.path.exists(data_path):
807 log.debug('remove %s', data_path)
808 log.debug('remove %s', data_path)
808 shutil.rmtree(data_path)
809 shutil.rmtree(data_path)
809
810
810 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
811 rc_testdata.extract_hg_dump('vcs_test_hg', jn(test_path, HG_REPO))
811 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
812 rc_testdata.extract_git_dump('vcs_test_git', jn(test_path, GIT_REPO))
812
813
813 # Note: Subversion is in the process of being integrated with the system,
814 # Note: Subversion is in the process of being integrated with the system,
814 # until we have a properly packed version of the test svn repository, this
815 # until we have a properly packed version of the test svn repository, this
815 # tries to copy over the repo from a package "rc_testdata"
816 # tries to copy over the repo from a package "rc_testdata"
816 svn_repo_path = rc_testdata.get_svn_repo_archive()
817 svn_repo_path = rc_testdata.get_svn_repo_archive()
817 with tarfile.open(svn_repo_path) as tar:
818 with tarfile.open(svn_repo_path) as tar:
818 tar.extractall(jn(test_path, SVN_REPO))
819 tar.extractall(jn(test_path, SVN_REPO))
819
820
820
821
821 #==============================================================================
822 #==============================================================================
822 # PASTER COMMANDS
823 # PASTER COMMANDS
823 #==============================================================================
824 #==============================================================================
824 class BasePasterCommand(Command):
825 class BasePasterCommand(Command):
825 """
826 """
826 Abstract Base Class for paster commands.
827 Abstract Base Class for paster commands.
827
828
828 The celery commands are somewhat aggressive about loading
829 The celery commands are somewhat aggressive about loading
829 celery.conf, and since our module sets the `CELERY_LOADER`
830 celery.conf, and since our module sets the `CELERY_LOADER`
830 environment variable to our loader, we have to bootstrap a bit and
831 environment variable to our loader, we have to bootstrap a bit and
831 make sure we've had a chance to load the pylons config off of the
832 make sure we've had a chance to load the pylons config off of the
832 command line, otherwise everything fails.
833 command line, otherwise everything fails.
833 """
834 """
834 min_args = 1
835 min_args = 1
835 min_args_error = "Please provide a paster config file as an argument."
836 min_args_error = "Please provide a paster config file as an argument."
836 takes_config_file = 1
837 takes_config_file = 1
837 requires_config_file = True
838 requires_config_file = True
838
839
839 def notify_msg(self, msg, log=False):
840 def notify_msg(self, msg, log=False):
840 """Make a notification to user, additionally if logger is passed
841 """Make a notification to user, additionally if logger is passed
841 it logs this action using given logger
842 it logs this action using given logger
842
843
843 :param msg: message that will be printed to user
844 :param msg: message that will be printed to user
844 :param log: logging instance, to use to additionally log this message
845 :param log: logging instance, to use to additionally log this message
845
846
846 """
847 """
847 if log and isinstance(log, logging):
848 if log and isinstance(log, logging):
848 log(msg)
849 log(msg)
849
850
850 def run(self, args):
851 def run(self, args):
851 """
852 """
852 Overrides Command.run
853 Overrides Command.run
853
854
854 Checks for a config file argument and loads it.
855 Checks for a config file argument and loads it.
855 """
856 """
856 if len(args) < self.min_args:
857 if len(args) < self.min_args:
857 raise BadCommand(
858 raise BadCommand(
858 self.min_args_error % {'min_args': self.min_args,
859 self.min_args_error % {'min_args': self.min_args,
859 'actual_args': len(args)})
860 'actual_args': len(args)})
860
861
861 # Decrement because we're going to lob off the first argument.
862 # Decrement because we're going to lob off the first argument.
862 # @@ This is hacky
863 # @@ This is hacky
863 self.min_args -= 1
864 self.min_args -= 1
864 self.bootstrap_config(args[0])
865 self.bootstrap_config(args[0])
865 self.update_parser()
866 self.update_parser()
866 return super(BasePasterCommand, self).run(args[1:])
867 return super(BasePasterCommand, self).run(args[1:])
867
868
868 def update_parser(self):
869 def update_parser(self):
869 """
870 """
870 Abstract method. Allows for the class' parser to be updated
871 Abstract method. Allows for the class' parser to be updated
871 before the superclass' `run` method is called. Necessary to
872 before the superclass' `run` method is called. Necessary to
872 allow options/arguments to be passed through to the underlying
873 allow options/arguments to be passed through to the underlying
873 celery command.
874 celery command.
874 """
875 """
875 raise NotImplementedError("Abstract Method.")
876 raise NotImplementedError("Abstract Method.")
876
877
877 def bootstrap_config(self, conf):
878 def bootstrap_config(self, conf):
878 """
879 """
879 Loads the pylons configuration.
880 Loads the pylons configuration.
880 """
881 """
881 from pylons import config as pylonsconfig
882 from pylons import config as pylonsconfig
882
883
883 self.path_to_ini_file = os.path.realpath(conf)
884 self.path_to_ini_file = os.path.realpath(conf)
884 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
885 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
885 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
886 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
886
887
887 def _init_session(self):
888 def _init_session(self):
888 """
889 """
889 Inits SqlAlchemy Session
890 Inits SqlAlchemy Session
890 """
891 """
891 logging.config.fileConfig(self.path_to_ini_file)
892 logging.config.fileConfig(self.path_to_ini_file)
892 from pylons import config
893 from pylons import config
893 from rhodecode.config.utils import initialize_database
894 from rhodecode.config.utils import initialize_database
894
895
895 # get to remove repos !!
896 # get to remove repos !!
896 add_cache(config)
897 add_cache(config)
897 initialize_database(config)
898 initialize_database(config)
898
899
899
900
900 @decorator.decorator
901 @decorator.decorator
901 def jsonify(func, *args, **kwargs):
902 def jsonify(func, *args, **kwargs):
902 """Action decorator that formats output for JSON
903 """Action decorator that formats output for JSON
903
904
904 Given a function that will return content, this decorator will turn
905 Given a function that will return content, this decorator will turn
905 the result into JSON, with a content-type of 'application/json' and
906 the result into JSON, with a content-type of 'application/json' and
906 output it.
907 output it.
907
908
908 """
909 """
909 from pylons.decorators.util import get_pylons
910 from pylons.decorators.util import get_pylons
910 from rhodecode.lib.ext_json import json
911 from rhodecode.lib.ext_json import json
911 pylons = get_pylons(args)
912 pylons = get_pylons(args)
912 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
913 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
913 data = func(*args, **kwargs)
914 data = func(*args, **kwargs)
914 if isinstance(data, (list, tuple)):
915 if isinstance(data, (list, tuple)):
915 msg = "JSON responses with Array envelopes are susceptible to " \
916 msg = "JSON responses with Array envelopes are susceptible to " \
916 "cross-site data leak attacks, see " \
917 "cross-site data leak attacks, see " \
917 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
918 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
918 warnings.warn(msg, Warning, 2)
919 warnings.warn(msg, Warning, 2)
919 log.warning(msg)
920 log.warning(msg)
920 log.debug("Returning JSON wrapped action output")
921 log.debug("Returning JSON wrapped action output")
921 return json.dumps(data, encoding='utf-8')
922 return json.dumps(data, encoding='utf-8')
922
923
923
924
924 class PartialRenderer(object):
925 class PartialRenderer(object):
925 """
926 """
926 Partial renderer used to render chunks of html used in datagrids
927 Partial renderer used to render chunks of html used in datagrids
927 use like::
928 use like::
928
929
929 _render = PartialRenderer('data_table/_dt_elements.mako')
930 _render = PartialRenderer('data_table/_dt_elements.mako')
930 _render('quick_menu', args, kwargs)
931 _render('quick_menu', args, kwargs)
931 PartialRenderer.h,
932 PartialRenderer.h,
932 c,
933 c,
933 _,
934 _,
934 ungettext
935 ungettext
935 are the template stuff initialized inside and can be re-used later
936 are the template stuff initialized inside and can be re-used later
936
937
937 :param tmpl_name: template path relate to /templates/ dir
938 :param tmpl_name: template path relate to /templates/ dir
938 """
939 """
939
940
940 def __init__(self, tmpl_name):
941 def __init__(self, tmpl_name):
941 import rhodecode
942 import rhodecode
942 from pylons import request, tmpl_context as c
943 from pylons import request, tmpl_context as c
943 from pylons.i18n.translation import _, ungettext
944 from pylons.i18n.translation import _, ungettext
944 from rhodecode.lib import helpers as h
945 from rhodecode.lib import helpers as h
945
946
946 self.tmpl_name = tmpl_name
947 self.tmpl_name = tmpl_name
947 self.rhodecode = rhodecode
948 self.rhodecode = rhodecode
948 self.c = c
949 self.c = c
949 self._ = _
950 self._ = _
950 self.ungettext = ungettext
951 self.ungettext = ungettext
951 self.h = h
952 self.h = h
952 self.request = request
953 self.request = request
953
954
954 def _mako_lookup(self):
955 def _mako_lookup(self):
955 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
956 _tmpl_lookup = self.rhodecode.CONFIG['pylons.app_globals'].mako_lookup
956 return _tmpl_lookup.get_template(self.tmpl_name)
957 return _tmpl_lookup.get_template(self.tmpl_name)
957
958
958 def _update_kwargs_for_render(self, kwargs):
959 def _update_kwargs_for_render(self, kwargs):
959 """
960 """
960 Inject params required for Mako rendering
961 Inject params required for Mako rendering
961 """
962 """
962 _kwargs = {
963 _kwargs = {
963 '_': self._,
964 '_': self._,
964 'h': self.h,
965 'h': self.h,
965 'c': self.c,
966 'c': self.c,
966 'request': self.request,
967 'request': self.request,
967 'ungettext': self.ungettext,
968 'ungettext': self.ungettext,
968 }
969 }
969 _kwargs.update(kwargs)
970 _kwargs.update(kwargs)
970 return _kwargs
971 return _kwargs
971
972
972 def _render_with_exc(self, render_func, args, kwargs):
973 def _render_with_exc(self, render_func, args, kwargs):
973 try:
974 try:
974 return render_func.render(*args, **kwargs)
975 return render_func.render(*args, **kwargs)
975 except:
976 except:
976 log.error(exceptions.text_error_template().render())
977 log.error(exceptions.text_error_template().render())
977 raise
978 raise
978
979
979 def _get_template(self, template_obj, def_name):
980 def _get_template(self, template_obj, def_name):
980 if def_name:
981 if def_name:
981 tmpl = template_obj.get_def(def_name)
982 tmpl = template_obj.get_def(def_name)
982 else:
983 else:
983 tmpl = template_obj
984 tmpl = template_obj
984 return tmpl
985 return tmpl
985
986
986 def render(self, def_name, *args, **kwargs):
987 def render(self, def_name, *args, **kwargs):
987 lookup_obj = self._mako_lookup()
988 lookup_obj = self._mako_lookup()
988 tmpl = self._get_template(lookup_obj, def_name=def_name)
989 tmpl = self._get_template(lookup_obj, def_name=def_name)
989 kwargs = self._update_kwargs_for_render(kwargs)
990 kwargs = self._update_kwargs_for_render(kwargs)
990 return self._render_with_exc(tmpl, args, kwargs)
991 return self._render_with_exc(tmpl, args, kwargs)
991
992
992 def __call__(self, tmpl, *args, **kwargs):
993 def __call__(self, tmpl, *args, **kwargs):
993 return self.render(tmpl, *args, **kwargs)
994 return self.render(tmpl, *args, **kwargs)
994
995
995
996
996 def password_changed(auth_user, session):
997 def password_changed(auth_user, session):
997 # Never report password change in case of default user or anonymous user.
998 # Never report password change in case of default user or anonymous user.
998 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
999 if auth_user.username == User.DEFAULT_USER or auth_user.user_id is None:
999 return False
1000 return False
1000
1001
1001 password_hash = md5(auth_user.password) if auth_user.password else None
1002 password_hash = md5(auth_user.password) if auth_user.password else None
1002 rhodecode_user = session.get('rhodecode_user', {})
1003 rhodecode_user = session.get('rhodecode_user', {})
1003 session_password_hash = rhodecode_user.get('password', '')
1004 session_password_hash = rhodecode_user.get('password', '')
1004 return password_hash != session_password_hash
1005 return password_hash != session_password_hash
1005
1006
1006
1007
1007 def read_opensource_licenses():
1008 def read_opensource_licenses():
1008 global _license_cache
1009 global _license_cache
1009
1010
1010 if not _license_cache:
1011 if not _license_cache:
1011 licenses = pkg_resources.resource_string(
1012 licenses = pkg_resources.resource_string(
1012 'rhodecode', 'config/licenses.json')
1013 'rhodecode', 'config/licenses.json')
1013 _license_cache = json.loads(licenses)
1014 _license_cache = json.loads(licenses)
1014
1015
1015 return _license_cache
1016 return _license_cache
1016
1017
1017
1018
1018 def get_registry(request):
1019 def get_registry(request):
1019 """
1020 """
1020 Utility to get the pyramid registry from a request. During migration to
1021 Utility to get the pyramid registry from a request. During migration to
1021 pyramid we sometimes want to use the pyramid registry from pylons context.
1022 pyramid we sometimes want to use the pyramid registry from pylons context.
1022 Therefore this utility returns `request.registry` for pyramid requests and
1023 Therefore this utility returns `request.registry` for pyramid requests and
1023 uses `get_current_registry()` for pylons requests.
1024 uses `get_current_registry()` for pylons requests.
1024 """
1025 """
1025 try:
1026 try:
1026 return request.registry
1027 return request.registry
1027 except AttributeError:
1028 except AttributeError:
1028 return get_current_registry()
1029 return get_current_registry()
1029
1030
1030
1031
1031 def generate_platform_uuid():
1032 def generate_platform_uuid():
1032 """
1033 """
1033 Generates platform UUID based on it's name
1034 Generates platform UUID based on it's name
1034 """
1035 """
1035 import platform
1036 import platform
1036
1037
1037 try:
1038 try:
1038 uuid_list = [platform.platform()]
1039 uuid_list = [platform.platform()]
1039 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
1040 return hashlib.sha256(':'.join(uuid_list)).hexdigest()
1040 except Exception as e:
1041 except Exception as e:
1041 log.error('Failed to generate host uuid: %s' % e)
1042 log.error('Failed to generate host uuid: %s' % e)
1042 return 'UNDEFINED'
1043 return 'UNDEFINED'
@@ -1,810 +1,811 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import hashlib
22 import hashlib
23 import logging
23 import logging
24 from collections import namedtuple
24 from collections import namedtuple
25 from functools import wraps
25 from functools import wraps
26
26
27 from rhodecode.lib import caches
27 from rhodecode.lib import caches
28 from rhodecode.lib.utils2 import (
28 from rhodecode.lib.utils2 import (
29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
29 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
30 from rhodecode.lib.vcs.backends import base
30 from rhodecode.lib.vcs.backends import base
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import (
32 from rhodecode.model.db import (
33 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
33 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting)
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35
35
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 UiSetting = namedtuple(
40 UiSetting = namedtuple(
41 'UiSetting', ['section', 'key', 'value', 'active'])
41 'UiSetting', ['section', 'key', 'value', 'active'])
42
42
43 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
43 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
44
44
45
45
46 class SettingNotFound(Exception):
46 class SettingNotFound(Exception):
47 def __init__(self):
47 def __init__(self):
48 super(SettingNotFound, self).__init__('Setting is not found')
48 super(SettingNotFound, self).__init__('Setting is not found')
49
49
50
50
51 class SettingsModel(BaseModel):
51 class SettingsModel(BaseModel):
52 BUILTIN_HOOKS = (
52 BUILTIN_HOOKS = (
53 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
53 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
54 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
54 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
55 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL)
55 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
56 RhodeCodeUi.HOOK_PUSH_KEY,)
56 HOOKS_SECTION = 'hooks'
57 HOOKS_SECTION = 'hooks'
57
58
58 def __init__(self, sa=None, repo=None):
59 def __init__(self, sa=None, repo=None):
59 self.repo = repo
60 self.repo = repo
60 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
61 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
61 self.SettingsDbModel = (
62 self.SettingsDbModel = (
62 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
63 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
63 super(SettingsModel, self).__init__(sa)
64 super(SettingsModel, self).__init__(sa)
64
65
65 def get_ui_by_key(self, key):
66 def get_ui_by_key(self, key):
66 q = self.UiDbModel.query()
67 q = self.UiDbModel.query()
67 q = q.filter(self.UiDbModel.ui_key == key)
68 q = q.filter(self.UiDbModel.ui_key == key)
68 q = self._filter_by_repo(RepoRhodeCodeUi, q)
69 q = self._filter_by_repo(RepoRhodeCodeUi, q)
69 return q.scalar()
70 return q.scalar()
70
71
71 def get_ui_by_section(self, section):
72 def get_ui_by_section(self, section):
72 q = self.UiDbModel.query()
73 q = self.UiDbModel.query()
73 q = q.filter(self.UiDbModel.ui_section == section)
74 q = q.filter(self.UiDbModel.ui_section == section)
74 q = self._filter_by_repo(RepoRhodeCodeUi, q)
75 q = self._filter_by_repo(RepoRhodeCodeUi, q)
75 return q.all()
76 return q.all()
76
77
77 def get_ui_by_section_and_key(self, section, key):
78 def get_ui_by_section_and_key(self, section, key):
78 q = self.UiDbModel.query()
79 q = self.UiDbModel.query()
79 q = q.filter(self.UiDbModel.ui_section == section)
80 q = q.filter(self.UiDbModel.ui_section == section)
80 q = q.filter(self.UiDbModel.ui_key == key)
81 q = q.filter(self.UiDbModel.ui_key == key)
81 q = self._filter_by_repo(RepoRhodeCodeUi, q)
82 q = self._filter_by_repo(RepoRhodeCodeUi, q)
82 return q.scalar()
83 return q.scalar()
83
84
84 def get_ui(self, section=None, key=None):
85 def get_ui(self, section=None, key=None):
85 q = self.UiDbModel.query()
86 q = self.UiDbModel.query()
86 q = self._filter_by_repo(RepoRhodeCodeUi, q)
87 q = self._filter_by_repo(RepoRhodeCodeUi, q)
87
88
88 if section:
89 if section:
89 q = q.filter(self.UiDbModel.ui_section == section)
90 q = q.filter(self.UiDbModel.ui_section == section)
90 if key:
91 if key:
91 q = q.filter(self.UiDbModel.ui_key == key)
92 q = q.filter(self.UiDbModel.ui_key == key)
92
93
93 # TODO: mikhail: add caching
94 # TODO: mikhail: add caching
94 result = [
95 result = [
95 UiSetting(
96 UiSetting(
96 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
97 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
97 value=safe_str(r.ui_value), active=r.ui_active
98 value=safe_str(r.ui_value), active=r.ui_active
98 )
99 )
99 for r in q.all()
100 for r in q.all()
100 ]
101 ]
101 return result
102 return result
102
103
103 def get_builtin_hooks(self):
104 def get_builtin_hooks(self):
104 q = self.UiDbModel.query()
105 q = self.UiDbModel.query()
105 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
106 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
106 return self._get_hooks(q)
107 return self._get_hooks(q)
107
108
108 def get_custom_hooks(self):
109 def get_custom_hooks(self):
109 q = self.UiDbModel.query()
110 q = self.UiDbModel.query()
110 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
111 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
111 return self._get_hooks(q)
112 return self._get_hooks(q)
112
113
113 def create_ui_section_value(self, section, val, key=None, active=True):
114 def create_ui_section_value(self, section, val, key=None, active=True):
114 new_ui = self.UiDbModel()
115 new_ui = self.UiDbModel()
115 new_ui.ui_section = section
116 new_ui.ui_section = section
116 new_ui.ui_value = val
117 new_ui.ui_value = val
117 new_ui.ui_active = active
118 new_ui.ui_active = active
118
119
119 if self.repo:
120 if self.repo:
120 repo = self._get_repo(self.repo)
121 repo = self._get_repo(self.repo)
121 repository_id = repo.repo_id
122 repository_id = repo.repo_id
122 new_ui.repository_id = repository_id
123 new_ui.repository_id = repository_id
123
124
124 if not key:
125 if not key:
125 # keys are unique so they need appended info
126 # keys are unique so they need appended info
126 if self.repo:
127 if self.repo:
127 key = hashlib.sha1(
128 key = hashlib.sha1(
128 '{}{}{}'.format(section, val, repository_id)).hexdigest()
129 '{}{}{}'.format(section, val, repository_id)).hexdigest()
129 else:
130 else:
130 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
131 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
131
132
132 new_ui.ui_key = key
133 new_ui.ui_key = key
133
134
134 Session().add(new_ui)
135 Session().add(new_ui)
135 return new_ui
136 return new_ui
136
137
137 def create_or_update_hook(self, key, value):
138 def create_or_update_hook(self, key, value):
138 ui = (
139 ui = (
139 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
140 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
140 self.UiDbModel())
141 self.UiDbModel())
141 ui.ui_section = self.HOOKS_SECTION
142 ui.ui_section = self.HOOKS_SECTION
142 ui.ui_active = True
143 ui.ui_active = True
143 ui.ui_key = key
144 ui.ui_key = key
144 ui.ui_value = value
145 ui.ui_value = value
145
146
146 if self.repo:
147 if self.repo:
147 repo = self._get_repo(self.repo)
148 repo = self._get_repo(self.repo)
148 repository_id = repo.repo_id
149 repository_id = repo.repo_id
149 ui.repository_id = repository_id
150 ui.repository_id = repository_id
150
151
151 Session().add(ui)
152 Session().add(ui)
152 return ui
153 return ui
153
154
154 def delete_ui(self, id_):
155 def delete_ui(self, id_):
155 ui = self.UiDbModel.get(id_)
156 ui = self.UiDbModel.get(id_)
156 if not ui:
157 if not ui:
157 raise SettingNotFound()
158 raise SettingNotFound()
158 Session().delete(ui)
159 Session().delete(ui)
159
160
160 def get_setting_by_name(self, name):
161 def get_setting_by_name(self, name):
161 q = self._get_settings_query()
162 q = self._get_settings_query()
162 q = q.filter(self.SettingsDbModel.app_settings_name == name)
163 q = q.filter(self.SettingsDbModel.app_settings_name == name)
163 return q.scalar()
164 return q.scalar()
164
165
165 def create_or_update_setting(
166 def create_or_update_setting(
166 self, name, val=Optional(''), type_=Optional('unicode')):
167 self, name, val=Optional(''), type_=Optional('unicode')):
167 """
168 """
168 Creates or updates RhodeCode setting. If updates is triggered it will
169 Creates or updates RhodeCode setting. If updates is triggered it will
169 only update parameters that are explicityl set Optional instance will
170 only update parameters that are explicityl set Optional instance will
170 be skipped
171 be skipped
171
172
172 :param name:
173 :param name:
173 :param val:
174 :param val:
174 :param type_:
175 :param type_:
175 :return:
176 :return:
176 """
177 """
177
178
178 res = self.get_setting_by_name(name)
179 res = self.get_setting_by_name(name)
179 repo = self._get_repo(self.repo) if self.repo else None
180 repo = self._get_repo(self.repo) if self.repo else None
180
181
181 if not res:
182 if not res:
182 val = Optional.extract(val)
183 val = Optional.extract(val)
183 type_ = Optional.extract(type_)
184 type_ = Optional.extract(type_)
184
185
185 args = (
186 args = (
186 (repo.repo_id, name, val, type_)
187 (repo.repo_id, name, val, type_)
187 if repo else (name, val, type_))
188 if repo else (name, val, type_))
188 res = self.SettingsDbModel(*args)
189 res = self.SettingsDbModel(*args)
189
190
190 else:
191 else:
191 if self.repo:
192 if self.repo:
192 res.repository_id = repo.repo_id
193 res.repository_id = repo.repo_id
193
194
194 res.app_settings_name = name
195 res.app_settings_name = name
195 if not isinstance(type_, Optional):
196 if not isinstance(type_, Optional):
196 # update if set
197 # update if set
197 res.app_settings_type = type_
198 res.app_settings_type = type_
198 if not isinstance(val, Optional):
199 if not isinstance(val, Optional):
199 # update if set
200 # update if set
200 res.app_settings_value = val
201 res.app_settings_value = val
201
202
202 Session().add(res)
203 Session().add(res)
203 return res
204 return res
204
205
205 def invalidate_settings_cache(self):
206 def invalidate_settings_cache(self):
206 namespace = 'rhodecode_settings'
207 namespace = 'rhodecode_settings'
207 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
208 cache_manager = caches.get_cache_manager('sql_cache_short', namespace)
208 caches.clear_cache_manager(cache_manager)
209 caches.clear_cache_manager(cache_manager)
209
210
210 def get_all_settings(self, cache=False):
211 def get_all_settings(self, cache=False):
211
212
212 def _compute():
213 def _compute():
213 q = self._get_settings_query()
214 q = self._get_settings_query()
214 if not q:
215 if not q:
215 raise Exception('Could not get application settings !')
216 raise Exception('Could not get application settings !')
216
217
217 settings = {
218 settings = {
218 'rhodecode_' + result.app_settings_name: result.app_settings_value
219 'rhodecode_' + result.app_settings_name: result.app_settings_value
219 for result in q
220 for result in q
220 }
221 }
221 return settings
222 return settings
222
223
223 if cache:
224 if cache:
224 log.debug('Fetching app settings using cache')
225 log.debug('Fetching app settings using cache')
225 repo = self._get_repo(self.repo) if self.repo else None
226 repo = self._get_repo(self.repo) if self.repo else None
226 namespace = 'rhodecode_settings'
227 namespace = 'rhodecode_settings'
227 cache_manager = caches.get_cache_manager(
228 cache_manager = caches.get_cache_manager(
228 'sql_cache_short', namespace)
229 'sql_cache_short', namespace)
229 _cache_key = (
230 _cache_key = (
230 "get_repo_{}_settings".format(repo.repo_id)
231 "get_repo_{}_settings".format(repo.repo_id)
231 if repo else "get_app_settings")
232 if repo else "get_app_settings")
232
233
233 return cache_manager.get(_cache_key, createfunc=_compute)
234 return cache_manager.get(_cache_key, createfunc=_compute)
234
235
235 else:
236 else:
236 return _compute()
237 return _compute()
237
238
238 def get_auth_settings(self):
239 def get_auth_settings(self):
239 q = self._get_settings_query()
240 q = self._get_settings_query()
240 q = q.filter(
241 q = q.filter(
241 self.SettingsDbModel.app_settings_name.startswith('auth_'))
242 self.SettingsDbModel.app_settings_name.startswith('auth_'))
242 rows = q.all()
243 rows = q.all()
243 auth_settings = {
244 auth_settings = {
244 row.app_settings_name: row.app_settings_value for row in rows}
245 row.app_settings_name: row.app_settings_value for row in rows}
245 return auth_settings
246 return auth_settings
246
247
247 def get_auth_plugins(self):
248 def get_auth_plugins(self):
248 auth_plugins = self.get_setting_by_name("auth_plugins")
249 auth_plugins = self.get_setting_by_name("auth_plugins")
249 return auth_plugins.app_settings_value
250 return auth_plugins.app_settings_value
250
251
251 def get_default_repo_settings(self, strip_prefix=False):
252 def get_default_repo_settings(self, strip_prefix=False):
252 q = self._get_settings_query()
253 q = self._get_settings_query()
253 q = q.filter(
254 q = q.filter(
254 self.SettingsDbModel.app_settings_name.startswith('default_'))
255 self.SettingsDbModel.app_settings_name.startswith('default_'))
255 rows = q.all()
256 rows = q.all()
256
257
257 result = {}
258 result = {}
258 for row in rows:
259 for row in rows:
259 key = row.app_settings_name
260 key = row.app_settings_name
260 if strip_prefix:
261 if strip_prefix:
261 key = remove_prefix(key, prefix='default_')
262 key = remove_prefix(key, prefix='default_')
262 result.update({key: row.app_settings_value})
263 result.update({key: row.app_settings_value})
263 return result
264 return result
264
265
265 def get_repo(self):
266 def get_repo(self):
266 repo = self._get_repo(self.repo)
267 repo = self._get_repo(self.repo)
267 if not repo:
268 if not repo:
268 raise Exception(
269 raise Exception(
269 'Repository `{}` cannot be found inside the database'.format(
270 'Repository `{}` cannot be found inside the database'.format(
270 self.repo))
271 self.repo))
271 return repo
272 return repo
272
273
273 def _filter_by_repo(self, model, query):
274 def _filter_by_repo(self, model, query):
274 if self.repo:
275 if self.repo:
275 repo = self.get_repo()
276 repo = self.get_repo()
276 query = query.filter(model.repository_id == repo.repo_id)
277 query = query.filter(model.repository_id == repo.repo_id)
277 return query
278 return query
278
279
279 def _get_hooks(self, query):
280 def _get_hooks(self, query):
280 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
281 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
281 query = self._filter_by_repo(RepoRhodeCodeUi, query)
282 query = self._filter_by_repo(RepoRhodeCodeUi, query)
282 return query.all()
283 return query.all()
283
284
284 def _get_settings_query(self):
285 def _get_settings_query(self):
285 q = self.SettingsDbModel.query()
286 q = self.SettingsDbModel.query()
286 return self._filter_by_repo(RepoRhodeCodeSetting, q)
287 return self._filter_by_repo(RepoRhodeCodeSetting, q)
287
288
288 def list_enabled_social_plugins(self, settings):
289 def list_enabled_social_plugins(self, settings):
289 enabled = []
290 enabled = []
290 for plug in SOCIAL_PLUGINS_LIST:
291 for plug in SOCIAL_PLUGINS_LIST:
291 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
292 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
292 )):
293 )):
293 enabled.append(plug)
294 enabled.append(plug)
294 return enabled
295 return enabled
295
296
296
297
297 def assert_repo_settings(func):
298 def assert_repo_settings(func):
298 @wraps(func)
299 @wraps(func)
299 def _wrapper(self, *args, **kwargs):
300 def _wrapper(self, *args, **kwargs):
300 if not self.repo_settings:
301 if not self.repo_settings:
301 raise Exception('Repository is not specified')
302 raise Exception('Repository is not specified')
302 return func(self, *args, **kwargs)
303 return func(self, *args, **kwargs)
303 return _wrapper
304 return _wrapper
304
305
305
306
306 class IssueTrackerSettingsModel(object):
307 class IssueTrackerSettingsModel(object):
307 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
308 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
308 SETTINGS_PREFIX = 'issuetracker_'
309 SETTINGS_PREFIX = 'issuetracker_'
309
310
310 def __init__(self, sa=None, repo=None):
311 def __init__(self, sa=None, repo=None):
311 self.global_settings = SettingsModel(sa=sa)
312 self.global_settings = SettingsModel(sa=sa)
312 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
313 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
313
314
314 @property
315 @property
315 def inherit_global_settings(self):
316 def inherit_global_settings(self):
316 if not self.repo_settings:
317 if not self.repo_settings:
317 return True
318 return True
318 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
319 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
319 return setting.app_settings_value if setting else True
320 return setting.app_settings_value if setting else True
320
321
321 @inherit_global_settings.setter
322 @inherit_global_settings.setter
322 def inherit_global_settings(self, value):
323 def inherit_global_settings(self, value):
323 if self.repo_settings:
324 if self.repo_settings:
324 settings = self.repo_settings.create_or_update_setting(
325 settings = self.repo_settings.create_or_update_setting(
325 self.INHERIT_SETTINGS, value, type_='bool')
326 self.INHERIT_SETTINGS, value, type_='bool')
326 Session().add(settings)
327 Session().add(settings)
327
328
328 def _get_keyname(self, key, uid, prefix=''):
329 def _get_keyname(self, key, uid, prefix=''):
329 return '{0}{1}{2}_{3}'.format(
330 return '{0}{1}{2}_{3}'.format(
330 prefix, self.SETTINGS_PREFIX, key, uid)
331 prefix, self.SETTINGS_PREFIX, key, uid)
331
332
332 def _make_dict_for_settings(self, qs):
333 def _make_dict_for_settings(self, qs):
333 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
334 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
334
335
335 issuetracker_entries = {}
336 issuetracker_entries = {}
336 # create keys
337 # create keys
337 for k, v in qs.items():
338 for k, v in qs.items():
338 if k.startswith(prefix_match):
339 if k.startswith(prefix_match):
339 uid = k[len(prefix_match):]
340 uid = k[len(prefix_match):]
340 issuetracker_entries[uid] = None
341 issuetracker_entries[uid] = None
341
342
342 # populate
343 # populate
343 for uid in issuetracker_entries:
344 for uid in issuetracker_entries:
344 issuetracker_entries[uid] = AttributeDict({
345 issuetracker_entries[uid] = AttributeDict({
345 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
346 'pat': qs.get(self._get_keyname('pat', uid, 'rhodecode_')),
346 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
347 'url': qs.get(self._get_keyname('url', uid, 'rhodecode_')),
347 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
348 'pref': qs.get(self._get_keyname('pref', uid, 'rhodecode_')),
348 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
349 'desc': qs.get(self._get_keyname('desc', uid, 'rhodecode_')),
349 })
350 })
350 return issuetracker_entries
351 return issuetracker_entries
351
352
352 def get_global_settings(self, cache=False):
353 def get_global_settings(self, cache=False):
353 """
354 """
354 Returns list of global issue tracker settings
355 Returns list of global issue tracker settings
355 """
356 """
356 defaults = self.global_settings.get_all_settings(cache=cache)
357 defaults = self.global_settings.get_all_settings(cache=cache)
357 settings = self._make_dict_for_settings(defaults)
358 settings = self._make_dict_for_settings(defaults)
358 return settings
359 return settings
359
360
360 def get_repo_settings(self, cache=False):
361 def get_repo_settings(self, cache=False):
361 """
362 """
362 Returns list of issue tracker settings per repository
363 Returns list of issue tracker settings per repository
363 """
364 """
364 if not self.repo_settings:
365 if not self.repo_settings:
365 raise Exception('Repository is not specified')
366 raise Exception('Repository is not specified')
366 all_settings = self.repo_settings.get_all_settings(cache=cache)
367 all_settings = self.repo_settings.get_all_settings(cache=cache)
367 settings = self._make_dict_for_settings(all_settings)
368 settings = self._make_dict_for_settings(all_settings)
368 return settings
369 return settings
369
370
370 def get_settings(self, cache=False):
371 def get_settings(self, cache=False):
371 if self.inherit_global_settings:
372 if self.inherit_global_settings:
372 return self.get_global_settings(cache=cache)
373 return self.get_global_settings(cache=cache)
373 else:
374 else:
374 return self.get_repo_settings(cache=cache)
375 return self.get_repo_settings(cache=cache)
375
376
376 def delete_entries(self, uid):
377 def delete_entries(self, uid):
377 if self.repo_settings:
378 if self.repo_settings:
378 all_patterns = self.get_repo_settings()
379 all_patterns = self.get_repo_settings()
379 settings_model = self.repo_settings
380 settings_model = self.repo_settings
380 else:
381 else:
381 all_patterns = self.get_global_settings()
382 all_patterns = self.get_global_settings()
382 settings_model = self.global_settings
383 settings_model = self.global_settings
383 entries = all_patterns.get(uid)
384 entries = all_patterns.get(uid)
384
385
385 for del_key in entries:
386 for del_key in entries:
386 setting_name = self._get_keyname(del_key, uid)
387 setting_name = self._get_keyname(del_key, uid)
387 entry = settings_model.get_setting_by_name(setting_name)
388 entry = settings_model.get_setting_by_name(setting_name)
388 if entry:
389 if entry:
389 Session().delete(entry)
390 Session().delete(entry)
390
391
391 Session().commit()
392 Session().commit()
392
393
393 def create_or_update_setting(
394 def create_or_update_setting(
394 self, name, val=Optional(''), type_=Optional('unicode')):
395 self, name, val=Optional(''), type_=Optional('unicode')):
395 if self.repo_settings:
396 if self.repo_settings:
396 setting = self.repo_settings.create_or_update_setting(
397 setting = self.repo_settings.create_or_update_setting(
397 name, val, type_)
398 name, val, type_)
398 else:
399 else:
399 setting = self.global_settings.create_or_update_setting(
400 setting = self.global_settings.create_or_update_setting(
400 name, val, type_)
401 name, val, type_)
401 return setting
402 return setting
402
403
403
404
404 class VcsSettingsModel(object):
405 class VcsSettingsModel(object):
405
406
406 INHERIT_SETTINGS = 'inherit_vcs_settings'
407 INHERIT_SETTINGS = 'inherit_vcs_settings'
407 GENERAL_SETTINGS = (
408 GENERAL_SETTINGS = (
408 'use_outdated_comments',
409 'use_outdated_comments',
409 'pr_merge_enabled',
410 'pr_merge_enabled',
410 'hg_use_rebase_for_merging')
411 'hg_use_rebase_for_merging')
411
412
412 HOOKS_SETTINGS = (
413 HOOKS_SETTINGS = (
413 ('hooks', 'changegroup.repo_size'),
414 ('hooks', 'changegroup.repo_size'),
414 ('hooks', 'changegroup.push_logger'),
415 ('hooks', 'changegroup.push_logger'),
415 ('hooks', 'outgoing.pull_logger'),)
416 ('hooks', 'outgoing.pull_logger'),)
416 HG_SETTINGS = (
417 HG_SETTINGS = (
417 ('extensions', 'largefiles'),
418 ('extensions', 'largefiles'),
418 ('phases', 'publish'),
419 ('phases', 'publish'),
419 ('extensions', 'evolve'),)
420 ('extensions', 'evolve'),)
420 GIT_SETTINGS = (
421 GIT_SETTINGS = (
421 ('vcs_git_lfs', 'enabled'),)
422 ('vcs_git_lfs', 'enabled'),)
422 GLOBAL_HG_SETTINGS = (
423 GLOBAL_HG_SETTINGS = (
423 ('extensions', 'largefiles'),
424 ('extensions', 'largefiles'),
424 ('largefiles', 'usercache'),
425 ('largefiles', 'usercache'),
425 ('phases', 'publish'),
426 ('phases', 'publish'),
426 ('extensions', 'hgsubversion'),
427 ('extensions', 'hgsubversion'),
427 ('extensions', 'evolve'),)
428 ('extensions', 'evolve'),)
428 GLOBAL_GIT_SETTINGS = (
429 GLOBAL_GIT_SETTINGS = (
429 ('vcs_git_lfs', 'enabled'),
430 ('vcs_git_lfs', 'enabled'),
430 ('vcs_git_lfs', 'store_location'))
431 ('vcs_git_lfs', 'store_location'))
431 GLOBAL_SVN_SETTINGS = (
432 GLOBAL_SVN_SETTINGS = (
432 ('vcs_svn_proxy', 'http_requests_enabled'),
433 ('vcs_svn_proxy', 'http_requests_enabled'),
433 ('vcs_svn_proxy', 'http_server_url'))
434 ('vcs_svn_proxy', 'http_server_url'))
434
435
435 SVN_BRANCH_SECTION = 'vcs_svn_branch'
436 SVN_BRANCH_SECTION = 'vcs_svn_branch'
436 SVN_TAG_SECTION = 'vcs_svn_tag'
437 SVN_TAG_SECTION = 'vcs_svn_tag'
437 SSL_SETTING = ('web', 'push_ssl')
438 SSL_SETTING = ('web', 'push_ssl')
438 PATH_SETTING = ('paths', '/')
439 PATH_SETTING = ('paths', '/')
439
440
440 def __init__(self, sa=None, repo=None):
441 def __init__(self, sa=None, repo=None):
441 self.global_settings = SettingsModel(sa=sa)
442 self.global_settings = SettingsModel(sa=sa)
442 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
443 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
443 self._ui_settings = (
444 self._ui_settings = (
444 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
445 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
445 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
446 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
446
447
447 @property
448 @property
448 @assert_repo_settings
449 @assert_repo_settings
449 def inherit_global_settings(self):
450 def inherit_global_settings(self):
450 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
451 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
451 return setting.app_settings_value if setting else True
452 return setting.app_settings_value if setting else True
452
453
453 @inherit_global_settings.setter
454 @inherit_global_settings.setter
454 @assert_repo_settings
455 @assert_repo_settings
455 def inherit_global_settings(self, value):
456 def inherit_global_settings(self, value):
456 self.repo_settings.create_or_update_setting(
457 self.repo_settings.create_or_update_setting(
457 self.INHERIT_SETTINGS, value, type_='bool')
458 self.INHERIT_SETTINGS, value, type_='bool')
458
459
459 def get_global_svn_branch_patterns(self):
460 def get_global_svn_branch_patterns(self):
460 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
461 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
461
462
462 @assert_repo_settings
463 @assert_repo_settings
463 def get_repo_svn_branch_patterns(self):
464 def get_repo_svn_branch_patterns(self):
464 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
465 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
465
466
466 def get_global_svn_tag_patterns(self):
467 def get_global_svn_tag_patterns(self):
467 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
468 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
468
469
469 @assert_repo_settings
470 @assert_repo_settings
470 def get_repo_svn_tag_patterns(self):
471 def get_repo_svn_tag_patterns(self):
471 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
472 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
472
473
473 def get_global_settings(self):
474 def get_global_settings(self):
474 return self._collect_all_settings(global_=True)
475 return self._collect_all_settings(global_=True)
475
476
476 @assert_repo_settings
477 @assert_repo_settings
477 def get_repo_settings(self):
478 def get_repo_settings(self):
478 return self._collect_all_settings(global_=False)
479 return self._collect_all_settings(global_=False)
479
480
480 @assert_repo_settings
481 @assert_repo_settings
481 def create_or_update_repo_settings(
482 def create_or_update_repo_settings(
482 self, data, inherit_global_settings=False):
483 self, data, inherit_global_settings=False):
483 from rhodecode.model.scm import ScmModel
484 from rhodecode.model.scm import ScmModel
484
485
485 self.inherit_global_settings = inherit_global_settings
486 self.inherit_global_settings = inherit_global_settings
486
487
487 repo = self.repo_settings.get_repo()
488 repo = self.repo_settings.get_repo()
488 if not inherit_global_settings:
489 if not inherit_global_settings:
489 if repo.repo_type == 'svn':
490 if repo.repo_type == 'svn':
490 self.create_repo_svn_settings(data)
491 self.create_repo_svn_settings(data)
491 else:
492 else:
492 self.create_or_update_repo_hook_settings(data)
493 self.create_or_update_repo_hook_settings(data)
493 self.create_or_update_repo_pr_settings(data)
494 self.create_or_update_repo_pr_settings(data)
494
495
495 if repo.repo_type == 'hg':
496 if repo.repo_type == 'hg':
496 self.create_or_update_repo_hg_settings(data)
497 self.create_or_update_repo_hg_settings(data)
497
498
498 if repo.repo_type == 'git':
499 if repo.repo_type == 'git':
499 self.create_or_update_repo_git_settings(data)
500 self.create_or_update_repo_git_settings(data)
500
501
501 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
502 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
502
503
503 @assert_repo_settings
504 @assert_repo_settings
504 def create_or_update_repo_hook_settings(self, data):
505 def create_or_update_repo_hook_settings(self, data):
505 for section, key in self.HOOKS_SETTINGS:
506 for section, key in self.HOOKS_SETTINGS:
506 data_key = self._get_form_ui_key(section, key)
507 data_key = self._get_form_ui_key(section, key)
507 if data_key not in data:
508 if data_key not in data:
508 raise ValueError(
509 raise ValueError(
509 'The given data does not contain {} key'.format(data_key))
510 'The given data does not contain {} key'.format(data_key))
510
511
511 active = data.get(data_key)
512 active = data.get(data_key)
512 repo_setting = self.repo_settings.get_ui_by_section_and_key(
513 repo_setting = self.repo_settings.get_ui_by_section_and_key(
513 section, key)
514 section, key)
514 if not repo_setting:
515 if not repo_setting:
515 global_setting = self.global_settings.\
516 global_setting = self.global_settings.\
516 get_ui_by_section_and_key(section, key)
517 get_ui_by_section_and_key(section, key)
517 self.repo_settings.create_ui_section_value(
518 self.repo_settings.create_ui_section_value(
518 section, global_setting.ui_value, key=key, active=active)
519 section, global_setting.ui_value, key=key, active=active)
519 else:
520 else:
520 repo_setting.ui_active = active
521 repo_setting.ui_active = active
521 Session().add(repo_setting)
522 Session().add(repo_setting)
522
523
523 def update_global_hook_settings(self, data):
524 def update_global_hook_settings(self, data):
524 for section, key in self.HOOKS_SETTINGS:
525 for section, key in self.HOOKS_SETTINGS:
525 data_key = self._get_form_ui_key(section, key)
526 data_key = self._get_form_ui_key(section, key)
526 if data_key not in data:
527 if data_key not in data:
527 raise ValueError(
528 raise ValueError(
528 'The given data does not contain {} key'.format(data_key))
529 'The given data does not contain {} key'.format(data_key))
529 active = data.get(data_key)
530 active = data.get(data_key)
530 repo_setting = self.global_settings.get_ui_by_section_and_key(
531 repo_setting = self.global_settings.get_ui_by_section_and_key(
531 section, key)
532 section, key)
532 repo_setting.ui_active = active
533 repo_setting.ui_active = active
533 Session().add(repo_setting)
534 Session().add(repo_setting)
534
535
535 @assert_repo_settings
536 @assert_repo_settings
536 def create_or_update_repo_pr_settings(self, data):
537 def create_or_update_repo_pr_settings(self, data):
537 return self._create_or_update_general_settings(
538 return self._create_or_update_general_settings(
538 self.repo_settings, data)
539 self.repo_settings, data)
539
540
540 def create_or_update_global_pr_settings(self, data):
541 def create_or_update_global_pr_settings(self, data):
541 return self._create_or_update_general_settings(
542 return self._create_or_update_general_settings(
542 self.global_settings, data)
543 self.global_settings, data)
543
544
544 @assert_repo_settings
545 @assert_repo_settings
545 def create_repo_svn_settings(self, data):
546 def create_repo_svn_settings(self, data):
546 return self._create_svn_settings(self.repo_settings, data)
547 return self._create_svn_settings(self.repo_settings, data)
547
548
548 @assert_repo_settings
549 @assert_repo_settings
549 def create_or_update_repo_hg_settings(self, data):
550 def create_or_update_repo_hg_settings(self, data):
550 largefiles, phases, evolve = \
551 largefiles, phases, evolve = \
551 self.HG_SETTINGS
552 self.HG_SETTINGS
552 largefiles_key, phases_key, evolve_key = \
553 largefiles_key, phases_key, evolve_key = \
553 self._get_settings_keys(self.HG_SETTINGS, data)
554 self._get_settings_keys(self.HG_SETTINGS, data)
554
555
555 self._create_or_update_ui(
556 self._create_or_update_ui(
556 self.repo_settings, *largefiles, value='',
557 self.repo_settings, *largefiles, value='',
557 active=data[largefiles_key])
558 active=data[largefiles_key])
558 self._create_or_update_ui(
559 self._create_or_update_ui(
559 self.repo_settings, *evolve, value='',
560 self.repo_settings, *evolve, value='',
560 active=data[evolve_key])
561 active=data[evolve_key])
561 self._create_or_update_ui(
562 self._create_or_update_ui(
562 self.repo_settings, *phases, value=safe_str(data[phases_key]))
563 self.repo_settings, *phases, value=safe_str(data[phases_key]))
563
564
564 def create_or_update_global_hg_settings(self, data):
565 def create_or_update_global_hg_settings(self, data):
565 largefiles, largefiles_store, phases, hgsubversion, evolve \
566 largefiles, largefiles_store, phases, hgsubversion, evolve \
566 = self.GLOBAL_HG_SETTINGS
567 = self.GLOBAL_HG_SETTINGS
567 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
568 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
568 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
569 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
569
570
570 self._create_or_update_ui(
571 self._create_or_update_ui(
571 self.global_settings, *largefiles, value='',
572 self.global_settings, *largefiles, value='',
572 active=data[largefiles_key])
573 active=data[largefiles_key])
573 self._create_or_update_ui(
574 self._create_or_update_ui(
574 self.global_settings, *largefiles_store,
575 self.global_settings, *largefiles_store,
575 value=data[largefiles_store_key])
576 value=data[largefiles_store_key])
576 self._create_or_update_ui(
577 self._create_or_update_ui(
577 self.global_settings, *phases, value=safe_str(data[phases_key]))
578 self.global_settings, *phases, value=safe_str(data[phases_key]))
578 self._create_or_update_ui(
579 self._create_or_update_ui(
579 self.global_settings, *hgsubversion, active=data[subversion_key])
580 self.global_settings, *hgsubversion, active=data[subversion_key])
580 self._create_or_update_ui(
581 self._create_or_update_ui(
581 self.global_settings, *evolve, value='',
582 self.global_settings, *evolve, value='',
582 active=data[evolve_key])
583 active=data[evolve_key])
583
584
584 def create_or_update_repo_git_settings(self, data):
585 def create_or_update_repo_git_settings(self, data):
585 # NOTE(marcink): # comma make unpack work properly
586 # NOTE(marcink): # comma make unpack work properly
586 lfs_enabled, \
587 lfs_enabled, \
587 = self.GIT_SETTINGS
588 = self.GIT_SETTINGS
588
589
589 lfs_enabled_key, \
590 lfs_enabled_key, \
590 = self._get_settings_keys(self.GIT_SETTINGS, data)
591 = self._get_settings_keys(self.GIT_SETTINGS, data)
591
592
592 self._create_or_update_ui(
593 self._create_or_update_ui(
593 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
594 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
594 active=data[lfs_enabled_key])
595 active=data[lfs_enabled_key])
595
596
596 def create_or_update_global_git_settings(self, data):
597 def create_or_update_global_git_settings(self, data):
597 lfs_enabled, lfs_store_location \
598 lfs_enabled, lfs_store_location \
598 = self.GLOBAL_GIT_SETTINGS
599 = self.GLOBAL_GIT_SETTINGS
599 lfs_enabled_key, lfs_store_location_key \
600 lfs_enabled_key, lfs_store_location_key \
600 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
601 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
601
602
602 self._create_or_update_ui(
603 self._create_or_update_ui(
603 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
604 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
604 active=data[lfs_enabled_key])
605 active=data[lfs_enabled_key])
605 self._create_or_update_ui(
606 self._create_or_update_ui(
606 self.global_settings, *lfs_store_location,
607 self.global_settings, *lfs_store_location,
607 value=data[lfs_store_location_key])
608 value=data[lfs_store_location_key])
608
609
609 def create_or_update_global_svn_settings(self, data):
610 def create_or_update_global_svn_settings(self, data):
610 # branch/tags patterns
611 # branch/tags patterns
611 self._create_svn_settings(self.global_settings, data)
612 self._create_svn_settings(self.global_settings, data)
612
613
613 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
614 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
614 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
615 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
615 self.GLOBAL_SVN_SETTINGS, data)
616 self.GLOBAL_SVN_SETTINGS, data)
616
617
617 self._create_or_update_ui(
618 self._create_or_update_ui(
618 self.global_settings, *http_requests_enabled,
619 self.global_settings, *http_requests_enabled,
619 value=safe_str(data[http_requests_enabled_key]))
620 value=safe_str(data[http_requests_enabled_key]))
620 self._create_or_update_ui(
621 self._create_or_update_ui(
621 self.global_settings, *http_server_url,
622 self.global_settings, *http_server_url,
622 value=data[http_server_url_key])
623 value=data[http_server_url_key])
623
624
624 def update_global_ssl_setting(self, value):
625 def update_global_ssl_setting(self, value):
625 self._create_or_update_ui(
626 self._create_or_update_ui(
626 self.global_settings, *self.SSL_SETTING, value=value)
627 self.global_settings, *self.SSL_SETTING, value=value)
627
628
628 def update_global_path_setting(self, value):
629 def update_global_path_setting(self, value):
629 self._create_or_update_ui(
630 self._create_or_update_ui(
630 self.global_settings, *self.PATH_SETTING, value=value)
631 self.global_settings, *self.PATH_SETTING, value=value)
631
632
632 @assert_repo_settings
633 @assert_repo_settings
633 def delete_repo_svn_pattern(self, id_):
634 def delete_repo_svn_pattern(self, id_):
634 self.repo_settings.delete_ui(id_)
635 self.repo_settings.delete_ui(id_)
635
636
636 def delete_global_svn_pattern(self, id_):
637 def delete_global_svn_pattern(self, id_):
637 self.global_settings.delete_ui(id_)
638 self.global_settings.delete_ui(id_)
638
639
639 @assert_repo_settings
640 @assert_repo_settings
640 def get_repo_ui_settings(self, section=None, key=None):
641 def get_repo_ui_settings(self, section=None, key=None):
641 global_uis = self.global_settings.get_ui(section, key)
642 global_uis = self.global_settings.get_ui(section, key)
642 repo_uis = self.repo_settings.get_ui(section, key)
643 repo_uis = self.repo_settings.get_ui(section, key)
643 filtered_repo_uis = self._filter_ui_settings(repo_uis)
644 filtered_repo_uis = self._filter_ui_settings(repo_uis)
644 filtered_repo_uis_keys = [
645 filtered_repo_uis_keys = [
645 (s.section, s.key) for s in filtered_repo_uis]
646 (s.section, s.key) for s in filtered_repo_uis]
646
647
647 def _is_global_ui_filtered(ui):
648 def _is_global_ui_filtered(ui):
648 return (
649 return (
649 (ui.section, ui.key) in filtered_repo_uis_keys
650 (ui.section, ui.key) in filtered_repo_uis_keys
650 or ui.section in self._svn_sections)
651 or ui.section in self._svn_sections)
651
652
652 filtered_global_uis = [
653 filtered_global_uis = [
653 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
654 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
654
655
655 return filtered_global_uis + filtered_repo_uis
656 return filtered_global_uis + filtered_repo_uis
656
657
657 def get_global_ui_settings(self, section=None, key=None):
658 def get_global_ui_settings(self, section=None, key=None):
658 return self.global_settings.get_ui(section, key)
659 return self.global_settings.get_ui(section, key)
659
660
660 def get_ui_settings_as_config_obj(self, section=None, key=None):
661 def get_ui_settings_as_config_obj(self, section=None, key=None):
661 config = base.Config()
662 config = base.Config()
662
663
663 ui_settings = self.get_ui_settings(section=section, key=key)
664 ui_settings = self.get_ui_settings(section=section, key=key)
664
665
665 for entry in ui_settings:
666 for entry in ui_settings:
666 config.set(entry.section, entry.key, entry.value)
667 config.set(entry.section, entry.key, entry.value)
667
668
668 return config
669 return config
669
670
670 def get_ui_settings(self, section=None, key=None):
671 def get_ui_settings(self, section=None, key=None):
671 if not self.repo_settings or self.inherit_global_settings:
672 if not self.repo_settings or self.inherit_global_settings:
672 return self.get_global_ui_settings(section, key)
673 return self.get_global_ui_settings(section, key)
673 else:
674 else:
674 return self.get_repo_ui_settings(section, key)
675 return self.get_repo_ui_settings(section, key)
675
676
676 def get_svn_patterns(self, section=None):
677 def get_svn_patterns(self, section=None):
677 if not self.repo_settings:
678 if not self.repo_settings:
678 return self.get_global_ui_settings(section)
679 return self.get_global_ui_settings(section)
679 else:
680 else:
680 return self.get_repo_ui_settings(section)
681 return self.get_repo_ui_settings(section)
681
682
682 @assert_repo_settings
683 @assert_repo_settings
683 def get_repo_general_settings(self):
684 def get_repo_general_settings(self):
684 global_settings = self.global_settings.get_all_settings()
685 global_settings = self.global_settings.get_all_settings()
685 repo_settings = self.repo_settings.get_all_settings()
686 repo_settings = self.repo_settings.get_all_settings()
686 filtered_repo_settings = self._filter_general_settings(repo_settings)
687 filtered_repo_settings = self._filter_general_settings(repo_settings)
687 global_settings.update(filtered_repo_settings)
688 global_settings.update(filtered_repo_settings)
688 return global_settings
689 return global_settings
689
690
690 def get_global_general_settings(self):
691 def get_global_general_settings(self):
691 return self.global_settings.get_all_settings()
692 return self.global_settings.get_all_settings()
692
693
693 def get_general_settings(self):
694 def get_general_settings(self):
694 if not self.repo_settings or self.inherit_global_settings:
695 if not self.repo_settings or self.inherit_global_settings:
695 return self.get_global_general_settings()
696 return self.get_global_general_settings()
696 else:
697 else:
697 return self.get_repo_general_settings()
698 return self.get_repo_general_settings()
698
699
699 def get_repos_location(self):
700 def get_repos_location(self):
700 return self.global_settings.get_ui_by_key('/').ui_value
701 return self.global_settings.get_ui_by_key('/').ui_value
701
702
702 def _filter_ui_settings(self, settings):
703 def _filter_ui_settings(self, settings):
703 filtered_settings = [
704 filtered_settings = [
704 s for s in settings if self._should_keep_setting(s)]
705 s for s in settings if self._should_keep_setting(s)]
705 return filtered_settings
706 return filtered_settings
706
707
707 def _should_keep_setting(self, setting):
708 def _should_keep_setting(self, setting):
708 keep = (
709 keep = (
709 (setting.section, setting.key) in self._ui_settings or
710 (setting.section, setting.key) in self._ui_settings or
710 setting.section in self._svn_sections)
711 setting.section in self._svn_sections)
711 return keep
712 return keep
712
713
713 def _filter_general_settings(self, settings):
714 def _filter_general_settings(self, settings):
714 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
715 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
715 return {
716 return {
716 k: settings[k]
717 k: settings[k]
717 for k in settings if k in keys}
718 for k in settings if k in keys}
718
719
719 def _collect_all_settings(self, global_=False):
720 def _collect_all_settings(self, global_=False):
720 settings = self.global_settings if global_ else self.repo_settings
721 settings = self.global_settings if global_ else self.repo_settings
721 result = {}
722 result = {}
722
723
723 for section, key in self._ui_settings:
724 for section, key in self._ui_settings:
724 ui = settings.get_ui_by_section_and_key(section, key)
725 ui = settings.get_ui_by_section_and_key(section, key)
725 result_key = self._get_form_ui_key(section, key)
726 result_key = self._get_form_ui_key(section, key)
726
727
727 if ui:
728 if ui:
728 if section in ('hooks', 'extensions'):
729 if section in ('hooks', 'extensions'):
729 result[result_key] = ui.ui_active
730 result[result_key] = ui.ui_active
730 elif result_key in ['vcs_git_lfs_enabled']:
731 elif result_key in ['vcs_git_lfs_enabled']:
731 result[result_key] = ui.ui_active
732 result[result_key] = ui.ui_active
732 else:
733 else:
733 result[result_key] = ui.ui_value
734 result[result_key] = ui.ui_value
734
735
735 for name in self.GENERAL_SETTINGS:
736 for name in self.GENERAL_SETTINGS:
736 setting = settings.get_setting_by_name(name)
737 setting = settings.get_setting_by_name(name)
737 if setting:
738 if setting:
738 result_key = 'rhodecode_{}'.format(name)
739 result_key = 'rhodecode_{}'.format(name)
739 result[result_key] = setting.app_settings_value
740 result[result_key] = setting.app_settings_value
740
741
741 return result
742 return result
742
743
743 def _get_form_ui_key(self, section, key):
744 def _get_form_ui_key(self, section, key):
744 return '{section}_{key}'.format(
745 return '{section}_{key}'.format(
745 section=section, key=key.replace('.', '_'))
746 section=section, key=key.replace('.', '_'))
746
747
747 def _create_or_update_ui(
748 def _create_or_update_ui(
748 self, settings, section, key, value=None, active=None):
749 self, settings, section, key, value=None, active=None):
749 ui = settings.get_ui_by_section_and_key(section, key)
750 ui = settings.get_ui_by_section_and_key(section, key)
750 if not ui:
751 if not ui:
751 active = True if active is None else active
752 active = True if active is None else active
752 settings.create_ui_section_value(
753 settings.create_ui_section_value(
753 section, value, key=key, active=active)
754 section, value, key=key, active=active)
754 else:
755 else:
755 if active is not None:
756 if active is not None:
756 ui.ui_active = active
757 ui.ui_active = active
757 if value is not None:
758 if value is not None:
758 ui.ui_value = value
759 ui.ui_value = value
759 Session().add(ui)
760 Session().add(ui)
760
761
761 def _create_svn_settings(self, settings, data):
762 def _create_svn_settings(self, settings, data):
762 svn_settings = {
763 svn_settings = {
763 'new_svn_branch': self.SVN_BRANCH_SECTION,
764 'new_svn_branch': self.SVN_BRANCH_SECTION,
764 'new_svn_tag': self.SVN_TAG_SECTION
765 'new_svn_tag': self.SVN_TAG_SECTION
765 }
766 }
766 for key in svn_settings:
767 for key in svn_settings:
767 if data.get(key):
768 if data.get(key):
768 settings.create_ui_section_value(svn_settings[key], data[key])
769 settings.create_ui_section_value(svn_settings[key], data[key])
769
770
770 def _create_or_update_general_settings(self, settings, data):
771 def _create_or_update_general_settings(self, settings, data):
771 for name in self.GENERAL_SETTINGS:
772 for name in self.GENERAL_SETTINGS:
772 data_key = 'rhodecode_{}'.format(name)
773 data_key = 'rhodecode_{}'.format(name)
773 if data_key not in data:
774 if data_key not in data:
774 raise ValueError(
775 raise ValueError(
775 'The given data does not contain {} key'.format(data_key))
776 'The given data does not contain {} key'.format(data_key))
776 setting = settings.create_or_update_setting(
777 setting = settings.create_or_update_setting(
777 name, data[data_key], 'bool')
778 name, data[data_key], 'bool')
778 Session().add(setting)
779 Session().add(setting)
779
780
780 def _get_settings_keys(self, settings, data):
781 def _get_settings_keys(self, settings, data):
781 data_keys = [self._get_form_ui_key(*s) for s in settings]
782 data_keys = [self._get_form_ui_key(*s) for s in settings]
782 for data_key in data_keys:
783 for data_key in data_keys:
783 if data_key not in data:
784 if data_key not in data:
784 raise ValueError(
785 raise ValueError(
785 'The given data does not contain {} key'.format(data_key))
786 'The given data does not contain {} key'.format(data_key))
786 return data_keys
787 return data_keys
787
788
788 def create_largeobjects_dirs_if_needed(self, repo_store_path):
789 def create_largeobjects_dirs_if_needed(self, repo_store_path):
789 """
790 """
790 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
791 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
791 does a repository scan if enabled in the settings.
792 does a repository scan if enabled in the settings.
792 """
793 """
793
794
794 from rhodecode.lib.vcs.backends.hg import largefiles_store
795 from rhodecode.lib.vcs.backends.hg import largefiles_store
795 from rhodecode.lib.vcs.backends.git import lfs_store
796 from rhodecode.lib.vcs.backends.git import lfs_store
796
797
797 paths = [
798 paths = [
798 largefiles_store(repo_store_path),
799 largefiles_store(repo_store_path),
799 lfs_store(repo_store_path)]
800 lfs_store(repo_store_path)]
800
801
801 for path in paths:
802 for path in paths:
802 if os.path.isdir(path):
803 if os.path.isdir(path):
803 continue
804 continue
804 if os.path.isfile(path):
805 if os.path.isfile(path):
805 continue
806 continue
806 # not a file nor dir, we try to create it
807 # not a file nor dir, we try to create it
807 try:
808 try:
808 os.makedirs(path)
809 os.makedirs(path)
809 except Exception:
810 except Exception:
810 log.warning('Failed to create largefiles dir:%s', path)
811 log.warning('Failed to create largefiles dir:%s', path)
@@ -1,471 +1,473 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import json
21 import json
22 import multiprocessing
22 import multiprocessing
23 import os
23 import os
24
24
25 import mock
25 import mock
26 import py
26 import py
27 import pytest
27 import pytest
28
28
29 from rhodecode.lib import caching_query
29 from rhodecode.lib import caching_query
30 from rhodecode.lib import utils
30 from rhodecode.lib import utils
31 from rhodecode.lib.utils2 import md5
31 from rhodecode.lib.utils2 import md5
32 from rhodecode.model import settings
32 from rhodecode.model import settings
33 from rhodecode.model import db
33 from rhodecode.model import db
34 from rhodecode.model import meta
34 from rhodecode.model import meta
35 from rhodecode.model.repo import RepoModel
35 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo_group import RepoGroupModel
36 from rhodecode.model.repo_group import RepoGroupModel
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.settings import UiSetting, SettingsModel
38 from rhodecode.model.settings import UiSetting, SettingsModel
39 from rhodecode.tests.fixture import Fixture
39 from rhodecode.tests.fixture import Fixture
40 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
40 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
41
41
42
42
43 fixture = Fixture()
43 fixture = Fixture()
44
44
45
45
46 def extract_hooks(config):
46 def extract_hooks(config):
47 """Return a dictionary with the hook entries of the given config."""
47 """Return a dictionary with the hook entries of the given config."""
48 hooks = {}
48 hooks = {}
49 config_items = config.serialize()
49 config_items = config.serialize()
50 for section, name, value in config_items:
50 for section, name, value in config_items:
51 if section != 'hooks':
51 if section != 'hooks':
52 continue
52 continue
53 hooks[name] = value
53 hooks[name] = value
54
54
55 return hooks
55 return hooks
56
56
57
57
58 def disable_hooks(request, hooks):
58 def disable_hooks(request, hooks):
59 """Disables the given hooks from the UI settings."""
59 """Disables the given hooks from the UI settings."""
60 session = meta.Session()
60 session = meta.Session()
61
61
62 model = SettingsModel()
62 model = SettingsModel()
63 for hook_key in hooks:
63 for hook_key in hooks:
64 sett = model.get_ui_by_key(hook_key)
64 sett = model.get_ui_by_key(hook_key)
65 sett.ui_active = False
65 sett.ui_active = False
66 session.add(sett)
66 session.add(sett)
67
67
68 # Invalidate cache
68 # Invalidate cache
69 ui_settings = session.query(db.RhodeCodeUi).options(
69 ui_settings = session.query(db.RhodeCodeUi).options(
70 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
70 caching_query.FromCache('sql_cache_short', 'get_hg_ui_settings'))
71 ui_settings.invalidate()
71 ui_settings.invalidate()
72
72
73 ui_settings = session.query(db.RhodeCodeUi).options(
73 ui_settings = session.query(db.RhodeCodeUi).options(
74 caching_query.FromCache(
74 caching_query.FromCache(
75 'sql_cache_short', 'get_hook_settings', 'get_hook_settings'))
75 'sql_cache_short', 'get_hook_settings', 'get_hook_settings'))
76 ui_settings.invalidate()
76 ui_settings.invalidate()
77
77
78 @request.addfinalizer
78 @request.addfinalizer
79 def rollback():
79 def rollback():
80 session.rollback()
80 session.rollback()
81
81
82
82
83 HOOK_PRE_PUSH = db.RhodeCodeUi.HOOK_PRE_PUSH
83 HOOK_PRE_PUSH = db.RhodeCodeUi.HOOK_PRE_PUSH
84 HOOK_PRETX_PUSH = db.RhodeCodeUi.HOOK_PRETX_PUSH
84 HOOK_PRETX_PUSH = db.RhodeCodeUi.HOOK_PRETX_PUSH
85 HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH
85 HOOK_PUSH = db.RhodeCodeUi.HOOK_PUSH
86 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
86 HOOK_PRE_PULL = db.RhodeCodeUi.HOOK_PRE_PULL
87 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
87 HOOK_PULL = db.RhodeCodeUi.HOOK_PULL
88 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
88 HOOK_REPO_SIZE = db.RhodeCodeUi.HOOK_REPO_SIZE
89 HOOK_PUSH_KEY = db.RhodeCodeUi.HOOK_PUSH_KEY
89
90
90 HG_HOOKS = frozenset(
91 HG_HOOKS = frozenset(
91 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH,
92 (HOOK_PRE_PULL, HOOK_PULL, HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH,
92 HOOK_REPO_SIZE))
93 HOOK_REPO_SIZE, HOOK_PUSH_KEY))
93
94
94
95
95 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
96 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
96 ([], HG_HOOKS),
97 ([], HG_HOOKS),
97 (HG_HOOKS, []),
98 (HG_HOOKS, []),
98
99
99 ([HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_REPO_SIZE], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
100 ([HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_REPO_SIZE, HOOK_PUSH_KEY], [HOOK_PRE_PULL, HOOK_PULL, HOOK_PUSH]),
100
101
101 # When a pull/push hook is disabled, its pre-pull/push counterpart should
102 # When a pull/push hook is disabled, its pre-pull/push counterpart should
102 # be disabled too.
103 # be disabled too.
103 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
104 ([HOOK_PUSH], [HOOK_PRE_PULL, HOOK_PULL, HOOK_REPO_SIZE]),
104 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH, HOOK_REPO_SIZE]),
105 ([HOOK_PULL], [HOOK_PRE_PUSH, HOOK_PRETX_PUSH, HOOK_PUSH, HOOK_REPO_SIZE,
106 HOOK_PUSH_KEY]),
105 ])
107 ])
106 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
108 def test_make_db_config_hg_hooks(pylonsapp, request, disabled_hooks,
107 expected_hooks):
109 expected_hooks):
108 disable_hooks(request, disabled_hooks)
110 disable_hooks(request, disabled_hooks)
109
111
110 config = utils.make_db_config()
112 config = utils.make_db_config()
111 hooks = extract_hooks(config)
113 hooks = extract_hooks(config)
112
114
113 assert set(hooks.iterkeys()).intersection(HG_HOOKS) == set(expected_hooks)
115 assert set(hooks.iterkeys()).intersection(HG_HOOKS) == set(expected_hooks)
114
116
115
117
116 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
118 @pytest.mark.parametrize('disabled_hooks,expected_hooks', [
117 ([], ['pull', 'push']),
119 ([], ['pull', 'push']),
118 ([HOOK_PUSH], ['pull']),
120 ([HOOK_PUSH], ['pull']),
119 ([HOOK_PULL], ['push']),
121 ([HOOK_PULL], ['push']),
120 ([HOOK_PULL, HOOK_PUSH], []),
122 ([HOOK_PULL, HOOK_PUSH], []),
121 ])
123 ])
122 def test_get_enabled_hook_classes(disabled_hooks, expected_hooks):
124 def test_get_enabled_hook_classes(disabled_hooks, expected_hooks):
123 hook_keys = (HOOK_PUSH, HOOK_PULL)
125 hook_keys = (HOOK_PUSH, HOOK_PULL)
124 ui_settings = [
126 ui_settings = [
125 ('hooks', key, 'some value', key not in disabled_hooks)
127 ('hooks', key, 'some value', key not in disabled_hooks)
126 for key in hook_keys]
128 for key in hook_keys]
127
129
128 result = utils.get_enabled_hook_classes(ui_settings)
130 result = utils.get_enabled_hook_classes(ui_settings)
129 assert sorted(result) == expected_hooks
131 assert sorted(result) == expected_hooks
130
132
131
133
132 def test_get_filesystem_repos_finds_repos(tmpdir, pylonsapp):
134 def test_get_filesystem_repos_finds_repos(tmpdir, pylonsapp):
133 _stub_git_repo(tmpdir.ensure('repo', dir=True))
135 _stub_git_repo(tmpdir.ensure('repo', dir=True))
134 repos = list(utils.get_filesystem_repos(str(tmpdir)))
136 repos = list(utils.get_filesystem_repos(str(tmpdir)))
135 assert repos == [('repo', ('git', tmpdir.join('repo')))]
137 assert repos == [('repo', ('git', tmpdir.join('repo')))]
136
138
137
139
138 def test_get_filesystem_repos_skips_directories(tmpdir, pylonsapp):
140 def test_get_filesystem_repos_skips_directories(tmpdir, pylonsapp):
139 tmpdir.ensure('not-a-repo', dir=True)
141 tmpdir.ensure('not-a-repo', dir=True)
140 repos = list(utils.get_filesystem_repos(str(tmpdir)))
142 repos = list(utils.get_filesystem_repos(str(tmpdir)))
141 assert repos == []
143 assert repos == []
142
144
143
145
144 def test_get_filesystem_repos_skips_directories_with_repos(tmpdir, pylonsapp):
146 def test_get_filesystem_repos_skips_directories_with_repos(tmpdir, pylonsapp):
145 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
147 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
146 repos = list(utils.get_filesystem_repos(str(tmpdir)))
148 repos = list(utils.get_filesystem_repos(str(tmpdir)))
147 assert repos == []
149 assert repos == []
148
150
149
151
150 def test_get_filesystem_repos_finds_repos_in_subdirectories(tmpdir, pylonsapp):
152 def test_get_filesystem_repos_finds_repos_in_subdirectories(tmpdir, pylonsapp):
151 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
153 _stub_git_repo(tmpdir.ensure('subdir/repo', dir=True))
152 repos = list(utils.get_filesystem_repos(str(tmpdir), recursive=True))
154 repos = list(utils.get_filesystem_repos(str(tmpdir), recursive=True))
153 assert repos == [('subdir/repo', ('git', tmpdir.join('subdir', 'repo')))]
155 assert repos == [('subdir/repo', ('git', tmpdir.join('subdir', 'repo')))]
154
156
155
157
156 def test_get_filesystem_repos_skips_names_starting_with_dot(tmpdir):
158 def test_get_filesystem_repos_skips_names_starting_with_dot(tmpdir):
157 _stub_git_repo(tmpdir.ensure('.repo', dir=True))
159 _stub_git_repo(tmpdir.ensure('.repo', dir=True))
158 repos = list(utils.get_filesystem_repos(str(tmpdir)))
160 repos = list(utils.get_filesystem_repos(str(tmpdir)))
159 assert repos == []
161 assert repos == []
160
162
161
163
162 def test_get_filesystem_repos_skips_files(tmpdir):
164 def test_get_filesystem_repos_skips_files(tmpdir):
163 tmpdir.ensure('test-file')
165 tmpdir.ensure('test-file')
164 repos = list(utils.get_filesystem_repos(str(tmpdir)))
166 repos = list(utils.get_filesystem_repos(str(tmpdir)))
165 assert repos == []
167 assert repos == []
166
168
167
169
168 def test_get_filesystem_repos_skips_removed_repositories(tmpdir):
170 def test_get_filesystem_repos_skips_removed_repositories(tmpdir):
169 removed_repo_name = 'rm__00000000_000000_000000__.stub'
171 removed_repo_name = 'rm__00000000_000000_000000__.stub'
170 assert utils.REMOVED_REPO_PAT.match(removed_repo_name)
172 assert utils.REMOVED_REPO_PAT.match(removed_repo_name)
171 _stub_git_repo(tmpdir.ensure(removed_repo_name, dir=True))
173 _stub_git_repo(tmpdir.ensure(removed_repo_name, dir=True))
172 repos = list(utils.get_filesystem_repos(str(tmpdir)))
174 repos = list(utils.get_filesystem_repos(str(tmpdir)))
173 assert repos == []
175 assert repos == []
174
176
175
177
176 def _stub_git_repo(repo_path):
178 def _stub_git_repo(repo_path):
177 """
179 """
178 Make `repo_path` look like a Git repository.
180 Make `repo_path` look like a Git repository.
179 """
181 """
180 repo_path.ensure('.git', dir=True)
182 repo_path.ensure('.git', dir=True)
181
183
182
184
183 @pytest.mark.parametrize('str_class', [str, unicode], ids=['str', 'unicode'])
185 @pytest.mark.parametrize('str_class', [str, unicode], ids=['str', 'unicode'])
184 def test_get_dirpaths_returns_all_paths(tmpdir, str_class):
186 def test_get_dirpaths_returns_all_paths(tmpdir, str_class):
185 tmpdir.ensure('test-file')
187 tmpdir.ensure('test-file')
186 dirpaths = utils._get_dirpaths(str_class(tmpdir))
188 dirpaths = utils._get_dirpaths(str_class(tmpdir))
187 assert dirpaths == ['test-file']
189 assert dirpaths == ['test-file']
188
190
189
191
190 def test_get_dirpaths_returns_all_paths_bytes(
192 def test_get_dirpaths_returns_all_paths_bytes(
191 tmpdir, platform_encodes_filenames):
193 tmpdir, platform_encodes_filenames):
192 if platform_encodes_filenames:
194 if platform_encodes_filenames:
193 pytest.skip("This platform seems to encode filenames.")
195 pytest.skip("This platform seems to encode filenames.")
194 tmpdir.ensure('repo-a-umlaut-\xe4')
196 tmpdir.ensure('repo-a-umlaut-\xe4')
195 dirpaths = utils._get_dirpaths(str(tmpdir))
197 dirpaths = utils._get_dirpaths(str(tmpdir))
196 assert dirpaths == ['repo-a-umlaut-\xe4']
198 assert dirpaths == ['repo-a-umlaut-\xe4']
197
199
198
200
199 def test_get_dirpaths_skips_paths_it_cannot_decode(
201 def test_get_dirpaths_skips_paths_it_cannot_decode(
200 tmpdir, platform_encodes_filenames):
202 tmpdir, platform_encodes_filenames):
201 if platform_encodes_filenames:
203 if platform_encodes_filenames:
202 pytest.skip("This platform seems to encode filenames.")
204 pytest.skip("This platform seems to encode filenames.")
203 path_with_latin1 = 'repo-a-umlaut-\xe4'
205 path_with_latin1 = 'repo-a-umlaut-\xe4'
204 tmpdir.ensure(path_with_latin1)
206 tmpdir.ensure(path_with_latin1)
205 dirpaths = utils._get_dirpaths(unicode(tmpdir))
207 dirpaths = utils._get_dirpaths(unicode(tmpdir))
206 assert dirpaths == []
208 assert dirpaths == []
207
209
208
210
209 @pytest.fixture(scope='session')
211 @pytest.fixture(scope='session')
210 def platform_encodes_filenames():
212 def platform_encodes_filenames():
211 """
213 """
212 Boolean indicator if the current platform changes filename encodings.
214 Boolean indicator if the current platform changes filename encodings.
213 """
215 """
214 path_with_latin1 = 'repo-a-umlaut-\xe4'
216 path_with_latin1 = 'repo-a-umlaut-\xe4'
215 tmpdir = py.path.local.mkdtemp()
217 tmpdir = py.path.local.mkdtemp()
216 tmpdir.ensure(path_with_latin1)
218 tmpdir.ensure(path_with_latin1)
217 read_path = tmpdir.listdir()[0].basename
219 read_path = tmpdir.listdir()[0].basename
218 tmpdir.remove()
220 tmpdir.remove()
219 return path_with_latin1 != read_path
221 return path_with_latin1 != read_path
220
222
221
223
222 def test_action_logger_action_size(pylonsapp, test_repo):
224 def test_action_logger_action_size(pylonsapp, test_repo):
223 action = 'x' * 1200001
225 action = 'x' * 1200001
224 utils.action_logger(TEST_USER_ADMIN_LOGIN, action, test_repo, commit=True)
226 utils.action_logger(TEST_USER_ADMIN_LOGIN, action, test_repo, commit=True)
225
227
226
228
227 @pytest.fixture
229 @pytest.fixture
228 def repo_groups(request):
230 def repo_groups(request):
229 session = meta.Session()
231 session = meta.Session()
230 zombie_group = fixture.create_repo_group('zombie')
232 zombie_group = fixture.create_repo_group('zombie')
231 parent_group = fixture.create_repo_group('parent')
233 parent_group = fixture.create_repo_group('parent')
232 child_group = fixture.create_repo_group('parent/child')
234 child_group = fixture.create_repo_group('parent/child')
233 groups_in_db = session.query(db.RepoGroup).all()
235 groups_in_db = session.query(db.RepoGroup).all()
234 assert len(groups_in_db) == 3
236 assert len(groups_in_db) == 3
235 assert child_group.group_parent_id == parent_group.group_id
237 assert child_group.group_parent_id == parent_group.group_id
236
238
237 @request.addfinalizer
239 @request.addfinalizer
238 def cleanup():
240 def cleanup():
239 fixture.destroy_repo_group(zombie_group)
241 fixture.destroy_repo_group(zombie_group)
240 fixture.destroy_repo_group(child_group)
242 fixture.destroy_repo_group(child_group)
241 fixture.destroy_repo_group(parent_group)
243 fixture.destroy_repo_group(parent_group)
242
244
243 return (zombie_group, parent_group, child_group)
245 return (zombie_group, parent_group, child_group)
244
246
245
247
246 def test_repo2db_mapper_groups(repo_groups):
248 def test_repo2db_mapper_groups(repo_groups):
247 session = meta.Session()
249 session = meta.Session()
248 zombie_group, parent_group, child_group = repo_groups
250 zombie_group, parent_group, child_group = repo_groups
249 zombie_path = os.path.join(
251 zombie_path = os.path.join(
250 RepoGroupModel().repos_path, zombie_group.full_path)
252 RepoGroupModel().repos_path, zombie_group.full_path)
251 os.rmdir(zombie_path)
253 os.rmdir(zombie_path)
252
254
253 # Avoid removing test repos when calling repo2db_mapper
255 # Avoid removing test repos when calling repo2db_mapper
254 repo_list = {
256 repo_list = {
255 repo.repo_name: 'test' for repo in session.query(db.Repository).all()
257 repo.repo_name: 'test' for repo in session.query(db.Repository).all()
256 }
258 }
257 utils.repo2db_mapper(repo_list, remove_obsolete=True)
259 utils.repo2db_mapper(repo_list, remove_obsolete=True)
258
260
259 groups_in_db = session.query(db.RepoGroup).all()
261 groups_in_db = session.query(db.RepoGroup).all()
260 assert child_group in groups_in_db
262 assert child_group in groups_in_db
261 assert parent_group in groups_in_db
263 assert parent_group in groups_in_db
262 assert zombie_path not in groups_in_db
264 assert zombie_path not in groups_in_db
263
265
264
266
265 def test_repo2db_mapper_enables_largefiles(backend):
267 def test_repo2db_mapper_enables_largefiles(backend):
266 repo = backend.create_repo()
268 repo = backend.create_repo()
267 repo_list = {repo.repo_name: 'test'}
269 repo_list = {repo.repo_name: 'test'}
268 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm_mock:
270 with mock.patch('rhodecode.model.db.Repository.scm_instance') as scm_mock:
269 with mock.patch.multiple('rhodecode.model.scm.ScmModel',
271 with mock.patch.multiple('rhodecode.model.scm.ScmModel',
270 install_git_hook=mock.DEFAULT,
272 install_git_hook=mock.DEFAULT,
271 install_svn_hooks=mock.DEFAULT):
273 install_svn_hooks=mock.DEFAULT):
272 utils.repo2db_mapper(repo_list, remove_obsolete=False)
274 utils.repo2db_mapper(repo_list, remove_obsolete=False)
273 _, kwargs = scm_mock.call_args
275 _, kwargs = scm_mock.call_args
274 assert kwargs['config'].get('extensions', 'largefiles') == ''
276 assert kwargs['config'].get('extensions', 'largefiles') == ''
275
277
276
278
277 @pytest.mark.backends("git", "svn")
279 @pytest.mark.backends("git", "svn")
278 def test_repo2db_mapper_installs_hooks_for_repos_in_db(backend):
280 def test_repo2db_mapper_installs_hooks_for_repos_in_db(backend):
279 repo = backend.create_repo()
281 repo = backend.create_repo()
280 repo_list = {repo.repo_name: 'test'}
282 repo_list = {repo.repo_name: 'test'}
281 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
283 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
282 utils.repo2db_mapper(repo_list, remove_obsolete=False)
284 utils.repo2db_mapper(repo_list, remove_obsolete=False)
283 install_hooks_mock.assert_called_once_with(
285 install_hooks_mock.assert_called_once_with(
284 repo.scm_instance(), repo_type=backend.alias)
286 repo.scm_instance(), repo_type=backend.alias)
285
287
286
288
287 @pytest.mark.backends("git", "svn")
289 @pytest.mark.backends("git", "svn")
288 def test_repo2db_mapper_installs_hooks_for_newly_added_repos(backend):
290 def test_repo2db_mapper_installs_hooks_for_newly_added_repos(backend):
289 repo = backend.create_repo()
291 repo = backend.create_repo()
290 RepoModel().delete(repo, fs_remove=False)
292 RepoModel().delete(repo, fs_remove=False)
291 meta.Session().commit()
293 meta.Session().commit()
292 repo_list = {repo.repo_name: repo.scm_instance()}
294 repo_list = {repo.repo_name: repo.scm_instance()}
293 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
295 with mock.patch.object(ScmModel, 'install_hooks') as install_hooks_mock:
294 utils.repo2db_mapper(repo_list, remove_obsolete=False)
296 utils.repo2db_mapper(repo_list, remove_obsolete=False)
295 assert install_hooks_mock.call_count == 1
297 assert install_hooks_mock.call_count == 1
296 install_hooks_args, _ = install_hooks_mock.call_args
298 install_hooks_args, _ = install_hooks_mock.call_args
297 assert install_hooks_args[0].name == repo.repo_name
299 assert install_hooks_args[0].name == repo.repo_name
298
300
299
301
300 class TestPasswordChanged(object):
302 class TestPasswordChanged(object):
301 def setup(self):
303 def setup(self):
302 self.session = {
304 self.session = {
303 'rhodecode_user': {
305 'rhodecode_user': {
304 'password': '0cc175b9c0f1b6a831c399e269772661'
306 'password': '0cc175b9c0f1b6a831c399e269772661'
305 }
307 }
306 }
308 }
307 self.auth_user = mock.Mock()
309 self.auth_user = mock.Mock()
308 self.auth_user.userame = 'test'
310 self.auth_user.userame = 'test'
309 self.auth_user.password = 'abc123'
311 self.auth_user.password = 'abc123'
310
312
311 def test_returns_false_for_default_user(self):
313 def test_returns_false_for_default_user(self):
312 self.auth_user.username = db.User.DEFAULT_USER
314 self.auth_user.username = db.User.DEFAULT_USER
313 result = utils.password_changed(self.auth_user, self.session)
315 result = utils.password_changed(self.auth_user, self.session)
314 assert result is False
316 assert result is False
315
317
316 def test_returns_false_if_password_was_not_changed(self):
318 def test_returns_false_if_password_was_not_changed(self):
317 self.session['rhodecode_user']['password'] = md5(
319 self.session['rhodecode_user']['password'] = md5(
318 self.auth_user.password)
320 self.auth_user.password)
319 result = utils.password_changed(self.auth_user, self.session)
321 result = utils.password_changed(self.auth_user, self.session)
320 assert result is False
322 assert result is False
321
323
322 def test_returns_true_if_password_was_changed(self):
324 def test_returns_true_if_password_was_changed(self):
323 result = utils.password_changed(self.auth_user, self.session)
325 result = utils.password_changed(self.auth_user, self.session)
324 assert result is True
326 assert result is True
325
327
326 def test_returns_true_if_auth_user_password_is_empty(self):
328 def test_returns_true_if_auth_user_password_is_empty(self):
327 self.auth_user.password = None
329 self.auth_user.password = None
328 result = utils.password_changed(self.auth_user, self.session)
330 result = utils.password_changed(self.auth_user, self.session)
329 assert result is True
331 assert result is True
330
332
331 def test_returns_true_if_session_password_is_empty(self):
333 def test_returns_true_if_session_password_is_empty(self):
332 self.session['rhodecode_user'].pop('password')
334 self.session['rhodecode_user'].pop('password')
333 result = utils.password_changed(self.auth_user, self.session)
335 result = utils.password_changed(self.auth_user, self.session)
334 assert result is True
336 assert result is True
335
337
336
338
337 class TestReadOpensourceLicenses(object):
339 class TestReadOpensourceLicenses(object):
338 def test_success(self):
340 def test_success(self):
339 utils._license_cache = None
341 utils._license_cache = None
340 json_data = '''
342 json_data = '''
341 {
343 {
342 "python2.7-pytest-2.7.1": {"UNKNOWN": null},
344 "python2.7-pytest-2.7.1": {"UNKNOWN": null},
343 "python2.7-Markdown-2.6.2": {
345 "python2.7-Markdown-2.6.2": {
344 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
346 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
345 }
347 }
346 }
348 }
347 '''
349 '''
348 resource_string_patch = mock.patch.object(
350 resource_string_patch = mock.patch.object(
349 utils.pkg_resources, 'resource_string', return_value=json_data)
351 utils.pkg_resources, 'resource_string', return_value=json_data)
350 with resource_string_patch:
352 with resource_string_patch:
351 result = utils.read_opensource_licenses()
353 result = utils.read_opensource_licenses()
352 assert result == json.loads(json_data)
354 assert result == json.loads(json_data)
353
355
354 def test_caching(self):
356 def test_caching(self):
355 utils._license_cache = {
357 utils._license_cache = {
356 "python2.7-pytest-2.7.1": {
358 "python2.7-pytest-2.7.1": {
357 "UNKNOWN": None
359 "UNKNOWN": None
358 },
360 },
359 "python2.7-Markdown-2.6.2": {
361 "python2.7-Markdown-2.6.2": {
360 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
362 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
361 }
363 }
362 }
364 }
363 resource_patch = mock.patch.object(
365 resource_patch = mock.patch.object(
364 utils.pkg_resources, 'resource_string', side_effect=Exception)
366 utils.pkg_resources, 'resource_string', side_effect=Exception)
365 json_patch = mock.patch.object(
367 json_patch = mock.patch.object(
366 utils.json, 'loads', side_effect=Exception)
368 utils.json, 'loads', side_effect=Exception)
367
369
368 with resource_patch as resource_mock, json_patch as json_mock:
370 with resource_patch as resource_mock, json_patch as json_mock:
369 result = utils.read_opensource_licenses()
371 result = utils.read_opensource_licenses()
370
372
371 assert resource_mock.call_count == 0
373 assert resource_mock.call_count == 0
372 assert json_mock.call_count == 0
374 assert json_mock.call_count == 0
373 assert result == utils._license_cache
375 assert result == utils._license_cache
374
376
375 def test_licenses_file_contains_no_unknown_licenses(self):
377 def test_licenses_file_contains_no_unknown_licenses(self):
376 utils._license_cache = None
378 utils._license_cache = None
377 result = utils.read_opensource_licenses()
379 result = utils.read_opensource_licenses()
378 license_names = []
380 license_names = []
379 for licenses in result.values():
381 for licenses in result.values():
380 license_names.extend(licenses.keys())
382 license_names.extend(licenses.keys())
381 assert 'UNKNOWN' not in license_names
383 assert 'UNKNOWN' not in license_names
382
384
383
385
384 class TestMakeDbConfig(object):
386 class TestMakeDbConfig(object):
385 def test_data_from_config_data_from_db_returned(self):
387 def test_data_from_config_data_from_db_returned(self):
386 test_data = [
388 test_data = [
387 ('section1', 'option1', 'value1'),
389 ('section1', 'option1', 'value1'),
388 ('section2', 'option2', 'value2'),
390 ('section2', 'option2', 'value2'),
389 ('section3', 'option3', 'value3'),
391 ('section3', 'option3', 'value3'),
390 ]
392 ]
391 with mock.patch.object(utils, 'config_data_from_db') as config_mock:
393 with mock.patch.object(utils, 'config_data_from_db') as config_mock:
392 config_mock.return_value = test_data
394 config_mock.return_value = test_data
393 kwargs = {'clear_session': False, 'repo': 'test_repo'}
395 kwargs = {'clear_session': False, 'repo': 'test_repo'}
394 result = utils.make_db_config(**kwargs)
396 result = utils.make_db_config(**kwargs)
395 config_mock.assert_called_once_with(**kwargs)
397 config_mock.assert_called_once_with(**kwargs)
396 for section, option, expected_value in test_data:
398 for section, option, expected_value in test_data:
397 value = result.get(section, option)
399 value = result.get(section, option)
398 assert value == expected_value
400 assert value == expected_value
399
401
400
402
401 class TestConfigDataFromDb(object):
403 class TestConfigDataFromDb(object):
402 def test_config_data_from_db_returns_active_settings(self):
404 def test_config_data_from_db_returns_active_settings(self):
403 test_data = [
405 test_data = [
404 UiSetting('section1', 'option1', 'value1', True),
406 UiSetting('section1', 'option1', 'value1', True),
405 UiSetting('section2', 'option2', 'value2', True),
407 UiSetting('section2', 'option2', 'value2', True),
406 UiSetting('section3', 'option3', 'value3', False),
408 UiSetting('section3', 'option3', 'value3', False),
407 ]
409 ]
408 repo_name = 'test_repo'
410 repo_name = 'test_repo'
409
411
410 model_patch = mock.patch.object(settings, 'VcsSettingsModel')
412 model_patch = mock.patch.object(settings, 'VcsSettingsModel')
411 hooks_patch = mock.patch.object(
413 hooks_patch = mock.patch.object(
412 utils, 'get_enabled_hook_classes',
414 utils, 'get_enabled_hook_classes',
413 return_value=['pull', 'push', 'repo_size'])
415 return_value=['pull', 'push', 'repo_size'])
414 with model_patch as model_mock, hooks_patch:
416 with model_patch as model_mock, hooks_patch:
415 instance_mock = mock.Mock()
417 instance_mock = mock.Mock()
416 model_mock.return_value = instance_mock
418 model_mock.return_value = instance_mock
417 instance_mock.get_ui_settings.return_value = test_data
419 instance_mock.get_ui_settings.return_value = test_data
418 result = utils.config_data_from_db(
420 result = utils.config_data_from_db(
419 clear_session=False, repo=repo_name)
421 clear_session=False, repo=repo_name)
420
422
421 self._assert_repo_name_passed(model_mock, repo_name)
423 self._assert_repo_name_passed(model_mock, repo_name)
422
424
423 expected_result = [
425 expected_result = [
424 ('section1', 'option1', 'value1'),
426 ('section1', 'option1', 'value1'),
425 ('section2', 'option2', 'value2'),
427 ('section2', 'option2', 'value2'),
426 ]
428 ]
427 assert result == expected_result
429 assert result == expected_result
428
430
429 def _assert_repo_name_passed(self, model_mock, repo_name):
431 def _assert_repo_name_passed(self, model_mock, repo_name):
430 assert model_mock.call_count == 1
432 assert model_mock.call_count == 1
431 call_args, call_kwargs = model_mock.call_args
433 call_args, call_kwargs = model_mock.call_args
432 assert call_kwargs['repo'] == repo_name
434 assert call_kwargs['repo'] == repo_name
433
435
434
436
435 class TestIsDirWritable(object):
437 class TestIsDirWritable(object):
436 def test_returns_false_when_not_writable(self):
438 def test_returns_false_when_not_writable(self):
437 with mock.patch('__builtin__.open', side_effect=OSError):
439 with mock.patch('__builtin__.open', side_effect=OSError):
438 assert not utils._is_dir_writable('/stub-path')
440 assert not utils._is_dir_writable('/stub-path')
439
441
440 def test_returns_true_when_writable(self, tmpdir):
442 def test_returns_true_when_writable(self, tmpdir):
441 assert utils._is_dir_writable(str(tmpdir))
443 assert utils._is_dir_writable(str(tmpdir))
442
444
443 def test_is_safe_against_race_conditions(self, tmpdir):
445 def test_is_safe_against_race_conditions(self, tmpdir):
444 workers = multiprocessing.Pool()
446 workers = multiprocessing.Pool()
445 directories = [str(tmpdir)] * 10
447 directories = [str(tmpdir)] * 10
446 workers.map(utils._is_dir_writable, directories)
448 workers.map(utils._is_dir_writable, directories)
447
449
448
450
449 class TestGetEnabledHooks(object):
451 class TestGetEnabledHooks(object):
450 def test_only_active_hooks_are_enabled(self):
452 def test_only_active_hooks_are_enabled(self):
451 ui_settings = [
453 ui_settings = [
452 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
454 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
453 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
455 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
454 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', False)
456 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', False)
455 ]
457 ]
456 result = utils.get_enabled_hook_classes(ui_settings)
458 result = utils.get_enabled_hook_classes(ui_settings)
457 assert result == ['push', 'repo_size']
459 assert result == ['push', 'repo_size']
458
460
459 def test_all_hooks_are_enabled(self):
461 def test_all_hooks_are_enabled(self):
460 ui_settings = [
462 ui_settings = [
461 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
463 UiSetting('hooks', db.RhodeCodeUi.HOOK_PUSH, 'value', True),
462 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
464 UiSetting('hooks', db.RhodeCodeUi.HOOK_REPO_SIZE, 'value', True),
463 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', True)
465 UiSetting('hooks', db.RhodeCodeUi.HOOK_PULL, 'value', True)
464 ]
466 ]
465 result = utils.get_enabled_hook_classes(ui_settings)
467 result = utils.get_enabled_hook_classes(ui_settings)
466 assert result == ['push', 'repo_size', 'pull']
468 assert result == ['push', 'repo_size', 'pull']
467
469
468 def test_no_enabled_hooks_when_no_hook_settings_are_found(self):
470 def test_no_enabled_hooks_when_no_hook_settings_are_found(self):
469 ui_settings = []
471 ui_settings = []
470 result = utils.get_enabled_hook_classes(ui_settings)
472 result = utils.get_enabled_hook_classes(ui_settings)
471 assert result == []
473 assert result == []
@@ -1,147 +1,154 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Base for test suite for making push/pull operations.
22 Base for test suite for making push/pull operations.
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30 from os.path import join as jn
30 from os.path import join as jn
31 from subprocess32 import Popen, PIPE
31 from subprocess32 import Popen, PIPE
32 import logging
32 import logging
33 import os
33 import os
34 import tempfile
34 import tempfile
35
35
36 from rhodecode.tests import GIT_REPO, HG_REPO
36 from rhodecode.tests import GIT_REPO, HG_REPO
37
37
38 DEBUG = True
38 DEBUG = True
39 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
39 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
40 REPO_GROUP = 'a_repo_group'
40 REPO_GROUP = 'a_repo_group'
41 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
41 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
42 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
42 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class Command(object):
47 class Command(object):
48
48
49 def __init__(self, cwd):
49 def __init__(self, cwd):
50 self.cwd = cwd
50 self.cwd = cwd
51 self.process = None
51 self.process = None
52
52
53 def execute(self, cmd, *args):
53 def execute(self, cmd, *args):
54 """
54 """
55 Runs command on the system with given ``args``.
55 Runs command on the system with given ``args``.
56 """
56 """
57
57
58 command = cmd + ' ' + ' '.join(args)
58 command = cmd + ' ' + ' '.join(args)
59 if DEBUG:
59 if DEBUG:
60 log.debug('*** CMD %s ***' % (command,))
60 log.debug('*** CMD %s ***' % (command,))
61
61
62 env = dict(os.environ)
62 env = dict(os.environ)
63 # Delete coverage variables, as they make the test fail for Mercurial
63 # Delete coverage variables, as they make the test fail for Mercurial
64 for key in env.keys():
64 for key in env.keys():
65 if key.startswith('COV_CORE_'):
65 if key.startswith('COV_CORE_'):
66 del env[key]
66 del env[key]
67
67
68 self.process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE,
68 self.process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE,
69 cwd=self.cwd, env=env)
69 cwd=self.cwd, env=env)
70 stdout, stderr = self.process.communicate()
70 stdout, stderr = self.process.communicate()
71 if DEBUG:
71 if DEBUG:
72 log.debug('STDOUT:%s' % (stdout,))
72 log.debug('STDOUT:%s' % (stdout,))
73 log.debug('STDERR:%s' % (stderr,))
73 log.debug('STDERR:%s' % (stderr,))
74 return stdout, stderr
74 return stdout, stderr
75
75
76 def assert_returncode_success(self):
76 def assert_returncode_success(self):
77 assert self.process.returncode == 0
77 assert self.process.returncode == 0
78
78
79
79
80 def _add_files_and_push(vcs, dest, clone_url=None, **kwargs):
80 def _add_files_and_push(vcs, dest, clone_url=None, **kwargs):
81 """
81 """
82 Generate some files, add it to DEST repo and push back
82 Generate some files, add it to DEST repo and push back
83 vcs is git or hg and defines what VCS we want to make those files for
83 vcs is git or hg and defines what VCS we want to make those files for
84 """
84 """
85 # commit some stuff into this repo
85 # commit some stuff into this repo
86 cwd = path = jn(dest)
86 cwd = path = jn(dest)
87 added_file = jn(path, '%ssetup.py' % tempfile._RandomNameSequence().next())
87 added_file = jn(path, '%ssetup.py' % tempfile._RandomNameSequence().next())
88 Command(cwd).execute('touch %s' % added_file)
88 Command(cwd).execute('touch %s' % added_file)
89 Command(cwd).execute('%s add %s' % (vcs, added_file))
89 Command(cwd).execute('%s add %s' % (vcs, added_file))
90 author_str = 'Marcin KuΕΊminski <me@email.com>'
90 author_str = 'Marcin KuΕΊminski <me@email.com>'
91
91
92 git_ident = "git config user.name {} && git config user.email {}".format(
92 git_ident = "git config user.name {} && git config user.email {}".format(
93 'Marcin KuΕΊminski', 'me@email.com')
93 'Marcin KuΕΊminski', 'me@email.com')
94
94
95 for i in xrange(kwargs.get('files_no', 3)):
95 for i in xrange(kwargs.get('files_no', 3)):
96 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
96 cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
97 Command(cwd).execute(cmd)
97 Command(cwd).execute(cmd)
98 if vcs == 'hg':
98 if vcs == 'hg':
99 cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % (
99 cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % (
100 i, author_str, added_file
100 i, author_str, added_file
101 )
101 )
102 elif vcs == 'git':
102 elif vcs == 'git':
103 cmd = """%s && git commit -m 'commited new %s' %s""" % (
103 cmd = """%s && git commit -m 'commited new %s' %s""" % (
104 git_ident, i, added_file)
104 git_ident, i, added_file)
105 Command(cwd).execute(cmd)
105 Command(cwd).execute(cmd)
106
106
107 # PUSH it back
107 # PUSH it back
108 stdout = stderr = None
108 stdout = stderr = None
109 if vcs == 'hg':
109 if vcs == 'hg':
110 stdout, stderr = Command(cwd).execute(
110 stdout, stderr = Command(cwd).execute(
111 'hg push --verbose', clone_url)
111 'hg push --verbose', clone_url)
112 elif vcs == 'git':
112 elif vcs == 'git':
113 stdout, stderr = Command(cwd).execute(
113 stdout, stderr = Command(cwd).execute(
114 """%s && git push --verbose %s master""" % (
114 """%s && git push --verbose %s master""" % (
115 git_ident, clone_url))
115 git_ident, clone_url))
116
116
117 return stdout, stderr
117 return stdout, stderr
118
118
119
119
120 def _check_proper_git_push(
120 def _check_proper_git_push(
121 stdout, stderr, branch='master', should_set_default_branch=False):
121 stdout, stderr, branch='master', should_set_default_branch=False):
122 # Note: Git is writing most information to stderr intentionally
122 # Note: Git is writing most information to stderr intentionally
123 assert 'fatal' not in stderr
123 assert 'fatal' not in stderr
124 assert 'rejected' not in stderr
124 assert 'rejected' not in stderr
125 assert 'Pushing to' in stderr
125 assert 'Pushing to' in stderr
126 assert '%s -> %s' % (branch, branch) in stderr
126 assert '%s -> %s' % (branch, branch) in stderr
127
127
128 if should_set_default_branch:
128 if should_set_default_branch:
129 assert "Setting default branch to %s" % branch in stderr
129 assert "Setting default branch to %s" % branch in stderr
130 else:
130 else:
131 assert "Setting default branch" not in stderr
131 assert "Setting default branch" not in stderr
132
132
133
133
134 def _check_proper_hg_push(stdout, stderr, branch='default'):
135 assert 'pushing to' in stdout
136 assert 'searching for changes' in stdout
137
138 assert 'abort:' not in stderr
139
140
134 def _check_proper_clone(stdout, stderr, vcs):
141 def _check_proper_clone(stdout, stderr, vcs):
135 if vcs == 'hg':
142 if vcs == 'hg':
136 assert 'requesting all changes' in stdout
143 assert 'requesting all changes' in stdout
137 assert 'adding changesets' in stdout
144 assert 'adding changesets' in stdout
138 assert 'adding manifests' in stdout
145 assert 'adding manifests' in stdout
139 assert 'adding file changes' in stdout
146 assert 'adding file changes' in stdout
140
147
141 assert stderr == ''
148 assert stderr == ''
142
149
143 if vcs == 'git':
150 if vcs == 'git':
144 assert '' == stdout
151 assert '' == stdout
145 assert 'Cloning into' in stderr
152 assert 'Cloning into' in stderr
146 assert 'abort:' not in stderr
153 assert 'abort:' not in stderr
147 assert 'fatal:' not in stderr
154 assert 'fatal:' not in stderr
@@ -1,267 +1,270 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 py.test config for test suite for making push/pull operations.
22 py.test config for test suite for making push/pull operations.
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30 import ConfigParser
30 import ConfigParser
31 import os
31 import os
32 import subprocess32
32 import subprocess32
33 import tempfile
33 import tempfile
34 import textwrap
34 import textwrap
35 import pytest
35 import pytest
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.model.db import Repository
38 from rhodecode.model.db import Repository
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.settings import SettingsModel
40 from rhodecode.model.settings import SettingsModel
41 from rhodecode.tests import (
41 from rhodecode.tests import (
42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
42 GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,)
43 from rhodecode.tests.fixture import Fixture
43 from rhodecode.tests.fixture import Fixture
44 from rhodecode.tests.utils import (
44 from rhodecode.tests.utils import (
45 set_anonymous_access, is_url_reachable, wait_for_url)
45 set_anonymous_access, is_url_reachable, wait_for_url)
46
46
47 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
47 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
48 REPO_GROUP = 'a_repo_group'
48 REPO_GROUP = 'a_repo_group'
49 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
49 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
50 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
50 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
51
51
52
52
53 def assert_no_running_instance(url):
53 def assert_no_running_instance(url):
54 if is_url_reachable(url):
54 if is_url_reachable(url):
55 print("Hint: Usually this means another instance of Enterprise "
55 print("Hint: Usually this means another instance of Enterprise "
56 "is running in the background.")
56 "is running in the background.")
57 pytest.fail(
57 pytest.fail(
58 "Port is not free at %s, cannot start web interface" % url)
58 "Port is not free at %s, cannot start web interface" % url)
59
59
60
60
61 def get_host_url(pylons_config):
61 def get_host_url(pylons_config):
62 """Construct the host url using the port in the test configuration."""
62 """Construct the host url using the port in the test configuration."""
63 config = ConfigParser.ConfigParser()
63 config = ConfigParser.ConfigParser()
64 config.read(pylons_config)
64 config.read(pylons_config)
65
65
66 return '127.0.0.1:%s' % config.get('server:main', 'port')
66 return '127.0.0.1:%s' % config.get('server:main', 'port')
67
67
68
68
69 class RcWebServer(object):
69 class RcWebServer(object):
70 """
70 """
71 Represents a running RCE web server used as a test fixture.
71 Represents a running RCE web server used as a test fixture.
72 """
72 """
73 def __init__(self, pylons_config):
73 def __init__(self, pylons_config):
74 self.pylons_config = pylons_config
74 self.pylons_config = pylons_config
75
75
76 def repo_clone_url(self, repo_name, **kwargs):
76 def repo_clone_url(self, repo_name, **kwargs):
77 params = {
77 params = {
78 'user': TEST_USER_ADMIN_LOGIN,
78 'user': TEST_USER_ADMIN_LOGIN,
79 'passwd': TEST_USER_ADMIN_PASS,
79 'passwd': TEST_USER_ADMIN_PASS,
80 'host': get_host_url(self.pylons_config),
80 'host': get_host_url(self.pylons_config),
81 'cloned_repo': repo_name,
81 'cloned_repo': repo_name,
82 }
82 }
83 params.update(**kwargs)
83 params.update(**kwargs)
84 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
84 _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s' % params
85 return _url
85 return _url
86
86
87 def host_url(self):
88 return 'http://' + get_host_url(self.pylons_config)
89
87
90
88 @pytest.fixture(scope="module")
91 @pytest.fixture(scope="module")
89 def rcextensions(request, pylonsapp, tmpdir_factory):
92 def rcextensions(request, pylonsapp, tmpdir_factory):
90 """
93 """
91 Installs a testing rcextensions pack to ensure they work as expected.
94 Installs a testing rcextensions pack to ensure they work as expected.
92 """
95 """
93 init_content = textwrap.dedent("""
96 init_content = textwrap.dedent("""
94 # Forward import the example rcextensions to make it
97 # Forward import the example rcextensions to make it
95 # active for our tests.
98 # active for our tests.
96 from rhodecode.tests.other.example_rcextensions import *
99 from rhodecode.tests.other.example_rcextensions import *
97 """)
100 """)
98
101
99 # Note: rcextensions are looked up based on the path of the ini file
102 # Note: rcextensions are looked up based on the path of the ini file
100 root_path = tmpdir_factory.getbasetemp()
103 root_path = tmpdir_factory.getbasetemp()
101 rcextensions_path = root_path.join('rcextensions')
104 rcextensions_path = root_path.join('rcextensions')
102 init_path = rcextensions_path.join('__init__.py')
105 init_path = rcextensions_path.join('__init__.py')
103
106
104 if rcextensions_path.check():
107 if rcextensions_path.check():
105 pytest.fail(
108 pytest.fail(
106 "Path for rcextensions already exists, please clean up before "
109 "Path for rcextensions already exists, please clean up before "
107 "test run this path: %s" % (rcextensions_path, ))
110 "test run this path: %s" % (rcextensions_path, ))
108 return
111 return
109
112
110 request.addfinalizer(rcextensions_path.remove)
113 request.addfinalizer(rcextensions_path.remove)
111 init_path.write_binary(init_content, ensure=True)
114 init_path.write_binary(init_content, ensure=True)
112
115
113
116
114 @pytest.fixture(scope="module")
117 @pytest.fixture(scope="module")
115 def repos(request, pylonsapp):
118 def repos(request, pylonsapp):
116 """Create a copy of each test repo in a repo group."""
119 """Create a copy of each test repo in a repo group."""
117 fixture = Fixture()
120 fixture = Fixture()
118 repo_group = fixture.create_repo_group(REPO_GROUP)
121 repo_group = fixture.create_repo_group(REPO_GROUP)
119 repo_group_id = repo_group.group_id
122 repo_group_id = repo_group.group_id
120 fixture.create_fork(HG_REPO, HG_REPO,
123 fixture.create_fork(HG_REPO, HG_REPO,
121 repo_name_full=HG_REPO_WITH_GROUP,
124 repo_name_full=HG_REPO_WITH_GROUP,
122 repo_group=repo_group_id)
125 repo_group=repo_group_id)
123 fixture.create_fork(GIT_REPO, GIT_REPO,
126 fixture.create_fork(GIT_REPO, GIT_REPO,
124 repo_name_full=GIT_REPO_WITH_GROUP,
127 repo_name_full=GIT_REPO_WITH_GROUP,
125 repo_group=repo_group_id)
128 repo_group=repo_group_id)
126
129
127 @request.addfinalizer
130 @request.addfinalizer
128 def cleanup():
131 def cleanup():
129 fixture.destroy_repo(HG_REPO_WITH_GROUP)
132 fixture.destroy_repo(HG_REPO_WITH_GROUP)
130 fixture.destroy_repo(GIT_REPO_WITH_GROUP)
133 fixture.destroy_repo(GIT_REPO_WITH_GROUP)
131 fixture.destroy_repo_group(repo_group_id)
134 fixture.destroy_repo_group(repo_group_id)
132
135
133
136
134 @pytest.fixture(scope="module")
137 @pytest.fixture(scope="module")
135 def rc_web_server_config(testini_factory):
138 def rc_web_server_config(testini_factory):
136 """
139 """
137 Configuration file used for the fixture `rc_web_server`.
140 Configuration file used for the fixture `rc_web_server`.
138 """
141 """
139 CUSTOM_PARAMS = [
142 CUSTOM_PARAMS = [
140 {'handler_console': {'level': 'DEBUG'}},
143 {'handler_console': {'level': 'DEBUG'}},
141 ]
144 ]
142 return testini_factory(CUSTOM_PARAMS)
145 return testini_factory(CUSTOM_PARAMS)
143
146
144
147
145 @pytest.fixture(scope="module")
148 @pytest.fixture(scope="module")
146 def rc_web_server(
149 def rc_web_server(
147 request, pylonsapp, rc_web_server_config, repos, rcextensions):
150 request, pylonsapp, rc_web_server_config, repos, rcextensions):
148 """
151 """
149 Run the web server as a subprocess.
152 Run the web server as a subprocess.
150
153
151 Since we have already a running vcsserver, this is not spawned again.
154 Since we have already a running vcsserver, this is not spawned again.
152 """
155 """
153 env = os.environ.copy()
156 env = os.environ.copy()
154 env['RC_NO_TMP_PATH'] = '1'
157 env['RC_NO_TMP_PATH'] = '1'
155
158
156 rc_log = RC_LOG
159 rc_log = RC_LOG
157 server_out = open(rc_log, 'w')
160 server_out = open(rc_log, 'w')
158
161
159 # TODO: Would be great to capture the output and err of the subprocess
162 # TODO: Would be great to capture the output and err of the subprocess
160 # and make it available in a section of the py.test report in case of an
163 # and make it available in a section of the py.test report in case of an
161 # error.
164 # error.
162
165
163 host_url = 'http://' + get_host_url(rc_web_server_config)
166 host_url = 'http://' + get_host_url(rc_web_server_config)
164 assert_no_running_instance(host_url)
167 assert_no_running_instance(host_url)
165 command = ['pserve', rc_web_server_config]
168 command = ['pserve', rc_web_server_config]
166
169
167 print('Starting rcserver: {}'.format(host_url))
170 print('Starting rcserver: {}'.format(host_url))
168 print('Command: {}'.format(command))
171 print('Command: {}'.format(command))
169 print('Logfile: {}'.format(rc_log))
172 print('Logfile: {}'.format(rc_log))
170
173
171 proc = subprocess32.Popen(
174 proc = subprocess32.Popen(
172 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
175 command, bufsize=0, env=env, stdout=server_out, stderr=server_out)
173
176
174 wait_for_url(host_url, timeout=30)
177 wait_for_url(host_url, timeout=30)
175
178
176 @request.addfinalizer
179 @request.addfinalizer
177 def stop_web_server():
180 def stop_web_server():
178 # TODO: Find out how to integrate with the reporting of py.test to
181 # TODO: Find out how to integrate with the reporting of py.test to
179 # make this information available.
182 # make this information available.
180 print("\nServer log file written to %s" % (rc_log, ))
183 print("\nServer log file written to %s" % (rc_log, ))
181 proc.kill()
184 proc.kill()
182 server_out.flush()
185 server_out.flush()
183 server_out.close()
186 server_out.close()
184
187
185 return RcWebServer(rc_web_server_config)
188 return RcWebServer(rc_web_server_config)
186
189
187
190
188 @pytest.fixture(scope='class', autouse=True)
191 @pytest.fixture(scope='class', autouse=True)
189 def disable_anonymous_user_access(pylonsapp):
192 def disable_anonymous_user_access(pylonsapp):
190 set_anonymous_access(False)
193 set_anonymous_access(False)
191
194
192
195
193 @pytest.fixture
196 @pytest.fixture
194 def disable_locking(pylonsapp):
197 def disable_locking(pylonsapp):
195 r = Repository.get_by_repo_name(GIT_REPO)
198 r = Repository.get_by_repo_name(GIT_REPO)
196 Repository.unlock(r)
199 Repository.unlock(r)
197 r.enable_locking = False
200 r.enable_locking = False
198 Session().add(r)
201 Session().add(r)
199 Session().commit()
202 Session().commit()
200
203
201 r = Repository.get_by_repo_name(HG_REPO)
204 r = Repository.get_by_repo_name(HG_REPO)
202 Repository.unlock(r)
205 Repository.unlock(r)
203 r.enable_locking = False
206 r.enable_locking = False
204 Session().add(r)
207 Session().add(r)
205 Session().commit()
208 Session().commit()
206
209
207
210
208 @pytest.fixture
211 @pytest.fixture
209 def enable_auth_plugins(request, pylonsapp, csrf_token):
212 def enable_auth_plugins(request, pylonsapp, csrf_token):
210 """
213 """
211 Return a factory object that when called, allows to control which
214 Return a factory object that when called, allows to control which
212 authentication plugins are enabled.
215 authentication plugins are enabled.
213 """
216 """
214 def _enable_plugins(plugins_list, override=None):
217 def _enable_plugins(plugins_list, override=None):
215 override = override or {}
218 override = override or {}
216 params = {
219 params = {
217 'auth_plugins': ','.join(plugins_list),
220 'auth_plugins': ','.join(plugins_list),
218 }
221 }
219
222
220 # helper translate some names to others
223 # helper translate some names to others
221 name_map = {
224 name_map = {
222 'token': 'authtoken'
225 'token': 'authtoken'
223 }
226 }
224
227
225 for module in plugins_list:
228 for module in plugins_list:
226 plugin_name = module.partition('#')[-1]
229 plugin_name = module.partition('#')[-1]
227 if plugin_name in name_map:
230 if plugin_name in name_map:
228 plugin_name = name_map[plugin_name]
231 plugin_name = name_map[plugin_name]
229 enabled_plugin = 'auth_%s_enabled' % plugin_name
232 enabled_plugin = 'auth_%s_enabled' % plugin_name
230 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
233 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
231
234
232 # default params that are needed for each plugin,
235 # default params that are needed for each plugin,
233 # `enabled` and `cache_ttl`
236 # `enabled` and `cache_ttl`
234 params.update({
237 params.update({
235 enabled_plugin: True,
238 enabled_plugin: True,
236 cache_ttl: 0
239 cache_ttl: 0
237 })
240 })
238 if override.get:
241 if override.get:
239 params.update(override.get(module, {}))
242 params.update(override.get(module, {}))
240
243
241 validated_params = params
244 validated_params = params
242 for k, v in validated_params.items():
245 for k, v in validated_params.items():
243 setting = SettingsModel().create_or_update_setting(k, v)
246 setting = SettingsModel().create_or_update_setting(k, v)
244 Session().add(setting)
247 Session().add(setting)
245 Session().commit()
248 Session().commit()
246
249
247 def cleanup():
250 def cleanup():
248 _enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
251 _enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
249
252
250 request.addfinalizer(cleanup)
253 request.addfinalizer(cleanup)
251
254
252 return _enable_plugins
255 return _enable_plugins
253
256
254
257
255 @pytest.fixture
258 @pytest.fixture
256 def fs_repo_only(request, rhodecode_fixtures):
259 def fs_repo_only(request, rhodecode_fixtures):
257 def fs_repo_fabric(repo_name, repo_type):
260 def fs_repo_fabric(repo_name, repo_type):
258 rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
261 rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
259 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
262 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
260
263
261 def cleanup():
264 def cleanup():
262 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
265 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
263 rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
266 rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
264
267
265 request.addfinalizer(cleanup)
268 request.addfinalizer(cleanup)
266
269
267 return fs_repo_fabric
270 return fs_repo_fabric
@@ -1,481 +1,655 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Test suite for making push/pull operations, on specially modified INI files
22 Test suite for making push/pull operations, on specially modified INI files
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30
30
31 import os
31 import os
32 import time
32 import time
33
33
34 import pytest
34 import pytest
35
35
36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
36 from rhodecode.lib.vcs.backends.git.repository import GitRepository
37 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
37 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.auth_token import AuthTokenModel
39 from rhodecode.model.db import Repository, UserIpMap, CacheKey
40 from rhodecode.model.db import Repository, UserIpMap, CacheKey
40 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
41 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
42 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
43 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
43
44
44 from rhodecode.tests.other.vcs_operations import (
45 from rhodecode.tests.other.vcs_operations import (
45 Command, _check_proper_clone, _check_proper_git_push, _add_files_and_push,
46 Command, _check_proper_clone, _check_proper_git_push,
47 _check_proper_hg_push, _add_files_and_push,
46 HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
48 HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
47
49
48
50
49 @pytest.mark.usefixtures("disable_locking")
51 @pytest.mark.usefixtures("disable_locking")
50 class TestVCSOperations(object):
52 class TestVCSOperations(object):
51
53
52 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
54 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
53 clone_url = rc_web_server.repo_clone_url(HG_REPO)
55 clone_url = rc_web_server.repo_clone_url(HG_REPO)
54 stdout, stderr = Command('/tmp').execute(
56 stdout, stderr = Command('/tmp').execute(
55 'hg clone', clone_url, tmpdir.strpath)
57 'hg clone', clone_url, tmpdir.strpath)
56 _check_proper_clone(stdout, stderr, 'hg')
58 _check_proper_clone(stdout, stderr, 'hg')
57
59
58 def test_clone_git_repo_by_admin(self, rc_web_server, tmpdir):
60 def test_clone_git_repo_by_admin(self, rc_web_server, tmpdir):
59 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
61 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
60 cmd = Command('/tmp')
62 cmd = Command('/tmp')
61 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
63 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
62 _check_proper_clone(stdout, stderr, 'git')
64 _check_proper_clone(stdout, stderr, 'git')
63 cmd.assert_returncode_success()
65 cmd.assert_returncode_success()
64
66
65 def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir):
67 def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir):
66 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
68 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
67 cmd = Command('/tmp')
69 cmd = Command('/tmp')
68 stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath)
70 stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath)
69 _check_proper_clone(stdout, stderr, 'git')
71 _check_proper_clone(stdout, stderr, 'git')
70 cmd.assert_returncode_success()
72 cmd.assert_returncode_success()
71
73
72 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
74 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
73 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
75 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
74 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
76 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
75 stdout, stderr = Command('/tmp').execute(
77 stdout, stderr = Command('/tmp').execute(
76 'hg clone', clone_url, tmpdir.strpath)
78 'hg clone', clone_url, tmpdir.strpath)
77 _check_proper_clone(stdout, stderr, 'hg')
79 _check_proper_clone(stdout, stderr, 'hg')
78
80
79 def test_clone_git_repo_by_id_by_admin(self, rc_web_server, tmpdir):
81 def test_clone_git_repo_by_id_by_admin(self, rc_web_server, tmpdir):
80 repo_id = Repository.get_by_repo_name(GIT_REPO).repo_id
82 repo_id = Repository.get_by_repo_name(GIT_REPO).repo_id
81 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
83 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
82 cmd = Command('/tmp')
84 cmd = Command('/tmp')
83 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
85 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
84 _check_proper_clone(stdout, stderr, 'git')
86 _check_proper_clone(stdout, stderr, 'git')
85 cmd.assert_returncode_success()
87 cmd.assert_returncode_success()
86
88
87 def test_clone_hg_repo_with_group_by_admin(self, rc_web_server, tmpdir):
89 def test_clone_hg_repo_with_group_by_admin(self, rc_web_server, tmpdir):
88 clone_url = rc_web_server.repo_clone_url(HG_REPO_WITH_GROUP)
90 clone_url = rc_web_server.repo_clone_url(HG_REPO_WITH_GROUP)
89 stdout, stderr = Command('/tmp').execute(
91 stdout, stderr = Command('/tmp').execute(
90 'hg clone', clone_url, tmpdir.strpath)
92 'hg clone', clone_url, tmpdir.strpath)
91 _check_proper_clone(stdout, stderr, 'hg')
93 _check_proper_clone(stdout, stderr, 'hg')
92
94
93 def test_clone_git_repo_with_group_by_admin(self, rc_web_server, tmpdir):
95 def test_clone_git_repo_with_group_by_admin(self, rc_web_server, tmpdir):
94 clone_url = rc_web_server.repo_clone_url(GIT_REPO_WITH_GROUP)
96 clone_url = rc_web_server.repo_clone_url(GIT_REPO_WITH_GROUP)
95 cmd = Command('/tmp')
97 cmd = Command('/tmp')
96 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
98 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
97 _check_proper_clone(stdout, stderr, 'git')
99 _check_proper_clone(stdout, stderr, 'git')
98 cmd.assert_returncode_success()
100 cmd.assert_returncode_success()
99
101
100 def test_clone_git_repo_shallow_by_admin(self, rc_web_server, tmpdir):
102 def test_clone_git_repo_shallow_by_admin(self, rc_web_server, tmpdir):
101 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
103 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
102 cmd = Command('/tmp')
104 cmd = Command('/tmp')
103 stdout, stderr = cmd.execute(
105 stdout, stderr = cmd.execute(
104 'git clone --depth=1', clone_url, tmpdir.strpath)
106 'git clone --depth=1', clone_url, tmpdir.strpath)
105
107
106 assert '' == stdout
108 assert '' == stdout
107 assert 'Cloning into' in stderr
109 assert 'Cloning into' in stderr
108 cmd.assert_returncode_success()
110 cmd.assert_returncode_success()
109
111
110 def test_clone_wrong_credentials_hg(self, rc_web_server, tmpdir):
112 def test_clone_wrong_credentials_hg(self, rc_web_server, tmpdir):
111 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
113 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
112 stdout, stderr = Command('/tmp').execute(
114 stdout, stderr = Command('/tmp').execute(
113 'hg clone', clone_url, tmpdir.strpath)
115 'hg clone', clone_url, tmpdir.strpath)
114 assert 'abort: authorization failed' in stderr
116 assert 'abort: authorization failed' in stderr
115
117
116 def test_clone_wrong_credentials_git(self, rc_web_server, tmpdir):
118 def test_clone_wrong_credentials_git(self, rc_web_server, tmpdir):
117 clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!')
119 clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!')
118 stdout, stderr = Command('/tmp').execute(
120 stdout, stderr = Command('/tmp').execute(
119 'git clone', clone_url, tmpdir.strpath)
121 'git clone', clone_url, tmpdir.strpath)
120 assert 'fatal: Authentication failed' in stderr
122 assert 'fatal: Authentication failed' in stderr
121
123
122 def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir):
124 def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir):
123 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
125 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
124 stdout, stderr = Command('/tmp').execute(
126 stdout, stderr = Command('/tmp').execute(
125 'hg clone', clone_url, tmpdir.strpath)
127 'hg clone', clone_url, tmpdir.strpath)
126 assert 'HTTP Error 404: Not Found' in stderr
128 assert 'HTTP Error 404: Not Found' in stderr
127
129
128 def test_clone_hg_repo_as_git(self, rc_web_server, tmpdir):
130 def test_clone_hg_repo_as_git(self, rc_web_server, tmpdir):
129 clone_url = rc_web_server.repo_clone_url(HG_REPO)
131 clone_url = rc_web_server.repo_clone_url(HG_REPO)
130 stdout, stderr = Command('/tmp').execute(
132 stdout, stderr = Command('/tmp').execute(
131 'git clone', clone_url, tmpdir.strpath)
133 'git clone', clone_url, tmpdir.strpath)
132 assert 'not found' in stderr
134 assert 'not found' in stderr
133
135
134 def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir):
136 def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir):
135 clone_url = rc_web_server.repo_clone_url('trololo')
137 clone_url = rc_web_server.repo_clone_url('trololo')
136 stdout, stderr = Command('/tmp').execute(
138 stdout, stderr = Command('/tmp').execute(
137 'hg clone', clone_url, tmpdir.strpath)
139 'hg clone', clone_url, tmpdir.strpath)
138 assert 'HTTP Error 404: Not Found' in stderr
140 assert 'HTTP Error 404: Not Found' in stderr
139
141
140 def test_clone_non_existing_path_git(self, rc_web_server, tmpdir):
142 def test_clone_non_existing_path_git(self, rc_web_server, tmpdir):
141 clone_url = rc_web_server.repo_clone_url('trololo')
143 clone_url = rc_web_server.repo_clone_url('trololo')
142 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
144 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
143 assert 'not found' in stderr
145 assert 'not found' in stderr
144
146
145 def test_clone_existing_path_hg_not_in_database(
147 def test_clone_existing_path_hg_not_in_database(
146 self, rc_web_server, tmpdir, fs_repo_only):
148 self, rc_web_server, tmpdir, fs_repo_only):
147
149
148 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
150 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
149 clone_url = rc_web_server.repo_clone_url(db_name)
151 clone_url = rc_web_server.repo_clone_url(db_name)
150 stdout, stderr = Command('/tmp').execute(
152 stdout, stderr = Command('/tmp').execute(
151 'hg clone', clone_url, tmpdir.strpath)
153 'hg clone', clone_url, tmpdir.strpath)
152 assert 'HTTP Error 404: Not Found' in stderr
154 assert 'HTTP Error 404: Not Found' in stderr
153
155
154 def test_clone_existing_path_git_not_in_database(
156 def test_clone_existing_path_git_not_in_database(
155 self, rc_web_server, tmpdir, fs_repo_only):
157 self, rc_web_server, tmpdir, fs_repo_only):
156 db_name = fs_repo_only('not-in-db-git', repo_type='git')
158 db_name = fs_repo_only('not-in-db-git', repo_type='git')
157 clone_url = rc_web_server.repo_clone_url(db_name)
159 clone_url = rc_web_server.repo_clone_url(db_name)
158 stdout, stderr = Command('/tmp').execute(
160 stdout, stderr = Command('/tmp').execute(
159 'git clone', clone_url, tmpdir.strpath)
161 'git clone', clone_url, tmpdir.strpath)
160 assert 'not found' in stderr
162 assert 'not found' in stderr
161
163
162 def test_clone_existing_path_hg_not_in_database_different_scm(
164 def test_clone_existing_path_hg_not_in_database_different_scm(
163 self, rc_web_server, tmpdir, fs_repo_only):
165 self, rc_web_server, tmpdir, fs_repo_only):
164 db_name = fs_repo_only('not-in-db-git', repo_type='git')
166 db_name = fs_repo_only('not-in-db-git', repo_type='git')
165 clone_url = rc_web_server.repo_clone_url(db_name)
167 clone_url = rc_web_server.repo_clone_url(db_name)
166 stdout, stderr = Command('/tmp').execute(
168 stdout, stderr = Command('/tmp').execute(
167 'hg clone', clone_url, tmpdir.strpath)
169 'hg clone', clone_url, tmpdir.strpath)
168 assert 'HTTP Error 404: Not Found' in stderr
170 assert 'HTTP Error 404: Not Found' in stderr
169
171
170 def test_clone_existing_path_git_not_in_database_different_scm(
172 def test_clone_existing_path_git_not_in_database_different_scm(
171 self, rc_web_server, tmpdir, fs_repo_only):
173 self, rc_web_server, tmpdir, fs_repo_only):
172 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
174 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
173 clone_url = rc_web_server.repo_clone_url(db_name)
175 clone_url = rc_web_server.repo_clone_url(db_name)
174 stdout, stderr = Command('/tmp').execute(
176 stdout, stderr = Command('/tmp').execute(
175 'git clone', clone_url, tmpdir.strpath)
177 'git clone', clone_url, tmpdir.strpath)
176 assert 'not found' in stderr
178 assert 'not found' in stderr
177
179
178 def test_push_new_file_hg(self, rc_web_server, tmpdir):
180 def test_push_new_file_hg(self, rc_web_server, tmpdir):
179 clone_url = rc_web_server.repo_clone_url(HG_REPO)
181 clone_url = rc_web_server.repo_clone_url(HG_REPO)
180 stdout, stderr = Command('/tmp').execute(
182 stdout, stderr = Command('/tmp').execute(
181 'hg clone', clone_url, tmpdir.strpath)
183 'hg clone', clone_url, tmpdir.strpath)
182
184
183 stdout, stderr = _add_files_and_push(
185 stdout, stderr = _add_files_and_push(
184 'hg', tmpdir.strpath, clone_url=clone_url)
186 'hg', tmpdir.strpath, clone_url=clone_url)
185
187
186 assert 'pushing to' in stdout
188 assert 'pushing to' in stdout
187 assert 'size summary' in stdout
189 assert 'size summary' in stdout
188
190
189 def test_push_new_file_git(self, rc_web_server, tmpdir):
191 def test_push_new_file_git(self, rc_web_server, tmpdir):
190 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
192 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
191 stdout, stderr = Command('/tmp').execute(
193 stdout, stderr = Command('/tmp').execute(
192 'git clone', clone_url, tmpdir.strpath)
194 'git clone', clone_url, tmpdir.strpath)
193
195
194 # commit some stuff into this repo
196 # commit some stuff into this repo
195 stdout, stderr = _add_files_and_push(
197 stdout, stderr = _add_files_and_push(
196 'git', tmpdir.strpath, clone_url=clone_url)
198 'git', tmpdir.strpath, clone_url=clone_url)
197
199
198 _check_proper_git_push(stdout, stderr)
200 _check_proper_git_push(stdout, stderr)
199
201
200 def test_push_invalidates_cache_hg(self, rc_web_server, tmpdir):
202 def test_push_invalidates_cache_hg(self, rc_web_server, tmpdir):
201 key = CacheKey.query().filter(CacheKey.cache_key == HG_REPO).scalar()
203 key = CacheKey.query().filter(CacheKey.cache_key == HG_REPO).scalar()
202 if not key:
204 if not key:
203 key = CacheKey(HG_REPO, HG_REPO)
205 key = CacheKey(HG_REPO, HG_REPO)
204
206
205 key.cache_active = True
207 key.cache_active = True
206 Session().add(key)
208 Session().add(key)
207 Session().commit()
209 Session().commit()
208
210
209 clone_url = rc_web_server.repo_clone_url(HG_REPO)
211 clone_url = rc_web_server.repo_clone_url(HG_REPO)
210 stdout, stderr = Command('/tmp').execute(
212 stdout, stderr = Command('/tmp').execute(
211 'hg clone', clone_url, tmpdir.strpath)
213 'hg clone', clone_url, tmpdir.strpath)
212
214
213 stdout, stderr = _add_files_and_push(
215 stdout, stderr = _add_files_and_push(
214 'hg', tmpdir.strpath, clone_url=clone_url, files_no=1)
216 'hg', tmpdir.strpath, clone_url=clone_url, files_no=1)
215
217
216 key = CacheKey.query().filter(CacheKey.cache_key == HG_REPO).one()
218 key = CacheKey.query().filter(CacheKey.cache_key == HG_REPO).one()
217 assert key.cache_active is False
219 assert key.cache_active is False
218
220
219 def test_push_invalidates_cache_git(self, rc_web_server, tmpdir):
221 def test_push_invalidates_cache_git(self, rc_web_server, tmpdir):
220 key = CacheKey.query().filter(CacheKey.cache_key == GIT_REPO).scalar()
222 key = CacheKey.query().filter(CacheKey.cache_key == GIT_REPO).scalar()
221 if not key:
223 if not key:
222 key = CacheKey(GIT_REPO, GIT_REPO)
224 key = CacheKey(GIT_REPO, GIT_REPO)
223
225
224 key.cache_active = True
226 key.cache_active = True
225 Session().add(key)
227 Session().add(key)
226 Session().commit()
228 Session().commit()
227
229
228 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
230 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
229 stdout, stderr = Command('/tmp').execute(
231 stdout, stderr = Command('/tmp').execute(
230 'git clone', clone_url, tmpdir.strpath)
232 'git clone', clone_url, tmpdir.strpath)
231
233
232 # commit some stuff into this repo
234 # commit some stuff into this repo
233 stdout, stderr = _add_files_and_push(
235 stdout, stderr = _add_files_and_push(
234 'git', tmpdir.strpath, clone_url=clone_url, files_no=1)
236 'git', tmpdir.strpath, clone_url=clone_url, files_no=1)
235 _check_proper_git_push(stdout, stderr)
237 _check_proper_git_push(stdout, stderr)
236
238
237 key = CacheKey.query().filter(CacheKey.cache_key == GIT_REPO).one()
239 key = CacheKey.query().filter(CacheKey.cache_key == GIT_REPO).one()
238
240
239 assert key.cache_active is False
241 assert key.cache_active is False
240
242
241 def test_push_wrong_credentials_hg(self, rc_web_server, tmpdir):
243 def test_push_wrong_credentials_hg(self, rc_web_server, tmpdir):
242 clone_url = rc_web_server.repo_clone_url(HG_REPO)
244 clone_url = rc_web_server.repo_clone_url(HG_REPO)
243 stdout, stderr = Command('/tmp').execute(
245 stdout, stderr = Command('/tmp').execute(
244 'hg clone', clone_url, tmpdir.strpath)
246 'hg clone', clone_url, tmpdir.strpath)
245
247
246 push_url = rc_web_server.repo_clone_url(
248 push_url = rc_web_server.repo_clone_url(
247 HG_REPO, user='bad', passwd='name')
249 HG_REPO, user='bad', passwd='name')
248 stdout, stderr = _add_files_and_push(
250 stdout, stderr = _add_files_and_push(
249 'hg', tmpdir.strpath, clone_url=push_url)
251 'hg', tmpdir.strpath, clone_url=push_url)
250
252
251 assert 'abort: authorization failed' in stderr
253 assert 'abort: authorization failed' in stderr
252
254
253 def test_push_wrong_credentials_git(self, rc_web_server, tmpdir):
255 def test_push_wrong_credentials_git(self, rc_web_server, tmpdir):
254 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
256 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
255 stdout, stderr = Command('/tmp').execute(
257 stdout, stderr = Command('/tmp').execute(
256 'git clone', clone_url, tmpdir.strpath)
258 'git clone', clone_url, tmpdir.strpath)
257
259
258 push_url = rc_web_server.repo_clone_url(
260 push_url = rc_web_server.repo_clone_url(
259 GIT_REPO, user='bad', passwd='name')
261 GIT_REPO, user='bad', passwd='name')
260 stdout, stderr = _add_files_and_push(
262 stdout, stderr = _add_files_and_push(
261 'git', tmpdir.strpath, clone_url=push_url)
263 'git', tmpdir.strpath, clone_url=push_url)
262
264
263 assert 'fatal: Authentication failed' in stderr
265 assert 'fatal: Authentication failed' in stderr
264
266
265 def test_push_back_to_wrong_url_hg(self, rc_web_server, tmpdir):
267 def test_push_back_to_wrong_url_hg(self, rc_web_server, tmpdir):
266 clone_url = rc_web_server.repo_clone_url(HG_REPO)
268 clone_url = rc_web_server.repo_clone_url(HG_REPO)
267 stdout, stderr = Command('/tmp').execute(
269 stdout, stderr = Command('/tmp').execute(
268 'hg clone', clone_url, tmpdir.strpath)
270 'hg clone', clone_url, tmpdir.strpath)
269
271
270 stdout, stderr = _add_files_and_push(
272 stdout, stderr = _add_files_and_push(
271 'hg', tmpdir.strpath,
273 'hg', tmpdir.strpath,
272 clone_url=rc_web_server.repo_clone_url('not-existing'))
274 clone_url=rc_web_server.repo_clone_url('not-existing'))
273
275
274 assert 'HTTP Error 404: Not Found' in stderr
276 assert 'HTTP Error 404: Not Found' in stderr
275
277
276 def test_push_back_to_wrong_url_git(self, rc_web_server, tmpdir):
278 def test_push_back_to_wrong_url_git(self, rc_web_server, tmpdir):
277 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
279 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
278 stdout, stderr = Command('/tmp').execute(
280 stdout, stderr = Command('/tmp').execute(
279 'git clone', clone_url, tmpdir.strpath)
281 'git clone', clone_url, tmpdir.strpath)
280
282
281 stdout, stderr = _add_files_and_push(
283 stdout, stderr = _add_files_and_push(
282 'git', tmpdir.strpath,
284 'git', tmpdir.strpath,
283 clone_url=rc_web_server.repo_clone_url('not-existing'))
285 clone_url=rc_web_server.repo_clone_url('not-existing'))
284
286
285 assert 'not found' in stderr
287 assert 'not found' in stderr
286
288
287 def test_ip_restriction_hg(self, rc_web_server, tmpdir):
289 def test_ip_restriction_hg(self, rc_web_server, tmpdir):
288 user_model = UserModel()
290 user_model = UserModel()
289 try:
291 try:
290 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
292 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
291 Session().commit()
293 Session().commit()
292 time.sleep(2)
294 time.sleep(2)
293 clone_url = rc_web_server.repo_clone_url(HG_REPO)
295 clone_url = rc_web_server.repo_clone_url(HG_REPO)
294 stdout, stderr = Command('/tmp').execute(
296 stdout, stderr = Command('/tmp').execute(
295 'hg clone', clone_url, tmpdir.strpath)
297 'hg clone', clone_url, tmpdir.strpath)
296 assert 'abort: HTTP Error 403: Forbidden' in stderr
298 assert 'abort: HTTP Error 403: Forbidden' in stderr
297 finally:
299 finally:
298 # release IP restrictions
300 # release IP restrictions
299 for ip in UserIpMap.getAll():
301 for ip in UserIpMap.getAll():
300 UserIpMap.delete(ip.ip_id)
302 UserIpMap.delete(ip.ip_id)
301 Session().commit()
303 Session().commit()
302
304
303 time.sleep(2)
305 time.sleep(2)
304
306
305 stdout, stderr = Command('/tmp').execute(
307 stdout, stderr = Command('/tmp').execute(
306 'hg clone', clone_url, tmpdir.strpath)
308 'hg clone', clone_url, tmpdir.strpath)
307 _check_proper_clone(stdout, stderr, 'hg')
309 _check_proper_clone(stdout, stderr, 'hg')
308
310
309 def test_ip_restriction_git(self, rc_web_server, tmpdir):
311 def test_ip_restriction_git(self, rc_web_server, tmpdir):
310 user_model = UserModel()
312 user_model = UserModel()
311 try:
313 try:
312 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
314 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
313 Session().commit()
315 Session().commit()
314 time.sleep(2)
316 time.sleep(2)
315 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
317 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
316 stdout, stderr = Command('/tmp').execute(
318 stdout, stderr = Command('/tmp').execute(
317 'git clone', clone_url, tmpdir.strpath)
319 'git clone', clone_url, tmpdir.strpath)
318 msg = "The requested URL returned error: 403"
320 msg = "The requested URL returned error: 403"
319 assert msg in stderr
321 assert msg in stderr
320 finally:
322 finally:
321 # release IP restrictions
323 # release IP restrictions
322 for ip in UserIpMap.getAll():
324 for ip in UserIpMap.getAll():
323 UserIpMap.delete(ip.ip_id)
325 UserIpMap.delete(ip.ip_id)
324 Session().commit()
326 Session().commit()
325
327
326 time.sleep(2)
328 time.sleep(2)
327
329
328 cmd = Command('/tmp')
330 cmd = Command('/tmp')
329 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
331 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
330 cmd.assert_returncode_success()
332 cmd.assert_returncode_success()
331 _check_proper_clone(stdout, stderr, 'git')
333 _check_proper_clone(stdout, stderr, 'git')
332
334
333 def test_clone_by_auth_token(
335 def test_clone_by_auth_token(
334 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
336 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
335 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
337 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
336 'egg:rhodecode-enterprise-ce#rhodecode'])
338 'egg:rhodecode-enterprise-ce#rhodecode'])
337
339
338 user = user_util.create_user()
340 user = user_util.create_user()
339 token = user.auth_tokens[1]
341 token = user.auth_tokens[1]
340
342
341 clone_url = rc_web_server.repo_clone_url(
343 clone_url = rc_web_server.repo_clone_url(
342 HG_REPO, user=user.username, passwd=token)
344 HG_REPO, user=user.username, passwd=token)
343
345
344 stdout, stderr = Command('/tmp').execute(
346 stdout, stderr = Command('/tmp').execute(
345 'hg clone', clone_url, tmpdir.strpath)
347 'hg clone', clone_url, tmpdir.strpath)
346 _check_proper_clone(stdout, stderr, 'hg')
348 _check_proper_clone(stdout, stderr, 'hg')
347
349
348 def test_clone_by_auth_token_expired(
350 def test_clone_by_auth_token_expired(
349 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
351 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
350 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
352 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
351 'egg:rhodecode-enterprise-ce#rhodecode'])
353 'egg:rhodecode-enterprise-ce#rhodecode'])
352
354
353 user = user_util.create_user()
355 user = user_util.create_user()
354 auth_token = AuthTokenModel().create(
356 auth_token = AuthTokenModel().create(
355 user.user_id, 'test-token', -10, AuthTokenModel.cls.ROLE_VCS)
357 user.user_id, 'test-token', -10, AuthTokenModel.cls.ROLE_VCS)
356 token = auth_token.api_key
358 token = auth_token.api_key
357
359
358 clone_url = rc_web_server.repo_clone_url(
360 clone_url = rc_web_server.repo_clone_url(
359 HG_REPO, user=user.username, passwd=token)
361 HG_REPO, user=user.username, passwd=token)
360
362
361 stdout, stderr = Command('/tmp').execute(
363 stdout, stderr = Command('/tmp').execute(
362 'hg clone', clone_url, tmpdir.strpath)
364 'hg clone', clone_url, tmpdir.strpath)
363 assert 'abort: authorization failed' in stderr
365 assert 'abort: authorization failed' in stderr
364
366
365 def test_clone_by_auth_token_bad_role(
367 def test_clone_by_auth_token_bad_role(
366 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
368 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
367 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
369 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
368 'egg:rhodecode-enterprise-ce#rhodecode'])
370 'egg:rhodecode-enterprise-ce#rhodecode'])
369
371
370 user = user_util.create_user()
372 user = user_util.create_user()
371 auth_token = AuthTokenModel().create(
373 auth_token = AuthTokenModel().create(
372 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
374 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
373 token = auth_token.api_key
375 token = auth_token.api_key
374
376
375 clone_url = rc_web_server.repo_clone_url(
377 clone_url = rc_web_server.repo_clone_url(
376 HG_REPO, user=user.username, passwd=token)
378 HG_REPO, user=user.username, passwd=token)
377
379
378 stdout, stderr = Command('/tmp').execute(
380 stdout, stderr = Command('/tmp').execute(
379 'hg clone', clone_url, tmpdir.strpath)
381 'hg clone', clone_url, tmpdir.strpath)
380 assert 'abort: authorization failed' in stderr
382 assert 'abort: authorization failed' in stderr
381
383
382 def test_clone_by_auth_token_user_disabled(
384 def test_clone_by_auth_token_user_disabled(
383 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
385 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
384 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
386 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
385 'egg:rhodecode-enterprise-ce#rhodecode'])
387 'egg:rhodecode-enterprise-ce#rhodecode'])
386 user = user_util.create_user()
388 user = user_util.create_user()
387 user.active = False
389 user.active = False
388 Session().add(user)
390 Session().add(user)
389 Session().commit()
391 Session().commit()
390 token = user.auth_tokens[1]
392 token = user.auth_tokens[1]
391
393
392 clone_url = rc_web_server.repo_clone_url(
394 clone_url = rc_web_server.repo_clone_url(
393 HG_REPO, user=user.username, passwd=token)
395 HG_REPO, user=user.username, passwd=token)
394
396
395 stdout, stderr = Command('/tmp').execute(
397 stdout, stderr = Command('/tmp').execute(
396 'hg clone', clone_url, tmpdir.strpath)
398 'hg clone', clone_url, tmpdir.strpath)
397 assert 'abort: authorization failed' in stderr
399 assert 'abort: authorization failed' in stderr
398
400
399
400 def test_clone_by_auth_token_with_scope(
401 def test_clone_by_auth_token_with_scope(
401 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
402 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
402 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
403 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
403 'egg:rhodecode-enterprise-ce#rhodecode'])
404 'egg:rhodecode-enterprise-ce#rhodecode'])
404 user = user_util.create_user()
405 user = user_util.create_user()
405 auth_token = AuthTokenModel().create(
406 auth_token = AuthTokenModel().create(
406 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
407 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
407 token = auth_token.api_key
408 token = auth_token.api_key
408
409
409 # manually set scope
410 # manually set scope
410 auth_token.repo = Repository.get_by_repo_name(HG_REPO)
411 auth_token.repo = Repository.get_by_repo_name(HG_REPO)
411 Session().add(auth_token)
412 Session().add(auth_token)
412 Session().commit()
413 Session().commit()
413
414
414 clone_url = rc_web_server.repo_clone_url(
415 clone_url = rc_web_server.repo_clone_url(
415 HG_REPO, user=user.username, passwd=token)
416 HG_REPO, user=user.username, passwd=token)
416
417
417 stdout, stderr = Command('/tmp').execute(
418 stdout, stderr = Command('/tmp').execute(
418 'hg clone', clone_url, tmpdir.strpath)
419 'hg clone', clone_url, tmpdir.strpath)
419 _check_proper_clone(stdout, stderr, 'hg')
420 _check_proper_clone(stdout, stderr, 'hg')
420
421
421 def test_clone_by_auth_token_with_wrong_scope(
422 def test_clone_by_auth_token_with_wrong_scope(
422 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
423 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
423 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
424 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
424 'egg:rhodecode-enterprise-ce#rhodecode'])
425 'egg:rhodecode-enterprise-ce#rhodecode'])
425 user = user_util.create_user()
426 user = user_util.create_user()
426 auth_token = AuthTokenModel().create(
427 auth_token = AuthTokenModel().create(
427 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
428 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
428 token = auth_token.api_key
429 token = auth_token.api_key
429
430
430 # manually set scope
431 # manually set scope
431 auth_token.repo = Repository.get_by_repo_name(GIT_REPO)
432 auth_token.repo = Repository.get_by_repo_name(GIT_REPO)
432 Session().add(auth_token)
433 Session().add(auth_token)
433 Session().commit()
434 Session().commit()
434
435
435 clone_url = rc_web_server.repo_clone_url(
436 clone_url = rc_web_server.repo_clone_url(
436 HG_REPO, user=user.username, passwd=token)
437 HG_REPO, user=user.username, passwd=token)
437
438
438 stdout, stderr = Command('/tmp').execute(
439 stdout, stderr = Command('/tmp').execute(
439 'hg clone', clone_url, tmpdir.strpath)
440 'hg clone', clone_url, tmpdir.strpath)
440 assert 'abort: authorization failed' in stderr
441 assert 'abort: authorization failed' in stderr
441
442
442
443
443 def test_git_sets_default_branch_if_not_master(
444 def test_git_sets_default_branch_if_not_master(
444 backend_git, tmpdir, disable_locking, rc_web_server):
445 backend_git, tmpdir, disable_locking, rc_web_server):
445 empty_repo = backend_git.create_repo()
446 empty_repo = backend_git.create_repo()
446 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
447 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
447
448
448 cmd = Command(tmpdir.strpath)
449 cmd = Command(tmpdir.strpath)
449 cmd.execute('git clone', clone_url)
450 cmd.execute('git clone', clone_url)
450
451
451 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
452 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
452 repo.in_memory_commit.add(FileNode('file', content=''))
453 repo.in_memory_commit.add(FileNode('file', content=''))
453 repo.in_memory_commit.commit(
454 repo.in_memory_commit.commit(
454 message='Commit on branch test',
455 message='Commit on branch test',
455 author='Automatic test',
456 author='Automatic test',
456 branch='test')
457 branch='test')
457
458
458 repo_cmd = Command(repo.path)
459 repo_cmd = Command(repo.path)
459 stdout, stderr = repo_cmd.execute('git push --verbose origin test')
460 stdout, stderr = repo_cmd.execute('git push --verbose origin test')
460 _check_proper_git_push(
461 _check_proper_git_push(
461 stdout, stderr, branch='test', should_set_default_branch=True)
462 stdout, stderr, branch='test', should_set_default_branch=True)
462
463
463 stdout, stderr = cmd.execute(
464 stdout, stderr = cmd.execute(
464 'git clone', clone_url, empty_repo.repo_name + '-clone')
465 'git clone', clone_url, empty_repo.repo_name + '-clone')
465 _check_proper_clone(stdout, stderr, 'git')
466 _check_proper_clone(stdout, stderr, 'git')
466
467
467 # Doing an explicit commit in order to get latest user logs on MySQL
468 # Doing an explicit commit in order to get latest user logs on MySQL
468 Session().commit()
469 Session().commit()
469
470
470
471
471 def test_git_fetches_from_remote_repository_with_annotated_tags(
472 def test_git_fetches_from_remote_repository_with_annotated_tags(
472 backend_git, disable_locking, rc_web_server):
473 backend_git, disable_locking, rc_web_server):
473 # Note: This is a test specific to the git backend. It checks the
474 # Note: This is a test specific to the git backend. It checks the
474 # integration of fetching from a remote repository which contains
475 # integration of fetching from a remote repository which contains
475 # annotated tags.
476 # annotated tags.
476
477
477 # Dulwich shows this specific behavior only when
478 # Dulwich shows this specific behavior only when
478 # operating against a remote repository.
479 # operating against a remote repository.
479 source_repo = backend_git['annotated-tag']
480 source_repo = backend_git['annotated-tag']
480 target_vcs_repo = backend_git.create_repo().scm_instance()
481 target_vcs_repo = backend_git.create_repo().scm_instance()
481 target_vcs_repo.fetch(rc_web_server.repo_clone_url(source_repo.repo_name))
482 target_vcs_repo.fetch(rc_web_server.repo_clone_url(source_repo.repo_name))
483
484
485 def test_git_push_shows_pull_request_refs(backend_git, rc_web_server, tmpdir):
486 """
487 test if remote info about refs is visible
488 """
489 empty_repo = backend_git.create_repo()
490
491 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
492
493 cmd = Command(tmpdir.strpath)
494 cmd.execute('git clone', clone_url)
495
496 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
497 repo.in_memory_commit.add(FileNode('readme.md', content='## Hello'))
498 repo.in_memory_commit.commit(
499 message='Commit on branch Master',
500 author='Automatic test',
501 branch='master')
502
503 repo_cmd = Command(repo.path)
504 stdout, stderr = repo_cmd.execute('git push --verbose origin master')
505 _check_proper_git_push(stdout, stderr, branch='master')
506
507 ref = '{}/{}/pull-request/new?branch=master'.format(
508 rc_web_server.host_url(), empty_repo.repo_name)
509 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
510 assert 'remote: RhodeCode: push completed' in stderr
511
512 # push on the same branch
513 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
514 repo.in_memory_commit.add(FileNode('setup.py', content='print\n'))
515 repo.in_memory_commit.commit(
516 message='Commit2 on branch Master',
517 author='Automatic test2',
518 branch='master')
519
520 repo_cmd = Command(repo.path)
521 stdout, stderr = repo_cmd.execute('git push --verbose origin master')
522 _check_proper_git_push(stdout, stderr, branch='master')
523
524 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
525 assert 'remote: RhodeCode: push completed' in stderr
526
527 # new Branch
528 repo = GitRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
529 repo.in_memory_commit.add(FileNode('feature1.py', content='## Hello world'))
530 repo.in_memory_commit.commit(
531 message='Commit on branch feature',
532 author='Automatic test',
533 branch='feature')
534
535 repo_cmd = Command(repo.path)
536 stdout, stderr = repo_cmd.execute('git push --verbose origin feature')
537 _check_proper_git_push(stdout, stderr, branch='feature')
538
539 ref = '{}/{}/pull-request/new?branch=feature'.format(
540 rc_web_server.host_url(), empty_repo.repo_name)
541 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stderr
542 assert 'remote: RhodeCode: push completed' in stderr
543
544
545 def test_hg_push_shows_pull_request_refs(backend_hg, rc_web_server, tmpdir):
546 empty_repo = backend_hg.create_repo()
547
548 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
549
550 cmd = Command(tmpdir.strpath)
551 cmd.execute('hg clone', clone_url)
552
553 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
554 repo.in_memory_commit.add(FileNode(u'readme.md', content=u'## Hello'))
555 repo.in_memory_commit.commit(
556 message=u'Commit on branch default',
557 author=u'Automatic test',
558 branch='default')
559
560 repo_cmd = Command(repo.path)
561 repo_cmd.execute('hg checkout default')
562
563 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
564 _check_proper_hg_push(stdout, stderr, branch='default')
565
566 ref = '{}/{}/pull-request/new?branch=default'.format(
567 rc_web_server.host_url(), empty_repo.repo_name)
568 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
569 assert 'remote: RhodeCode: push completed' in stdout
570
571 # push on the same branch
572 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
573 repo.in_memory_commit.add(FileNode(u'setup.py', content=u'print\n'))
574 repo.in_memory_commit.commit(
575 message=u'Commit2 on branch default',
576 author=u'Automatic test2',
577 branch=u'default')
578
579 repo_cmd = Command(repo.path)
580 repo_cmd.execute('hg checkout default')
581
582 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
583 _check_proper_hg_push(stdout, stderr, branch='default')
584
585 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
586 assert 'remote: RhodeCode: push completed' in stdout
587
588 # new Branch
589 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
590 repo.in_memory_commit.add(FileNode(u'feature1.py', content=u'## Hello world'))
591 repo.in_memory_commit.commit(
592 message=u'Commit on branch feature',
593 author=u'Automatic test',
594 branch=u'feature')
595
596 repo_cmd = Command(repo.path)
597 repo_cmd.execute('hg checkout feature')
598
599 stdout, stderr = repo_cmd.execute('hg push --new-branch --verbose', clone_url)
600 _check_proper_hg_push(stdout, stderr, branch='feature')
601
602 ref = '{}/{}/pull-request/new?branch=feature'.format(
603 rc_web_server.host_url(), empty_repo.repo_name)
604 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
605 assert 'remote: RhodeCode: push completed' in stdout
606
607
608 def test_hg_push_shows_pull_request_refs_book(backend_hg, rc_web_server, tmpdir):
609 empty_repo = backend_hg.create_repo()
610
611 clone_url = rc_web_server.repo_clone_url(empty_repo.repo_name)
612
613 cmd = Command(tmpdir.strpath)
614 cmd.execute('hg clone', clone_url)
615
616 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
617 repo.in_memory_commit.add(FileNode(u'readme.md', content=u'## Hello'))
618 repo.in_memory_commit.commit(
619 message=u'Commit on branch default',
620 author=u'Automatic test',
621 branch='default')
622
623 repo_cmd = Command(repo.path)
624 repo_cmd.execute('hg checkout default')
625
626 stdout, stderr = repo_cmd.execute('hg push --verbose', clone_url)
627 _check_proper_hg_push(stdout, stderr, branch='default')
628
629 ref = '{}/{}/pull-request/new?branch=default'.format(
630 rc_web_server.host_url(), empty_repo.repo_name)
631 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
632 assert 'remote: RhodeCode: push completed' in stdout
633
634 # add bookmark
635 repo = MercurialRepository(os.path.join(tmpdir.strpath, empty_repo.repo_name))
636 repo.in_memory_commit.add(FileNode(u'setup.py', content=u'print\n'))
637 repo.in_memory_commit.commit(
638 message=u'Commit2 on branch default',
639 author=u'Automatic test2',
640 branch=u'default')
641
642 repo_cmd = Command(repo.path)
643 repo_cmd.execute('hg checkout default')
644 repo_cmd.execute('hg bookmark feature2')
645 stdout, stderr = repo_cmd.execute('hg push -B feature2 --verbose', clone_url)
646 _check_proper_hg_push(stdout, stderr, branch='default')
647
648 ref = '{}/{}/pull-request/new?branch=default'.format(
649 rc_web_server.host_url(), empty_repo.repo_name)
650 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
651 ref = '{}/{}/pull-request/new?bookmark=feature2'.format(
652 rc_web_server.host_url(), empty_repo.repo_name)
653 assert 'remote: RhodeCode: open pull request link: {}'.format(ref) in stdout
654 assert 'remote: RhodeCode: push completed' in stdout
655 assert 'exporting bookmark feature2' in stdout
General Comments 0
You need to be logged in to leave comments. Login now