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