##// END OF EJS Templates
hg: use 'revset injection safe' repo.revs for revsets
Mads Kiilerich -
r3811:3591b33e beta
parent child Browse files
Show More
@@ -1,275 +1,271 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.compare
3 rhodecode.controllers.compare
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 compare controller for pylons showing differences between two
6 compare controller for pylons showing differences between two
7 repos, branches, bookmarks or tips
7 repos, branches, bookmarks or tips
8
8
9 :created_on: May 6, 2012
9 :created_on: May 6, 2012
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
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import re
29 import re
30
30
31 from webob.exc import HTTPNotFound
31 from webob.exc import HTTPNotFound
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
35
36 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
36 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
37 from rhodecode.lib.vcs.utils import safe_str
37 from rhodecode.lib.vcs.utils import safe_str
38 from rhodecode.lib.vcs.utils.hgcompat import scmutil
38 from rhodecode.lib.vcs.utils.hgcompat import scmutil
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib import diffs, unionrepo
42 from rhodecode.lib import diffs, unionrepo
43
43
44 from rhodecode.model.db import Repository
44 from rhodecode.model.db import Repository
45 from webob.exc import HTTPBadRequest
45 from webob.exc import HTTPBadRequest
46 from rhodecode.lib.diffs import LimitedDiffContainer
46 from rhodecode.lib.diffs import LimitedDiffContainer
47
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class CompareController(BaseRepoController):
52 class CompareController(BaseRepoController):
53
53
54 def __before__(self):
54 def __before__(self):
55 super(CompareController, self).__before__()
55 super(CompareController, self).__before__()
56
56
57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
57 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
58 partial=False):
58 partial=False):
59 """
59 """
60 Safe way to get changeset if error occur it redirects to changeset with
60 Safe way to get changeset if error occur it redirects to changeset with
61 proper message. If partial is set then don't do redirect raise Exception
61 proper message. If partial is set then don't do redirect raise Exception
62 instead
62 instead
63
63
64 :param rev: revision to fetch
64 :param rev: revision to fetch
65 :param repo: repo instance
65 :param repo: repo instance
66 """
66 """
67
67
68 try:
68 try:
69 type_, rev = rev
69 type_, rev = rev
70 return repo.scm_instance.get_changeset(rev)
70 return repo.scm_instance.get_changeset(rev)
71 except EmptyRepositoryError, e:
71 except EmptyRepositoryError, e:
72 if not redirect_after:
72 if not redirect_after:
73 return None
73 return None
74 h.flash(h.literal(_('There are no changesets yet')),
74 h.flash(h.literal(_('There are no changesets yet')),
75 category='warning')
75 category='warning')
76 redirect(url('summary_home', repo_name=repo.repo_name))
76 redirect(url('summary_home', repo_name=repo.repo_name))
77
77
78 except RepositoryError, e:
78 except RepositoryError, e:
79 log.error(traceback.format_exc())
79 log.error(traceback.format_exc())
80 h.flash(str(e), category='warning')
80 h.flash(str(e), category='warning')
81 if not partial:
81 if not partial:
82 redirect(h.url('summary_home', repo_name=repo.repo_name))
82 redirect(h.url('summary_home', repo_name=repo.repo_name))
83 raise HTTPBadRequest()
83 raise HTTPBadRequest()
84
84
85 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
85 def _get_changesets(self, alias, org_repo, org_ref, other_repo, other_ref, merge):
86 """
86 """
87 Returns a list of changesets that can be merged from org_repo@org_ref
87 Returns a list of changesets that can be merged from org_repo@org_ref
88 to other_repo@other_ref ... and the ancestor that would be used for merge
88 to other_repo@other_ref ... and the ancestor that would be used for merge
89
89
90 :param org_repo:
90 :param org_repo:
91 :param org_ref:
91 :param org_ref:
92 :param other_repo:
92 :param other_repo:
93 :param other_ref:
93 :param other_ref:
94 :param tmp:
94 :param tmp:
95 """
95 """
96
96
97 ancestor = None
97 ancestor = None
98
98
99 if alias == 'hg':
99 if alias == 'hg':
100 # lookup up the exact node id
100 # lookup up the exact node id
101 _revset_predicates = {
101 _revset_predicates = {
102 'branch': 'branch',
102 'branch': 'branch',
103 'book': 'bookmark',
103 'book': 'bookmark',
104 'tag': 'tag',
104 'tag': 'tag',
105 'rev': 'id',
105 'rev': 'id',
106 }
106 }
107
107
108 org_rev_spec = "max(%s('%s'))" % (_revset_predicates[org_ref[0]],
108 org_rev_spec = "max(%s(%%s))" % _revset_predicates[org_ref[0]]
109 safe_str(org_ref[1]))
109 org_revs = org_repo._repo.revs(org_rev_spec, safe_str(org_ref[1]))
110 org_revs = scmutil.revrange(org_repo._repo, [org_rev_spec])
111 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
110 org_rev = org_repo._repo[org_revs[-1] if org_revs else -1].hex()
112
111
113 other_rev_spec = "max(%s('%s'))" % (_revset_predicates[other_ref[0]],
112 other_revs_spec = "max(%s(%%s))" % _revset_predicates[other_ref[0]]
114 safe_str(other_ref[1]))
113 other_revs = other_repo._repo.revs(other_revs_spec, safe_str(other_ref[1]))
115 other_revs = scmutil.revrange(other_repo._repo, [other_rev_spec])
116 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
114 other_rev = other_repo._repo[other_revs[-1] if other_revs else -1].hex()
117
115
118 #case two independent repos
116 #case two independent repos
119 if org_repo != other_repo:
117 if org_repo != other_repo:
120 hgrepo = unionrepo.unionrepository(other_repo.baseui,
118 hgrepo = unionrepo.unionrepository(other_repo.baseui,
121 other_repo.path,
119 other_repo.path,
122 org_repo.path)
120 org_repo.path)
123 # all the changesets we are looking for will be in other_repo,
121 # all the changesets we are looking for will be in other_repo,
124 # so rev numbers from hgrepo can be used in other_repo
122 # so rev numbers from hgrepo can be used in other_repo
125
123
126 #no remote compare do it on the same repository
124 #no remote compare do it on the same repository
127 else:
125 else:
128 hgrepo = other_repo._repo
126 hgrepo = other_repo._repo
129
127
130 if merge:
128 if merge:
131 revs = ["ancestors(id('%s')) and not ancestors(id('%s')) and not id('%s')" %
129 revs = hgrepo.revs("ancestors(id(%s)) and not ancestors(id(%s)) and not id(%s)",
132 (other_rev, org_rev, org_rev)]
130 other_rev, org_rev, org_rev)
133
131
134 ancestors = scmutil.revrange(hgrepo,
132 ancestors = hgrepo.revs("ancestor(id(%s), id(%s))", org_rev, other_rev)
135 ["ancestor(id('%s'), id('%s'))" % (org_rev, other_rev)])
136 if ancestors:
133 if ancestors:
137 # pick arbitrary ancestor - but there is usually only one
134 # pick arbitrary ancestor - but there is usually only one
138 ancestor = hgrepo[ancestors[0]].hex()
135 ancestor = hgrepo[ancestors[0]].hex()
139 else:
136 else:
140 # TODO: have both + and - changesets
137 # TODO: have both + and - changesets
141 revs = ["id('%s') :: id('%s') - id('%s')" %
138 revs = hgrepo.revs("id(%s) :: id(%s) - id(%s)",
142 (org_rev, other_rev, org_rev)]
139 org_rev, other_rev, org_rev)
143
140
144 changesets = [other_repo.get_changeset(cs)
141 changesets = [other_repo.get_changeset(rev) for rev in revs]
145 for cs in scmutil.revrange(hgrepo, revs)]
146
142
147 elif alias == 'git':
143 elif alias == 'git':
148 if org_repo != other_repo:
144 if org_repo != other_repo:
149 raise Exception('Comparing of different GIT repositories is not'
145 raise Exception('Comparing of different GIT repositories is not'
150 'allowed. Got %s != %s' % (org_repo, other_repo))
146 'allowed. Got %s != %s' % (org_repo, other_repo))
151
147
152 so, se = org_repo.run_git_command(
148 so, se = org_repo.run_git_command(
153 'log --reverse --pretty="format: %%H" -s -p %s..%s'
149 'log --reverse --pretty="format: %%H" -s -p %s..%s'
154 % (org_ref[1], other_ref[1])
150 % (org_ref[1], other_ref[1])
155 )
151 )
156 changesets = [org_repo.get_changeset(cs)
152 changesets = [org_repo.get_changeset(cs)
157 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
153 for cs in re.findall(r'[0-9a-fA-F]{40}', so)]
158
154
159 return changesets, ancestor
155 return changesets, ancestor
160
156
161 @LoginRequired()
157 @LoginRequired()
162 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
158 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
163 'repository.admin')
159 'repository.admin')
164 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
160 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
165 # org_ref will be evaluated in org_repo
161 # org_ref will be evaluated in org_repo
166 org_repo = c.rhodecode_db_repo.repo_name
162 org_repo = c.rhodecode_db_repo.repo_name
167 org_ref = (org_ref_type, org_ref)
163 org_ref = (org_ref_type, org_ref)
168 # other_ref will be evaluated in other_repo
164 # other_ref will be evaluated in other_repo
169 other_ref = (other_ref_type, other_ref)
165 other_ref = (other_ref_type, other_ref)
170 other_repo = request.GET.get('other_repo', org_repo)
166 other_repo = request.GET.get('other_repo', org_repo)
171 # If merge is True:
167 # If merge is True:
172 # Show what org would get if merged with other:
168 # Show what org would get if merged with other:
173 # List changesets that are ancestors of other but not of org.
169 # List changesets that are ancestors of other but not of org.
174 # New changesets in org is thus ignored.
170 # New changesets in org is thus ignored.
175 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
171 # Diff will be from common ancestor, and merges of org to other will thus be ignored.
176 # If merge is False:
172 # If merge is False:
177 # Make a raw diff from org to other, no matter if related or not.
173 # Make a raw diff from org to other, no matter if related or not.
178 # Changesets in one and not in the other will be ignored
174 # Changesets in one and not in the other will be ignored
179 merge = bool(request.GET.get('merge'))
175 merge = bool(request.GET.get('merge'))
180 # fulldiff disables cut_off_limit
176 # fulldiff disables cut_off_limit
181 c.fulldiff = request.GET.get('fulldiff')
177 c.fulldiff = request.GET.get('fulldiff')
182 # partial uses compare_cs.html template directly
178 # partial uses compare_cs.html template directly
183 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
179 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
184 # as_form puts hidden input field with changeset revisions
180 # as_form puts hidden input field with changeset revisions
185 c.as_form = partial and request.GET.get('as_form')
181 c.as_form = partial and request.GET.get('as_form')
186 # swap url for compare_diff page - never partial and never as_form
182 # swap url for compare_diff page - never partial and never as_form
187 c.swap_url = h.url('compare_url',
183 c.swap_url = h.url('compare_url',
188 repo_name=other_repo,
184 repo_name=other_repo,
189 org_ref_type=other_ref[0], org_ref=other_ref[1],
185 org_ref_type=other_ref[0], org_ref=other_ref[1],
190 other_repo=org_repo,
186 other_repo=org_repo,
191 other_ref_type=org_ref[0], other_ref=org_ref[1],
187 other_ref_type=org_ref[0], other_ref=org_ref[1],
192 merge=merge or '')
188 merge=merge or '')
193
189
194 org_repo = Repository.get_by_repo_name(org_repo)
190 org_repo = Repository.get_by_repo_name(org_repo)
195 other_repo = Repository.get_by_repo_name(other_repo)
191 other_repo = Repository.get_by_repo_name(other_repo)
196
192
197 if org_repo is None:
193 if org_repo is None:
198 log.error('Could not find org repo %s' % org_repo)
194 log.error('Could not find org repo %s' % org_repo)
199 raise HTTPNotFound
195 raise HTTPNotFound
200 if other_repo is None:
196 if other_repo is None:
201 log.error('Could not find other repo %s' % other_repo)
197 log.error('Could not find other repo %s' % other_repo)
202 raise HTTPNotFound
198 raise HTTPNotFound
203
199
204 if org_repo != other_repo and h.is_git(org_repo):
200 if org_repo != other_repo and h.is_git(org_repo):
205 log.error('compare of two remote repos not available for GIT REPOS')
201 log.error('compare of two remote repos not available for GIT REPOS')
206 raise HTTPNotFound
202 raise HTTPNotFound
207
203
208 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
204 if org_repo.scm_instance.alias != other_repo.scm_instance.alias:
209 log.error('compare of two different kind of remote repos not available')
205 log.error('compare of two different kind of remote repos not available')
210 raise HTTPNotFound
206 raise HTTPNotFound
211
207
212 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
208 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
213 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
209 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
214
210
215 c.org_repo = org_repo
211 c.org_repo = org_repo
216 c.other_repo = other_repo
212 c.other_repo = other_repo
217 c.org_ref = org_ref[1]
213 c.org_ref = org_ref[1]
218 c.other_ref = other_ref[1]
214 c.other_ref = other_ref[1]
219 c.org_ref_type = org_ref[0]
215 c.org_ref_type = org_ref[0]
220 c.other_ref_type = other_ref[0]
216 c.other_ref_type = other_ref[0]
221
217
222 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
218 c.cs_ranges, c.ancestor = self._get_changesets(org_repo.scm_instance.alias,
223 org_repo.scm_instance, org_ref,
219 org_repo.scm_instance, org_ref,
224 other_repo.scm_instance, other_ref,
220 other_repo.scm_instance, other_ref,
225 merge)
221 merge)
226
222
227 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
223 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
228 c.cs_ranges])
224 c.cs_ranges])
229 if not c.ancestor:
225 if not c.ancestor:
230 log.warning('Unable to find ancestor revision')
226 log.warning('Unable to find ancestor revision')
231
227
232 if partial:
228 if partial:
233 return render('compare/compare_cs.html')
229 return render('compare/compare_cs.html')
234
230
235 if c.ancestor:
231 if c.ancestor:
236 assert merge
232 assert merge
237 # case we want a simple diff without incoming changesets,
233 # case we want a simple diff without incoming changesets,
238 # previewing what will be merged.
234 # previewing what will be merged.
239 # Make the diff on the other repo (which is known to have other_ref)
235 # Make the diff on the other repo (which is known to have other_ref)
240 log.debug('Using ancestor %s as org_ref instead of %s'
236 log.debug('Using ancestor %s as org_ref instead of %s'
241 % (c.ancestor, org_ref))
237 % (c.ancestor, org_ref))
242 org_ref = ('rev', c.ancestor)
238 org_ref = ('rev', c.ancestor)
243 org_repo = other_repo
239 org_repo = other_repo
244
240
245 diff_limit = self.cut_off_limit if not c.fulldiff else None
241 diff_limit = self.cut_off_limit if not c.fulldiff else None
246
242
247 log.debug('running diff between %s@%s and %s@%s'
243 log.debug('running diff between %s@%s and %s@%s'
248 % (org_repo.scm_instance.path, org_ref,
244 % (org_repo.scm_instance.path, org_ref,
249 other_repo.scm_instance.path, other_ref))
245 other_repo.scm_instance.path, other_ref))
250 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]),
246 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(org_ref[1]),
251 rev2=safe_str(other_ref[1]))
247 rev2=safe_str(other_ref[1]))
252
248
253 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
249 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
254 diff_limit=diff_limit)
250 diff_limit=diff_limit)
255 _parsed = diff_processor.prepare()
251 _parsed = diff_processor.prepare()
256
252
257 c.limited_diff = False
253 c.limited_diff = False
258 if isinstance(_parsed, LimitedDiffContainer):
254 if isinstance(_parsed, LimitedDiffContainer):
259 c.limited_diff = True
255 c.limited_diff = True
260
256
261 c.files = []
257 c.files = []
262 c.changes = {}
258 c.changes = {}
263 c.lines_added = 0
259 c.lines_added = 0
264 c.lines_deleted = 0
260 c.lines_deleted = 0
265 for f in _parsed:
261 for f in _parsed:
266 st = f['stats']
262 st = f['stats']
267 if st[0] != 'b':
263 if st[0] != 'b':
268 c.lines_added += st[0]
264 c.lines_added += st[0]
269 c.lines_deleted += st[1]
265 c.lines_deleted += st[1]
270 fid = h.FID('', f['filename'])
266 fid = h.FID('', f['filename'])
271 c.files.append([fid, f['operation'], f['filename'], f['stats']])
267 c.files.append([fid, f['operation'], f['filename'], f['stats']])
272 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
268 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
273 c.changes[fid] = [f['operation'], f['filename'], diff]
269 c.changes[fid] = [f['operation'], f['filename'], diff]
274
270
275 return render('compare/compare_diff.html')
271 return render('compare/compare_diff.html')
@@ -1,541 +1,547 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 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
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 NotAnonymous
40 NotAnonymous
41 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.utils import action_logger, jsonify
45 from rhodecode.lib.vcs.utils import safe_str
45 from rhodecode.lib.vcs.utils import safe_str
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.lib.diffs import LimitedDiffContainer
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
50 ChangesetComment
50 ChangesetComment
51 from rhodecode.model.pull_request import PullRequestModel
51 from rhodecode.model.pull_request import PullRequestModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.comment import ChangesetCommentsModel
54 from rhodecode.model.comment import ChangesetCommentsModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
56 from rhodecode.model.forms import PullRequestForm
56 from rhodecode.model.forms import PullRequestForm
57 from mercurial import scmutil
57 from mercurial import scmutil
58 from rhodecode.lib.utils2 import safe_int
58 from rhodecode.lib.utils2 import safe_int
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class PullrequestsController(BaseRepoController):
63 class PullrequestsController(BaseRepoController):
64
64
65 def __before__(self):
65 def __before__(self):
66 super(PullrequestsController, self).__before__()
66 super(PullrequestsController, self).__before__()
67 repo_model = RepoModel()
67 repo_model = RepoModel()
68 c.users_array = repo_model.get_users_js()
68 c.users_array = repo_model.get_users_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
70
70
71 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
71 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
72 """return a structure with repo's interesting changesets, suitable for
72 """return a structure with repo's interesting changesets, suitable for
73 the selectors in pullrequest.html"""
73 the selectors in pullrequest.html
74
75 rev: a revision that must be in the list and selected by default
76 branch_rev: a revision of which peers should be preferred and available."""
74 # list named branches that has been merged to this named branch - it should probably merge back
77 # list named branches that has been merged to this named branch - it should probably merge back
75 peers = []
78 peers = []
76
79
77 if rev:
80 if rev:
78 rev = safe_str(rev)
81 rev = safe_str(rev)
79
82
80 if branch_rev:
83 if branch_rev:
81 branch_rev = safe_str(branch_rev)
84 branch_rev = safe_str(branch_rev)
82 # not restricting to merge() would also get branch point and be better
85 # not restricting to merge() would also get branch point and be better
83 # (especially because it would get the branch point) ... but is currently too expensive
86 # (especially because it would get the branch point) ... but is currently too expensive
84 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
85 (branch_rev, branch_rev)]
86 otherbranches = {}
87 otherbranches = {}
87 for i in scmutil.revrange(repo._repo, revs):
88 for i in repo._repo.revs(
89 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
90 branch_rev, branch_rev):
88 cs = repo.get_changeset(i)
91 cs = repo.get_changeset(i)
89 otherbranches[cs.branch] = cs.raw_id
92 otherbranches[cs.branch] = cs.raw_id
90 for branch, node in otherbranches.iteritems():
93 for abranch, node in otherbranches.iteritems():
91 selected = 'branch:%s:%s' % (branch, node)
94 selected = 'branch:%s:%s' % (abranch, node)
92 peers.append((selected, branch))
95 peers.append((selected, abranch))
93
96
94 selected = None
97 selected = None
98
95 branches = []
99 branches = []
96 for branch, branchrev in repo.branches.iteritems():
100 for abranch, branchrev in repo.branches.iteritems():
97 n = 'branch:%s:%s' % (branch, branchrev)
101 n = 'branch:%s:%s' % (abranch, branchrev)
98 branches.append((n, branch))
102 branches.append((n, abranch))
99 if rev == branchrev:
103 if rev == branchrev:
100 selected = n
104 selected = n
105
101 bookmarks = []
106 bookmarks = []
102 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
107 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
103 n = 'book:%s:%s' % (bookmark, bookmarkrev)
108 n = 'book:%s:%s' % (bookmark, bookmarkrev)
104 bookmarks.append((n, bookmark))
109 bookmarks.append((n, bookmark))
105 if rev == bookmarkrev:
110 if rev == bookmarkrev:
106 selected = n
111 selected = n
112
107 tags = []
113 tags = []
108 for tag, tagrev in repo.tags.iteritems():
114 for tag, tagrev in repo.tags.iteritems():
109 n = 'tag:%s:%s' % (tag, tagrev)
115 n = 'tag:%s:%s' % (tag, tagrev)
110 tags.append((n, tag))
116 tags.append((n, tag))
111 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
117 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
112 selected = n
118 selected = n
113
119
114 # prio 1: rev was selected as existing entry above
120 # prio 1: rev was selected as existing entry above
115
121
116 # prio 2: create special entry for rev; rev _must_ be used
122 # prio 2: create special entry for rev; rev _must_ be used
117 specials = []
123 specials = []
118 if rev and selected is None:
124 if rev and selected is None:
119 selected = 'rev:%s:%s' % (rev, rev)
125 selected = 'rev:%s:%s' % (rev, rev)
120 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
126 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
121
127
122 # prio 3: most recent peer branch
128 # prio 3: most recent peer branch
123 if peers and not selected:
129 if peers and not selected:
124 selected = peers[0][0][0]
130 selected = peers[0][0][0]
125
131
126 # prio 4: tip revision
132 # prio 4: tip revision
127 if not selected:
133 if not selected:
128 selected = 'tag:tip:%s' % repo.tags['tip']
134 selected = 'tag:tip:%s' % repo.tags['tip']
129
135
130 groups = [(specials, _("Special")),
136 groups = [(specials, _("Special")),
131 (peers, _("Peer branches")),
137 (peers, _("Peer branches")),
132 (bookmarks, _("Bookmarks")),
138 (bookmarks, _("Bookmarks")),
133 (branches, _("Branches")),
139 (branches, _("Branches")),
134 (tags, _("Tags")),
140 (tags, _("Tags")),
135 ]
141 ]
136 return [g for g in groups if g[0]], selected
142 return [g for g in groups if g[0]], selected
137
143
138 def _get_is_allowed_change_status(self, pull_request):
144 def _get_is_allowed_change_status(self, pull_request):
139 owner = self.rhodecode_user.user_id == pull_request.user_id
145 owner = self.rhodecode_user.user_id == pull_request.user_id
140 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
146 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
141 pull_request.reviewers]
147 pull_request.reviewers]
142 return (self.rhodecode_user.admin or owner or reviewer)
148 return (self.rhodecode_user.admin or owner or reviewer)
143
149
144 def _load_compare_data(self, pull_request, enable_comments=True):
150 def _load_compare_data(self, pull_request, enable_comments=True):
145 """
151 """
146 Load context data needed for generating compare diff
152 Load context data needed for generating compare diff
147
153
148 :param pull_request:
154 :param pull_request:
149 :type pull_request:
155 :type pull_request:
150 """
156 """
151 org_repo = pull_request.org_repo
157 org_repo = pull_request.org_repo
152 (org_ref_type,
158 (org_ref_type,
153 org_ref_name,
159 org_ref_name,
154 org_ref_rev) = pull_request.org_ref.split(':')
160 org_ref_rev) = pull_request.org_ref.split(':')
155
161
156 other_repo = org_repo
162 other_repo = org_repo
157 (other_ref_type,
163 (other_ref_type,
158 other_ref_name,
164 other_ref_name,
159 other_ref_rev) = pull_request.other_ref.split(':')
165 other_ref_rev) = pull_request.other_ref.split(':')
160
166
161 # despite opening revisions for bookmarks/branches/tags, we always
167 # despite opening revisions for bookmarks/branches/tags, we always
162 # convert this to rev to prevent changes after bookmark or branch change
168 # convert this to rev to prevent changes after bookmark or branch change
163 org_ref = ('rev', org_ref_rev)
169 org_ref = ('rev', org_ref_rev)
164 other_ref = ('rev', other_ref_rev)
170 other_ref = ('rev', other_ref_rev)
165
171
166 c.org_repo = org_repo
172 c.org_repo = org_repo
167 c.other_repo = other_repo
173 c.other_repo = other_repo
168
174
169 c.fulldiff = fulldiff = request.GET.get('fulldiff')
175 c.fulldiff = fulldiff = request.GET.get('fulldiff')
170
176
171 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
177 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
172
178
173 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
179 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
174
180
175 c.org_ref = org_ref[1]
181 c.org_ref = org_ref[1]
176 c.org_ref_type = org_ref[0]
182 c.org_ref_type = org_ref[0]
177 c.other_ref = other_ref[1]
183 c.other_ref = other_ref[1]
178 c.other_ref_type = other_ref[0]
184 c.other_ref_type = other_ref[0]
179
185
180 diff_limit = self.cut_off_limit if not fulldiff else None
186 diff_limit = self.cut_off_limit if not fulldiff else None
181
187
182 # we swap org/other ref since we run a simple diff on one repo
188 # we swap org/other ref since we run a simple diff on one repo
183 log.debug('running diff between %s@%s and %s@%s'
189 log.debug('running diff between %s@%s and %s@%s'
184 % (org_repo.scm_instance.path, org_ref,
190 % (org_repo.scm_instance.path, org_ref,
185 other_repo.scm_instance.path, other_ref))
191 other_repo.scm_instance.path, other_ref))
186 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
192 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
187
193
188 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
194 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
189 diff_limit=diff_limit)
195 diff_limit=diff_limit)
190 _parsed = diff_processor.prepare()
196 _parsed = diff_processor.prepare()
191
197
192 c.limited_diff = False
198 c.limited_diff = False
193 if isinstance(_parsed, LimitedDiffContainer):
199 if isinstance(_parsed, LimitedDiffContainer):
194 c.limited_diff = True
200 c.limited_diff = True
195
201
196 c.files = []
202 c.files = []
197 c.changes = {}
203 c.changes = {}
198 c.lines_added = 0
204 c.lines_added = 0
199 c.lines_deleted = 0
205 c.lines_deleted = 0
200 for f in _parsed:
206 for f in _parsed:
201 st = f['stats']
207 st = f['stats']
202 if st[0] != 'b':
208 if st[0] != 'b':
203 c.lines_added += st[0]
209 c.lines_added += st[0]
204 c.lines_deleted += st[1]
210 c.lines_deleted += st[1]
205 fid = h.FID('', f['filename'])
211 fid = h.FID('', f['filename'])
206 c.files.append([fid, f['operation'], f['filename'], f['stats']])
212 c.files.append([fid, f['operation'], f['filename'], f['stats']])
207 diff = diff_processor.as_html(enable_comments=enable_comments,
213 diff = diff_processor.as_html(enable_comments=enable_comments,
208 parsed_lines=[f])
214 parsed_lines=[f])
209 c.changes[fid] = [f['operation'], f['filename'], diff]
215 c.changes[fid] = [f['operation'], f['filename'], diff]
210
216
211 @LoginRequired()
217 @LoginRequired()
212 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
218 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
213 'repository.admin')
219 'repository.admin')
214 def show_all(self, repo_name):
220 def show_all(self, repo_name):
215 c.pull_requests = PullRequestModel().get_all(repo_name)
221 c.pull_requests = PullRequestModel().get_all(repo_name)
216 c.repo_name = repo_name
222 c.repo_name = repo_name
217 p = safe_int(request.GET.get('page', 1), 1)
223 p = safe_int(request.GET.get('page', 1), 1)
218
224
219 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
225 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
220
226
221 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
227 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
222
228
223 if request.environ.get('HTTP_X_PARTIAL_XHR'):
229 if request.environ.get('HTTP_X_PARTIAL_XHR'):
224 return c.pullrequest_data
230 return c.pullrequest_data
225
231
226 return render('/pullrequests/pullrequest_show_all.html')
232 return render('/pullrequests/pullrequest_show_all.html')
227
233
228 @LoginRequired()
234 @LoginRequired()
229 @NotAnonymous()
235 @NotAnonymous()
230 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
236 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
231 'repository.admin')
237 'repository.admin')
232 def index(self):
238 def index(self):
233 org_repo = c.rhodecode_db_repo
239 org_repo = c.rhodecode_db_repo
234
240
235 if org_repo.scm_instance.alias != 'hg':
241 if org_repo.scm_instance.alias != 'hg':
236 log.error('Review not available for GIT REPOS')
242 log.error('Review not available for GIT REPOS')
237 raise HTTPNotFound
243 raise HTTPNotFound
238
244
239 try:
245 try:
240 org_repo.scm_instance.get_changeset()
246 org_repo.scm_instance.get_changeset()
241 except EmptyRepositoryError, e:
247 except EmptyRepositoryError, e:
242 h.flash(h.literal(_('There are no changesets yet')),
248 h.flash(h.literal(_('There are no changesets yet')),
243 category='warning')
249 category='warning')
244 redirect(url('summary_home', repo_name=org_repo.repo_name))
250 redirect(url('summary_home', repo_name=org_repo.repo_name))
245
251
246 org_rev = request.GET.get('rev_end')
252 org_rev = request.GET.get('rev_end')
247 # rev_start is not directly useful - its parent could however be used
253 # rev_start is not directly useful - its parent could however be used
248 # as default for other and thus give a simple compare view
254 # as default for other and thus give a simple compare view
249 #other_rev = request.POST.get('rev_start')
255 #other_rev = request.POST.get('rev_start')
250
256
251 c.org_repos = []
257 c.org_repos = []
252 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
258 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
253 c.default_org_repo = org_repo.repo_name
259 c.default_org_repo = org_repo.repo_name
254 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
260 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
255
261
256 c.other_repos = []
262 c.other_repos = []
257 other_repos_info = {}
263 other_repos_info = {}
258
264
259 def add_other_repo(repo, branch_rev=None):
265 def add_other_repo(repo, branch_rev=None):
260 if repo.repo_name in other_repos_info: # shouldn't happen
266 if repo.repo_name in other_repos_info: # shouldn't happen
261 return
267 return
262 c.other_repos.append((repo.repo_name, repo.repo_name))
268 c.other_repos.append((repo.repo_name, repo.repo_name))
263 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
269 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
264 other_repos_info[repo.repo_name] = {
270 other_repos_info[repo.repo_name] = {
265 'user': dict(user_id=repo.user.user_id,
271 'user': dict(user_id=repo.user.user_id,
266 username=repo.user.username,
272 username=repo.user.username,
267 firstname=repo.user.firstname,
273 firstname=repo.user.firstname,
268 lastname=repo.user.lastname,
274 lastname=repo.user.lastname,
269 gravatar_link=h.gravatar_url(repo.user.email, 14)),
275 gravatar_link=h.gravatar_url(repo.user.email, 14)),
270 'description': repo.description.split('\n', 1)[0],
276 'description': repo.description.split('\n', 1)[0],
271 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
277 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
272 }
278 }
273
279
274 # add org repo to other so we can open pull request against peer branches on itself
280 # add org repo to other so we can open pull request against peer branches on itself
275 add_other_repo(org_repo, branch_rev=org_rev)
281 add_other_repo(org_repo, branch_rev=org_rev)
276 c.default_other_repo = org_repo.repo_name
282 c.default_other_repo = org_repo.repo_name
277
283
278 # gather forks and add to this list ... even though it is rare to
284 # gather forks and add to this list ... even though it is rare to
279 # request forks to pull from their parent
285 # request forks to pull from their parent
280 for fork in org_repo.forks:
286 for fork in org_repo.forks:
281 add_other_repo(fork)
287 add_other_repo(fork)
282
288
283 # add parents of this fork also, but only if it's not empty
289 # add parents of this fork also, but only if it's not empty
284 if org_repo.parent and org_repo.parent.scm_instance.revisions:
290 if org_repo.parent and org_repo.parent.scm_instance.revisions:
285 add_other_repo(org_repo.parent)
291 add_other_repo(org_repo.parent)
286 c.default_other_repo = org_repo.parent.repo_name
292 c.default_other_repo = org_repo.parent.repo_name
287
293
288 c.default_other_repo_info = other_repos_info[c.default_other_repo]
294 c.default_other_repo_info = other_repos_info[c.default_other_repo]
289 c.other_repos_info = json.dumps(other_repos_info)
295 c.other_repos_info = json.dumps(other_repos_info)
290
296
291 return render('/pullrequests/pullrequest.html')
297 return render('/pullrequests/pullrequest.html')
292
298
293 @LoginRequired()
299 @LoginRequired()
294 @NotAnonymous()
300 @NotAnonymous()
295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
301 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
296 'repository.admin')
302 'repository.admin')
297 def create(self, repo_name):
303 def create(self, repo_name):
298 repo = RepoModel()._get_repo(repo_name)
304 repo = RepoModel()._get_repo(repo_name)
299 try:
305 try:
300 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
306 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
301 except formencode.Invalid, errors:
307 except formencode.Invalid, errors:
302 log.error(traceback.format_exc())
308 log.error(traceback.format_exc())
303 if errors.error_dict.get('revisions'):
309 if errors.error_dict.get('revisions'):
304 msg = 'Revisions: %s' % errors.error_dict['revisions']
310 msg = 'Revisions: %s' % errors.error_dict['revisions']
305 elif errors.error_dict.get('pullrequest_title'):
311 elif errors.error_dict.get('pullrequest_title'):
306 msg = _('Pull request requires a title with min. 3 chars')
312 msg = _('Pull request requires a title with min. 3 chars')
307 else:
313 else:
308 msg = _('Error creating pull request')
314 msg = _('Error creating pull request')
309
315
310 h.flash(msg, 'error')
316 h.flash(msg, 'error')
311 return redirect(url('pullrequest_home', repo_name=repo_name))
317 return redirect(url('pullrequest_home', repo_name=repo_name))
312
318
313 org_repo = _form['org_repo']
319 org_repo = _form['org_repo']
314 org_ref = 'rev:merge:%s' % _form['merge_rev']
320 org_ref = 'rev:merge:%s' % _form['merge_rev']
315 other_repo = _form['other_repo']
321 other_repo = _form['other_repo']
316 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
322 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
317 revisions = [x for x in reversed(_form['revisions'])]
323 revisions = [x for x in reversed(_form['revisions'])]
318 reviewers = _form['review_members']
324 reviewers = _form['review_members']
319
325
320 title = _form['pullrequest_title']
326 title = _form['pullrequest_title']
321 description = _form['pullrequest_desc']
327 description = _form['pullrequest_desc']
322 try:
328 try:
323 pull_request = PullRequestModel().create(
329 pull_request = PullRequestModel().create(
324 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
330 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
325 other_ref, revisions, reviewers, title, description
331 other_ref, revisions, reviewers, title, description
326 )
332 )
327 Session().commit()
333 Session().commit()
328 h.flash(_('Successfully opened new pull request'),
334 h.flash(_('Successfully opened new pull request'),
329 category='success')
335 category='success')
330 except Exception:
336 except Exception:
331 h.flash(_('Error occurred during sending pull request'),
337 h.flash(_('Error occurred during sending pull request'),
332 category='error')
338 category='error')
333 log.error(traceback.format_exc())
339 log.error(traceback.format_exc())
334 return redirect(url('pullrequest_home', repo_name=repo_name))
340 return redirect(url('pullrequest_home', repo_name=repo_name))
335
341
336 return redirect(url('pullrequest_show', repo_name=other_repo,
342 return redirect(url('pullrequest_show', repo_name=other_repo,
337 pull_request_id=pull_request.pull_request_id))
343 pull_request_id=pull_request.pull_request_id))
338
344
339 @LoginRequired()
345 @LoginRequired()
340 @NotAnonymous()
346 @NotAnonymous()
341 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
347 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
342 'repository.admin')
348 'repository.admin')
343 @jsonify
349 @jsonify
344 def update(self, repo_name, pull_request_id):
350 def update(self, repo_name, pull_request_id):
345 pull_request = PullRequest.get_or_404(pull_request_id)
351 pull_request = PullRequest.get_or_404(pull_request_id)
346 if pull_request.is_closed():
352 if pull_request.is_closed():
347 raise HTTPForbidden()
353 raise HTTPForbidden()
348 #only owner or admin can update it
354 #only owner or admin can update it
349 owner = pull_request.author.user_id == c.rhodecode_user.user_id
355 owner = pull_request.author.user_id == c.rhodecode_user.user_id
350 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
356 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
351 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
357 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
352 request.POST.get('reviewers_ids', '').split(',')))
358 request.POST.get('reviewers_ids', '').split(',')))
353
359
354 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
360 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
355 Session().commit()
361 Session().commit()
356 return True
362 return True
357 raise HTTPForbidden()
363 raise HTTPForbidden()
358
364
359 @LoginRequired()
365 @LoginRequired()
360 @NotAnonymous()
366 @NotAnonymous()
361 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
367 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
362 'repository.admin')
368 'repository.admin')
363 @jsonify
369 @jsonify
364 def delete(self, repo_name, pull_request_id):
370 def delete(self, repo_name, pull_request_id):
365 pull_request = PullRequest.get_or_404(pull_request_id)
371 pull_request = PullRequest.get_or_404(pull_request_id)
366 #only owner can delete it !
372 #only owner can delete it !
367 if pull_request.author.user_id == c.rhodecode_user.user_id:
373 if pull_request.author.user_id == c.rhodecode_user.user_id:
368 PullRequestModel().delete(pull_request)
374 PullRequestModel().delete(pull_request)
369 Session().commit()
375 Session().commit()
370 h.flash(_('Successfully deleted pull request'),
376 h.flash(_('Successfully deleted pull request'),
371 category='success')
377 category='success')
372 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
378 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
373 raise HTTPForbidden()
379 raise HTTPForbidden()
374
380
375 @LoginRequired()
381 @LoginRequired()
376 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
382 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
377 'repository.admin')
383 'repository.admin')
378 def show(self, repo_name, pull_request_id):
384 def show(self, repo_name, pull_request_id):
379 repo_model = RepoModel()
385 repo_model = RepoModel()
380 c.users_array = repo_model.get_users_js()
386 c.users_array = repo_model.get_users_js()
381 c.users_groups_array = repo_model.get_users_groups_js()
387 c.users_groups_array = repo_model.get_users_groups_js()
382 c.pull_request = PullRequest.get_or_404(pull_request_id)
388 c.pull_request = PullRequest.get_or_404(pull_request_id)
383 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
389 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
384 cc_model = ChangesetCommentsModel()
390 cc_model = ChangesetCommentsModel()
385 cs_model = ChangesetStatusModel()
391 cs_model = ChangesetStatusModel()
386 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
392 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
387 pull_request=c.pull_request,
393 pull_request=c.pull_request,
388 with_revisions=True)
394 with_revisions=True)
389
395
390 cs_statuses = defaultdict(list)
396 cs_statuses = defaultdict(list)
391 for st in _cs_statuses:
397 for st in _cs_statuses:
392 cs_statuses[st.author.username] += [st]
398 cs_statuses[st.author.username] += [st]
393
399
394 c.pull_request_reviewers = []
400 c.pull_request_reviewers = []
395 c.pull_request_pending_reviewers = []
401 c.pull_request_pending_reviewers = []
396 for o in c.pull_request.reviewers:
402 for o in c.pull_request.reviewers:
397 st = cs_statuses.get(o.user.username, None)
403 st = cs_statuses.get(o.user.username, None)
398 if st:
404 if st:
399 sorter = lambda k: k.version
405 sorter = lambda k: k.version
400 st = [(x, list(y)[0])
406 st = [(x, list(y)[0])
401 for x, y in (groupby(sorted(st, key=sorter), sorter))]
407 for x, y in (groupby(sorted(st, key=sorter), sorter))]
402 else:
408 else:
403 c.pull_request_pending_reviewers.append(o.user)
409 c.pull_request_pending_reviewers.append(o.user)
404 c.pull_request_reviewers.append([o.user, st])
410 c.pull_request_reviewers.append([o.user, st])
405
411
406 # pull_requests repo_name we opened it against
412 # pull_requests repo_name we opened it against
407 # ie. other_repo must match
413 # ie. other_repo must match
408 if repo_name != c.pull_request.other_repo.repo_name:
414 if repo_name != c.pull_request.other_repo.repo_name:
409 raise HTTPNotFound
415 raise HTTPNotFound
410
416
411 # load compare data into template context
417 # load compare data into template context
412 enable_comments = not c.pull_request.is_closed()
418 enable_comments = not c.pull_request.is_closed()
413 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
419 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
414
420
415 # inline comments
421 # inline comments
416 c.inline_cnt = 0
422 c.inline_cnt = 0
417 c.inline_comments = cc_model.get_inline_comments(
423 c.inline_comments = cc_model.get_inline_comments(
418 c.rhodecode_db_repo.repo_id,
424 c.rhodecode_db_repo.repo_id,
419 pull_request=pull_request_id)
425 pull_request=pull_request_id)
420 # count inline comments
426 # count inline comments
421 for __, lines in c.inline_comments:
427 for __, lines in c.inline_comments:
422 for comments in lines.values():
428 for comments in lines.values():
423 c.inline_cnt += len(comments)
429 c.inline_cnt += len(comments)
424 # comments
430 # comments
425 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
431 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
426 pull_request=pull_request_id)
432 pull_request=pull_request_id)
427
433
428 try:
434 try:
429 cur_status = c.statuses[c.pull_request.revisions[0]][0]
435 cur_status = c.statuses[c.pull_request.revisions[0]][0]
430 except Exception:
436 except Exception:
431 log.error(traceback.format_exc())
437 log.error(traceback.format_exc())
432 cur_status = 'undefined'
438 cur_status = 'undefined'
433 if c.pull_request.is_closed() and 0:
439 if c.pull_request.is_closed() and 0:
434 c.current_changeset_status = cur_status
440 c.current_changeset_status = cur_status
435 else:
441 else:
436 # changeset(pull-request) status calulation based on reviewers
442 # changeset(pull-request) status calulation based on reviewers
437 c.current_changeset_status = cs_model.calculate_status(
443 c.current_changeset_status = cs_model.calculate_status(
438 c.pull_request_reviewers,
444 c.pull_request_reviewers,
439 )
445 )
440 c.changeset_statuses = ChangesetStatus.STATUSES
446 c.changeset_statuses = ChangesetStatus.STATUSES
441
447
442 c.as_form = False
448 c.as_form = False
443 c.ancestor = None # there is one - but right here we don't know which
449 c.ancestor = None # there is one - but right here we don't know which
444 return render('/pullrequests/pullrequest_show.html')
450 return render('/pullrequests/pullrequest_show.html')
445
451
446 @LoginRequired()
452 @LoginRequired()
447 @NotAnonymous()
453 @NotAnonymous()
448 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
454 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
449 'repository.admin')
455 'repository.admin')
450 @jsonify
456 @jsonify
451 def comment(self, repo_name, pull_request_id):
457 def comment(self, repo_name, pull_request_id):
452 pull_request = PullRequest.get_or_404(pull_request_id)
458 pull_request = PullRequest.get_or_404(pull_request_id)
453 if pull_request.is_closed():
459 if pull_request.is_closed():
454 raise HTTPForbidden()
460 raise HTTPForbidden()
455
461
456 status = request.POST.get('changeset_status')
462 status = request.POST.get('changeset_status')
457 change_status = request.POST.get('change_changeset_status')
463 change_status = request.POST.get('change_changeset_status')
458 text = request.POST.get('text')
464 text = request.POST.get('text')
459 close_pr = request.POST.get('save_close')
465 close_pr = request.POST.get('save_close')
460
466
461 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
467 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
462 if status and change_status and allowed_to_change_status:
468 if status and change_status and allowed_to_change_status:
463 _def = (_('Status change -> %s')
469 _def = (_('Status change -> %s')
464 % ChangesetStatus.get_status_lbl(status))
470 % ChangesetStatus.get_status_lbl(status))
465 if close_pr:
471 if close_pr:
466 _def = _('Closing with') + ' ' + _def
472 _def = _('Closing with') + ' ' + _def
467 text = text or _def
473 text = text or _def
468 comm = ChangesetCommentsModel().create(
474 comm = ChangesetCommentsModel().create(
469 text=text,
475 text=text,
470 repo=c.rhodecode_db_repo.repo_id,
476 repo=c.rhodecode_db_repo.repo_id,
471 user=c.rhodecode_user.user_id,
477 user=c.rhodecode_user.user_id,
472 pull_request=pull_request_id,
478 pull_request=pull_request_id,
473 f_path=request.POST.get('f_path'),
479 f_path=request.POST.get('f_path'),
474 line_no=request.POST.get('line'),
480 line_no=request.POST.get('line'),
475 status_change=(ChangesetStatus.get_status_lbl(status)
481 status_change=(ChangesetStatus.get_status_lbl(status)
476 if status and change_status
482 if status and change_status
477 and allowed_to_change_status else None),
483 and allowed_to_change_status else None),
478 closing_pr=close_pr
484 closing_pr=close_pr
479 )
485 )
480
486
481 action_logger(self.rhodecode_user,
487 action_logger(self.rhodecode_user,
482 'user_commented_pull_request:%s' % pull_request_id,
488 'user_commented_pull_request:%s' % pull_request_id,
483 c.rhodecode_db_repo, self.ip_addr, self.sa)
489 c.rhodecode_db_repo, self.ip_addr, self.sa)
484
490
485 if allowed_to_change_status:
491 if allowed_to_change_status:
486 # get status if set !
492 # get status if set !
487 if status and change_status:
493 if status and change_status:
488 ChangesetStatusModel().set_status(
494 ChangesetStatusModel().set_status(
489 c.rhodecode_db_repo.repo_id,
495 c.rhodecode_db_repo.repo_id,
490 status,
496 status,
491 c.rhodecode_user.user_id,
497 c.rhodecode_user.user_id,
492 comm,
498 comm,
493 pull_request=pull_request_id
499 pull_request=pull_request_id
494 )
500 )
495
501
496 if close_pr:
502 if close_pr:
497 if status in ['rejected', 'approved']:
503 if status in ['rejected', 'approved']:
498 PullRequestModel().close_pull_request(pull_request_id)
504 PullRequestModel().close_pull_request(pull_request_id)
499 action_logger(self.rhodecode_user,
505 action_logger(self.rhodecode_user,
500 'user_closed_pull_request:%s' % pull_request_id,
506 'user_closed_pull_request:%s' % pull_request_id,
501 c.rhodecode_db_repo, self.ip_addr, self.sa)
507 c.rhodecode_db_repo, self.ip_addr, self.sa)
502 else:
508 else:
503 h.flash(_('Closing pull request on other statuses than '
509 h.flash(_('Closing pull request on other statuses than '
504 'rejected or approved forbidden'),
510 'rejected or approved forbidden'),
505 category='warning')
511 category='warning')
506
512
507 Session().commit()
513 Session().commit()
508
514
509 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
515 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
510 return redirect(h.url('pullrequest_show', repo_name=repo_name,
516 return redirect(h.url('pullrequest_show', repo_name=repo_name,
511 pull_request_id=pull_request_id))
517 pull_request_id=pull_request_id))
512
518
513 data = {
519 data = {
514 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
520 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
515 }
521 }
516 if comm:
522 if comm:
517 c.co = comm
523 c.co = comm
518 data.update(comm.get_dict())
524 data.update(comm.get_dict())
519 data.update({'rendered_text':
525 data.update({'rendered_text':
520 render('changeset/changeset_comment_block.html')})
526 render('changeset/changeset_comment_block.html')})
521
527
522 return data
528 return data
523
529
524 @LoginRequired()
530 @LoginRequired()
525 @NotAnonymous()
531 @NotAnonymous()
526 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
532 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
527 'repository.admin')
533 'repository.admin')
528 @jsonify
534 @jsonify
529 def delete_comment(self, repo_name, comment_id):
535 def delete_comment(self, repo_name, comment_id):
530 co = ChangesetComment.get(comment_id)
536 co = ChangesetComment.get(comment_id)
531 if co.pull_request.is_closed():
537 if co.pull_request.is_closed():
532 #don't allow deleting comments on closed pull request
538 #don't allow deleting comments on closed pull request
533 raise HTTPForbidden()
539 raise HTTPForbidden()
534
540
535 owner = co.author.user_id == c.rhodecode_user.user_id
541 owner = co.author.user_id == c.rhodecode_user.user_id
536 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
542 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
537 ChangesetCommentsModel().delete(comment=co)
543 ChangesetCommentsModel().delete(comment=co)
538 Session().commit()
544 Session().commit()
539 return True
545 return True
540 else:
546 else:
541 raise HTTPForbidden()
547 raise HTTPForbidden()
General Comments 0
You need to be logged in to leave comments. Login now