##// END OF EJS Templates
repos: recover properly from bad extraction of repo_id from URL and DB calls.
super-admin -
r4725:65a9cd36 default
parent child Browse files
Show More
@@ -1,1183 +1,1185 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 re
22 import re
23 import shutil
23 import shutil
24 import time
24 import time
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28
28
29 from pyramid.threadlocal import get_current_request
29 from pyramid.threadlocal import get_current_request
30 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
31
31
32 from rhodecode import events
32 from rhodecode import events
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
33 from rhodecode.lib.auth import HasUserGroupPermissionAny
34 from rhodecode.lib.caching_query import FromCache
34 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
35 from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError
36 from rhodecode.lib import hooks_base
36 from rhodecode.lib import hooks_base
37 from rhodecode.lib.user_log_filter import user_log_filter
37 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.utils import make_db_config
38 from rhodecode.lib.utils import make_db_config
39 from rhodecode.lib.utils2 import (
39 from rhodecode.lib.utils2 import (
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
40 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
41 get_current_rhodecode_user, safe_int, action_logger_generic)
41 get_current_rhodecode_user, safe_int, action_logger_generic)
42 from rhodecode.lib.vcs.backends import get_backend
42 from rhodecode.lib.vcs.backends import get_backend
43 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, func, case, joinedload, or_, in_filter_generator,
45 _hash_key, func, case, joinedload, or_, in_filter_generator,
46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
46 Session, Repository, UserRepoToPerm, UserGroupRepoToPerm,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
47 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission,
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
48 Statistics, UserGroup, RepoGroup, RepositoryField, UserLog)
49 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.permission import PermissionModel
50 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.settings import VcsSettingsModel
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class RepoModel(BaseModel):
55 class RepoModel(BaseModel):
56
56
57 cls = Repository
57 cls = Repository
58
58
59 def _get_user_group(self, users_group):
59 def _get_user_group(self, users_group):
60 return self._get_instance(UserGroup, users_group,
60 return self._get_instance(UserGroup, users_group,
61 callback=UserGroup.get_by_group_name)
61 callback=UserGroup.get_by_group_name)
62
62
63 def _get_repo_group(self, repo_group):
63 def _get_repo_group(self, repo_group):
64 return self._get_instance(RepoGroup, repo_group,
64 return self._get_instance(RepoGroup, repo_group,
65 callback=RepoGroup.get_by_group_name)
65 callback=RepoGroup.get_by_group_name)
66
66
67 def _create_default_perms(self, repository, private):
67 def _create_default_perms(self, repository, private):
68 # create default permission
68 # create default permission
69 default = 'repository.read'
69 default = 'repository.read'
70 def_user = User.get_default_user()
70 def_user = User.get_default_user()
71 for p in def_user.user_perms:
71 for p in def_user.user_perms:
72 if p.permission.permission_name.startswith('repository.'):
72 if p.permission.permission_name.startswith('repository.'):
73 default = p.permission.permission_name
73 default = p.permission.permission_name
74 break
74 break
75
75
76 default_perm = 'repository.none' if private else default
76 default_perm = 'repository.none' if private else default
77
77
78 repo_to_perm = UserRepoToPerm()
78 repo_to_perm = UserRepoToPerm()
79 repo_to_perm.permission = Permission.get_by_key(default_perm)
79 repo_to_perm.permission = Permission.get_by_key(default_perm)
80
80
81 repo_to_perm.repository = repository
81 repo_to_perm.repository = repository
82 repo_to_perm.user_id = def_user.user_id
82 repo_to_perm.user_id = def_user.user_id
83
83
84 return repo_to_perm
84 return repo_to_perm
85
85
86 @LazyProperty
86 @LazyProperty
87 def repos_path(self):
87 def repos_path(self):
88 """
88 """
89 Gets the repositories root path from database
89 Gets the repositories root path from database
90 """
90 """
91 settings_model = VcsSettingsModel(sa=self.sa)
91 settings_model = VcsSettingsModel(sa=self.sa)
92 return settings_model.get_repos_location()
92 return settings_model.get_repos_location()
93
93
94 def get(self, repo_id):
94 def get(self, repo_id):
95 repo = self.sa.query(Repository) \
95 repo = self.sa.query(Repository) \
96 .filter(Repository.repo_id == repo_id)
96 .filter(Repository.repo_id == repo_id)
97
97
98 return repo.scalar()
98 return repo.scalar()
99
99
100 def get_repo(self, repository):
100 def get_repo(self, repository):
101 return self._get_repo(repository)
101 return self._get_repo(repository)
102
102
103 def get_by_repo_name(self, repo_name, cache=False):
103 def get_by_repo_name(self, repo_name, cache=False):
104 repo = self.sa.query(Repository) \
104 repo = self.sa.query(Repository) \
105 .filter(Repository.repo_name == repo_name)
105 .filter(Repository.repo_name == repo_name)
106
106
107 if cache:
107 if cache:
108 name_key = _hash_key(repo_name)
108 name_key = _hash_key(repo_name)
109 repo = repo.options(
109 repo = repo.options(
110 FromCache("sql_cache_short", "get_repo_%s" % name_key))
110 FromCache("sql_cache_short", "get_repo_%s" % name_key))
111 return repo.scalar()
111 return repo.scalar()
112
112
113 def _extract_id_from_repo_name(self, repo_name):
113 def _extract_id_from_repo_name(self, repo_name):
114 if repo_name.startswith('/'):
114 if repo_name.startswith('/'):
115 repo_name = repo_name.lstrip('/')
115 repo_name = repo_name.lstrip('/')
116 by_id_match = re.match(r'^_(\d{1,})', repo_name)
116 by_id_match = re.match(r'^_(\d{1,})', repo_name)
117 if by_id_match:
117 if by_id_match:
118 return by_id_match.groups()[0]
118 return by_id_match.groups()[0]
119
119
120 def get_repo_by_id(self, repo_name):
120 def get_repo_by_id(self, repo_name):
121 """
121 """
122 Extracts repo_name by id from special urls.
122 Extracts repo_name by id from special urls.
123 Example url is _11/repo_name
123 Example url is _11/repo_name
124
124
125 :param repo_name:
125 :param repo_name:
126 :return: repo object if matched else None
126 :return: repo object if matched else None
127 """
127 """
128
128 _repo_id = None
129 try:
129 try:
130 _repo_id = self._extract_id_from_repo_name(repo_name)
130 _repo_id = self._extract_id_from_repo_name(repo_name)
131 if _repo_id:
131 if _repo_id:
132 return self.get(_repo_id)
132 return self.get(_repo_id)
133 except Exception:
133 except Exception:
134 log.exception('Failed to extract repo_name from URL')
134 log.exception('Failed to extract repo_name from URL')
135 if _repo_id:
136 Session().rollback()
135
137
136 return None
138 return None
137
139
138 def get_repos_for_root(self, root, traverse=False):
140 def get_repos_for_root(self, root, traverse=False):
139 if traverse:
141 if traverse:
140 like_expression = u'{}%'.format(safe_unicode(root))
142 like_expression = u'{}%'.format(safe_unicode(root))
141 repos = Repository.query().filter(
143 repos = Repository.query().filter(
142 Repository.repo_name.like(like_expression)).all()
144 Repository.repo_name.like(like_expression)).all()
143 else:
145 else:
144 if root and not isinstance(root, RepoGroup):
146 if root and not isinstance(root, RepoGroup):
145 raise ValueError(
147 raise ValueError(
146 'Root must be an instance '
148 'Root must be an instance '
147 'of RepoGroup, got:{} instead'.format(type(root)))
149 'of RepoGroup, got:{} instead'.format(type(root)))
148 repos = Repository.query().filter(Repository.group == root).all()
150 repos = Repository.query().filter(Repository.group == root).all()
149 return repos
151 return repos
150
152
151 def get_url(self, repo, request=None, permalink=False):
153 def get_url(self, repo, request=None, permalink=False):
152 if not request:
154 if not request:
153 request = get_current_request()
155 request = get_current_request()
154
156
155 if not request:
157 if not request:
156 return
158 return
157
159
158 if permalink:
160 if permalink:
159 return request.route_url(
161 return request.route_url(
160 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
162 'repo_summary', repo_name='_{}'.format(safe_str(repo.repo_id)))
161 else:
163 else:
162 return request.route_url(
164 return request.route_url(
163 'repo_summary', repo_name=safe_str(repo.repo_name))
165 'repo_summary', repo_name=safe_str(repo.repo_name))
164
166
165 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
167 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
166 if not request:
168 if not request:
167 request = get_current_request()
169 request = get_current_request()
168
170
169 if not request:
171 if not request:
170 return
172 return
171
173
172 if permalink:
174 if permalink:
173 return request.route_url(
175 return request.route_url(
174 'repo_commit', repo_name=safe_str(repo.repo_id),
176 'repo_commit', repo_name=safe_str(repo.repo_id),
175 commit_id=commit_id)
177 commit_id=commit_id)
176
178
177 else:
179 else:
178 return request.route_url(
180 return request.route_url(
179 'repo_commit', repo_name=safe_str(repo.repo_name),
181 'repo_commit', repo_name=safe_str(repo.repo_name),
180 commit_id=commit_id)
182 commit_id=commit_id)
181
183
182 def get_repo_log(self, repo, filter_term):
184 def get_repo_log(self, repo, filter_term):
183 repo_log = UserLog.query()\
185 repo_log = UserLog.query()\
184 .filter(or_(UserLog.repository_id == repo.repo_id,
186 .filter(or_(UserLog.repository_id == repo.repo_id,
185 UserLog.repository_name == repo.repo_name))\
187 UserLog.repository_name == repo.repo_name))\
186 .options(joinedload(UserLog.user))\
188 .options(joinedload(UserLog.user))\
187 .options(joinedload(UserLog.repository))\
189 .options(joinedload(UserLog.repository))\
188 .order_by(UserLog.action_date.desc())
190 .order_by(UserLog.action_date.desc())
189
191
190 repo_log = user_log_filter(repo_log, filter_term)
192 repo_log = user_log_filter(repo_log, filter_term)
191 return repo_log
193 return repo_log
192
194
193 @classmethod
195 @classmethod
194 def update_commit_cache(cls, repositories=None):
196 def update_commit_cache(cls, repositories=None):
195 if not repositories:
197 if not repositories:
196 repositories = Repository.getAll()
198 repositories = Repository.getAll()
197 for repo in repositories:
199 for repo in repositories:
198 repo.update_commit_cache()
200 repo.update_commit_cache()
199
201
200 def get_repos_as_dict(self, repo_list=None, admin=False,
202 def get_repos_as_dict(self, repo_list=None, admin=False,
201 super_user_actions=False, short_name=None):
203 super_user_actions=False, short_name=None):
202
204
203 _render = get_current_request().get_partial_renderer(
205 _render = get_current_request().get_partial_renderer(
204 'rhodecode:templates/data_table/_dt_elements.mako')
206 'rhodecode:templates/data_table/_dt_elements.mako')
205 c = _render.get_call_context()
207 c = _render.get_call_context()
206 h = _render.get_helpers()
208 h = _render.get_helpers()
207
209
208 def quick_menu(repo_name):
210 def quick_menu(repo_name):
209 return _render('quick_menu', repo_name)
211 return _render('quick_menu', repo_name)
210
212
211 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
213 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
212 if short_name is not None:
214 if short_name is not None:
213 short_name_var = short_name
215 short_name_var = short_name
214 else:
216 else:
215 short_name_var = not admin
217 short_name_var = not admin
216 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
218 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
217 short_name=short_name_var, admin=False)
219 short_name=short_name_var, admin=False)
218
220
219 def last_change(last_change):
221 def last_change(last_change):
220 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
222 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
221 ts = time.time()
223 ts = time.time()
222 utc_offset = (datetime.datetime.fromtimestamp(ts)
224 utc_offset = (datetime.datetime.fromtimestamp(ts)
223 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
225 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
224 last_change = last_change + datetime.timedelta(seconds=utc_offset)
226 last_change = last_change + datetime.timedelta(seconds=utc_offset)
225
227
226 return _render("last_change", last_change)
228 return _render("last_change", last_change)
227
229
228 def rss_lnk(repo_name):
230 def rss_lnk(repo_name):
229 return _render("rss", repo_name)
231 return _render("rss", repo_name)
230
232
231 def atom_lnk(repo_name):
233 def atom_lnk(repo_name):
232 return _render("atom", repo_name)
234 return _render("atom", repo_name)
233
235
234 def last_rev(repo_name, cs_cache):
236 def last_rev(repo_name, cs_cache):
235 return _render('revision', repo_name, cs_cache.get('revision'),
237 return _render('revision', repo_name, cs_cache.get('revision'),
236 cs_cache.get('raw_id'), cs_cache.get('author'),
238 cs_cache.get('raw_id'), cs_cache.get('author'),
237 cs_cache.get('message'), cs_cache.get('date'))
239 cs_cache.get('message'), cs_cache.get('date'))
238
240
239 def desc(desc):
241 def desc(desc):
240 return _render('repo_desc', desc, c.visual.stylify_metatags)
242 return _render('repo_desc', desc, c.visual.stylify_metatags)
241
243
242 def state(repo_state):
244 def state(repo_state):
243 return _render("repo_state", repo_state)
245 return _render("repo_state", repo_state)
244
246
245 def repo_actions(repo_name):
247 def repo_actions(repo_name):
246 return _render('repo_actions', repo_name, super_user_actions)
248 return _render('repo_actions', repo_name, super_user_actions)
247
249
248 def user_profile(username):
250 def user_profile(username):
249 return _render('user_profile', username)
251 return _render('user_profile', username)
250
252
251 repos_data = []
253 repos_data = []
252 for repo in repo_list:
254 for repo in repo_list:
253 # NOTE(marcink): because we use only raw column we need to load it like that
255 # NOTE(marcink): because we use only raw column we need to load it like that
254 changeset_cache = Repository._load_changeset_cache(
256 changeset_cache = Repository._load_changeset_cache(
255 repo.repo_id, repo._changeset_cache)
257 repo.repo_id, repo._changeset_cache)
256
258
257 row = {
259 row = {
258 "menu": quick_menu(repo.repo_name),
260 "menu": quick_menu(repo.repo_name),
259
261
260 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
262 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
261 repo.private, repo.archived, repo.fork),
263 repo.private, repo.archived, repo.fork),
262
264
263 "desc": desc(h.escape(repo.description)),
265 "desc": desc(h.escape(repo.description)),
264
266
265 "last_change": last_change(repo.updated_on),
267 "last_change": last_change(repo.updated_on),
266
268
267 "last_changeset": last_rev(repo.repo_name, changeset_cache),
269 "last_changeset": last_rev(repo.repo_name, changeset_cache),
268 "last_changeset_raw": changeset_cache.get('revision'),
270 "last_changeset_raw": changeset_cache.get('revision'),
269
271
270 "owner": user_profile(repo.User.username),
272 "owner": user_profile(repo.User.username),
271
273
272 "state": state(repo.repo_state),
274 "state": state(repo.repo_state),
273 "rss": rss_lnk(repo.repo_name),
275 "rss": rss_lnk(repo.repo_name),
274 "atom": atom_lnk(repo.repo_name),
276 "atom": atom_lnk(repo.repo_name),
275 }
277 }
276 if admin:
278 if admin:
277 row.update({
279 row.update({
278 "action": repo_actions(repo.repo_name),
280 "action": repo_actions(repo.repo_name),
279 })
281 })
280 repos_data.append(row)
282 repos_data.append(row)
281
283
282 return repos_data
284 return repos_data
283
285
284 def get_repos_data_table(
286 def get_repos_data_table(
285 self, draw, start, limit,
287 self, draw, start, limit,
286 search_q, order_by, order_dir,
288 search_q, order_by, order_dir,
287 auth_user, repo_group_id):
289 auth_user, repo_group_id):
288 from rhodecode.model.scm import RepoList
290 from rhodecode.model.scm import RepoList
289
291
290 _perms = ['repository.read', 'repository.write', 'repository.admin']
292 _perms = ['repository.read', 'repository.write', 'repository.admin']
291
293
292 repos = Repository.query() \
294 repos = Repository.query() \
293 .filter(Repository.group_id == repo_group_id) \
295 .filter(Repository.group_id == repo_group_id) \
294 .all()
296 .all()
295 auth_repo_list = RepoList(
297 auth_repo_list = RepoList(
296 repos, perm_set=_perms,
298 repos, perm_set=_perms,
297 extra_kwargs=dict(user=auth_user))
299 extra_kwargs=dict(user=auth_user))
298
300
299 allowed_ids = [-1]
301 allowed_ids = [-1]
300 for repo in auth_repo_list:
302 for repo in auth_repo_list:
301 allowed_ids.append(repo.repo_id)
303 allowed_ids.append(repo.repo_id)
302
304
303 repos_data_total_count = Repository.query() \
305 repos_data_total_count = Repository.query() \
304 .filter(Repository.group_id == repo_group_id) \
306 .filter(Repository.group_id == repo_group_id) \
305 .filter(or_(
307 .filter(or_(
306 # generate multiple IN to fix limitation problems
308 # generate multiple IN to fix limitation problems
307 *in_filter_generator(Repository.repo_id, allowed_ids))
309 *in_filter_generator(Repository.repo_id, allowed_ids))
308 ) \
310 ) \
309 .count()
311 .count()
310
312
311 base_q = Session.query(
313 base_q = Session.query(
312 Repository.repo_id,
314 Repository.repo_id,
313 Repository.repo_name,
315 Repository.repo_name,
314 Repository.description,
316 Repository.description,
315 Repository.repo_type,
317 Repository.repo_type,
316 Repository.repo_state,
318 Repository.repo_state,
317 Repository.private,
319 Repository.private,
318 Repository.archived,
320 Repository.archived,
319 Repository.fork,
321 Repository.fork,
320 Repository.updated_on,
322 Repository.updated_on,
321 Repository._changeset_cache,
323 Repository._changeset_cache,
322 User,
324 User,
323 ) \
325 ) \
324 .filter(Repository.group_id == repo_group_id) \
326 .filter(Repository.group_id == repo_group_id) \
325 .filter(or_(
327 .filter(or_(
326 # generate multiple IN to fix limitation problems
328 # generate multiple IN to fix limitation problems
327 *in_filter_generator(Repository.repo_id, allowed_ids))
329 *in_filter_generator(Repository.repo_id, allowed_ids))
328 ) \
330 ) \
329 .join(User, User.user_id == Repository.user_id) \
331 .join(User, User.user_id == Repository.user_id) \
330 .group_by(Repository, User)
332 .group_by(Repository, User)
331
333
332 repos_data_total_filtered_count = base_q.count()
334 repos_data_total_filtered_count = base_q.count()
333
335
334 sort_defined = False
336 sort_defined = False
335 if order_by == 'repo_name':
337 if order_by == 'repo_name':
336 sort_col = func.lower(Repository.repo_name)
338 sort_col = func.lower(Repository.repo_name)
337 sort_defined = True
339 sort_defined = True
338 elif order_by == 'user_username':
340 elif order_by == 'user_username':
339 sort_col = User.username
341 sort_col = User.username
340 else:
342 else:
341 sort_col = getattr(Repository, order_by, None)
343 sort_col = getattr(Repository, order_by, None)
342
344
343 if sort_defined or sort_col:
345 if sort_defined or sort_col:
344 if order_dir == 'asc':
346 if order_dir == 'asc':
345 sort_col = sort_col.asc()
347 sort_col = sort_col.asc()
346 else:
348 else:
347 sort_col = sort_col.desc()
349 sort_col = sort_col.desc()
348
350
349 base_q = base_q.order_by(sort_col)
351 base_q = base_q.order_by(sort_col)
350 base_q = base_q.offset(start).limit(limit)
352 base_q = base_q.offset(start).limit(limit)
351
353
352 repos_list = base_q.all()
354 repos_list = base_q.all()
353
355
354 repos_data = RepoModel().get_repos_as_dict(
356 repos_data = RepoModel().get_repos_as_dict(
355 repo_list=repos_list, admin=False)
357 repo_list=repos_list, admin=False)
356
358
357 data = ({
359 data = ({
358 'draw': draw,
360 'draw': draw,
359 'data': repos_data,
361 'data': repos_data,
360 'recordsTotal': repos_data_total_count,
362 'recordsTotal': repos_data_total_count,
361 'recordsFiltered': repos_data_total_filtered_count,
363 'recordsFiltered': repos_data_total_filtered_count,
362 })
364 })
363 return data
365 return data
364
366
365 def _get_defaults(self, repo_name):
367 def _get_defaults(self, repo_name):
366 """
368 """
367 Gets information about repository, and returns a dict for
369 Gets information about repository, and returns a dict for
368 usage in forms
370 usage in forms
369
371
370 :param repo_name:
372 :param repo_name:
371 """
373 """
372
374
373 repo_info = Repository.get_by_repo_name(repo_name)
375 repo_info = Repository.get_by_repo_name(repo_name)
374
376
375 if repo_info is None:
377 if repo_info is None:
376 return None
378 return None
377
379
378 defaults = repo_info.get_dict()
380 defaults = repo_info.get_dict()
379 defaults['repo_name'] = repo_info.just_name
381 defaults['repo_name'] = repo_info.just_name
380
382
381 groups = repo_info.groups_with_parents
383 groups = repo_info.groups_with_parents
382 parent_group = groups[-1] if groups else None
384 parent_group = groups[-1] if groups else None
383
385
384 # we use -1 as this is how in HTML, we mark an empty group
386 # we use -1 as this is how in HTML, we mark an empty group
385 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
387 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
386
388
387 keys_to_process = (
389 keys_to_process = (
388 {'k': 'repo_type', 'strip': False},
390 {'k': 'repo_type', 'strip': False},
389 {'k': 'repo_enable_downloads', 'strip': True},
391 {'k': 'repo_enable_downloads', 'strip': True},
390 {'k': 'repo_description', 'strip': True},
392 {'k': 'repo_description', 'strip': True},
391 {'k': 'repo_enable_locking', 'strip': True},
393 {'k': 'repo_enable_locking', 'strip': True},
392 {'k': 'repo_landing_rev', 'strip': True},
394 {'k': 'repo_landing_rev', 'strip': True},
393 {'k': 'clone_uri', 'strip': False},
395 {'k': 'clone_uri', 'strip': False},
394 {'k': 'push_uri', 'strip': False},
396 {'k': 'push_uri', 'strip': False},
395 {'k': 'repo_private', 'strip': True},
397 {'k': 'repo_private', 'strip': True},
396 {'k': 'repo_enable_statistics', 'strip': True}
398 {'k': 'repo_enable_statistics', 'strip': True}
397 )
399 )
398
400
399 for item in keys_to_process:
401 for item in keys_to_process:
400 attr = item['k']
402 attr = item['k']
401 if item['strip']:
403 if item['strip']:
402 attr = remove_prefix(item['k'], 'repo_')
404 attr = remove_prefix(item['k'], 'repo_')
403
405
404 val = defaults[attr]
406 val = defaults[attr]
405 if item['k'] == 'repo_landing_rev':
407 if item['k'] == 'repo_landing_rev':
406 val = ':'.join(defaults[attr])
408 val = ':'.join(defaults[attr])
407 defaults[item['k']] = val
409 defaults[item['k']] = val
408 if item['k'] == 'clone_uri':
410 if item['k'] == 'clone_uri':
409 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
411 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
410 if item['k'] == 'push_uri':
412 if item['k'] == 'push_uri':
411 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
413 defaults['push_uri_hidden'] = repo_info.push_uri_hidden
412
414
413 # fill owner
415 # fill owner
414 if repo_info.user:
416 if repo_info.user:
415 defaults.update({'user': repo_info.user.username})
417 defaults.update({'user': repo_info.user.username})
416 else:
418 else:
417 replacement_user = User.get_first_super_admin().username
419 replacement_user = User.get_first_super_admin().username
418 defaults.update({'user': replacement_user})
420 defaults.update({'user': replacement_user})
419
421
420 return defaults
422 return defaults
421
423
422 def update(self, repo, **kwargs):
424 def update(self, repo, **kwargs):
423 try:
425 try:
424 cur_repo = self._get_repo(repo)
426 cur_repo = self._get_repo(repo)
425 source_repo_name = cur_repo.repo_name
427 source_repo_name = cur_repo.repo_name
426
428
427 affected_user_ids = []
429 affected_user_ids = []
428 if 'user' in kwargs:
430 if 'user' in kwargs:
429 old_owner_id = cur_repo.user.user_id
431 old_owner_id = cur_repo.user.user_id
430 new_owner = User.get_by_username(kwargs['user'])
432 new_owner = User.get_by_username(kwargs['user'])
431 cur_repo.user = new_owner
433 cur_repo.user = new_owner
432
434
433 if old_owner_id != new_owner.user_id:
435 if old_owner_id != new_owner.user_id:
434 affected_user_ids = [new_owner.user_id, old_owner_id]
436 affected_user_ids = [new_owner.user_id, old_owner_id]
435
437
436 if 'repo_group' in kwargs:
438 if 'repo_group' in kwargs:
437 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
439 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
438 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
440 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
439
441
440 update_keys = [
442 update_keys = [
441 (1, 'repo_description'),
443 (1, 'repo_description'),
442 (1, 'repo_landing_rev'),
444 (1, 'repo_landing_rev'),
443 (1, 'repo_private'),
445 (1, 'repo_private'),
444 (1, 'repo_enable_downloads'),
446 (1, 'repo_enable_downloads'),
445 (1, 'repo_enable_locking'),
447 (1, 'repo_enable_locking'),
446 (1, 'repo_enable_statistics'),
448 (1, 'repo_enable_statistics'),
447 (0, 'clone_uri'),
449 (0, 'clone_uri'),
448 (0, 'push_uri'),
450 (0, 'push_uri'),
449 (0, 'fork_id')
451 (0, 'fork_id')
450 ]
452 ]
451 for strip, k in update_keys:
453 for strip, k in update_keys:
452 if k in kwargs:
454 if k in kwargs:
453 val = kwargs[k]
455 val = kwargs[k]
454 if strip:
456 if strip:
455 k = remove_prefix(k, 'repo_')
457 k = remove_prefix(k, 'repo_')
456
458
457 setattr(cur_repo, k, val)
459 setattr(cur_repo, k, val)
458
460
459 new_name = cur_repo.get_new_name(kwargs['repo_name'])
461 new_name = cur_repo.get_new_name(kwargs['repo_name'])
460 cur_repo.repo_name = new_name
462 cur_repo.repo_name = new_name
461
463
462 # if private flag is set, reset default permission to NONE
464 # if private flag is set, reset default permission to NONE
463 if kwargs.get('repo_private'):
465 if kwargs.get('repo_private'):
464 EMPTY_PERM = 'repository.none'
466 EMPTY_PERM = 'repository.none'
465 RepoModel().grant_user_permission(
467 RepoModel().grant_user_permission(
466 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
468 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
467 )
469 )
468
470
469 # handle extra fields
471 # handle extra fields
470 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
472 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
471 k = RepositoryField.un_prefix_key(field)
473 k = RepositoryField.un_prefix_key(field)
472 ex_field = RepositoryField.get_by_key_name(
474 ex_field = RepositoryField.get_by_key_name(
473 key=k, repo=cur_repo)
475 key=k, repo=cur_repo)
474 if ex_field:
476 if ex_field:
475 ex_field.field_value = kwargs[field]
477 ex_field.field_value = kwargs[field]
476 self.sa.add(ex_field)
478 self.sa.add(ex_field)
477
479
478 self.sa.add(cur_repo)
480 self.sa.add(cur_repo)
479
481
480 if source_repo_name != new_name:
482 if source_repo_name != new_name:
481 # rename repository
483 # rename repository
482 self._rename_filesystem_repo(
484 self._rename_filesystem_repo(
483 old=source_repo_name, new=new_name)
485 old=source_repo_name, new=new_name)
484
486
485 if affected_user_ids:
487 if affected_user_ids:
486 PermissionModel().trigger_permission_flush(affected_user_ids)
488 PermissionModel().trigger_permission_flush(affected_user_ids)
487
489
488 return cur_repo
490 return cur_repo
489 except Exception:
491 except Exception:
490 log.error(traceback.format_exc())
492 log.error(traceback.format_exc())
491 raise
493 raise
492
494
493 def _create_repo(self, repo_name, repo_type, description, owner,
495 def _create_repo(self, repo_name, repo_type, description, owner,
494 private=False, clone_uri=None, repo_group=None,
496 private=False, clone_uri=None, repo_group=None,
495 landing_rev='rev:tip', fork_of=None,
497 landing_rev='rev:tip', fork_of=None,
496 copy_fork_permissions=False, enable_statistics=False,
498 copy_fork_permissions=False, enable_statistics=False,
497 enable_locking=False, enable_downloads=False,
499 enable_locking=False, enable_downloads=False,
498 copy_group_permissions=False,
500 copy_group_permissions=False,
499 state=Repository.STATE_PENDING):
501 state=Repository.STATE_PENDING):
500 """
502 """
501 Create repository inside database with PENDING state, this should be
503 Create repository inside database with PENDING state, this should be
502 only executed by create() repo. With exception of importing existing
504 only executed by create() repo. With exception of importing existing
503 repos
505 repos
504 """
506 """
505 from rhodecode.model.scm import ScmModel
507 from rhodecode.model.scm import ScmModel
506
508
507 owner = self._get_user(owner)
509 owner = self._get_user(owner)
508 fork_of = self._get_repo(fork_of)
510 fork_of = self._get_repo(fork_of)
509 repo_group = self._get_repo_group(safe_int(repo_group))
511 repo_group = self._get_repo_group(safe_int(repo_group))
510
512
511 try:
513 try:
512 repo_name = safe_unicode(repo_name)
514 repo_name = safe_unicode(repo_name)
513 description = safe_unicode(description)
515 description = safe_unicode(description)
514 # repo name is just a name of repository
516 # repo name is just a name of repository
515 # while repo_name_full is a full qualified name that is combined
517 # while repo_name_full is a full qualified name that is combined
516 # with name and path of group
518 # with name and path of group
517 repo_name_full = repo_name
519 repo_name_full = repo_name
518 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
520 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
519
521
520 new_repo = Repository()
522 new_repo = Repository()
521 new_repo.repo_state = state
523 new_repo.repo_state = state
522 new_repo.enable_statistics = False
524 new_repo.enable_statistics = False
523 new_repo.repo_name = repo_name_full
525 new_repo.repo_name = repo_name_full
524 new_repo.repo_type = repo_type
526 new_repo.repo_type = repo_type
525 new_repo.user = owner
527 new_repo.user = owner
526 new_repo.group = repo_group
528 new_repo.group = repo_group
527 new_repo.description = description or repo_name
529 new_repo.description = description or repo_name
528 new_repo.private = private
530 new_repo.private = private
529 new_repo.archived = False
531 new_repo.archived = False
530 new_repo.clone_uri = clone_uri
532 new_repo.clone_uri = clone_uri
531 new_repo.landing_rev = landing_rev
533 new_repo.landing_rev = landing_rev
532
534
533 new_repo.enable_statistics = enable_statistics
535 new_repo.enable_statistics = enable_statistics
534 new_repo.enable_locking = enable_locking
536 new_repo.enable_locking = enable_locking
535 new_repo.enable_downloads = enable_downloads
537 new_repo.enable_downloads = enable_downloads
536
538
537 if repo_group:
539 if repo_group:
538 new_repo.enable_locking = repo_group.enable_locking
540 new_repo.enable_locking = repo_group.enable_locking
539
541
540 if fork_of:
542 if fork_of:
541 parent_repo = fork_of
543 parent_repo = fork_of
542 new_repo.fork = parent_repo
544 new_repo.fork = parent_repo
543
545
544 events.trigger(events.RepoPreCreateEvent(new_repo))
546 events.trigger(events.RepoPreCreateEvent(new_repo))
545
547
546 self.sa.add(new_repo)
548 self.sa.add(new_repo)
547
549
548 EMPTY_PERM = 'repository.none'
550 EMPTY_PERM = 'repository.none'
549 if fork_of and copy_fork_permissions:
551 if fork_of and copy_fork_permissions:
550 repo = fork_of
552 repo = fork_of
551 user_perms = UserRepoToPerm.query() \
553 user_perms = UserRepoToPerm.query() \
552 .filter(UserRepoToPerm.repository == repo).all()
554 .filter(UserRepoToPerm.repository == repo).all()
553 group_perms = UserGroupRepoToPerm.query() \
555 group_perms = UserGroupRepoToPerm.query() \
554 .filter(UserGroupRepoToPerm.repository == repo).all()
556 .filter(UserGroupRepoToPerm.repository == repo).all()
555
557
556 for perm in user_perms:
558 for perm in user_perms:
557 UserRepoToPerm.create(
559 UserRepoToPerm.create(
558 perm.user, new_repo, perm.permission)
560 perm.user, new_repo, perm.permission)
559
561
560 for perm in group_perms:
562 for perm in group_perms:
561 UserGroupRepoToPerm.create(
563 UserGroupRepoToPerm.create(
562 perm.users_group, new_repo, perm.permission)
564 perm.users_group, new_repo, perm.permission)
563 # in case we copy permissions and also set this repo to private
565 # in case we copy permissions and also set this repo to private
564 # override the default user permission to make it a private repo
566 # override the default user permission to make it a private repo
565 if private:
567 if private:
566 RepoModel(self.sa).grant_user_permission(
568 RepoModel(self.sa).grant_user_permission(
567 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
569 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
568
570
569 elif repo_group and copy_group_permissions:
571 elif repo_group and copy_group_permissions:
570 user_perms = UserRepoGroupToPerm.query() \
572 user_perms = UserRepoGroupToPerm.query() \
571 .filter(UserRepoGroupToPerm.group == repo_group).all()
573 .filter(UserRepoGroupToPerm.group == repo_group).all()
572
574
573 group_perms = UserGroupRepoGroupToPerm.query() \
575 group_perms = UserGroupRepoGroupToPerm.query() \
574 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
576 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
575
577
576 for perm in user_perms:
578 for perm in user_perms:
577 perm_name = perm.permission.permission_name.replace(
579 perm_name = perm.permission.permission_name.replace(
578 'group.', 'repository.')
580 'group.', 'repository.')
579 perm_obj = Permission.get_by_key(perm_name)
581 perm_obj = Permission.get_by_key(perm_name)
580 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
582 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
581
583
582 for perm in group_perms:
584 for perm in group_perms:
583 perm_name = perm.permission.permission_name.replace(
585 perm_name = perm.permission.permission_name.replace(
584 'group.', 'repository.')
586 'group.', 'repository.')
585 perm_obj = Permission.get_by_key(perm_name)
587 perm_obj = Permission.get_by_key(perm_name)
586 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
588 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
587
589
588 if private:
590 if private:
589 RepoModel(self.sa).grant_user_permission(
591 RepoModel(self.sa).grant_user_permission(
590 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
592 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
591
593
592 else:
594 else:
593 perm_obj = self._create_default_perms(new_repo, private)
595 perm_obj = self._create_default_perms(new_repo, private)
594 self.sa.add(perm_obj)
596 self.sa.add(perm_obj)
595
597
596 # now automatically start following this repository as owner
598 # now automatically start following this repository as owner
597 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
599 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, owner.user_id)
598
600
599 # we need to flush here, in order to check if database won't
601 # we need to flush here, in order to check if database won't
600 # throw any exceptions, create filesystem dirs at the very end
602 # throw any exceptions, create filesystem dirs at the very end
601 self.sa.flush()
603 self.sa.flush()
602 events.trigger(events.RepoCreateEvent(new_repo))
604 events.trigger(events.RepoCreateEvent(new_repo))
603 return new_repo
605 return new_repo
604
606
605 except Exception:
607 except Exception:
606 log.error(traceback.format_exc())
608 log.error(traceback.format_exc())
607 raise
609 raise
608
610
609 def create(self, form_data, cur_user):
611 def create(self, form_data, cur_user):
610 """
612 """
611 Create repository using celery tasks
613 Create repository using celery tasks
612
614
613 :param form_data:
615 :param form_data:
614 :param cur_user:
616 :param cur_user:
615 """
617 """
616 from rhodecode.lib.celerylib import tasks, run_task
618 from rhodecode.lib.celerylib import tasks, run_task
617 return run_task(tasks.create_repo, form_data, cur_user)
619 return run_task(tasks.create_repo, form_data, cur_user)
618
620
619 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
621 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
620 perm_deletions=None, check_perms=True,
622 perm_deletions=None, check_perms=True,
621 cur_user=None):
623 cur_user=None):
622 if not perm_additions:
624 if not perm_additions:
623 perm_additions = []
625 perm_additions = []
624 if not perm_updates:
626 if not perm_updates:
625 perm_updates = []
627 perm_updates = []
626 if not perm_deletions:
628 if not perm_deletions:
627 perm_deletions = []
629 perm_deletions = []
628
630
629 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
631 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
630
632
631 changes = {
633 changes = {
632 'added': [],
634 'added': [],
633 'updated': [],
635 'updated': [],
634 'deleted': [],
636 'deleted': [],
635 'default_user_changed': None
637 'default_user_changed': None
636 }
638 }
637
639
638 repo = self._get_repo(repo)
640 repo = self._get_repo(repo)
639
641
640 # update permissions
642 # update permissions
641 for member_id, perm, member_type in perm_updates:
643 for member_id, perm, member_type in perm_updates:
642 member_id = int(member_id)
644 member_id = int(member_id)
643 if member_type == 'user':
645 if member_type == 'user':
644 member_name = User.get(member_id).username
646 member_name = User.get(member_id).username
645 if member_name == User.DEFAULT_USER:
647 if member_name == User.DEFAULT_USER:
646 # NOTE(dan): detect if we changed permissions for default user
648 # NOTE(dan): detect if we changed permissions for default user
647 perm_obj = self.sa.query(UserRepoToPerm) \
649 perm_obj = self.sa.query(UserRepoToPerm) \
648 .filter(UserRepoToPerm.user_id == member_id) \
650 .filter(UserRepoToPerm.user_id == member_id) \
649 .filter(UserRepoToPerm.repository == repo) \
651 .filter(UserRepoToPerm.repository == repo) \
650 .scalar()
652 .scalar()
651 if perm_obj and perm_obj.permission.permission_name != perm:
653 if perm_obj and perm_obj.permission.permission_name != perm:
652 changes['default_user_changed'] = True
654 changes['default_user_changed'] = True
653
655
654 # this updates also current one if found
656 # this updates also current one if found
655 self.grant_user_permission(
657 self.grant_user_permission(
656 repo=repo, user=member_id, perm=perm)
658 repo=repo, user=member_id, perm=perm)
657 elif member_type == 'user_group':
659 elif member_type == 'user_group':
658 # check if we have permissions to alter this usergroup
660 # check if we have permissions to alter this usergroup
659 member_name = UserGroup.get(member_id).users_group_name
661 member_name = UserGroup.get(member_id).users_group_name
660 if not check_perms or HasUserGroupPermissionAny(
662 if not check_perms or HasUserGroupPermissionAny(
661 *req_perms)(member_name, user=cur_user):
663 *req_perms)(member_name, user=cur_user):
662 self.grant_user_group_permission(
664 self.grant_user_group_permission(
663 repo=repo, group_name=member_id, perm=perm)
665 repo=repo, group_name=member_id, perm=perm)
664 else:
666 else:
665 raise ValueError("member_type must be 'user' or 'user_group' "
667 raise ValueError("member_type must be 'user' or 'user_group' "
666 "got {} instead".format(member_type))
668 "got {} instead".format(member_type))
667 changes['updated'].append({'type': member_type, 'id': member_id,
669 changes['updated'].append({'type': member_type, 'id': member_id,
668 'name': member_name, 'new_perm': perm})
670 'name': member_name, 'new_perm': perm})
669
671
670 # set new permissions
672 # set new permissions
671 for member_id, perm, member_type in perm_additions:
673 for member_id, perm, member_type in perm_additions:
672 member_id = int(member_id)
674 member_id = int(member_id)
673 if member_type == 'user':
675 if member_type == 'user':
674 member_name = User.get(member_id).username
676 member_name = User.get(member_id).username
675 self.grant_user_permission(
677 self.grant_user_permission(
676 repo=repo, user=member_id, perm=perm)
678 repo=repo, user=member_id, perm=perm)
677 elif member_type == 'user_group':
679 elif member_type == 'user_group':
678 # check if we have permissions to alter this usergroup
680 # check if we have permissions to alter this usergroup
679 member_name = UserGroup.get(member_id).users_group_name
681 member_name = UserGroup.get(member_id).users_group_name
680 if not check_perms or HasUserGroupPermissionAny(
682 if not check_perms or HasUserGroupPermissionAny(
681 *req_perms)(member_name, user=cur_user):
683 *req_perms)(member_name, user=cur_user):
682 self.grant_user_group_permission(
684 self.grant_user_group_permission(
683 repo=repo, group_name=member_id, perm=perm)
685 repo=repo, group_name=member_id, perm=perm)
684 else:
686 else:
685 raise ValueError("member_type must be 'user' or 'user_group' "
687 raise ValueError("member_type must be 'user' or 'user_group' "
686 "got {} instead".format(member_type))
688 "got {} instead".format(member_type))
687
689
688 changes['added'].append({'type': member_type, 'id': member_id,
690 changes['added'].append({'type': member_type, 'id': member_id,
689 'name': member_name, 'new_perm': perm})
691 'name': member_name, 'new_perm': perm})
690 # delete permissions
692 # delete permissions
691 for member_id, perm, member_type in perm_deletions:
693 for member_id, perm, member_type in perm_deletions:
692 member_id = int(member_id)
694 member_id = int(member_id)
693 if member_type == 'user':
695 if member_type == 'user':
694 member_name = User.get(member_id).username
696 member_name = User.get(member_id).username
695 self.revoke_user_permission(repo=repo, user=member_id)
697 self.revoke_user_permission(repo=repo, user=member_id)
696 elif member_type == 'user_group':
698 elif member_type == 'user_group':
697 # check if we have permissions to alter this usergroup
699 # check if we have permissions to alter this usergroup
698 member_name = UserGroup.get(member_id).users_group_name
700 member_name = UserGroup.get(member_id).users_group_name
699 if not check_perms or HasUserGroupPermissionAny(
701 if not check_perms or HasUserGroupPermissionAny(
700 *req_perms)(member_name, user=cur_user):
702 *req_perms)(member_name, user=cur_user):
701 self.revoke_user_group_permission(
703 self.revoke_user_group_permission(
702 repo=repo, group_name=member_id)
704 repo=repo, group_name=member_id)
703 else:
705 else:
704 raise ValueError("member_type must be 'user' or 'user_group' "
706 raise ValueError("member_type must be 'user' or 'user_group' "
705 "got {} instead".format(member_type))
707 "got {} instead".format(member_type))
706
708
707 changes['deleted'].append({'type': member_type, 'id': member_id,
709 changes['deleted'].append({'type': member_type, 'id': member_id,
708 'name': member_name, 'new_perm': perm})
710 'name': member_name, 'new_perm': perm})
709 return changes
711 return changes
710
712
711 def create_fork(self, form_data, cur_user):
713 def create_fork(self, form_data, cur_user):
712 """
714 """
713 Simple wrapper into executing celery task for fork creation
715 Simple wrapper into executing celery task for fork creation
714
716
715 :param form_data:
717 :param form_data:
716 :param cur_user:
718 :param cur_user:
717 """
719 """
718 from rhodecode.lib.celerylib import tasks, run_task
720 from rhodecode.lib.celerylib import tasks, run_task
719 return run_task(tasks.create_repo_fork, form_data, cur_user)
721 return run_task(tasks.create_repo_fork, form_data, cur_user)
720
722
721 def archive(self, repo):
723 def archive(self, repo):
722 """
724 """
723 Archive given repository. Set archive flag.
725 Archive given repository. Set archive flag.
724
726
725 :param repo:
727 :param repo:
726 """
728 """
727 repo = self._get_repo(repo)
729 repo = self._get_repo(repo)
728 if repo:
730 if repo:
729
731
730 try:
732 try:
731 repo.archived = True
733 repo.archived = True
732 self.sa.add(repo)
734 self.sa.add(repo)
733 self.sa.commit()
735 self.sa.commit()
734 except Exception:
736 except Exception:
735 log.error(traceback.format_exc())
737 log.error(traceback.format_exc())
736 raise
738 raise
737
739
738 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
740 def delete(self, repo, forks=None, pull_requests=None, fs_remove=True, cur_user=None):
739 """
741 """
740 Delete given repository, forks parameter defines what do do with
742 Delete given repository, forks parameter defines what do do with
741 attached forks. Throws AttachedForksError if deleted repo has attached
743 attached forks. Throws AttachedForksError if deleted repo has attached
742 forks
744 forks
743
745
744 :param repo:
746 :param repo:
745 :param forks: str 'delete' or 'detach'
747 :param forks: str 'delete' or 'detach'
746 :param pull_requests: str 'delete' or None
748 :param pull_requests: str 'delete' or None
747 :param fs_remove: remove(archive) repo from filesystem
749 :param fs_remove: remove(archive) repo from filesystem
748 """
750 """
749 if not cur_user:
751 if not cur_user:
750 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
752 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
751 repo = self._get_repo(repo)
753 repo = self._get_repo(repo)
752 if repo:
754 if repo:
753 if forks == 'detach':
755 if forks == 'detach':
754 for r in repo.forks:
756 for r in repo.forks:
755 r.fork = None
757 r.fork = None
756 self.sa.add(r)
758 self.sa.add(r)
757 elif forks == 'delete':
759 elif forks == 'delete':
758 for r in repo.forks:
760 for r in repo.forks:
759 self.delete(r, forks='delete')
761 self.delete(r, forks='delete')
760 elif [f for f in repo.forks]:
762 elif [f for f in repo.forks]:
761 raise AttachedForksError()
763 raise AttachedForksError()
762
764
763 # check for pull requests
765 # check for pull requests
764 pr_sources = repo.pull_requests_source
766 pr_sources = repo.pull_requests_source
765 pr_targets = repo.pull_requests_target
767 pr_targets = repo.pull_requests_target
766 if pull_requests != 'delete' and (pr_sources or pr_targets):
768 if pull_requests != 'delete' and (pr_sources or pr_targets):
767 raise AttachedPullRequestsError()
769 raise AttachedPullRequestsError()
768
770
769 old_repo_dict = repo.get_dict()
771 old_repo_dict = repo.get_dict()
770 events.trigger(events.RepoPreDeleteEvent(repo))
772 events.trigger(events.RepoPreDeleteEvent(repo))
771 try:
773 try:
772 self.sa.delete(repo)
774 self.sa.delete(repo)
773 if fs_remove:
775 if fs_remove:
774 self._delete_filesystem_repo(repo)
776 self._delete_filesystem_repo(repo)
775 else:
777 else:
776 log.debug('skipping removal from filesystem')
778 log.debug('skipping removal from filesystem')
777 old_repo_dict.update({
779 old_repo_dict.update({
778 'deleted_by': cur_user,
780 'deleted_by': cur_user,
779 'deleted_on': time.time(),
781 'deleted_on': time.time(),
780 })
782 })
781 hooks_base.delete_repository(**old_repo_dict)
783 hooks_base.delete_repository(**old_repo_dict)
782 events.trigger(events.RepoDeleteEvent(repo))
784 events.trigger(events.RepoDeleteEvent(repo))
783 except Exception:
785 except Exception:
784 log.error(traceback.format_exc())
786 log.error(traceback.format_exc())
785 raise
787 raise
786
788
787 def grant_user_permission(self, repo, user, perm):
789 def grant_user_permission(self, repo, user, perm):
788 """
790 """
789 Grant permission for user on given repository, or update existing one
791 Grant permission for user on given repository, or update existing one
790 if found
792 if found
791
793
792 :param repo: Instance of Repository, repository_id, or repository name
794 :param repo: Instance of Repository, repository_id, or repository name
793 :param user: Instance of User, user_id or username
795 :param user: Instance of User, user_id or username
794 :param perm: Instance of Permission, or permission_name
796 :param perm: Instance of Permission, or permission_name
795 """
797 """
796 user = self._get_user(user)
798 user = self._get_user(user)
797 repo = self._get_repo(repo)
799 repo = self._get_repo(repo)
798 permission = self._get_perm(perm)
800 permission = self._get_perm(perm)
799
801
800 # check if we have that permission already
802 # check if we have that permission already
801 obj = self.sa.query(UserRepoToPerm) \
803 obj = self.sa.query(UserRepoToPerm) \
802 .filter(UserRepoToPerm.user == user) \
804 .filter(UserRepoToPerm.user == user) \
803 .filter(UserRepoToPerm.repository == repo) \
805 .filter(UserRepoToPerm.repository == repo) \
804 .scalar()
806 .scalar()
805 if obj is None:
807 if obj is None:
806 # create new !
808 # create new !
807 obj = UserRepoToPerm()
809 obj = UserRepoToPerm()
808 obj.repository = repo
810 obj.repository = repo
809 obj.user = user
811 obj.user = user
810 obj.permission = permission
812 obj.permission = permission
811 self.sa.add(obj)
813 self.sa.add(obj)
812 log.debug('Granted perm %s to %s on %s', perm, user, repo)
814 log.debug('Granted perm %s to %s on %s', perm, user, repo)
813 action_logger_generic(
815 action_logger_generic(
814 'granted permission: {} to user: {} on repo: {}'.format(
816 'granted permission: {} to user: {} on repo: {}'.format(
815 perm, user, repo), namespace='security.repo')
817 perm, user, repo), namespace='security.repo')
816 return obj
818 return obj
817
819
818 def revoke_user_permission(self, repo, user):
820 def revoke_user_permission(self, repo, user):
819 """
821 """
820 Revoke permission for user on given repository
822 Revoke permission for user on given repository
821
823
822 :param repo: Instance of Repository, repository_id, or repository name
824 :param repo: Instance of Repository, repository_id, or repository name
823 :param user: Instance of User, user_id or username
825 :param user: Instance of User, user_id or username
824 """
826 """
825
827
826 user = self._get_user(user)
828 user = self._get_user(user)
827 repo = self._get_repo(repo)
829 repo = self._get_repo(repo)
828
830
829 obj = self.sa.query(UserRepoToPerm) \
831 obj = self.sa.query(UserRepoToPerm) \
830 .filter(UserRepoToPerm.repository == repo) \
832 .filter(UserRepoToPerm.repository == repo) \
831 .filter(UserRepoToPerm.user == user) \
833 .filter(UserRepoToPerm.user == user) \
832 .scalar()
834 .scalar()
833 if obj:
835 if obj:
834 self.sa.delete(obj)
836 self.sa.delete(obj)
835 log.debug('Revoked perm on %s on %s', repo, user)
837 log.debug('Revoked perm on %s on %s', repo, user)
836 action_logger_generic(
838 action_logger_generic(
837 'revoked permission from user: {} on repo: {}'.format(
839 'revoked permission from user: {} on repo: {}'.format(
838 user, repo), namespace='security.repo')
840 user, repo), namespace='security.repo')
839
841
840 def grant_user_group_permission(self, repo, group_name, perm):
842 def grant_user_group_permission(self, repo, group_name, perm):
841 """
843 """
842 Grant permission for user group on given repository, or update
844 Grant permission for user group on given repository, or update
843 existing one if found
845 existing one if found
844
846
845 :param repo: Instance of Repository, repository_id, or repository name
847 :param repo: Instance of Repository, repository_id, or repository name
846 :param group_name: Instance of UserGroup, users_group_id,
848 :param group_name: Instance of UserGroup, users_group_id,
847 or user group name
849 or user group name
848 :param perm: Instance of Permission, or permission_name
850 :param perm: Instance of Permission, or permission_name
849 """
851 """
850 repo = self._get_repo(repo)
852 repo = self._get_repo(repo)
851 group_name = self._get_user_group(group_name)
853 group_name = self._get_user_group(group_name)
852 permission = self._get_perm(perm)
854 permission = self._get_perm(perm)
853
855
854 # check if we have that permission already
856 # check if we have that permission already
855 obj = self.sa.query(UserGroupRepoToPerm) \
857 obj = self.sa.query(UserGroupRepoToPerm) \
856 .filter(UserGroupRepoToPerm.users_group == group_name) \
858 .filter(UserGroupRepoToPerm.users_group == group_name) \
857 .filter(UserGroupRepoToPerm.repository == repo) \
859 .filter(UserGroupRepoToPerm.repository == repo) \
858 .scalar()
860 .scalar()
859
861
860 if obj is None:
862 if obj is None:
861 # create new
863 # create new
862 obj = UserGroupRepoToPerm()
864 obj = UserGroupRepoToPerm()
863
865
864 obj.repository = repo
866 obj.repository = repo
865 obj.users_group = group_name
867 obj.users_group = group_name
866 obj.permission = permission
868 obj.permission = permission
867 self.sa.add(obj)
869 self.sa.add(obj)
868 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
870 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
869 action_logger_generic(
871 action_logger_generic(
870 'granted permission: {} to usergroup: {} on repo: {}'.format(
872 'granted permission: {} to usergroup: {} on repo: {}'.format(
871 perm, group_name, repo), namespace='security.repo')
873 perm, group_name, repo), namespace='security.repo')
872
874
873 return obj
875 return obj
874
876
875 def revoke_user_group_permission(self, repo, group_name):
877 def revoke_user_group_permission(self, repo, group_name):
876 """
878 """
877 Revoke permission for user group on given repository
879 Revoke permission for user group on given repository
878
880
879 :param repo: Instance of Repository, repository_id, or repository name
881 :param repo: Instance of Repository, repository_id, or repository name
880 :param group_name: Instance of UserGroup, users_group_id,
882 :param group_name: Instance of UserGroup, users_group_id,
881 or user group name
883 or user group name
882 """
884 """
883 repo = self._get_repo(repo)
885 repo = self._get_repo(repo)
884 group_name = self._get_user_group(group_name)
886 group_name = self._get_user_group(group_name)
885
887
886 obj = self.sa.query(UserGroupRepoToPerm) \
888 obj = self.sa.query(UserGroupRepoToPerm) \
887 .filter(UserGroupRepoToPerm.repository == repo) \
889 .filter(UserGroupRepoToPerm.repository == repo) \
888 .filter(UserGroupRepoToPerm.users_group == group_name) \
890 .filter(UserGroupRepoToPerm.users_group == group_name) \
889 .scalar()
891 .scalar()
890 if obj:
892 if obj:
891 self.sa.delete(obj)
893 self.sa.delete(obj)
892 log.debug('Revoked perm to %s on %s', repo, group_name)
894 log.debug('Revoked perm to %s on %s', repo, group_name)
893 action_logger_generic(
895 action_logger_generic(
894 'revoked permission from usergroup: {} on repo: {}'.format(
896 'revoked permission from usergroup: {} on repo: {}'.format(
895 group_name, repo), namespace='security.repo')
897 group_name, repo), namespace='security.repo')
896
898
897 def delete_stats(self, repo_name):
899 def delete_stats(self, repo_name):
898 """
900 """
899 removes stats for given repo
901 removes stats for given repo
900
902
901 :param repo_name:
903 :param repo_name:
902 """
904 """
903 repo = self._get_repo(repo_name)
905 repo = self._get_repo(repo_name)
904 try:
906 try:
905 obj = self.sa.query(Statistics) \
907 obj = self.sa.query(Statistics) \
906 .filter(Statistics.repository == repo).scalar()
908 .filter(Statistics.repository == repo).scalar()
907 if obj:
909 if obj:
908 self.sa.delete(obj)
910 self.sa.delete(obj)
909 except Exception:
911 except Exception:
910 log.error(traceback.format_exc())
912 log.error(traceback.format_exc())
911 raise
913 raise
912
914
913 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
915 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
914 field_type='str', field_desc=''):
916 field_type='str', field_desc=''):
915
917
916 repo = self._get_repo(repo_name)
918 repo = self._get_repo(repo_name)
917
919
918 new_field = RepositoryField()
920 new_field = RepositoryField()
919 new_field.repository = repo
921 new_field.repository = repo
920 new_field.field_key = field_key
922 new_field.field_key = field_key
921 new_field.field_type = field_type # python type
923 new_field.field_type = field_type # python type
922 new_field.field_value = field_value
924 new_field.field_value = field_value
923 new_field.field_desc = field_desc
925 new_field.field_desc = field_desc
924 new_field.field_label = field_label
926 new_field.field_label = field_label
925 self.sa.add(new_field)
927 self.sa.add(new_field)
926 return new_field
928 return new_field
927
929
928 def delete_repo_field(self, repo_name, field_key):
930 def delete_repo_field(self, repo_name, field_key):
929 repo = self._get_repo(repo_name)
931 repo = self._get_repo(repo_name)
930 field = RepositoryField.get_by_key_name(field_key, repo)
932 field = RepositoryField.get_by_key_name(field_key, repo)
931 if field:
933 if field:
932 self.sa.delete(field)
934 self.sa.delete(field)
933
935
934 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
936 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
935 clone_uri=None, repo_store_location=None,
937 clone_uri=None, repo_store_location=None,
936 use_global_config=False, install_hooks=True):
938 use_global_config=False, install_hooks=True):
937 """
939 """
938 makes repository on filesystem. It's group aware means it'll create
940 makes repository on filesystem. It's group aware means it'll create
939 a repository within a group, and alter the paths accordingly of
941 a repository within a group, and alter the paths accordingly of
940 group location
942 group location
941
943
942 :param repo_name:
944 :param repo_name:
943 :param alias:
945 :param alias:
944 :param parent:
946 :param parent:
945 :param clone_uri:
947 :param clone_uri:
946 :param repo_store_location:
948 :param repo_store_location:
947 """
949 """
948 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
950 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
949 from rhodecode.model.scm import ScmModel
951 from rhodecode.model.scm import ScmModel
950
952
951 if Repository.NAME_SEP in repo_name:
953 if Repository.NAME_SEP in repo_name:
952 raise ValueError(
954 raise ValueError(
953 'repo_name must not contain groups got `%s`' % repo_name)
955 'repo_name must not contain groups got `%s`' % repo_name)
954
956
955 if isinstance(repo_group, RepoGroup):
957 if isinstance(repo_group, RepoGroup):
956 new_parent_path = os.sep.join(repo_group.full_path_splitted)
958 new_parent_path = os.sep.join(repo_group.full_path_splitted)
957 else:
959 else:
958 new_parent_path = repo_group or ''
960 new_parent_path = repo_group or ''
959
961
960 if repo_store_location:
962 if repo_store_location:
961 _paths = [repo_store_location]
963 _paths = [repo_store_location]
962 else:
964 else:
963 _paths = [self.repos_path, new_parent_path, repo_name]
965 _paths = [self.repos_path, new_parent_path, repo_name]
964 # we need to make it str for mercurial
966 # we need to make it str for mercurial
965 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
967 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
966
968
967 # check if this path is not a repository
969 # check if this path is not a repository
968 if is_valid_repo(repo_path, self.repos_path):
970 if is_valid_repo(repo_path, self.repos_path):
969 raise Exception('This path %s is a valid repository' % repo_path)
971 raise Exception('This path %s is a valid repository' % repo_path)
970
972
971 # check if this path is a group
973 # check if this path is a group
972 if is_valid_repo_group(repo_path, self.repos_path):
974 if is_valid_repo_group(repo_path, self.repos_path):
973 raise Exception('This path %s is a valid group' % repo_path)
975 raise Exception('This path %s is a valid group' % repo_path)
974
976
975 log.info('creating repo %s in %s from url: `%s`',
977 log.info('creating repo %s in %s from url: `%s`',
976 repo_name, safe_unicode(repo_path),
978 repo_name, safe_unicode(repo_path),
977 obfuscate_url_pw(clone_uri))
979 obfuscate_url_pw(clone_uri))
978
980
979 backend = get_backend(repo_type)
981 backend = get_backend(repo_type)
980
982
981 config_repo = None if use_global_config else repo_name
983 config_repo = None if use_global_config else repo_name
982 if config_repo and new_parent_path:
984 if config_repo and new_parent_path:
983 config_repo = Repository.NAME_SEP.join(
985 config_repo = Repository.NAME_SEP.join(
984 (new_parent_path, config_repo))
986 (new_parent_path, config_repo))
985 config = make_db_config(clear_session=False, repo=config_repo)
987 config = make_db_config(clear_session=False, repo=config_repo)
986 config.set('extensions', 'largefiles', '')
988 config.set('extensions', 'largefiles', '')
987
989
988 # patch and reset hooks section of UI config to not run any
990 # patch and reset hooks section of UI config to not run any
989 # hooks on creating remote repo
991 # hooks on creating remote repo
990 config.clear_section('hooks')
992 config.clear_section('hooks')
991
993
992 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
994 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
993 if repo_type == 'git':
995 if repo_type == 'git':
994 repo = backend(
996 repo = backend(
995 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
997 repo_path, config=config, create=True, src_url=clone_uri, bare=True,
996 with_wire={"cache": False})
998 with_wire={"cache": False})
997 else:
999 else:
998 repo = backend(
1000 repo = backend(
999 repo_path, config=config, create=True, src_url=clone_uri,
1001 repo_path, config=config, create=True, src_url=clone_uri,
1000 with_wire={"cache": False})
1002 with_wire={"cache": False})
1001
1003
1002 if install_hooks:
1004 if install_hooks:
1003 repo.install_hooks()
1005 repo.install_hooks()
1004
1006
1005 log.debug('Created repo %s with %s backend',
1007 log.debug('Created repo %s with %s backend',
1006 safe_unicode(repo_name), safe_unicode(repo_type))
1008 safe_unicode(repo_name), safe_unicode(repo_type))
1007 return repo
1009 return repo
1008
1010
1009 def _rename_filesystem_repo(self, old, new):
1011 def _rename_filesystem_repo(self, old, new):
1010 """
1012 """
1011 renames repository on filesystem
1013 renames repository on filesystem
1012
1014
1013 :param old: old name
1015 :param old: old name
1014 :param new: new name
1016 :param new: new name
1015 """
1017 """
1016 log.info('renaming repo from %s to %s', old, new)
1018 log.info('renaming repo from %s to %s', old, new)
1017
1019
1018 old_path = os.path.join(self.repos_path, old)
1020 old_path = os.path.join(self.repos_path, old)
1019 new_path = os.path.join(self.repos_path, new)
1021 new_path = os.path.join(self.repos_path, new)
1020 if os.path.isdir(new_path):
1022 if os.path.isdir(new_path):
1021 raise Exception(
1023 raise Exception(
1022 'Was trying to rename to already existing dir %s' % new_path
1024 'Was trying to rename to already existing dir %s' % new_path
1023 )
1025 )
1024 shutil.move(old_path, new_path)
1026 shutil.move(old_path, new_path)
1025
1027
1026 def _delete_filesystem_repo(self, repo):
1028 def _delete_filesystem_repo(self, repo):
1027 """
1029 """
1028 removes repo from filesystem, the removal is acctually made by
1030 removes repo from filesystem, the removal is acctually made by
1029 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
1031 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
1030 repository is no longer valid for rhodecode, can be undeleted later on
1032 repository is no longer valid for rhodecode, can be undeleted later on
1031 by reverting the renames on this repository
1033 by reverting the renames on this repository
1032
1034
1033 :param repo: repo object
1035 :param repo: repo object
1034 """
1036 """
1035 rm_path = os.path.join(self.repos_path, repo.repo_name)
1037 rm_path = os.path.join(self.repos_path, repo.repo_name)
1036 repo_group = repo.group
1038 repo_group = repo.group
1037 log.info("Removing repository %s", rm_path)
1039 log.info("Removing repository %s", rm_path)
1038 # disable hg/git internal that it doesn't get detected as repo
1040 # disable hg/git internal that it doesn't get detected as repo
1039 alias = repo.repo_type
1041 alias = repo.repo_type
1040
1042
1041 config = make_db_config(clear_session=False)
1043 config = make_db_config(clear_session=False)
1042 config.set('extensions', 'largefiles', '')
1044 config.set('extensions', 'largefiles', '')
1043 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1045 bare = getattr(repo.scm_instance(config=config), 'bare', False)
1044
1046
1045 # skip this for bare git repos
1047 # skip this for bare git repos
1046 if not bare:
1048 if not bare:
1047 # disable VCS repo
1049 # disable VCS repo
1048 vcs_path = os.path.join(rm_path, '.%s' % alias)
1050 vcs_path = os.path.join(rm_path, '.%s' % alias)
1049 if os.path.exists(vcs_path):
1051 if os.path.exists(vcs_path):
1050 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1052 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
1051
1053
1052 _now = datetime.datetime.now()
1054 _now = datetime.datetime.now()
1053 _ms = str(_now.microsecond).rjust(6, '0')
1055 _ms = str(_now.microsecond).rjust(6, '0')
1054 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1056 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
1055 repo.just_name)
1057 repo.just_name)
1056 if repo_group:
1058 if repo_group:
1057 # if repository is in group, prefix the removal path with the group
1059 # if repository is in group, prefix the removal path with the group
1058 args = repo_group.full_path_splitted + [_d]
1060 args = repo_group.full_path_splitted + [_d]
1059 _d = os.path.join(*args)
1061 _d = os.path.join(*args)
1060
1062
1061 if os.path.isdir(rm_path):
1063 if os.path.isdir(rm_path):
1062 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1064 shutil.move(rm_path, os.path.join(self.repos_path, _d))
1063
1065
1064 # finally cleanup diff-cache if it exists
1066 # finally cleanup diff-cache if it exists
1065 cached_diffs_dir = repo.cached_diffs_dir
1067 cached_diffs_dir = repo.cached_diffs_dir
1066 if os.path.isdir(cached_diffs_dir):
1068 if os.path.isdir(cached_diffs_dir):
1067 shutil.rmtree(cached_diffs_dir)
1069 shutil.rmtree(cached_diffs_dir)
1068
1070
1069
1071
1070 class ReadmeFinder:
1072 class ReadmeFinder:
1071 """
1073 """
1072 Utility which knows how to find a readme for a specific commit.
1074 Utility which knows how to find a readme for a specific commit.
1073
1075
1074 The main idea is that this is a configurable algorithm. When creating an
1076 The main idea is that this is a configurable algorithm. When creating an
1075 instance you can define parameters, currently only the `default_renderer`.
1077 instance you can define parameters, currently only the `default_renderer`.
1076 Based on this configuration the method :meth:`search` behaves slightly
1078 Based on this configuration the method :meth:`search` behaves slightly
1077 different.
1079 different.
1078 """
1080 """
1079
1081
1080 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1082 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
1081 path_re = re.compile(r'^docs?', re.IGNORECASE)
1083 path_re = re.compile(r'^docs?', re.IGNORECASE)
1082
1084
1083 default_priorities = {
1085 default_priorities = {
1084 None: 0,
1086 None: 0,
1085 '.text': 2,
1087 '.text': 2,
1086 '.txt': 3,
1088 '.txt': 3,
1087 '.rst': 1,
1089 '.rst': 1,
1088 '.rest': 2,
1090 '.rest': 2,
1089 '.md': 1,
1091 '.md': 1,
1090 '.mkdn': 2,
1092 '.mkdn': 2,
1091 '.mdown': 3,
1093 '.mdown': 3,
1092 '.markdown': 4,
1094 '.markdown': 4,
1093 }
1095 }
1094
1096
1095 path_priority = {
1097 path_priority = {
1096 'doc': 0,
1098 'doc': 0,
1097 'docs': 1,
1099 'docs': 1,
1098 }
1100 }
1099
1101
1100 FALLBACK_PRIORITY = 99
1102 FALLBACK_PRIORITY = 99
1101
1103
1102 RENDERER_TO_EXTENSION = {
1104 RENDERER_TO_EXTENSION = {
1103 'rst': ['.rst', '.rest'],
1105 'rst': ['.rst', '.rest'],
1104 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1106 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
1105 }
1107 }
1106
1108
1107 def __init__(self, default_renderer=None):
1109 def __init__(self, default_renderer=None):
1108 self._default_renderer = default_renderer
1110 self._default_renderer = default_renderer
1109 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1111 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
1110 default_renderer, [])
1112 default_renderer, [])
1111
1113
1112 def search(self, commit, path=u'/'):
1114 def search(self, commit, path=u'/'):
1113 """
1115 """
1114 Find a readme in the given `commit`.
1116 Find a readme in the given `commit`.
1115 """
1117 """
1116 nodes = commit.get_nodes(path)
1118 nodes = commit.get_nodes(path)
1117 matches = self._match_readmes(nodes)
1119 matches = self._match_readmes(nodes)
1118 matches = self._sort_according_to_priority(matches)
1120 matches = self._sort_according_to_priority(matches)
1119 if matches:
1121 if matches:
1120 return matches[0].node
1122 return matches[0].node
1121
1123
1122 paths = self._match_paths(nodes)
1124 paths = self._match_paths(nodes)
1123 paths = self._sort_paths_according_to_priority(paths)
1125 paths = self._sort_paths_according_to_priority(paths)
1124 for path in paths:
1126 for path in paths:
1125 match = self.search(commit, path=path)
1127 match = self.search(commit, path=path)
1126 if match:
1128 if match:
1127 return match
1129 return match
1128
1130
1129 return None
1131 return None
1130
1132
1131 def _match_readmes(self, nodes):
1133 def _match_readmes(self, nodes):
1132 for node in nodes:
1134 for node in nodes:
1133 if not node.is_file():
1135 if not node.is_file():
1134 continue
1136 continue
1135 path = node.path.rsplit('/', 1)[-1]
1137 path = node.path.rsplit('/', 1)[-1]
1136 match = self.readme_re.match(path)
1138 match = self.readme_re.match(path)
1137 if match:
1139 if match:
1138 extension = match.group(1)
1140 extension = match.group(1)
1139 yield ReadmeMatch(node, match, self._priority(extension))
1141 yield ReadmeMatch(node, match, self._priority(extension))
1140
1142
1141 def _match_paths(self, nodes):
1143 def _match_paths(self, nodes):
1142 for node in nodes:
1144 for node in nodes:
1143 if not node.is_dir():
1145 if not node.is_dir():
1144 continue
1146 continue
1145 match = self.path_re.match(node.path)
1147 match = self.path_re.match(node.path)
1146 if match:
1148 if match:
1147 yield node.path
1149 yield node.path
1148
1150
1149 def _priority(self, extension):
1151 def _priority(self, extension):
1150 renderer_priority = (
1152 renderer_priority = (
1151 0 if extension in self._renderer_extensions else 1)
1153 0 if extension in self._renderer_extensions else 1)
1152 extension_priority = self.default_priorities.get(
1154 extension_priority = self.default_priorities.get(
1153 extension, self.FALLBACK_PRIORITY)
1155 extension, self.FALLBACK_PRIORITY)
1154 return (renderer_priority, extension_priority)
1156 return (renderer_priority, extension_priority)
1155
1157
1156 def _sort_according_to_priority(self, matches):
1158 def _sort_according_to_priority(self, matches):
1157
1159
1158 def priority_and_path(match):
1160 def priority_and_path(match):
1159 return (match.priority, match.path)
1161 return (match.priority, match.path)
1160
1162
1161 return sorted(matches, key=priority_and_path)
1163 return sorted(matches, key=priority_and_path)
1162
1164
1163 def _sort_paths_according_to_priority(self, paths):
1165 def _sort_paths_according_to_priority(self, paths):
1164
1166
1165 def priority_and_path(path):
1167 def priority_and_path(path):
1166 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1168 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1167
1169
1168 return sorted(paths, key=priority_and_path)
1170 return sorted(paths, key=priority_and_path)
1169
1171
1170
1172
1171 class ReadmeMatch:
1173 class ReadmeMatch:
1172
1174
1173 def __init__(self, node, match, priority):
1175 def __init__(self, node, match, priority):
1174 self.node = node
1176 self.node = node
1175 self._match = match
1177 self._match = match
1176 self.priority = priority
1178 self.priority = priority
1177
1179
1178 @property
1180 @property
1179 def path(self):
1181 def path(self):
1180 return self.node.path
1182 return self.node.path
1181
1183
1182 def __repr__(self):
1184 def __repr__(self):
1183 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1185 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
General Comments 0
You need to be logged in to leave comments. Login now