##// END OF EJS Templates
fixed issue #671 commenting on pull requests sometimes used old JSON encoder and broke. This changeset replaces it's with RhodeCode json encoder to ensure all data is properly serializable
marcink -
r3061:7727faad beta
parent child Browse files
Show More
@@ -1,391 +1,390
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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 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 from collections import defaultdict
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden, HTTPBadRequest
29 from webob.exc import HTTPForbidden, HTTPBadRequest
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
34 from rhodecode.lib.utils import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
39
38
40 import rhodecode.lib.helpers as h
39 import rhodecode.lib.helpers as h
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import action_logger
42 from rhodecode.lib.utils import action_logger
44 from rhodecode.lib.compat import OrderedDict
43 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
44 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
45 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.comment import ChangesetCommentsModel
46 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.lib.diffs import LimitedDiffContainer
50 from rhodecode.lib.diffs import LimitedDiffContainer
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
51 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
53 from rhodecode.lib.vcs.backends.base import EmptyChangeset
52 from rhodecode.lib.vcs.backends.base import EmptyChangeset
54 from rhodecode.lib.utils2 import safe_unicode
53 from rhodecode.lib.utils2 import safe_unicode
55
54
56 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
57
56
58
57
59 def _update_with_GET(params, GET):
58 def _update_with_GET(params, GET):
60 for k in ['diff1', 'diff2', 'diff']:
59 for k in ['diff1', 'diff2', 'diff']:
61 params[k] += GET.getall(k)
60 params[k] += GET.getall(k)
62
61
63
62
64 def anchor_url(revision, path, GET):
63 def anchor_url(revision, path, GET):
65 fid = h.FID(revision, path)
64 fid = h.FID(revision, path)
66 return h.url.current(anchor=fid, **dict(GET))
65 return h.url.current(anchor=fid, **dict(GET))
67
66
68
67
69 def get_ignore_ws(fid, GET):
68 def get_ignore_ws(fid, GET):
70 ig_ws_global = GET.get('ignorews')
69 ig_ws_global = GET.get('ignorews')
71 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
70 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
72 if ig_ws:
71 if ig_ws:
73 try:
72 try:
74 return int(ig_ws[0].split(':')[-1])
73 return int(ig_ws[0].split(':')[-1])
75 except:
74 except:
76 pass
75 pass
77 return ig_ws_global
76 return ig_ws_global
78
77
79
78
80 def _ignorews_url(GET, fileid=None):
79 def _ignorews_url(GET, fileid=None):
81 fileid = str(fileid) if fileid else None
80 fileid = str(fileid) if fileid else None
82 params = defaultdict(list)
81 params = defaultdict(list)
83 _update_with_GET(params, GET)
82 _update_with_GET(params, GET)
84 lbl = _('show white space')
83 lbl = _('show white space')
85 ig_ws = get_ignore_ws(fileid, GET)
84 ig_ws = get_ignore_ws(fileid, GET)
86 ln_ctx = get_line_ctx(fileid, GET)
85 ln_ctx = get_line_ctx(fileid, GET)
87 # global option
86 # global option
88 if fileid is None:
87 if fileid is None:
89 if ig_ws is None:
88 if ig_ws is None:
90 params['ignorews'] += [1]
89 params['ignorews'] += [1]
91 lbl = _('ignore white space')
90 lbl = _('ignore white space')
92 ctx_key = 'context'
91 ctx_key = 'context'
93 ctx_val = ln_ctx
92 ctx_val = ln_ctx
94 # per file options
93 # per file options
95 else:
94 else:
96 if ig_ws is None:
95 if ig_ws is None:
97 params[fileid] += ['WS:1']
96 params[fileid] += ['WS:1']
98 lbl = _('ignore white space')
97 lbl = _('ignore white space')
99
98
100 ctx_key = fileid
99 ctx_key = fileid
101 ctx_val = 'C:%s' % ln_ctx
100 ctx_val = 'C:%s' % ln_ctx
102 # if we have passed in ln_ctx pass it along to our params
101 # if we have passed in ln_ctx pass it along to our params
103 if ln_ctx:
102 if ln_ctx:
104 params[ctx_key] += [ctx_val]
103 params[ctx_key] += [ctx_val]
105
104
106 params['anchor'] = fileid
105 params['anchor'] = fileid
107 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
106 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
108 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
107 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
109
108
110
109
111 def get_line_ctx(fid, GET):
110 def get_line_ctx(fid, GET):
112 ln_ctx_global = GET.get('context')
111 ln_ctx_global = GET.get('context')
113 if fid:
112 if fid:
114 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
113 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
115 else:
114 else:
116 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
115 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
117 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
116 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
118 if ln_ctx:
117 if ln_ctx:
119 ln_ctx = [ln_ctx]
118 ln_ctx = [ln_ctx]
120
119
121 if ln_ctx:
120 if ln_ctx:
122 retval = ln_ctx[0].split(':')[-1]
121 retval = ln_ctx[0].split(':')[-1]
123 else:
122 else:
124 retval = ln_ctx_global
123 retval = ln_ctx_global
125
124
126 try:
125 try:
127 return int(retval)
126 return int(retval)
128 except:
127 except:
129 return 3
128 return 3
130
129
131
130
132 def _context_url(GET, fileid=None):
131 def _context_url(GET, fileid=None):
133 """
132 """
134 Generates url for context lines
133 Generates url for context lines
135
134
136 :param fileid:
135 :param fileid:
137 """
136 """
138
137
139 fileid = str(fileid) if fileid else None
138 fileid = str(fileid) if fileid else None
140 ig_ws = get_ignore_ws(fileid, GET)
139 ig_ws = get_ignore_ws(fileid, GET)
141 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
140 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
142
141
143 params = defaultdict(list)
142 params = defaultdict(list)
144 _update_with_GET(params, GET)
143 _update_with_GET(params, GET)
145
144
146 # global option
145 # global option
147 if fileid is None:
146 if fileid is None:
148 if ln_ctx > 0:
147 if ln_ctx > 0:
149 params['context'] += [ln_ctx]
148 params['context'] += [ln_ctx]
150
149
151 if ig_ws:
150 if ig_ws:
152 ig_ws_key = 'ignorews'
151 ig_ws_key = 'ignorews'
153 ig_ws_val = 1
152 ig_ws_val = 1
154
153
155 # per file option
154 # per file option
156 else:
155 else:
157 params[fileid] += ['C:%s' % ln_ctx]
156 params[fileid] += ['C:%s' % ln_ctx]
158 ig_ws_key = fileid
157 ig_ws_key = fileid
159 ig_ws_val = 'WS:%s' % 1
158 ig_ws_val = 'WS:%s' % 1
160
159
161 if ig_ws:
160 if ig_ws:
162 params[ig_ws_key] += [ig_ws_val]
161 params[ig_ws_key] += [ig_ws_val]
163
162
164 lbl = _('%s line context') % ln_ctx
163 lbl = _('%s line context') % ln_ctx
165
164
166 params['anchor'] = fileid
165 params['anchor'] = fileid
167 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
166 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
168 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
167 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
169
168
170
169
171 class ChangesetController(BaseRepoController):
170 class ChangesetController(BaseRepoController):
172
171
173 @LoginRequired()
172 @LoginRequired()
174 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
173 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
175 'repository.admin')
174 'repository.admin')
176 def __before__(self):
175 def __before__(self):
177 super(ChangesetController, self).__before__()
176 super(ChangesetController, self).__before__()
178 c.affected_files_cut_off = 60
177 c.affected_files_cut_off = 60
179 repo_model = RepoModel()
178 repo_model = RepoModel()
180 c.users_array = repo_model.get_users_js()
179 c.users_array = repo_model.get_users_js()
181 c.users_groups_array = repo_model.get_users_groups_js()
180 c.users_groups_array = repo_model.get_users_groups_js()
182
181
183 def index(self, revision, method='show'):
182 def index(self, revision, method='show'):
184 c.anchor_url = anchor_url
183 c.anchor_url = anchor_url
185 c.ignorews_url = _ignorews_url
184 c.ignorews_url = _ignorews_url
186 c.context_url = _context_url
185 c.context_url = _context_url
187 c.fulldiff = fulldiff = request.GET.get('fulldiff')
186 c.fulldiff = fulldiff = request.GET.get('fulldiff')
188 #get ranges of revisions if preset
187 #get ranges of revisions if preset
189 rev_range = revision.split('...')[:2]
188 rev_range = revision.split('...')[:2]
190 enable_comments = True
189 enable_comments = True
191 try:
190 try:
192 if len(rev_range) == 2:
191 if len(rev_range) == 2:
193 enable_comments = False
192 enable_comments = False
194 rev_start = rev_range[0]
193 rev_start = rev_range[0]
195 rev_end = rev_range[1]
194 rev_end = rev_range[1]
196 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
195 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
197 end=rev_end)
196 end=rev_end)
198 else:
197 else:
199 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
198 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
200
199
201 c.cs_ranges = list(rev_ranges)
200 c.cs_ranges = list(rev_ranges)
202 if not c.cs_ranges:
201 if not c.cs_ranges:
203 raise RepositoryError('Changeset range returned empty result')
202 raise RepositoryError('Changeset range returned empty result')
204
203
205 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
204 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
206 log.error(traceback.format_exc())
205 log.error(traceback.format_exc())
207 h.flash(str(e), category='warning')
206 h.flash(str(e), category='warning')
208 return redirect(url('home'))
207 return redirect(url('home'))
209
208
210 c.changes = OrderedDict()
209 c.changes = OrderedDict()
211
210
212 c.lines_added = 0 # count of lines added
211 c.lines_added = 0 # count of lines added
213 c.lines_deleted = 0 # count of lines removes
212 c.lines_deleted = 0 # count of lines removes
214
213
215 c.changeset_statuses = ChangesetStatus.STATUSES
214 c.changeset_statuses = ChangesetStatus.STATUSES
216 c.comments = []
215 c.comments = []
217 c.statuses = []
216 c.statuses = []
218 c.inline_comments = []
217 c.inline_comments = []
219 c.inline_cnt = 0
218 c.inline_cnt = 0
220
219
221 # Iterate over ranges (default changeset view is always one changeset)
220 # Iterate over ranges (default changeset view is always one changeset)
222 for changeset in c.cs_ranges:
221 for changeset in c.cs_ranges:
223 inlines = []
222 inlines = []
224 if method == 'show':
223 if method == 'show':
225 c.statuses.extend([ChangesetStatusModel()\
224 c.statuses.extend([ChangesetStatusModel()\
226 .get_status(c.rhodecode_db_repo.repo_id,
225 .get_status(c.rhodecode_db_repo.repo_id,
227 changeset.raw_id)])
226 changeset.raw_id)])
228
227
229 c.comments.extend(ChangesetCommentsModel()\
228 c.comments.extend(ChangesetCommentsModel()\
230 .get_comments(c.rhodecode_db_repo.repo_id,
229 .get_comments(c.rhodecode_db_repo.repo_id,
231 revision=changeset.raw_id))
230 revision=changeset.raw_id))
232 inlines = ChangesetCommentsModel()\
231 inlines = ChangesetCommentsModel()\
233 .get_inline_comments(c.rhodecode_db_repo.repo_id,
232 .get_inline_comments(c.rhodecode_db_repo.repo_id,
234 revision=changeset.raw_id)
233 revision=changeset.raw_id)
235 c.inline_comments.extend(inlines)
234 c.inline_comments.extend(inlines)
236
235
237 c.changes[changeset.raw_id] = []
236 c.changes[changeset.raw_id] = []
238
237
239 cs2 = changeset.raw_id
238 cs2 = changeset.raw_id
240 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
239 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
241 context_lcl = get_line_ctx('', request.GET)
240 context_lcl = get_line_ctx('', request.GET)
242 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
241 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
243
242
244 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
243 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
245 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
244 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
246 diff_limit = self.cut_off_limit if not fulldiff else None
245 diff_limit = self.cut_off_limit if not fulldiff else None
247 diff_processor = diffs.DiffProcessor(_diff,
246 diff_processor = diffs.DiffProcessor(_diff,
248 vcs=c.rhodecode_repo.alias,
247 vcs=c.rhodecode_repo.alias,
249 format='gitdiff',
248 format='gitdiff',
250 diff_limit=diff_limit)
249 diff_limit=diff_limit)
251 cs_changes = OrderedDict()
250 cs_changes = OrderedDict()
252 if method == 'show':
251 if method == 'show':
253 _parsed = diff_processor.prepare()
252 _parsed = diff_processor.prepare()
254 c.limited_diff = False
253 c.limited_diff = False
255 if isinstance(_parsed, LimitedDiffContainer):
254 if isinstance(_parsed, LimitedDiffContainer):
256 c.limited_diff = True
255 c.limited_diff = True
257 for f in _parsed:
256 for f in _parsed:
258 st = f['stats']
257 st = f['stats']
259 if st[0] != 'b':
258 if st[0] != 'b':
260 c.lines_added += st[0]
259 c.lines_added += st[0]
261 c.lines_deleted += st[1]
260 c.lines_deleted += st[1]
262 fid = h.FID(changeset.raw_id, f['filename'])
261 fid = h.FID(changeset.raw_id, f['filename'])
263 diff = diff_processor.as_html(enable_comments=enable_comments,
262 diff = diff_processor.as_html(enable_comments=enable_comments,
264 parsed_lines=[f])
263 parsed_lines=[f])
265 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
264 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
266 diff, st]
265 diff, st]
267 else:
266 else:
268 # downloads/raw we only need RAW diff nothing else
267 # downloads/raw we only need RAW diff nothing else
269 diff = diff_processor.as_raw()
268 diff = diff_processor.as_raw()
270 cs_changes[''] = [None, None, None, None, diff, None]
269 cs_changes[''] = [None, None, None, None, diff, None]
271 c.changes[changeset.raw_id] = cs_changes
270 c.changes[changeset.raw_id] = cs_changes
272
271
273 # count inline comments
272 # count inline comments
274 for __, lines in c.inline_comments:
273 for __, lines in c.inline_comments:
275 for comments in lines.values():
274 for comments in lines.values():
276 c.inline_cnt += len(comments)
275 c.inline_cnt += len(comments)
277
276
278 if len(c.cs_ranges) == 1:
277 if len(c.cs_ranges) == 1:
279 c.changeset = c.cs_ranges[0]
278 c.changeset = c.cs_ranges[0]
280 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
279 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
281 for x in c.changeset.parents])
280 for x in c.changeset.parents])
282 if method == 'download':
281 if method == 'download':
283 response.content_type = 'text/plain'
282 response.content_type = 'text/plain'
284 response.content_disposition = 'attachment; filename=%s.diff' \
283 response.content_disposition = 'attachment; filename=%s.diff' \
285 % revision[:12]
284 % revision[:12]
286 return diff
285 return diff
287 elif method == 'patch':
286 elif method == 'patch':
288 response.content_type = 'text/plain'
287 response.content_type = 'text/plain'
289 c.diff = safe_unicode(diff)
288 c.diff = safe_unicode(diff)
290 return render('changeset/patch_changeset.html')
289 return render('changeset/patch_changeset.html')
291 elif method == 'raw':
290 elif method == 'raw':
292 response.content_type = 'text/plain'
291 response.content_type = 'text/plain'
293 return diff
292 return diff
294 elif method == 'show':
293 elif method == 'show':
295 if len(c.cs_ranges) == 1:
294 if len(c.cs_ranges) == 1:
296 return render('changeset/changeset.html')
295 return render('changeset/changeset.html')
297 else:
296 else:
298 return render('changeset/changeset_range.html')
297 return render('changeset/changeset_range.html')
299
298
300 def changeset_raw(self, revision):
299 def changeset_raw(self, revision):
301 return self.index(revision, method='raw')
300 return self.index(revision, method='raw')
302
301
303 def changeset_patch(self, revision):
302 def changeset_patch(self, revision):
304 return self.index(revision, method='patch')
303 return self.index(revision, method='patch')
305
304
306 def changeset_download(self, revision):
305 def changeset_download(self, revision):
307 return self.index(revision, method='download')
306 return self.index(revision, method='download')
308
307
309 @jsonify
308 @jsonify
310 def comment(self, repo_name, revision):
309 def comment(self, repo_name, revision):
311 status = request.POST.get('changeset_status')
310 status = request.POST.get('changeset_status')
312 change_status = request.POST.get('change_changeset_status')
311 change_status = request.POST.get('change_changeset_status')
313 text = request.POST.get('text')
312 text = request.POST.get('text')
314 if status and change_status:
313 if status and change_status:
315 text = text or (_('Status change -> %s')
314 text = text or (_('Status change -> %s')
316 % ChangesetStatus.get_status_lbl(status))
315 % ChangesetStatus.get_status_lbl(status))
317
316
318 comm = ChangesetCommentsModel().create(
317 comm = ChangesetCommentsModel().create(
319 text=text,
318 text=text,
320 repo=c.rhodecode_db_repo.repo_id,
319 repo=c.rhodecode_db_repo.repo_id,
321 user=c.rhodecode_user.user_id,
320 user=c.rhodecode_user.user_id,
322 revision=revision,
321 revision=revision,
323 f_path=request.POST.get('f_path'),
322 f_path=request.POST.get('f_path'),
324 line_no=request.POST.get('line'),
323 line_no=request.POST.get('line'),
325 status_change=(ChangesetStatus.get_status_lbl(status)
324 status_change=(ChangesetStatus.get_status_lbl(status)
326 if status and change_status else None)
325 if status and change_status else None)
327 )
326 )
328
327
329 # get status if set !
328 # get status if set !
330 if status and change_status:
329 if status and change_status:
331 # if latest status was from pull request and it's closed
330 # if latest status was from pull request and it's closed
332 # disallow changing status !
331 # disallow changing status !
333 # dont_allow_on_closed_pull_request = True !
332 # dont_allow_on_closed_pull_request = True !
334
333
335 try:
334 try:
336 ChangesetStatusModel().set_status(
335 ChangesetStatusModel().set_status(
337 c.rhodecode_db_repo.repo_id,
336 c.rhodecode_db_repo.repo_id,
338 status,
337 status,
339 c.rhodecode_user.user_id,
338 c.rhodecode_user.user_id,
340 comm,
339 comm,
341 revision=revision,
340 revision=revision,
342 dont_allow_on_closed_pull_request=True
341 dont_allow_on_closed_pull_request=True
343 )
342 )
344 except StatusChangeOnClosedPullRequestError:
343 except StatusChangeOnClosedPullRequestError:
345 log.error(traceback.format_exc())
344 log.error(traceback.format_exc())
346 msg = _('Changing status on a changeset associated with'
345 msg = _('Changing status on a changeset associated with'
347 'a closed pull request is not allowed')
346 'a closed pull request is not allowed')
348 h.flash(msg, category='warning')
347 h.flash(msg, category='warning')
349 return redirect(h.url('changeset_home', repo_name=repo_name,
348 return redirect(h.url('changeset_home', repo_name=repo_name,
350 revision=revision))
349 revision=revision))
351 action_logger(self.rhodecode_user,
350 action_logger(self.rhodecode_user,
352 'user_commented_revision:%s' % revision,
351 'user_commented_revision:%s' % revision,
353 c.rhodecode_db_repo, self.ip_addr, self.sa)
352 c.rhodecode_db_repo, self.ip_addr, self.sa)
354
353
355 Session().commit()
354 Session().commit()
356
355
357 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
356 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
358 return redirect(h.url('changeset_home', repo_name=repo_name,
357 return redirect(h.url('changeset_home', repo_name=repo_name,
359 revision=revision))
358 revision=revision))
360
359
361 data = {
360 data = {
362 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
361 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
363 }
362 }
364 if comm:
363 if comm:
365 c.co = comm
364 c.co = comm
366 data.update(comm.get_dict())
365 data.update(comm.get_dict())
367 data.update({'rendered_text':
366 data.update({'rendered_text':
368 render('changeset/changeset_comment_block.html')})
367 render('changeset/changeset_comment_block.html')})
369
368
370 return data
369 return data
371
370
372 @jsonify
371 @jsonify
373 def delete_comment(self, repo_name, comment_id):
372 def delete_comment(self, repo_name, comment_id):
374 co = ChangesetComment.get(comment_id)
373 co = ChangesetComment.get(comment_id)
375 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
374 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
376 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
375 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
377 ChangesetCommentsModel().delete(comment=co)
376 ChangesetCommentsModel().delete(comment=co)
378 Session().commit()
377 Session().commit()
379 return True
378 return True
380 else:
379 else:
381 raise HTTPForbidden()
380 raise HTTPForbidden()
382
381
383 @jsonify
382 @jsonify
384 def changeset_info(self, repo_name, revision):
383 def changeset_info(self, repo_name, revision):
385 if request.is_xhr:
384 if request.is_xhr:
386 try:
385 try:
387 return c.rhodecode_repo.get_changeset(revision)
386 return c.rhodecode_repo.get_changeset(revision)
388 except ChangesetDoesNotExistError, e:
387 except ChangesetDoesNotExistError, e:
389 return EmptyChangeset(message=str(e))
388 return EmptyChangeset(message=str(e))
390 else:
389 else:
391 raise HTTPBadRequest()
390 raise HTTPBadRequest()
@@ -1,594 +1,594
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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import tempfile
29 import tempfile
30
30
31 from pylons import request, response, tmpl_context as c, url
31 from pylons import request, response, tmpl_context as c, url
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
34 from rhodecode.lib.utils import jsonify
35
35
36 from rhodecode.lib import diffs
36 from rhodecode.lib import diffs
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38
38
39 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
40 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
41 str2bool
41 str2bool
42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.conf import settings
45 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.exceptions import RepositoryError, \
46 from rhodecode.lib.vcs.exceptions import RepositoryError, \
47 ChangesetDoesNotExistError, EmptyRepositoryError, \
47 ChangesetDoesNotExistError, EmptyRepositoryError, \
48 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
48 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
49 NodeDoesNotExistError, ChangesetError, NodeError
49 NodeDoesNotExistError, ChangesetError, NodeError
50 from rhodecode.lib.vcs.nodes import FileNode
50 from rhodecode.lib.vcs.nodes import FileNode
51
51
52 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.db import Repository
54 from rhodecode.model.db import Repository
55
55
56 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
56 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
57 _context_url, get_line_ctx, get_ignore_ws
57 _context_url, get_line_ctx, get_ignore_ws
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class FilesController(BaseRepoController):
63 class FilesController(BaseRepoController):
64
64
65 def __before__(self):
65 def __before__(self):
66 super(FilesController, self).__before__()
66 super(FilesController, self).__before__()
67 c.cut_off_limit = self.cut_off_limit
67 c.cut_off_limit = self.cut_off_limit
68
68
69 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
69 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
70 """
70 """
71 Safe way to get changeset if error occur it redirects to tip with
71 Safe way to get changeset if error occur it redirects to tip with
72 proper message
72 proper message
73
73
74 :param rev: revision to fetch
74 :param rev: revision to fetch
75 :param repo_name: repo name to redirect after
75 :param repo_name: repo name to redirect after
76 """
76 """
77
77
78 try:
78 try:
79 return c.rhodecode_repo.get_changeset(rev)
79 return c.rhodecode_repo.get_changeset(rev)
80 except EmptyRepositoryError, e:
80 except EmptyRepositoryError, e:
81 if not redirect_after:
81 if not redirect_after:
82 return None
82 return None
83 url_ = url('files_add_home',
83 url_ = url('files_add_home',
84 repo_name=c.repo_name,
84 repo_name=c.repo_name,
85 revision=0, f_path='')
85 revision=0, f_path='')
86 add_new = '<a href="%s">[%s]</a>' % (url_, _('click here to add new file'))
86 add_new = '<a href="%s">[%s]</a>' % (url_, _('click here to add new file'))
87 h.flash(h.literal(_('There are no files yet %s') % add_new),
87 h.flash(h.literal(_('There are no files yet %s') % add_new),
88 category='warning')
88 category='warning')
89 redirect(h.url('summary_home', repo_name=repo_name))
89 redirect(h.url('summary_home', repo_name=repo_name))
90
90
91 except RepositoryError, e:
91 except RepositoryError, e:
92 h.flash(str(e), category='warning')
92 h.flash(str(e), category='warning')
93 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
93 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
94
94
95 def __get_filenode_or_redirect(self, repo_name, cs, path):
95 def __get_filenode_or_redirect(self, repo_name, cs, path):
96 """
96 """
97 Returns file_node, if error occurs or given path is directory,
97 Returns file_node, if error occurs or given path is directory,
98 it'll redirect to top level path
98 it'll redirect to top level path
99
99
100 :param repo_name: repo_name
100 :param repo_name: repo_name
101 :param cs: given changeset
101 :param cs: given changeset
102 :param path: path to lookup
102 :param path: path to lookup
103 """
103 """
104
104
105 try:
105 try:
106 file_node = cs.get_node(path)
106 file_node = cs.get_node(path)
107 if file_node.is_dir():
107 if file_node.is_dir():
108 raise RepositoryError('given path is a directory')
108 raise RepositoryError('given path is a directory')
109 except RepositoryError, e:
109 except RepositoryError, e:
110 h.flash(str(e), category='warning')
110 h.flash(str(e), category='warning')
111 redirect(h.url('files_home', repo_name=repo_name,
111 redirect(h.url('files_home', repo_name=repo_name,
112 revision=cs.raw_id))
112 revision=cs.raw_id))
113
113
114 return file_node
114 return file_node
115
115
116 @LoginRequired()
116 @LoginRequired()
117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 'repository.admin')
118 'repository.admin')
119 def index(self, repo_name, revision, f_path, annotate=False):
119 def index(self, repo_name, revision, f_path, annotate=False):
120 # redirect to given revision from form if given
120 # redirect to given revision from form if given
121 post_revision = request.POST.get('at_rev', None)
121 post_revision = request.POST.get('at_rev', None)
122 if post_revision:
122 if post_revision:
123 cs = self.__get_cs_or_redirect(post_revision, repo_name)
123 cs = self.__get_cs_or_redirect(post_revision, repo_name)
124 redirect(url('files_home', repo_name=c.repo_name,
124 redirect(url('files_home', repo_name=c.repo_name,
125 revision=cs.raw_id, f_path=f_path))
125 revision=cs.raw_id, f_path=f_path))
126
126
127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
128 c.branch = request.GET.get('branch', None)
128 c.branch = request.GET.get('branch', None)
129 c.f_path = f_path
129 c.f_path = f_path
130 c.annotate = annotate
130 c.annotate = annotate
131 cur_rev = c.changeset.revision
131 cur_rev = c.changeset.revision
132
132
133 # prev link
133 # prev link
134 try:
134 try:
135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 c.url_prev = url('files_home', repo_name=c.repo_name,
136 c.url_prev = url('files_home', repo_name=c.repo_name,
137 revision=prev_rev.raw_id, f_path=f_path)
137 revision=prev_rev.raw_id, f_path=f_path)
138 if c.branch:
138 if c.branch:
139 c.url_prev += '?branch=%s' % c.branch
139 c.url_prev += '?branch=%s' % c.branch
140 except (ChangesetDoesNotExistError, VCSError):
140 except (ChangesetDoesNotExistError, VCSError):
141 c.url_prev = '#'
141 c.url_prev = '#'
142
142
143 # next link
143 # next link
144 try:
144 try:
145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 c.url_next = url('files_home', repo_name=c.repo_name,
146 c.url_next = url('files_home', repo_name=c.repo_name,
147 revision=next_rev.raw_id, f_path=f_path)
147 revision=next_rev.raw_id, f_path=f_path)
148 if c.branch:
148 if c.branch:
149 c.url_next += '?branch=%s' % c.branch
149 c.url_next += '?branch=%s' % c.branch
150 except (ChangesetDoesNotExistError, VCSError):
150 except (ChangesetDoesNotExistError, VCSError):
151 c.url_next = '#'
151 c.url_next = '#'
152
152
153 # files or dirs
153 # files or dirs
154 try:
154 try:
155 c.file = c.changeset.get_node(f_path)
155 c.file = c.changeset.get_node(f_path)
156
156
157 if c.file.is_file():
157 if c.file.is_file():
158 c.load_full_history = False
158 c.load_full_history = False
159 file_last_cs = c.file.last_changeset
159 file_last_cs = c.file.last_changeset
160 c.file_changeset = (c.changeset
160 c.file_changeset = (c.changeset
161 if c.changeset.revision < file_last_cs.revision
161 if c.changeset.revision < file_last_cs.revision
162 else file_last_cs)
162 else file_last_cs)
163 _hist = []
163 _hist = []
164 c.file_history = []
164 c.file_history = []
165 if c.load_full_history:
165 if c.load_full_history:
166 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
166 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
167
167
168 c.authors = []
168 c.authors = []
169 for a in set([x.author for x in _hist]):
169 for a in set([x.author for x in _hist]):
170 c.authors.append((h.email(a), h.person(a)))
170 c.authors.append((h.email(a), h.person(a)))
171 else:
171 else:
172 c.authors = c.file_history = []
172 c.authors = c.file_history = []
173 except RepositoryError, e:
173 except RepositoryError, e:
174 h.flash(str(e), category='warning')
174 h.flash(str(e), category='warning')
175 redirect(h.url('files_home', repo_name=repo_name,
175 redirect(h.url('files_home', repo_name=repo_name,
176 revision='tip'))
176 revision='tip'))
177
177
178 if request.environ.get('HTTP_X_PARTIAL_XHR'):
178 if request.environ.get('HTTP_X_PARTIAL_XHR'):
179 return render('files/files_ypjax.html')
179 return render('files/files_ypjax.html')
180
180
181 return render('files/files.html')
181 return render('files/files.html')
182
182
183 def history(self, repo_name, revision, f_path, annotate=False):
183 def history(self, repo_name, revision, f_path, annotate=False):
184 if request.environ.get('HTTP_X_PARTIAL_XHR'):
184 if request.environ.get('HTTP_X_PARTIAL_XHR'):
185 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
185 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
186 c.f_path = f_path
186 c.f_path = f_path
187 c.annotate = annotate
187 c.annotate = annotate
188 c.file = c.changeset.get_node(f_path)
188 c.file = c.changeset.get_node(f_path)
189 if c.file.is_file():
189 if c.file.is_file():
190 file_last_cs = c.file.last_changeset
190 file_last_cs = c.file.last_changeset
191 c.file_changeset = (c.changeset
191 c.file_changeset = (c.changeset
192 if c.changeset.revision < file_last_cs.revision
192 if c.changeset.revision < file_last_cs.revision
193 else file_last_cs)
193 else file_last_cs)
194 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
194 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
195 c.authors = []
195 c.authors = []
196 for a in set([x.author for x in _hist]):
196 for a in set([x.author for x in _hist]):
197 c.authors.append((h.email(a), h.person(a)))
197 c.authors.append((h.email(a), h.person(a)))
198 return render('files/files_history_box.html')
198 return render('files/files_history_box.html')
199
199
200 @LoginRequired()
200 @LoginRequired()
201 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
201 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
202 'repository.admin')
202 'repository.admin')
203 def rawfile(self, repo_name, revision, f_path):
203 def rawfile(self, repo_name, revision, f_path):
204 cs = self.__get_cs_or_redirect(revision, repo_name)
204 cs = self.__get_cs_or_redirect(revision, repo_name)
205 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
205 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
206
206
207 response.content_disposition = 'attachment; filename=%s' % \
207 response.content_disposition = 'attachment; filename=%s' % \
208 safe_str(f_path.split(Repository.url_sep())[-1])
208 safe_str(f_path.split(Repository.url_sep())[-1])
209
209
210 response.content_type = file_node.mimetype
210 response.content_type = file_node.mimetype
211 return file_node.content
211 return file_node.content
212
212
213 @LoginRequired()
213 @LoginRequired()
214 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
214 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
215 'repository.admin')
215 'repository.admin')
216 def raw(self, repo_name, revision, f_path):
216 def raw(self, repo_name, revision, f_path):
217 cs = self.__get_cs_or_redirect(revision, repo_name)
217 cs = self.__get_cs_or_redirect(revision, repo_name)
218 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
218 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
219
219
220 raw_mimetype_mapping = {
220 raw_mimetype_mapping = {
221 # map original mimetype to a mimetype used for "show as raw"
221 # map original mimetype to a mimetype used for "show as raw"
222 # you can also provide a content-disposition to override the
222 # you can also provide a content-disposition to override the
223 # default "attachment" disposition.
223 # default "attachment" disposition.
224 # orig_type: (new_type, new_dispo)
224 # orig_type: (new_type, new_dispo)
225
225
226 # show images inline:
226 # show images inline:
227 'image/x-icon': ('image/x-icon', 'inline'),
227 'image/x-icon': ('image/x-icon', 'inline'),
228 'image/png': ('image/png', 'inline'),
228 'image/png': ('image/png', 'inline'),
229 'image/gif': ('image/gif', 'inline'),
229 'image/gif': ('image/gif', 'inline'),
230 'image/jpeg': ('image/jpeg', 'inline'),
230 'image/jpeg': ('image/jpeg', 'inline'),
231 'image/svg+xml': ('image/svg+xml', 'inline'),
231 'image/svg+xml': ('image/svg+xml', 'inline'),
232 }
232 }
233
233
234 mimetype = file_node.mimetype
234 mimetype = file_node.mimetype
235 try:
235 try:
236 mimetype, dispo = raw_mimetype_mapping[mimetype]
236 mimetype, dispo = raw_mimetype_mapping[mimetype]
237 except KeyError:
237 except KeyError:
238 # we don't know anything special about this, handle it safely
238 # we don't know anything special about this, handle it safely
239 if file_node.is_binary:
239 if file_node.is_binary:
240 # do same as download raw for binary files
240 # do same as download raw for binary files
241 mimetype, dispo = 'application/octet-stream', 'attachment'
241 mimetype, dispo = 'application/octet-stream', 'attachment'
242 else:
242 else:
243 # do not just use the original mimetype, but force text/plain,
243 # do not just use the original mimetype, but force text/plain,
244 # otherwise it would serve text/html and that might be unsafe.
244 # otherwise it would serve text/html and that might be unsafe.
245 # Note: underlying vcs library fakes text/plain mimetype if the
245 # Note: underlying vcs library fakes text/plain mimetype if the
246 # mimetype can not be determined and it thinks it is not
246 # mimetype can not be determined and it thinks it is not
247 # binary.This might lead to erroneous text display in some
247 # binary.This might lead to erroneous text display in some
248 # cases, but helps in other cases, like with text files
248 # cases, but helps in other cases, like with text files
249 # without extension.
249 # without extension.
250 mimetype, dispo = 'text/plain', 'inline'
250 mimetype, dispo = 'text/plain', 'inline'
251
251
252 if dispo == 'attachment':
252 if dispo == 'attachment':
253 dispo = 'attachment; filename=%s' % \
253 dispo = 'attachment; filename=%s' % \
254 safe_str(f_path.split(os.sep)[-1])
254 safe_str(f_path.split(os.sep)[-1])
255
255
256 response.content_disposition = dispo
256 response.content_disposition = dispo
257 response.content_type = mimetype
257 response.content_type = mimetype
258 return file_node.content
258 return file_node.content
259
259
260 @LoginRequired()
260 @LoginRequired()
261 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
261 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
262 def edit(self, repo_name, revision, f_path):
262 def edit(self, repo_name, revision, f_path):
263 repo = Repository.get_by_repo_name(repo_name)
263 repo = Repository.get_by_repo_name(repo_name)
264 if repo.enable_locking and repo.locked[0]:
264 if repo.enable_locking and repo.locked[0]:
265 h.flash(_('This repository is has been locked by %s on %s')
265 h.flash(_('This repository is has been locked by %s on %s')
266 % (h.person_by_id(repo.locked[0]),
266 % (h.person_by_id(repo.locked[0]),
267 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
267 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
268 'warning')
268 'warning')
269 return redirect(h.url('files_home',
269 return redirect(h.url('files_home',
270 repo_name=repo_name, revision='tip'))
270 repo_name=repo_name, revision='tip'))
271
271
272 r_post = request.POST
272 r_post = request.POST
273
273
274 c.cs = self.__get_cs_or_redirect(revision, repo_name)
274 c.cs = self.__get_cs_or_redirect(revision, repo_name)
275 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
275 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
276
276
277 if c.file.is_binary:
277 if c.file.is_binary:
278 return redirect(url('files_home', repo_name=c.repo_name,
278 return redirect(url('files_home', repo_name=c.repo_name,
279 revision=c.cs.raw_id, f_path=f_path))
279 revision=c.cs.raw_id, f_path=f_path))
280
280
281 c.f_path = f_path
281 c.f_path = f_path
282
282
283 if r_post:
283 if r_post:
284
284
285 old_content = c.file.content
285 old_content = c.file.content
286 sl = old_content.splitlines(1)
286 sl = old_content.splitlines(1)
287 first_line = sl[0] if sl else ''
287 first_line = sl[0] if sl else ''
288 # modes: 0 - Unix, 1 - Mac, 2 - DOS
288 # modes: 0 - Unix, 1 - Mac, 2 - DOS
289 mode = detect_mode(first_line, 0)
289 mode = detect_mode(first_line, 0)
290 content = convert_line_endings(r_post.get('content'), mode)
290 content = convert_line_endings(r_post.get('content'), mode)
291
291
292 message = r_post.get('message') or (_('Edited %s via RhodeCode')
292 message = r_post.get('message') or (_('Edited %s via RhodeCode')
293 % (f_path))
293 % (f_path))
294 author = self.rhodecode_user.full_contact
294 author = self.rhodecode_user.full_contact
295
295
296 if content == old_content:
296 if content == old_content:
297 h.flash(_('No changes'),
297 h.flash(_('No changes'),
298 category='warning')
298 category='warning')
299 return redirect(url('changeset_home', repo_name=c.repo_name,
299 return redirect(url('changeset_home', repo_name=c.repo_name,
300 revision='tip'))
300 revision='tip'))
301
301
302 try:
302 try:
303 self.scm_model.commit_change(repo=c.rhodecode_repo,
303 self.scm_model.commit_change(repo=c.rhodecode_repo,
304 repo_name=repo_name, cs=c.cs,
304 repo_name=repo_name, cs=c.cs,
305 user=self.rhodecode_user,
305 user=self.rhodecode_user,
306 author=author, message=message,
306 author=author, message=message,
307 content=content, f_path=f_path)
307 content=content, f_path=f_path)
308 h.flash(_('Successfully committed to %s') % f_path,
308 h.flash(_('Successfully committed to %s') % f_path,
309 category='success')
309 category='success')
310
310
311 except Exception:
311 except Exception:
312 log.error(traceback.format_exc())
312 log.error(traceback.format_exc())
313 h.flash(_('Error occurred during commit'), category='error')
313 h.flash(_('Error occurred during commit'), category='error')
314 return redirect(url('changeset_home',
314 return redirect(url('changeset_home',
315 repo_name=c.repo_name, revision='tip'))
315 repo_name=c.repo_name, revision='tip'))
316
316
317 return render('files/files_edit.html')
317 return render('files/files_edit.html')
318
318
319 @LoginRequired()
319 @LoginRequired()
320 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
320 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
321 def add(self, repo_name, revision, f_path):
321 def add(self, repo_name, revision, f_path):
322
322
323 repo = Repository.get_by_repo_name(repo_name)
323 repo = Repository.get_by_repo_name(repo_name)
324 if repo.enable_locking and repo.locked[0]:
324 if repo.enable_locking and repo.locked[0]:
325 h.flash(_('This repository is has been locked by %s on %s')
325 h.flash(_('This repository is has been locked by %s on %s')
326 % (h.person_by_id(repo.locked[0]),
326 % (h.person_by_id(repo.locked[0]),
327 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
327 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
328 'warning')
328 'warning')
329 return redirect(h.url('files_home',
329 return redirect(h.url('files_home',
330 repo_name=repo_name, revision='tip'))
330 repo_name=repo_name, revision='tip'))
331
331
332 r_post = request.POST
332 r_post = request.POST
333 c.cs = self.__get_cs_or_redirect(revision, repo_name,
333 c.cs = self.__get_cs_or_redirect(revision, repo_name,
334 redirect_after=False)
334 redirect_after=False)
335 if c.cs is None:
335 if c.cs is None:
336 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
336 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
337
337
338 c.f_path = f_path
338 c.f_path = f_path
339
339
340 if r_post:
340 if r_post:
341 unix_mode = 0
341 unix_mode = 0
342 content = convert_line_endings(r_post.get('content'), unix_mode)
342 content = convert_line_endings(r_post.get('content'), unix_mode)
343
343
344 message = r_post.get('message') or (_('Added %s via RhodeCode')
344 message = r_post.get('message') or (_('Added %s via RhodeCode')
345 % (f_path))
345 % (f_path))
346 location = r_post.get('location')
346 location = r_post.get('location')
347 filename = r_post.get('filename')
347 filename = r_post.get('filename')
348 file_obj = r_post.get('upload_file', None)
348 file_obj = r_post.get('upload_file', None)
349
349
350 if file_obj is not None and hasattr(file_obj, 'filename'):
350 if file_obj is not None and hasattr(file_obj, 'filename'):
351 filename = file_obj.filename
351 filename = file_obj.filename
352 content = file_obj.file
352 content = file_obj.file
353
353
354 node_path = os.path.join(location, filename)
354 node_path = os.path.join(location, filename)
355 author = self.rhodecode_user.full_contact
355 author = self.rhodecode_user.full_contact
356
356
357 if not content:
357 if not content:
358 h.flash(_('No content'), category='warning')
358 h.flash(_('No content'), category='warning')
359 return redirect(url('changeset_home', repo_name=c.repo_name,
359 return redirect(url('changeset_home', repo_name=c.repo_name,
360 revision='tip'))
360 revision='tip'))
361 if not filename:
361 if not filename:
362 h.flash(_('No filename'), category='warning')
362 h.flash(_('No filename'), category='warning')
363 return redirect(url('changeset_home', repo_name=c.repo_name,
363 return redirect(url('changeset_home', repo_name=c.repo_name,
364 revision='tip'))
364 revision='tip'))
365
365
366 try:
366 try:
367 self.scm_model.create_node(repo=c.rhodecode_repo,
367 self.scm_model.create_node(repo=c.rhodecode_repo,
368 repo_name=repo_name, cs=c.cs,
368 repo_name=repo_name, cs=c.cs,
369 user=self.rhodecode_user,
369 user=self.rhodecode_user,
370 author=author, message=message,
370 author=author, message=message,
371 content=content, f_path=node_path)
371 content=content, f_path=node_path)
372 h.flash(_('Successfully committed to %s') % node_path,
372 h.flash(_('Successfully committed to %s') % node_path,
373 category='success')
373 category='success')
374 except NodeAlreadyExistsError, e:
374 except NodeAlreadyExistsError, e:
375 h.flash(_(e), category='error')
375 h.flash(_(e), category='error')
376 except Exception:
376 except Exception:
377 log.error(traceback.format_exc())
377 log.error(traceback.format_exc())
378 h.flash(_('Error occurred during commit'), category='error')
378 h.flash(_('Error occurred during commit'), category='error')
379 return redirect(url('changeset_home',
379 return redirect(url('changeset_home',
380 repo_name=c.repo_name, revision='tip'))
380 repo_name=c.repo_name, revision='tip'))
381
381
382 return render('files/files_add.html')
382 return render('files/files_add.html')
383
383
384 @LoginRequired()
384 @LoginRequired()
385 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
385 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
386 'repository.admin')
386 'repository.admin')
387 def archivefile(self, repo_name, fname):
387 def archivefile(self, repo_name, fname):
388
388
389 fileformat = None
389 fileformat = None
390 revision = None
390 revision = None
391 ext = None
391 ext = None
392 subrepos = request.GET.get('subrepos') == 'true'
392 subrepos = request.GET.get('subrepos') == 'true'
393
393
394 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
394 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
395 archive_spec = fname.split(ext_data[1])
395 archive_spec = fname.split(ext_data[1])
396 if len(archive_spec) == 2 and archive_spec[1] == '':
396 if len(archive_spec) == 2 and archive_spec[1] == '':
397 fileformat = a_type or ext_data[1]
397 fileformat = a_type or ext_data[1]
398 revision = archive_spec[0]
398 revision = archive_spec[0]
399 ext = ext_data[1]
399 ext = ext_data[1]
400
400
401 try:
401 try:
402 dbrepo = RepoModel().get_by_repo_name(repo_name)
402 dbrepo = RepoModel().get_by_repo_name(repo_name)
403 if dbrepo.enable_downloads is False:
403 if dbrepo.enable_downloads is False:
404 return _('downloads disabled')
404 return _('downloads disabled')
405
405
406 if c.rhodecode_repo.alias == 'hg':
406 if c.rhodecode_repo.alias == 'hg':
407 # patch and reset hooks section of UI config to not run any
407 # patch and reset hooks section of UI config to not run any
408 # hooks on fetching archives with subrepos
408 # hooks on fetching archives with subrepos
409 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
409 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
410 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
410 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
411
411
412 cs = c.rhodecode_repo.get_changeset(revision)
412 cs = c.rhodecode_repo.get_changeset(revision)
413 content_type = settings.ARCHIVE_SPECS[fileformat][0]
413 content_type = settings.ARCHIVE_SPECS[fileformat][0]
414 except ChangesetDoesNotExistError:
414 except ChangesetDoesNotExistError:
415 return _('Unknown revision %s') % revision
415 return _('Unknown revision %s') % revision
416 except EmptyRepositoryError:
416 except EmptyRepositoryError:
417 return _('Empty repository')
417 return _('Empty repository')
418 except (ImproperArchiveTypeError, KeyError):
418 except (ImproperArchiveTypeError, KeyError):
419 return _('Unknown archive type')
419 return _('Unknown archive type')
420
420
421 fd, archive = tempfile.mkstemp()
421 fd, archive = tempfile.mkstemp()
422 t = open(archive, 'wb')
422 t = open(archive, 'wb')
423 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
423 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
424 t.close()
424 t.close()
425
425
426 def get_chunked_archive(archive):
426 def get_chunked_archive(archive):
427 stream = open(archive, 'rb')
427 stream = open(archive, 'rb')
428 while True:
428 while True:
429 data = stream.read(16 * 1024)
429 data = stream.read(16 * 1024)
430 if not data:
430 if not data:
431 stream.close()
431 stream.close()
432 os.close(fd)
432 os.close(fd)
433 os.remove(archive)
433 os.remove(archive)
434 break
434 break
435 yield data
435 yield data
436
436
437 response.content_disposition = str('attachment; filename=%s-%s%s' \
437 response.content_disposition = str('attachment; filename=%s-%s%s' \
438 % (repo_name, revision[:12], ext))
438 % (repo_name, revision[:12], ext))
439 response.content_type = str(content_type)
439 response.content_type = str(content_type)
440 return get_chunked_archive(archive)
440 return get_chunked_archive(archive)
441
441
442 @LoginRequired()
442 @LoginRequired()
443 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
443 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
444 'repository.admin')
444 'repository.admin')
445 def diff(self, repo_name, f_path):
445 def diff(self, repo_name, f_path):
446 ignore_whitespace = request.GET.get('ignorews') == '1'
446 ignore_whitespace = request.GET.get('ignorews') == '1'
447 line_context = request.GET.get('context', 3)
447 line_context = request.GET.get('context', 3)
448 diff1 = request.GET.get('diff1', '')
448 diff1 = request.GET.get('diff1', '')
449 diff2 = request.GET.get('diff2', '')
449 diff2 = request.GET.get('diff2', '')
450 c.action = request.GET.get('diff')
450 c.action = request.GET.get('diff')
451 c.no_changes = diff1 == diff2
451 c.no_changes = diff1 == diff2
452 c.f_path = f_path
452 c.f_path = f_path
453 c.big_diff = False
453 c.big_diff = False
454 c.anchor_url = anchor_url
454 c.anchor_url = anchor_url
455 c.ignorews_url = _ignorews_url
455 c.ignorews_url = _ignorews_url
456 c.context_url = _context_url
456 c.context_url = _context_url
457 c.changes = OrderedDict()
457 c.changes = OrderedDict()
458 c.changes[diff2] = []
458 c.changes[diff2] = []
459
459
460 #special case if we want a show rev only, it's impl here
460 #special case if we want a show rev only, it's impl here
461 #to reduce JS and callbacks
461 #to reduce JS and callbacks
462
462
463 if request.GET.get('show_rev'):
463 if request.GET.get('show_rev'):
464 if str2bool(request.GET.get('annotate', 'False')):
464 if str2bool(request.GET.get('annotate', 'False')):
465 _url = url('files_annotate_home', repo_name=c.repo_name,
465 _url = url('files_annotate_home', repo_name=c.repo_name,
466 revision=diff1, f_path=c.f_path)
466 revision=diff1, f_path=c.f_path)
467 else:
467 else:
468 _url = url('files_home', repo_name=c.repo_name,
468 _url = url('files_home', repo_name=c.repo_name,
469 revision=diff1, f_path=c.f_path)
469 revision=diff1, f_path=c.f_path)
470
470
471 return redirect(_url)
471 return redirect(_url)
472 try:
472 try:
473 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
473 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
474 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
474 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
475 try:
475 try:
476 node1 = c.changeset_1.get_node(f_path)
476 node1 = c.changeset_1.get_node(f_path)
477 except NodeDoesNotExistError:
477 except NodeDoesNotExistError:
478 c.changeset_1 = EmptyChangeset(cs=diff1,
478 c.changeset_1 = EmptyChangeset(cs=diff1,
479 revision=c.changeset_1.revision,
479 revision=c.changeset_1.revision,
480 repo=c.rhodecode_repo)
480 repo=c.rhodecode_repo)
481 node1 = FileNode(f_path, '', changeset=c.changeset_1)
481 node1 = FileNode(f_path, '', changeset=c.changeset_1)
482 else:
482 else:
483 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
483 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
484 node1 = FileNode(f_path, '', changeset=c.changeset_1)
484 node1 = FileNode(f_path, '', changeset=c.changeset_1)
485
485
486 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
486 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
487 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
487 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
488 try:
488 try:
489 node2 = c.changeset_2.get_node(f_path)
489 node2 = c.changeset_2.get_node(f_path)
490 except NodeDoesNotExistError:
490 except NodeDoesNotExistError:
491 c.changeset_2 = EmptyChangeset(cs=diff2,
491 c.changeset_2 = EmptyChangeset(cs=diff2,
492 revision=c.changeset_2.revision,
492 revision=c.changeset_2.revision,
493 repo=c.rhodecode_repo)
493 repo=c.rhodecode_repo)
494 node2 = FileNode(f_path, '', changeset=c.changeset_2)
494 node2 = FileNode(f_path, '', changeset=c.changeset_2)
495 else:
495 else:
496 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
496 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
497 node2 = FileNode(f_path, '', changeset=c.changeset_2)
497 node2 = FileNode(f_path, '', changeset=c.changeset_2)
498 except (RepositoryError, NodeError):
498 except (RepositoryError, NodeError):
499 log.error(traceback.format_exc())
499 log.error(traceback.format_exc())
500 return redirect(url('files_home', repo_name=c.repo_name,
500 return redirect(url('files_home', repo_name=c.repo_name,
501 f_path=f_path))
501 f_path=f_path))
502
502
503 if c.action == 'download':
503 if c.action == 'download':
504 _diff = diffs.get_gitdiff(node1, node2,
504 _diff = diffs.get_gitdiff(node1, node2,
505 ignore_whitespace=ignore_whitespace,
505 ignore_whitespace=ignore_whitespace,
506 context=line_context)
506 context=line_context)
507 diff = diffs.DiffProcessor(_diff, format='gitdiff')
507 diff = diffs.DiffProcessor(_diff, format='gitdiff')
508
508
509 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
509 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
510 response.content_type = 'text/plain'
510 response.content_type = 'text/plain'
511 response.content_disposition = (
511 response.content_disposition = (
512 'attachment; filename=%s' % diff_name
512 'attachment; filename=%s' % diff_name
513 )
513 )
514 return diff.as_raw()
514 return diff.as_raw()
515
515
516 elif c.action == 'raw':
516 elif c.action == 'raw':
517 _diff = diffs.get_gitdiff(node1, node2,
517 _diff = diffs.get_gitdiff(node1, node2,
518 ignore_whitespace=ignore_whitespace,
518 ignore_whitespace=ignore_whitespace,
519 context=line_context)
519 context=line_context)
520 diff = diffs.DiffProcessor(_diff, format='gitdiff')
520 diff = diffs.DiffProcessor(_diff, format='gitdiff')
521 response.content_type = 'text/plain'
521 response.content_type = 'text/plain'
522 return diff.as_raw()
522 return diff.as_raw()
523
523
524 else:
524 else:
525 fid = h.FID(diff2, node2.path)
525 fid = h.FID(diff2, node2.path)
526 line_context_lcl = get_line_ctx(fid, request.GET)
526 line_context_lcl = get_line_ctx(fid, request.GET)
527 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
527 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
528
528
529 lim = request.GET.get('fulldiff') or self.cut_off_limit
529 lim = request.GET.get('fulldiff') or self.cut_off_limit
530 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
530 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
531 filenode_new=node2,
531 filenode_new=node2,
532 cut_off_limit=lim,
532 cut_off_limit=lim,
533 ignore_whitespace=ign_whitespace_lcl,
533 ignore_whitespace=ign_whitespace_lcl,
534 line_context=line_context_lcl,
534 line_context=line_context_lcl,
535 enable_comments=False)
535 enable_comments=False)
536 op = ''
536 op = ''
537 filename = node1.path
537 filename = node1.path
538 cs_changes = {
538 cs_changes = {
539 'fid': [cs1, cs2, op, filename, diff, st]
539 'fid': [cs1, cs2, op, filename, diff, st]
540 }
540 }
541 c.changes = cs_changes
541 c.changes = cs_changes
542
542
543 return render('files/file_diff.html')
543 return render('files/file_diff.html')
544
544
545 def _get_node_history(self, cs, f_path, changesets=None):
545 def _get_node_history(self, cs, f_path, changesets=None):
546 """
546 """
547 get changesets history for given node
547 get changesets history for given node
548
548
549 :param cs: changeset to calculate history
549 :param cs: changeset to calculate history
550 :param f_path: path for node to calculate history for
550 :param f_path: path for node to calculate history for
551 :param changesets: if passed don't calculate history and take
551 :param changesets: if passed don't calculate history and take
552 changesets defined in this list
552 changesets defined in this list
553 """
553 """
554 # calculate history based on tip
554 # calculate history based on tip
555 tip_cs = c.rhodecode_repo.get_changeset()
555 tip_cs = c.rhodecode_repo.get_changeset()
556 if changesets is None:
556 if changesets is None:
557 try:
557 try:
558 changesets = tip_cs.get_file_history(f_path)
558 changesets = tip_cs.get_file_history(f_path)
559 except (NodeDoesNotExistError, ChangesetError):
559 except (NodeDoesNotExistError, ChangesetError):
560 #this node is not present at tip !
560 #this node is not present at tip !
561 changesets = cs.get_file_history(f_path)
561 changesets = cs.get_file_history(f_path)
562 hist_l = []
562 hist_l = []
563
563
564 changesets_group = ([], _("Changesets"))
564 changesets_group = ([], _("Changesets"))
565 branches_group = ([], _("Branches"))
565 branches_group = ([], _("Branches"))
566 tags_group = ([], _("Tags"))
566 tags_group = ([], _("Tags"))
567 _hg = cs.repository.alias == 'hg'
567 _hg = cs.repository.alias == 'hg'
568 for chs in changesets:
568 for chs in changesets:
569 #_branch = '(%s)' % chs.branch if _hg else ''
569 #_branch = '(%s)' % chs.branch if _hg else ''
570 _branch = chs.branch
570 _branch = chs.branch
571 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
571 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
572 changesets_group[0].append((chs.raw_id, n_desc,))
572 changesets_group[0].append((chs.raw_id, n_desc,))
573 hist_l.append(changesets_group)
573 hist_l.append(changesets_group)
574
574
575 for name, chs in c.rhodecode_repo.branches.items():
575 for name, chs in c.rhodecode_repo.branches.items():
576 branches_group[0].append((chs, name),)
576 branches_group[0].append((chs, name),)
577 hist_l.append(branches_group)
577 hist_l.append(branches_group)
578
578
579 for name, chs in c.rhodecode_repo.tags.items():
579 for name, chs in c.rhodecode_repo.tags.items():
580 tags_group[0].append((chs, name),)
580 tags_group[0].append((chs, name),)
581 hist_l.append(tags_group)
581 hist_l.append(tags_group)
582
582
583 return hist_l, changesets
583 return hist_l, changesets
584
584
585 @LoginRequired()
585 @LoginRequired()
586 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
586 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
587 'repository.admin')
587 'repository.admin')
588 @jsonify
588 @jsonify
589 def nodelist(self, repo_name, revision, f_path):
589 def nodelist(self, repo_name, revision, f_path):
590 if request.environ.get('HTTP_X_PARTIAL_XHR'):
590 if request.environ.get('HTTP_X_PARTIAL_XHR'):
591 cs = self.__get_cs_or_redirect(revision, repo_name)
591 cs = self.__get_cs_or_redirect(revision, repo_name)
592 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
592 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
593 flat=False)
593 flat=False)
594 return {'nodes': _d + _f}
594 return {'nodes': _d + _f}
@@ -1,474 +1,472
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 import logging
25 import logging
26 import traceback
26 import traceback
27 import formencode
27 import formencode
28
28
29 from webob.exc import HTTPNotFound, HTTPForbidden
29 from webob.exc import HTTPNotFound, HTTPForbidden
30 from collections import defaultdict
30 from collections import defaultdict
31 from itertools import groupby
31 from itertools import groupby
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36 from pylons.decorators import jsonify
37
36
38 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
39 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
41 NotAnonymous
40 NotAnonymous
42 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
42 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger
43 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.diffs import LimitedDiffContainer
45 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
47 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
46 ChangesetComment
48 ChangesetComment
47 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
49 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.comment import ChangesetCommentsModel
52 from rhodecode.model.comment import ChangesetCommentsModel
51 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.forms import PullRequestForm
54 from rhodecode.model.forms import PullRequestForm
53 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
54 from rhodecode.lib.vcs.backends.base import EmptyChangeset
55 from rhodecode.lib.diffs import LimitedDiffContainer
56 from rhodecode.lib.utils2 import str2bool
57
55
58 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
59
57
60
58
61 class PullrequestsController(BaseRepoController):
59 class PullrequestsController(BaseRepoController):
62
60
63 @LoginRequired()
61 @LoginRequired()
64 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
62 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
65 'repository.admin')
63 'repository.admin')
66 def __before__(self):
64 def __before__(self):
67 super(PullrequestsController, self).__before__()
65 super(PullrequestsController, self).__before__()
68 repo_model = RepoModel()
66 repo_model = RepoModel()
69 c.users_array = repo_model.get_users_js()
67 c.users_array = repo_model.get_users_js()
70 c.users_groups_array = repo_model.get_users_groups_js()
68 c.users_groups_array = repo_model.get_users_groups_js()
71
69
72 def _get_repo_refs(self, repo):
70 def _get_repo_refs(self, repo):
73 hist_l = []
71 hist_l = []
74
72
75 branches_group = ([('branch:%s:%s' % (k, v), k) for
73 branches_group = ([('branch:%s:%s' % (k, v), k) for
76 k, v in repo.branches.iteritems()], _("Branches"))
74 k, v in repo.branches.iteritems()], _("Branches"))
77 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
75 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
78 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
76 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
79 tags_group = ([('tag:%s:%s' % (k, v), k) for
77 tags_group = ([('tag:%s:%s' % (k, v), k) for
80 k, v in repo.tags.iteritems()], _("Tags"))
78 k, v in repo.tags.iteritems()], _("Tags"))
81
79
82 hist_l.append(bookmarks_group)
80 hist_l.append(bookmarks_group)
83 hist_l.append(branches_group)
81 hist_l.append(branches_group)
84 hist_l.append(tags_group)
82 hist_l.append(tags_group)
85
83
86 return hist_l
84 return hist_l
87
85
88 def _get_default_rev(self, repo):
86 def _get_default_rev(self, repo):
89 """
87 """
90 Get's default revision to do compare on pull request
88 Get's default revision to do compare on pull request
91
89
92 :param repo:
90 :param repo:
93 """
91 """
94 repo = repo.scm_instance
92 repo = repo.scm_instance
95 if 'default' in repo.branches:
93 if 'default' in repo.branches:
96 return 'default'
94 return 'default'
97 else:
95 else:
98 #if repo doesn't have default branch return first found
96 #if repo doesn't have default branch return first found
99 return repo.branches.keys()[0]
97 return repo.branches.keys()[0]
100
98
101 def show_all(self, repo_name):
99 def show_all(self, repo_name):
102 c.pull_requests = PullRequestModel().get_all(repo_name)
100 c.pull_requests = PullRequestModel().get_all(repo_name)
103 c.repo_name = repo_name
101 c.repo_name = repo_name
104 return render('/pullrequests/pullrequest_show_all.html')
102 return render('/pullrequests/pullrequest_show_all.html')
105
103
106 @NotAnonymous()
104 @NotAnonymous()
107 def index(self):
105 def index(self):
108 org_repo = c.rhodecode_db_repo
106 org_repo = c.rhodecode_db_repo
109
107
110 if org_repo.scm_instance.alias != 'hg':
108 if org_repo.scm_instance.alias != 'hg':
111 log.error('Review not available for GIT REPOS')
109 log.error('Review not available for GIT REPOS')
112 raise HTTPNotFound
110 raise HTTPNotFound
113
111
114 try:
112 try:
115 org_repo.scm_instance.get_changeset()
113 org_repo.scm_instance.get_changeset()
116 except EmptyRepositoryError, e:
114 except EmptyRepositoryError, e:
117 h.flash(h.literal(_('There are no changesets yet')),
115 h.flash(h.literal(_('There are no changesets yet')),
118 category='warning')
116 category='warning')
119 redirect(url('summary_home', repo_name=org_repo.repo_name))
117 redirect(url('summary_home', repo_name=org_repo.repo_name))
120
118
121 other_repos_info = {}
119 other_repos_info = {}
122
120
123 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
121 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
124 c.org_repos = []
122 c.org_repos = []
125 c.other_repos = []
123 c.other_repos = []
126 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
124 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
127 org_repo.user.username, c.repo_name))
125 org_repo.user.username, c.repo_name))
128 )
126 )
129
127
130 # add org repo to other so we can open pull request agains itself
128 # add org repo to other so we can open pull request agains itself
131 c.other_repos.extend(c.org_repos)
129 c.other_repos.extend(c.org_repos)
132
130
133 c.default_pull_request = org_repo.repo_name # repo name pre-selected
131 c.default_pull_request = org_repo.repo_name # repo name pre-selected
134 c.default_pull_request_rev = self._get_default_rev(org_repo) # revision pre-selected
132 c.default_pull_request_rev = self._get_default_rev(org_repo) # revision pre-selected
135 c.default_revs = self._get_repo_refs(org_repo.scm_instance)
133 c.default_revs = self._get_repo_refs(org_repo.scm_instance)
136 #add orginal repo
134 #add orginal repo
137 other_repos_info[org_repo.repo_name] = {
135 other_repos_info[org_repo.repo_name] = {
138 'gravatar': h.gravatar_url(org_repo.user.email, 24),
136 'gravatar': h.gravatar_url(org_repo.user.email, 24),
139 'description': org_repo.description,
137 'description': org_repo.description,
140 'revs': h.select('other_ref', '', c.default_revs, class_='refs')
138 'revs': h.select('other_ref', '', c.default_revs, class_='refs')
141 }
139 }
142
140
143 #gather forks and add to this list
141 #gather forks and add to this list
144 for fork in org_repo.forks:
142 for fork in org_repo.forks:
145 c.other_repos.append((fork.repo_name, '%s/%s' % (
143 c.other_repos.append((fork.repo_name, '%s/%s' % (
146 fork.user.username, fork.repo_name))
144 fork.user.username, fork.repo_name))
147 )
145 )
148 other_repos_info[fork.repo_name] = {
146 other_repos_info[fork.repo_name] = {
149 'gravatar': h.gravatar_url(fork.user.email, 24),
147 'gravatar': h.gravatar_url(fork.user.email, 24),
150 'description': fork.description,
148 'description': fork.description,
151 'revs': h.select('other_ref', '',
149 'revs': h.select('other_ref', '',
152 self._get_repo_refs(fork.scm_instance),
150 self._get_repo_refs(fork.scm_instance),
153 class_='refs')
151 class_='refs')
154 }
152 }
155 #add parents of this fork also, but only if it's not empty
153 #add parents of this fork also, but only if it's not empty
156 if org_repo.parent and org_repo.parent.scm_instance.revisions:
154 if org_repo.parent and org_repo.parent.scm_instance.revisions:
157 c.default_pull_request = org_repo.parent.repo_name
155 c.default_pull_request = org_repo.parent.repo_name
158 c.default_pull_request_rev = self._get_default_rev(org_repo.parent)
156 c.default_pull_request_rev = self._get_default_rev(org_repo.parent)
159 c.default_revs = self._get_repo_refs(org_repo.parent.scm_instance)
157 c.default_revs = self._get_repo_refs(org_repo.parent.scm_instance)
160 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
158 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
161 org_repo.parent.user.username,
159 org_repo.parent.user.username,
162 org_repo.parent.repo_name))
160 org_repo.parent.repo_name))
163 )
161 )
164 other_repos_info[org_repo.parent.repo_name] = {
162 other_repos_info[org_repo.parent.repo_name] = {
165 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
163 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
166 'description': org_repo.parent.description,
164 'description': org_repo.parent.description,
167 'revs': h.select('other_ref', '',
165 'revs': h.select('other_ref', '',
168 self._get_repo_refs(org_repo.parent.scm_instance),
166 self._get_repo_refs(org_repo.parent.scm_instance),
169 class_='refs')
167 class_='refs')
170 }
168 }
171
169
172 c.other_repos_info = json.dumps(other_repos_info)
170 c.other_repos_info = json.dumps(other_repos_info)
173 c.review_members = [org_repo.user]
171 c.review_members = [org_repo.user]
174 return render('/pullrequests/pullrequest.html')
172 return render('/pullrequests/pullrequest.html')
175
173
176 @NotAnonymous()
174 @NotAnonymous()
177 def create(self, repo_name):
175 def create(self, repo_name):
178 repo = RepoModel()._get_repo(repo_name)
176 repo = RepoModel()._get_repo(repo_name)
179 try:
177 try:
180 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
178 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
181 except formencode.Invalid, errors:
179 except formencode.Invalid, errors:
182 log.error(traceback.format_exc())
180 log.error(traceback.format_exc())
183 if errors.error_dict.get('revisions'):
181 if errors.error_dict.get('revisions'):
184 msg = 'Revisions: %s' % errors.error_dict['revisions']
182 msg = 'Revisions: %s' % errors.error_dict['revisions']
185 elif errors.error_dict.get('pullrequest_title'):
183 elif errors.error_dict.get('pullrequest_title'):
186 msg = _('Pull request requires a title with min. 3 chars')
184 msg = _('Pull request requires a title with min. 3 chars')
187 else:
185 else:
188 msg = _('error during creation of pull request')
186 msg = _('error during creation of pull request')
189
187
190 h.flash(msg, 'error')
188 h.flash(msg, 'error')
191 return redirect(url('pullrequest_home', repo_name=repo_name))
189 return redirect(url('pullrequest_home', repo_name=repo_name))
192
190
193 org_repo = _form['org_repo']
191 org_repo = _form['org_repo']
194 org_ref = _form['org_ref']
192 org_ref = _form['org_ref']
195 other_repo = _form['other_repo']
193 other_repo = _form['other_repo']
196 other_ref = _form['other_ref']
194 other_ref = _form['other_ref']
197 revisions = _form['revisions']
195 revisions = _form['revisions']
198 reviewers = _form['review_members']
196 reviewers = _form['review_members']
199
197
200 # if we have cherry picked pull request we don't care what is in
198 # if we have cherry picked pull request we don't care what is in
201 # org_ref/other_ref
199 # org_ref/other_ref
202 rev_start = request.POST.get('rev_start')
200 rev_start = request.POST.get('rev_start')
203 rev_end = request.POST.get('rev_end')
201 rev_end = request.POST.get('rev_end')
204
202
205 if rev_start and rev_end:
203 if rev_start and rev_end:
206 # this is swapped to simulate that rev_end is a revision from
204 # this is swapped to simulate that rev_end is a revision from
207 # parent of the fork
205 # parent of the fork
208 org_ref = 'rev:%s:%s' % (rev_end, rev_end)
206 org_ref = 'rev:%s:%s' % (rev_end, rev_end)
209 other_ref = 'rev:%s:%s' % (rev_start, rev_start)
207 other_ref = 'rev:%s:%s' % (rev_start, rev_start)
210
208
211 title = _form['pullrequest_title']
209 title = _form['pullrequest_title']
212 description = _form['pullrequest_desc']
210 description = _form['pullrequest_desc']
213
211
214 try:
212 try:
215 pull_request = PullRequestModel().create(
213 pull_request = PullRequestModel().create(
216 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
214 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
217 other_ref, revisions, reviewers, title, description
215 other_ref, revisions, reviewers, title, description
218 )
216 )
219 Session().commit()
217 Session().commit()
220 h.flash(_('Successfully opened new pull request'),
218 h.flash(_('Successfully opened new pull request'),
221 category='success')
219 category='success')
222 except Exception:
220 except Exception:
223 h.flash(_('Error occurred during sending pull request'),
221 h.flash(_('Error occurred during sending pull request'),
224 category='error')
222 category='error')
225 log.error(traceback.format_exc())
223 log.error(traceback.format_exc())
226 return redirect(url('pullrequest_home', repo_name=repo_name))
224 return redirect(url('pullrequest_home', repo_name=repo_name))
227
225
228 return redirect(url('pullrequest_show', repo_name=other_repo,
226 return redirect(url('pullrequest_show', repo_name=other_repo,
229 pull_request_id=pull_request.pull_request_id))
227 pull_request_id=pull_request.pull_request_id))
230
228
231 @NotAnonymous()
229 @NotAnonymous()
232 @jsonify
230 @jsonify
233 def update(self, repo_name, pull_request_id):
231 def update(self, repo_name, pull_request_id):
234 pull_request = PullRequest.get_or_404(pull_request_id)
232 pull_request = PullRequest.get_or_404(pull_request_id)
235 if pull_request.is_closed():
233 if pull_request.is_closed():
236 raise HTTPForbidden()
234 raise HTTPForbidden()
237 #only owner or admin can update it
235 #only owner or admin can update it
238 owner = pull_request.author.user_id == c.rhodecode_user.user_id
236 owner = pull_request.author.user_id == c.rhodecode_user.user_id
239 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
237 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
240 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
238 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
241 request.POST.get('reviewers_ids', '').split(',')))
239 request.POST.get('reviewers_ids', '').split(',')))
242
240
243 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
241 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
244 Session().commit()
242 Session().commit()
245 return True
243 return True
246 raise HTTPForbidden()
244 raise HTTPForbidden()
247
245
248 @NotAnonymous()
246 @NotAnonymous()
249 @jsonify
247 @jsonify
250 def delete(self, repo_name, pull_request_id):
248 def delete(self, repo_name, pull_request_id):
251 pull_request = PullRequest.get_or_404(pull_request_id)
249 pull_request = PullRequest.get_or_404(pull_request_id)
252 #only owner can delete it !
250 #only owner can delete it !
253 if pull_request.author.user_id == c.rhodecode_user.user_id:
251 if pull_request.author.user_id == c.rhodecode_user.user_id:
254 PullRequestModel().delete(pull_request)
252 PullRequestModel().delete(pull_request)
255 Session().commit()
253 Session().commit()
256 h.flash(_('Successfully deleted pull request'),
254 h.flash(_('Successfully deleted pull request'),
257 category='success')
255 category='success')
258 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
256 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
259 raise HTTPForbidden()
257 raise HTTPForbidden()
260
258
261 def _load_compare_data(self, pull_request, enable_comments=True):
259 def _load_compare_data(self, pull_request, enable_comments=True):
262 """
260 """
263 Load context data needed for generating compare diff
261 Load context data needed for generating compare diff
264
262
265 :param pull_request:
263 :param pull_request:
266 :type pull_request:
264 :type pull_request:
267 """
265 """
268 rev_start = request.GET.get('rev_start')
266 rev_start = request.GET.get('rev_start')
269 rev_end = request.GET.get('rev_end')
267 rev_end = request.GET.get('rev_end')
270
268
271 org_repo = pull_request.org_repo
269 org_repo = pull_request.org_repo
272 (org_ref_type,
270 (org_ref_type,
273 org_ref_name,
271 org_ref_name,
274 org_ref_rev) = pull_request.org_ref.split(':')
272 org_ref_rev) = pull_request.org_ref.split(':')
275
273
276 other_repo = org_repo
274 other_repo = org_repo
277 (other_ref_type,
275 (other_ref_type,
278 other_ref_name,
276 other_ref_name,
279 other_ref_rev) = pull_request.other_ref.split(':')
277 other_ref_rev) = pull_request.other_ref.split(':')
280
278
281 # despite opening revisions for bookmarks/branches/tags, we always
279 # despite opening revisions for bookmarks/branches/tags, we always
282 # convert this to rev to prevent changes after book or branch change
280 # convert this to rev to prevent changes after book or branch change
283 org_ref = ('rev', org_ref_rev)
281 org_ref = ('rev', org_ref_rev)
284 other_ref = ('rev', other_ref_rev)
282 other_ref = ('rev', other_ref_rev)
285
283
286 c.org_repo = org_repo
284 c.org_repo = org_repo
287 c.other_repo = other_repo
285 c.other_repo = other_repo
288
286
289 c.fulldiff = fulldiff = request.GET.get('fulldiff')
287 c.fulldiff = fulldiff = request.GET.get('fulldiff')
290
288
291 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
289 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
292
290
293 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
291 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
294 if c.cs_ranges[0].parents
292 if c.cs_ranges[0].parents
295 else EmptyChangeset(), 'raw_id'))
293 else EmptyChangeset(), 'raw_id'))
296
294
297 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
295 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
298 c.target_repo = c.repo_name
296 c.target_repo = c.repo_name
299 # defines that we need hidden inputs with changesets
297 # defines that we need hidden inputs with changesets
300 c.as_form = request.GET.get('as_form', False)
298 c.as_form = request.GET.get('as_form', False)
301
299
302 c.org_ref = org_ref[1]
300 c.org_ref = org_ref[1]
303 c.other_ref = other_ref[1]
301 c.other_ref = other_ref[1]
304
302
305 diff_limit = self.cut_off_limit if not fulldiff else None
303 diff_limit = self.cut_off_limit if not fulldiff else None
306
304
307 #we swap org/other ref since we run a simple diff on one repo
305 #we swap org/other ref since we run a simple diff on one repo
308 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
306 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
309
307
310 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
308 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
311 diff_limit=diff_limit)
309 diff_limit=diff_limit)
312 _parsed = diff_processor.prepare()
310 _parsed = diff_processor.prepare()
313
311
314 c.limited_diff = False
312 c.limited_diff = False
315 if isinstance(_parsed, LimitedDiffContainer):
313 if isinstance(_parsed, LimitedDiffContainer):
316 c.limited_diff = True
314 c.limited_diff = True
317
315
318 c.files = []
316 c.files = []
319 c.changes = {}
317 c.changes = {}
320 c.lines_added = 0
318 c.lines_added = 0
321 c.lines_deleted = 0
319 c.lines_deleted = 0
322 for f in _parsed:
320 for f in _parsed:
323 st = f['stats']
321 st = f['stats']
324 if st[0] != 'b':
322 if st[0] != 'b':
325 c.lines_added += st[0]
323 c.lines_added += st[0]
326 c.lines_deleted += st[1]
324 c.lines_deleted += st[1]
327 fid = h.FID('', f['filename'])
325 fid = h.FID('', f['filename'])
328 c.files.append([fid, f['operation'], f['filename'], f['stats']])
326 c.files.append([fid, f['operation'], f['filename'], f['stats']])
329 diff = diff_processor.as_html(enable_comments=enable_comments,
327 diff = diff_processor.as_html(enable_comments=enable_comments,
330 parsed_lines=[f])
328 parsed_lines=[f])
331 c.changes[fid] = [f['operation'], f['filename'], diff]
329 c.changes[fid] = [f['operation'], f['filename'], diff]
332
330
333 def show(self, repo_name, pull_request_id):
331 def show(self, repo_name, pull_request_id):
334 repo_model = RepoModel()
332 repo_model = RepoModel()
335 c.users_array = repo_model.get_users_js()
333 c.users_array = repo_model.get_users_js()
336 c.users_groups_array = repo_model.get_users_groups_js()
334 c.users_groups_array = repo_model.get_users_groups_js()
337 c.pull_request = PullRequest.get_or_404(pull_request_id)
335 c.pull_request = PullRequest.get_or_404(pull_request_id)
338 c.target_repo = c.pull_request.org_repo.repo_name
336 c.target_repo = c.pull_request.org_repo.repo_name
339
337
340 cc_model = ChangesetCommentsModel()
338 cc_model = ChangesetCommentsModel()
341 cs_model = ChangesetStatusModel()
339 cs_model = ChangesetStatusModel()
342 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
340 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
343 pull_request=c.pull_request,
341 pull_request=c.pull_request,
344 with_revisions=True)
342 with_revisions=True)
345
343
346 cs_statuses = defaultdict(list)
344 cs_statuses = defaultdict(list)
347 for st in _cs_statuses:
345 for st in _cs_statuses:
348 cs_statuses[st.author.username] += [st]
346 cs_statuses[st.author.username] += [st]
349
347
350 c.pull_request_reviewers = []
348 c.pull_request_reviewers = []
351 c.pull_request_pending_reviewers = []
349 c.pull_request_pending_reviewers = []
352 for o in c.pull_request.reviewers:
350 for o in c.pull_request.reviewers:
353 st = cs_statuses.get(o.user.username, None)
351 st = cs_statuses.get(o.user.username, None)
354 if st:
352 if st:
355 sorter = lambda k: k.version
353 sorter = lambda k: k.version
356 st = [(x, list(y)[0])
354 st = [(x, list(y)[0])
357 for x, y in (groupby(sorted(st, key=sorter), sorter))]
355 for x, y in (groupby(sorted(st, key=sorter), sorter))]
358 else:
356 else:
359 c.pull_request_pending_reviewers.append(o.user)
357 c.pull_request_pending_reviewers.append(o.user)
360 c.pull_request_reviewers.append([o.user, st])
358 c.pull_request_reviewers.append([o.user, st])
361
359
362 # pull_requests repo_name we opened it against
360 # pull_requests repo_name we opened it against
363 # ie. other_repo must match
361 # ie. other_repo must match
364 if repo_name != c.pull_request.other_repo.repo_name:
362 if repo_name != c.pull_request.other_repo.repo_name:
365 raise HTTPNotFound
363 raise HTTPNotFound
366
364
367 # load compare data into template context
365 # load compare data into template context
368 enable_comments = not c.pull_request.is_closed()
366 enable_comments = not c.pull_request.is_closed()
369 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
367 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
370
368
371 # inline comments
369 # inline comments
372 c.inline_cnt = 0
370 c.inline_cnt = 0
373 c.inline_comments = cc_model.get_inline_comments(
371 c.inline_comments = cc_model.get_inline_comments(
374 c.rhodecode_db_repo.repo_id,
372 c.rhodecode_db_repo.repo_id,
375 pull_request=pull_request_id)
373 pull_request=pull_request_id)
376 # count inline comments
374 # count inline comments
377 for __, lines in c.inline_comments:
375 for __, lines in c.inline_comments:
378 for comments in lines.values():
376 for comments in lines.values():
379 c.inline_cnt += len(comments)
377 c.inline_cnt += len(comments)
380 # comments
378 # comments
381 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
379 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
382 pull_request=pull_request_id)
380 pull_request=pull_request_id)
383
381
384 try:
382 try:
385 cur_status = c.statuses[c.pull_request.revisions[0]][0]
383 cur_status = c.statuses[c.pull_request.revisions[0]][0]
386 except:
384 except:
387 log.error(traceback.format_exc())
385 log.error(traceback.format_exc())
388 cur_status = 'undefined'
386 cur_status = 'undefined'
389 if c.pull_request.is_closed() and 0:
387 if c.pull_request.is_closed() and 0:
390 c.current_changeset_status = cur_status
388 c.current_changeset_status = cur_status
391 else:
389 else:
392 # changeset(pull-request) status calulation based on reviewers
390 # changeset(pull-request) status calulation based on reviewers
393 c.current_changeset_status = cs_model.calculate_status(
391 c.current_changeset_status = cs_model.calculate_status(
394 c.pull_request_reviewers,
392 c.pull_request_reviewers,
395 )
393 )
396 c.changeset_statuses = ChangesetStatus.STATUSES
394 c.changeset_statuses = ChangesetStatus.STATUSES
397
395
398 return render('/pullrequests/pullrequest_show.html')
396 return render('/pullrequests/pullrequest_show.html')
399
397
400 @NotAnonymous()
398 @NotAnonymous()
401 @jsonify
399 @jsonify
402 def comment(self, repo_name, pull_request_id):
400 def comment(self, repo_name, pull_request_id):
403 pull_request = PullRequest.get_or_404(pull_request_id)
401 pull_request = PullRequest.get_or_404(pull_request_id)
404 if pull_request.is_closed():
402 if pull_request.is_closed():
405 raise HTTPForbidden()
403 raise HTTPForbidden()
406
404
407 status = request.POST.get('changeset_status')
405 status = request.POST.get('changeset_status')
408 change_status = request.POST.get('change_changeset_status')
406 change_status = request.POST.get('change_changeset_status')
409 text = request.POST.get('text')
407 text = request.POST.get('text')
410 if status and change_status:
408 if status and change_status:
411 text = text or (_('Status change -> %s')
409 text = text or (_('Status change -> %s')
412 % ChangesetStatus.get_status_lbl(status))
410 % ChangesetStatus.get_status_lbl(status))
413 comm = ChangesetCommentsModel().create(
411 comm = ChangesetCommentsModel().create(
414 text=text,
412 text=text,
415 repo=c.rhodecode_db_repo.repo_id,
413 repo=c.rhodecode_db_repo.repo_id,
416 user=c.rhodecode_user.user_id,
414 user=c.rhodecode_user.user_id,
417 pull_request=pull_request_id,
415 pull_request=pull_request_id,
418 f_path=request.POST.get('f_path'),
416 f_path=request.POST.get('f_path'),
419 line_no=request.POST.get('line'),
417 line_no=request.POST.get('line'),
420 status_change=(ChangesetStatus.get_status_lbl(status)
418 status_change=(ChangesetStatus.get_status_lbl(status)
421 if status and change_status else None)
419 if status and change_status else None)
422 )
420 )
423
421
424 # get status if set !
422 # get status if set !
425 if status and change_status:
423 if status and change_status:
426 ChangesetStatusModel().set_status(
424 ChangesetStatusModel().set_status(
427 c.rhodecode_db_repo.repo_id,
425 c.rhodecode_db_repo.repo_id,
428 status,
426 status,
429 c.rhodecode_user.user_id,
427 c.rhodecode_user.user_id,
430 comm,
428 comm,
431 pull_request=pull_request_id
429 pull_request=pull_request_id
432 )
430 )
433 action_logger(self.rhodecode_user,
431 action_logger(self.rhodecode_user,
434 'user_commented_pull_request:%s' % pull_request_id,
432 'user_commented_pull_request:%s' % pull_request_id,
435 c.rhodecode_db_repo, self.ip_addr, self.sa)
433 c.rhodecode_db_repo, self.ip_addr, self.sa)
436
434
437 if request.POST.get('save_close'):
435 if request.POST.get('save_close'):
438 PullRequestModel().close_pull_request(pull_request_id)
436 PullRequestModel().close_pull_request(pull_request_id)
439 action_logger(self.rhodecode_user,
437 action_logger(self.rhodecode_user,
440 'user_closed_pull_request:%s' % pull_request_id,
438 'user_closed_pull_request:%s' % pull_request_id,
441 c.rhodecode_db_repo, self.ip_addr, self.sa)
439 c.rhodecode_db_repo, self.ip_addr, self.sa)
442
440
443 Session().commit()
441 Session().commit()
444
442
445 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
443 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
446 return redirect(h.url('pullrequest_show', repo_name=repo_name,
444 return redirect(h.url('pullrequest_show', repo_name=repo_name,
447 pull_request_id=pull_request_id))
445 pull_request_id=pull_request_id))
448
446
449 data = {
447 data = {
450 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
448 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
451 }
449 }
452 if comm:
450 if comm:
453 c.co = comm
451 c.co = comm
454 data.update(comm.get_dict())
452 data.update(comm.get_dict())
455 data.update({'rendered_text':
453 data.update({'rendered_text':
456 render('changeset/changeset_comment_block.html')})
454 render('changeset/changeset_comment_block.html')})
457
455
458 return data
456 return data
459
457
460 @NotAnonymous()
458 @NotAnonymous()
461 @jsonify
459 @jsonify
462 def delete_comment(self, repo_name, comment_id):
460 def delete_comment(self, repo_name, comment_id):
463 co = ChangesetComment.get(comment_id)
461 co = ChangesetComment.get(comment_id)
464 if co.pull_request.is_closed():
462 if co.pull_request.is_closed():
465 #don't allow deleting comments on closed pull request
463 #don't allow deleting comments on closed pull request
466 raise HTTPForbidden()
464 raise HTTPForbidden()
467
465
468 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
466 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
469 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
467 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
470 ChangesetCommentsModel().delete(comment=co)
468 ChangesetCommentsModel().delete(comment=co)
471 Session().commit()
469 Session().commit()
472 return True
470 return True
473 else:
471 else:
474 raise HTTPForbidden()
472 raise HTTPForbidden()
@@ -1,716 +1,742
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 import decorator
36 import warnings
35 from os.path import abspath
37 from os.path import abspath
36 from os.path import dirname as dn, join as jn
38 from os.path import dirname as dn, join as jn
37
39
38 from paste.script.command import Command, BadCommand
40 from paste.script.command import Command, BadCommand
39
41
40 from mercurial import ui, config
42 from mercurial import ui, config
41
43
42 from webhelpers.text import collapse, remove_formatting, strip_tags
44 from webhelpers.text import collapse, remove_formatting, strip_tags
43
45
44 from rhodecode.lib.vcs import get_backend
46 from rhodecode.lib.vcs import get_backend
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.utils.helpers import get_scm
49 from rhodecode.lib.vcs.utils.helpers import get_scm
48 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.exceptions import VCSError
49
51
50 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
51
53
52 from rhodecode.model import meta
54 from rhodecode.model import meta
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
55 from rhodecode.model.meta import Session
57 from rhodecode.model.meta import Session
56 from rhodecode.model.repos_group import ReposGroupModel
58 from rhodecode.model.repos_group import ReposGroupModel
57 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
58 from rhodecode.lib.vcs.utils.fakemod import create_module
60 from rhodecode.lib.vcs.utils.fakemod import create_module
59
61
60 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
61
63
62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
63
65
64
66
65 def recursive_replace(str_, replace=' '):
67 def recursive_replace(str_, replace=' '):
66 """
68 """
67 Recursive replace of given sign to just one instance
69 Recursive replace of given sign to just one instance
68
70
69 :param str_: given string
71 :param str_: given string
70 :param replace: char to find and replace multiple instances
72 :param replace: char to find and replace multiple instances
71
73
72 Examples::
74 Examples::
73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
74 'Mighty-Mighty-Bo-sstones'
76 'Mighty-Mighty-Bo-sstones'
75 """
77 """
76
78
77 if str_.find(replace * 2) == -1:
79 if str_.find(replace * 2) == -1:
78 return str_
80 return str_
79 else:
81 else:
80 str_ = str_.replace(replace * 2, replace)
82 str_ = str_.replace(replace * 2, replace)
81 return recursive_replace(str_, replace)
83 return recursive_replace(str_, replace)
82
84
83
85
84 def repo_name_slug(value):
86 def repo_name_slug(value):
85 """
87 """
86 Return slug of name of repository
88 Return slug of name of repository
87 This function is called on each creation/modification
89 This function is called on each creation/modification
88 of repository to prevent bad names in repo
90 of repository to prevent bad names in repo
89 """
91 """
90
92
91 slug = remove_formatting(value)
93 slug = remove_formatting(value)
92 slug = strip_tags(slug)
94 slug = strip_tags(slug)
93
95
94 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
95 slug = slug.replace(c, '-')
97 slug = slug.replace(c, '-')
96 slug = recursive_replace(slug, '-')
98 slug = recursive_replace(slug, '-')
97 slug = collapse(slug, '-')
99 slug = collapse(slug, '-')
98 return slug
100 return slug
99
101
100
102
101 def get_repo_slug(request):
103 def get_repo_slug(request):
102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
103 if _repo:
105 if _repo:
104 _repo = _repo.rstrip('/')
106 _repo = _repo.rstrip('/')
105 return _repo
107 return _repo
106
108
107
109
108 def get_repos_group_slug(request):
110 def get_repos_group_slug(request):
109 _group = request.environ['pylons.routes_dict'].get('group_name')
111 _group = request.environ['pylons.routes_dict'].get('group_name')
110 if _group:
112 if _group:
111 _group = _group.rstrip('/')
113 _group = _group.rstrip('/')
112 return _group
114 return _group
113
115
114
116
115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
116 """
118 """
117 Action logger for various actions made by users
119 Action logger for various actions made by users
118
120
119 :param user: user that made this action, can be a unique username string or
121 :param user: user that made this action, can be a unique username string or
120 object containing user_id attribute
122 object containing user_id attribute
121 :param action: action to log, should be on of predefined unique actions for
123 :param action: action to log, should be on of predefined unique actions for
122 easy translations
124 easy translations
123 :param repo: string name of repository or object containing repo_id,
125 :param repo: string name of repository or object containing repo_id,
124 that action was made on
126 that action was made on
125 :param ipaddr: optional ip address from what the action was made
127 :param ipaddr: optional ip address from what the action was made
126 :param sa: optional sqlalchemy session
128 :param sa: optional sqlalchemy session
127
129
128 """
130 """
129
131
130 if not sa:
132 if not sa:
131 sa = meta.Session()
133 sa = meta.Session()
132
134
133 try:
135 try:
134 if hasattr(user, 'user_id'):
136 if hasattr(user, 'user_id'):
135 user_obj = user
137 user_obj = user
136 elif isinstance(user, basestring):
138 elif isinstance(user, basestring):
137 user_obj = User.get_by_username(user)
139 user_obj = User.get_by_username(user)
138 else:
140 else:
139 raise Exception('You have to provide a user object or a username')
141 raise Exception('You have to provide a user object or a username')
140
142
141 if hasattr(repo, 'repo_id'):
143 if hasattr(repo, 'repo_id'):
142 repo_obj = Repository.get(repo.repo_id)
144 repo_obj = Repository.get(repo.repo_id)
143 repo_name = repo_obj.repo_name
145 repo_name = repo_obj.repo_name
144 elif isinstance(repo, basestring):
146 elif isinstance(repo, basestring):
145 repo_name = repo.lstrip('/')
147 repo_name = repo.lstrip('/')
146 repo_obj = Repository.get_by_repo_name(repo_name)
148 repo_obj = Repository.get_by_repo_name(repo_name)
147 else:
149 else:
148 repo_obj = None
150 repo_obj = None
149 repo_name = ''
151 repo_name = ''
150
152
151 user_log = UserLog()
153 user_log = UserLog()
152 user_log.user_id = user_obj.user_id
154 user_log.user_id = user_obj.user_id
153 user_log.action = safe_unicode(action)
155 user_log.action = safe_unicode(action)
154
156
155 user_log.repository = repo_obj
157 user_log.repository = repo_obj
156 user_log.repository_name = repo_name
158 user_log.repository_name = repo_name
157
159
158 user_log.action_date = datetime.datetime.now()
160 user_log.action_date = datetime.datetime.now()
159 user_log.user_ip = ipaddr
161 user_log.user_ip = ipaddr
160 sa.add(user_log)
162 sa.add(user_log)
161
163
162 log.info(
164 log.info(
163 'Adding user %s, action %s on %s' % (user_obj, action,
165 'Adding user %s, action %s on %s' % (user_obj, action,
164 safe_unicode(repo))
166 safe_unicode(repo))
165 )
167 )
166 if commit:
168 if commit:
167 sa.commit()
169 sa.commit()
168 except:
170 except:
169 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
170 raise
172 raise
171
173
172
174
173 def get_repos(path, recursive=False):
175 def get_repos(path, recursive=False):
174 """
176 """
175 Scans given path for repos and return (name,(type,path)) tuple
177 Scans given path for repos and return (name,(type,path)) tuple
176
178
177 :param path: path to scan for repositories
179 :param path: path to scan for repositories
178 :param recursive: recursive search and return names with subdirs in front
180 :param recursive: recursive search and return names with subdirs in front
179 """
181 """
180
182
181 # remove ending slash for better results
183 # remove ending slash for better results
182 path = path.rstrip(os.sep)
184 path = path.rstrip(os.sep)
183
185
184 def _get_repos(p):
186 def _get_repos(p):
185 if not os.access(p, os.W_OK):
187 if not os.access(p, os.W_OK):
186 return
188 return
187 for dirpath in os.listdir(p):
189 for dirpath in os.listdir(p):
188 if os.path.isfile(os.path.join(p, dirpath)):
190 if os.path.isfile(os.path.join(p, dirpath)):
189 continue
191 continue
190 cur_path = os.path.join(p, dirpath)
192 cur_path = os.path.join(p, dirpath)
191 try:
193 try:
192 scm_info = get_scm(cur_path)
194 scm_info = get_scm(cur_path)
193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
195 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 except VCSError:
196 except VCSError:
195 if not recursive:
197 if not recursive:
196 continue
198 continue
197 #check if this dir containts other repos for recursive scan
199 #check if this dir containts other repos for recursive scan
198 rec_path = os.path.join(p, dirpath)
200 rec_path = os.path.join(p, dirpath)
199 if os.path.isdir(rec_path):
201 if os.path.isdir(rec_path):
200 for inner_scm in _get_repos(rec_path):
202 for inner_scm in _get_repos(rec_path):
201 yield inner_scm
203 yield inner_scm
202
204
203 return _get_repos(path)
205 return _get_repos(path)
204
206
205
207
206 def is_valid_repo(repo_name, base_path, scm=None):
208 def is_valid_repo(repo_name, base_path, scm=None):
207 """
209 """
208 Returns True if given path is a valid repository False otherwise.
210 Returns True if given path is a valid repository False otherwise.
209 If scm param is given also compare if given scm is the same as expected
211 If scm param is given also compare if given scm is the same as expected
210 from scm parameter
212 from scm parameter
211
213
212 :param repo_name:
214 :param repo_name:
213 :param base_path:
215 :param base_path:
214 :param scm:
216 :param scm:
215
217
216 :return True: if given path is a valid repository
218 :return True: if given path is a valid repository
217 """
219 """
218 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
220 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
219
221
220 try:
222 try:
221 scm_ = get_scm(full_path)
223 scm_ = get_scm(full_path)
222 if scm:
224 if scm:
223 return scm_[0] == scm
225 return scm_[0] == scm
224 return True
226 return True
225 except VCSError:
227 except VCSError:
226 return False
228 return False
227
229
228
230
229 def is_valid_repos_group(repos_group_name, base_path):
231 def is_valid_repos_group(repos_group_name, base_path):
230 """
232 """
231 Returns True if given path is a repos group False otherwise
233 Returns True if given path is a repos group False otherwise
232
234
233 :param repo_name:
235 :param repo_name:
234 :param base_path:
236 :param base_path:
235 """
237 """
236 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
238 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
237
239
238 # check if it's not a repo
240 # check if it's not a repo
239 if is_valid_repo(repos_group_name, base_path):
241 if is_valid_repo(repos_group_name, base_path):
240 return False
242 return False
241
243
242 try:
244 try:
243 # we need to check bare git repos at higher level
245 # we need to check bare git repos at higher level
244 # since we might match branches/hooks/info/objects or possible
246 # since we might match branches/hooks/info/objects or possible
245 # other things inside bare git repo
247 # other things inside bare git repo
246 get_scm(os.path.dirname(full_path))
248 get_scm(os.path.dirname(full_path))
247 return False
249 return False
248 except VCSError:
250 except VCSError:
249 pass
251 pass
250
252
251 # check if it's a valid path
253 # check if it's a valid path
252 if os.path.isdir(full_path):
254 if os.path.isdir(full_path):
253 return True
255 return True
254
256
255 return False
257 return False
256
258
257
259
258 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
260 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
259 while True:
261 while True:
260 ok = raw_input(prompt)
262 ok = raw_input(prompt)
261 if ok in ('y', 'ye', 'yes'):
263 if ok in ('y', 'ye', 'yes'):
262 return True
264 return True
263 if ok in ('n', 'no', 'nop', 'nope'):
265 if ok in ('n', 'no', 'nop', 'nope'):
264 return False
266 return False
265 retries = retries - 1
267 retries = retries - 1
266 if retries < 0:
268 if retries < 0:
267 raise IOError
269 raise IOError
268 print complaint
270 print complaint
269
271
270 #propagated from mercurial documentation
272 #propagated from mercurial documentation
271 ui_sections = ['alias', 'auth',
273 ui_sections = ['alias', 'auth',
272 'decode/encode', 'defaults',
274 'decode/encode', 'defaults',
273 'diff', 'email',
275 'diff', 'email',
274 'extensions', 'format',
276 'extensions', 'format',
275 'merge-patterns', 'merge-tools',
277 'merge-patterns', 'merge-tools',
276 'hooks', 'http_proxy',
278 'hooks', 'http_proxy',
277 'smtp', 'patch',
279 'smtp', 'patch',
278 'paths', 'profiling',
280 'paths', 'profiling',
279 'server', 'trusted',
281 'server', 'trusted',
280 'ui', 'web', ]
282 'ui', 'web', ]
281
283
282
284
283 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
285 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
284 """
286 """
285 A function that will read python rc files or database
287 A function that will read python rc files or database
286 and make an mercurial ui object from read options
288 and make an mercurial ui object from read options
287
289
288 :param path: path to mercurial config file
290 :param path: path to mercurial config file
289 :param checkpaths: check the path
291 :param checkpaths: check the path
290 :param read_from: read from 'file' or 'db'
292 :param read_from: read from 'file' or 'db'
291 """
293 """
292
294
293 baseui = ui.ui()
295 baseui = ui.ui()
294
296
295 # clean the baseui object
297 # clean the baseui object
296 baseui._ocfg = config.config()
298 baseui._ocfg = config.config()
297 baseui._ucfg = config.config()
299 baseui._ucfg = config.config()
298 baseui._tcfg = config.config()
300 baseui._tcfg = config.config()
299
301
300 if read_from == 'file':
302 if read_from == 'file':
301 if not os.path.isfile(path):
303 if not os.path.isfile(path):
302 log.debug('hgrc file is not present at %s, skipping...' % path)
304 log.debug('hgrc file is not present at %s, skipping...' % path)
303 return False
305 return False
304 log.debug('reading hgrc from %s' % path)
306 log.debug('reading hgrc from %s' % path)
305 cfg = config.config()
307 cfg = config.config()
306 cfg.read(path)
308 cfg.read(path)
307 for section in ui_sections:
309 for section in ui_sections:
308 for k, v in cfg.items(section):
310 for k, v in cfg.items(section):
309 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
311 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
310 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
312 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
311
313
312 elif read_from == 'db':
314 elif read_from == 'db':
313 sa = meta.Session()
315 sa = meta.Session()
314 ret = sa.query(RhodeCodeUi)\
316 ret = sa.query(RhodeCodeUi)\
315 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
317 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
316 .all()
318 .all()
317
319
318 hg_ui = ret
320 hg_ui = ret
319 for ui_ in hg_ui:
321 for ui_ in hg_ui:
320 if ui_.ui_active:
322 if ui_.ui_active:
321 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
323 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
322 ui_.ui_key, ui_.ui_value)
324 ui_.ui_key, ui_.ui_value)
323 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
325 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
324 safe_str(ui_.ui_value))
326 safe_str(ui_.ui_value))
325 if ui_.ui_key == 'push_ssl':
327 if ui_.ui_key == 'push_ssl':
326 # force set push_ssl requirement to False, rhodecode
328 # force set push_ssl requirement to False, rhodecode
327 # handles that
329 # handles that
328 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
330 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
329 False)
331 False)
330 if clear_session:
332 if clear_session:
331 meta.Session.remove()
333 meta.Session.remove()
332 return baseui
334 return baseui
333
335
334
336
335 def set_rhodecode_config(config):
337 def set_rhodecode_config(config):
336 """
338 """
337 Updates pylons config with new settings from database
339 Updates pylons config with new settings from database
338
340
339 :param config:
341 :param config:
340 """
342 """
341 hgsettings = RhodeCodeSetting.get_app_settings()
343 hgsettings = RhodeCodeSetting.get_app_settings()
342
344
343 for k, v in hgsettings.items():
345 for k, v in hgsettings.items():
344 config[k] = v
346 config[k] = v
345
347
346
348
347 def invalidate_cache(cache_key, *args):
349 def invalidate_cache(cache_key, *args):
348 """
350 """
349 Puts cache invalidation task into db for
351 Puts cache invalidation task into db for
350 further global cache invalidation
352 further global cache invalidation
351 """
353 """
352
354
353 from rhodecode.model.scm import ScmModel
355 from rhodecode.model.scm import ScmModel
354
356
355 if cache_key.startswith('get_repo_cached_'):
357 if cache_key.startswith('get_repo_cached_'):
356 name = cache_key.split('get_repo_cached_')[-1]
358 name = cache_key.split('get_repo_cached_')[-1]
357 ScmModel().mark_for_invalidation(name)
359 ScmModel().mark_for_invalidation(name)
358
360
359
361
360 def map_groups(path):
362 def map_groups(path):
361 """
363 """
362 Given a full path to a repository, create all nested groups that this
364 Given a full path to a repository, create all nested groups that this
363 repo is inside. This function creates parent-child relationships between
365 repo is inside. This function creates parent-child relationships between
364 groups and creates default perms for all new groups.
366 groups and creates default perms for all new groups.
365
367
366 :param paths: full path to repository
368 :param paths: full path to repository
367 """
369 """
368 sa = meta.Session()
370 sa = meta.Session()
369 groups = path.split(Repository.url_sep())
371 groups = path.split(Repository.url_sep())
370 parent = None
372 parent = None
371 group = None
373 group = None
372
374
373 # last element is repo in nested groups structure
375 # last element is repo in nested groups structure
374 groups = groups[:-1]
376 groups = groups[:-1]
375 rgm = ReposGroupModel(sa)
377 rgm = ReposGroupModel(sa)
376 for lvl, group_name in enumerate(groups):
378 for lvl, group_name in enumerate(groups):
377 group_name = '/'.join(groups[:lvl] + [group_name])
379 group_name = '/'.join(groups[:lvl] + [group_name])
378 group = RepoGroup.get_by_group_name(group_name)
380 group = RepoGroup.get_by_group_name(group_name)
379 desc = '%s group' % group_name
381 desc = '%s group' % group_name
380
382
381 # skip folders that are now removed repos
383 # skip folders that are now removed repos
382 if REMOVED_REPO_PAT.match(group_name):
384 if REMOVED_REPO_PAT.match(group_name):
383 break
385 break
384
386
385 if group is None:
387 if group is None:
386 log.debug('creating group level: %s group_name: %s' % (lvl,
388 log.debug('creating group level: %s group_name: %s' % (lvl,
387 group_name))
389 group_name))
388 group = RepoGroup(group_name, parent)
390 group = RepoGroup(group_name, parent)
389 group.group_description = desc
391 group.group_description = desc
390 sa.add(group)
392 sa.add(group)
391 rgm._create_default_perms(group)
393 rgm._create_default_perms(group)
392 sa.flush()
394 sa.flush()
393 parent = group
395 parent = group
394 return group
396 return group
395
397
396
398
397 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
399 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
398 install_git_hook=False):
400 install_git_hook=False):
399 """
401 """
400 maps all repos given in initial_repo_list, non existing repositories
402 maps all repos given in initial_repo_list, non existing repositories
401 are created, if remove_obsolete is True it also check for db entries
403 are created, if remove_obsolete is True it also check for db entries
402 that are not in initial_repo_list and removes them.
404 that are not in initial_repo_list and removes them.
403
405
404 :param initial_repo_list: list of repositories found by scanning methods
406 :param initial_repo_list: list of repositories found by scanning methods
405 :param remove_obsolete: check for obsolete entries in database
407 :param remove_obsolete: check for obsolete entries in database
406 :param install_git_hook: if this is True, also check and install githook
408 :param install_git_hook: if this is True, also check and install githook
407 for a repo if missing
409 for a repo if missing
408 """
410 """
409 from rhodecode.model.repo import RepoModel
411 from rhodecode.model.repo import RepoModel
410 from rhodecode.model.scm import ScmModel
412 from rhodecode.model.scm import ScmModel
411 sa = meta.Session()
413 sa = meta.Session()
412 rm = RepoModel()
414 rm = RepoModel()
413 user = sa.query(User).filter(User.admin == True).first()
415 user = sa.query(User).filter(User.admin == True).first()
414 if user is None:
416 if user is None:
415 raise Exception('Missing administrative account!')
417 raise Exception('Missing administrative account!')
416 added = []
418 added = []
417
419
418 # # clear cache keys
420 # # clear cache keys
419 # log.debug("Clearing cache keys now...")
421 # log.debug("Clearing cache keys now...")
420 # CacheInvalidation.clear_cache()
422 # CacheInvalidation.clear_cache()
421 # sa.commit()
423 # sa.commit()
422
424
423 for name, repo in initial_repo_list.items():
425 for name, repo in initial_repo_list.items():
424 group = map_groups(name)
426 group = map_groups(name)
425 db_repo = rm.get_by_repo_name(name)
427 db_repo = rm.get_by_repo_name(name)
426 # found repo that is on filesystem not in RhodeCode database
428 # found repo that is on filesystem not in RhodeCode database
427 if not db_repo:
429 if not db_repo:
428 log.info('repository %s not found, creating now' % name)
430 log.info('repository %s not found, creating now' % name)
429 added.append(name)
431 added.append(name)
430 desc = (repo.description
432 desc = (repo.description
431 if repo.description != 'unknown'
433 if repo.description != 'unknown'
432 else '%s repository' % name)
434 else '%s repository' % name)
433 new_repo = rm.create_repo(
435 new_repo = rm.create_repo(
434 repo_name=name,
436 repo_name=name,
435 repo_type=repo.alias,
437 repo_type=repo.alias,
436 description=desc,
438 description=desc,
437 repos_group=getattr(group, 'group_id', None),
439 repos_group=getattr(group, 'group_id', None),
438 owner=user,
440 owner=user,
439 just_db=True
441 just_db=True
440 )
442 )
441 # we added that repo just now, and make sure it has githook
443 # we added that repo just now, and make sure it has githook
442 # installed
444 # installed
443 if new_repo.repo_type == 'git':
445 if new_repo.repo_type == 'git':
444 ScmModel().install_git_hook(new_repo.scm_instance)
446 ScmModel().install_git_hook(new_repo.scm_instance)
445 elif install_git_hook:
447 elif install_git_hook:
446 if db_repo.repo_type == 'git':
448 if db_repo.repo_type == 'git':
447 ScmModel().install_git_hook(db_repo.scm_instance)
449 ScmModel().install_git_hook(db_repo.scm_instance)
448 # during starting install all cache keys for all repositories in the
450 # during starting install all cache keys for all repositories in the
449 # system, this will register all repos and multiple instances
451 # system, this will register all repos and multiple instances
450 key, _prefix, _org_key = CacheInvalidation._get_key(name)
452 key, _prefix, _org_key = CacheInvalidation._get_key(name)
451 CacheInvalidation.invalidate(name)
453 CacheInvalidation.invalidate(name)
452 log.debug("Creating a cache key for %s instance_id=>`%s`"
454 log.debug("Creating a cache key for %s instance_id=>`%s`"
453 % (name, _prefix or '-'))
455 % (name, _prefix or '-'))
454
456
455 sa.commit()
457 sa.commit()
456 removed = []
458 removed = []
457 if remove_obsolete:
459 if remove_obsolete:
458 # remove from database those repositories that are not in the filesystem
460 # remove from database those repositories that are not in the filesystem
459 for repo in sa.query(Repository).all():
461 for repo in sa.query(Repository).all():
460 if repo.repo_name not in initial_repo_list.keys():
462 if repo.repo_name not in initial_repo_list.keys():
461 log.debug("Removing non-existing repository found in db `%s`" %
463 log.debug("Removing non-existing repository found in db `%s`" %
462 repo.repo_name)
464 repo.repo_name)
463 try:
465 try:
464 sa.delete(repo)
466 sa.delete(repo)
465 sa.commit()
467 sa.commit()
466 removed.append(repo.repo_name)
468 removed.append(repo.repo_name)
467 except:
469 except:
468 #don't hold further removals on error
470 #don't hold further removals on error
469 log.error(traceback.format_exc())
471 log.error(traceback.format_exc())
470 sa.rollback()
472 sa.rollback()
471
473
472 return added, removed
474 return added, removed
473
475
474
476
475 # set cache regions for beaker so celery can utilise it
477 # set cache regions for beaker so celery can utilise it
476 def add_cache(settings):
478 def add_cache(settings):
477 cache_settings = {'regions': None}
479 cache_settings = {'regions': None}
478 for key in settings.keys():
480 for key in settings.keys():
479 for prefix in ['beaker.cache.', 'cache.']:
481 for prefix in ['beaker.cache.', 'cache.']:
480 if key.startswith(prefix):
482 if key.startswith(prefix):
481 name = key.split(prefix)[1].strip()
483 name = key.split(prefix)[1].strip()
482 cache_settings[name] = settings[key].strip()
484 cache_settings[name] = settings[key].strip()
483 if cache_settings['regions']:
485 if cache_settings['regions']:
484 for region in cache_settings['regions'].split(','):
486 for region in cache_settings['regions'].split(','):
485 region = region.strip()
487 region = region.strip()
486 region_settings = {}
488 region_settings = {}
487 for key, value in cache_settings.items():
489 for key, value in cache_settings.items():
488 if key.startswith(region):
490 if key.startswith(region):
489 region_settings[key.split('.')[1]] = value
491 region_settings[key.split('.')[1]] = value
490 region_settings['expire'] = int(region_settings.get('expire',
492 region_settings['expire'] = int(region_settings.get('expire',
491 60))
493 60))
492 region_settings.setdefault('lock_dir',
494 region_settings.setdefault('lock_dir',
493 cache_settings.get('lock_dir'))
495 cache_settings.get('lock_dir'))
494 region_settings.setdefault('data_dir',
496 region_settings.setdefault('data_dir',
495 cache_settings.get('data_dir'))
497 cache_settings.get('data_dir'))
496
498
497 if 'type' not in region_settings:
499 if 'type' not in region_settings:
498 region_settings['type'] = cache_settings.get('type',
500 region_settings['type'] = cache_settings.get('type',
499 'memory')
501 'memory')
500 beaker.cache.cache_regions[region] = region_settings
502 beaker.cache.cache_regions[region] = region_settings
501
503
502
504
503 def load_rcextensions(root_path):
505 def load_rcextensions(root_path):
504 import rhodecode
506 import rhodecode
505 from rhodecode.config import conf
507 from rhodecode.config import conf
506
508
507 path = os.path.join(root_path, 'rcextensions', '__init__.py')
509 path = os.path.join(root_path, 'rcextensions', '__init__.py')
508 if os.path.isfile(path):
510 if os.path.isfile(path):
509 rcext = create_module('rc', path)
511 rcext = create_module('rc', path)
510 EXT = rhodecode.EXTENSIONS = rcext
512 EXT = rhodecode.EXTENSIONS = rcext
511 log.debug('Found rcextensions now loading %s...' % rcext)
513 log.debug('Found rcextensions now loading %s...' % rcext)
512
514
513 # Additional mappings that are not present in the pygments lexers
515 # Additional mappings that are not present in the pygments lexers
514 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
516 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
515
517
516 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
518 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
517
519
518 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
520 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
519 log.debug('settings custom INDEX_EXTENSIONS')
521 log.debug('settings custom INDEX_EXTENSIONS')
520 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
522 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
521
523
522 #ADDITIONAL MAPPINGS
524 #ADDITIONAL MAPPINGS
523 log.debug('adding extra into INDEX_EXTENSIONS')
525 log.debug('adding extra into INDEX_EXTENSIONS')
524 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
526 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
525
527
526
528
527 #==============================================================================
529 #==============================================================================
528 # TEST FUNCTIONS AND CREATORS
530 # TEST FUNCTIONS AND CREATORS
529 #==============================================================================
531 #==============================================================================
530 def create_test_index(repo_location, config, full_index):
532 def create_test_index(repo_location, config, full_index):
531 """
533 """
532 Makes default test index
534 Makes default test index
533
535
534 :param config: test config
536 :param config: test config
535 :param full_index:
537 :param full_index:
536 """
538 """
537
539
538 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
540 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
539 from rhodecode.lib.pidlock import DaemonLock, LockHeld
541 from rhodecode.lib.pidlock import DaemonLock, LockHeld
540
542
541 repo_location = repo_location
543 repo_location = repo_location
542
544
543 index_location = os.path.join(config['app_conf']['index_dir'])
545 index_location = os.path.join(config['app_conf']['index_dir'])
544 if not os.path.exists(index_location):
546 if not os.path.exists(index_location):
545 os.makedirs(index_location)
547 os.makedirs(index_location)
546
548
547 try:
549 try:
548 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
550 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
549 WhooshIndexingDaemon(index_location=index_location,
551 WhooshIndexingDaemon(index_location=index_location,
550 repo_location=repo_location)\
552 repo_location=repo_location)\
551 .run(full_index=full_index)
553 .run(full_index=full_index)
552 l.release()
554 l.release()
553 except LockHeld:
555 except LockHeld:
554 pass
556 pass
555
557
556
558
557 def create_test_env(repos_test_path, config):
559 def create_test_env(repos_test_path, config):
558 """
560 """
559 Makes a fresh database and
561 Makes a fresh database and
560 install test repository into tmp dir
562 install test repository into tmp dir
561 """
563 """
562 from rhodecode.lib.db_manage import DbManage
564 from rhodecode.lib.db_manage import DbManage
563 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
565 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
564
566
565 # PART ONE create db
567 # PART ONE create db
566 dbconf = config['sqlalchemy.db1.url']
568 dbconf = config['sqlalchemy.db1.url']
567 log.debug('making test db %s' % dbconf)
569 log.debug('making test db %s' % dbconf)
568
570
569 # create test dir if it doesn't exist
571 # create test dir if it doesn't exist
570 if not os.path.isdir(repos_test_path):
572 if not os.path.isdir(repos_test_path):
571 log.debug('Creating testdir %s' % repos_test_path)
573 log.debug('Creating testdir %s' % repos_test_path)
572 os.makedirs(repos_test_path)
574 os.makedirs(repos_test_path)
573
575
574 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
576 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
575 tests=True)
577 tests=True)
576 dbmanage.create_tables(override=True)
578 dbmanage.create_tables(override=True)
577 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
579 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
578 dbmanage.create_default_user()
580 dbmanage.create_default_user()
579 dbmanage.admin_prompt()
581 dbmanage.admin_prompt()
580 dbmanage.create_permissions()
582 dbmanage.create_permissions()
581 dbmanage.populate_default_permissions()
583 dbmanage.populate_default_permissions()
582 Session().commit()
584 Session().commit()
583 # PART TWO make test repo
585 # PART TWO make test repo
584 log.debug('making test vcs repositories')
586 log.debug('making test vcs repositories')
585
587
586 idx_path = config['app_conf']['index_dir']
588 idx_path = config['app_conf']['index_dir']
587 data_path = config['app_conf']['cache_dir']
589 data_path = config['app_conf']['cache_dir']
588
590
589 #clean index and data
591 #clean index and data
590 if idx_path and os.path.exists(idx_path):
592 if idx_path and os.path.exists(idx_path):
591 log.debug('remove %s' % idx_path)
593 log.debug('remove %s' % idx_path)
592 shutil.rmtree(idx_path)
594 shutil.rmtree(idx_path)
593
595
594 if data_path and os.path.exists(data_path):
596 if data_path and os.path.exists(data_path):
595 log.debug('remove %s' % data_path)
597 log.debug('remove %s' % data_path)
596 shutil.rmtree(data_path)
598 shutil.rmtree(data_path)
597
599
598 #CREATE DEFAULT TEST REPOS
600 #CREATE DEFAULT TEST REPOS
599 cur_dir = dn(dn(abspath(__file__)))
601 cur_dir = dn(dn(abspath(__file__)))
600 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
602 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
601 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
603 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
602 tar.close()
604 tar.close()
603
605
604 cur_dir = dn(dn(abspath(__file__)))
606 cur_dir = dn(dn(abspath(__file__)))
605 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
607 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
606 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
608 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
607 tar.close()
609 tar.close()
608
610
609 #LOAD VCS test stuff
611 #LOAD VCS test stuff
610 from rhodecode.tests.vcs import setup_package
612 from rhodecode.tests.vcs import setup_package
611 setup_package()
613 setup_package()
612
614
613
615
614 #==============================================================================
616 #==============================================================================
615 # PASTER COMMANDS
617 # PASTER COMMANDS
616 #==============================================================================
618 #==============================================================================
617 class BasePasterCommand(Command):
619 class BasePasterCommand(Command):
618 """
620 """
619 Abstract Base Class for paster commands.
621 Abstract Base Class for paster commands.
620
622
621 The celery commands are somewhat aggressive about loading
623 The celery commands are somewhat aggressive about loading
622 celery.conf, and since our module sets the `CELERY_LOADER`
624 celery.conf, and since our module sets the `CELERY_LOADER`
623 environment variable to our loader, we have to bootstrap a bit and
625 environment variable to our loader, we have to bootstrap a bit and
624 make sure we've had a chance to load the pylons config off of the
626 make sure we've had a chance to load the pylons config off of the
625 command line, otherwise everything fails.
627 command line, otherwise everything fails.
626 """
628 """
627 min_args = 1
629 min_args = 1
628 min_args_error = "Please provide a paster config file as an argument."
630 min_args_error = "Please provide a paster config file as an argument."
629 takes_config_file = 1
631 takes_config_file = 1
630 requires_config_file = True
632 requires_config_file = True
631
633
632 def notify_msg(self, msg, log=False):
634 def notify_msg(self, msg, log=False):
633 """Make a notification to user, additionally if logger is passed
635 """Make a notification to user, additionally if logger is passed
634 it logs this action using given logger
636 it logs this action using given logger
635
637
636 :param msg: message that will be printed to user
638 :param msg: message that will be printed to user
637 :param log: logging instance, to use to additionally log this message
639 :param log: logging instance, to use to additionally log this message
638
640
639 """
641 """
640 if log and isinstance(log, logging):
642 if log and isinstance(log, logging):
641 log(msg)
643 log(msg)
642
644
643 def run(self, args):
645 def run(self, args):
644 """
646 """
645 Overrides Command.run
647 Overrides Command.run
646
648
647 Checks for a config file argument and loads it.
649 Checks for a config file argument and loads it.
648 """
650 """
649 if len(args) < self.min_args:
651 if len(args) < self.min_args:
650 raise BadCommand(
652 raise BadCommand(
651 self.min_args_error % {'min_args': self.min_args,
653 self.min_args_error % {'min_args': self.min_args,
652 'actual_args': len(args)})
654 'actual_args': len(args)})
653
655
654 # Decrement because we're going to lob off the first argument.
656 # Decrement because we're going to lob off the first argument.
655 # @@ This is hacky
657 # @@ This is hacky
656 self.min_args -= 1
658 self.min_args -= 1
657 self.bootstrap_config(args[0])
659 self.bootstrap_config(args[0])
658 self.update_parser()
660 self.update_parser()
659 return super(BasePasterCommand, self).run(args[1:])
661 return super(BasePasterCommand, self).run(args[1:])
660
662
661 def update_parser(self):
663 def update_parser(self):
662 """
664 """
663 Abstract method. Allows for the class's parser to be updated
665 Abstract method. Allows for the class's parser to be updated
664 before the superclass's `run` method is called. Necessary to
666 before the superclass's `run` method is called. Necessary to
665 allow options/arguments to be passed through to the underlying
667 allow options/arguments to be passed through to the underlying
666 celery command.
668 celery command.
667 """
669 """
668 raise NotImplementedError("Abstract Method.")
670 raise NotImplementedError("Abstract Method.")
669
671
670 def bootstrap_config(self, conf):
672 def bootstrap_config(self, conf):
671 """
673 """
672 Loads the pylons configuration.
674 Loads the pylons configuration.
673 """
675 """
674 from pylons import config as pylonsconfig
676 from pylons import config as pylonsconfig
675
677
676 self.path_to_ini_file = os.path.realpath(conf)
678 self.path_to_ini_file = os.path.realpath(conf)
677 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
679 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
678 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
680 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
679
681
680
682
681 def check_git_version():
683 def check_git_version():
682 """
684 """
683 Checks what version of git is installed in system, and issues a warning
685 Checks what version of git is installed in system, and issues a warning
684 if it's too old for RhodeCode to properly work.
686 if it's too old for RhodeCode to properly work.
685 """
687 """
686 import subprocess
688 import subprocess
687 from distutils.version import StrictVersion
689 from distutils.version import StrictVersion
688 from rhodecode import BACKENDS
690 from rhodecode import BACKENDS
689
691
690 p = subprocess.Popen('git --version', shell=True,
692 p = subprocess.Popen('git --version', shell=True,
691 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
693 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
692 stdout, stderr = p.communicate()
694 stdout, stderr = p.communicate()
693 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
695 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
694 if len(ver.split('.')) > 3:
696 if len(ver.split('.')) > 3:
695 #StrictVersion needs to be only 3 element type
697 #StrictVersion needs to be only 3 element type
696 ver = '.'.join(ver.split('.')[:3])
698 ver = '.'.join(ver.split('.')[:3])
697 try:
699 try:
698 _ver = StrictVersion(ver)
700 _ver = StrictVersion(ver)
699 except:
701 except:
700 _ver = StrictVersion('0.0.0')
702 _ver = StrictVersion('0.0.0')
701 stderr = traceback.format_exc()
703 stderr = traceback.format_exc()
702
704
703 req_ver = '1.7.4'
705 req_ver = '1.7.4'
704 to_old_git = False
706 to_old_git = False
705 if _ver < StrictVersion(req_ver):
707 if _ver < StrictVersion(req_ver):
706 to_old_git = True
708 to_old_git = True
707
709
708 if 'git' in BACKENDS:
710 if 'git' in BACKENDS:
709 log.debug('GIT version detected: %s' % stdout)
711 log.debug('GIT version detected: %s' % stdout)
710 if stderr:
712 if stderr:
711 log.warning('Unable to detect git version org error was:%r' % stderr)
713 log.warning('Unable to detect git version org error was:%r' % stderr)
712 elif to_old_git:
714 elif to_old_git:
713 log.warning('RhodeCode detected git version %s, which is too old '
715 log.warning('RhodeCode detected git version %s, which is too old '
714 'for the system to function properly. Make sure '
716 'for the system to function properly. Make sure '
715 'its version is at least %s' % (ver, req_ver))
717 'its version is at least %s' % (ver, req_ver))
716 return _ver
718 return _ver
719
720
721 @decorator.decorator
722 def jsonify(func, *args, **kwargs):
723 """Action decorator that formats output for JSON
724
725 Given a function that will return content, this decorator will turn
726 the result into JSON, with a content-type of 'application/json' and
727 output it.
728
729 """
730 from pylons.decorators.util import get_pylons
731 from rhodecode.lib.ext_json import json
732 pylons = get_pylons(args)
733 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
734 data = func(*args, **kwargs)
735 if isinstance(data, (list, tuple)):
736 msg = "JSON responses with Array envelopes are susceptible to " \
737 "cross-site data leak attacks, see " \
738 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
739 warnings.warn(msg, Warning, 2)
740 log.warning(msg)
741 log.debug("Returning JSON wrapped action output")
742 return json.dumps(data, encoding='utf-8') No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now