##// END OF EJS Templates
Added dynamic data loading for other repo we open pull request against...
marcink -
r2541:1c2ba03c beta
parent child Browse files
Show More
@@ -1,428 +1,428 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 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
29 from webob.exc import HTTPForbidden
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 pylons.decorators import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
39
39
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import EmptyChangeset, action_logger
43 from rhodecode.lib.utils import EmptyChangeset, action_logger
44 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
50 from rhodecode.lib.diffs import wrapped_diff
50 from rhodecode.lib.diffs import wrapped_diff
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 def _update_with_GET(params, GET):
56 def _update_with_GET(params, GET):
57 for k in ['diff1', 'diff2', 'diff']:
57 for k in ['diff1', 'diff2', 'diff']:
58 params[k] += GET.getall(k)
58 params[k] += GET.getall(k)
59
59
60
60
61 def anchor_url(revision, path, GET):
61 def anchor_url(revision, path, GET):
62 fid = h.FID(revision, path)
62 fid = h.FID(revision, path)
63 return h.url.current(anchor=fid, **dict(GET))
63 return h.url.current(anchor=fid, **dict(GET))
64
64
65
65
66 def get_ignore_ws(fid, GET):
66 def get_ignore_ws(fid, GET):
67 ig_ws_global = GET.get('ignorews')
67 ig_ws_global = GET.get('ignorews')
68 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
68 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
69 if ig_ws:
69 if ig_ws:
70 try:
70 try:
71 return int(ig_ws[0].split(':')[-1])
71 return int(ig_ws[0].split(':')[-1])
72 except:
72 except:
73 pass
73 pass
74 return ig_ws_global
74 return ig_ws_global
75
75
76
76
77 def _ignorews_url(GET, fileid=None):
77 def _ignorews_url(GET, fileid=None):
78 fileid = str(fileid) if fileid else None
78 fileid = str(fileid) if fileid else None
79 params = defaultdict(list)
79 params = defaultdict(list)
80 _update_with_GET(params, GET)
80 _update_with_GET(params, GET)
81 lbl = _('show white space')
81 lbl = _('show white space')
82 ig_ws = get_ignore_ws(fileid, GET)
82 ig_ws = get_ignore_ws(fileid, GET)
83 ln_ctx = get_line_ctx(fileid, GET)
83 ln_ctx = get_line_ctx(fileid, GET)
84 # global option
84 # global option
85 if fileid is None:
85 if fileid is None:
86 if ig_ws is None:
86 if ig_ws is None:
87 params['ignorews'] += [1]
87 params['ignorews'] += [1]
88 lbl = _('ignore white space')
88 lbl = _('ignore white space')
89 ctx_key = 'context'
89 ctx_key = 'context'
90 ctx_val = ln_ctx
90 ctx_val = ln_ctx
91 # per file options
91 # per file options
92 else:
92 else:
93 if ig_ws is None:
93 if ig_ws is None:
94 params[fileid] += ['WS:1']
94 params[fileid] += ['WS:1']
95 lbl = _('ignore white space')
95 lbl = _('ignore white space')
96
96
97 ctx_key = fileid
97 ctx_key = fileid
98 ctx_val = 'C:%s' % ln_ctx
98 ctx_val = 'C:%s' % ln_ctx
99 # if we have passed in ln_ctx pass it along to our params
99 # if we have passed in ln_ctx pass it along to our params
100 if ln_ctx:
100 if ln_ctx:
101 params[ctx_key] += [ctx_val]
101 params[ctx_key] += [ctx_val]
102
102
103 params['anchor'] = fileid
103 params['anchor'] = fileid
104 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
104 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
105 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
105 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
106
106
107
107
108 def get_line_ctx(fid, GET):
108 def get_line_ctx(fid, GET):
109 ln_ctx_global = GET.get('context')
109 ln_ctx_global = GET.get('context')
110 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
110 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
111
111
112 if ln_ctx:
112 if ln_ctx:
113 retval = ln_ctx[0].split(':')[-1]
113 retval = ln_ctx[0].split(':')[-1]
114 else:
114 else:
115 retval = ln_ctx_global
115 retval = ln_ctx_global
116
116
117 try:
117 try:
118 return int(retval)
118 return int(retval)
119 except:
119 except:
120 return
120 return
121
121
122
122
123 def _context_url(GET, fileid=None):
123 def _context_url(GET, fileid=None):
124 """
124 """
125 Generates url for context lines
125 Generates url for context lines
126
126
127 :param fileid:
127 :param fileid:
128 """
128 """
129
129
130 fileid = str(fileid) if fileid else None
130 fileid = str(fileid) if fileid else None
131 ig_ws = get_ignore_ws(fileid, GET)
131 ig_ws = get_ignore_ws(fileid, GET)
132 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
132 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
133
133
134 params = defaultdict(list)
134 params = defaultdict(list)
135 _update_with_GET(params, GET)
135 _update_with_GET(params, GET)
136
136
137 # global option
137 # global option
138 if fileid is None:
138 if fileid is None:
139 if ln_ctx > 0:
139 if ln_ctx > 0:
140 params['context'] += [ln_ctx]
140 params['context'] += [ln_ctx]
141
141
142 if ig_ws:
142 if ig_ws:
143 ig_ws_key = 'ignorews'
143 ig_ws_key = 'ignorews'
144 ig_ws_val = 1
144 ig_ws_val = 1
145
145
146 # per file option
146 # per file option
147 else:
147 else:
148 params[fileid] += ['C:%s' % ln_ctx]
148 params[fileid] += ['C:%s' % ln_ctx]
149 ig_ws_key = fileid
149 ig_ws_key = fileid
150 ig_ws_val = 'WS:%s' % 1
150 ig_ws_val = 'WS:%s' % 1
151
151
152 if ig_ws:
152 if ig_ws:
153 params[ig_ws_key] += [ig_ws_val]
153 params[ig_ws_key] += [ig_ws_val]
154
154
155 lbl = _('%s line context') % ln_ctx
155 lbl = _('%s line context') % ln_ctx
156
156
157 params['anchor'] = fileid
157 params['anchor'] = fileid
158 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
158 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
159 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
159 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
160
160
161
161
162 class ChangesetController(BaseRepoController):
162 class ChangesetController(BaseRepoController):
163
163
164 @LoginRequired()
164 @LoginRequired()
165 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
165 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
166 'repository.admin')
166 'repository.admin')
167 def __before__(self):
167 def __before__(self):
168 super(ChangesetController, self).__before__()
168 super(ChangesetController, self).__before__()
169 c.affected_files_cut_off = 60
169 c.affected_files_cut_off = 60
170 repo_model = RepoModel()
170 repo_model = RepoModel()
171 c.users_array = repo_model.get_users_js()
171 c.users_array = repo_model.get_users_js()
172 c.users_groups_array = repo_model.get_users_groups_js()
172 c.users_groups_array = repo_model.get_users_groups_js()
173
173
174 def index(self, revision):
174 def index(self, revision):
175
175
176 c.anchor_url = anchor_url
176 c.anchor_url = anchor_url
177 c.ignorews_url = _ignorews_url
177 c.ignorews_url = _ignorews_url
178 c.context_url = _context_url
178 c.context_url = _context_url
179 limit_off = request.GET.get('fulldiff')
179 limit_off = request.GET.get('fulldiff')
180 #get ranges of revisions if preset
180 #get ranges of revisions if preset
181 rev_range = revision.split('...')[:2]
181 rev_range = revision.split('...')[:2]
182 enable_comments = True
182 enable_comments = True
183 try:
183 try:
184 if len(rev_range) == 2:
184 if len(rev_range) == 2:
185 enable_comments = False
185 enable_comments = False
186 rev_start = rev_range[0]
186 rev_start = rev_range[0]
187 rev_end = rev_range[1]
187 rev_end = rev_range[1]
188 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
188 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
189 end=rev_end)
189 end=rev_end)
190 else:
190 else:
191 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
191 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
192
192
193 c.cs_ranges = list(rev_ranges)
193 c.cs_ranges = list(rev_ranges)
194 if not c.cs_ranges:
194 if not c.cs_ranges:
195 raise RepositoryError('Changeset range returned empty result')
195 raise RepositoryError('Changeset range returned empty result')
196
196
197 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
197 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
198 log.error(traceback.format_exc())
198 log.error(traceback.format_exc())
199 h.flash(str(e), category='warning')
199 h.flash(str(e), category='warning')
200 return redirect(url('home'))
200 return redirect(url('home'))
201
201
202 c.changes = OrderedDict()
202 c.changes = OrderedDict()
203
203
204 c.lines_added = 0 # count of lines added
204 c.lines_added = 0 # count of lines added
205 c.lines_deleted = 0 # count of lines removes
205 c.lines_deleted = 0 # count of lines removes
206
206
207 cumulative_diff = 0
207 cumulative_diff = 0
208 c.cut_off = False # defines if cut off limit is reached
208 c.cut_off = False # defines if cut off limit is reached
209 c.changeset_statuses = ChangesetStatus.STATUSES
209 c.changeset_statuses = ChangesetStatus.STATUSES
210 c.comments = []
210 c.comments = []
211 c.statuses = []
211 c.statuses = []
212 c.inline_comments = []
212 c.inline_comments = []
213 c.inline_cnt = 0
213 c.inline_cnt = 0
214 # Iterate over ranges (default changeset view is always one changeset)
214 # Iterate over ranges (default changeset view is always one changeset)
215 for changeset in c.cs_ranges:
215 for changeset in c.cs_ranges:
216
216
217 c.statuses.extend([ChangesetStatusModel()\
217 c.statuses.extend([ChangesetStatusModel()\
218 .get_status(c.rhodecode_db_repo.repo_id,
218 .get_status(c.rhodecode_db_repo.repo_id,
219 changeset.raw_id)])
219 changeset.raw_id)])
220
220
221 c.comments.extend(ChangesetCommentsModel()\
221 c.comments.extend(ChangesetCommentsModel()\
222 .get_comments(c.rhodecode_db_repo.repo_id,
222 .get_comments(c.rhodecode_db_repo.repo_id,
223 revision=changeset.raw_id))
223 revision=changeset.raw_id))
224 inlines = ChangesetCommentsModel()\
224 inlines = ChangesetCommentsModel()\
225 .get_inline_comments(c.rhodecode_db_repo.repo_id,
225 .get_inline_comments(c.rhodecode_db_repo.repo_id,
226 revision=changeset.raw_id)
226 revision=changeset.raw_id)
227 c.inline_comments.extend(inlines)
227 c.inline_comments.extend(inlines)
228 c.changes[changeset.raw_id] = []
228 c.changes[changeset.raw_id] = []
229 try:
229 try:
230 changeset_parent = changeset.parents[0]
230 changeset_parent = changeset.parents[0]
231 except IndexError:
231 except IndexError:
232 changeset_parent = None
232 changeset_parent = None
233
233
234 #==================================================================
234 #==================================================================
235 # ADDED FILES
235 # ADDED FILES
236 #==================================================================
236 #==================================================================
237 for node in changeset.added:
237 for node in changeset.added:
238 fid = h.FID(revision, node.path)
238 fid = h.FID(revision, node.path)
239 line_context_lcl = get_line_ctx(fid, request.GET)
239 line_context_lcl = get_line_ctx(fid, request.GET)
240 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
240 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
241 lim = self.cut_off_limit
241 lim = self.cut_off_limit
242 if cumulative_diff > self.cut_off_limit:
242 if cumulative_diff > self.cut_off_limit:
243 lim = -1 if limit_off is None else None
243 lim = -1 if limit_off is None else None
244 size, cs1, cs2, diff, st = wrapped_diff(
244 size, cs1, cs2, diff, st = wrapped_diff(
245 filenode_old=None,
245 filenode_old=None,
246 filenode_new=node,
246 filenode_new=node,
247 cut_off_limit=lim,
247 cut_off_limit=lim,
248 ignore_whitespace=ign_whitespace_lcl,
248 ignore_whitespace=ign_whitespace_lcl,
249 line_context=line_context_lcl,
249 line_context=line_context_lcl,
250 enable_comments=enable_comments
250 enable_comments=enable_comments
251 )
251 )
252 cumulative_diff += size
252 cumulative_diff += size
253 c.lines_added += st[0]
253 c.lines_added += st[0]
254 c.lines_deleted += st[1]
254 c.lines_deleted += st[1]
255 c.changes[changeset.raw_id].append(
255 c.changes[changeset.raw_id].append(
256 ('added', node, diff, cs1, cs2, st)
256 ('added', node, diff, cs1, cs2, st)
257 )
257 )
258
258
259 #==================================================================
259 #==================================================================
260 # CHANGED FILES
260 # CHANGED FILES
261 #==================================================================
261 #==================================================================
262 for node in changeset.changed:
262 for node in changeset.changed:
263 try:
263 try:
264 filenode_old = changeset_parent.get_node(node.path)
264 filenode_old = changeset_parent.get_node(node.path)
265 except ChangesetError:
265 except ChangesetError:
266 log.warning('Unable to fetch parent node for diff')
266 log.warning('Unable to fetch parent node for diff')
267 filenode_old = FileNode(node.path, '', EmptyChangeset())
267 filenode_old = FileNode(node.path, '', EmptyChangeset())
268
268
269 fid = h.FID(revision, node.path)
269 fid = h.FID(revision, node.path)
270 line_context_lcl = get_line_ctx(fid, request.GET)
270 line_context_lcl = get_line_ctx(fid, request.GET)
271 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
271 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
272 lim = self.cut_off_limit
272 lim = self.cut_off_limit
273 if cumulative_diff > self.cut_off_limit:
273 if cumulative_diff > self.cut_off_limit:
274 lim = -1 if limit_off is None else None
274 lim = -1 if limit_off is None else None
275 size, cs1, cs2, diff, st = wrapped_diff(
275 size, cs1, cs2, diff, st = wrapped_diff(
276 filenode_old=filenode_old,
276 filenode_old=filenode_old,
277 filenode_new=node,
277 filenode_new=node,
278 cut_off_limit=lim,
278 cut_off_limit=lim,
279 ignore_whitespace=ign_whitespace_lcl,
279 ignore_whitespace=ign_whitespace_lcl,
280 line_context=line_context_lcl,
280 line_context=line_context_lcl,
281 enable_comments=enable_comments
281 enable_comments=enable_comments
282 )
282 )
283 cumulative_diff += size
283 cumulative_diff += size
284 c.lines_added += st[0]
284 c.lines_added += st[0]
285 c.lines_deleted += st[1]
285 c.lines_deleted += st[1]
286 c.changes[changeset.raw_id].append(
286 c.changes[changeset.raw_id].append(
287 ('changed', node, diff, cs1, cs2, st)
287 ('changed', node, diff, cs1, cs2, st)
288 )
288 )
289 #==================================================================
289 #==================================================================
290 # REMOVED FILES
290 # REMOVED FILES
291 #==================================================================
291 #==================================================================
292 for node in changeset.removed:
292 for node in changeset.removed:
293 c.changes[changeset.raw_id].append(
293 c.changes[changeset.raw_id].append(
294 ('removed', node, None, None, None, (0, 0))
294 ('removed', node, None, None, None, (0, 0))
295 )
295 )
296
296
297 # count inline comments
297 # count inline comments
298 for __, lines in c.inline_comments:
298 for __, lines in c.inline_comments:
299 for comments in lines.values():
299 for comments in lines.values():
300 c.inline_cnt += len(comments)
300 c.inline_cnt += len(comments)
301
301
302 if len(c.cs_ranges) == 1:
302 if len(c.cs_ranges) == 1:
303 c.changeset = c.cs_ranges[0]
303 c.changeset = c.cs_ranges[0]
304 c.changes = c.changes[c.changeset.raw_id]
304 c.changes = c.changes[c.changeset.raw_id]
305
305
306 return render('changeset/changeset.html')
306 return render('changeset/changeset.html')
307 else:
307 else:
308 return render('changeset/changeset_range.html')
308 return render('changeset/changeset_range.html')
309
309
310 def raw_changeset(self, revision):
310 def raw_changeset(self, revision):
311
311
312 method = request.GET.get('diff', 'show')
312 method = request.GET.get('diff', 'show')
313 ignore_whitespace = request.GET.get('ignorews') == '1'
313 ignore_whitespace = request.GET.get('ignorews') == '1'
314 line_context = request.GET.get('context', 3)
314 line_context = request.GET.get('context', 3)
315 try:
315 try:
316 c.scm_type = c.rhodecode_repo.alias
316 c.scm_type = c.rhodecode_repo.alias
317 c.changeset = c.rhodecode_repo.get_changeset(revision)
317 c.changeset = c.rhodecode_repo.get_changeset(revision)
318 except RepositoryError:
318 except RepositoryError:
319 log.error(traceback.format_exc())
319 log.error(traceback.format_exc())
320 return redirect(url('home'))
320 return redirect(url('home'))
321 else:
321 else:
322 try:
322 try:
323 c.changeset_parent = c.changeset.parents[0]
323 c.changeset_parent = c.changeset.parents[0]
324 except IndexError:
324 except IndexError:
325 c.changeset_parent = None
325 c.changeset_parent = None
326 c.changes = []
326 c.changes = []
327
327
328 for node in c.changeset.added:
328 for node in c.changeset.added:
329 filenode_old = FileNode(node.path, '')
329 filenode_old = FileNode(node.path, '')
330 if filenode_old.is_binary or node.is_binary:
330 if filenode_old.is_binary or node.is_binary:
331 diff = _('binary file') + '\n'
331 diff = _('binary file') + '\n'
332 else:
332 else:
333 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
333 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
334 ignore_whitespace=ignore_whitespace,
334 ignore_whitespace=ignore_whitespace,
335 context=line_context)
335 context=line_context)
336 diff = diffs.DiffProcessor(f_gitdiff,
336 diff = diffs.DiffProcessor(f_gitdiff,
337 format='gitdiff').raw_diff()
337 format='gitdiff').raw_diff()
338
338
339 cs1 = None
339 cs1 = None
340 cs2 = node.changeset.raw_id
340 cs2 = node.changeset.raw_id
341 c.changes.append(('added', node, diff, cs1, cs2))
341 c.changes.append(('added', node, diff, cs1, cs2))
342
342
343 for node in c.changeset.changed:
343 for node in c.changeset.changed:
344 filenode_old = c.changeset_parent.get_node(node.path)
344 filenode_old = c.changeset_parent.get_node(node.path)
345 if filenode_old.is_binary or node.is_binary:
345 if filenode_old.is_binary or node.is_binary:
346 diff = _('binary file')
346 diff = _('binary file')
347 else:
347 else:
348 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
348 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
349 ignore_whitespace=ignore_whitespace,
349 ignore_whitespace=ignore_whitespace,
350 context=line_context)
350 context=line_context)
351 diff = diffs.DiffProcessor(f_gitdiff,
351 diff = diffs.DiffProcessor(f_gitdiff,
352 format='gitdiff').raw_diff()
352 format='gitdiff').raw_diff()
353
353
354 cs1 = filenode_old.changeset.raw_id
354 cs1 = filenode_old.changeset.raw_id
355 cs2 = node.changeset.raw_id
355 cs2 = node.changeset.raw_id
356 c.changes.append(('changed', node, diff, cs1, cs2))
356 c.changes.append(('changed', node, diff, cs1, cs2))
357
357
358 response.content_type = 'text/plain'
358 response.content_type = 'text/plain'
359
359
360 if method == 'download':
360 if method == 'download':
361 response.content_disposition = 'attachment; filename=%s.patch' \
361 response.content_disposition = 'attachment; filename=%s.patch' \
362 % revision
362 % revision
363
363
364 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
364 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
365 for x in c.changeset.parents])
365 for x in c.changeset.parents])
366
366
367 c.diffs = ''
367 c.diffs = ''
368 for x in c.changes:
368 for x in c.changes:
369 c.diffs += x[2]
369 c.diffs += x[2]
370
370
371 return render('changeset/raw_changeset.html')
371 return render('changeset/raw_changeset.html')
372
372
373 @jsonify
373 @jsonify
374 def comment(self, repo_name, revision):
374 def comment(self, repo_name, revision):
375 status = request.POST.get('changeset_status')
375 status = request.POST.get('changeset_status')
376 change_status = request.POST.get('change_changeset_status')
376 change_status = request.POST.get('change_changeset_status')
377
377
378 comm = ChangesetCommentsModel().create(
378 comm = ChangesetCommentsModel().create(
379 text=request.POST.get('text'),
379 text=request.POST.get('text'),
380 repo_id=c.rhodecode_db_repo.repo_id,
380 repo=c.rhodecode_db_repo.repo_id,
381 user_id=c.rhodecode_user.user_id,
381 user=c.rhodecode_user.user_id,
382 revision=revision,
382 revision=revision,
383 f_path=request.POST.get('f_path'),
383 f_path=request.POST.get('f_path'),
384 line_no=request.POST.get('line'),
384 line_no=request.POST.get('line'),
385 status_change=(ChangesetStatus.get_status_lbl(status)
385 status_change=(ChangesetStatus.get_status_lbl(status)
386 if status and change_status else None)
386 if status and change_status else None)
387 )
387 )
388
388
389 # get status if set !
389 # get status if set !
390 if status and change_status:
390 if status and change_status:
391 ChangesetStatusModel().set_status(
391 ChangesetStatusModel().set_status(
392 c.rhodecode_db_repo.repo_id,
392 c.rhodecode_db_repo.repo_id,
393 status,
393 status,
394 c.rhodecode_user.user_id,
394 c.rhodecode_user.user_id,
395 comm,
395 comm,
396 revision=revision,
396 revision=revision,
397 )
397 )
398 action_logger(self.rhodecode_user,
398 action_logger(self.rhodecode_user,
399 'user_commented_revision:%s' % revision,
399 'user_commented_revision:%s' % revision,
400 c.rhodecode_db_repo, self.ip_addr, self.sa)
400 c.rhodecode_db_repo, self.ip_addr, self.sa)
401
401
402 Session.commit()
402 Session.commit()
403
403
404 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
404 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
405 return redirect(h.url('changeset_home', repo_name=repo_name,
405 return redirect(h.url('changeset_home', repo_name=repo_name,
406 revision=revision))
406 revision=revision))
407
407
408 data = {
408 data = {
409 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
409 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
410 }
410 }
411 if comm:
411 if comm:
412 c.co = comm
412 c.co = comm
413 data.update(comm.get_dict())
413 data.update(comm.get_dict())
414 data.update({'rendered_text':
414 data.update({'rendered_text':
415 render('changeset/changeset_comment_block.html')})
415 render('changeset/changeset_comment_block.html')})
416
416
417 return data
417 return data
418
418
419 @jsonify
419 @jsonify
420 def delete_comment(self, repo_name, comment_id):
420 def delete_comment(self, repo_name, comment_id):
421 co = ChangesetComment.get(comment_id)
421 co = ChangesetComment.get(comment_id)
422 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
422 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
423 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
423 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
424 ChangesetCommentsModel().delete(comment=co)
424 ChangesetCommentsModel().delete(comment=co)
425 Session.commit()
425 Session.commit()
426 return True
426 return True
427 else:
427 else:
428 raise HTTPForbidden()
428 raise HTTPForbidden()
@@ -1,308 +1,328 b''
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
27
28 from webob.exc import HTTPNotFound, HTTPForbidden
28 from webob.exc import HTTPNotFound, HTTPForbidden
29 from collections import defaultdict
29 from collections import defaultdict
30 from itertools import groupby
30 from itertools import groupby
31
31
32 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pylons.decorators import jsonify
35 from pylons.decorators import jsonify
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import diffs
41 from rhodecode.lib import diffs
41 from rhodecode.lib.utils import action_logger
42 from rhodecode.lib.utils import action_logger
42 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
43 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
43 ChangesetComment
44 ChangesetComment
44 from rhodecode.model.pull_request import PullRequestModel
45 from rhodecode.model.pull_request import PullRequestModel
45 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
46 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.changeset_status import ChangesetStatusModel
49
50
50 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
51
52
52
53
53 class PullrequestsController(BaseRepoController):
54 class PullrequestsController(BaseRepoController):
54
55
55 @LoginRequired()
56 @LoginRequired()
56 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
57 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
57 'repository.admin')
58 'repository.admin')
58 def __before__(self):
59 def __before__(self):
59 super(PullrequestsController, self).__before__()
60 super(PullrequestsController, self).__before__()
60
61
61 def _get_repo_refs(self, repo):
62 def _get_repo_refs(self, repo):
62 hist_l = []
63 hist_l = []
63
64
64 branches_group = ([('branch:%s:%s' % (k, v), k) for
65 branches_group = ([('branch:%s:%s' % (k, v), k) for
65 k, v in repo.branches.iteritems()], _("Branches"))
66 k, v in repo.branches.iteritems()], _("Branches"))
66 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
67 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
67 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
68 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
68 tags_group = ([('tag:%s:%s' % (k, v), k) for
69 tags_group = ([('tag:%s:%s' % (k, v), k) for
69 k, v in repo.tags.iteritems()], _("Tags"))
70 k, v in repo.tags.iteritems()], _("Tags"))
70
71
71 hist_l.append(bookmarks_group)
72 hist_l.append(bookmarks_group)
72 hist_l.append(branches_group)
73 hist_l.append(branches_group)
73 hist_l.append(tags_group)
74 hist_l.append(tags_group)
74
75
75 return hist_l
76 return hist_l
76
77
77 def show_all(self, repo_name):
78 def show_all(self, repo_name):
78 c.pull_requests = PullRequestModel().get_all(repo_name)
79 c.pull_requests = PullRequestModel().get_all(repo_name)
79 c.repo_name = repo_name
80 c.repo_name = repo_name
80 return render('/pullrequests/pullrequest_show_all.html')
81 return render('/pullrequests/pullrequest_show_all.html')
81
82
82 def index(self):
83 def index(self):
83 org_repo = c.rhodecode_db_repo
84 org_repo = c.rhodecode_db_repo
84
85
85 if org_repo.scm_instance.alias != 'hg':
86 if org_repo.scm_instance.alias != 'hg':
86 log.error('Review not available for GIT REPOS')
87 log.error('Review not available for GIT REPOS')
87 raise HTTPNotFound
88 raise HTTPNotFound
88
89
90 other_repos_info = {}
91
89 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
92 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
90 c.org_repos = []
93 c.org_repos = []
91 c.other_repos = []
94 c.other_repos = []
92 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
95 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
93 org_repo.user.username, c.repo_name))
96 org_repo.user.username, c.repo_name))
94 )
97 )
95
98
96 c.other_refs = c.org_refs
99 c.other_refs = c.org_refs
97 c.other_repos.extend(c.org_repos)
100 c.other_repos.extend(c.org_repos)
101
102 #add orginal repo
103 other_repos_info[org_repo.repo_name] = {
104 'gravatar': h.gravatar_url(org_repo.user.email, 24),
105 'description': org_repo.description
106 }
107
98 c.default_pull_request = org_repo.repo_name
108 c.default_pull_request = org_repo.repo_name
99 #gather forks and add to this list
109 #gather forks and add to this list
100 for fork in org_repo.forks:
110 for fork in org_repo.forks:
101 c.other_repos.append((fork.repo_name, '%s/%s' % (
111 c.other_repos.append((fork.repo_name, '%s/%s' % (
102 fork.user.username, fork.repo_name))
112 fork.user.username, fork.repo_name))
103 )
113 )
114 other_repos_info[fork.repo_name] = {
115 'gravatar': h.gravatar_url(fork.user.email, 24),
116 'description': fork.description
117 }
104 #add parents of this fork also
118 #add parents of this fork also
105 if org_repo.parent:
119 if org_repo.parent:
106 c.default_pull_request = org_repo.parent.repo_name
120 c.default_pull_request = org_repo.parent.repo_name
107 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
121 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
108 org_repo.parent.user.username,
122 org_repo.parent.user.username,
109 org_repo.parent.repo_name))
123 org_repo.parent.repo_name))
110 )
124 )
125 other_repos_info[org_repo.parent.repo_name] = {
126 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
127 'description': org_repo.parent.description
128 }
111
129
130 c.other_repos_info = json.dumps(other_repos_info)
112 c.review_members = []
131 c.review_members = []
113 c.available_members = []
132 c.available_members = []
114 for u in User.query().filter(User.username != 'default').all():
133 for u in User.query().filter(User.username != 'default').all():
115 uname = u.username
134 uname = u.username
116 if org_repo.user == u:
135 if org_repo.user == u:
117 uname = _('%s (owner)' % u.username)
136 uname = _('%s (owner)' % u.username)
118 # auto add owner to pull-request recipients
137 # auto add owner to pull-request recipients
119 c.review_members.append([u.user_id, uname])
138 c.review_members.append([u.user_id, uname])
120 c.available_members.append([u.user_id, uname])
139 c.available_members.append([u.user_id, uname])
121 return render('/pullrequests/pullrequest.html')
140 return render('/pullrequests/pullrequest.html')
122
141
123 def create(self, repo_name):
142 def create(self, repo_name):
124 req_p = request.POST
143 req_p = request.POST
125 org_repo = req_p['org_repo']
144 org_repo = req_p['org_repo']
126 org_ref = req_p['org_ref']
145 org_ref = req_p['org_ref']
127 other_repo = req_p['other_repo']
146 other_repo = req_p['other_repo']
128 other_ref = req_p['other_ref']
147 other_ref = req_p['other_ref']
129 revisions = req_p.getall('revisions')
148 revisions = req_p.getall('revisions')
130 reviewers = req_p.getall('review_members')
149 reviewers = req_p.getall('review_members')
131 #TODO: wrap this into a FORM !!!
150 #TODO: wrap this into a FORM !!!
132
151
133 title = req_p['pullrequest_title']
152 title = req_p['pullrequest_title']
134 description = req_p['pullrequest_desc']
153 description = req_p['pullrequest_desc']
135
154
136 try:
155 try:
137 model = PullRequestModel()
156 pull_request = PullRequestModel().create(
138 pull_request = model.create(self.rhodecode_user.user_id, org_repo,
157 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
139 org_ref, other_repo, other_ref, revisions,
158 other_ref, revisions, reviewers, title, description
140 reviewers, title, description)
159 )
141 Session.commit()
160 Session().commit()
142 h.flash(_('Successfully opened new pull request'),
161 h.flash(_('Successfully opened new pull request'),
143 category='success')
162 category='success')
144 except Exception:
163 except Exception:
145 h.flash(_('Error occurred during sending pull request'),
164 h.flash(_('Error occurred during sending pull request'),
146 category='error')
165 category='error')
147 log.error(traceback.format_exc())
166 log.error(traceback.format_exc())
167 return redirect(url('changelog_home', repo_name=org_repo,))
148
168
149 return redirect(url('pullrequest_show', repo_name=other_repo,
169 return redirect(url('pullrequest_show', repo_name=other_repo,
150 pull_request_id=pull_request.pull_request_id))
170 pull_request_id=pull_request.pull_request_id))
151
171
152 def _load_compare_data(self, pull_request):
172 def _load_compare_data(self, pull_request):
153 """
173 """
154 Load context data needed for generating compare diff
174 Load context data needed for generating compare diff
155
175
156 :param pull_request:
176 :param pull_request:
157 :type pull_request:
177 :type pull_request:
158 """
178 """
159
179
160 org_repo = pull_request.org_repo
180 org_repo = pull_request.org_repo
161 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
181 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
162 other_repo = pull_request.other_repo
182 other_repo = pull_request.other_repo
163 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
183 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
164
184
165 org_ref = (org_ref_type, org_ref)
185 org_ref = (org_ref_type, org_ref)
166 other_ref = (other_ref_type, other_ref)
186 other_ref = (other_ref_type, other_ref)
167
187
168 c.org_repo = org_repo
188 c.org_repo = org_repo
169 c.other_repo = other_repo
189 c.other_repo = other_repo
170
190
171 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
191 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
172 org_repo, org_ref, other_repo, other_ref
192 org_repo, org_ref, other_repo, other_ref
173 )
193 )
174
194
175 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
195 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
176 c.cs_ranges])
196 c.cs_ranges])
177 # defines that we need hidden inputs with changesets
197 # defines that we need hidden inputs with changesets
178 c.as_form = request.GET.get('as_form', False)
198 c.as_form = request.GET.get('as_form', False)
179
199
180 c.org_ref = org_ref[1]
200 c.org_ref = org_ref[1]
181 c.other_ref = other_ref[1]
201 c.other_ref = other_ref[1]
182 # diff needs to have swapped org with other to generate proper diff
202 # diff needs to have swapped org with other to generate proper diff
183 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
203 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
184 discovery_data)
204 discovery_data)
185 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
205 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
186 _parsed = diff_processor.prepare()
206 _parsed = diff_processor.prepare()
187
207
188 c.files = []
208 c.files = []
189 c.changes = {}
209 c.changes = {}
190
210
191 for f in _parsed:
211 for f in _parsed:
192 fid = h.FID('', f['filename'])
212 fid = h.FID('', f['filename'])
193 c.files.append([fid, f['operation'], f['filename'], f['stats']])
213 c.files.append([fid, f['operation'], f['filename'], f['stats']])
194 diff = diff_processor.as_html(enable_comments=True,
214 diff = diff_processor.as_html(enable_comments=True,
195 diff_lines=[f])
215 diff_lines=[f])
196 c.changes[fid] = [f['operation'], f['filename'], diff]
216 c.changes[fid] = [f['operation'], f['filename'], diff]
197
217
198 def show(self, repo_name, pull_request_id):
218 def show(self, repo_name, pull_request_id):
199 repo_model = RepoModel()
219 repo_model = RepoModel()
200 c.users_array = repo_model.get_users_js()
220 c.users_array = repo_model.get_users_js()
201 c.users_groups_array = repo_model.get_users_groups_js()
221 c.users_groups_array = repo_model.get_users_groups_js()
202 c.pull_request = PullRequest.get_or_404(pull_request_id)
222 c.pull_request = PullRequest.get_or_404(pull_request_id)
203
223
204 cc_model = ChangesetCommentsModel()
224 cc_model = ChangesetCommentsModel()
205 cs_model = ChangesetStatusModel()
225 cs_model = ChangesetStatusModel()
206 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
226 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
207 pull_request=c.pull_request,
227 pull_request=c.pull_request,
208 with_revisions=True)
228 with_revisions=True)
209
229
210 cs_statuses = defaultdict(list)
230 cs_statuses = defaultdict(list)
211 for st in _cs_statuses:
231 for st in _cs_statuses:
212 cs_statuses[st.author.username] += [st]
232 cs_statuses[st.author.username] += [st]
213
233
214 c.pull_request_reviewers = []
234 c.pull_request_reviewers = []
215 for o in c.pull_request.reviewers:
235 for o in c.pull_request.reviewers:
216 st = cs_statuses.get(o.user.username, None)
236 st = cs_statuses.get(o.user.username, None)
217 if st:
237 if st:
218 sorter = lambda k: k.version
238 sorter = lambda k: k.version
219 st = [(x, list(y)[0])
239 st = [(x, list(y)[0])
220 for x, y in (groupby(sorted(st, key=sorter), sorter))]
240 for x, y in (groupby(sorted(st, key=sorter), sorter))]
221 c.pull_request_reviewers.append([o.user, st])
241 c.pull_request_reviewers.append([o.user, st])
222
242
223 # pull_requests repo_name we opened it against
243 # pull_requests repo_name we opened it against
224 # ie. other_repo must match
244 # ie. other_repo must match
225 if repo_name != c.pull_request.other_repo.repo_name:
245 if repo_name != c.pull_request.other_repo.repo_name:
226 raise HTTPNotFound
246 raise HTTPNotFound
227
247
228 # load compare data into template context
248 # load compare data into template context
229 self._load_compare_data(c.pull_request)
249 self._load_compare_data(c.pull_request)
230
250
231 # inline comments
251 # inline comments
232 c.inline_cnt = 0
252 c.inline_cnt = 0
233 c.inline_comments = cc_model.get_inline_comments(
253 c.inline_comments = cc_model.get_inline_comments(
234 c.rhodecode_db_repo.repo_id,
254 c.rhodecode_db_repo.repo_id,
235 pull_request=pull_request_id)
255 pull_request=pull_request_id)
236 # count inline comments
256 # count inline comments
237 for __, lines in c.inline_comments:
257 for __, lines in c.inline_comments:
238 for comments in lines.values():
258 for comments in lines.values():
239 c.inline_cnt += len(comments)
259 c.inline_cnt += len(comments)
240 # comments
260 # comments
241 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
261 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
242 pull_request=pull_request_id)
262 pull_request=pull_request_id)
243
263
244 # changeset(pull-request) status
264 # changeset(pull-request) status
245 c.current_changeset_status = cs_model.calculate_status(
265 c.current_changeset_status = cs_model.calculate_status(
246 c.pull_request_reviewers
266 c.pull_request_reviewers
247 )
267 )
248 c.changeset_statuses = ChangesetStatus.STATUSES
268 c.changeset_statuses = ChangesetStatus.STATUSES
249 c.target_repo = c.pull_request.org_repo.repo_name
269 c.target_repo = c.pull_request.org_repo.repo_name
250 return render('/pullrequests/pullrequest_show.html')
270 return render('/pullrequests/pullrequest_show.html')
251
271
252 @jsonify
272 @jsonify
253 def comment(self, repo_name, pull_request_id):
273 def comment(self, repo_name, pull_request_id):
254
274
255 status = request.POST.get('changeset_status')
275 status = request.POST.get('changeset_status')
256 change_status = request.POST.get('change_changeset_status')
276 change_status = request.POST.get('change_changeset_status')
257
277
258 comm = ChangesetCommentsModel().create(
278 comm = ChangesetCommentsModel().create(
259 text=request.POST.get('text'),
279 text=request.POST.get('text'),
260 repo_id=c.rhodecode_db_repo.repo_id,
280 repo=c.rhodecode_db_repo.repo_id,
261 user_id=c.rhodecode_user.user_id,
281 user=c.rhodecode_user.user_id,
262 pull_request=pull_request_id,
282 pull_request=pull_request_id,
263 f_path=request.POST.get('f_path'),
283 f_path=request.POST.get('f_path'),
264 line_no=request.POST.get('line'),
284 line_no=request.POST.get('line'),
265 status_change=(ChangesetStatus.get_status_lbl(status)
285 status_change=(ChangesetStatus.get_status_lbl(status)
266 if status and change_status else None)
286 if status and change_status else None)
267 )
287 )
268
288
269 # get status if set !
289 # get status if set !
270 if status and change_status:
290 if status and change_status:
271 ChangesetStatusModel().set_status(
291 ChangesetStatusModel().set_status(
272 c.rhodecode_db_repo.repo_id,
292 c.rhodecode_db_repo.repo_id,
273 status,
293 status,
274 c.rhodecode_user.user_id,
294 c.rhodecode_user.user_id,
275 comm,
295 comm,
276 pull_request=pull_request_id
296 pull_request=pull_request_id
277 )
297 )
278 action_logger(self.rhodecode_user,
298 action_logger(self.rhodecode_user,
279 'user_commented_pull_request:%s' % pull_request_id,
299 'user_commented_pull_request:%s' % pull_request_id,
280 c.rhodecode_db_repo, self.ip_addr, self.sa)
300 c.rhodecode_db_repo, self.ip_addr, self.sa)
281
301
282 Session.commit()
302 Session().commit()
283
303
284 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
304 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
285 return redirect(h.url('pullrequest_show', repo_name=repo_name,
305 return redirect(h.url('pullrequest_show', repo_name=repo_name,
286 pull_request_id=pull_request_id))
306 pull_request_id=pull_request_id))
287
307
288 data = {
308 data = {
289 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
309 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
290 }
310 }
291 if comm:
311 if comm:
292 c.co = comm
312 c.co = comm
293 data.update(comm.get_dict())
313 data.update(comm.get_dict())
294 data.update({'rendered_text':
314 data.update({'rendered_text':
295 render('changeset/changeset_comment_block.html')})
315 render('changeset/changeset_comment_block.html')})
296
316
297 return data
317 return data
298
318
299 @jsonify
319 @jsonify
300 def delete_comment(self, repo_name, comment_id):
320 def delete_comment(self, repo_name, comment_id):
301 co = ChangesetComment.get(comment_id)
321 co = ChangesetComment.get(comment_id)
302 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
322 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
303 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
323 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
304 ChangesetCommentsModel().delete(comment=co)
324 ChangesetCommentsModel().delete(comment=co)
305 Session.commit()
325 Session().commit()
306 return True
326 return True
307 else:
327 else:
308 raise HTTPForbidden() No newline at end of file
328 raise HTTPForbidden()
@@ -1,213 +1,224 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.comment
3 rhodecode.model.comment
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 comments model for RhodeCode
6 comments model for RhodeCode
7
7
8 :created_on: Nov 11, 2011
8 :created_on: Nov 11, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-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 logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from sqlalchemy.util.compat import defaultdict
30 from sqlalchemy.util.compat import defaultdict
31
31
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import ChangesetComment, User, Repository, \
35 from rhodecode.model.db import ChangesetComment, User, Repository, \
36 Notification, PullRequest
36 Notification, PullRequest
37 from rhodecode.model.notification import NotificationModel
37 from rhodecode.model.notification import NotificationModel
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class ChangesetCommentsModel(BaseModel):
42 class ChangesetCommentsModel(BaseModel):
43
43
44 cls = ChangesetComment
44 cls = ChangesetComment
45
45
46 def __get_changeset_comment(self, changeset_comment):
46 def __get_changeset_comment(self, changeset_comment):
47 return self._get_instance(ChangesetComment, changeset_comment)
47 return self._get_instance(ChangesetComment, changeset_comment)
48
48
49 def __get_pull_request(self, pull_request):
49 def __get_pull_request(self, pull_request):
50 return self._get_instance(PullRequest, pull_request)
50 return self._get_instance(PullRequest, pull_request)
51
51
52 def _extract_mentions(self, s):
52 def _extract_mentions(self, s):
53 user_objects = []
53 user_objects = []
54 for username in extract_mentioned_users(s):
54 for username in extract_mentioned_users(s):
55 user_obj = User.get_by_username(username, case_insensitive=True)
55 user_obj = User.get_by_username(username, case_insensitive=True)
56 if user_obj:
56 if user_obj:
57 user_objects.append(user_obj)
57 user_objects.append(user_obj)
58 return user_objects
58 return user_objects
59
59
60 def create(self, text, repo_id, user_id, revision=None, pull_request=None,
60 def create(self, text, repo, user, revision=None, pull_request=None,
61 f_path=None, line_no=None, status_change=None):
61 f_path=None, line_no=None, status_change=None):
62 """
62 """
63 Creates new comment for changeset or pull request.
63 Creates new comment for changeset or pull request.
64 IF status_change is not none this comment is associated with a
64 IF status_change is not none this comment is associated with a
65 status change of changeset or changesets associated with pull request
65 status change of changeset or changesets associated with pull request
66
66
67 :param text:
67 :param text:
68 :param repo_id:
68 :param repo:
69 :param user_id:
69 :param user:
70 :param revision:
70 :param revision:
71 :param pull_request:
71 :param pull_request:
72 :param f_path:
72 :param f_path:
73 :param line_no:
73 :param line_no:
74 :param status_change:
74 :param status_change:
75 """
75 """
76 if not text:
76 if not text:
77 return
77 return
78
78
79 repo = Repository.get(repo_id)
79 repo = self._get_repo(repo)
80 user = self._get_user(user)
80 comment = ChangesetComment()
81 comment = ChangesetComment()
81 comment.repo = repo
82 comment.repo = repo
82 comment.user_id = user_id
83 comment.author = user
83 comment.text = text
84 comment.text = text
84 comment.f_path = f_path
85 comment.f_path = f_path
85 comment.line_no = line_no
86 comment.line_no = line_no
86
87
87 if revision:
88 if revision:
88 cs = repo.scm_instance.get_changeset(revision)
89 cs = repo.scm_instance.get_changeset(revision)
89 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
90 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
90 author_email = cs.author_email
91 author_email = cs.author_email
91 comment.revision = revision
92 comment.revision = revision
92 elif pull_request:
93 elif pull_request:
93 pull_request = self.__get_pull_request(pull_request)
94 pull_request = self.__get_pull_request(pull_request)
94 comment.pull_request = pull_request
95 comment.pull_request = pull_request
95 desc = ''
96 desc = pull_request.pull_request_id
96 else:
97 else:
97 raise Exception('Please specify revision or pull_request_id')
98 raise Exception('Please specify revision or pull_request_id')
98
99
99 self.sa.add(comment)
100 self.sa.add(comment)
100 self.sa.flush()
101 self.sa.flush()
101
102
102 # make notification
103 # make notification
103 line = ''
104 line = ''
104 body = text
105 body = text
105
106
106 #changeset
107 #changeset
107 if revision:
108 if revision:
108 if line_no:
109 if line_no:
109 line = _('on line %s') % line_no
110 line = _('on line %s') % line_no
110 subj = safe_unicode(
111 subj = safe_unicode(
111 h.link_to('Re commit: %(commit_desc)s %(line)s' % \
112 h.link_to('Re commit: %(desc)s %(line)s' % \
112 {'commit_desc': desc, 'line': line},
113 {'desc': desc, 'line': line},
113 h.url('changeset_home', repo_name=repo.repo_name,
114 h.url('changeset_home', repo_name=repo.repo_name,
114 revision=revision,
115 revision=revision,
115 anchor='comment-%s' % comment.comment_id,
116 anchor='comment-%s' % comment.comment_id,
116 qualified=True,
117 qualified=True,
117 )
118 )
118 )
119 )
119 )
120 )
120 notification_type = Notification.TYPE_CHANGESET_COMMENT
121 notification_type = Notification.TYPE_CHANGESET_COMMENT
121 # get the current participants of this changeset
122 # get the current participants of this changeset
122 recipients = ChangesetComment.get_users(revision=revision)
123 recipients = ChangesetComment.get_users(revision=revision)
123 # add changeset author if it's in rhodecode system
124 # add changeset author if it's in rhodecode system
124 recipients += [User.get_by_email(author_email)]
125 recipients += [User.get_by_email(author_email)]
125 #pull request
126 #pull request
126 elif pull_request:
127 elif pull_request:
127 #TODO: make this something usefull
128 subj = safe_unicode(
128 subj = 'commented on pull request something...'
129 h.link_to('Re pull request: %(desc)s %(line)s' % \
130 {'desc': desc, 'line': line},
131 h.url('pullrequest_show',
132 repo_name=pull_request.other_repo.repo_name,
133 pull_request_id=pull_request.pull_request_id,
134 anchor='comment-%s' % comment.comment_id,
135 qualified=True,
136 )
137 )
138 )
139
129 notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
140 notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
130 # get the current participants of this pull request
141 # get the current participants of this pull request
131 recipients = ChangesetComment.get_users(pull_request_id=
142 recipients = ChangesetComment.get_users(pull_request_id=
132 pull_request.pull_request_id)
143 pull_request.pull_request_id)
133 # add pull request author
144 # add pull request author
134 recipients += [pull_request.author]
145 recipients += [pull_request.author]
135
146
136 # create notification objects, and emails
147 # create notification objects, and emails
137 NotificationModel().create(
148 NotificationModel().create(
138 created_by=user_id, subject=subj, body=body,
149 created_by=user, subject=subj, body=body,
139 recipients=recipients, type_=notification_type,
150 recipients=recipients, type_=notification_type,
140 email_kwargs={'status_change': status_change}
151 email_kwargs={'status_change': status_change}
141 )
152 )
142
153
143 mention_recipients = set(self._extract_mentions(body))\
154 mention_recipients = set(self._extract_mentions(body))\
144 .difference(recipients)
155 .difference(recipients)
145 if mention_recipients:
156 if mention_recipients:
146 subj = _('[Mention]') + ' ' + subj
157 subj = _('[Mention]') + ' ' + subj
147 NotificationModel().create(
158 NotificationModel().create(
148 created_by=user_id, subject=subj, body=body,
159 created_by=user, subject=subj, body=body,
149 recipients=mention_recipients,
160 recipients=mention_recipients,
150 type_=notification_type,
161 type_=notification_type,
151 email_kwargs={'status_change': status_change}
162 email_kwargs={'status_change': status_change}
152 )
163 )
153
164
154 return comment
165 return comment
155
166
156 def delete(self, comment):
167 def delete(self, comment):
157 """
168 """
158 Deletes given comment
169 Deletes given comment
159
170
160 :param comment_id:
171 :param comment_id:
161 """
172 """
162 comment = self.__get_changeset_comment(comment)
173 comment = self.__get_changeset_comment(comment)
163 self.sa.delete(comment)
174 self.sa.delete(comment)
164
175
165 return comment
176 return comment
166
177
167 def get_comments(self, repo_id, revision=None, pull_request=None):
178 def get_comments(self, repo_id, revision=None, pull_request=None):
168 """
179 """
169 Get's main comments based on revision or pull_request_id
180 Get's main comments based on revision or pull_request_id
170
181
171 :param repo_id:
182 :param repo_id:
172 :type repo_id:
183 :type repo_id:
173 :param revision:
184 :param revision:
174 :type revision:
185 :type revision:
175 :param pull_request:
186 :param pull_request:
176 :type pull_request:
187 :type pull_request:
177 """
188 """
178
189
179 q = ChangesetComment.query()\
190 q = ChangesetComment.query()\
180 .filter(ChangesetComment.repo_id == repo_id)\
191 .filter(ChangesetComment.repo_id == repo_id)\
181 .filter(ChangesetComment.line_no == None)\
192 .filter(ChangesetComment.line_no == None)\
182 .filter(ChangesetComment.f_path == None)
193 .filter(ChangesetComment.f_path == None)
183 if revision:
194 if revision:
184 q = q.filter(ChangesetComment.revision == revision)
195 q = q.filter(ChangesetComment.revision == revision)
185 elif pull_request:
196 elif pull_request:
186 pull_request = self.__get_pull_request(pull_request)
197 pull_request = self.__get_pull_request(pull_request)
187 q = q.filter(ChangesetComment.pull_request == pull_request)
198 q = q.filter(ChangesetComment.pull_request == pull_request)
188 else:
199 else:
189 raise Exception('Please specify revision or pull_request')
200 raise Exception('Please specify revision or pull_request')
190 return q.all()
201 return q.all()
191
202
192 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
203 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
193 q = self.sa.query(ChangesetComment)\
204 q = self.sa.query(ChangesetComment)\
194 .filter(ChangesetComment.repo_id == repo_id)\
205 .filter(ChangesetComment.repo_id == repo_id)\
195 .filter(ChangesetComment.line_no != None)\
206 .filter(ChangesetComment.line_no != None)\
196 .filter(ChangesetComment.f_path != None)\
207 .filter(ChangesetComment.f_path != None)\
197 .order_by(ChangesetComment.comment_id.asc())\
208 .order_by(ChangesetComment.comment_id.asc())\
198
209
199 if revision:
210 if revision:
200 q = q.filter(ChangesetComment.revision == revision)
211 q = q.filter(ChangesetComment.revision == revision)
201 elif pull_request:
212 elif pull_request:
202 pull_request = self.__get_pull_request(pull_request)
213 pull_request = self.__get_pull_request(pull_request)
203 q = q.filter(ChangesetComment.pull_request == pull_request)
214 q = q.filter(ChangesetComment.pull_request == pull_request)
204 else:
215 else:
205 raise Exception('Please specify revision or pull_request_id')
216 raise Exception('Please specify revision or pull_request_id')
206
217
207 comments = q.all()
218 comments = q.all()
208
219
209 paths = defaultdict(lambda: defaultdict(list))
220 paths = defaultdict(lambda: defaultdict(list))
210
221
211 for co in comments:
222 for co in comments:
212 paths[co.f_path][co.line_no].append(co)
223 paths[co.f_path][co.line_no].append(co)
213 return paths.items()
224 return paths.items()
@@ -1,189 +1,196 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.pull_reuquest
3 rhodecode.model.pull_reuquest
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull request model for RhodeCode
6 pull request model for RhodeCode
7
7
8 :created_on: Jun 6, 2012
8 :created_on: Jun 6, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2012-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 logging
26 import logging
27 import binascii
27 import binascii
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29
29
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
32 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
33 ChangesetStatus
33 from rhodecode.model.notification import NotificationModel
34 from rhodecode.model.notification import NotificationModel
34 from rhodecode.lib.utils2 import safe_unicode
35 from rhodecode.lib.utils2 import safe_unicode
35
36
36 from rhodecode.lib.vcs.utils.hgcompat import discovery
37 from rhodecode.lib.vcs.utils.hgcompat import discovery
38 from rhodecode.model.changeset_status import ChangesetStatusModel
39 from rhodecode.model.comment import ChangesetCommentsModel
40 from rhodecode.model.meta import Session
37
41
38 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
39
43
40
44
41 class PullRequestModel(BaseModel):
45 class PullRequestModel(BaseModel):
42
46
43 cls = PullRequest
47 cls = PullRequest
44
48
45 def get_all(self, repo):
49 def get_all(self, repo):
46 repo = self._get_repo(repo)
50 repo = self._get_repo(repo)
47 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
51 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
48
52
49 def create(self, created_by, org_repo, org_ref, other_repo,
53 def create(self, created_by, org_repo, org_ref, other_repo,
50 other_ref, revisions, reviewers, title, description=None):
54 other_ref, revisions, reviewers, title, description=None):
55
51 created_by_user = self._get_user(created_by)
56 created_by_user = self._get_user(created_by)
57 org_repo = self._get_repo(org_repo)
58 other_repo = self._get_repo(other_repo)
52
59
53 new = PullRequest()
60 new = PullRequest()
54 new.org_repo = self._get_repo(org_repo)
61 new.org_repo = org_repo
55 new.org_ref = org_ref
62 new.org_ref = org_ref
56 new.other_repo = self._get_repo(other_repo)
63 new.other_repo = other_repo
57 new.other_ref = other_ref
64 new.other_ref = other_ref
58 new.revisions = revisions
65 new.revisions = revisions
59 new.title = title
66 new.title = title
60 new.description = description
67 new.description = description
61 new.author = created_by_user
68 new.author = created_by_user
62 self.sa.add(new)
69 self.sa.add(new)
63
70 Session().flush()
64 #members
71 #members
65 for member in reviewers:
72 for member in reviewers:
66 _usr = self._get_user(member)
73 _usr = self._get_user(member)
67 reviewer = PullRequestReviewers(_usr, new)
74 reviewer = PullRequestReviewers(_usr, new)
68 self.sa.add(reviewer)
75 self.sa.add(reviewer)
69
76
70 #notification to reviewers
77 #notification to reviewers
71 notif = NotificationModel()
78 notif = NotificationModel()
72
79
73 subject = safe_unicode(
80 subject = safe_unicode(
74 h.link_to(
81 h.link_to(
75 _('%(user)s wants you to review pull request #%(pr_id)s') % \
82 _('%(user)s wants you to review pull request #%(pr_id)s') % \
76 {'user': created_by_user.username,
83 {'user': created_by_user.username,
77 'pr_id': new.pull_request_id},
84 'pr_id': new.pull_request_id},
78 h.url('pullrequest_show', repo_name=other_repo,
85 h.url('pullrequest_show', repo_name=other_repo,
79 pull_request_id=new.pull_request_id,
86 pull_request_id=new.pull_request_id,
80 qualified=True,
87 qualified=True,
81 )
88 )
82 )
89 )
83 )
90 )
84 body = description
91 body = description
85 notif.create(created_by=created_by, subject=subject, body=body,
92 notif.create(created_by=created_by_user, subject=subject, body=body,
86 recipients=reviewers,
93 recipients=reviewers,
87 type_=Notification.TYPE_PULL_REQUEST,)
94 type_=Notification.TYPE_PULL_REQUEST,)
88
95
89 return new
96 return new
90
97
91 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref,
98 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref,
92 discovery_data):
99 discovery_data):
93 """
100 """
94 Returns a list of changesets that are incoming from org_repo@org_ref
101 Returns a list of changesets that are incoming from org_repo@org_ref
95 to other_repo@other_ref
102 to other_repo@other_ref
96
103
97 :param org_repo:
104 :param org_repo:
98 :type org_repo:
105 :type org_repo:
99 :param org_ref:
106 :param org_ref:
100 :type org_ref:
107 :type org_ref:
101 :param other_repo:
108 :param other_repo:
102 :type other_repo:
109 :type other_repo:
103 :param other_ref:
110 :param other_ref:
104 :type other_ref:
111 :type other_ref:
105 :param tmp:
112 :param tmp:
106 :type tmp:
113 :type tmp:
107 """
114 """
108 changesets = []
115 changesets = []
109 #case two independent repos
116 #case two independent repos
110 if org_repo != other_repo:
117 if org_repo != other_repo:
111 common, incoming, rheads = discovery_data
118 common, incoming, rheads = discovery_data
112
119
113 if not incoming:
120 if not incoming:
114 revs = []
121 revs = []
115 else:
122 else:
116 revs = org_repo._repo.changelog.findmissing(common, rheads)
123 revs = org_repo._repo.changelog.findmissing(common, rheads)
117
124
118 for cs in reversed(map(binascii.hexlify, revs)):
125 for cs in reversed(map(binascii.hexlify, revs)):
119 changesets.append(org_repo.get_changeset(cs))
126 changesets.append(org_repo.get_changeset(cs))
120 else:
127 else:
121 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
128 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
122 other_ref[1])]
129 other_ref[1])]
123 from mercurial import scmutil
130 from mercurial import scmutil
124 out = scmutil.revrange(org_repo._repo, revs)
131 out = scmutil.revrange(org_repo._repo, revs)
125 for cs in reversed(out):
132 for cs in reversed(out):
126 changesets.append(org_repo.get_changeset(cs))
133 changesets.append(org_repo.get_changeset(cs))
127
134
128 return changesets
135 return changesets
129
136
130 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
137 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
131 """
138 """
132 Get's mercurial discovery data used to calculate difference between
139 Get's mercurial discovery data used to calculate difference between
133 repos and refs
140 repos and refs
134
141
135 :param org_repo:
142 :param org_repo:
136 :type org_repo:
143 :type org_repo:
137 :param org_ref:
144 :param org_ref:
138 :type org_ref:
145 :type org_ref:
139 :param other_repo:
146 :param other_repo:
140 :type other_repo:
147 :type other_repo:
141 :param other_ref:
148 :param other_ref:
142 :type other_ref:
149 :type other_ref:
143 """
150 """
144
151
145 other = org_repo._repo
152 other = org_repo._repo
146 repo = other_repo._repo
153 repo = other_repo._repo
147 tip = other[org_ref[1]]
154 tip = other[org_ref[1]]
148 log.debug('Doing discovery for %s@%s vs %s@%s' % (
155 log.debug('Doing discovery for %s@%s vs %s@%s' % (
149 org_repo, org_ref, other_repo, other_ref)
156 org_repo, org_ref, other_repo, other_ref)
150 )
157 )
151 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
158 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
152 tmp = discovery.findcommonincoming(
159 tmp = discovery.findcommonincoming(
153 repo=repo, # other_repo we check for incoming
160 repo=repo, # other_repo we check for incoming
154 remote=other, # org_repo source for incoming
161 remote=other, # org_repo source for incoming
155 heads=[tip.node()],
162 heads=[tip.node()],
156 force=False
163 force=False
157 )
164 )
158 return tmp
165 return tmp
159
166
160 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
167 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
161 """
168 """
162 Returns a tuple of incomming changesets, and discoverydata cache
169 Returns a tuple of incomming changesets, and discoverydata cache
163
170
164 :param org_repo:
171 :param org_repo:
165 :type org_repo:
172 :type org_repo:
166 :param org_ref:
173 :param org_ref:
167 :type org_ref:
174 :type org_ref:
168 :param other_repo:
175 :param other_repo:
169 :type other_repo:
176 :type other_repo:
170 :param other_ref:
177 :param other_ref:
171 :type other_ref:
178 :type other_ref:
172 """
179 """
173
180
174 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
181 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
175 raise Exception('org_ref must be a two element list/tuple')
182 raise Exception('org_ref must be a two element list/tuple')
176
183
177 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
184 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
178 raise Exception('other_ref must be a two element list/tuple')
185 raise Exception('other_ref must be a two element list/tuple')
179
186
180 discovery_data = self._get_discovery(org_repo.scm_instance,
187 discovery_data = self._get_discovery(org_repo.scm_instance,
181 org_ref,
188 org_ref,
182 other_repo.scm_instance,
189 other_repo.scm_instance,
183 other_ref)
190 other_ref)
184 cs_ranges = self._get_changesets(org_repo.scm_instance,
191 cs_ranges = self._get_changesets(org_repo.scm_instance,
185 org_ref,
192 org_ref,
186 other_repo.scm_instance,
193 other_repo.scm_instance,
187 other_ref,
194 other_ref,
188 discovery_data)
195 discovery_data)
189 return cs_ranges, discovery_data
196 return cs_ranges, discovery_data
@@ -1,192 +1,196 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('New pull request')}
4 ${c.repo_name} ${_('New pull request')}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_(u'Home'),h.url('/'))}
8 ${h.link_to(_(u'Home'),h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('New pull request')}
12 ${_('New pull request')}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
22 ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
23 <div style="float:left;padding:0px 30px 30px 30px">
23 <div style="float:left;padding:0px 30px 30px 30px">
24 <div style="padding:0px 5px 5px 5px">
24 <div style="padding:0px 5px 5px 5px">
25 <span>
25 <span>
26 <a id="refresh" href="#">
26 <a id="refresh" href="#">
27 <img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
27 <img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/>
28 ${_('refresh overview')}
28 ${_('refresh overview')}
29 </a>
29 </a>
30 </span>
30 </span>
31 </div>
31 </div>
32 ##ORG
32 ##ORG
33 <div style="float:left">
33 <div style="float:left">
34 <div class="fork_user">
34 <div class="fork_user">
35 <div class="gravatar">
35 <div class="gravatar">
36 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
36 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
37 </div>
37 </div>
38 <span style="font-size: 20px">
38 <span style="font-size: 20px">
39 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')}
39 ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')}
40 </span>
40 </span>
41 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
41 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
42 </div>
42 </div>
43 <div style="clear:both;padding-top: 10px"></div>
43 <div style="clear:both;padding-top: 10px"></div>
44 </div>
44 </div>
45 <div style="float:left;font-size:24px;padding:0px 20px">
45 <div style="float:left;font-size:24px;padding:0px 20px">
46 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
46 <img height=32 width=32 src="${h.url('/images/arrow_right_64.png')}"/>
47 </div>
47 </div>
48
48
49 ##OTHER, most Probably the PARENT OF THIS FORK
49 ##OTHER, most Probably the PARENT OF THIS FORK
50 <div style="float:left">
50 <div style="float:left">
51 <div class="fork_user">
51 <div class="fork_user">
52 <div class="gravatar">
52 <div class="gravatar">
53 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_db_repo.user.email,24)}"/>
53 <img id="other_repo_gravatar" alt="gravatar" src=""/>
54 </div>
54 </div>
55 <span style="font-size: 20px">
55 <span style="font-size: 20px">
56 ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref','',c.other_refs,class_='refs')}
56 ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref','',c.other_refs,class_='refs')}
57 </span>
57 </span>
58 <div style="padding:5px 3px 3px 42px;">${c.rhodecode_db_repo.description}</div>
58 <div id="other_repo_desc" style="padding:5px 3px 3px 42px;"></div>
59 </div>
59 </div>
60 <div style="clear:both;padding-top: 10px"></div>
60 <div style="clear:both;padding-top: 10px"></div>
61 </div>
61 </div>
62 <div style="clear:both;padding-top: 10px"></div>
62 <div style="clear:both;padding-top: 10px"></div>
63 ## overview pulled by ajax
63 ## overview pulled by ajax
64 <div style="float:left" id="pull_request_overview"></div>
64 <div style="float:left" id="pull_request_overview"></div>
65 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
65 <div style="float:left;clear:both;padding:10px 10px 10px 0px;display:none">
66 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
66 <a id="pull_request_overview_url" href="#">${_('Detailed compare view')}</a>
67 </div>
67 </div>
68 </div>
68 </div>
69 <div style="float:left; border-left:1px dashed #eee">
69 <div style="float:left; border-left:1px dashed #eee">
70 <h4>${_('Pull request reviewers')}</h4>
70 <h4>${_('Pull request reviewers')}</h4>
71 <div id="reviewers" style="padding:0px 0px 0px 15px">
71 <div id="reviewers" style="padding:0px 0px 0px 15px">
72 ##TODO: make this nicer :)
72 ##TODO: make this nicer :)
73 <table class="table noborder">
73 <table class="table noborder">
74 <tr>
74 <tr>
75 <td>
75 <td>
76 <div>
76 <div>
77 <div style="float:left">
77 <div style="float:left">
78 <div class="text" style="padding: 0px 0px 6px;">${_('Chosen reviewers')}</div>
78 <div class="text" style="padding: 0px 0px 6px;">${_('Chosen reviewers')}</div>
79 ${h.select('review_members',[x[0] for x in c.review_members],c.review_members,multiple=True,size=8,style="min-width:210px")}
79 ${h.select('review_members',[x[0] for x in c.review_members],c.review_members,multiple=True,size=8,style="min-width:210px")}
80 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
80 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
81 ${_('Remove all elements')}
81 ${_('Remove all elements')}
82 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
82 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
83 </div>
83 </div>
84 </div>
84 </div>
85 <div style="float:left;width:20px;padding-top:50px">
85 <div style="float:left;width:20px;padding-top:50px">
86 <img alt="add" id="add_element"
86 <img alt="add" id="add_element"
87 style="padding:2px;cursor:pointer"
87 style="padding:2px;cursor:pointer"
88 src="${h.url('/images/icons/arrow_left.png')}"/>
88 src="${h.url('/images/icons/arrow_left.png')}"/>
89 <br />
89 <br />
90 <img alt="remove" id="remove_element"
90 <img alt="remove" id="remove_element"
91 style="padding:2px;cursor:pointer"
91 style="padding:2px;cursor:pointer"
92 src="${h.url('/images/icons/arrow_right.png')}"/>
92 src="${h.url('/images/icons/arrow_right.png')}"/>
93 </div>
93 </div>
94 <div style="float:left">
94 <div style="float:left">
95 <div class="text" style="padding: 0px 0px 6px;">${_('Available reviewers')}</div>
95 <div class="text" style="padding: 0px 0px 6px;">${_('Available reviewers')}</div>
96 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
96 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
97 <div id="add_all_elements" style="cursor:pointer;text-align:center">
97 <div id="add_all_elements" style="cursor:pointer;text-align:center">
98 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
98 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
99 ${_('Add all elements')}
99 ${_('Add all elements')}
100 </div>
100 </div>
101 </div>
101 </div>
102 </div>
102 </div>
103 </td>
103 </td>
104 </tr>
104 </tr>
105 </table>
105 </table>
106 </div>
106 </div>
107 </div>
107 </div>
108 <h3>${_('Create new pull request')}</h3>
108 <h3>${_('Create new pull request')}</h3>
109
109
110 <div class="form">
110 <div class="form">
111 <!-- fields -->
111 <!-- fields -->
112
112
113 <div class="fields">
113 <div class="fields">
114
114
115 <div class="field">
115 <div class="field">
116 <div class="label">
116 <div class="label">
117 <label for="pullrequest_title">${_('Title')}:</label>
117 <label for="pullrequest_title">${_('Title')}:</label>
118 </div>
118 </div>
119 <div class="input">
119 <div class="input">
120 ${h.text('pullrequest_title',size=30)}
120 ${h.text('pullrequest_title',size=30)}
121 </div>
121 </div>
122 </div>
122 </div>
123
123
124 <div class="field">
124 <div class="field">
125 <div class="label label-textarea">
125 <div class="label label-textarea">
126 <label for="pullrequest_desc">${_('description')}:</label>
126 <label for="pullrequest_desc">${_('description')}:</label>
127 </div>
127 </div>
128 <div class="textarea text-area editor">
128 <div class="textarea text-area editor">
129 ${h.textarea('pullrequest_desc',size=30)}
129 ${h.textarea('pullrequest_desc',size=30)}
130 </div>
130 </div>
131 </div>
131 </div>
132
132
133 <div class="buttons">
133 <div class="buttons">
134 ${h.submit('save',_('Send pull request'),class_="ui-button")}
134 ${h.submit('save',_('Send pull request'),class_="ui-button")}
135 ${h.reset('reset',_('Reset'),class_="ui-button")}
135 ${h.reset('reset',_('Reset'),class_="ui-button")}
136 </div>
136 </div>
137 </div>
137 </div>
138 </div>
138 </div>
139 ${h.end_form()}
139 ${h.end_form()}
140
140
141 </div>
141 </div>
142
142
143 <script type="text/javascript">
143 <script type="text/javascript">
144 MultiSelectWidget('review_members','available_members','pull_request_form');
144 MultiSelectWidget('review_members','available_members','pull_request_form');
145
145 var other_repos_info = ${c.other_repos_info|n};
146 var loadPreview = function(){
146 var loadPreview = function(){
147 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
147 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','none');
148 var url = "${h.url('compare_url',
148 var url = "${h.url('compare_url',
149 repo_name='org_repo',
149 repo_name='org_repo',
150 org_ref_type='branch', org_ref='org_ref',
150 org_ref_type='branch', org_ref='org_ref',
151 other_ref_type='branch', other_ref='other_ref',
151 other_ref_type='branch', other_ref='other_ref',
152 repo='other_repo',
152 repo='other_repo',
153 as_form=True)}";
153 as_form=True)}";
154
154
155 var select_refs = YUQ('#pull_request_form select.refs')
155 var select_refs = YUQ('#pull_request_form select.refs')
156
156
157 for(var i=0;i<select_refs.length;i++){
157 for(var i=0;i<select_refs.length;i++){
158 var select_ref = select_refs[i];
158 var select_ref = select_refs[i];
159 var select_ref_data = select_ref.value.split(':');
159 var select_ref_data = select_ref.value.split(':');
160 var key = null;
160 var key = null;
161 var val = null;
161 var val = null;
162 if(select_ref_data.length>1){
162 if(select_ref_data.length>1){
163 key = select_ref.name+"_type";
163 key = select_ref.name+"_type";
164 val = select_ref_data[0];
164 val = select_ref_data[0];
165 url = url.replace(key,val);
165 url = url.replace(key,val);
166
166
167 key = select_ref.name;
167 key = select_ref.name;
168 val = select_ref_data[1];
168 val = select_ref_data[1];
169 url = url.replace(key,val);
169 url = url.replace(key,val);
170
170
171 }else{
171 }else{
172 key = select_ref.name;
172 key = select_ref.name;
173 val = select_ref.value;
173 val = select_ref.value;
174 url = url.replace(key,val);
174 url = url.replace(key,val);
175 }
175 }
176 }
176 }
177
177
178 ypjax(url,'pull_request_overview', function(data){
178 ypjax(url,'pull_request_overview', function(data){
179 var sel_box = YUQ('#pull_request_form #other_repo')[0];
180 var repo_name = sel_box.options[sel_box.selectedIndex].value;
179 YUD.get('pull_request_overview_url').href = url;
181 YUD.get('pull_request_overview_url').href = url;
180 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
182 YUD.setStyle(YUD.get('pull_request_overview_url').parentElement,'display','');
183 YUD.get('other_repo_gravatar').src = other_repos_info[repo_name]['gravatar'];
184 YUD.get('other_repo_desc').innerHTML = other_repos_info[repo_name]['description'];
181 })
185 })
182 }
186 }
183 YUE.on('refresh','click',function(e){
187 YUE.on('refresh','click',function(e){
184 loadPreview()
188 loadPreview()
185 })
189 })
186
190
187 //lazy load overview after 0.5s
191 //lazy load overview after 0.5s
188 setTimeout(loadPreview, 500)
192 setTimeout(loadPreview, 500)
189
193
190 </script>
194 </script>
191
195
192 </%def>
196 </%def>
General Comments 0
You need to be logged in to leave comments. Login now