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