##// END OF EJS Templates
Added handling of ignore whitespace flag in changesets...
marcink -
r1752:f28dc032 beta
parent child Browse files
Show More
@@ -1,398 +1,398 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Admin controller for RhodeCode
6 Admin controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from paste.httpexceptions import HTTPInternalServerError
31 from paste.httpexceptions import HTTPInternalServerError
32 from pylons import request, session, tmpl_context as c, url
32 from pylons import request, session, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.exc import IntegrityError
35 from sqlalchemy.exc import IntegrityError
36
36
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 HasPermissionAnyDecorator
39 HasPermissionAnyDecorator
40 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 from rhodecode.lib.helpers import get_token
42 from rhodecode.lib.helpers import get_token
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
45 from rhodecode.model.forms import RepoForm
45 from rhodecode.model.forms import RepoForm
46 from rhodecode.model.scm import ScmModel
46 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class ReposController(BaseController):
52 class ReposController(BaseController):
53 """
53 """
54 REST Controller styled on the Atom Publishing Protocol"""
54 REST Controller styled on the Atom Publishing Protocol"""
55 # To properly map this controller, ensure your config/routing.py
55 # To properly map this controller, ensure your config/routing.py
56 # file has a resource setup:
56 # file has a resource setup:
57 # map.resource('repo', 'repos')
57 # map.resource('repo', 'repos')
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 def __before__(self):
61 def __before__(self):
62 c.admin_user = session.get('admin_user')
62 c.admin_user = session.get('admin_user')
63 c.admin_username = session.get('admin_username')
63 c.admin_username = session.get('admin_username')
64 super(ReposController, self).__before__()
64 super(ReposController, self).__before__()
65
65
66 def __load_defaults(self):
66 def __load_defaults(self):
67 c.repo_groups = RepoGroup.groups_choices()
67 c.repo_groups = RepoGroup.groups_choices()
68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69
69
70 repo_model = RepoModel()
70 repo_model = RepoModel()
71 c.users_array = repo_model.get_users_js()
71 c.users_array = repo_model.get_users_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
73
73
74 def __load_data(self, repo_name=None):
74 def __load_data(self, repo_name=None):
75 """
75 """
76 Load defaults settings for edit, and update
76 Load defaults settings for edit, and update
77
77
78 :param repo_name:
78 :param repo_name:
79 """
79 """
80 self.__load_defaults()
80 self.__load_defaults()
81
81
82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
83 repo = db_repo.scm_instance
83 repo = db_repo.scm_instance
84
84
85 if c.repo_info is None:
85 if c.repo_info is None:
86 h.flash(_('%s repository is not mapped to db perhaps'
86 h.flash(_('%s repository is not mapped to db perhaps'
87 ' it was created or renamed from the filesystem'
87 ' it was created or renamed from the filesystem'
88 ' please run the application again'
88 ' please run the application again'
89 ' in order to rescan repositories') % repo_name,
89 ' in order to rescan repositories') % repo_name,
90 category='error')
90 category='error')
91
91
92 return redirect(url('repos'))
92 return redirect(url('repos'))
93
93
94 c.default_user_id = User.get_by_username('default').user_id
94 c.default_user_id = User.get_by_username('default').user_id
95 c.in_public_journal = UserFollowing.query()\
95 c.in_public_journal = UserFollowing.query()\
96 .filter(UserFollowing.user_id == c.default_user_id)\
96 .filter(UserFollowing.user_id == c.default_user_id)\
97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
98
98
99 if c.repo_info.stats:
99 if c.repo_info.stats:
100 last_rev = c.repo_info.stats.stat_on_revision
100 last_rev = c.repo_info.stats.stat_on_revision
101 else:
101 else:
102 last_rev = 0
102 last_rev = 0
103 c.stats_revision = last_rev
103 c.stats_revision = last_rev
104
104
105 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
105 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
106
106
107 if last_rev == 0 or c.repo_last_rev == 0:
107 if last_rev == 0 or c.repo_last_rev == 0:
108 c.stats_percentage = 0
108 c.stats_percentage = 0
109 else:
109 else:
110 c.stats_percentage = '%.2f' % ((float((last_rev)) /
110 c.stats_percentage = '%.2f' % ((float((last_rev)) /
111 c.repo_last_rev) * 100)
111 c.repo_last_rev) * 100)
112
112
113 defaults = RepoModel()._get_defaults(repo_name)
113 defaults = RepoModel()._get_defaults(repo_name)
114 return defaults
114 return defaults
115
115
116 @HasPermissionAllDecorator('hg.admin')
116 @HasPermissionAllDecorator('hg.admin')
117 def index(self, format='html'):
117 def index(self, format='html'):
118 """GET /repos: All items in the collection"""
118 """GET /repos: All items in the collection"""
119 # url('repos')
119 # url('repos')
120
120
121 c.repos_list = ScmModel().get_repos(Repository.query()
121 c.repos_list = ScmModel().get_repos(Repository.query()
122 .order_by(Repository.repo_name)
122 .order_by(Repository.repo_name)
123 .all(), sort_key='name_sort')
123 .all(), sort_key='name_sort')
124 return render('admin/repos/repos.html')
124 return render('admin/repos/repos.html')
125
125
126 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
126 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
127 def create(self):
127 def create(self):
128 """
128 """
129 POST /repos: Create a new item"""
129 POST /repos: Create a new item"""
130 # url('repos')
130 # url('repos')
131
131
132 self.__load_defaults()
132 self.__load_defaults()
133 form_result = {}
133 form_result = {}
134 try:
134 try:
135 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
135 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
136 .to_python(dict(request.POST))
136 .to_python(dict(request.POST))
137 RepoModel().create(form_result, self.rhodecode_user)
137 RepoModel().create(form_result, self.rhodecode_user)
138 if form_result['clone_uri']:
138 if form_result['clone_uri']:
139 h.flash(_('created repository %s from %s') \
139 h.flash(_('created repository %s from %s') \
140 % (form_result['repo_name'], form_result['clone_uri']),
140 % (form_result['repo_name'], form_result['clone_uri']),
141 category='success')
141 category='success')
142 else:
142 else:
143 h.flash(_('created repository %s') % form_result['repo_name'],
143 h.flash(_('created repository %s') % form_result['repo_name'],
144 category='success')
144 category='success')
145
145
146 if request.POST.get('user_created'):
146 if request.POST.get('user_created'):
147 # created by regular non admin user
147 # created by regular non admin user
148 action_logger(self.rhodecode_user, 'user_created_repo',
148 action_logger(self.rhodecode_user, 'user_created_repo',
149 form_result['repo_name_full'], '', self.sa)
149 form_result['repo_name_full'], '', self.sa)
150 else:
150 else:
151 action_logger(self.rhodecode_user, 'admin_created_repo',
151 action_logger(self.rhodecode_user, 'admin_created_repo',
152 form_result['repo_name_full'], '', self.sa)
152 form_result['repo_name_full'], '', self.sa)
153 Session.commit()
153 Session.commit()
154 except formencode.Invalid, errors:
154 except formencode.Invalid, errors:
155
155
156 c.new_repo = errors.value['repo_name']
156 c.new_repo = errors.value['repo_name']
157
157
158 if request.POST.get('user_created'):
158 if request.POST.get('user_created'):
159 r = render('admin/repos/repo_add_create_repository.html')
159 r = render('admin/repos/repo_add_create_repository.html')
160 else:
160 else:
161 r = render('admin/repos/repo_add.html')
161 r = render('admin/repos/repo_add.html')
162
162
163 return htmlfill.render(
163 return htmlfill.render(
164 r,
164 r,
165 defaults=errors.value,
165 defaults=errors.value,
166 errors=errors.error_dict or {},
166 errors=errors.error_dict or {},
167 prefix_error=False,
167 prefix_error=False,
168 encoding="UTF-8")
168 encoding="UTF-8")
169
169
170 except Exception:
170 except Exception:
171 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
172 msg = _('error occurred during creation of repository %s') \
172 msg = _('error occurred during creation of repository %s') \
173 % form_result.get('repo_name')
173 % form_result.get('repo_name')
174 h.flash(msg, category='error')
174 h.flash(msg, category='error')
175 if request.POST.get('user_created'):
175 if request.POST.get('user_created'):
176 return redirect(url('home'))
176 return redirect(url('home'))
177 return redirect(url('repos'))
177 return redirect(url('repos'))
178
178
179 @HasPermissionAllDecorator('hg.admin')
179 @HasPermissionAllDecorator('hg.admin')
180 def new(self, format='html'):
180 def new(self, format='html'):
181 """GET /repos/new: Form to create a new item"""
181 """GET /repos/new: Form to create a new item"""
182 new_repo = request.GET.get('repo', '')
182 new_repo = request.GET.get('repo', '')
183 c.new_repo = repo_name_slug(new_repo)
183 c.new_repo = repo_name_slug(new_repo)
184 self.__load_defaults()
184 self.__load_defaults()
185 return render('admin/repos/repo_add.html')
185 return render('admin/repos/repo_add.html')
186
186
187 @HasPermissionAllDecorator('hg.admin')
187 @HasPermissionAllDecorator('hg.admin')
188 def update(self, repo_name):
188 def update(self, repo_name):
189 """
189 """
190 PUT /repos/repo_name: Update an existing item"""
190 PUT /repos/repo_name: Update an existing item"""
191 # Forms posted to this method should contain a hidden field:
191 # Forms posted to this method should contain a hidden field:
192 # <input type="hidden" name="_method" value="PUT" />
192 # <input type="hidden" name="_method" value="PUT" />
193 # Or using helpers:
193 # Or using helpers:
194 # h.form(url('repo', repo_name=ID),
194 # h.form(url('repo', repo_name=ID),
195 # method='put')
195 # method='put')
196 # url('repo', repo_name=ID)
196 # url('repo', repo_name=ID)
197 self.__load_defaults()
197 self.__load_defaults()
198 repo_model = RepoModel()
198 repo_model = RepoModel()
199 changed_name = repo_name
199 changed_name = repo_name
200 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
200 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
201 repo_groups=c.repo_groups_choices)()
201 repo_groups=c.repo_groups_choices)()
202 try:
202 try:
203 form_result = _form.to_python(dict(request.POST))
203 form_result = _form.to_python(dict(request.POST))
204 repo = repo_model.update(repo_name, form_result)
204 repo = repo_model.update(repo_name, form_result)
205 invalidate_cache('get_repo_cached_%s' % repo_name)
205 invalidate_cache('get_repo_cached_%s' % repo_name)
206 h.flash(_('Repository %s updated successfully' % repo_name),
206 h.flash(_('Repository %s updated successfully' % repo_name),
207 category='success')
207 category='success')
208 changed_name = repo.repo_name
208 changed_name = repo.repo_name
209 action_logger(self.rhodecode_user, 'admin_updated_repo',
209 action_logger(self.rhodecode_user, 'admin_updated_repo',
210 changed_name, '', self.sa)
210 changed_name, '', self.sa)
211 Session.commit()
211 Session.commit()
212 except formencode.Invalid, errors:
212 except formencode.Invalid, errors:
213 defaults = self.__load_data(repo_name)
213 defaults = self.__load_data(repo_name)
214 defaults.update(errors.value)
214 defaults.update(errors.value)
215 return htmlfill.render(
215 return htmlfill.render(
216 render('admin/repos/repo_edit.html'),
216 render('admin/repos/repo_edit.html'),
217 defaults=defaults,
217 defaults=defaults,
218 errors=errors.error_dict or {},
218 errors=errors.error_dict or {},
219 prefix_error=False,
219 prefix_error=False,
220 encoding="UTF-8")
220 encoding="UTF-8")
221
221
222 except Exception:
222 except Exception:
223 log.error(traceback.format_exc())
223 log.error(traceback.format_exc())
224 h.flash(_('error occurred during update of repository %s') \
224 h.flash(_('error occurred during update of repository %s') \
225 % repo_name, category='error')
225 % repo_name, category='error')
226 return redirect(url('edit_repo', repo_name=changed_name))
226 return redirect(url('edit_repo', repo_name=changed_name))
227
227
228 @HasPermissionAllDecorator('hg.admin')
228 @HasPermissionAllDecorator('hg.admin')
229 def delete(self, repo_name):
229 def delete(self, repo_name):
230 """
230 """
231 DELETE /repos/repo_name: Delete an existing item"""
231 DELETE /repos/repo_name: Delete an existing item"""
232 # Forms posted to this method should contain a hidden field:
232 # Forms posted to this method should contain a hidden field:
233 # <input type="hidden" name="_method" value="DELETE" />
233 # <input type="hidden" name="_method" value="DELETE" />
234 # Or using helpers:
234 # Or using helpers:
235 # h.form(url('repo', repo_name=ID),
235 # h.form(url('repo', repo_name=ID),
236 # method='delete')
236 # method='delete')
237 # url('repo', repo_name=ID)
237 # url('repo', repo_name=ID)
238
238
239 repo_model = RepoModel()
239 repo_model = RepoModel()
240 repo = repo_model.get_by_repo_name(repo_name)
240 repo = repo_model.get_by_repo_name(repo_name)
241 if not repo:
241 if not repo:
242 h.flash(_('%s repository is not mapped to db perhaps'
242 h.flash(_('%s repository is not mapped to db perhaps'
243 ' it was moved or renamed from the filesystem'
243 ' it was moved or renamed from the filesystem'
244 ' please run the application again'
244 ' please run the application again'
245 ' in order to rescan repositories') % repo_name,
245 ' in order to rescan repositories') % repo_name,
246 category='error')
246 category='error')
247
247
248 return redirect(url('repos'))
248 return redirect(url('repos'))
249 try:
249 try:
250 action_logger(self.rhodecode_user, 'admin_deleted_repo',
250 action_logger(self.rhodecode_user, 'admin_deleted_repo',
251 repo_name, '', self.sa)
251 repo_name, '', self.sa)
252 repo_model.delete(repo)
252 repo_model.delete(repo)
253 invalidate_cache('get_repo_cached_%s' % repo_name)
253 invalidate_cache('get_repo_cached_%s' % repo_name)
254 h.flash(_('deleted repository %s') % repo_name, category='success')
254 h.flash(_('deleted repository %s') % repo_name, category='success')
255 Session.commit()
255 Session.commit()
256 except IntegrityError, e:
256 except IntegrityError, e:
257 if e.message.find('repositories_fork_id_fkey'):
257 if e.message.find('repositories_fork_id_fkey') != -1:
258 log.error(traceback.format_exc())
258 log.error(traceback.format_exc())
259 h.flash(_('Cannot delete %s it still contains attached '
259 h.flash(_('Cannot delete %s it still contains attached '
260 'forks') % repo_name,
260 'forks') % repo_name,
261 category='warning')
261 category='warning')
262 else:
262 else:
263 log.error(traceback.format_exc())
263 log.error(traceback.format_exc())
264 h.flash(_('An error occurred during '
264 h.flash(_('An error occurred during '
265 'deletion of %s') % repo_name,
265 'deletion of %s') % repo_name,
266 category='error')
266 category='error')
267
267
268 except Exception, e:
268 except Exception, e:
269 log.error(traceback.format_exc())
269 log.error(traceback.format_exc())
270 h.flash(_('An error occurred during deletion of %s') % repo_name,
270 h.flash(_('An error occurred during deletion of %s') % repo_name,
271 category='error')
271 category='error')
272
272
273 return redirect(url('repos'))
273 return redirect(url('repos'))
274
274
275 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
276 def delete_perm_user(self, repo_name):
276 def delete_perm_user(self, repo_name):
277 """
277 """
278 DELETE an existing repository permission user
278 DELETE an existing repository permission user
279
279
280 :param repo_name:
280 :param repo_name:
281 """
281 """
282
282
283 try:
283 try:
284 repo_model = RepoModel()
284 repo_model = RepoModel()
285 repo_model.delete_perm_user(request.POST, repo_name)
285 repo_model.delete_perm_user(request.POST, repo_name)
286 except Exception, e:
286 except Exception, e:
287 h.flash(_('An error occurred during deletion of repository user'),
287 h.flash(_('An error occurred during deletion of repository user'),
288 category='error')
288 category='error')
289 raise HTTPInternalServerError()
289 raise HTTPInternalServerError()
290
290
291 @HasPermissionAllDecorator('hg.admin')
291 @HasPermissionAllDecorator('hg.admin')
292 def delete_perm_users_group(self, repo_name):
292 def delete_perm_users_group(self, repo_name):
293 """
293 """
294 DELETE an existing repository permission users group
294 DELETE an existing repository permission users group
295
295
296 :param repo_name:
296 :param repo_name:
297 """
297 """
298 try:
298 try:
299 repo_model = RepoModel()
299 repo_model = RepoModel()
300 repo_model.delete_perm_users_group(request.POST, repo_name)
300 repo_model.delete_perm_users_group(request.POST, repo_name)
301 except Exception, e:
301 except Exception, e:
302 h.flash(_('An error occurred during deletion of repository'
302 h.flash(_('An error occurred during deletion of repository'
303 ' users groups'),
303 ' users groups'),
304 category='error')
304 category='error')
305 raise HTTPInternalServerError()
305 raise HTTPInternalServerError()
306
306
307 @HasPermissionAllDecorator('hg.admin')
307 @HasPermissionAllDecorator('hg.admin')
308 def repo_stats(self, repo_name):
308 def repo_stats(self, repo_name):
309 """
309 """
310 DELETE an existing repository statistics
310 DELETE an existing repository statistics
311
311
312 :param repo_name:
312 :param repo_name:
313 """
313 """
314
314
315 try:
315 try:
316 repo_model = RepoModel()
316 repo_model = RepoModel()
317 repo_model.delete_stats(repo_name)
317 repo_model.delete_stats(repo_name)
318 except Exception, e:
318 except Exception, e:
319 h.flash(_('An error occurred during deletion of repository stats'),
319 h.flash(_('An error occurred during deletion of repository stats'),
320 category='error')
320 category='error')
321 return redirect(url('edit_repo', repo_name=repo_name))
321 return redirect(url('edit_repo', repo_name=repo_name))
322
322
323 @HasPermissionAllDecorator('hg.admin')
323 @HasPermissionAllDecorator('hg.admin')
324 def repo_cache(self, repo_name):
324 def repo_cache(self, repo_name):
325 """
325 """
326 INVALIDATE existing repository cache
326 INVALIDATE existing repository cache
327
327
328 :param repo_name:
328 :param repo_name:
329 """
329 """
330
330
331 try:
331 try:
332 ScmModel().mark_for_invalidation(repo_name)
332 ScmModel().mark_for_invalidation(repo_name)
333 except Exception, e:
333 except Exception, e:
334 h.flash(_('An error occurred during cache invalidation'),
334 h.flash(_('An error occurred during cache invalidation'),
335 category='error')
335 category='error')
336 return redirect(url('edit_repo', repo_name=repo_name))
336 return redirect(url('edit_repo', repo_name=repo_name))
337
337
338 @HasPermissionAllDecorator('hg.admin')
338 @HasPermissionAllDecorator('hg.admin')
339 def repo_public_journal(self, repo_name):
339 def repo_public_journal(self, repo_name):
340 """
340 """
341 Set's this repository to be visible in public journal,
341 Set's this repository to be visible in public journal,
342 in other words assing default user to follow this repo
342 in other words assing default user to follow this repo
343
343
344 :param repo_name:
344 :param repo_name:
345 """
345 """
346
346
347 cur_token = request.POST.get('auth_token')
347 cur_token = request.POST.get('auth_token')
348 token = get_token()
348 token = get_token()
349 if cur_token == token:
349 if cur_token == token:
350 try:
350 try:
351 repo_id = Repository.get_by_repo_name(repo_name).repo_id
351 repo_id = Repository.get_by_repo_name(repo_name).repo_id
352 user_id = User.get_by_username('default').user_id
352 user_id = User.get_by_username('default').user_id
353 self.scm_model.toggle_following_repo(repo_id, user_id)
353 self.scm_model.toggle_following_repo(repo_id, user_id)
354 h.flash(_('Updated repository visibility in public journal'),
354 h.flash(_('Updated repository visibility in public journal'),
355 category='success')
355 category='success')
356 except:
356 except:
357 h.flash(_('An error occurred during setting this'
357 h.flash(_('An error occurred during setting this'
358 ' repository in public journal'),
358 ' repository in public journal'),
359 category='error')
359 category='error')
360
360
361 else:
361 else:
362 h.flash(_('Token mismatch'), category='error')
362 h.flash(_('Token mismatch'), category='error')
363 return redirect(url('edit_repo', repo_name=repo_name))
363 return redirect(url('edit_repo', repo_name=repo_name))
364
364
365 @HasPermissionAllDecorator('hg.admin')
365 @HasPermissionAllDecorator('hg.admin')
366 def repo_pull(self, repo_name):
366 def repo_pull(self, repo_name):
367 """
367 """
368 Runs task to update given repository with remote changes,
368 Runs task to update given repository with remote changes,
369 ie. make pull on remote location
369 ie. make pull on remote location
370
370
371 :param repo_name:
371 :param repo_name:
372 """
372 """
373 try:
373 try:
374 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
374 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
375 h.flash(_('Pulled from remote location'), category='success')
375 h.flash(_('Pulled from remote location'), category='success')
376 except Exception, e:
376 except Exception, e:
377 h.flash(_('An error occurred during pull from remote location'),
377 h.flash(_('An error occurred during pull from remote location'),
378 category='error')
378 category='error')
379
379
380 return redirect(url('edit_repo', repo_name=repo_name))
380 return redirect(url('edit_repo', repo_name=repo_name))
381
381
382 @HasPermissionAllDecorator('hg.admin')
382 @HasPermissionAllDecorator('hg.admin')
383 def show(self, repo_name, format='html'):
383 def show(self, repo_name, format='html'):
384 """GET /repos/repo_name: Show a specific item"""
384 """GET /repos/repo_name: Show a specific item"""
385 # url('repo', repo_name=ID)
385 # url('repo', repo_name=ID)
386
386
387 @HasPermissionAllDecorator('hg.admin')
387 @HasPermissionAllDecorator('hg.admin')
388 def edit(self, repo_name, format='html'):
388 def edit(self, repo_name, format='html'):
389 """GET /repos/repo_name/edit: Form to edit an existing item"""
389 """GET /repos/repo_name/edit: Form to edit an existing item"""
390 # url('edit_repo', repo_name=ID)
390 # url('edit_repo', repo_name=ID)
391 defaults = self.__load_data(repo_name)
391 defaults = self.__load_data(repo_name)
392
392
393 return htmlfill.render(
393 return htmlfill.render(
394 render('admin/repos/repo_edit.html'),
394 render('admin/repos/repo_edit.html'),
395 defaults=defaults,
395 defaults=defaults,
396 encoding="UTF-8",
396 encoding="UTF-8",
397 force_defaults=False
397 force_defaults=False
398 )
398 )
@@ -1,230 +1,230 b''
1 import logging
1 import logging
2 import traceback
2 import traceback
3 import formencode
3 import formencode
4
4
5 from formencode import htmlfill
5 from formencode import htmlfill
6 from operator import itemgetter
6 from operator import itemgetter
7
7
8 from pylons import request, response, session, tmpl_context as c, url
8 from pylons import request, response, session, tmpl_context as c, url
9 from pylons.controllers.util import abort, redirect
9 from pylons.controllers.util import abort, redirect
10 from pylons.i18n.translation import _
10 from pylons.i18n.translation import _
11
11
12 from sqlalchemy.exc import IntegrityError
12 from sqlalchemy.exc import IntegrityError
13
13
14 from rhodecode.lib import helpers as h
14 from rhodecode.lib import helpers as h
15 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator
15 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator
16 from rhodecode.lib.base import BaseController, render
16 from rhodecode.lib.base import BaseController, render
17 from rhodecode.model.db import RepoGroup
17 from rhodecode.model.db import RepoGroup
18 from rhodecode.model.repos_group import ReposGroupModel
18 from rhodecode.model.repos_group import ReposGroupModel
19 from rhodecode.model.forms import ReposGroupForm
19 from rhodecode.model.forms import ReposGroupForm
20 from rhodecode.model.meta import Session
20 from rhodecode.model.meta import Session
21
21
22 log = logging.getLogger(__name__)
22 log = logging.getLogger(__name__)
23
23
24
24
25 class ReposGroupsController(BaseController):
25 class ReposGroupsController(BaseController):
26 """REST Controller styled on the Atom Publishing Protocol"""
26 """REST Controller styled on the Atom Publishing Protocol"""
27 # To properly map this controller, ensure your config/routing.py
27 # To properly map this controller, ensure your config/routing.py
28 # file has a resource setup:
28 # file has a resource setup:
29 # map.resource('repos_group', 'repos_groups')
29 # map.resource('repos_group', 'repos_groups')
30
30
31 @LoginRequired()
31 @LoginRequired()
32 def __before__(self):
32 def __before__(self):
33 super(ReposGroupsController, self).__before__()
33 super(ReposGroupsController, self).__before__()
34
34
35 def __load_defaults(self):
35 def __load_defaults(self):
36 c.repo_groups = RepoGroup.groups_choices()
36 c.repo_groups = RepoGroup.groups_choices()
37 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
37 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
38
38
39 def __load_data(self, group_id):
39 def __load_data(self, group_id):
40 """
40 """
41 Load defaults settings for edit, and update
41 Load defaults settings for edit, and update
42
42
43 :param group_id:
43 :param group_id:
44 """
44 """
45 self.__load_defaults()
45 self.__load_defaults()
46
46
47 repo_group = RepoGroup.get(group_id)
47 repo_group = RepoGroup.get(group_id)
48
48
49 data = repo_group.get_dict()
49 data = repo_group.get_dict()
50
50
51 data['group_name'] = repo_group.name
51 data['group_name'] = repo_group.name
52
52
53 return data
53 return data
54
54
55 @HasPermissionAnyDecorator('hg.admin')
55 @HasPermissionAnyDecorator('hg.admin')
56 def index(self, format='html'):
56 def index(self, format='html'):
57 """GET /repos_groups: All items in the collection"""
57 """GET /repos_groups: All items in the collection"""
58 # url('repos_groups')
58 # url('repos_groups')
59
59
60 sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
60 sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
61 c.groups = sorted(RepoGroup.query().all(), key=sk)
61 c.groups = sorted(RepoGroup.query().all(), key=sk)
62 return render('admin/repos_groups/repos_groups_show.html')
62 return render('admin/repos_groups/repos_groups_show.html')
63
63
64 @HasPermissionAnyDecorator('hg.admin')
64 @HasPermissionAnyDecorator('hg.admin')
65 def create(self):
65 def create(self):
66 """POST /repos_groups: Create a new item"""
66 """POST /repos_groups: Create a new item"""
67 # url('repos_groups')
67 # url('repos_groups')
68 self.__load_defaults()
68 self.__load_defaults()
69 repos_group_form = ReposGroupForm(available_groups=
69 repos_group_form = ReposGroupForm(available_groups=
70 c.repo_groups_choices)()
70 c.repo_groups_choices)()
71 try:
71 try:
72 form_result = repos_group_form.to_python(dict(request.POST))
72 form_result = repos_group_form.to_python(dict(request.POST))
73 ReposGroupModel().create(form_result)
73 ReposGroupModel().create(form_result)
74 Session.commit()
74 Session.commit()
75 h.flash(_('created repos group %s') \
75 h.flash(_('created repos group %s') \
76 % form_result['group_name'], category='success')
76 % form_result['group_name'], category='success')
77 #TODO: in futureaction_logger(, '', '', '', self.sa)
77 #TODO: in futureaction_logger(, '', '', '', self.sa)
78 except formencode.Invalid, errors:
78 except formencode.Invalid, errors:
79
79
80 return htmlfill.render(
80 return htmlfill.render(
81 render('admin/repos_groups/repos_groups_add.html'),
81 render('admin/repos_groups/repos_groups_add.html'),
82 defaults=errors.value,
82 defaults=errors.value,
83 errors=errors.error_dict or {},
83 errors=errors.error_dict or {},
84 prefix_error=False,
84 prefix_error=False,
85 encoding="UTF-8")
85 encoding="UTF-8")
86 except Exception:
86 except Exception:
87 log.error(traceback.format_exc())
87 log.error(traceback.format_exc())
88 h.flash(_('error occurred during creation of repos group %s') \
88 h.flash(_('error occurred during creation of repos group %s') \
89 % request.POST.get('group_name'), category='error')
89 % request.POST.get('group_name'), category='error')
90
90
91 return redirect(url('repos_groups'))
91 return redirect(url('repos_groups'))
92
92
93
93
94 @HasPermissionAnyDecorator('hg.admin')
94 @HasPermissionAnyDecorator('hg.admin')
95 def new(self, format='html'):
95 def new(self, format='html'):
96 """GET /repos_groups/new: Form to create a new item"""
96 """GET /repos_groups/new: Form to create a new item"""
97 # url('new_repos_group')
97 # url('new_repos_group')
98 self.__load_defaults()
98 self.__load_defaults()
99 return render('admin/repos_groups/repos_groups_add.html')
99 return render('admin/repos_groups/repos_groups_add.html')
100
100
101 @HasPermissionAnyDecorator('hg.admin')
101 @HasPermissionAnyDecorator('hg.admin')
102 def update(self, id):
102 def update(self, id):
103 """PUT /repos_groups/id: Update an existing item"""
103 """PUT /repos_groups/id: Update an existing item"""
104 # Forms posted to this method should contain a hidden field:
104 # Forms posted to this method should contain a hidden field:
105 # <input type="hidden" name="_method" value="PUT" />
105 # <input type="hidden" name="_method" value="PUT" />
106 # Or using helpers:
106 # Or using helpers:
107 # h.form(url('repos_group', id=ID),
107 # h.form(url('repos_group', id=ID),
108 # method='put')
108 # method='put')
109 # url('repos_group', id=ID)
109 # url('repos_group', id=ID)
110
110
111 self.__load_defaults()
111 self.__load_defaults()
112 c.repos_group = RepoGroup.get(id)
112 c.repos_group = RepoGroup.get(id)
113
113
114 repos_group_form = ReposGroupForm(edit=True,
114 repos_group_form = ReposGroupForm(edit=True,
115 old_data=c.repos_group.get_dict(),
115 old_data=c.repos_group.get_dict(),
116 available_groups=
116 available_groups=
117 c.repo_groups_choices)()
117 c.repo_groups_choices)()
118 try:
118 try:
119 form_result = repos_group_form.to_python(dict(request.POST))
119 form_result = repos_group_form.to_python(dict(request.POST))
120 ReposGroupModel().update(id, form_result)
120 ReposGroupModel().update(id, form_result)
121 Session.commit()
121 Session.commit()
122 h.flash(_('updated repos group %s') \
122 h.flash(_('updated repos group %s') \
123 % form_result['group_name'], category='success')
123 % form_result['group_name'], category='success')
124 #TODO: in futureaction_logger(, '', '', '', self.sa)
124 #TODO: in futureaction_logger(, '', '', '', self.sa)
125 except formencode.Invalid, errors:
125 except formencode.Invalid, errors:
126
126
127 return htmlfill.render(
127 return htmlfill.render(
128 render('admin/repos_groups/repos_groups_edit.html'),
128 render('admin/repos_groups/repos_groups_edit.html'),
129 defaults=errors.value,
129 defaults=errors.value,
130 errors=errors.error_dict or {},
130 errors=errors.error_dict or {},
131 prefix_error=False,
131 prefix_error=False,
132 encoding="UTF-8")
132 encoding="UTF-8")
133 except Exception:
133 except Exception:
134 log.error(traceback.format_exc())
134 log.error(traceback.format_exc())
135 h.flash(_('error occurred during update of repos group %s') \
135 h.flash(_('error occurred during update of repos group %s') \
136 % request.POST.get('group_name'), category='error')
136 % request.POST.get('group_name'), category='error')
137
137
138 return redirect(url('repos_groups'))
138 return redirect(url('repos_groups'))
139
139
140
140
141 @HasPermissionAnyDecorator('hg.admin')
141 @HasPermissionAnyDecorator('hg.admin')
142 def delete(self, id):
142 def delete(self, id):
143 """DELETE /repos_groups/id: Delete an existing item"""
143 """DELETE /repos_groups/id: Delete an existing item"""
144 # Forms posted to this method should contain a hidden field:
144 # Forms posted to this method should contain a hidden field:
145 # <input type="hidden" name="_method" value="DELETE" />
145 # <input type="hidden" name="_method" value="DELETE" />
146 # Or using helpers:
146 # Or using helpers:
147 # h.form(url('repos_group', id=ID),
147 # h.form(url('repos_group', id=ID),
148 # method='delete')
148 # method='delete')
149 # url('repos_group', id=ID)
149 # url('repos_group', id=ID)
150
150
151 gr = RepoGroup.get(id)
151 gr = RepoGroup.get(id)
152 repos = gr.repositories.all()
152 repos = gr.repositories.all()
153 if repos:
153 if repos:
154 h.flash(_('This group contains %s repositores and cannot be '
154 h.flash(_('This group contains %s repositores and cannot be '
155 'deleted' % len(repos)),
155 'deleted' % len(repos)),
156 category='error')
156 category='error')
157 return redirect(url('repos_groups'))
157 return redirect(url('repos_groups'))
158
158
159 try:
159 try:
160 ReposGroupModel().delete(id)
160 ReposGroupModel().delete(id)
161 Session.commit()
161 Session.commit()
162 h.flash(_('removed repos group %s' % gr.group_name), category='success')
162 h.flash(_('removed repos group %s' % gr.group_name), category='success')
163 #TODO: in future action_logger(, '', '', '', self.sa)
163 #TODO: in future action_logger(, '', '', '', self.sa)
164 except IntegrityError, e:
164 except IntegrityError, e:
165 if e.message.find('groups_group_parent_id_fkey'):
165 if e.message.find('groups_group_parent_id_fkey') != -1:
166 log.error(traceback.format_exc())
166 log.error(traceback.format_exc())
167 h.flash(_('Cannot delete this group it still contains '
167 h.flash(_('Cannot delete this group it still contains '
168 'subgroups'),
168 'subgroups'),
169 category='warning')
169 category='warning')
170 else:
170 else:
171 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
172 h.flash(_('error occurred during deletion of repos '
172 h.flash(_('error occurred during deletion of repos '
173 'group %s' % gr.group_name), category='error')
173 'group %s' % gr.group_name), category='error')
174
174
175 except Exception:
175 except Exception:
176 log.error(traceback.format_exc())
176 log.error(traceback.format_exc())
177 h.flash(_('error occurred during deletion of repos '
177 h.flash(_('error occurred during deletion of repos '
178 'group %s' % gr.group_name), category='error')
178 'group %s' % gr.group_name), category='error')
179
179
180 return redirect(url('repos_groups'))
180 return redirect(url('repos_groups'))
181
181
182 def show_by_name(self, group_name):
182 def show_by_name(self, group_name):
183 id_ = RepoGroup.get_by_group_name(group_name).group_id
183 id_ = RepoGroup.get_by_group_name(group_name).group_id
184 return self.show(id_)
184 return self.show(id_)
185
185
186 def show(self, id, format='html'):
186 def show(self, id, format='html'):
187 """GET /repos_groups/id: Show a specific item"""
187 """GET /repos_groups/id: Show a specific item"""
188 # url('repos_group', id=ID)
188 # url('repos_group', id=ID)
189
189
190 c.group = RepoGroup.get(id)
190 c.group = RepoGroup.get(id)
191
191
192 if c.group:
192 if c.group:
193 c.group_repos = c.group.repositories.all()
193 c.group_repos = c.group.repositories.all()
194 else:
194 else:
195 return redirect(url('home'))
195 return redirect(url('home'))
196
196
197 #overwrite our cached list with current filter
197 #overwrite our cached list with current filter
198 gr_filter = c.group_repos
198 gr_filter = c.group_repos
199 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
199 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
200
200
201 c.repos_list = c.cached_repo_list
201 c.repos_list = c.cached_repo_list
202
202
203 c.repo_cnt = 0
203 c.repo_cnt = 0
204
204
205 c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\
205 c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\
206 .filter(RepoGroup.group_parent_id == id).all()
206 .filter(RepoGroup.group_parent_id == id).all()
207
207
208 return render('admin/repos_groups/repos_groups.html')
208 return render('admin/repos_groups/repos_groups.html')
209
209
210 @HasPermissionAnyDecorator('hg.admin')
210 @HasPermissionAnyDecorator('hg.admin')
211 def edit(self, id, format='html'):
211 def edit(self, id, format='html'):
212 """GET /repos_groups/id/edit: Form to edit an existing item"""
212 """GET /repos_groups/id/edit: Form to edit an existing item"""
213 # url('edit_repos_group', id=ID)
213 # url('edit_repos_group', id=ID)
214
214
215 id_ = int(id)
215 id_ = int(id)
216
216
217 c.repos_group = RepoGroup.get(id_)
217 c.repos_group = RepoGroup.get(id_)
218 defaults = self.__load_data(id_)
218 defaults = self.__load_data(id_)
219
219
220 # we need to exclude this group from the group list for editing
220 # we need to exclude this group from the group list for editing
221 c.repo_groups = filter(lambda x:x[0] != id_, c.repo_groups)
221 c.repo_groups = filter(lambda x:x[0] != id_, c.repo_groups)
222
222
223 return htmlfill.render(
223 return htmlfill.render(
224 render('admin/repos_groups/repos_groups_edit.html'),
224 render('admin/repos_groups/repos_groups_edit.html'),
225 defaults=defaults,
225 defaults=defaults,
226 encoding="UTF-8",
226 encoding="UTF-8",
227 force_defaults=False
227 force_defaults=False
228 )
228 )
229
229
230
230
@@ -1,297 +1,302 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons import tmpl_context as c, url, request, response
29 from pylons import tmpl_context as c, url, request, response
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.decorators import jsonify
32 from pylons.decorators import jsonify
33
33
34 import rhodecode.lib.helpers as h
34 import rhodecode.lib.helpers as h
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.utils import EmptyChangeset
37 from rhodecode.lib.utils import EmptyChangeset
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.model.db import ChangesetComment
39 from rhodecode.model.db import ChangesetComment
40 from rhodecode.model.comment import ChangesetCommentsModel
40 from rhodecode.model.comment import ChangesetCommentsModel
41
41
42 from vcs.exceptions import RepositoryError, ChangesetError, \
42 from vcs.exceptions import RepositoryError, ChangesetError, \
43 ChangesetDoesNotExistError
43 ChangesetDoesNotExistError
44 from vcs.nodes import FileNode
44 from vcs.nodes import FileNode
45 from vcs.utils import diffs as differ
45 from vcs.utils import diffs as differ
46 from webob.exc import HTTPForbidden
46 from webob.exc import HTTPForbidden
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class ChangesetController(BaseRepoController):
52 class ChangesetController(BaseRepoController):
53
53
54 @LoginRequired()
54 @LoginRequired()
55 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
55 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
56 'repository.admin')
56 'repository.admin')
57 def __before__(self):
57 def __before__(self):
58 super(ChangesetController, self).__before__()
58 super(ChangesetController, self).__before__()
59 c.affected_files_cut_off = 60
59 c.affected_files_cut_off = 60
60
60
61 def index(self, revision):
61 def index(self, revision):
62
62 ignore_whitespace = request.GET.get('ignorews') == '1'
63 def wrap_to_table(str):
63 def wrap_to_table(str):
64
64
65 return '''<table class="code-difftable">
65 return '''<table class="code-difftable">
66 <tr class="line">
66 <tr class="line">
67 <td class="lineno new"></td>
67 <td class="lineno new"></td>
68 <td class="code"><pre>%s</pre></td>
68 <td class="code"><pre>%s</pre></td>
69 </tr>
69 </tr>
70 </table>''' % str
70 </table>''' % str
71
71
72 #get ranges of revisions if preset
72 #get ranges of revisions if preset
73 rev_range = revision.split('...')[:2]
73 rev_range = revision.split('...')[:2]
74
74
75 try:
75 try:
76 if len(rev_range) == 2:
76 if len(rev_range) == 2:
77 rev_start = rev_range[0]
77 rev_start = rev_range[0]
78 rev_end = rev_range[1]
78 rev_end = rev_range[1]
79 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
79 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
80 end=rev_end)
80 end=rev_end)
81 else:
81 else:
82 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
82 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
83
83
84 c.cs_ranges = list(rev_ranges)
84 c.cs_ranges = list(rev_ranges)
85 if not c.cs_ranges:
85 if not c.cs_ranges:
86 raise RepositoryError('Changeset range returned empty result')
86 raise RepositoryError('Changeset range returned empty result')
87
87
88 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
88 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
89 log.error(traceback.format_exc())
89 log.error(traceback.format_exc())
90 h.flash(str(e), category='warning')
90 h.flash(str(e), category='warning')
91 return redirect(url('home'))
91 return redirect(url('home'))
92
92
93 c.changes = OrderedDict()
93 c.changes = OrderedDict()
94 c.sum_added = 0
94 c.sum_added = 0
95 c.sum_removed = 0
95 c.sum_removed = 0
96 c.lines_added = 0
96 c.lines_added = 0
97 c.lines_deleted = 0
97 c.lines_deleted = 0
98 c.cut_off = False # defines if cut off limit is reached
98 c.cut_off = False # defines if cut off limit is reached
99
99
100 c.comments = []
100 c.comments = []
101 c.inline_comments = []
101 c.inline_comments = []
102 c.inline_cnt = 0
102 c.inline_cnt = 0
103 # Iterate over ranges (default changeset view is always one changeset)
103 # Iterate over ranges (default changeset view is always one changeset)
104 for changeset in c.cs_ranges:
104 for changeset in c.cs_ranges:
105 c.comments.extend(ChangesetCommentsModel()\
105 c.comments.extend(ChangesetCommentsModel()\
106 .get_comments(c.rhodecode_db_repo.repo_id,
106 .get_comments(c.rhodecode_db_repo.repo_id,
107 changeset.raw_id))
107 changeset.raw_id))
108 inlines = ChangesetCommentsModel()\
108 inlines = ChangesetCommentsModel()\
109 .get_inline_comments(c.rhodecode_db_repo.repo_id,
109 .get_inline_comments(c.rhodecode_db_repo.repo_id,
110 changeset.raw_id)
110 changeset.raw_id)
111 c.inline_comments.extend(inlines)
111 c.inline_comments.extend(inlines)
112 c.changes[changeset.raw_id] = []
112 c.changes[changeset.raw_id] = []
113 try:
113 try:
114 changeset_parent = changeset.parents[0]
114 changeset_parent = changeset.parents[0]
115 except IndexError:
115 except IndexError:
116 changeset_parent = None
116 changeset_parent = None
117
117
118 #==================================================================
118 #==================================================================
119 # ADDED FILES
119 # ADDED FILES
120 #==================================================================
120 #==================================================================
121 for node in changeset.added:
121 for node in changeset.added:
122
122
123 filenode_old = FileNode(node.path, '', EmptyChangeset())
123 filenode_old = FileNode(node.path, '', EmptyChangeset())
124 if filenode_old.is_binary or node.is_binary:
124 if filenode_old.is_binary or node.is_binary:
125 diff = wrap_to_table(_('binary file'))
125 diff = wrap_to_table(_('binary file'))
126 st = (0, 0)
126 st = (0, 0)
127 else:
127 else:
128 # in this case node.size is good parameter since those are
128 # in this case node.size is good parameter since those are
129 # added nodes and their size defines how many changes were
129 # added nodes and their size defines how many changes were
130 # made
130 # made
131 c.sum_added += node.size
131 c.sum_added += node.size
132 if c.sum_added < self.cut_off_limit:
132 if c.sum_added < self.cut_off_limit:
133 f_gitdiff = differ.get_gitdiff(filenode_old, node)
133 f_gitdiff = differ.get_gitdiff(filenode_old, node,
134 ignore_whitespace=ignore_whitespace)
134 d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
135 d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
135
136
136 st = d.stat()
137 st = d.stat()
137 diff = d.as_html()
138 diff = d.as_html()
138
139
139 else:
140 else:
140 diff = wrap_to_table(_('Changeset is to big and '
141 diff = wrap_to_table(_('Changeset is to big and '
141 'was cut off, see raw '
142 'was cut off, see raw '
142 'changeset instead'))
143 'changeset instead'))
143 c.cut_off = True
144 c.cut_off = True
144 break
145 break
145
146
146 cs1 = None
147 cs1 = None
147 cs2 = node.last_changeset.raw_id
148 cs2 = node.last_changeset.raw_id
148 c.lines_added += st[0]
149 c.lines_added += st[0]
149 c.lines_deleted += st[1]
150 c.lines_deleted += st[1]
150 c.changes[changeset.raw_id].append(('added', node, diff,
151 c.changes[changeset.raw_id].append(('added', node, diff,
151 cs1, cs2, st))
152 cs1, cs2, st))
152
153
153 #==================================================================
154 #==================================================================
154 # CHANGED FILES
155 # CHANGED FILES
155 #==================================================================
156 #==================================================================
156 if not c.cut_off:
157 if not c.cut_off:
157 for node in changeset.changed:
158 for node in changeset.changed:
158 try:
159 try:
159 filenode_old = changeset_parent.get_node(node.path)
160 filenode_old = changeset_parent.get_node(node.path)
160 except ChangesetError:
161 except ChangesetError:
161 log.warning('Unable to fetch parent node for diff')
162 log.warning('Unable to fetch parent node for diff')
162 filenode_old = FileNode(node.path, '',
163 filenode_old = FileNode(node.path, '',
163 EmptyChangeset())
164 EmptyChangeset())
164
165
165 if filenode_old.is_binary or node.is_binary:
166 if filenode_old.is_binary or node.is_binary:
166 diff = wrap_to_table(_('binary file'))
167 diff = wrap_to_table(_('binary file'))
167 st = (0, 0)
168 st = (0, 0)
168 else:
169 else:
169
170
170 if c.sum_removed < self.cut_off_limit:
171 if c.sum_removed < self.cut_off_limit:
171 f_gitdiff = differ.get_gitdiff(filenode_old, node)
172 f_gitdiff = differ.get_gitdiff(filenode_old, node,
173 ignore_whitespace=ignore_whitespace)
172 d = differ.DiffProcessor(f_gitdiff,
174 d = differ.DiffProcessor(f_gitdiff,
173 format='gitdiff')
175 format='gitdiff')
174 st = d.stat()
176 st = d.stat()
175 if (st[0] + st[1]) * 256 > self.cut_off_limit:
177 if (st[0] + st[1]) * 256 > self.cut_off_limit:
176 diff = wrap_to_table(_('Diff is to big '
178 diff = wrap_to_table(_('Diff is to big '
177 'and was cut off, see '
179 'and was cut off, see '
178 'raw diff instead'))
180 'raw diff instead'))
179 else:
181 else:
180 diff = d.as_html()
182 diff = d.as_html()
181
183
182 if diff:
184 if diff:
183 c.sum_removed += len(diff)
185 c.sum_removed += len(diff)
184 else:
186 else:
185 diff = wrap_to_table(_('Changeset is to big and '
187 diff = wrap_to_table(_('Changeset is to big and '
186 'was cut off, see raw '
188 'was cut off, see raw '
187 'changeset instead'))
189 'changeset instead'))
188 c.cut_off = True
190 c.cut_off = True
189 break
191 break
190
192
191 cs1 = filenode_old.last_changeset.raw_id
193 cs1 = filenode_old.last_changeset.raw_id
192 cs2 = node.last_changeset.raw_id
194 cs2 = node.last_changeset.raw_id
193 c.lines_added += st[0]
195 c.lines_added += st[0]
194 c.lines_deleted += st[1]
196 c.lines_deleted += st[1]
195 c.changes[changeset.raw_id].append(('changed', node, diff,
197 c.changes[changeset.raw_id].append(('changed', node, diff,
196 cs1, cs2, st))
198 cs1, cs2, st))
197
199
198 #==================================================================
200 #==================================================================
199 # REMOVED FILES
201 # REMOVED FILES
200 #==================================================================
202 #==================================================================
201 if not c.cut_off:
203 if not c.cut_off:
202 for node in changeset.removed:
204 for node in changeset.removed:
203 c.changes[changeset.raw_id].append(('removed', node, None,
205 c.changes[changeset.raw_id].append(('removed', node, None,
204 None, None, (0, 0)))
206 None, None, (0, 0)))
205
207
206 # count inline comments
208 # count inline comments
207 for path, lines in c.inline_comments:
209 for path, lines in c.inline_comments:
208 for comments in lines.values():
210 for comments in lines.values():
209 c.inline_cnt += len(comments)
211 c.inline_cnt += len(comments)
210
212
211 if len(c.cs_ranges) == 1:
213 if len(c.cs_ranges) == 1:
212 c.changeset = c.cs_ranges[0]
214 c.changeset = c.cs_ranges[0]
213 c.changes = c.changes[c.changeset.raw_id]
215 c.changes = c.changes[c.changeset.raw_id]
214
216
215 return render('changeset/changeset.html')
217 return render('changeset/changeset.html')
216 else:
218 else:
217 return render('changeset/changeset_range.html')
219 return render('changeset/changeset_range.html')
218
220
219 def raw_changeset(self, revision):
221 def raw_changeset(self, revision):
220
222
221 method = request.GET.get('diff', 'show')
223 method = request.GET.get('diff', 'show')
224 ignore_whitespace = request.GET.get('ignorews') == '1'
222 try:
225 try:
223 c.scm_type = c.rhodecode_repo.alias
226 c.scm_type = c.rhodecode_repo.alias
224 c.changeset = c.rhodecode_repo.get_changeset(revision)
227 c.changeset = c.rhodecode_repo.get_changeset(revision)
225 except RepositoryError:
228 except RepositoryError:
226 log.error(traceback.format_exc())
229 log.error(traceback.format_exc())
227 return redirect(url('home'))
230 return redirect(url('home'))
228 else:
231 else:
229 try:
232 try:
230 c.changeset_parent = c.changeset.parents[0]
233 c.changeset_parent = c.changeset.parents[0]
231 except IndexError:
234 except IndexError:
232 c.changeset_parent = None
235 c.changeset_parent = None
233 c.changes = []
236 c.changes = []
234
237
235 for node in c.changeset.added:
238 for node in c.changeset.added:
236 filenode_old = FileNode(node.path, '')
239 filenode_old = FileNode(node.path, '')
237 if filenode_old.is_binary or node.is_binary:
240 if filenode_old.is_binary or node.is_binary:
238 diff = _('binary file') + '\n'
241 diff = _('binary file') + '\n'
239 else:
242 else:
240 f_gitdiff = differ.get_gitdiff(filenode_old, node)
243 f_gitdiff = differ.get_gitdiff(filenode_old, node,
244 ignore_whitespace=ignore_whitespace)
241 diff = differ.DiffProcessor(f_gitdiff,
245 diff = differ.DiffProcessor(f_gitdiff,
242 format='gitdiff').raw_diff()
246 format='gitdiff').raw_diff()
243
247
244 cs1 = None
248 cs1 = None
245 cs2 = node.last_changeset.raw_id
249 cs2 = node.last_changeset.raw_id
246 c.changes.append(('added', node, diff, cs1, cs2))
250 c.changes.append(('added', node, diff, cs1, cs2))
247
251
248 for node in c.changeset.changed:
252 for node in c.changeset.changed:
249 filenode_old = c.changeset_parent.get_node(node.path)
253 filenode_old = c.changeset_parent.get_node(node.path)
250 if filenode_old.is_binary or node.is_binary:
254 if filenode_old.is_binary or node.is_binary:
251 diff = _('binary file')
255 diff = _('binary file')
252 else:
256 else:
253 f_gitdiff = differ.get_gitdiff(filenode_old, node)
257 f_gitdiff = differ.get_gitdiff(filenode_old, node,
258 ignore_whitespace=ignore_whitespace)
254 diff = differ.DiffProcessor(f_gitdiff,
259 diff = differ.DiffProcessor(f_gitdiff,
255 format='gitdiff').raw_diff()
260 format='gitdiff').raw_diff()
256
261
257 cs1 = filenode_old.last_changeset.raw_id
262 cs1 = filenode_old.last_changeset.raw_id
258 cs2 = node.last_changeset.raw_id
263 cs2 = node.last_changeset.raw_id
259 c.changes.append(('changed', node, diff, cs1, cs2))
264 c.changes.append(('changed', node, diff, cs1, cs2))
260
265
261 response.content_type = 'text/plain'
266 response.content_type = 'text/plain'
262
267
263 if method == 'download':
268 if method == 'download':
264 response.content_disposition = 'attachment; filename=%s.patch' \
269 response.content_disposition = 'attachment; filename=%s.patch' \
265 % revision
270 % revision
266
271
267 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
272 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
268 c.changeset.parents])
273 c.changeset.parents])
269
274
270 c.diffs = ''
275 c.diffs = ''
271 for x in c.changes:
276 for x in c.changes:
272 c.diffs += x[2]
277 c.diffs += x[2]
273
278
274 return render('changeset/raw_changeset.html')
279 return render('changeset/raw_changeset.html')
275
280
276 def comment(self, repo_name, revision):
281 def comment(self, repo_name, revision):
277 ChangesetCommentsModel().create(text=request.POST.get('text'),
282 ChangesetCommentsModel().create(text=request.POST.get('text'),
278 repo_id=c.rhodecode_db_repo.repo_id,
283 repo_id=c.rhodecode_db_repo.repo_id,
279 user_id=c.rhodecode_user.user_id,
284 user_id=c.rhodecode_user.user_id,
280 revision=revision,
285 revision=revision,
281 f_path=request.POST.get('f_path'),
286 f_path=request.POST.get('f_path'),
282 line_no=request.POST.get('line'))
287 line_no=request.POST.get('line'))
283 Session.commit()
288 Session.commit()
284 return redirect(h.url('changeset_home', repo_name=repo_name,
289 return redirect(h.url('changeset_home', repo_name=repo_name,
285 revision=revision))
290 revision=revision))
286
291
287 @jsonify
292 @jsonify
288 def delete_comment(self, repo_name, comment_id):
293 def delete_comment(self, repo_name, comment_id):
289 co = ChangesetComment.get(comment_id)
294 co = ChangesetComment.get(comment_id)
290 owner = lambda : co.author.user_id == c.rhodecode_user.user_id
295 owner = lambda : co.author.user_id == c.rhodecode_user.user_id
291 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
296 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
292 ChangesetCommentsModel().delete(comment=co)
297 ChangesetCommentsModel().delete(comment=co)
293 Session.commit()
298 Session.commit()
294 return True
299 return True
295 else:
300 else:
296 raise HTTPForbidden()
301 raise HTTPForbidden()
297
302
@@ -1,512 +1,517 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from os.path import join as jn
30 from os.path import join as jn
31
31
32 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from pylons.decorators import jsonify
35 from pylons.decorators import jsonify
36
36
37 from vcs.conf import settings
37 from vcs.conf import settings
38 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
38 from vcs.exceptions import RepositoryError, ChangesetDoesNotExistError, \
39 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
39 EmptyRepositoryError, ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
40 from vcs.nodes import FileNode, NodeKind
40 from vcs.nodes import FileNode, NodeKind
41 from vcs.utils import diffs as differ
41 from vcs.utils import diffs as differ
42
42
43 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
43 from rhodecode.lib import convert_line_endings, detect_mode, safe_str
44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
45 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.base import BaseRepoController, render
46 from rhodecode.lib.utils import EmptyChangeset
46 from rhodecode.lib.utils import EmptyChangeset
47 import rhodecode.lib.helpers as h
47 import rhodecode.lib.helpers as h
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class FilesController(BaseRepoController):
53 class FilesController(BaseRepoController):
54
54
55 @LoginRequired()
55 @LoginRequired()
56 def __before__(self):
56 def __before__(self):
57 super(FilesController, self).__before__()
57 super(FilesController, self).__before__()
58 c.cut_off_limit = self.cut_off_limit
58 c.cut_off_limit = self.cut_off_limit
59
59
60 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
60 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
61 """
61 """
62 Safe way to get changeset if error occur it redirects to tip with
62 Safe way to get changeset if error occur it redirects to tip with
63 proper message
63 proper message
64
64
65 :param rev: revision to fetch
65 :param rev: revision to fetch
66 :param repo_name: repo name to redirect after
66 :param repo_name: repo name to redirect after
67 """
67 """
68
68
69 try:
69 try:
70 return c.rhodecode_repo.get_changeset(rev)
70 return c.rhodecode_repo.get_changeset(rev)
71 except EmptyRepositoryError, e:
71 except EmptyRepositoryError, e:
72 if not redirect_after:
72 if not redirect_after:
73 return None
73 return None
74 url_ = url('files_add_home',
74 url_ = url('files_add_home',
75 repo_name=c.repo_name,
75 repo_name=c.repo_name,
76 revision=0, f_path='')
76 revision=0, f_path='')
77 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
77 add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
78 h.flash(h.literal(_('There are no files yet %s' % add_new)),
78 h.flash(h.literal(_('There are no files yet %s' % add_new)),
79 category='warning')
79 category='warning')
80 redirect(h.url('summary_home', repo_name=repo_name))
80 redirect(h.url('summary_home', repo_name=repo_name))
81
81
82 except RepositoryError, e:
82 except RepositoryError, e:
83 h.flash(str(e), category='warning')
83 h.flash(str(e), category='warning')
84 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
84 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
85
85
86 def __get_filenode_or_redirect(self, repo_name, cs, path):
86 def __get_filenode_or_redirect(self, repo_name, cs, path):
87 """
87 """
88 Returns file_node, if error occurs or given path is directory,
88 Returns file_node, if error occurs or given path is directory,
89 it'll redirect to top level path
89 it'll redirect to top level path
90
90
91 :param repo_name: repo_name
91 :param repo_name: repo_name
92 :param cs: given changeset
92 :param cs: given changeset
93 :param path: path to lookup
93 :param path: path to lookup
94 """
94 """
95
95
96 try:
96 try:
97 file_node = cs.get_node(path)
97 file_node = cs.get_node(path)
98 if file_node.is_dir():
98 if file_node.is_dir():
99 raise RepositoryError('given path is a directory')
99 raise RepositoryError('given path is a directory')
100 except RepositoryError, e:
100 except RepositoryError, e:
101 h.flash(str(e), category='warning')
101 h.flash(str(e), category='warning')
102 redirect(h.url('files_home', repo_name=repo_name,
102 redirect(h.url('files_home', repo_name=repo_name,
103 revision=cs.raw_id))
103 revision=cs.raw_id))
104
104
105 return file_node
105 return file_node
106
106
107
107
108 def __get_paths(self, changeset, starting_path):
108 def __get_paths(self, changeset, starting_path):
109 """recursive walk in root dir and return a set of all path in that dir
109 """recursive walk in root dir and return a set of all path in that dir
110 based on repository walk function
110 based on repository walk function
111 """
111 """
112 _files = list()
112 _files = list()
113 _dirs = list()
113 _dirs = list()
114
114
115 try:
115 try:
116 tip = changeset
116 tip = changeset
117 for topnode, dirs, files in tip.walk(starting_path):
117 for topnode, dirs, files in tip.walk(starting_path):
118 for f in files:
118 for f in files:
119 _files.append(f.path)
119 _files.append(f.path)
120 for d in dirs:
120 for d in dirs:
121 _dirs.append(d.path)
121 _dirs.append(d.path)
122 except RepositoryError, e:
122 except RepositoryError, e:
123 log.debug(traceback.format_exc())
123 log.debug(traceback.format_exc())
124 pass
124 pass
125 return _dirs, _files
125 return _dirs, _files
126
126
127 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
127 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
128 'repository.admin')
128 'repository.admin')
129 def index(self, repo_name, revision, f_path):
129 def index(self, repo_name, revision, f_path):
130 #reditect to given revision from form if given
130 #reditect to given revision from form if given
131 post_revision = request.POST.get('at_rev', None)
131 post_revision = request.POST.get('at_rev', None)
132 if post_revision:
132 if post_revision:
133 cs = self.__get_cs_or_redirect(post_revision, repo_name)
133 cs = self.__get_cs_or_redirect(post_revision, repo_name)
134 redirect(url('files_home', repo_name=c.repo_name,
134 redirect(url('files_home', repo_name=c.repo_name,
135 revision=cs.raw_id, f_path=f_path))
135 revision=cs.raw_id, f_path=f_path))
136
136
137 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
137 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
138 c.branch = request.GET.get('branch', None)
138 c.branch = request.GET.get('branch', None)
139 c.f_path = f_path
139 c.f_path = f_path
140
140
141 cur_rev = c.changeset.revision
141 cur_rev = c.changeset.revision
142
142
143 #prev link
143 #prev link
144 try:
144 try:
145 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
145 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
146 c.url_prev = url('files_home', repo_name=c.repo_name,
146 c.url_prev = url('files_home', repo_name=c.repo_name,
147 revision=prev_rev.raw_id, f_path=f_path)
147 revision=prev_rev.raw_id, f_path=f_path)
148 if c.branch:
148 if c.branch:
149 c.url_prev += '?branch=%s' % c.branch
149 c.url_prev += '?branch=%s' % c.branch
150 except (ChangesetDoesNotExistError, VCSError):
150 except (ChangesetDoesNotExistError, VCSError):
151 c.url_prev = '#'
151 c.url_prev = '#'
152
152
153 #next link
153 #next link
154 try:
154 try:
155 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
155 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
156 c.url_next = url('files_home', repo_name=c.repo_name,
156 c.url_next = url('files_home', repo_name=c.repo_name,
157 revision=next_rev.raw_id, f_path=f_path)
157 revision=next_rev.raw_id, f_path=f_path)
158 if c.branch:
158 if c.branch:
159 c.url_next += '?branch=%s' % c.branch
159 c.url_next += '?branch=%s' % c.branch
160 except (ChangesetDoesNotExistError, VCSError):
160 except (ChangesetDoesNotExistError, VCSError):
161 c.url_next = '#'
161 c.url_next = '#'
162
162
163 #files or dirs
163 #files or dirs
164 try:
164 try:
165 c.file = c.changeset.get_node(f_path)
165 c.file = c.changeset.get_node(f_path)
166
166
167 if c.file.is_file():
167 if c.file.is_file():
168 c.file_history = self._get_node_history(c.changeset, f_path)
168 c.file_history = self._get_node_history(c.changeset, f_path)
169 else:
169 else:
170 c.file_history = []
170 c.file_history = []
171 except RepositoryError, e:
171 except RepositoryError, e:
172 h.flash(str(e), category='warning')
172 h.flash(str(e), category='warning')
173 redirect(h.url('files_home', repo_name=repo_name,
173 redirect(h.url('files_home', repo_name=repo_name,
174 revision=revision))
174 revision=revision))
175
175
176 return render('files/files.html')
176 return render('files/files.html')
177
177
178 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
178 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
179 'repository.admin')
179 'repository.admin')
180 def rawfile(self, repo_name, revision, f_path):
180 def rawfile(self, repo_name, revision, f_path):
181 cs = self.__get_cs_or_redirect(revision, repo_name)
181 cs = self.__get_cs_or_redirect(revision, repo_name)
182 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
182 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
183
183
184 response.content_disposition = 'attachment; filename=%s' % \
184 response.content_disposition = 'attachment; filename=%s' % \
185 safe_str(f_path.split(os.sep)[-1])
185 safe_str(f_path.split(os.sep)[-1])
186
186
187 response.content_type = file_node.mimetype
187 response.content_type = file_node.mimetype
188 return file_node.content
188 return file_node.content
189
189
190 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
190 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
191 'repository.admin')
191 'repository.admin')
192 def raw(self, repo_name, revision, f_path):
192 def raw(self, repo_name, revision, f_path):
193 cs = self.__get_cs_or_redirect(revision, repo_name)
193 cs = self.__get_cs_or_redirect(revision, repo_name)
194 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
194 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
195
195
196 raw_mimetype_mapping = {
196 raw_mimetype_mapping = {
197 # map original mimetype to a mimetype used for "show as raw"
197 # map original mimetype to a mimetype used for "show as raw"
198 # you can also provide a content-disposition to override the
198 # you can also provide a content-disposition to override the
199 # default "attachment" disposition.
199 # default "attachment" disposition.
200 # orig_type: (new_type, new_dispo)
200 # orig_type: (new_type, new_dispo)
201
201
202 # show images inline:
202 # show images inline:
203 'image/x-icon': ('image/x-icon', 'inline'),
203 'image/x-icon': ('image/x-icon', 'inline'),
204 'image/png': ('image/png', 'inline'),
204 'image/png': ('image/png', 'inline'),
205 'image/gif': ('image/gif', 'inline'),
205 'image/gif': ('image/gif', 'inline'),
206 'image/jpeg': ('image/jpeg', 'inline'),
206 'image/jpeg': ('image/jpeg', 'inline'),
207 'image/svg+xml': ('image/svg+xml', 'inline'),
207 'image/svg+xml': ('image/svg+xml', 'inline'),
208 }
208 }
209
209
210 mimetype = file_node.mimetype
210 mimetype = file_node.mimetype
211 try:
211 try:
212 mimetype, dispo = raw_mimetype_mapping[mimetype]
212 mimetype, dispo = raw_mimetype_mapping[mimetype]
213 except KeyError:
213 except KeyError:
214 # we don't know anything special about this, handle it safely
214 # we don't know anything special about this, handle it safely
215 if file_node.is_binary:
215 if file_node.is_binary:
216 # do same as download raw for binary files
216 # do same as download raw for binary files
217 mimetype, dispo = 'application/octet-stream', 'attachment'
217 mimetype, dispo = 'application/octet-stream', 'attachment'
218 else:
218 else:
219 # do not just use the original mimetype, but force text/plain,
219 # do not just use the original mimetype, but force text/plain,
220 # otherwise it would serve text/html and that might be unsafe.
220 # otherwise it would serve text/html and that might be unsafe.
221 # Note: underlying vcs library fakes text/plain mimetype if the
221 # Note: underlying vcs library fakes text/plain mimetype if the
222 # mimetype can not be determined and it thinks it is not
222 # mimetype can not be determined and it thinks it is not
223 # binary.This might lead to erroneous text display in some
223 # binary.This might lead to erroneous text display in some
224 # cases, but helps in other cases, like with text files
224 # cases, but helps in other cases, like with text files
225 # without extension.
225 # without extension.
226 mimetype, dispo = 'text/plain', 'inline'
226 mimetype, dispo = 'text/plain', 'inline'
227
227
228 if dispo == 'attachment':
228 if dispo == 'attachment':
229 dispo = 'attachment; filename=%s' % \
229 dispo = 'attachment; filename=%s' % \
230 safe_str(f_path.split(os.sep)[-1])
230 safe_str(f_path.split(os.sep)[-1])
231
231
232 response.content_disposition = dispo
232 response.content_disposition = dispo
233 response.content_type = mimetype
233 response.content_type = mimetype
234 return file_node.content
234 return file_node.content
235
235
236 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
236 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
237 'repository.admin')
237 'repository.admin')
238 def annotate(self, repo_name, revision, f_path):
238 def annotate(self, repo_name, revision, f_path):
239 c.cs = self.__get_cs_or_redirect(revision, repo_name)
239 c.cs = self.__get_cs_or_redirect(revision, repo_name)
240 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
240 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
241
241
242 c.file_history = self._get_node_history(c.cs, f_path)
242 c.file_history = self._get_node_history(c.cs, f_path)
243 c.f_path = f_path
243 c.f_path = f_path
244 return render('files/files_annotate.html')
244 return render('files/files_annotate.html')
245
245
246 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
246 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
247 def edit(self, repo_name, revision, f_path):
247 def edit(self, repo_name, revision, f_path):
248 r_post = request.POST
248 r_post = request.POST
249
249
250 c.cs = self.__get_cs_or_redirect(revision, repo_name)
250 c.cs = self.__get_cs_or_redirect(revision, repo_name)
251 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
251 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
252
252
253 if c.file.is_binary:
253 if c.file.is_binary:
254 return redirect(url('files_home', repo_name=c.repo_name,
254 return redirect(url('files_home', repo_name=c.repo_name,
255 revision=c.cs.raw_id, f_path=f_path))
255 revision=c.cs.raw_id, f_path=f_path))
256
256
257 c.f_path = f_path
257 c.f_path = f_path
258
258
259 if r_post:
259 if r_post:
260
260
261 old_content = c.file.content
261 old_content = c.file.content
262 sl = old_content.splitlines(1)
262 sl = old_content.splitlines(1)
263 first_line = sl[0] if sl else ''
263 first_line = sl[0] if sl else ''
264 # modes: 0 - Unix, 1 - Mac, 2 - DOS
264 # modes: 0 - Unix, 1 - Mac, 2 - DOS
265 mode = detect_mode(first_line, 0)
265 mode = detect_mode(first_line, 0)
266 content = convert_line_endings(r_post.get('content'), mode)
266 content = convert_line_endings(r_post.get('content'), mode)
267
267
268 message = r_post.get('message') or (_('Edited %s via RhodeCode')
268 message = r_post.get('message') or (_('Edited %s via RhodeCode')
269 % (f_path))
269 % (f_path))
270 author = self.rhodecode_user.full_contact
270 author = self.rhodecode_user.full_contact
271
271
272 if content == old_content:
272 if content == old_content:
273 h.flash(_('No changes'),
273 h.flash(_('No changes'),
274 category='warning')
274 category='warning')
275 return redirect(url('changeset_home', repo_name=c.repo_name,
275 return redirect(url('changeset_home', repo_name=c.repo_name,
276 revision='tip'))
276 revision='tip'))
277
277
278 try:
278 try:
279 self.scm_model.commit_change(repo=c.rhodecode_repo,
279 self.scm_model.commit_change(repo=c.rhodecode_repo,
280 repo_name=repo_name, cs=c.cs,
280 repo_name=repo_name, cs=c.cs,
281 user=self.rhodecode_user,
281 user=self.rhodecode_user,
282 author=author, message=message,
282 author=author, message=message,
283 content=content, f_path=f_path)
283 content=content, f_path=f_path)
284 h.flash(_('Successfully committed to %s' % f_path),
284 h.flash(_('Successfully committed to %s' % f_path),
285 category='success')
285 category='success')
286
286
287 except Exception:
287 except Exception:
288 log.error(traceback.format_exc())
288 log.error(traceback.format_exc())
289 h.flash(_('Error occurred during commit'), category='error')
289 h.flash(_('Error occurred during commit'), category='error')
290 return redirect(url('changeset_home',
290 return redirect(url('changeset_home',
291 repo_name=c.repo_name, revision='tip'))
291 repo_name=c.repo_name, revision='tip'))
292
292
293 return render('files/files_edit.html')
293 return render('files/files_edit.html')
294
294
295 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
295 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
296 def add(self, repo_name, revision, f_path):
296 def add(self, repo_name, revision, f_path):
297 r_post = request.POST
297 r_post = request.POST
298 c.cs = self.__get_cs_or_redirect(revision, repo_name,
298 c.cs = self.__get_cs_or_redirect(revision, repo_name,
299 redirect_after=False)
299 redirect_after=False)
300 if c.cs is None:
300 if c.cs is None:
301 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
301 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
302
302
303 c.f_path = f_path
303 c.f_path = f_path
304
304
305 if r_post:
305 if r_post:
306 unix_mode = 0
306 unix_mode = 0
307 content = convert_line_endings(r_post.get('content'), unix_mode)
307 content = convert_line_endings(r_post.get('content'), unix_mode)
308
308
309 message = r_post.get('message') or (_('Added %s via RhodeCode')
309 message = r_post.get('message') or (_('Added %s via RhodeCode')
310 % (f_path))
310 % (f_path))
311 location = r_post.get('location')
311 location = r_post.get('location')
312 filename = r_post.get('filename')
312 filename = r_post.get('filename')
313 file_obj = r_post.get('upload_file', None)
313 file_obj = r_post.get('upload_file', None)
314
314
315 if file_obj is not None and hasattr(file_obj, 'filename'):
315 if file_obj is not None and hasattr(file_obj, 'filename'):
316 filename = file_obj.filename
316 filename = file_obj.filename
317 content = file_obj.file
317 content = file_obj.file
318
318
319 node_path = os.path.join(location, filename)
319 node_path = os.path.join(location, filename)
320 author = self.rhodecode_user.full_contact
320 author = self.rhodecode_user.full_contact
321
321
322 if not content:
322 if not content:
323 h.flash(_('No content'), category='warning')
323 h.flash(_('No content'), category='warning')
324 return redirect(url('changeset_home', repo_name=c.repo_name,
324 return redirect(url('changeset_home', repo_name=c.repo_name,
325 revision='tip'))
325 revision='tip'))
326 if not filename:
326 if not filename:
327 h.flash(_('No filename'), category='warning')
327 h.flash(_('No filename'), category='warning')
328 return redirect(url('changeset_home', repo_name=c.repo_name,
328 return redirect(url('changeset_home', repo_name=c.repo_name,
329 revision='tip'))
329 revision='tip'))
330
330
331 try:
331 try:
332 self.scm_model.create_node(repo=c.rhodecode_repo,
332 self.scm_model.create_node(repo=c.rhodecode_repo,
333 repo_name=repo_name, cs=c.cs,
333 repo_name=repo_name, cs=c.cs,
334 user=self.rhodecode_user,
334 user=self.rhodecode_user,
335 author=author, message=message,
335 author=author, message=message,
336 content=content, f_path=node_path)
336 content=content, f_path=node_path)
337 h.flash(_('Successfully committed to %s' % node_path),
337 h.flash(_('Successfully committed to %s' % node_path),
338 category='success')
338 category='success')
339 except NodeAlreadyExistsError, e:
339 except NodeAlreadyExistsError, e:
340 h.flash(_(e), category='error')
340 h.flash(_(e), category='error')
341 except Exception:
341 except Exception:
342 log.error(traceback.format_exc())
342 log.error(traceback.format_exc())
343 h.flash(_('Error occurred during commit'), category='error')
343 h.flash(_('Error occurred during commit'), category='error')
344 return redirect(url('changeset_home',
344 return redirect(url('changeset_home',
345 repo_name=c.repo_name, revision='tip'))
345 repo_name=c.repo_name, revision='tip'))
346
346
347 return render('files/files_add.html')
347 return render('files/files_add.html')
348
348
349 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
349 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
350 'repository.admin')
350 'repository.admin')
351 def archivefile(self, repo_name, fname):
351 def archivefile(self, repo_name, fname):
352
352
353 fileformat = None
353 fileformat = None
354 revision = None
354 revision = None
355 ext = None
355 ext = None
356 subrepos = request.GET.get('subrepos') == 'true'
356 subrepos = request.GET.get('subrepos') == 'true'
357
357
358 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
358 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
359 archive_spec = fname.split(ext_data[1])
359 archive_spec = fname.split(ext_data[1])
360 if len(archive_spec) == 2 and archive_spec[1] == '':
360 if len(archive_spec) == 2 and archive_spec[1] == '':
361 fileformat = a_type or ext_data[1]
361 fileformat = a_type or ext_data[1]
362 revision = archive_spec[0]
362 revision = archive_spec[0]
363 ext = ext_data[1]
363 ext = ext_data[1]
364
364
365 try:
365 try:
366 dbrepo = RepoModel().get_by_repo_name(repo_name)
366 dbrepo = RepoModel().get_by_repo_name(repo_name)
367 if dbrepo.enable_downloads is False:
367 if dbrepo.enable_downloads is False:
368 return _('downloads disabled')
368 return _('downloads disabled')
369
369
370 # patch and reset hooks section of UI config to not run any
370 # patch and reset hooks section of UI config to not run any
371 # hooks on fetching archives with subrepos
371 # hooks on fetching archives with subrepos
372 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
372 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
373 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
373 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
374
374
375 cs = c.rhodecode_repo.get_changeset(revision)
375 cs = c.rhodecode_repo.get_changeset(revision)
376 content_type = settings.ARCHIVE_SPECS[fileformat][0]
376 content_type = settings.ARCHIVE_SPECS[fileformat][0]
377 except ChangesetDoesNotExistError:
377 except ChangesetDoesNotExistError:
378 return _('Unknown revision %s') % revision
378 return _('Unknown revision %s') % revision
379 except EmptyRepositoryError:
379 except EmptyRepositoryError:
380 return _('Empty repository')
380 return _('Empty repository')
381 except (ImproperArchiveTypeError, KeyError):
381 except (ImproperArchiveTypeError, KeyError):
382 return _('Unknown archive type')
382 return _('Unknown archive type')
383
383
384 response.content_type = content_type
384 response.content_type = content_type
385 response.content_disposition = 'attachment; filename=%s-%s%s' \
385 response.content_disposition = 'attachment; filename=%s-%s%s' \
386 % (repo_name, revision, ext)
386 % (repo_name, revision, ext)
387
387
388 import tempfile
388 import tempfile
389 archive = tempfile.mkstemp()[1]
389 archive = tempfile.mkstemp()[1]
390 t = open(archive, 'wb')
390 t = open(archive, 'wb')
391 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
391 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
392
392
393 def get_chunked_archive(archive):
393 def get_chunked_archive(archive):
394 stream = open(archive, 'rb')
394 stream = open(archive, 'rb')
395 while True:
395 while True:
396 data = stream.read(4096)
396 data = stream.read(4096)
397 if not data:
397 if not data:
398 os.remove(archive)
398 os.remove(archive)
399 break
399 break
400 yield data
400 yield data
401
401
402 return get_chunked_archive(archive)
402 return get_chunked_archive(archive)
403
403
404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
405 'repository.admin')
405 'repository.admin')
406 def diff(self, repo_name, f_path):
406 def diff(self, repo_name, f_path):
407 ignore_whitespace = request.GET.get('ignorews') == '1'
407 diff1 = request.GET.get('diff1')
408 diff1 = request.GET.get('diff1')
408 diff2 = request.GET.get('diff2')
409 diff2 = request.GET.get('diff2')
409 c.action = request.GET.get('diff')
410 c.action = request.GET.get('diff')
410 c.no_changes = diff1 == diff2
411 c.no_changes = diff1 == diff2
411 c.f_path = f_path
412 c.f_path = f_path
412 c.big_diff = False
413 c.big_diff = False
413
414
414 try:
415 try:
415 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
416 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
416 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
417 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
417 node1 = c.changeset_1.get_node(f_path)
418 node1 = c.changeset_1.get_node(f_path)
418 else:
419 else:
419 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
420 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
420 node1 = FileNode('.', '', changeset=c.changeset_1)
421 node1 = FileNode('.', '', changeset=c.changeset_1)
421
422
422 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
423 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
423 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
424 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
424 node2 = c.changeset_2.get_node(f_path)
425 node2 = c.changeset_2.get_node(f_path)
425 else:
426 else:
426 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
427 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
427 node2 = FileNode('.', '', changeset=c.changeset_2)
428 node2 = FileNode('.', '', changeset=c.changeset_2)
428 except RepositoryError:
429 except RepositoryError:
429 return redirect(url('files_home',
430 return redirect(url('files_home',
430 repo_name=c.repo_name, f_path=f_path))
431 repo_name=c.repo_name, f_path=f_path))
431
432
432 if c.action == 'download':
433 if c.action == 'download':
433 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
434 _diff = differ.get_gitdiff(node1, node2,
434 format='gitdiff')
435 ignore_whitespace=ignore_whitespace)
436 diff = differ.DiffProcessor(_diff,format='gitdiff')
435
437
436 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
438 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
437 response.content_type = 'text/plain'
439 response.content_type = 'text/plain'
438 response.content_disposition = 'attachment; filename=%s' \
440 response.content_disposition = 'attachment; filename=%s' \
439 % diff_name
441 % diff_name
440 return diff.raw_diff()
442 return diff.raw_diff()
441
443
442 elif c.action == 'raw':
444 elif c.action == 'raw':
443 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
445 _diff = differ.get_gitdiff(node1, node2,
444 format='gitdiff')
446 ignore_whitespace=ignore_whitespace)
447 diff = differ.DiffProcessor(_diff,format='gitdiff')
445 response.content_type = 'text/plain'
448 response.content_type = 'text/plain'
446 return diff.raw_diff()
449 return diff.raw_diff()
447
450
448 elif c.action == 'diff':
451 elif c.action == 'diff':
449 if node1.is_binary or node2.is_binary:
452 if node1.is_binary or node2.is_binary:
450 c.cur_diff = _('Binary file')
453 c.cur_diff = _('Binary file')
451 elif node1.size > self.cut_off_limit or \
454 elif node1.size > self.cut_off_limit or \
452 node2.size > self.cut_off_limit:
455 node2.size > self.cut_off_limit:
453 c.cur_diff = ''
456 c.cur_diff = ''
454 c.big_diff = True
457 c.big_diff = True
455 else:
458 else:
456 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
459 _diff = differ.get_gitdiff(node1, node2,
457 format='gitdiff')
460 ignore_whitespace=ignore_whitespace)
461 diff = differ.DiffProcessor(_diff,format='gitdiff')
458 c.cur_diff = diff.as_html()
462 c.cur_diff = diff.as_html()
459 else:
463 else:
460
464
461 #default option
465 #default option
462 if node1.is_binary or node2.is_binary:
466 if node1.is_binary or node2.is_binary:
463 c.cur_diff = _('Binary file')
467 c.cur_diff = _('Binary file')
464 elif node1.size > self.cut_off_limit or \
468 elif node1.size > self.cut_off_limit or \
465 node2.size > self.cut_off_limit:
469 node2.size > self.cut_off_limit:
466 c.cur_diff = ''
470 c.cur_diff = ''
467 c.big_diff = True
471 c.big_diff = True
468
472
469 else:
473 else:
470 diff = differ.DiffProcessor(differ.get_gitdiff(node1, node2),
474 _diff = differ.get_gitdiff(node1, node2,
471 format='gitdiff')
475 ignore_whitespace=ignore_whitespace)
476 diff = differ.DiffProcessor(_diff,format='gitdiff')
472 c.cur_diff = diff.as_html()
477 c.cur_diff = diff.as_html()
473
478
474 if not c.cur_diff and not c.big_diff:
479 if not c.cur_diff and not c.big_diff:
475 c.no_changes = True
480 c.no_changes = True
476 return render('files/file_diff.html')
481 return render('files/file_diff.html')
477
482
478 def _get_node_history(self, cs, f_path):
483 def _get_node_history(self, cs, f_path):
479 changesets = cs.get_file_history(f_path)
484 changesets = cs.get_file_history(f_path)
480 hist_l = []
485 hist_l = []
481
486
482 changesets_group = ([], _("Changesets"))
487 changesets_group = ([], _("Changesets"))
483 branches_group = ([], _("Branches"))
488 branches_group = ([], _("Branches"))
484 tags_group = ([], _("Tags"))
489 tags_group = ([], _("Tags"))
485
490
486 for chs in changesets:
491 for chs in changesets:
487 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
492 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
488 changesets_group[0].append((chs.raw_id, n_desc,))
493 changesets_group[0].append((chs.raw_id, n_desc,))
489
494
490 hist_l.append(changesets_group)
495 hist_l.append(changesets_group)
491
496
492 for name, chs in c.rhodecode_repo.branches.items():
497 for name, chs in c.rhodecode_repo.branches.items():
493 #chs = chs.split(':')[-1]
498 #chs = chs.split(':')[-1]
494 branches_group[0].append((chs, name),)
499 branches_group[0].append((chs, name),)
495 hist_l.append(branches_group)
500 hist_l.append(branches_group)
496
501
497 for name, chs in c.rhodecode_repo.tags.items():
502 for name, chs in c.rhodecode_repo.tags.items():
498 #chs = chs.split(':')[-1]
503 #chs = chs.split(':')[-1]
499 tags_group[0].append((chs, name),)
504 tags_group[0].append((chs, name),)
500 hist_l.append(tags_group)
505 hist_l.append(tags_group)
501
506
502 return hist_l
507 return hist_l
503
508
504 @jsonify
509 @jsonify
505 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
510 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
506 'repository.admin')
511 'repository.admin')
507 def nodelist(self, repo_name, revision, f_path):
512 def nodelist(self, repo_name, revision, f_path):
508 if request.environ.get('HTTP_X_PARTIAL_XHR'):
513 if request.environ.get('HTTP_X_PARTIAL_XHR'):
509 cs = self.__get_cs_or_redirect(revision, repo_name)
514 cs = self.__get_cs_or_redirect(revision, repo_name)
510 _d, _f = self.__get_paths(cs, f_path)
515 _d, _f = self.__get_paths(cs, f_path)
511 return _d + _f
516 return _d + _f
512
517
General Comments 0
You need to be logged in to leave comments. Login now