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