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