##// END OF EJS Templates
pull requests: make it possible control display of closed PRs and whether it is PRs to or from repo
Mads Kiilerich -
r4024:73ef2a5d default
parent child Browse files
Show More
@@ -1,556 +1,558 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, tmpl_context as c, url
33 from pylons import request, tmpl_context as c, url
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import 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.diffs import LimitedDiffContainer
47 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment
48 from rhodecode.model.db import PullRequest, ChangesetStatus, ChangesetComment
49 from rhodecode.model.pull_request import PullRequestModel
49 from rhodecode.model.pull_request import PullRequestModel
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.comment import ChangesetCommentsModel
52 from rhodecode.model.comment import ChangesetCommentsModel
53 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.changeset_status import ChangesetStatusModel
54 from rhodecode.model.forms import PullRequestForm
54 from rhodecode.model.forms import PullRequestForm
55 from rhodecode.lib.utils2 import safe_int
55 from rhodecode.lib.utils2 import safe_int
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class PullrequestsController(BaseRepoController):
60 class PullrequestsController(BaseRepoController):
61
61
62 def __before__(self):
62 def __before__(self):
63 super(PullrequestsController, self).__before__()
63 super(PullrequestsController, self).__before__()
64 repo_model = RepoModel()
64 repo_model = RepoModel()
65 c.users_array = repo_model.get_users_js()
65 c.users_array = repo_model.get_users_js()
66 c.users_groups_array = repo_model.get_users_groups_js()
66 c.users_groups_array = repo_model.get_users_groups_js()
67
67
68 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
68 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
69 """return a structure with repo's interesting changesets, suitable for
69 """return a structure with repo's interesting changesets, suitable for
70 the selectors in pullrequest.html
70 the selectors in pullrequest.html
71
71
72 rev: a revision that must be in the list somehow and selected by default
72 rev: a revision that must be in the list somehow and selected by default
73 branch: a branch that must be in the list and selected by default - even if closed
73 branch: a branch that must be in the list and selected by default - even if closed
74 branch_rev: a revision of which peers should be preferred and available."""
74 branch_rev: a revision of which peers should be preferred and available."""
75 # list named branches that has been merged to this named branch - it should probably merge back
75 # list named branches that has been merged to this named branch - it should probably merge back
76 peers = []
76 peers = []
77
77
78 if rev:
78 if rev:
79 rev = safe_str(rev)
79 rev = safe_str(rev)
80
80
81 if branch:
81 if branch:
82 branch = safe_str(branch)
82 branch = safe_str(branch)
83
83
84 if branch_rev:
84 if branch_rev:
85 branch_rev = safe_str(branch_rev)
85 branch_rev = safe_str(branch_rev)
86 # not restricting to merge() would also get branch point and be better
86 # not restricting to merge() would also get branch point and be better
87 # (especially because it would get the branch point) ... but is currently too expensive
87 # (especially because it would get the branch point) ... but is currently too expensive
88 otherbranches = {}
88 otherbranches = {}
89 for i in repo._repo.revs(
89 for i in repo._repo.revs(
90 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
90 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
91 branch_rev, branch_rev):
91 branch_rev, branch_rev):
92 cs = repo.get_changeset(i)
92 cs = repo.get_changeset(i)
93 otherbranches[cs.branch] = cs.raw_id
93 otherbranches[cs.branch] = cs.raw_id
94 for abranch, node in otherbranches.iteritems():
94 for abranch, node in otherbranches.iteritems():
95 selected = 'branch:%s:%s' % (abranch, node)
95 selected = 'branch:%s:%s' % (abranch, node)
96 peers.append((selected, abranch))
96 peers.append((selected, abranch))
97
97
98 selected = None
98 selected = None
99
99
100 branches = []
100 branches = []
101 for abranch, branchrev in repo.branches.iteritems():
101 for abranch, branchrev in repo.branches.iteritems():
102 n = 'branch:%s:%s' % (abranch, branchrev)
102 n = 'branch:%s:%s' % (abranch, branchrev)
103 branches.append((n, abranch))
103 branches.append((n, abranch))
104 if rev == branchrev:
104 if rev == branchrev:
105 selected = n
105 selected = n
106 if branch == abranch:
106 if branch == abranch:
107 selected = n
107 selected = n
108 branch = None
108 branch = None
109 if branch: # branch not in list - it is probably closed
109 if branch: # branch not in list - it is probably closed
110 revs = repo._repo.revs('max(branch(%s))', branch)
110 revs = repo._repo.revs('max(branch(%s))', branch)
111 if revs:
111 if revs:
112 cs = repo.get_changeset(revs[0])
112 cs = repo.get_changeset(revs[0])
113 selected = 'branch:%s:%s' % (branch, cs.raw_id)
113 selected = 'branch:%s:%s' % (branch, cs.raw_id)
114 branches.append((selected, branch))
114 branches.append((selected, branch))
115
115
116 bookmarks = []
116 bookmarks = []
117 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
117 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
118 n = 'book:%s:%s' % (bookmark, bookmarkrev)
118 n = 'book:%s:%s' % (bookmark, bookmarkrev)
119 bookmarks.append((n, bookmark))
119 bookmarks.append((n, bookmark))
120 if rev == bookmarkrev:
120 if rev == bookmarkrev:
121 selected = n
121 selected = n
122
122
123 tags = []
123 tags = []
124 for tag, tagrev in repo.tags.iteritems():
124 for tag, tagrev in repo.tags.iteritems():
125 n = 'tag:%s:%s' % (tag, tagrev)
125 n = 'tag:%s:%s' % (tag, tagrev)
126 tags.append((n, tag))
126 tags.append((n, tag))
127 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
127 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
128 selected = n
128 selected = n
129
129
130 # prio 1: rev was selected as existing entry above
130 # prio 1: rev was selected as existing entry above
131
131
132 # prio 2: create special entry for rev; rev _must_ be used
132 # prio 2: create special entry for rev; rev _must_ be used
133 specials = []
133 specials = []
134 if rev and selected is None:
134 if rev and selected is None:
135 selected = 'rev:%s:%s' % (rev, rev)
135 selected = 'rev:%s:%s' % (rev, rev)
136 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
136 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
137
137
138 # prio 3: most recent peer branch
138 # prio 3: most recent peer branch
139 if peers and not selected:
139 if peers and not selected:
140 selected = peers[0][0][0]
140 selected = peers[0][0][0]
141
141
142 # prio 4: tip revision
142 # prio 4: tip revision
143 if not selected:
143 if not selected:
144 selected = 'tag:tip:%s' % repo.tags['tip']
144 selected = 'tag:tip:%s' % repo.tags['tip']
145
145
146 groups = [(specials, _("Special")),
146 groups = [(specials, _("Special")),
147 (peers, _("Peer branches")),
147 (peers, _("Peer branches")),
148 (bookmarks, _("Bookmarks")),
148 (bookmarks, _("Bookmarks")),
149 (branches, _("Branches")),
149 (branches, _("Branches")),
150 (tags, _("Tags")),
150 (tags, _("Tags")),
151 ]
151 ]
152 return [g for g in groups if g[0]], selected
152 return [g for g in groups if g[0]], selected
153
153
154 def _get_is_allowed_change_status(self, pull_request):
154 def _get_is_allowed_change_status(self, pull_request):
155 owner = self.rhodecode_user.user_id == pull_request.user_id
155 owner = self.rhodecode_user.user_id == pull_request.user_id
156 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
156 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
157 pull_request.reviewers]
157 pull_request.reviewers]
158 return (self.rhodecode_user.admin or owner or reviewer)
158 return (self.rhodecode_user.admin or owner or reviewer)
159
159
160 def _load_compare_data(self, pull_request, enable_comments=True):
160 def _load_compare_data(self, pull_request, enable_comments=True):
161 """
161 """
162 Load context data needed for generating compare diff
162 Load context data needed for generating compare diff
163
163
164 :param pull_request:
164 :param pull_request:
165 """
165 """
166 org_repo = pull_request.org_repo
166 org_repo = pull_request.org_repo
167 (org_ref_type,
167 (org_ref_type,
168 org_ref_name,
168 org_ref_name,
169 org_ref_rev) = pull_request.org_ref.split(':')
169 org_ref_rev) = pull_request.org_ref.split(':')
170
170
171 other_repo = org_repo
171 other_repo = org_repo
172 (other_ref_type,
172 (other_ref_type,
173 other_ref_name,
173 other_ref_name,
174 other_ref_rev) = pull_request.other_ref.split(':')
174 other_ref_rev) = pull_request.other_ref.split(':')
175
175
176 # despite opening revisions for bookmarks/branches/tags, we always
176 # despite opening revisions for bookmarks/branches/tags, we always
177 # convert this to rev to prevent changes after bookmark or branch change
177 # convert this to rev to prevent changes after bookmark or branch change
178 org_ref = ('rev', org_ref_rev)
178 org_ref = ('rev', org_ref_rev)
179 other_ref = ('rev', other_ref_rev)
179 other_ref = ('rev', other_ref_rev)
180
180
181 c.org_repo = org_repo
181 c.org_repo = org_repo
182 c.other_repo = other_repo
182 c.other_repo = other_repo
183
183
184 c.fulldiff = fulldiff = request.GET.get('fulldiff')
184 c.fulldiff = fulldiff = request.GET.get('fulldiff')
185
185
186 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
186 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
187
187
188 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
188 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
189
189
190 c.org_ref = org_ref[1]
190 c.org_ref = org_ref[1]
191 c.org_ref_type = org_ref[0]
191 c.org_ref_type = org_ref[0]
192 c.other_ref = other_ref[1]
192 c.other_ref = other_ref[1]
193 c.other_ref_type = other_ref[0]
193 c.other_ref_type = other_ref[0]
194
194
195 diff_limit = self.cut_off_limit if not fulldiff else None
195 diff_limit = self.cut_off_limit if not fulldiff else None
196
196
197 # we swap org/other ref since we run a simple diff on one repo
197 # we swap org/other ref since we run a simple diff on one repo
198 log.debug('running diff between %s and %s in %s'
198 log.debug('running diff between %s and %s in %s'
199 % (other_ref, org_ref, org_repo.scm_instance.path))
199 % (other_ref, org_ref, org_repo.scm_instance.path))
200 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
200 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
201
201
202 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
202 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
203 diff_limit=diff_limit)
203 diff_limit=diff_limit)
204 _parsed = diff_processor.prepare()
204 _parsed = diff_processor.prepare()
205
205
206 c.limited_diff = False
206 c.limited_diff = False
207 if isinstance(_parsed, LimitedDiffContainer):
207 if isinstance(_parsed, LimitedDiffContainer):
208 c.limited_diff = True
208 c.limited_diff = True
209
209
210 c.files = []
210 c.files = []
211 c.changes = {}
211 c.changes = {}
212 c.lines_added = 0
212 c.lines_added = 0
213 c.lines_deleted = 0
213 c.lines_deleted = 0
214
214
215 for f in _parsed:
215 for f in _parsed:
216 st = f['stats']
216 st = f['stats']
217 c.lines_added += st['added']
217 c.lines_added += st['added']
218 c.lines_deleted += st['deleted']
218 c.lines_deleted += st['deleted']
219 fid = h.FID('', f['filename'])
219 fid = h.FID('', f['filename'])
220 c.files.append([fid, f['operation'], f['filename'], f['stats']])
220 c.files.append([fid, f['operation'], f['filename'], f['stats']])
221 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
221 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
222 parsed_lines=[f])
222 parsed_lines=[f])
223 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
223 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
224
224
225 @LoginRequired()
225 @LoginRequired()
226 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
226 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
227 'repository.admin')
227 'repository.admin')
228 def show_all(self, repo_name):
228 def show_all(self, repo_name):
229 c.pull_requests = PullRequestModel().get_all(repo_name)
229 c.from_ = request.GET.get('from_') or ''
230 c.closed = request.GET.get('closed') or ''
231 c.pull_requests = PullRequestModel().get_all(repo_name, from_=c.from_, closed=c.closed)
230 c.repo_name = repo_name
232 c.repo_name = repo_name
231 p = safe_int(request.GET.get('page', 1), 1)
233 p = safe_int(request.GET.get('page', 1), 1)
232
234
233 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
235 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
234
236
235 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
237 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
236
238
237 if request.environ.get('HTTP_X_PARTIAL_XHR'):
239 if request.environ.get('HTTP_X_PARTIAL_XHR'):
238 return c.pullrequest_data
240 return c.pullrequest_data
239
241
240 return render('/pullrequests/pullrequest_show_all.html')
242 return render('/pullrequests/pullrequest_show_all.html')
241
243
242 @LoginRequired()
244 @LoginRequired()
243 @NotAnonymous()
245 @NotAnonymous()
244 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
246 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
245 'repository.admin')
247 'repository.admin')
246 def index(self):
248 def index(self):
247 org_repo = c.rhodecode_db_repo
249 org_repo = c.rhodecode_db_repo
248
250
249 if org_repo.scm_instance.alias != 'hg':
251 if org_repo.scm_instance.alias != 'hg':
250 log.error('Review not available for GIT REPOS')
252 log.error('Review not available for GIT REPOS')
251 raise HTTPNotFound
253 raise HTTPNotFound
252
254
253 try:
255 try:
254 org_repo.scm_instance.get_changeset()
256 org_repo.scm_instance.get_changeset()
255 except EmptyRepositoryError, e:
257 except EmptyRepositoryError, e:
256 h.flash(h.literal(_('There are no changesets yet')),
258 h.flash(h.literal(_('There are no changesets yet')),
257 category='warning')
259 category='warning')
258 redirect(url('summary_home', repo_name=org_repo.repo_name))
260 redirect(url('summary_home', repo_name=org_repo.repo_name))
259
261
260 org_rev = request.GET.get('rev_end')
262 org_rev = request.GET.get('rev_end')
261 # rev_start is not directly useful - its parent could however be used
263 # rev_start is not directly useful - its parent could however be used
262 # as default for other and thus give a simple compare view
264 # as default for other and thus give a simple compare view
263 #other_rev = request.POST.get('rev_start')
265 #other_rev = request.POST.get('rev_start')
264 branch = request.GET.get('branch')
266 branch = request.GET.get('branch')
265
267
266 c.org_repos = []
268 c.org_repos = []
267 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
269 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
268 c.default_org_repo = org_repo.repo_name
270 c.default_org_repo = org_repo.repo_name
269 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
271 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
270
272
271 c.other_repos = []
273 c.other_repos = []
272 other_repos_info = {}
274 other_repos_info = {}
273
275
274 def add_other_repo(repo, branch_rev=None):
276 def add_other_repo(repo, branch_rev=None):
275 if repo.repo_name in other_repos_info: # shouldn't happen
277 if repo.repo_name in other_repos_info: # shouldn't happen
276 return
278 return
277 c.other_repos.append((repo.repo_name, repo.repo_name))
279 c.other_repos.append((repo.repo_name, repo.repo_name))
278 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
280 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
279 other_repos_info[repo.repo_name] = {
281 other_repos_info[repo.repo_name] = {
280 'user': dict(user_id=repo.user.user_id,
282 'user': dict(user_id=repo.user.user_id,
281 username=repo.user.username,
283 username=repo.user.username,
282 firstname=repo.user.firstname,
284 firstname=repo.user.firstname,
283 lastname=repo.user.lastname,
285 lastname=repo.user.lastname,
284 gravatar_link=h.gravatar_url(repo.user.email, 14)),
286 gravatar_link=h.gravatar_url(repo.user.email, 14)),
285 'description': repo.description.split('\n', 1)[0],
287 'description': repo.description.split('\n', 1)[0],
286 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
288 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
287 }
289 }
288
290
289 # add org repo to other so we can open pull request against peer branches on itself
291 # add org repo to other so we can open pull request against peer branches on itself
290 add_other_repo(org_repo, branch_rev=org_rev)
292 add_other_repo(org_repo, branch_rev=org_rev)
291 c.default_other_repo = org_repo.repo_name
293 c.default_other_repo = org_repo.repo_name
292
294
293 # gather forks and add to this list ... even though it is rare to
295 # gather forks and add to this list ... even though it is rare to
294 # request forks to pull from their parent
296 # request forks to pull from their parent
295 for fork in org_repo.forks:
297 for fork in org_repo.forks:
296 add_other_repo(fork)
298 add_other_repo(fork)
297
299
298 # add parents of this fork also, but only if it's not empty
300 # add parents of this fork also, but only if it's not empty
299 if org_repo.parent and org_repo.parent.scm_instance.revisions:
301 if org_repo.parent and org_repo.parent.scm_instance.revisions:
300 add_other_repo(org_repo.parent)
302 add_other_repo(org_repo.parent)
301 c.default_other_repo = org_repo.parent.repo_name
303 c.default_other_repo = org_repo.parent.repo_name
302
304
303 c.default_other_repo_info = other_repos_info[c.default_other_repo]
305 c.default_other_repo_info = other_repos_info[c.default_other_repo]
304 c.other_repos_info = json.dumps(other_repos_info)
306 c.other_repos_info = json.dumps(other_repos_info)
305
307
306 return render('/pullrequests/pullrequest.html')
308 return render('/pullrequests/pullrequest.html')
307
309
308 @LoginRequired()
310 @LoginRequired()
309 @NotAnonymous()
311 @NotAnonymous()
310 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
312 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
311 'repository.admin')
313 'repository.admin')
312 def create(self, repo_name):
314 def create(self, repo_name):
313 repo = RepoModel()._get_repo(repo_name)
315 repo = RepoModel()._get_repo(repo_name)
314 try:
316 try:
315 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
317 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
316 except formencode.Invalid, errors:
318 except formencode.Invalid, errors:
317 log.error(traceback.format_exc())
319 log.error(traceback.format_exc())
318 if errors.error_dict.get('revisions'):
320 if errors.error_dict.get('revisions'):
319 msg = 'Revisions: %s' % errors.error_dict['revisions']
321 msg = 'Revisions: %s' % errors.error_dict['revisions']
320 elif errors.error_dict.get('pullrequest_title'):
322 elif errors.error_dict.get('pullrequest_title'):
321 msg = _('Pull request requires a title with min. 3 chars')
323 msg = _('Pull request requires a title with min. 3 chars')
322 else:
324 else:
323 msg = _('Error creating pull request')
325 msg = _('Error creating pull request')
324
326
325 h.flash(msg, 'error')
327 h.flash(msg, 'error')
326 return redirect(url('pullrequest_home', repo_name=repo_name))
328 return redirect(url('pullrequest_home', repo_name=repo_name))
327
329
328 org_repo = _form['org_repo']
330 org_repo = _form['org_repo']
329 org_ref = _form['org_ref'] # will end with merge_rev but have symbolic name
331 org_ref = _form['org_ref'] # will end with merge_rev but have symbolic name
330 other_repo = _form['other_repo']
332 other_repo = _form['other_repo']
331 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev'] # could be calculated from other_ref ...
333 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev'] # could be calculated from other_ref ...
332 revisions = [x for x in reversed(_form['revisions'])]
334 revisions = [x for x in reversed(_form['revisions'])]
333 reviewers = _form['review_members']
335 reviewers = _form['review_members']
334
336
335 title = _form['pullrequest_title']
337 title = _form['pullrequest_title']
336 description = _form['pullrequest_desc']
338 description = _form['pullrequest_desc']
337 try:
339 try:
338 pull_request = PullRequestModel().create(
340 pull_request = PullRequestModel().create(
339 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
341 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
340 other_ref, revisions, reviewers, title, description
342 other_ref, revisions, reviewers, title, description
341 )
343 )
342 Session().commit()
344 Session().commit()
343 h.flash(_('Successfully opened new pull request'),
345 h.flash(_('Successfully opened new pull request'),
344 category='success')
346 category='success')
345 except Exception:
347 except Exception:
346 h.flash(_('Error occurred during sending pull request'),
348 h.flash(_('Error occurred during sending pull request'),
347 category='error')
349 category='error')
348 log.error(traceback.format_exc())
350 log.error(traceback.format_exc())
349 return redirect(url('pullrequest_home', repo_name=repo_name))
351 return redirect(url('pullrequest_home', repo_name=repo_name))
350
352
351 return redirect(url('pullrequest_show', repo_name=other_repo,
353 return redirect(url('pullrequest_show', repo_name=other_repo,
352 pull_request_id=pull_request.pull_request_id))
354 pull_request_id=pull_request.pull_request_id))
353
355
354 @LoginRequired()
356 @LoginRequired()
355 @NotAnonymous()
357 @NotAnonymous()
356 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
358 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
357 'repository.admin')
359 'repository.admin')
358 @jsonify
360 @jsonify
359 def update(self, repo_name, pull_request_id):
361 def update(self, repo_name, pull_request_id):
360 pull_request = PullRequest.get_or_404(pull_request_id)
362 pull_request = PullRequest.get_or_404(pull_request_id)
361 if pull_request.is_closed():
363 if pull_request.is_closed():
362 raise HTTPForbidden()
364 raise HTTPForbidden()
363 #only owner or admin can update it
365 #only owner or admin can update it
364 owner = pull_request.author.user_id == c.rhodecode_user.user_id
366 owner = pull_request.author.user_id == c.rhodecode_user.user_id
365 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
367 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
366 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
368 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
367 request.POST.get('reviewers_ids', '').split(',')))
369 request.POST.get('reviewers_ids', '').split(',')))
368
370
369 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
371 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
370 Session().commit()
372 Session().commit()
371 return True
373 return True
372 raise HTTPForbidden()
374 raise HTTPForbidden()
373
375
374 @LoginRequired()
376 @LoginRequired()
375 @NotAnonymous()
377 @NotAnonymous()
376 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
378 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
377 'repository.admin')
379 'repository.admin')
378 @jsonify
380 @jsonify
379 def delete(self, repo_name, pull_request_id):
381 def delete(self, repo_name, pull_request_id):
380 pull_request = PullRequest.get_or_404(pull_request_id)
382 pull_request = PullRequest.get_or_404(pull_request_id)
381 #only owner can delete it !
383 #only owner can delete it !
382 if pull_request.author.user_id == c.rhodecode_user.user_id:
384 if pull_request.author.user_id == c.rhodecode_user.user_id:
383 PullRequestModel().delete(pull_request)
385 PullRequestModel().delete(pull_request)
384 Session().commit()
386 Session().commit()
385 h.flash(_('Successfully deleted pull request'),
387 h.flash(_('Successfully deleted pull request'),
386 category='success')
388 category='success')
387 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
389 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
388 raise HTTPForbidden()
390 raise HTTPForbidden()
389
391
390 @LoginRequired()
392 @LoginRequired()
391 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
393 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
392 'repository.admin')
394 'repository.admin')
393 def show(self, repo_name, pull_request_id):
395 def show(self, repo_name, pull_request_id):
394 repo_model = RepoModel()
396 repo_model = RepoModel()
395 c.users_array = repo_model.get_users_js()
397 c.users_array = repo_model.get_users_js()
396 c.users_groups_array = repo_model.get_users_groups_js()
398 c.users_groups_array = repo_model.get_users_groups_js()
397 c.pull_request = PullRequest.get_or_404(pull_request_id)
399 c.pull_request = PullRequest.get_or_404(pull_request_id)
398 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
400 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
399 cc_model = ChangesetCommentsModel()
401 cc_model = ChangesetCommentsModel()
400 cs_model = ChangesetStatusModel()
402 cs_model = ChangesetStatusModel()
401 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
403 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
402 pull_request=c.pull_request,
404 pull_request=c.pull_request,
403 with_revisions=True)
405 with_revisions=True)
404
406
405 cs_statuses = defaultdict(list)
407 cs_statuses = defaultdict(list)
406 for st in _cs_statuses:
408 for st in _cs_statuses:
407 cs_statuses[st.author.username] += [st]
409 cs_statuses[st.author.username] += [st]
408
410
409 c.pull_request_reviewers = []
411 c.pull_request_reviewers = []
410 c.pull_request_pending_reviewers = []
412 c.pull_request_pending_reviewers = []
411 for o in c.pull_request.reviewers:
413 for o in c.pull_request.reviewers:
412 st = cs_statuses.get(o.user.username, None)
414 st = cs_statuses.get(o.user.username, None)
413 if st:
415 if st:
414 sorter = lambda k: k.version
416 sorter = lambda k: k.version
415 st = [(x, list(y)[0])
417 st = [(x, list(y)[0])
416 for x, y in (groupby(sorted(st, key=sorter), sorter))]
418 for x, y in (groupby(sorted(st, key=sorter), sorter))]
417 else:
419 else:
418 c.pull_request_pending_reviewers.append(o.user)
420 c.pull_request_pending_reviewers.append(o.user)
419 c.pull_request_reviewers.append([o.user, st])
421 c.pull_request_reviewers.append([o.user, st])
420
422
421 # pull_requests repo_name we opened it against
423 # pull_requests repo_name we opened it against
422 # ie. other_repo must match
424 # ie. other_repo must match
423 if repo_name != c.pull_request.other_repo.repo_name:
425 if repo_name != c.pull_request.other_repo.repo_name:
424 raise HTTPNotFound
426 raise HTTPNotFound
425
427
426 # load compare data into template context
428 # load compare data into template context
427 enable_comments = not c.pull_request.is_closed()
429 enable_comments = not c.pull_request.is_closed()
428 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
430 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
429
431
430 # inline comments
432 # inline comments
431 c.inline_cnt = 0
433 c.inline_cnt = 0
432 c.inline_comments = cc_model.get_inline_comments(
434 c.inline_comments = cc_model.get_inline_comments(
433 c.rhodecode_db_repo.repo_id,
435 c.rhodecode_db_repo.repo_id,
434 pull_request=pull_request_id)
436 pull_request=pull_request_id)
435 # count inline comments
437 # count inline comments
436 for __, lines in c.inline_comments:
438 for __, lines in c.inline_comments:
437 for comments in lines.values():
439 for comments in lines.values():
438 c.inline_cnt += len(comments)
440 c.inline_cnt += len(comments)
439 # comments
441 # comments
440 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
442 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
441 pull_request=pull_request_id)
443 pull_request=pull_request_id)
442
444
443 try:
445 try:
444 cur_status = c.statuses[c.pull_request.revisions[0]][0]
446 cur_status = c.statuses[c.pull_request.revisions[0]][0]
445 except Exception:
447 except Exception:
446 log.error(traceback.format_exc())
448 log.error(traceback.format_exc())
447 cur_status = 'undefined'
449 cur_status = 'undefined'
448 if c.pull_request.is_closed() and 0:
450 if c.pull_request.is_closed() and 0:
449 c.current_changeset_status = cur_status
451 c.current_changeset_status = cur_status
450 else:
452 else:
451 # changeset(pull-request) status calulation based on reviewers
453 # changeset(pull-request) status calulation based on reviewers
452 c.current_changeset_status = cs_model.calculate_status(
454 c.current_changeset_status = cs_model.calculate_status(
453 c.pull_request_reviewers,
455 c.pull_request_reviewers,
454 )
456 )
455 c.changeset_statuses = ChangesetStatus.STATUSES
457 c.changeset_statuses = ChangesetStatus.STATUSES
456
458
457 c.as_form = False
459 c.as_form = False
458 c.ancestor = None # there is one - but right here we don't know which
460 c.ancestor = None # there is one - but right here we don't know which
459 return render('/pullrequests/pullrequest_show.html')
461 return render('/pullrequests/pullrequest_show.html')
460
462
461 @LoginRequired()
463 @LoginRequired()
462 @NotAnonymous()
464 @NotAnonymous()
463 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
465 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
464 'repository.admin')
466 'repository.admin')
465 @jsonify
467 @jsonify
466 def comment(self, repo_name, pull_request_id):
468 def comment(self, repo_name, pull_request_id):
467 pull_request = PullRequest.get_or_404(pull_request_id)
469 pull_request = PullRequest.get_or_404(pull_request_id)
468 if pull_request.is_closed():
470 if pull_request.is_closed():
469 raise HTTPForbidden()
471 raise HTTPForbidden()
470
472
471 status = request.POST.get('changeset_status')
473 status = request.POST.get('changeset_status')
472 change_status = request.POST.get('change_changeset_status')
474 change_status = request.POST.get('change_changeset_status')
473 text = request.POST.get('text')
475 text = request.POST.get('text')
474 close_pr = request.POST.get('save_close')
476 close_pr = request.POST.get('save_close')
475
477
476 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
478 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
477 if status and change_status and allowed_to_change_status:
479 if status and change_status and allowed_to_change_status:
478 _def = (_('Status change -> %s')
480 _def = (_('Status change -> %s')
479 % ChangesetStatus.get_status_lbl(status))
481 % ChangesetStatus.get_status_lbl(status))
480 if close_pr:
482 if close_pr:
481 _def = _('Closing with') + ' ' + _def
483 _def = _('Closing with') + ' ' + _def
482 text = text or _def
484 text = text or _def
483 comm = ChangesetCommentsModel().create(
485 comm = ChangesetCommentsModel().create(
484 text=text,
486 text=text,
485 repo=c.rhodecode_db_repo.repo_id,
487 repo=c.rhodecode_db_repo.repo_id,
486 user=c.rhodecode_user.user_id,
488 user=c.rhodecode_user.user_id,
487 pull_request=pull_request_id,
489 pull_request=pull_request_id,
488 f_path=request.POST.get('f_path'),
490 f_path=request.POST.get('f_path'),
489 line_no=request.POST.get('line'),
491 line_no=request.POST.get('line'),
490 status_change=(ChangesetStatus.get_status_lbl(status)
492 status_change=(ChangesetStatus.get_status_lbl(status)
491 if status and change_status
493 if status and change_status
492 and allowed_to_change_status else None),
494 and allowed_to_change_status else None),
493 closing_pr=close_pr
495 closing_pr=close_pr
494 )
496 )
495
497
496 action_logger(self.rhodecode_user,
498 action_logger(self.rhodecode_user,
497 'user_commented_pull_request:%s' % pull_request_id,
499 'user_commented_pull_request:%s' % pull_request_id,
498 c.rhodecode_db_repo, self.ip_addr, self.sa)
500 c.rhodecode_db_repo, self.ip_addr, self.sa)
499
501
500 if allowed_to_change_status:
502 if allowed_to_change_status:
501 # get status if set !
503 # get status if set !
502 if status and change_status:
504 if status and change_status:
503 ChangesetStatusModel().set_status(
505 ChangesetStatusModel().set_status(
504 c.rhodecode_db_repo.repo_id,
506 c.rhodecode_db_repo.repo_id,
505 status,
507 status,
506 c.rhodecode_user.user_id,
508 c.rhodecode_user.user_id,
507 comm,
509 comm,
508 pull_request=pull_request_id
510 pull_request=pull_request_id
509 )
511 )
510
512
511 if close_pr:
513 if close_pr:
512 if status in ['rejected', 'approved']:
514 if status in ['rejected', 'approved']:
513 PullRequestModel().close_pull_request(pull_request_id)
515 PullRequestModel().close_pull_request(pull_request_id)
514 action_logger(self.rhodecode_user,
516 action_logger(self.rhodecode_user,
515 'user_closed_pull_request:%s' % pull_request_id,
517 'user_closed_pull_request:%s' % pull_request_id,
516 c.rhodecode_db_repo, self.ip_addr, self.sa)
518 c.rhodecode_db_repo, self.ip_addr, self.sa)
517 else:
519 else:
518 h.flash(_('Closing pull request on other statuses than '
520 h.flash(_('Closing pull request on other statuses than '
519 'rejected or approved forbidden'),
521 'rejected or approved forbidden'),
520 category='warning')
522 category='warning')
521
523
522 Session().commit()
524 Session().commit()
523
525
524 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
526 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
525 return redirect(h.url('pullrequest_show', repo_name=repo_name,
527 return redirect(h.url('pullrequest_show', repo_name=repo_name,
526 pull_request_id=pull_request_id))
528 pull_request_id=pull_request_id))
527
529
528 data = {
530 data = {
529 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
531 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
530 }
532 }
531 if comm:
533 if comm:
532 c.co = comm
534 c.co = comm
533 data.update(comm.get_dict())
535 data.update(comm.get_dict())
534 data.update({'rendered_text':
536 data.update({'rendered_text':
535 render('changeset/changeset_comment_block.html')})
537 render('changeset/changeset_comment_block.html')})
536
538
537 return data
539 return data
538
540
539 @LoginRequired()
541 @LoginRequired()
540 @NotAnonymous()
542 @NotAnonymous()
541 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
543 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
542 'repository.admin')
544 'repository.admin')
543 @jsonify
545 @jsonify
544 def delete_comment(self, repo_name, comment_id):
546 def delete_comment(self, repo_name, comment_id):
545 co = ChangesetComment.get(comment_id)
547 co = ChangesetComment.get(comment_id)
546 if co.pull_request.is_closed():
548 if co.pull_request.is_closed():
547 #don't allow deleting comments on closed pull request
549 #don't allow deleting comments on closed pull request
548 raise HTTPForbidden()
550 raise HTTPForbidden()
549
551
550 owner = co.author.user_id == c.rhodecode_user.user_id
552 owner = co.author.user_id == c.rhodecode_user.user_id
551 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
553 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
552 ChangesetCommentsModel().delete(comment=co)
554 ChangesetCommentsModel().delete(comment=co)
553 Session().commit()
555 Session().commit()
554 return True
556 return True
555 else:
557 else:
556 raise HTTPForbidden()
558 raise HTTPForbidden()
@@ -1,156 +1,163 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.pull_request
3 rhodecode.model.pull_request
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull request model for RhodeCode
6 pull request model for RhodeCode
7
7
8 :created_on: Jun 6, 2012
8 :created_on: Jun 6, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import datetime
27 import datetime
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from rhodecode.model.meta import Session
31 from rhodecode.model.meta import Session
32 from rhodecode.lib import helpers as h
32 from rhodecode.lib import helpers as h
33 from rhodecode.model import BaseModel
33 from rhodecode.model import BaseModel
34 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
34 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification,\
35 ChangesetStatus
35 ChangesetStatus
36 from rhodecode.model.notification import NotificationModel
36 from rhodecode.model.notification import NotificationModel
37 from rhodecode.lib.utils2 import safe_unicode
37 from rhodecode.lib.utils2 import safe_unicode
38
38
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class PullRequestModel(BaseModel):
43 class PullRequestModel(BaseModel):
44
44
45 cls = PullRequest
45 cls = PullRequest
46
46
47 def __get_pull_request(self, pull_request):
47 def __get_pull_request(self, pull_request):
48 return self._get_instance(PullRequest, pull_request)
48 return self._get_instance(PullRequest, pull_request)
49
49
50 def get_all(self, repo):
50 def get_all(self, repo_name, from_=False, closed=False):
51 repo = self._get_repo(repo)
51 """Get all PRs for repo.
52 return PullRequest.query()\
52 Default is all PRs to the repo, PRs from the repo if from_.
53 .filter(PullRequest.other_repo == repo)\
53 Closed PRs are only included if closed is true."""
54 .order_by(PullRequest.created_on.desc())\
54 repo = self._get_repo(repo_name)
55 .all()
55 q = PullRequest.query()
56 if from_:
57 q = q.filter(PullRequest.org_repo == repo)
58 else:
59 q = q.filter(PullRequest.other_repo == repo)
60 if not closed:
61 q = q.filter(PullRequest.status != PullRequest.STATUS_CLOSED)
62 return q.order_by(PullRequest.created_on.desc()).all()
56
63
57 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
64 def create(self, created_by, org_repo, org_ref, other_repo, other_ref,
58 revisions, reviewers, title, description=None):
65 revisions, reviewers, title, description=None):
59 from rhodecode.model.changeset_status import ChangesetStatusModel
66 from rhodecode.model.changeset_status import ChangesetStatusModel
60
67
61 created_by_user = self._get_user(created_by)
68 created_by_user = self._get_user(created_by)
62 org_repo = self._get_repo(org_repo)
69 org_repo = self._get_repo(org_repo)
63 other_repo = self._get_repo(other_repo)
70 other_repo = self._get_repo(other_repo)
64
71
65 new = PullRequest()
72 new = PullRequest()
66 new.org_repo = org_repo
73 new.org_repo = org_repo
67 new.org_ref = org_ref
74 new.org_ref = org_ref
68 new.other_repo = other_repo
75 new.other_repo = other_repo
69 new.other_ref = other_ref
76 new.other_ref = other_ref
70 new.revisions = revisions
77 new.revisions = revisions
71 new.title = title
78 new.title = title
72 new.description = description
79 new.description = description
73 new.author = created_by_user
80 new.author = created_by_user
74 Session().add(new)
81 Session().add(new)
75 Session().flush()
82 Session().flush()
76 #members
83 #members
77 for member in set(reviewers):
84 for member in set(reviewers):
78 _usr = self._get_user(member)
85 _usr = self._get_user(member)
79 reviewer = PullRequestReviewers(_usr, new)
86 reviewer = PullRequestReviewers(_usr, new)
80 Session().add(reviewer)
87 Session().add(reviewer)
81
88
82 #reset state to under-review
89 #reset state to under-review
83 ChangesetStatusModel().set_status(
90 ChangesetStatusModel().set_status(
84 repo=org_repo,
91 repo=org_repo,
85 status=ChangesetStatus.STATUS_UNDER_REVIEW,
92 status=ChangesetStatus.STATUS_UNDER_REVIEW,
86 user=created_by_user,
93 user=created_by_user,
87 pull_request=new
94 pull_request=new
88 )
95 )
89 revision_data = [(x.raw_id, x.message)
96 revision_data = [(x.raw_id, x.message)
90 for x in map(org_repo.get_changeset, revisions)]
97 for x in map(org_repo.get_changeset, revisions)]
91 #notification to reviewers
98 #notification to reviewers
92 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
99 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
93 pull_request_id=new.pull_request_id,
100 pull_request_id=new.pull_request_id,
94 qualified=True,
101 qualified=True,
95 )
102 )
96 subject = safe_unicode(
103 subject = safe_unicode(
97 h.link_to(
104 h.link_to(
98 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
105 _('%(user)s wants you to review pull request #%(pr_id)s: %(pr_title)s') % \
99 {'user': created_by_user.username,
106 {'user': created_by_user.username,
100 'pr_title': new.title,
107 'pr_title': new.title,
101 'pr_id': new.pull_request_id},
108 'pr_id': new.pull_request_id},
102 pr_url
109 pr_url
103 )
110 )
104 )
111 )
105 body = description
112 body = description
106 kwargs = {
113 kwargs = {
107 'pr_title': title,
114 'pr_title': title,
108 'pr_user_created': h.person(created_by_user.email),
115 'pr_user_created': h.person(created_by_user.email),
109 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
116 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
110 qualified=True,),
117 qualified=True,),
111 'pr_url': pr_url,
118 'pr_url': pr_url,
112 'pr_revisions': revision_data
119 'pr_revisions': revision_data
113 }
120 }
114
121
115 NotificationModel().create(created_by=created_by_user, subject=subject, body=body,
122 NotificationModel().create(created_by=created_by_user, subject=subject, body=body,
116 recipients=reviewers,
123 recipients=reviewers,
117 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
124 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
118 return new
125 return new
119
126
120 def update_reviewers(self, pull_request, reviewers_ids):
127 def update_reviewers(self, pull_request, reviewers_ids):
121 reviewers_ids = set(reviewers_ids)
128 reviewers_ids = set(reviewers_ids)
122 pull_request = self.__get_pull_request(pull_request)
129 pull_request = self.__get_pull_request(pull_request)
123 current_reviewers = PullRequestReviewers.query()\
130 current_reviewers = PullRequestReviewers.query()\
124 .filter(PullRequestReviewers.pull_request==
131 .filter(PullRequestReviewers.pull_request==
125 pull_request)\
132 pull_request)\
126 .all()
133 .all()
127 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
134 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
128
135
129 to_add = reviewers_ids.difference(current_reviewers_ids)
136 to_add = reviewers_ids.difference(current_reviewers_ids)
130 to_remove = current_reviewers_ids.difference(reviewers_ids)
137 to_remove = current_reviewers_ids.difference(reviewers_ids)
131
138
132 log.debug("Adding %s reviewers" % to_add)
139 log.debug("Adding %s reviewers" % to_add)
133 log.debug("Removing %s reviewers" % to_remove)
140 log.debug("Removing %s reviewers" % to_remove)
134
141
135 for uid in to_add:
142 for uid in to_add:
136 _usr = self._get_user(uid)
143 _usr = self._get_user(uid)
137 reviewer = PullRequestReviewers(_usr, pull_request)
144 reviewer = PullRequestReviewers(_usr, pull_request)
138 Session().add(reviewer)
145 Session().add(reviewer)
139
146
140 for uid in to_remove:
147 for uid in to_remove:
141 reviewer = PullRequestReviewers.query()\
148 reviewer = PullRequestReviewers.query()\
142 .filter(PullRequestReviewers.user_id==uid,
149 .filter(PullRequestReviewers.user_id==uid,
143 PullRequestReviewers.pull_request==pull_request)\
150 PullRequestReviewers.pull_request==pull_request)\
144 .scalar()
151 .scalar()
145 if reviewer:
152 if reviewer:
146 Session().delete(reviewer)
153 Session().delete(reviewer)
147
154
148 def delete(self, pull_request):
155 def delete(self, pull_request):
149 pull_request = self.__get_pull_request(pull_request)
156 pull_request = self.__get_pull_request(pull_request)
150 Session().delete(pull_request)
157 Session().delete(pull_request)
151
158
152 def close_pull_request(self, pull_request):
159 def close_pull_request(self, pull_request):
153 pull_request = self.__get_pull_request(pull_request)
160 pull_request = self.__get_pull_request(pull_request)
154 pull_request.status = PullRequest.STATUS_CLOSED
161 pull_request.status = PullRequest.STATUS_CLOSED
155 pull_request.updated_on = datetime.datetime.now()
162 pull_request.updated_on = datetime.datetime.now()
156 Session().add(pull_request)
163 Session().add(pull_request)
@@ -1,361 +1,361 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <!-- HEADER -->
4 <!-- HEADER -->
5 <div id="header-dd"></div>
5 <div id="header-dd"></div>
6 <div id="header">
6 <div id="header">
7 <div id="header-inner" class="title">
7 <div id="header-inner" class="title">
8 <div id="logo">
8 <div id="logo">
9 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
9 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
10 </div>
10 </div>
11 <!-- MENU -->
11 <!-- MENU -->
12 ${self.page_nav()}
12 ${self.page_nav()}
13 <!-- END MENU -->
13 <!-- END MENU -->
14 ${self.body()}
14 ${self.body()}
15 </div>
15 </div>
16 </div>
16 </div>
17 <!-- END HEADER -->
17 <!-- END HEADER -->
18
18
19 <!-- CONTENT -->
19 <!-- CONTENT -->
20 <div id="content">
20 <div id="content">
21 <div class="flash_msg">
21 <div class="flash_msg">
22 <% messages = h.flash.pop_messages() %>
22 <% messages = h.flash.pop_messages() %>
23 % if messages:
23 % if messages:
24 <ul id="flash-messages">
24 <ul id="flash-messages">
25 % for message in messages:
25 % for message in messages:
26 <li class="${message.category}_msg">${message}</li>
26 <li class="${message.category}_msg">${message}</li>
27 % endfor
27 % endfor
28 </ul>
28 </ul>
29 % endif
29 % endif
30 </div>
30 </div>
31 <div id="main">
31 <div id="main">
32 ${next.main()}
32 ${next.main()}
33 </div>
33 </div>
34 </div>
34 </div>
35 <!-- END CONTENT -->
35 <!-- END CONTENT -->
36
36
37 <!-- FOOTER -->
37 <!-- FOOTER -->
38 <div id="footer">
38 <div id="footer">
39 <div id="footer-inner" class="title">
39 <div id="footer-inner" class="title">
40 <div>
40 <div>
41 <p class="footer-link">
41 <p class="footer-link">
42 ${_('Server instance: %s') % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}
42 ${_('Server instance: %s') % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}
43 </p>
43 </p>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 <a href="${h.url('rhodecode_official')}">
45 <a href="${h.url('rhodecode_official')}">
46 RhodeCode
46 RhodeCode
47 %if c.visual.show_version:
47 %if c.visual.show_version:
48 ${c.rhodecode_version}
48 ${c.rhodecode_version}
49 %endif
49 %endif
50 </a>
50 </a>
51 &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski and others
51 &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski and others
52 %if c.rhodecode_bugtracker:
52 %if c.rhodecode_bugtracker:
53 &ndash; <a href="${c.rhodecode_bugtracker}">${_('Report a bug')}</a>
53 &ndash; <a href="${c.rhodecode_bugtracker}">${_('Report a bug')}</a>
54 %endif
54 %endif
55 </p>
55 </p>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <!-- END FOOTER -->
60 <!-- END FOOTER -->
61
61
62 ### MAKO DEFS ###
62 ### MAKO DEFS ###
63 <%def name="breadcrumbs()">
63 <%def name="breadcrumbs()">
64 <div class="breadcrumbs">
64 <div class="breadcrumbs">
65 ${self.breadcrumbs_links()}
65 ${self.breadcrumbs_links()}
66 </div>
66 </div>
67 </%def>
67 </%def>
68
68
69 <%def name="admin_menu()">
69 <%def name="admin_menu()">
70 <ul class="admin_menu">
70 <ul class="admin_menu">
71 <li>${h.link_to(_('Admin journal'),h.url('admin_home'),class_='journal ')}</li>
71 <li>${h.link_to(_('Admin journal'),h.url('admin_home'),class_='journal ')}</li>
72 <li>${h.link_to(_('Repositories'),h.url('repos'),class_='repos')}</li>
72 <li>${h.link_to(_('Repositories'),h.url('repos'),class_='repos')}</li>
73 <li>${h.link_to(_('Repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
73 <li>${h.link_to(_('Repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
74 <li>${h.link_to(_('Users'),h.url('users'),class_='users')}</li>
74 <li>${h.link_to(_('Users'),h.url('users'),class_='users')}</li>
75 <li>${h.link_to(_('User groups'),h.url('users_groups'),class_='groups')}</li>
75 <li>${h.link_to(_('User groups'),h.url('users_groups'),class_='groups')}</li>
76 <li>${h.link_to(_('Permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
76 <li>${h.link_to(_('Permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
77 <li>${h.link_to(_('LDAP'),h.url('ldap_home'),class_='ldap')}</li>
77 <li>${h.link_to(_('LDAP'),h.url('ldap_home'),class_='ldap')}</li>
78 <li>${h.link_to(_('Defaults'),h.url('defaults'),class_='defaults')}</li>
78 <li>${h.link_to(_('Defaults'),h.url('defaults'),class_='defaults')}</li>
79 <li class="last">${h.link_to(_('Settings'),h.url('admin_settings'),class_='settings')}</li>
79 <li class="last">${h.link_to(_('Settings'),h.url('admin_settings'),class_='settings')}</li>
80 </ul>
80 </ul>
81 </%def>
81 </%def>
82
82
83 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
83 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
84 <ul>
84 <ul>
85 %if repositories:
85 %if repositories:
86 <li>${h.link_to(_('Repositories'),h.url('repos'),class_='repos')}</li>
86 <li>${h.link_to(_('Repositories'),h.url('repos'),class_='repos')}</li>
87 %endif
87 %endif
88 %if repository_groups:
88 %if repository_groups:
89 <li>${h.link_to(_('Repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
89 <li>${h.link_to(_('Repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
90 %endif
90 %endif
91 %if user_groups:
91 %if user_groups:
92 <li>${h.link_to(_('User groups'),h.url('users_groups'),class_='groups')}</li>
92 <li>${h.link_to(_('User groups'),h.url('users_groups'),class_='groups')}</li>
93 %endif
93 %endif
94 </ul>
94 </ul>
95 </%def>
95 </%def>
96
96
97 <%def name="repo_context_bar(current=None)">
97 <%def name="repo_context_bar(current=None)">
98 <%
98 <%
99 def follow_class():
99 def follow_class():
100 if c.repository_following:
100 if c.repository_following:
101 return h.literal('following')
101 return h.literal('following')
102 else:
102 else:
103 return h.literal('follow')
103 return h.literal('follow')
104 %>
104 %>
105 <%
105 <%
106 def is_current(selected):
106 def is_current(selected):
107 if selected == current:
107 if selected == current:
108 return h.literal('class="current"')
108 return h.literal('class="current"')
109 %>
109 %>
110
110
111 <!--- CONTEXT BAR -->
111 <!--- CONTEXT BAR -->
112 <div id="context-bar" class="box">
112 <div id="context-bar" class="box">
113 <div id="breadcrumbs">
113 <div id="breadcrumbs">
114 ${h.link_to(_(u'Repositories'),h.url('home'))}
114 ${h.link_to(_(u'Repositories'),h.url('home'))}
115 &raquo;
115 &raquo;
116 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
116 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
117 </div>
117 </div>
118 <ul id="context-pages" class="horizontal-list">
118 <ul id="context-pages" class="horizontal-list">
119 <li ${is_current('summary')}><a href="${h.url('summary_home', repo_name=c.repo_name)}" class="summary">${_('Summary')}</a></li>
119 <li ${is_current('summary')}><a href="${h.url('summary_home', repo_name=c.repo_name)}" class="summary">${_('Summary')}</a></li>
120 <li ${is_current('changelog')}><a href="${h.url('changelog_home', repo_name=c.repo_name)}" class="changelogs">${_('Changelog')}</a></li>
120 <li ${is_current('changelog')}><a href="${h.url('changelog_home', repo_name=c.repo_name)}" class="changelogs">${_('Changelog')}</a></li>
121 <li ${is_current('files')}><a href="${h.url('files_home', repo_name=c.repo_name)}" class="files"></span>${_('Files')}</a></li>
121 <li ${is_current('files')}><a href="${h.url('files_home', repo_name=c.repo_name)}" class="files"></span>${_('Files')}</a></li>
122 <li ${is_current('switch-to')}>
122 <li ${is_current('switch-to')}>
123 <a href="#" id="branch_tag_switcher_2" class="dropdown switch-to"></span>${_('Switch To')}</a>
123 <a href="#" id="branch_tag_switcher_2" class="dropdown switch-to"></span>${_('Switch To')}</a>
124 <ul id="switch_to_list_2" class="switch_to submenu">
124 <ul id="switch_to_list_2" class="switch_to submenu">
125 <li><a href="#">${_('Loading...')}</a></li>
125 <li><a href="#">${_('Loading...')}</a></li>
126 </ul>
126 </ul>
127 </li>
127 </li>
128 <li ${is_current('options')}>
128 <li ${is_current('options')}>
129 <a href="#" class="dropdown options"></span>${_('Options')}</a>
129 <a href="#" class="dropdown options"></span>${_('Options')}</a>
130 <ul>
130 <ul>
131 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
131 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
132 <li>${h.link_to(_('Settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
132 <li>${h.link_to(_('Settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
133 %endif
133 %endif
134 %if c.rhodecode_db_repo.fork:
134 %if c.rhodecode_db_repo.fork:
135 <li>${h.link_to(_('Compare fork'),h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default', merge=1),class_='compare_request')}</li>
135 <li>${h.link_to(_('Compare fork'),h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default', merge=1),class_='compare_request')}</li>
136 %endif
136 %endif
137 <li>${h.link_to(_('Search'),h.url('search_repo',repo_name=c.repo_name),class_='search')}</li>
137 <li>${h.link_to(_('Search'),h.url('search_repo',repo_name=c.repo_name),class_='search')}</li>
138
138
139 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
139 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
140 %if c.rhodecode_db_repo.locked[0]:
140 %if c.rhodecode_db_repo.locked[0]:
141 <li>${h.link_to(_('Unlock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_del')}</li>
141 <li>${h.link_to(_('Unlock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_del')}</li>
142 %else:
142 %else:
143 <li>${h.link_to(_('Lock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_add')}</li>
143 <li>${h.link_to(_('Lock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_add')}</li>
144 %endif
144 %endif
145 %endif
145 %endif
146 ## TODO: this check feels wrong, it would be better to have a check for permissions
146 ## TODO: this check feels wrong, it would be better to have a check for permissions
147 ## also it feels like a job for the controller
147 ## also it feels like a job for the controller
148 %if c.rhodecode_user.username != 'default':
148 %if c.rhodecode_user.username != 'default':
149 <li>
149 <li>
150 <a class="${follow_class()}" onclick="javascript:toggleFollowingRepo(this,${c.rhodecode_db_repo.repo_id},'${str(h.get_token())}');">
150 <a class="${follow_class()}" onclick="javascript:toggleFollowingRepo(this,${c.rhodecode_db_repo.repo_id},'${str(h.get_token())}');">
151 <span class="show-follow">${_('Follow')}</span>
151 <span class="show-follow">${_('Follow')}</span>
152 <span class="show-following">${_('Unfollow')}</span>
152 <span class="show-following">${_('Unfollow')}</span>
153 </a>
153 </a>
154 </li>
154 </li>
155 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}" class="fork">${_('Fork')}</a></li>
155 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}" class="fork">${_('Fork')}</a></li>
156 %if h.is_hg(c.rhodecode_repo):
156 %if h.is_hg(c.rhodecode_repo):
157 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="pull-request">${_('Create Pull Request')}</a></li>
157 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="pull-request">${_('Create Pull Request')}</a></li>
158 %endif
158 %endif
159 %endif
159 %endif
160 </ul>
160 </ul>
161 </li>
161 </li>
162 <li ${is_current('showpullrequest')}>
162 <li ${is_current('showpullrequest')}>
163 <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests')}" class="pull-request">${_('Pull Requests')}
163 <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}" class="pull-request">${_('Pull Requests')}
164 %if c.repository_pull_requests:
164 %if c.repository_pull_requests:
165 <span>${c.repository_pull_requests}</span>
165 <span>${c.repository_pull_requests}</span>
166 %endif
166 %endif
167 </a>
167 </a>
168 </li>
168 </li>
169 </ul>
169 </ul>
170 </div>
170 </div>
171 <script type="text/javascript">
171 <script type="text/javascript">
172 YUE.on('branch_tag_switcher_2','mouseover',function(){
172 YUE.on('branch_tag_switcher_2','mouseover',function(){
173 var loaded = YUD.hasClass('branch_tag_switcher_2','loaded');
173 var loaded = YUD.hasClass('branch_tag_switcher_2','loaded');
174 if(!loaded){
174 if(!loaded){
175 YUD.addClass('branch_tag_switcher_2','loaded');
175 YUD.addClass('branch_tag_switcher_2','loaded');
176 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list_2',
176 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list_2',
177 function(o){},
177 function(o){},
178 function(o){YUD.removeClass('branch_tag_switcher_2','loaded');}
178 function(o){YUD.removeClass('branch_tag_switcher_2','loaded');}
179 ,null);
179 ,null);
180 }
180 }
181 return false;
181 return false;
182 });
182 });
183 </script>
183 </script>
184 <!--- END CONTEXT BAR -->
184 <!--- END CONTEXT BAR -->
185 </%def>
185 </%def>
186
186
187 <%def name="usermenu()">
187 <%def name="usermenu()">
188 ## USER MENU
188 ## USER MENU
189 <li>
189 <li>
190 <a class="menu_link childs" id="quick_login_link">
190 <a class="menu_link childs" id="quick_login_link">
191 <span class="icon">
191 <span class="icon">
192 <img src="${h.gravatar_url(c.rhodecode_user.email,20)}" alt="avatar">
192 <img src="${h.gravatar_url(c.rhodecode_user.email,20)}" alt="avatar">
193 </span>
193 </span>
194 %if c.rhodecode_user.username != 'default':
194 %if c.rhodecode_user.username != 'default':
195 <span class="menu_link_user">${c.rhodecode_user.username}</span>
195 <span class="menu_link_user">${c.rhodecode_user.username}</span>
196 %if c.unread_notifications != 0:
196 %if c.unread_notifications != 0:
197 <span class="menu_link_notifications">${c.unread_notifications}</span>
197 <span class="menu_link_notifications">${c.unread_notifications}</span>
198 %endif
198 %endif
199 %else:
199 %else:
200 <span>${_('Not logged in')}</span>
200 <span>${_('Not logged in')}</span>
201 %endif
201 %endif
202 </a>
202 </a>
203
203
204 <div class="user-menu">
204 <div class="user-menu">
205 <div id="quick_login">
205 <div id="quick_login">
206 %if c.rhodecode_user.username == 'default':
206 %if c.rhodecode_user.username == 'default':
207 <h4>${_('Login to your account')}</h4>
207 <h4>${_('Login to your account')}</h4>
208 ${h.form(h.url('login_home',came_from=h.url.current()))}
208 ${h.form(h.url('login_home',came_from=h.url.current()))}
209 <div class="form">
209 <div class="form">
210 <div class="fields">
210 <div class="fields">
211 <div class="field">
211 <div class="field">
212 <div class="label">
212 <div class="label">
213 <label for="username">${_('Username')}:</label>
213 <label for="username">${_('Username')}:</label>
214 </div>
214 </div>
215 <div class="input">
215 <div class="input">
216 ${h.text('username',class_='focus')}
216 ${h.text('username',class_='focus')}
217 </div>
217 </div>
218
218
219 </div>
219 </div>
220 <div class="field">
220 <div class="field">
221 <div class="label">
221 <div class="label">
222 <label for="password">${_('Password')}:</label>
222 <label for="password">${_('Password')}:</label>
223 </div>
223 </div>
224 <div class="input">
224 <div class="input">
225 ${h.password('password',class_='focus')}
225 ${h.password('password',class_='focus')}
226 </div>
226 </div>
227
227
228 </div>
228 </div>
229 <div class="buttons">
229 <div class="buttons">
230 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
230 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
231 <div class="register">
231 <div class="register">
232 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
232 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
233 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
233 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
234 %endif
234 %endif
235 </div>
235 </div>
236 <div class="submit">
236 <div class="submit">
237 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
237 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
238 </div>
238 </div>
239 </div>
239 </div>
240 </div>
240 </div>
241 </div>
241 </div>
242 ${h.end_form()}
242 ${h.end_form()}
243 %else:
243 %else:
244 <div class="links_left">
244 <div class="links_left">
245 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
245 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
246 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
246 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
247 <div class="email">${c.rhodecode_user.email}</div>
247 <div class="email">${c.rhodecode_user.email}</div>
248 </div>
248 </div>
249 <div class="links_right">
249 <div class="links_right">
250 <ol class="links">
250 <ol class="links">
251 <li><a href="${h.url('notifications')}">${_('Notifications')}: ${c.unread_notifications}</a></li>
251 <li><a href="${h.url('notifications')}">${_('Notifications')}: ${c.unread_notifications}</a></li>
252 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
252 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
253 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
253 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
254 </ol>
254 </ol>
255 </div>
255 </div>
256 %endif
256 %endif
257 </div>
257 </div>
258 </div>
258 </div>
259
259
260 </li>
260 </li>
261 </%def>
261 </%def>
262
262
263 <%def name="menu(current=None)">
263 <%def name="menu(current=None)">
264 <%
264 <%
265 def is_current(selected):
265 def is_current(selected):
266 if selected == current:
266 if selected == current:
267 return h.literal('class="current"')
267 return h.literal('class="current"')
268 %>
268 %>
269 <ul id="quick" class="horizontal-list">
269 <ul id="quick" class="horizontal-list">
270 <!-- repo switcher -->
270 <!-- repo switcher -->
271 <li ${is_current('repositories')}>
271 <li ${is_current('repositories')}>
272 <a class="menu_link repo_switcher childs" id="repo_switcher" title="${_('Switch repository')}" href="${h.url('home')}">
272 <a class="menu_link repo_switcher childs" id="repo_switcher" title="${_('Switch repository')}" href="${h.url('home')}">
273 ${_('Repositories')}
273 ${_('Repositories')}
274 </a>
274 </a>
275 <ul id="repo_switcher_list" class="repo_switcher">
275 <ul id="repo_switcher_list" class="repo_switcher">
276 <li>
276 <li>
277 <a href="#">${_('Loading...')}</a>
277 <a href="#">${_('Loading...')}</a>
278 </li>
278 </li>
279 </ul>
279 </ul>
280 </li>
280 </li>
281 ##ROOT MENU
281 ##ROOT MENU
282 %if c.rhodecode_user.username != 'default':
282 %if c.rhodecode_user.username != 'default':
283 <li ${is_current('journal')}>
283 <li ${is_current('journal')}>
284 <a class="menu_link journal" title="${_('Show recent activity')}" href="${h.url('journal')}">
284 <a class="menu_link journal" title="${_('Show recent activity')}" href="${h.url('journal')}">
285 ${_('Journal')}
285 ${_('Journal')}
286 </a>
286 </a>
287 </li>
287 </li>
288 %else:
288 %else:
289 <li ${is_current('journal')}>
289 <li ${is_current('journal')}>
290 <a class="menu_link journal" title="${_('Public journal')}" href="${h.url('public_journal')}">
290 <a class="menu_link journal" title="${_('Public journal')}" href="${h.url('public_journal')}">
291 ${_('Public journal')}
291 ${_('Public journal')}
292 </a>
292 </a>
293 </li>
293 </li>
294 %endif
294 %endif
295 <li ${is_current('gists')}>
295 <li ${is_current('gists')}>
296 <a class="menu_link gists childs" title="${_('Show public gists')}" href="${h.url('gists')}">
296 <a class="menu_link gists childs" title="${_('Show public gists')}" href="${h.url('gists')}">
297 ${_('Gists')}
297 ${_('Gists')}
298 </a>
298 </a>
299 <ul class="admin_menu">
299 <ul class="admin_menu">
300 <li>${h.link_to(_('Create new gist'),h.url('new_gist'),class_='gists-new ')}</li>
300 <li>${h.link_to(_('Create new gist'),h.url('new_gist'),class_='gists-new ')}</li>
301 <li>${h.link_to(_('All public gists'),h.url('gists'),class_='gists ')}</li>
301 <li>${h.link_to(_('All public gists'),h.url('gists'),class_='gists ')}</li>
302 %if c.rhodecode_user.username != 'default':
302 %if c.rhodecode_user.username != 'default':
303 <li>${h.link_to(_('My public gists'),h.url('gists', public=1),class_='gists')}</li>
303 <li>${h.link_to(_('My public gists'),h.url('gists', public=1),class_='gists')}</li>
304 <li>${h.link_to(_('My private gists'),h.url('gists', private=1),class_='gists-private ')}</li>
304 <li>${h.link_to(_('My private gists'),h.url('gists', private=1),class_='gists-private ')}</li>
305 %endif
305 %endif
306 </ul>
306 </ul>
307 </li>
307 </li>
308 <li ${is_current('search')}>
308 <li ${is_current('search')}>
309 <a class="menu_link search" title="${_('Search in repositories')}" href="${h.url('search')}">
309 <a class="menu_link search" title="${_('Search in repositories')}" href="${h.url('search')}">
310 ${_('Search')}
310 ${_('Search')}
311 </a>
311 </a>
312 </li>
312 </li>
313 % if h.HasPermissionAll('hg.admin')('access admin main page'):
313 % if h.HasPermissionAll('hg.admin')('access admin main page'):
314 <li ${is_current('admin')}>
314 <li ${is_current('admin')}>
315 <a class="menu_link admin childs" title="${_('Admin')}" href="${h.url('admin_home')}">
315 <a class="menu_link admin childs" title="${_('Admin')}" href="${h.url('admin_home')}">
316 ${_('Admin')}
316 ${_('Admin')}
317 </a>
317 </a>
318 ${admin_menu()}
318 ${admin_menu()}
319 </li>
319 </li>
320 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
320 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
321 <li ${is_current('admin')}>
321 <li ${is_current('admin')}>
322 <a class="menu_link admin childs" title="${_('Admin')}">
322 <a class="menu_link admin childs" title="${_('Admin')}">
323 ${_('Admin')}
323 ${_('Admin')}
324 </a>
324 </a>
325 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
325 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
326 c.rhodecode_user.repository_groups_admin,
326 c.rhodecode_user.repository_groups_admin,
327 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
327 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
328 </li>
328 </li>
329 % endif
329 % endif
330 ${usermenu()}
330 ${usermenu()}
331 <script type="text/javascript">
331 <script type="text/javascript">
332 YUE.on('repo_switcher','mouseover',function(){
332 YUE.on('repo_switcher','mouseover',function(){
333 var target = 'q_filter_rs';
333 var target = 'q_filter_rs';
334 var qfilter_activate = function(){
334 var qfilter_activate = function(){
335 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
335 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
336 var func = function(node){
336 var func = function(node){
337 return node.parentNode;
337 return node.parentNode;
338 }
338 }
339 q_filter(target,nodes,func);
339 q_filter(target,nodes,func);
340 }
340 }
341
341
342 var loaded = YUD.hasClass('repo_switcher','loaded');
342 var loaded = YUD.hasClass('repo_switcher','loaded');
343 if(!loaded){
343 if(!loaded){
344 YUD.addClass('repo_switcher','loaded');
344 YUD.addClass('repo_switcher','loaded');
345 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
345 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
346 function(o){qfilter_activate();YUD.get(target).focus()},
346 function(o){qfilter_activate();YUD.get(target).focus()},
347 function(o){YUD.removeClass('repo_switcher','loaded');}
347 function(o){YUD.removeClass('repo_switcher','loaded');}
348 ,null);
348 ,null);
349 }else{
349 }else{
350 YUD.get(target).focus();
350 YUD.get(target).focus();
351 }
351 }
352 return false;
352 return false;
353 });
353 });
354
354
355 YUE.on('header-dd', 'click',function(e){
355 YUE.on('header-dd', 'click',function(e){
356 YUD.addClass('header-inner', 'hover');
356 YUD.addClass('header-inner', 'hover');
357 YUD.addClass('content', 'hover');
357 YUD.addClass('content', 'hover');
358 });
358 });
359
359
360 </script>
360 </script>
361 </%def>
361 </%def>
@@ -1,24 +1,24 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 % for pr in c.pullrequests_pager:
3 % for pr in c.pullrequests_pager:
4 <div class="pr ${'pr-closed' if pr.is_closed() else ''}">
4 <div class="pr ${'pr-closed' if pr.is_closed() else ''}">
5 <div class="pr-title">
5 <div class="pr-title">
6 <img src="${h.url('/images/icons/flag_status_%s.png' % str(pr.last_review_status))}" />
6 <img src="${h.url('/images/icons/flag_status_%s.png' % str(pr.last_review_status))}" />
7 <a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">
7 <a href="${h.url('pullrequest_show',repo_name=pr.other_repo.repo_name,pull_request_id=pr.pull_request_id)}">
8 ${_('Pull request #%s opened by %s on %s') % (pr.pull_request_id, pr.author.full_name, h.fmt_date(pr.created_on))}
8 ${_('Pull request #%s opened by %s on %s') % (pr.pull_request_id, pr.author.full_name, h.fmt_date(pr.created_on))}
9 </a>
9 </a>
10 %if pr.is_closed():
10 %if pr.is_closed():
11 <span class="pr-closed-tag">${_('Closed')}</span>
11 <span class="pr-closed-tag">${_('Closed')}</span>
12 %endif
12 %endif
13 </div>
13 </div>
14 <h5 style="border:0px;padding-bottom:0px">${_('Title')}: ${pr.title}</h5>
14 <h5 style="border:0px;padding-bottom:0px">${_('Title')}: ${pr.title}</h5>
15 <div class="pr-desc">${pr.description}</div>
15 <div class="pr-desc">${pr.description}</div>
16 </div>
16 </div>
17 % endfor
17 % endfor
18
18
19
19
20 <div class="notification-paginator">
20 <div class="notification-paginator">
21 <div class="pagination-wh pagination-left">
21 <div class="pagination-wh pagination-left">
22 ${c.pullrequests_pager.pager('$link_previous ~2~ $link_next')}
22 ${c.pullrequests_pager.pager('$link_previous ~2~ $link_next')}
23 </div>
23 </div>
24 </div>
24 </div>
@@ -1,26 +1,50 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${_('%s Pull Requests') % c.repo_name} &middot; ${c.rhodecode_name}
4 ${_('%s Pull Requests') % c.repo_name} &middot; ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${_('Pull requests')}
8 %if c.from_:
9 ${_('Pull requests from %s') % c.repo_name}
10 %else:
11 ${_('Pull requests to %s') % c.repo_name}
12 %endif
9 </%def>
13 </%def>
10
14
11 <%def name="page_nav()">
15 <%def name="page_nav()">
12 ${self.menu('repositories')}
16 ${self.menu('repositories')}
13 </%def>
17 </%def>
14
18
15 <%def name="main()">
19 <%def name="main()">
16 ${self.repo_context_bar('showpullrequest')}
20 ${self.repo_context_bar('showpullrequest')}
17
21
18 <div class="box">
22 <div class="box">
19 <!-- box / title -->
23 <!-- box / title -->
20 <div class="title">
24 <div class="title">
21 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
22 </div>
26 </div>
27
28 <div style="margin: 0 20px">
29 <div>
30 %if c.from_:
31 ${h.link_to(_('Instead, show pull requests to %s') % c.repo_name, h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed))}
32 %else:
33 ${h.link_to(_('Instead, show pull requests from %s') % c.repo_name, h.url('pullrequest_show_all',repo_name=c.repo_name,closed=c.closed,from_=1))}
34 %endif
35 </div>
36
37 <div>
38 %if c.closed:
39 ${h.link_to(_('Hide closed pull requests'), h.url('pullrequest_show_all',repo_name=c.repo_name,from_=c.from_))}
40 %else:
41 ${h.link_to(_('Show closed pull requests too'), h.url('pullrequest_show_all',repo_name=c.repo_name,from_=c.from_,closed=1))}
42 %endif
43 </div>
44 </div>
45
23 ${c.pullrequest_data}
46 ${c.pullrequest_data}
47
24 </div>
48 </div>
25
49
26 </%def>
50 </%def>
General Comments 0
You need to be logged in to leave comments. Login now