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