##// END OF EJS Templates
- fixed issue with missing commits on some repos commands...
marcink -
r1807:1635a214 beta
parent child Browse files
Show More
@@ -1,424 +1,430
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Admin controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from paste.httpexceptions import HTTPInternalServerError
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 39 HasPermissionAnyDecorator
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 42 from rhodecode.lib.helpers import get_token
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
45 45 from rhodecode.model.forms import RepoForm
46 46 from rhodecode.model.scm import ScmModel
47 47 from rhodecode.model.repo import RepoModel
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class ReposController(BaseController):
53 53 """
54 54 REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('repo', 'repos')
58 58
59 59 @LoginRequired()
60 60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(ReposController, self).__before__()
65 65
66 66 def __load_defaults(self):
67 67 c.repo_groups = RepoGroup.groups_choices()
68 68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69 69
70 70 repo_model = RepoModel()
71 71 c.users_array = repo_model.get_users_js()
72 72 c.users_groups_array = repo_model.get_users_groups_js()
73 73
74 74 def __load_data(self, repo_name=None):
75 75 """
76 76 Load defaults settings for edit, and update
77 77
78 78 :param repo_name:
79 79 """
80 80 self.__load_defaults()
81 81
82 82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
83 83 repo = db_repo.scm_instance
84 84
85 85 if c.repo_info is None:
86 86 h.flash(_('%s repository is not mapped to db perhaps'
87 87 ' it was created or renamed from the filesystem'
88 88 ' please run the application again'
89 89 ' in order to rescan repositories') % repo_name,
90 90 category='error')
91 91
92 92 return redirect(url('repos'))
93 93
94 94 c.default_user_id = User.get_by_username('default').user_id
95 95 c.in_public_journal = UserFollowing.query()\
96 96 .filter(UserFollowing.user_id == c.default_user_id)\
97 97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
98 98
99 99 if c.repo_info.stats:
100 last_rev = c.repo_info.stats.stat_on_revision
100 # this is on what revision we ended up so we add +1 for count
101 last_rev = c.repo_info.stats.stat_on_revision + 1
101 102 else:
102 103 last_rev = 0
103 104 c.stats_revision = last_rev
104 105
105 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
106 c.repo_last_rev = repo.count() if repo.revisions else 0
106 107
107 108 if last_rev == 0 or c.repo_last_rev == 0:
108 109 c.stats_percentage = 0
109 110 else:
110 111 c.stats_percentage = '%.2f' % ((float((last_rev)) /
111 112 c.repo_last_rev) * 100)
112 113
113 114 defaults = RepoModel()._get_defaults(repo_name)
114 115
115 116 c.repos_list = [('', _('--REMOVE FORK--'))]
116 117 c.repos_list += [(x.repo_id, x.repo_name) for x in
117 118 Repository.query().order_by(Repository.repo_name).all()]
118 119 return defaults
119 120
120 121 @HasPermissionAllDecorator('hg.admin')
121 122 def index(self, format='html'):
122 123 """GET /repos: All items in the collection"""
123 124 # url('repos')
124 125
125 126 c.repos_list = ScmModel().get_repos(Repository.query()
126 127 .order_by(Repository.repo_name)
127 128 .all(), sort_key='name_sort')
128 129 return render('admin/repos/repos.html')
129 130
130 131 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
131 132 def create(self):
132 133 """
133 134 POST /repos: Create a new item"""
134 135 # url('repos')
135 136
136 137 self.__load_defaults()
137 138 form_result = {}
138 139 try:
139 140 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
140 141 .to_python(dict(request.POST))
141 142 RepoModel().create(form_result, self.rhodecode_user)
142 143 if form_result['clone_uri']:
143 144 h.flash(_('created repository %s from %s') \
144 145 % (form_result['repo_name'], form_result['clone_uri']),
145 146 category='success')
146 147 else:
147 148 h.flash(_('created repository %s') % form_result['repo_name'],
148 149 category='success')
149 150
150 151 if request.POST.get('user_created'):
151 152 # created by regular non admin user
152 153 action_logger(self.rhodecode_user, 'user_created_repo',
153 154 form_result['repo_name_full'], '', self.sa)
154 155 else:
155 156 action_logger(self.rhodecode_user, 'admin_created_repo',
156 157 form_result['repo_name_full'], '', self.sa)
157 158 Session.commit()
158 159 except formencode.Invalid, errors:
159 160
160 161 c.new_repo = errors.value['repo_name']
161 162
162 163 if request.POST.get('user_created'):
163 164 r = render('admin/repos/repo_add_create_repository.html')
164 165 else:
165 166 r = render('admin/repos/repo_add.html')
166 167
167 168 return htmlfill.render(
168 169 r,
169 170 defaults=errors.value,
170 171 errors=errors.error_dict or {},
171 172 prefix_error=False,
172 173 encoding="UTF-8")
173 174
174 175 except Exception:
175 176 log.error(traceback.format_exc())
176 177 msg = _('error occurred during creation of repository %s') \
177 178 % form_result.get('repo_name')
178 179 h.flash(msg, category='error')
179 180 if request.POST.get('user_created'):
180 181 return redirect(url('home'))
181 182 return redirect(url('repos'))
182 183
183 184 @HasPermissionAllDecorator('hg.admin')
184 185 def new(self, format='html'):
185 186 """GET /repos/new: Form to create a new item"""
186 187 new_repo = request.GET.get('repo', '')
187 188 c.new_repo = repo_name_slug(new_repo)
188 189 self.__load_defaults()
189 190 return render('admin/repos/repo_add.html')
190 191
191 192 @HasPermissionAllDecorator('hg.admin')
192 193 def update(self, repo_name):
193 194 """
194 195 PUT /repos/repo_name: Update an existing item"""
195 196 # Forms posted to this method should contain a hidden field:
196 197 # <input type="hidden" name="_method" value="PUT" />
197 198 # Or using helpers:
198 199 # h.form(url('repo', repo_name=ID),
199 200 # method='put')
200 201 # url('repo', repo_name=ID)
201 202 self.__load_defaults()
202 203 repo_model = RepoModel()
203 204 changed_name = repo_name
204 205 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
205 206 repo_groups=c.repo_groups_choices)()
206 207 try:
207 208 form_result = _form.to_python(dict(request.POST))
208 209 repo = repo_model.update(repo_name, form_result)
209 210 invalidate_cache('get_repo_cached_%s' % repo_name)
210 211 h.flash(_('Repository %s updated successfully' % repo_name),
211 212 category='success')
212 213 changed_name = repo.repo_name
213 214 action_logger(self.rhodecode_user, 'admin_updated_repo',
214 215 changed_name, '', self.sa)
215 216 Session.commit()
216 217 except formencode.Invalid, errors:
217 218 defaults = self.__load_data(repo_name)
218 219 defaults.update(errors.value)
219 220 return htmlfill.render(
220 221 render('admin/repos/repo_edit.html'),
221 222 defaults=defaults,
222 223 errors=errors.error_dict or {},
223 224 prefix_error=False,
224 225 encoding="UTF-8")
225 226
226 227 except Exception:
227 228 log.error(traceback.format_exc())
228 229 h.flash(_('error occurred during update of repository %s') \
229 230 % repo_name, category='error')
230 231 return redirect(url('edit_repo', repo_name=changed_name))
231 232
232 233 @HasPermissionAllDecorator('hg.admin')
233 234 def delete(self, repo_name):
234 235 """
235 236 DELETE /repos/repo_name: Delete an existing item"""
236 237 # Forms posted to this method should contain a hidden field:
237 238 # <input type="hidden" name="_method" value="DELETE" />
238 239 # Or using helpers:
239 240 # h.form(url('repo', repo_name=ID),
240 241 # method='delete')
241 242 # url('repo', repo_name=ID)
242 243
243 244 repo_model = RepoModel()
244 245 repo = repo_model.get_by_repo_name(repo_name)
245 246 if not repo:
246 247 h.flash(_('%s repository is not mapped to db perhaps'
247 248 ' it was moved or renamed from the filesystem'
248 249 ' please run the application again'
249 250 ' in order to rescan repositories') % repo_name,
250 251 category='error')
251 252
252 253 return redirect(url('repos'))
253 254 try:
254 255 action_logger(self.rhodecode_user, 'admin_deleted_repo',
255 256 repo_name, '', self.sa)
256 257 repo_model.delete(repo)
257 258 invalidate_cache('get_repo_cached_%s' % repo_name)
258 259 h.flash(_('deleted repository %s') % repo_name, category='success')
259 260 Session.commit()
260 261 except IntegrityError, e:
261 262 if e.message.find('repositories_fork_id_fkey') != -1:
262 263 log.error(traceback.format_exc())
263 264 h.flash(_('Cannot delete %s it still contains attached '
264 265 'forks') % repo_name,
265 266 category='warning')
266 267 else:
267 268 log.error(traceback.format_exc())
268 269 h.flash(_('An error occurred during '
269 270 'deletion of %s') % repo_name,
270 271 category='error')
271 272
272 273 except Exception, e:
273 274 log.error(traceback.format_exc())
274 275 h.flash(_('An error occurred during deletion of %s') % repo_name,
275 276 category='error')
276 277
277 278 return redirect(url('repos'))
278 279
279 280 @HasPermissionAllDecorator('hg.admin')
280 281 def delete_perm_user(self, repo_name):
281 282 """
282 283 DELETE an existing repository permission user
283 284
284 285 :param repo_name:
285 286 """
286 287
287 288 try:
288 289 repo_model = RepoModel()
289 290 repo_model.delete_perm_user(request.POST, repo_name)
291 Session.commit()
290 292 except Exception, e:
291 293 h.flash(_('An error occurred during deletion of repository user'),
292 294 category='error')
293 295 raise HTTPInternalServerError()
294 296
295 297 @HasPermissionAllDecorator('hg.admin')
296 298 def delete_perm_users_group(self, repo_name):
297 299 """
298 300 DELETE an existing repository permission users group
299 301
300 302 :param repo_name:
301 303 """
302 304 try:
303 305 repo_model = RepoModel()
304 306 repo_model.delete_perm_users_group(request.POST, repo_name)
307 Session.commit()
305 308 except Exception, e:
306 309 h.flash(_('An error occurred during deletion of repository'
307 310 ' users groups'),
308 311 category='error')
309 312 raise HTTPInternalServerError()
310 313
311 314 @HasPermissionAllDecorator('hg.admin')
312 315 def repo_stats(self, repo_name):
313 316 """
314 317 DELETE an existing repository statistics
315 318
316 319 :param repo_name:
317 320 """
318 321
319 322 try:
320 323 repo_model = RepoModel()
321 324 repo_model.delete_stats(repo_name)
325 Session.commit()
322 326 except Exception, e:
323 327 h.flash(_('An error occurred during deletion of repository stats'),
324 328 category='error')
325 329 return redirect(url('edit_repo', repo_name=repo_name))
326 330
327 331 @HasPermissionAllDecorator('hg.admin')
328 332 def repo_cache(self, repo_name):
329 333 """
330 334 INVALIDATE existing repository cache
331 335
332 336 :param repo_name:
333 337 """
334 338
335 339 try:
336 340 ScmModel().mark_for_invalidation(repo_name)
341 Session.commit()
337 342 except Exception, e:
338 343 h.flash(_('An error occurred during cache invalidation'),
339 344 category='error')
340 345 return redirect(url('edit_repo', repo_name=repo_name))
341 346
342 347 @HasPermissionAllDecorator('hg.admin')
343 348 def repo_public_journal(self, repo_name):
344 349 """
345 350 Set's this repository to be visible in public journal,
346 351 in other words assing default user to follow this repo
347 352
348 353 :param repo_name:
349 354 """
350 355
351 356 cur_token = request.POST.get('auth_token')
352 357 token = get_token()
353 358 if cur_token == token:
354 359 try:
355 360 repo_id = Repository.get_by_repo_name(repo_name).repo_id
356 361 user_id = User.get_by_username('default').user_id
357 362 self.scm_model.toggle_following_repo(repo_id, user_id)
358 363 h.flash(_('Updated repository visibility in public journal'),
359 364 category='success')
365 Session.commit()
360 366 except:
361 367 h.flash(_('An error occurred during setting this'
362 368 ' repository in public journal'),
363 369 category='error')
364 370
365 371 else:
366 372 h.flash(_('Token mismatch'), category='error')
367 373 return redirect(url('edit_repo', repo_name=repo_name))
368 374
369 375 @HasPermissionAllDecorator('hg.admin')
370 376 def repo_pull(self, repo_name):
371 377 """
372 378 Runs task to update given repository with remote changes,
373 379 ie. make pull on remote location
374 380
375 381 :param repo_name:
376 382 """
377 383 try:
378 384 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
379 385 h.flash(_('Pulled from remote location'), category='success')
380 386 except Exception, e:
381 387 h.flash(_('An error occurred during pull from remote location'),
382 388 category='error')
383 389
384 390 return redirect(url('edit_repo', repo_name=repo_name))
385 391
386 392 @HasPermissionAllDecorator('hg.admin')
387 393 def repo_as_fork(self, repo_name):
388 394 """
389 395 Mark given repository as a fork of another
390 396
391 397 :param repo_name:
392 398 """
393 399 try:
394 400 fork_id = request.POST.get('id_fork_of')
395 401 repo = ScmModel().mark_as_fork(repo_name, fork_id,
396 402 self.rhodecode_user.username)
397 403 fork = repo.fork.repo_name if repo.fork else _('Nothing')
398 404 Session.commit()
399 405 h.flash(_('Marked repo %s as fork of %s' % (repo_name,fork)),
400 406 category='success')
401 407 except Exception, e:
402 408 raise
403 409 h.flash(_('An error occurred during this operation'),
404 410 category='error')
405 411
406 412 return redirect(url('edit_repo', repo_name=repo_name))
407 413
408 414 @HasPermissionAllDecorator('hg.admin')
409 415 def show(self, repo_name, format='html'):
410 416 """GET /repos/repo_name: Show a specific item"""
411 417 # url('repo', repo_name=ID)
412 418
413 419 @HasPermissionAllDecorator('hg.admin')
414 420 def edit(self, repo_name, format='html'):
415 421 """GET /repos/repo_name/edit: Form to edit an existing item"""
416 422 # url('edit_repo', repo_name=ID)
417 423 defaults = self.__load_data(repo_name)
418 424
419 425 return htmlfill.render(
420 426 render('admin/repos/repo_edit.html'),
421 427 defaults=defaults,
422 428 encoding="UTF-8",
423 429 force_defaults=False
424 430 )
@@ -1,174 +1,174
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.forks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 forks controller for rhodecode
7 7
8 8 :created_on: Apr 23, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import formencode
27 27 import traceback
28 28 from formencode import htmlfill
29 29
30 30 from pylons import tmpl_context as c, request, url
31 31 from pylons.controllers.util import redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 import rhodecode.lib.helpers as h
35 35
36 36 from rhodecode.lib.helpers import Page
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
38 38 NotAnonymous
39 39 from rhodecode.lib.base import BaseRepoController, render
40 40 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
41 41 from rhodecode.model.repo import RepoModel
42 42 from rhodecode.model.forms import RepoForkForm
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class ForksController(BaseRepoController):
48 48
49 49 @LoginRequired()
50 50 def __before__(self):
51 51 super(ForksController, self).__before__()
52 52
53 53 def __load_defaults(self):
54 54 c.repo_groups = RepoGroup.groups_choices()
55 55 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
56 56
57 57 def __load_data(self, repo_name=None):
58 58 """
59 59 Load defaults settings for edit, and update
60 60
61 61 :param repo_name:
62 62 """
63 63 self.__load_defaults()
64 64
65 65 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
66 66 repo = db_repo.scm_instance
67 67
68 68 if c.repo_info is None:
69 69 h.flash(_('%s repository is not mapped to db perhaps'
70 70 ' it was created or renamed from the filesystem'
71 71 ' please run the application again'
72 72 ' in order to rescan repositories') % repo_name,
73 73 category='error')
74 74
75 75 return redirect(url('repos'))
76 76
77 77 c.default_user_id = User.get_by_username('default').user_id
78 78 c.in_public_journal = UserFollowing.query()\
79 79 .filter(UserFollowing.user_id == c.default_user_id)\
80 80 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
81 81
82 82 if c.repo_info.stats:
83 last_rev = c.repo_info.stats.stat_on_revision
83 last_rev = c.repo_info.stats.stat_on_revision+1
84 84 else:
85 85 last_rev = 0
86 86 c.stats_revision = last_rev
87 87
88 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
88 c.repo_last_rev = repo.count() if repo.revisions else 0
89 89
90 90 if last_rev == 0 or c.repo_last_rev == 0:
91 91 c.stats_percentage = 0
92 92 else:
93 93 c.stats_percentage = '%.2f' % ((float((last_rev)) /
94 94 c.repo_last_rev) * 100)
95 95
96 96 defaults = RepoModel()._get_defaults(repo_name)
97 97 # add prefix to fork
98 98 defaults['repo_name'] = 'fork-' + defaults['repo_name']
99 99 return defaults
100 100
101 101 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
102 102 'repository.admin')
103 103 def forks(self, repo_name):
104 104 p = int(request.params.get('page', 1))
105 105 repo_id = c.rhodecode_db_repo.repo_id
106 106 d = Repository.get_repo_forks(repo_id)
107 107 c.forks_pager = Page(d, page=p, items_per_page=20)
108 108
109 109 c.forks_data = render('/forks/forks_data.html')
110 110
111 111 if request.environ.get('HTTP_X_PARTIAL_XHR'):
112 112 return c.forks_data
113 113
114 114 return render('/forks/forks.html')
115 115
116 116 @NotAnonymous()
117 117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 118 'repository.admin')
119 119 def fork(self, repo_name):
120 120 c.repo_info = Repository.get_by_repo_name(repo_name)
121 121 if not c.repo_info:
122 122 h.flash(_('%s repository is not mapped to db perhaps'
123 123 ' it was created or renamed from the file system'
124 124 ' please run the application again'
125 125 ' in order to rescan repositories') % repo_name,
126 126 category='error')
127 127
128 128 return redirect(url('home'))
129 129
130 130 defaults = self.__load_data(repo_name)
131 131
132 132 return htmlfill.render(
133 133 render('forks/fork.html'),
134 134 defaults=defaults,
135 135 encoding="UTF-8",
136 136 force_defaults=False
137 137 )
138 138
139 139
140 140 @NotAnonymous()
141 141 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
142 142 'repository.admin')
143 143 def fork_create(self, repo_name):
144 144 self.__load_defaults()
145 145 c.repo_info = Repository.get_by_repo_name(repo_name)
146 146 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
147 147 repo_groups=c.repo_groups_choices,)()
148 148 form_result = {}
149 149 try:
150 150 form_result = _form.to_python(dict(request.POST))
151 151 # add org_path of repo so we can do a clone from it later
152 152 form_result['org_path'] = c.repo_info.repo_name
153 153
154 154 # create fork is done sometimes async on celery, db transaction
155 155 # management is handled there.
156 156 RepoModel().create_fork(form_result, self.rhodecode_user)
157 157 h.flash(_('forked %s repository as %s') \
158 158 % (repo_name, form_result['repo_name']),
159 159 category='success')
160 160 except formencode.Invalid, errors:
161 161 c.new_repo = errors.value['repo_name']
162 162
163 163 return htmlfill.render(
164 164 render('forks/fork.html'),
165 165 defaults=errors.value,
166 166 errors=errors.error_dict or {},
167 167 prefix_error=False,
168 168 encoding="UTF-8")
169 169 except Exception:
170 170 log.error(traceback.format_exc())
171 171 h.flash(_('An error occurred during repository forking %s') %
172 172 repo_name, category='error')
173 173
174 174 return redirect(url('home'))
@@ -1,228 +1,229
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.summary
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Summary controller for Rhodecode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import traceback
27 27 import calendar
28 28 import logging
29 29 from time import mktime
30 30 from datetime import timedelta, date
31 31 from itertools import product
32 32 from urlparse import urlparse
33 33
34 34 from vcs.exceptions import ChangesetError, EmptyRepositoryError, \
35 35 NodeDoesNotExistError
36 36
37 37 from pylons import tmpl_context as c, request, url, config
38 38 from pylons.i18n.translation import _
39 39
40 40 from beaker.cache import cache_region, region_invalidate
41 41
42 42 from rhodecode.model.db import Statistics, CacheInvalidation
43 43 from rhodecode.lib import ALL_READMES, ALL_EXTS
44 44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
45 45 from rhodecode.lib.base import BaseRepoController, render
46 46 from rhodecode.lib.utils import EmptyChangeset
47 47 from rhodecode.lib.markup_renderer import MarkupRenderer
48 48 from rhodecode.lib.celerylib import run_task
49 49 from rhodecode.lib.celerylib.tasks import get_commits_stats, \
50 50 LANGUAGES_EXTENSIONS_MAP
51 51 from rhodecode.lib.helpers import RepoPage
52 52 from rhodecode.lib.compat import json, OrderedDict
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
57 57 sorted(list(product(ALL_READMES, ALL_EXTS)),
58 58 key=lambda y:y[0][1] + y[1][1])]
59 59
60
60 61 class SummaryController(BaseRepoController):
61 62
62 63 @LoginRequired()
63 64 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
64 65 'repository.admin')
65 66 def __before__(self):
66 67 super(SummaryController, self).__before__()
67 68
68 69 def index(self, repo_name):
69 70 c.dbrepo = dbrepo = c.rhodecode_db_repo
70 71 c.following = self.scm_model.is_following_repo(repo_name,
71 72 self.rhodecode_user.user_id)
72 73
73 74 def url_generator(**kw):
74 75 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
75 76
76 77 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
77 78 items_per_page=10, url=url_generator)
78 79
79 80 if self.rhodecode_user.username == 'default':
80 81 # for default(anonymous) user we don't need to pass credentials
81 82 username = ''
82 83 password = ''
83 84 else:
84 85 username = str(self.rhodecode_user.username)
85 86 password = '@'
86 87
87 88 parsed_url = urlparse(url.current(qualified=True))
88 89
89 90 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
90 91
91 92 uri_tmpl = config.get('clone_uri', default_clone_uri)
92 93 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
93 94
94 95 uri = uri_tmpl % {'user': username,
95 96 'pass': password,
96 97 'scheme': parsed_url.scheme,
97 'netloc':parsed_url.netloc,
98 'netloc': parsed_url.netloc,
98 99 'path':parsed_url.path}
99 100
100 101 c.clone_repo_url = uri
101 102 c.repo_tags = OrderedDict()
102 103 for name, hash in c.rhodecode_repo.tags.items()[:10]:
103 104 try:
104 105 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
105 106 except ChangesetError:
106 107 c.repo_tags[name] = EmptyChangeset(hash)
107 108
108 109 c.repo_branches = OrderedDict()
109 110 for name, hash in c.rhodecode_repo.branches.items()[:10]:
110 111 try:
111 112 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
112 113 except ChangesetError:
113 114 c.repo_branches[name] = EmptyChangeset(hash)
114 115
115 116 td = date.today() + timedelta(days=1)
116 117 td_1m = td - timedelta(days=calendar.mdays[td.month])
117 118 td_1y = td - timedelta(days=365)
118 119
119 120 ts_min_m = mktime(td_1m.timetuple())
120 121 ts_min_y = mktime(td_1y.timetuple())
121 122 ts_max_y = mktime(td.timetuple())
122 123
123 124 if dbrepo.enable_statistics:
124 125 c.show_stats = True
125 126 c.no_data_msg = _('No data loaded yet')
126 127 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
127 128 else:
128 129 c.show_stats = False
129 130 c.no_data_msg = _('Statistics are disabled for this repository')
130 131 c.ts_min = ts_min_m
131 132 c.ts_max = ts_max_y
132 133
133 134 stats = self.sa.query(Statistics)\
134 135 .filter(Statistics.repository == dbrepo)\
135 136 .scalar()
136 137
137 138 c.stats_percentage = 0
138 139
139 140 if stats and stats.languages:
140 141 c.no_data = False is dbrepo.enable_statistics
141 142 lang_stats_d = json.loads(stats.languages)
142 143 c.commit_data = stats.commit_activity
143 144 c.overview_data = stats.commit_activity_combined
144 145
145 146 lang_stats = ((x, {"count": y,
146 147 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
147 148 for x, y in lang_stats_d.items())
148 149
149 150 c.trending_languages = json.dumps(OrderedDict(
150 151 sorted(lang_stats, reverse=True,
151 152 key=lambda k: k[1])[:10]
152 153 )
153 154 )
154 last_rev = stats.stat_on_revision
155 c.repo_last_rev = c.rhodecode_repo.count() - 1 \
155 last_rev = stats.stat_on_revision + 1
156 c.repo_last_rev = c.rhodecode_repo.count()\
156 157 if c.rhodecode_repo.revisions else 0
157 158 if last_rev == 0 or c.repo_last_rev == 0:
158 159 pass
159 160 else:
160 161 c.stats_percentage = '%.2f' % ((float((last_rev)) /
161 162 c.repo_last_rev) * 100)
162 163 else:
163 164 c.commit_data = json.dumps({})
164 165 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
165 166 c.trending_languages = json.dumps({})
166 167 c.no_data = True
167 168
168 169 c.enable_downloads = dbrepo.enable_downloads
169 170 if c.enable_downloads:
170 171 c.download_options = self._get_download_links(c.rhodecode_repo)
171 172
172 173 c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_repo)
173 174 return render('summary/summary.html')
174 175
175 176 def __get_readme_data(self, repo):
176 177
177 178 @cache_region('long_term')
178 179 def _get_readme_from_cache(key):
179 180 readme_data = None
180 181 readme_file = None
181 182 log.debug('Fetching readme file')
182 183 try:
183 184 cs = repo.get_changeset('tip')
184 185 renderer = MarkupRenderer()
185 186 for f in README_FILES:
186 187 try:
187 188 readme = cs.get_node(f)
188 189 readme_file = f
189 190 readme_data = renderer.render(readme.content, f)
190 191 log.debug('Found readme %s' % readme_file)
191 192 break
192 193 except NodeDoesNotExistError:
193 194 continue
194 195 except ChangesetError:
195 196 pass
196 197 except EmptyRepositoryError:
197 198 pass
198 199 except Exception:
199 200 log.error(traceback.format_exc())
200 201
201 202 return readme_data, readme_file
202 203
203 204 key = repo.name + '_README'
204 205 inv = CacheInvalidation.invalidate(key)
205 206 if inv is not None:
206 207 region_invalidate(_get_readme_from_cache, None, key)
207 208 CacheInvalidation.set_valid(inv.cache_key)
208 209 return _get_readme_from_cache(key)
209 210
210 211 def _get_download_links(self, repo):
211 212
212 213 download_l = []
213 214
214 215 branches_group = ([], _("Branches"))
215 216 tags_group = ([], _("Tags"))
216 217
217 218 for name, chs in c.rhodecode_repo.branches.items():
218 219 #chs = chs.split(':')[-1]
219 220 branches_group[0].append((chs, name),)
220 221 download_l.append(branches_group)
221 222
222 223 for name, chs in c.rhodecode_repo.tags.items():
223 224 #chs = chs.split(':')[-1]
224 225 tags_group[0].append((chs, name),)
225 226 download_l.append(tags_group)
226 227
227 228 return download_l
228 229
@@ -1,414 +1,418
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 from celery.decorators import task
27 27
28 28 import os
29 29 import traceback
30 30 import logging
31 31 from os.path import join as jn
32 32
33 33 from time import mktime
34 34 from operator import itemgetter
35 35 from string import lower
36 36
37 37 from pylons import config, url
38 38 from pylons.i18n.translation import _
39 39
40 40 from vcs import get_backend
41 41
42 42 from rhodecode import CELERY_ON
43 43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
44 44 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
45 45 __get_lockkey, LockHeld, DaemonLock
46 46 from rhodecode.lib.helpers import person
47 47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
48 48 from rhodecode.lib.utils import add_cache, action_logger
49 49 from rhodecode.lib.compat import json, OrderedDict
50 50
51 51 from rhodecode.model import init_model
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Statistics, Repository, User
54 54
55 55 from sqlalchemy import engine_from_config
56 56
57 57 add_cache(config)
58 58
59 59 __all__ = ['whoosh_index', 'get_commits_stats',
60 60 'reset_user_password', 'send_email']
61 61
62 62
63 63 def get_session():
64 64 if CELERY_ON:
65 65 engine = engine_from_config(config, 'sqlalchemy.db1.')
66 66 init_model(engine)
67 67 sa = meta.Session
68 68 return sa
69 69
70 70 def get_logger(cls):
71 71 if CELERY_ON:
72 72 try:
73 73 log = cls.get_logger()
74 74 except:
75 75 log = logging.getLogger(__name__)
76 76 else:
77 77 log = logging.getLogger(__name__)
78 78
79 79 return log
80 80
81
81 82 @task(ignore_result=True)
82 83 @locked_task
83 84 def whoosh_index(repo_location, full_index):
84 85 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
85 86
86 #log = whoosh_index.get_logger()
87 # log = whoosh_index.get_logger(whoosh_index)
87 88
88 89 index_location = config['index_dir']
89 90 WhooshIndexingDaemon(index_location=index_location,
90 91 repo_location=repo_location, sa=get_session())\
91 92 .run(full_index=full_index)
92 93
93 94
94 95 @task(ignore_result=True)
95 96 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
96 97 log = get_logger(get_commits_stats)
97 98
98 99 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
99 100 ts_max_y)
100 101 lockkey_path = config['here']
101 102
102 103 log.info('running task with lockkey %s', lockkey)
104
103 105 try:
104 106 sa = get_session()
105 107 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
106 108
107 109 # for js data compatibilty cleans the key for person from '
108 110 akc = lambda k: person(k).replace('"', "")
109 111
110 112 co_day_auth_aggr = {}
111 113 commits_by_day_aggregate = {}
112 114 repo = Repository.get_by_repo_name(repo_name)
113 115 if repo is None:
114 116 return True
115 117
116 118 repo = repo.scm_instance
117 repo_size = len(repo.revisions)
118 #return if repo have no revisions
119 repo_size = repo.count()
120 # return if repo have no revisions
119 121 if repo_size < 1:
120 122 lock.release()
121 123 return True
122 124
123 125 skip_date_limit = True
124 126 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
125 last_rev = 0
127 last_rev = None
126 128 last_cs = None
127 129 timegetter = itemgetter('time')
128 130
129 131 dbrepo = sa.query(Repository)\
130 132 .filter(Repository.repo_name == repo_name).scalar()
131 133 cur_stats = sa.query(Statistics)\
132 134 .filter(Statistics.repository == dbrepo).scalar()
133 135
134 136 if cur_stats is not None:
135 137 last_rev = cur_stats.stat_on_revision
136 138
137 139 if last_rev == repo.get_changeset().revision and repo_size > 1:
138 140 # pass silently without any work if we're not on first revision or
139 141 # current state of parsing revision(from db marker) is the
140 142 # last revision
141 143 lock.release()
142 144 return True
143 145
144 146 if cur_stats:
145 147 commits_by_day_aggregate = OrderedDict(json.loads(
146 148 cur_stats.commit_activity_combined))
147 149 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
148 150
149 151 log.debug('starting parsing %s', parse_limit)
150 152 lmktime = mktime
151 153
152 last_rev = last_rev + 1 if last_rev > 0 else last_rev
153
154 last_rev = last_rev + 1 if last_rev >= 0 else 0
155 log.debug('Getting revisions from %s to %s' % (
156 last_rev, last_rev + parse_limit)
157 )
154 158 for cs in repo[last_rev:last_rev + parse_limit]:
155 159 last_cs = cs # remember last parsed changeset
156 160 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
157 161 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
158 162
159 163 if akc(cs.author) in co_day_auth_aggr:
160 164 try:
161 165 l = [timegetter(x) for x in
162 166 co_day_auth_aggr[akc(cs.author)]['data']]
163 167 time_pos = l.index(k)
164 168 except ValueError:
165 169 time_pos = False
166 170
167 171 if time_pos >= 0 and time_pos is not False:
168 172
169 173 datadict = \
170 174 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
171 175
172 176 datadict["commits"] += 1
173 177 datadict["added"] += len(cs.added)
174 178 datadict["changed"] += len(cs.changed)
175 179 datadict["removed"] += len(cs.removed)
176 180
177 181 else:
178 182 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
179 183
180 184 datadict = {"time": k,
181 185 "commits": 1,
182 186 "added": len(cs.added),
183 187 "changed": len(cs.changed),
184 188 "removed": len(cs.removed),
185 189 }
186 190 co_day_auth_aggr[akc(cs.author)]['data']\
187 191 .append(datadict)
188 192
189 193 else:
190 194 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
191 195 co_day_auth_aggr[akc(cs.author)] = {
192 196 "label": akc(cs.author),
193 197 "data": [{"time":k,
194 198 "commits":1,
195 199 "added":len(cs.added),
196 200 "changed":len(cs.changed),
197 201 "removed":len(cs.removed),
198 202 }],
199 203 "schema": ["commits"],
200 204 }
201 205
202 206 #gather all data by day
203 207 if k in commits_by_day_aggregate:
204 208 commits_by_day_aggregate[k] += 1
205 209 else:
206 210 commits_by_day_aggregate[k] = 1
207 211
208 212 overview_data = sorted(commits_by_day_aggregate.items(),
209 213 key=itemgetter(0))
210 214
211 215 if not co_day_auth_aggr:
212 216 co_day_auth_aggr[akc(repo.contact)] = {
213 217 "label": akc(repo.contact),
214 218 "data": [0, 1],
215 219 "schema": ["commits"],
216 220 }
217 221
218 222 stats = cur_stats if cur_stats else Statistics()
219 223 stats.commit_activity = json.dumps(co_day_auth_aggr)
220 224 stats.commit_activity_combined = json.dumps(overview_data)
221 225
222 226 log.debug('last revison %s', last_rev)
223 227 leftovers = len(repo.revisions[last_rev:])
224 228 log.debug('revisions to parse %s', leftovers)
225 229
226 230 if last_rev == 0 or leftovers < parse_limit:
227 231 log.debug('getting code trending stats')
228 232 stats.languages = json.dumps(__get_codes_stats(repo_name))
229 233
230 234 try:
231 235 stats.repository = dbrepo
232 236 stats.stat_on_revision = last_cs.revision if last_cs else 0
233 237 sa.add(stats)
234 238 sa.commit()
235 239 except:
236 240 log.error(traceback.format_exc())
237 241 sa.rollback()
238 242 lock.release()
239 243 return False
240 244
241 245 #final release
242 246 lock.release()
243 247
244 248 #execute another task if celery is enabled
245 249 if len(repo.revisions) > 1 and CELERY_ON:
246 250 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
247 251 return True
248 252 except LockHeld:
249 253 log.info('LockHeld')
250 254 return 'Task with key %s already running' % lockkey
251 255
252 256 @task(ignore_result=True)
253 257 def send_password_link(user_email):
254 258 from rhodecode.model.notification import EmailNotificationModel
255 259
256 260 log = get_logger(send_password_link)
257 261
258 262 try:
259 263 sa = get_session()
260 264 user = User.get_by_email(user_email)
261 265 if user:
262 266 log.debug('password reset user found %s' % user)
263 267 link = url('reset_password_confirmation', key=user.api_key,
264 268 qualified=True)
265 269 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
266 270 body = EmailNotificationModel().get_email_tmpl(reg_type,
267 271 **{'user':user.short_contact,
268 272 'reset_url':link})
269 273 log.debug('sending email')
270 274 run_task(send_email, user_email,
271 275 _("password reset link"), body)
272 276 log.info('send new password mail to %s', user_email)
273 277 else:
274 278 log.debug("password reset email %s not found" % user_email)
275 279 except:
276 280 log.error(traceback.format_exc())
277 281 return False
278 282
279 283 return True
280 284
281 285 @task(ignore_result=True)
282 286 def reset_user_password(user_email):
283 287 from rhodecode.lib import auth
284 288
285 289 log = get_logger(reset_user_password)
286 290
287 291 try:
288 292 try:
289 293 sa = get_session()
290 294 user = User.get_by_email(user_email)
291 295 new_passwd = auth.PasswordGenerator().gen_password(8,
292 296 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
293 297 if user:
294 298 user.password = auth.get_crypt_password(new_passwd)
295 299 user.api_key = auth.generate_api_key(user.username)
296 300 sa.add(user)
297 301 sa.commit()
298 302 log.info('change password for %s', user_email)
299 303 if new_passwd is None:
300 304 raise Exception('unable to generate new password')
301 305 except:
302 306 log.error(traceback.format_exc())
303 307 sa.rollback()
304 308
305 309 run_task(send_email, user_email,
306 310 'Your new password',
307 311 'Your new RhodeCode password:%s' % (new_passwd))
308 312 log.info('send new password mail to %s', user_email)
309 313
310 314 except:
311 315 log.error('Failed to update user password')
312 316 log.error(traceback.format_exc())
313 317
314 318 return True
315 319
316 320
317 321 @task(ignore_result=True)
318 322 def send_email(recipients, subject, body, html_body=''):
319 323 """
320 324 Sends an email with defined parameters from the .ini files.
321 325
322 326 :param recipients: list of recipients, it this is empty the defined email
323 327 address from field 'email_to' is used instead
324 328 :param subject: subject of the mail
325 329 :param body: body of the mail
326 330 :param html_body: html version of body
327 331 """
328 332 log = get_logger(send_email)
329 333 sa = get_session()
330 334 email_config = config
331 335 subject = "%s %s" % (email_config.get('email_prefix'), subject)
332 336 if not recipients:
333 337 # if recipients are not defined we send to email_config + all admins
334 338 admins = [u.email for u in User.query()
335 339 .filter(User.admin == True).all()]
336 340 recipients = [email_config.get('email_to')] + admins
337 341
338 342 mail_from = email_config.get('app_email_from', 'RhodeCode')
339 343 user = email_config.get('smtp_username')
340 344 passwd = email_config.get('smtp_password')
341 345 mail_server = email_config.get('smtp_server')
342 346 mail_port = email_config.get('smtp_port')
343 347 tls = str2bool(email_config.get('smtp_use_tls'))
344 348 ssl = str2bool(email_config.get('smtp_use_ssl'))
345 349 debug = str2bool(config.get('debug'))
346 350 smtp_auth = email_config.get('smtp_auth')
347 351
348 352 try:
349 353 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
350 354 mail_port, ssl, tls, debug=debug)
351 355 m.send(recipients, subject, body, html_body)
352 356 except:
353 357 log.error('Mail sending failed')
354 358 log.error(traceback.format_exc())
355 359 return False
356 360 return True
357 361
358 362
359 363 @task(ignore_result=True)
360 364 def create_repo_fork(form_data, cur_user):
361 365 """
362 366 Creates a fork of repository using interval VCS methods
363 367
364 368 :param form_data:
365 369 :param cur_user:
366 370 """
367 371 from rhodecode.model.repo import RepoModel
368 372
369 373 log = get_logger(create_repo_fork)
370 374
371 375 Session = get_session()
372 376 base_path = Repository.base_path()
373 377
374 378 RepoModel(Session).create(form_data, cur_user, just_db=True, fork=True)
375 379
376 380 alias = form_data['repo_type']
377 381 org_repo_name = form_data['org_path']
378 382 fork_name = form_data['repo_name_full']
379 383 update_after_clone = form_data['update_after_clone']
380 384 source_repo_path = os.path.join(base_path, org_repo_name)
381 385 destination_fork_path = os.path.join(base_path, fork_name)
382 386
383 387 log.info('creating fork of %s as %s', source_repo_path,
384 388 destination_fork_path)
385 389 backend = get_backend(alias)
386 390 backend(safe_str(destination_fork_path), create=True,
387 391 src_url=safe_str(source_repo_path),
388 392 update_after_clone=update_after_clone)
389 393 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
390 394 org_repo_name, '', Session)
391 395
392 396 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
393 397 fork_name, '', Session)
394 398 # finally commit at latest possible stage
395 399 Session.commit()
396 400
397 401 def __get_codes_stats(repo_name):
398 402 repo = Repository.get_by_repo_name(repo_name).scm_instance
399 403
400 404 tip = repo.get_changeset()
401 405 code_stats = {}
402 406
403 407 def aggregate(cs):
404 408 for f in cs[2]:
405 409 ext = lower(f.extension)
406 410 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
407 411 if ext in code_stats:
408 412 code_stats[ext] += 1
409 413 else:
410 414 code_stats[ext] = 1
411 415
412 416 map(aggregate, tip.walk('/'))
413 417
414 418 return code_stats or {}
General Comments 0
You need to be logged in to leave comments. Login now