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