##// END OF EJS Templates
pull requests: use branch name when creating PRs from a changelog with branch filter
Mads Kiilerich -
r3813:dca89d57 beta
parent child Browse files
Show More
@@ -1,546 +1,560 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import formencode
27 import formencode
28
28
29 from webob.exc import HTTPNotFound, HTTPForbidden
29 from webob.exc import HTTPNotFound, HTTPForbidden
30 from collections import defaultdict
30 from collections import defaultdict
31 from itertools import groupby
31 from itertools import groupby
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 NotAnonymous
40 NotAnonymous
41 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.utils import action_logger, jsonify
45 from rhodecode.lib.vcs.utils import safe_str
45 from rhodecode.lib.vcs.utils import safe_str
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.lib.diffs import LimitedDiffContainer
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
50 ChangesetComment
50 ChangesetComment
51 from rhodecode.model.pull_request import PullRequestModel
51 from rhodecode.model.pull_request import PullRequestModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.comment import ChangesetCommentsModel
54 from rhodecode.model.comment import ChangesetCommentsModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
56 from rhodecode.model.forms import PullRequestForm
56 from rhodecode.model.forms import PullRequestForm
57 from mercurial import scmutil
57 from mercurial import scmutil
58 from rhodecode.lib.utils2 import safe_int
58 from rhodecode.lib.utils2 import safe_int
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class PullrequestsController(BaseRepoController):
63 class PullrequestsController(BaseRepoController):
64
64
65 def __before__(self):
65 def __before__(self):
66 super(PullrequestsController, self).__before__()
66 super(PullrequestsController, self).__before__()
67 repo_model = RepoModel()
67 repo_model = RepoModel()
68 c.users_array = repo_model.get_users_js()
68 c.users_array = repo_model.get_users_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
70
70
71 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
71 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
72 """return a structure with repo's interesting changesets, suitable for
72 """return a structure with repo's interesting changesets, suitable for
73 the selectors in pullrequest.html
73 the selectors in pullrequest.html
74
74
75 rev: a revision that must be in the list and selected by default
75 rev: a revision that must be in the list somehow and selected by default
76 branch: a branch that must be in the list and selected by default - even if closed
76 branch_rev: a revision of which peers should be preferred and available."""
77 branch_rev: a revision of which peers should be preferred and available."""
77 # list named branches that has been merged to this named branch - it should probably merge back
78 # list named branches that has been merged to this named branch - it should probably merge back
78 peers = []
79 peers = []
79
80
80 if rev:
81 if rev:
81 rev = safe_str(rev)
82 rev = safe_str(rev)
82
83
84 if branch:
85 branch = safe_str(branch)
86
83 if branch_rev:
87 if branch_rev:
84 branch_rev = safe_str(branch_rev)
88 branch_rev = safe_str(branch_rev)
85 # not restricting to merge() would also get branch point and be better
89 # not restricting to merge() would also get branch point and be better
86 # (especially because it would get the branch point) ... but is currently too expensive
90 # (especially because it would get the branch point) ... but is currently too expensive
87 otherbranches = {}
91 otherbranches = {}
88 for i in repo._repo.revs(
92 for i in repo._repo.revs(
89 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
93 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
90 branch_rev, branch_rev):
94 branch_rev, branch_rev):
91 cs = repo.get_changeset(i)
95 cs = repo.get_changeset(i)
92 otherbranches[cs.branch] = cs.raw_id
96 otherbranches[cs.branch] = cs.raw_id
93 for abranch, node in otherbranches.iteritems():
97 for abranch, node in otherbranches.iteritems():
94 selected = 'branch:%s:%s' % (abranch, node)
98 selected = 'branch:%s:%s' % (abranch, node)
95 peers.append((selected, abranch))
99 peers.append((selected, abranch))
96
100
97 selected = None
101 selected = None
98
102
99 branches = []
103 branches = []
100 for abranch, branchrev in repo.branches.iteritems():
104 for abranch, branchrev in repo.branches.iteritems():
101 n = 'branch:%s:%s' % (abranch, branchrev)
105 n = 'branch:%s:%s' % (abranch, branchrev)
102 branches.append((n, abranch))
106 branches.append((n, abranch))
103 if rev == branchrev:
107 if rev == branchrev:
104 selected = n
108 selected = n
109 if branch == abranch:
110 selected = n
111 branch = None
112 if branch: # branch not in list - it is probably closed
113 revs = repo._repo.revs('max(branch(%s))', branch)
114 if revs:
115 cs = repo.get_changeset(revs[0])
116 selected = 'branch:%s:%s' % (branch, cs.raw_id)
117 branches.append((selected, branch))
105
118
106 bookmarks = []
119 bookmarks = []
107 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
120 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
108 n = 'book:%s:%s' % (bookmark, bookmarkrev)
121 n = 'book:%s:%s' % (bookmark, bookmarkrev)
109 bookmarks.append((n, bookmark))
122 bookmarks.append((n, bookmark))
110 if rev == bookmarkrev:
123 if rev == bookmarkrev:
111 selected = n
124 selected = n
112
125
113 tags = []
126 tags = []
114 for tag, tagrev in repo.tags.iteritems():
127 for tag, tagrev in repo.tags.iteritems():
115 n = 'tag:%s:%s' % (tag, tagrev)
128 n = 'tag:%s:%s' % (tag, tagrev)
116 tags.append((n, tag))
129 tags.append((n, tag))
117 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
130 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
118 selected = n
131 selected = n
119
132
120 # prio 1: rev was selected as existing entry above
133 # prio 1: rev was selected as existing entry above
121
134
122 # prio 2: create special entry for rev; rev _must_ be used
135 # prio 2: create special entry for rev; rev _must_ be used
123 specials = []
136 specials = []
124 if rev and selected is None:
137 if rev and selected is None:
125 selected = 'rev:%s:%s' % (rev, rev)
138 selected = 'rev:%s:%s' % (rev, rev)
126 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
139 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
127
140
128 # prio 3: most recent peer branch
141 # prio 3: most recent peer branch
129 if peers and not selected:
142 if peers and not selected:
130 selected = peers[0][0][0]
143 selected = peers[0][0][0]
131
144
132 # prio 4: tip revision
145 # prio 4: tip revision
133 if not selected:
146 if not selected:
134 selected = 'tag:tip:%s' % repo.tags['tip']
147 selected = 'tag:tip:%s' % repo.tags['tip']
135
148
136 groups = [(specials, _("Special")),
149 groups = [(specials, _("Special")),
137 (peers, _("Peer branches")),
150 (peers, _("Peer branches")),
138 (bookmarks, _("Bookmarks")),
151 (bookmarks, _("Bookmarks")),
139 (branches, _("Branches")),
152 (branches, _("Branches")),
140 (tags, _("Tags")),
153 (tags, _("Tags")),
141 ]
154 ]
142 return [g for g in groups if g[0]], selected
155 return [g for g in groups if g[0]], selected
143
156
144 def _get_is_allowed_change_status(self, pull_request):
157 def _get_is_allowed_change_status(self, pull_request):
145 owner = self.rhodecode_user.user_id == pull_request.user_id
158 owner = self.rhodecode_user.user_id == pull_request.user_id
146 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
159 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
147 pull_request.reviewers]
160 pull_request.reviewers]
148 return (self.rhodecode_user.admin or owner or reviewer)
161 return (self.rhodecode_user.admin or owner or reviewer)
149
162
150 def _load_compare_data(self, pull_request, enable_comments=True):
163 def _load_compare_data(self, pull_request, enable_comments=True):
151 """
164 """
152 Load context data needed for generating compare diff
165 Load context data needed for generating compare diff
153
166
154 :param pull_request:
167 :param pull_request:
155 :type pull_request:
168 :type pull_request:
156 """
169 """
157 org_repo = pull_request.org_repo
170 org_repo = pull_request.org_repo
158 (org_ref_type,
171 (org_ref_type,
159 org_ref_name,
172 org_ref_name,
160 org_ref_rev) = pull_request.org_ref.split(':')
173 org_ref_rev) = pull_request.org_ref.split(':')
161
174
162 other_repo = org_repo
175 other_repo = org_repo
163 (other_ref_type,
176 (other_ref_type,
164 other_ref_name,
177 other_ref_name,
165 other_ref_rev) = pull_request.other_ref.split(':')
178 other_ref_rev) = pull_request.other_ref.split(':')
166
179
167 # despite opening revisions for bookmarks/branches/tags, we always
180 # despite opening revisions for bookmarks/branches/tags, we always
168 # convert this to rev to prevent changes after bookmark or branch change
181 # convert this to rev to prevent changes after bookmark or branch change
169 org_ref = ('rev', org_ref_rev)
182 org_ref = ('rev', org_ref_rev)
170 other_ref = ('rev', other_ref_rev)
183 other_ref = ('rev', other_ref_rev)
171
184
172 c.org_repo = org_repo
185 c.org_repo = org_repo
173 c.other_repo = other_repo
186 c.other_repo = other_repo
174
187
175 c.fulldiff = fulldiff = request.GET.get('fulldiff')
188 c.fulldiff = fulldiff = request.GET.get('fulldiff')
176
189
177 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
190 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
178
191
179 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
192 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
180
193
181 c.org_ref = org_ref[1]
194 c.org_ref = org_ref[1]
182 c.org_ref_type = org_ref[0]
195 c.org_ref_type = org_ref[0]
183 c.other_ref = other_ref[1]
196 c.other_ref = other_ref[1]
184 c.other_ref_type = other_ref[0]
197 c.other_ref_type = other_ref[0]
185
198
186 diff_limit = self.cut_off_limit if not fulldiff else None
199 diff_limit = self.cut_off_limit if not fulldiff else None
187
200
188 # we swap org/other ref since we run a simple diff on one repo
201 # we swap org/other ref since we run a simple diff on one repo
189 log.debug('running diff between %s and %s in %s'
202 log.debug('running diff between %s and %s in %s'
190 % (other_ref, org_ref, org_repo.scm_instance.path))
203 % (other_ref, org_ref, org_repo.scm_instance.path))
191 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
204 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
192
205
193 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
206 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
194 diff_limit=diff_limit)
207 diff_limit=diff_limit)
195 _parsed = diff_processor.prepare()
208 _parsed = diff_processor.prepare()
196
209
197 c.limited_diff = False
210 c.limited_diff = False
198 if isinstance(_parsed, LimitedDiffContainer):
211 if isinstance(_parsed, LimitedDiffContainer):
199 c.limited_diff = True
212 c.limited_diff = True
200
213
201 c.files = []
214 c.files = []
202 c.changes = {}
215 c.changes = {}
203 c.lines_added = 0
216 c.lines_added = 0
204 c.lines_deleted = 0
217 c.lines_deleted = 0
205 for f in _parsed:
218 for f in _parsed:
206 st = f['stats']
219 st = f['stats']
207 if st[0] != 'b':
220 if st[0] != 'b':
208 c.lines_added += st[0]
221 c.lines_added += st[0]
209 c.lines_deleted += st[1]
222 c.lines_deleted += st[1]
210 fid = h.FID('', f['filename'])
223 fid = h.FID('', f['filename'])
211 c.files.append([fid, f['operation'], f['filename'], f['stats']])
224 c.files.append([fid, f['operation'], f['filename'], f['stats']])
212 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
225 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
213 parsed_lines=[f])
226 parsed_lines=[f])
214 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
227 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
215
228
216 @LoginRequired()
229 @LoginRequired()
217 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
230 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
218 'repository.admin')
231 'repository.admin')
219 def show_all(self, repo_name):
232 def show_all(self, repo_name):
220 c.pull_requests = PullRequestModel().get_all(repo_name)
233 c.pull_requests = PullRequestModel().get_all(repo_name)
221 c.repo_name = repo_name
234 c.repo_name = repo_name
222 p = safe_int(request.GET.get('page', 1), 1)
235 p = safe_int(request.GET.get('page', 1), 1)
223
236
224 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
237 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
225
238
226 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
239 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
227
240
228 if request.environ.get('HTTP_X_PARTIAL_XHR'):
241 if request.environ.get('HTTP_X_PARTIAL_XHR'):
229 return c.pullrequest_data
242 return c.pullrequest_data
230
243
231 return render('/pullrequests/pullrequest_show_all.html')
244 return render('/pullrequests/pullrequest_show_all.html')
232
245
233 @LoginRequired()
246 @LoginRequired()
234 @NotAnonymous()
247 @NotAnonymous()
235 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
248 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
236 'repository.admin')
249 'repository.admin')
237 def index(self):
250 def index(self):
238 org_repo = c.rhodecode_db_repo
251 org_repo = c.rhodecode_db_repo
239
252
240 if org_repo.scm_instance.alias != 'hg':
253 if org_repo.scm_instance.alias != 'hg':
241 log.error('Review not available for GIT REPOS')
254 log.error('Review not available for GIT REPOS')
242 raise HTTPNotFound
255 raise HTTPNotFound
243
256
244 try:
257 try:
245 org_repo.scm_instance.get_changeset()
258 org_repo.scm_instance.get_changeset()
246 except EmptyRepositoryError, e:
259 except EmptyRepositoryError, e:
247 h.flash(h.literal(_('There are no changesets yet')),
260 h.flash(h.literal(_('There are no changesets yet')),
248 category='warning')
261 category='warning')
249 redirect(url('summary_home', repo_name=org_repo.repo_name))
262 redirect(url('summary_home', repo_name=org_repo.repo_name))
250
263
251 org_rev = request.GET.get('rev_end')
264 org_rev = request.GET.get('rev_end')
252 # rev_start is not directly useful - its parent could however be used
265 # rev_start is not directly useful - its parent could however be used
253 # as default for other and thus give a simple compare view
266 # as default for other and thus give a simple compare view
254 #other_rev = request.POST.get('rev_start')
267 #other_rev = request.POST.get('rev_start')
268 branch = request.GET.get('branch')
255
269
256 c.org_repos = []
270 c.org_repos = []
257 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
271 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
258 c.default_org_repo = org_repo.repo_name
272 c.default_org_repo = org_repo.repo_name
259 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
273 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
260
274
261 c.other_repos = []
275 c.other_repos = []
262 other_repos_info = {}
276 other_repos_info = {}
263
277
264 def add_other_repo(repo, branch_rev=None):
278 def add_other_repo(repo, branch_rev=None):
265 if repo.repo_name in other_repos_info: # shouldn't happen
279 if repo.repo_name in other_repos_info: # shouldn't happen
266 return
280 return
267 c.other_repos.append((repo.repo_name, repo.repo_name))
281 c.other_repos.append((repo.repo_name, repo.repo_name))
268 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
282 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
269 other_repos_info[repo.repo_name] = {
283 other_repos_info[repo.repo_name] = {
270 'user': dict(user_id=repo.user.user_id,
284 'user': dict(user_id=repo.user.user_id,
271 username=repo.user.username,
285 username=repo.user.username,
272 firstname=repo.user.firstname,
286 firstname=repo.user.firstname,
273 lastname=repo.user.lastname,
287 lastname=repo.user.lastname,
274 gravatar_link=h.gravatar_url(repo.user.email, 14)),
288 gravatar_link=h.gravatar_url(repo.user.email, 14)),
275 'description': repo.description.split('\n', 1)[0],
289 'description': repo.description.split('\n', 1)[0],
276 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
290 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
277 }
291 }
278
292
279 # add org repo to other so we can open pull request against peer branches on itself
293 # add org repo to other so we can open pull request against peer branches on itself
280 add_other_repo(org_repo, branch_rev=org_rev)
294 add_other_repo(org_repo, branch_rev=org_rev)
281 c.default_other_repo = org_repo.repo_name
295 c.default_other_repo = org_repo.repo_name
282
296
283 # gather forks and add to this list ... even though it is rare to
297 # gather forks and add to this list ... even though it is rare to
284 # request forks to pull from their parent
298 # request forks to pull from their parent
285 for fork in org_repo.forks:
299 for fork in org_repo.forks:
286 add_other_repo(fork)
300 add_other_repo(fork)
287
301
288 # add parents of this fork also, but only if it's not empty
302 # add parents of this fork also, but only if it's not empty
289 if org_repo.parent and org_repo.parent.scm_instance.revisions:
303 if org_repo.parent and org_repo.parent.scm_instance.revisions:
290 add_other_repo(org_repo.parent)
304 add_other_repo(org_repo.parent)
291 c.default_other_repo = org_repo.parent.repo_name
305 c.default_other_repo = org_repo.parent.repo_name
292
306
293 c.default_other_repo_info = other_repos_info[c.default_other_repo]
307 c.default_other_repo_info = other_repos_info[c.default_other_repo]
294 c.other_repos_info = json.dumps(other_repos_info)
308 c.other_repos_info = json.dumps(other_repos_info)
295
309
296 return render('/pullrequests/pullrequest.html')
310 return render('/pullrequests/pullrequest.html')
297
311
298 @LoginRequired()
312 @LoginRequired()
299 @NotAnonymous()
313 @NotAnonymous()
300 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
314 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
301 'repository.admin')
315 'repository.admin')
302 def create(self, repo_name):
316 def create(self, repo_name):
303 repo = RepoModel()._get_repo(repo_name)
317 repo = RepoModel()._get_repo(repo_name)
304 try:
318 try:
305 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
319 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
306 except formencode.Invalid, errors:
320 except formencode.Invalid, errors:
307 log.error(traceback.format_exc())
321 log.error(traceback.format_exc())
308 if errors.error_dict.get('revisions'):
322 if errors.error_dict.get('revisions'):
309 msg = 'Revisions: %s' % errors.error_dict['revisions']
323 msg = 'Revisions: %s' % errors.error_dict['revisions']
310 elif errors.error_dict.get('pullrequest_title'):
324 elif errors.error_dict.get('pullrequest_title'):
311 msg = _('Pull request requires a title with min. 3 chars')
325 msg = _('Pull request requires a title with min. 3 chars')
312 else:
326 else:
313 msg = _('Error creating pull request')
327 msg = _('Error creating pull request')
314
328
315 h.flash(msg, 'error')
329 h.flash(msg, 'error')
316 return redirect(url('pullrequest_home', repo_name=repo_name))
330 return redirect(url('pullrequest_home', repo_name=repo_name))
317
331
318 org_repo = _form['org_repo']
332 org_repo = _form['org_repo']
319 org_ref = 'rev:merge:%s' % _form['merge_rev']
333 org_ref = 'rev:merge:%s' % _form['merge_rev']
320 other_repo = _form['other_repo']
334 other_repo = _form['other_repo']
321 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
335 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
322 revisions = [x for x in reversed(_form['revisions'])]
336 revisions = [x for x in reversed(_form['revisions'])]
323 reviewers = _form['review_members']
337 reviewers = _form['review_members']
324
338
325 title = _form['pullrequest_title']
339 title = _form['pullrequest_title']
326 description = _form['pullrequest_desc']
340 description = _form['pullrequest_desc']
327 try:
341 try:
328 pull_request = PullRequestModel().create(
342 pull_request = PullRequestModel().create(
329 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
343 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
330 other_ref, revisions, reviewers, title, description
344 other_ref, revisions, reviewers, title, description
331 )
345 )
332 Session().commit()
346 Session().commit()
333 h.flash(_('Successfully opened new pull request'),
347 h.flash(_('Successfully opened new pull request'),
334 category='success')
348 category='success')
335 except Exception:
349 except Exception:
336 h.flash(_('Error occurred during sending pull request'),
350 h.flash(_('Error occurred during sending pull request'),
337 category='error')
351 category='error')
338 log.error(traceback.format_exc())
352 log.error(traceback.format_exc())
339 return redirect(url('pullrequest_home', repo_name=repo_name))
353 return redirect(url('pullrequest_home', repo_name=repo_name))
340
354
341 return redirect(url('pullrequest_show', repo_name=other_repo,
355 return redirect(url('pullrequest_show', repo_name=other_repo,
342 pull_request_id=pull_request.pull_request_id))
356 pull_request_id=pull_request.pull_request_id))
343
357
344 @LoginRequired()
358 @LoginRequired()
345 @NotAnonymous()
359 @NotAnonymous()
346 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
360 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
347 'repository.admin')
361 'repository.admin')
348 @jsonify
362 @jsonify
349 def update(self, repo_name, pull_request_id):
363 def update(self, repo_name, pull_request_id):
350 pull_request = PullRequest.get_or_404(pull_request_id)
364 pull_request = PullRequest.get_or_404(pull_request_id)
351 if pull_request.is_closed():
365 if pull_request.is_closed():
352 raise HTTPForbidden()
366 raise HTTPForbidden()
353 #only owner or admin can update it
367 #only owner or admin can update it
354 owner = pull_request.author.user_id == c.rhodecode_user.user_id
368 owner = pull_request.author.user_id == c.rhodecode_user.user_id
355 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
369 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
356 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
370 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
357 request.POST.get('reviewers_ids', '').split(',')))
371 request.POST.get('reviewers_ids', '').split(',')))
358
372
359 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
373 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
360 Session().commit()
374 Session().commit()
361 return True
375 return True
362 raise HTTPForbidden()
376 raise HTTPForbidden()
363
377
364 @LoginRequired()
378 @LoginRequired()
365 @NotAnonymous()
379 @NotAnonymous()
366 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
380 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
367 'repository.admin')
381 'repository.admin')
368 @jsonify
382 @jsonify
369 def delete(self, repo_name, pull_request_id):
383 def delete(self, repo_name, pull_request_id):
370 pull_request = PullRequest.get_or_404(pull_request_id)
384 pull_request = PullRequest.get_or_404(pull_request_id)
371 #only owner can delete it !
385 #only owner can delete it !
372 if pull_request.author.user_id == c.rhodecode_user.user_id:
386 if pull_request.author.user_id == c.rhodecode_user.user_id:
373 PullRequestModel().delete(pull_request)
387 PullRequestModel().delete(pull_request)
374 Session().commit()
388 Session().commit()
375 h.flash(_('Successfully deleted pull request'),
389 h.flash(_('Successfully deleted pull request'),
376 category='success')
390 category='success')
377 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
391 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
378 raise HTTPForbidden()
392 raise HTTPForbidden()
379
393
380 @LoginRequired()
394 @LoginRequired()
381 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
395 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
382 'repository.admin')
396 'repository.admin')
383 def show(self, repo_name, pull_request_id):
397 def show(self, repo_name, pull_request_id):
384 repo_model = RepoModel()
398 repo_model = RepoModel()
385 c.users_array = repo_model.get_users_js()
399 c.users_array = repo_model.get_users_js()
386 c.users_groups_array = repo_model.get_users_groups_js()
400 c.users_groups_array = repo_model.get_users_groups_js()
387 c.pull_request = PullRequest.get_or_404(pull_request_id)
401 c.pull_request = PullRequest.get_or_404(pull_request_id)
388 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
402 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
389 cc_model = ChangesetCommentsModel()
403 cc_model = ChangesetCommentsModel()
390 cs_model = ChangesetStatusModel()
404 cs_model = ChangesetStatusModel()
391 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
405 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
392 pull_request=c.pull_request,
406 pull_request=c.pull_request,
393 with_revisions=True)
407 with_revisions=True)
394
408
395 cs_statuses = defaultdict(list)
409 cs_statuses = defaultdict(list)
396 for st in _cs_statuses:
410 for st in _cs_statuses:
397 cs_statuses[st.author.username] += [st]
411 cs_statuses[st.author.username] += [st]
398
412
399 c.pull_request_reviewers = []
413 c.pull_request_reviewers = []
400 c.pull_request_pending_reviewers = []
414 c.pull_request_pending_reviewers = []
401 for o in c.pull_request.reviewers:
415 for o in c.pull_request.reviewers:
402 st = cs_statuses.get(o.user.username, None)
416 st = cs_statuses.get(o.user.username, None)
403 if st:
417 if st:
404 sorter = lambda k: k.version
418 sorter = lambda k: k.version
405 st = [(x, list(y)[0])
419 st = [(x, list(y)[0])
406 for x, y in (groupby(sorted(st, key=sorter), sorter))]
420 for x, y in (groupby(sorted(st, key=sorter), sorter))]
407 else:
421 else:
408 c.pull_request_pending_reviewers.append(o.user)
422 c.pull_request_pending_reviewers.append(o.user)
409 c.pull_request_reviewers.append([o.user, st])
423 c.pull_request_reviewers.append([o.user, st])
410
424
411 # pull_requests repo_name we opened it against
425 # pull_requests repo_name we opened it against
412 # ie. other_repo must match
426 # ie. other_repo must match
413 if repo_name != c.pull_request.other_repo.repo_name:
427 if repo_name != c.pull_request.other_repo.repo_name:
414 raise HTTPNotFound
428 raise HTTPNotFound
415
429
416 # load compare data into template context
430 # load compare data into template context
417 enable_comments = not c.pull_request.is_closed()
431 enable_comments = not c.pull_request.is_closed()
418 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
432 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
419
433
420 # inline comments
434 # inline comments
421 c.inline_cnt = 0
435 c.inline_cnt = 0
422 c.inline_comments = cc_model.get_inline_comments(
436 c.inline_comments = cc_model.get_inline_comments(
423 c.rhodecode_db_repo.repo_id,
437 c.rhodecode_db_repo.repo_id,
424 pull_request=pull_request_id)
438 pull_request=pull_request_id)
425 # count inline comments
439 # count inline comments
426 for __, lines in c.inline_comments:
440 for __, lines in c.inline_comments:
427 for comments in lines.values():
441 for comments in lines.values():
428 c.inline_cnt += len(comments)
442 c.inline_cnt += len(comments)
429 # comments
443 # comments
430 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
444 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
431 pull_request=pull_request_id)
445 pull_request=pull_request_id)
432
446
433 try:
447 try:
434 cur_status = c.statuses[c.pull_request.revisions[0]][0]
448 cur_status = c.statuses[c.pull_request.revisions[0]][0]
435 except Exception:
449 except Exception:
436 log.error(traceback.format_exc())
450 log.error(traceback.format_exc())
437 cur_status = 'undefined'
451 cur_status = 'undefined'
438 if c.pull_request.is_closed() and 0:
452 if c.pull_request.is_closed() and 0:
439 c.current_changeset_status = cur_status
453 c.current_changeset_status = cur_status
440 else:
454 else:
441 # changeset(pull-request) status calulation based on reviewers
455 # changeset(pull-request) status calulation based on reviewers
442 c.current_changeset_status = cs_model.calculate_status(
456 c.current_changeset_status = cs_model.calculate_status(
443 c.pull_request_reviewers,
457 c.pull_request_reviewers,
444 )
458 )
445 c.changeset_statuses = ChangesetStatus.STATUSES
459 c.changeset_statuses = ChangesetStatus.STATUSES
446
460
447 c.as_form = False
461 c.as_form = False
448 c.ancestor = None # there is one - but right here we don't know which
462 c.ancestor = None # there is one - but right here we don't know which
449 return render('/pullrequests/pullrequest_show.html')
463 return render('/pullrequests/pullrequest_show.html')
450
464
451 @LoginRequired()
465 @LoginRequired()
452 @NotAnonymous()
466 @NotAnonymous()
453 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
467 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
454 'repository.admin')
468 'repository.admin')
455 @jsonify
469 @jsonify
456 def comment(self, repo_name, pull_request_id):
470 def comment(self, repo_name, pull_request_id):
457 pull_request = PullRequest.get_or_404(pull_request_id)
471 pull_request = PullRequest.get_or_404(pull_request_id)
458 if pull_request.is_closed():
472 if pull_request.is_closed():
459 raise HTTPForbidden()
473 raise HTTPForbidden()
460
474
461 status = request.POST.get('changeset_status')
475 status = request.POST.get('changeset_status')
462 change_status = request.POST.get('change_changeset_status')
476 change_status = request.POST.get('change_changeset_status')
463 text = request.POST.get('text')
477 text = request.POST.get('text')
464 close_pr = request.POST.get('save_close')
478 close_pr = request.POST.get('save_close')
465
479
466 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
480 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
467 if status and change_status and allowed_to_change_status:
481 if status and change_status and allowed_to_change_status:
468 _def = (_('Status change -> %s')
482 _def = (_('Status change -> %s')
469 % ChangesetStatus.get_status_lbl(status))
483 % ChangesetStatus.get_status_lbl(status))
470 if close_pr:
484 if close_pr:
471 _def = _('Closing with') + ' ' + _def
485 _def = _('Closing with') + ' ' + _def
472 text = text or _def
486 text = text or _def
473 comm = ChangesetCommentsModel().create(
487 comm = ChangesetCommentsModel().create(
474 text=text,
488 text=text,
475 repo=c.rhodecode_db_repo.repo_id,
489 repo=c.rhodecode_db_repo.repo_id,
476 user=c.rhodecode_user.user_id,
490 user=c.rhodecode_user.user_id,
477 pull_request=pull_request_id,
491 pull_request=pull_request_id,
478 f_path=request.POST.get('f_path'),
492 f_path=request.POST.get('f_path'),
479 line_no=request.POST.get('line'),
493 line_no=request.POST.get('line'),
480 status_change=(ChangesetStatus.get_status_lbl(status)
494 status_change=(ChangesetStatus.get_status_lbl(status)
481 if status and change_status
495 if status and change_status
482 and allowed_to_change_status else None),
496 and allowed_to_change_status else None),
483 closing_pr=close_pr
497 closing_pr=close_pr
484 )
498 )
485
499
486 action_logger(self.rhodecode_user,
500 action_logger(self.rhodecode_user,
487 'user_commented_pull_request:%s' % pull_request_id,
501 'user_commented_pull_request:%s' % pull_request_id,
488 c.rhodecode_db_repo, self.ip_addr, self.sa)
502 c.rhodecode_db_repo, self.ip_addr, self.sa)
489
503
490 if allowed_to_change_status:
504 if allowed_to_change_status:
491 # get status if set !
505 # get status if set !
492 if status and change_status:
506 if status and change_status:
493 ChangesetStatusModel().set_status(
507 ChangesetStatusModel().set_status(
494 c.rhodecode_db_repo.repo_id,
508 c.rhodecode_db_repo.repo_id,
495 status,
509 status,
496 c.rhodecode_user.user_id,
510 c.rhodecode_user.user_id,
497 comm,
511 comm,
498 pull_request=pull_request_id
512 pull_request=pull_request_id
499 )
513 )
500
514
501 if close_pr:
515 if close_pr:
502 if status in ['rejected', 'approved']:
516 if status in ['rejected', 'approved']:
503 PullRequestModel().close_pull_request(pull_request_id)
517 PullRequestModel().close_pull_request(pull_request_id)
504 action_logger(self.rhodecode_user,
518 action_logger(self.rhodecode_user,
505 'user_closed_pull_request:%s' % pull_request_id,
519 'user_closed_pull_request:%s' % pull_request_id,
506 c.rhodecode_db_repo, self.ip_addr, self.sa)
520 c.rhodecode_db_repo, self.ip_addr, self.sa)
507 else:
521 else:
508 h.flash(_('Closing pull request on other statuses than '
522 h.flash(_('Closing pull request on other statuses than '
509 'rejected or approved forbidden'),
523 'rejected or approved forbidden'),
510 category='warning')
524 category='warning')
511
525
512 Session().commit()
526 Session().commit()
513
527
514 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
528 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
515 return redirect(h.url('pullrequest_show', repo_name=repo_name,
529 return redirect(h.url('pullrequest_show', repo_name=repo_name,
516 pull_request_id=pull_request_id))
530 pull_request_id=pull_request_id))
517
531
518 data = {
532 data = {
519 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
533 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
520 }
534 }
521 if comm:
535 if comm:
522 c.co = comm
536 c.co = comm
523 data.update(comm.get_dict())
537 data.update(comm.get_dict())
524 data.update({'rendered_text':
538 data.update({'rendered_text':
525 render('changeset/changeset_comment_block.html')})
539 render('changeset/changeset_comment_block.html')})
526
540
527 return data
541 return data
528
542
529 @LoginRequired()
543 @LoginRequired()
530 @NotAnonymous()
544 @NotAnonymous()
531 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
545 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
532 'repository.admin')
546 'repository.admin')
533 @jsonify
547 @jsonify
534 def delete_comment(self, repo_name, comment_id):
548 def delete_comment(self, repo_name, comment_id):
535 co = ChangesetComment.get(comment_id)
549 co = ChangesetComment.get(comment_id)
536 if co.pull_request.is_closed():
550 if co.pull_request.is_closed():
537 #don't allow deleting comments on closed pull request
551 #don't allow deleting comments on closed pull request
538 raise HTTPForbidden()
552 raise HTTPForbidden()
539
553
540 owner = co.author.user_id == c.rhodecode_user.user_id
554 owner = co.author.user_id == c.rhodecode_user.user_id
541 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
555 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
542 ChangesetCommentsModel().delete(comment=co)
556 ChangesetCommentsModel().delete(comment=co)
543 Session().commit()
557 Session().commit()
544 return True
558 return True
545 else:
559 else:
546 raise HTTPForbidden()
560 raise HTTPForbidden()
@@ -1,288 +1,290 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name} &middot;
6 ${_('%s Changelog') % c.repo_name} &middot;
7 %if c.changelog_for_path:
7 %if c.changelog_for_path:
8 /${c.changelog_for_path} &middot;
8 /${c.changelog_for_path} &middot;
9 %endif
9 %endif
10 ${c.rhodecode_name}
10 ${c.rhodecode_name}
11 </%def>
11 </%def>
12
12
13 <%def name="breadcrumbs_links()">
13 <%def name="breadcrumbs_links()">
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
15 ${_('Changelog')}
15 ${_('Changelog')}
16 %if c.changelog_for_path:
16 %if c.changelog_for_path:
17 - /${c.changelog_for_path}
17 - /${c.changelog_for_path}
18 %endif
18 %endif
19 - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
19 - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
20 </%def>
20 </%def>
21
21
22 <%def name="page_nav()">
22 <%def name="page_nav()">
23 ${self.menu('repositories')}
23 ${self.menu('repositories')}
24 </%def>
24 </%def>
25
25
26 <%def name="main()">
26 <%def name="main()">
27 ${self.context_bar('changelog')}
27 ${self.context_bar('changelog')}
28 <div class="box">
28 <div class="box">
29 <!-- box / title -->
29 <!-- box / title -->
30 <div class="title">
30 <div class="title">
31 ${self.breadcrumbs()}
31 ${self.breadcrumbs()}
32 </div>
32 </div>
33 <div class="table">
33 <div class="table">
34 % if c.pagination:
34 % if c.pagination:
35 <div id="graph">
35 <div id="graph">
36 <div style="display:${'none' if c.changelog_for_path else ''}">
36 <div style="display:${'none' if c.changelog_for_path else ''}">
37 <div class="info_box" style="clear: both;padding: 10px 6px;min-height: 12px;text-align: right;">
37 <div class="info_box" style="clear: both;padding: 10px 6px;min-height: 12px;text-align: right;">
38 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
38 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
39 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
39 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
40
40
41 %if c.rhodecode_db_repo.fork:
41 %if c.rhodecode_db_repo.fork:
42 <a id="compare_fork" title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${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="ui-btn small">${_('Compare fork with parent')}</a>
42 <a id="compare_fork" title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${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="ui-btn small">${_('Compare fork with parent')}</a>
43 %endif
43 %endif
44 %if h.is_hg(c.rhodecode_repo):
44 %if h.is_hg(c.rhodecode_repo):
45 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
45 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
46 %endif
46 %endif
47 </div>
47 </div>
48 <div class="container_header">
48 <div class="container_header">
49 ${h.form(h.url.current(),method='get')}
49 ${h.form(h.url.current(),method='get')}
50 <div style="float:left">
50 <div style="float:left">
51 ${h.submit('set',_('Show'),class_="ui-btn")}
51 ${h.submit('set',_('Show'),class_="ui-btn")}
52 ${h.text('size',size=1,value=c.size)}
52 ${h.text('size',size=1,value=c.size)}
53 ${_('revisions')}
53 ${_('revisions')}
54 </div>
54 </div>
55 ${h.end_form()}
55 ${h.end_form()}
56 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
56 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
57 </div>
57 </div>
58 </div>
58 </div>
59 <div id="graph_nodes">
59 <div id="graph_nodes">
60 <canvas id="graph_canvas"></canvas>
60 <canvas id="graph_canvas"></canvas>
61 </div>
61 </div>
62 <div id="graph_content">
62 <div id="graph_content">
63
63
64 <table id="changesets">
64 <table id="changesets">
65 <tbody>
65 <tbody>
66 %for cnt,cs in enumerate(c.pagination):
66 %for cnt,cs in enumerate(c.pagination):
67 <tr id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
67 <tr id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
68 <td class="checkbox">
68 <td class="checkbox">
69 %if c.changelog_for_path:
69 %if c.changelog_for_path:
70 ${h.checkbox(cs.raw_id,class_="changeset_range", disabled="disabled")}
70 ${h.checkbox(cs.raw_id,class_="changeset_range", disabled="disabled")}
71 %else:
71 %else:
72 ${h.checkbox(cs.raw_id,class_="changeset_range")}
72 ${h.checkbox(cs.raw_id,class_="changeset_range")}
73 %endif
73 %endif
74 <td class="status">
74 <td class="status">
75 %if c.statuses.get(cs.raw_id):
75 %if c.statuses.get(cs.raw_id):
76 <div class="changeset-status-ico">
76 <div class="changeset-status-ico">
77 %if c.statuses.get(cs.raw_id)[2]:
77 %if c.statuses.get(cs.raw_id)[2]:
78 <a class="tooltip" title="${_('Click to open associated pull request #%s' % c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
78 <a class="tooltip" title="${_('Click to open associated pull request #%s' % c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
79 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
79 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
80 </a>
80 </a>
81 %else:
81 %else:
82 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
82 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
83 %endif
83 %endif
84 </div>
84 </div>
85 %endif
85 %endif
86 </td>
86 </td>
87 <td class="author">
87 <td class="author">
88 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
88 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
89 <span title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</span>
89 <span title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</span>
90 </td>
90 </td>
91 <td class="hash" style="width:${len(h.show_id(cs))*6.5}px">
91 <td class="hash" style="width:${len(h.show_id(cs))*6.5}px">
92 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">
92 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">
93 <span class="changeset_hash">${h.show_id(cs)}</span>
93 <span class="changeset_hash">${h.show_id(cs)}</span>
94 </a>
94 </a>
95 </td>
95 </td>
96 <td class="date">
96 <td class="date">
97 <div class="date">${h.age(cs.date,True)}</div>
97 <div class="date">${h.age(cs.date,True)}</div>
98 </td>
98 </td>
99 <td class="mid">
99 <td class="mid">
100 <div class="log-container">
100 <div class="log-container">
101 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
101 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
102 <div class="expand"><span class="expandtext">&darr; ${_('Show more')} &darr;</span></div>
102 <div class="expand"><span class="expandtext">&darr; ${_('Show more')} &darr;</span></div>
103 <div class="extra-container">
103 <div class="extra-container">
104 %if c.comments.get(cs.raw_id):
104 %if c.comments.get(cs.raw_id):
105 <div class="comments-container">
105 <div class="comments-container">
106 <div class="comments-cnt" title="${('comments')}">
106 <div class="comments-cnt" title="${('comments')}">
107 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
107 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
108 ${len(c.comments[cs.raw_id])}
108 ${len(c.comments[cs.raw_id])}
109 </a>
109 </a>
110 </div>
110 </div>
111 </div>
111 </div>
112 %endif
112 %endif
113 %if h.is_hg(c.rhodecode_repo):
113 %if h.is_hg(c.rhodecode_repo):
114 %for book in cs.bookmarks:
114 %for book in cs.bookmarks:
115 <div class="booktag" title="${_('Bookmark %s') % book}">
115 <div class="booktag" title="${_('Bookmark %s') % book}">
116 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
116 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
117 </div>
117 </div>
118 %endfor
118 %endfor
119 %endif
119 %endif
120 %for tag in cs.tags:
120 %for tag in cs.tags:
121 <div class="tagtag" title="${_('Tag %s') % tag}">
121 <div class="tagtag" title="${_('Tag %s') % tag}">
122 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
122 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
123 </div>
123 </div>
124 %endfor
124 %endfor
125 %if (not c.branch_name) and cs.branch:
125 %if (not c.branch_name) and cs.branch:
126 <div class="branchtag" title="${_('Branch %s' % cs.branch)}">
126 <div class="branchtag" title="${_('Branch %s' % cs.branch)}">
127 ${h.link_to(h.shorter(cs.branch),h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch))}
127 ${h.link_to(h.shorter(cs.branch),h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch))}
128 </div>
128 </div>
129 %endif
129 %endif
130 </div>
130 </div>
131 </div>
131 </div>
132 </td>
132 </td>
133 </tr>
133 </tr>
134 %endfor
134 %endfor
135 </tbody>
135 </tbody>
136 </table>
136 </table>
137
137
138 <div class="pagination-wh pagination-left">
138 <div class="pagination-wh pagination-left">
139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
140 </div>
140 </div>
141 </div>
141 </div>
142 </div>
142 </div>
143
143
144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
145 <script type="text/javascript">
145 <script type="text/javascript">
146 YAHOO.util.Event.onDOMReady(function(){
146 YAHOO.util.Event.onDOMReady(function(){
147
147
148 //Monitor range checkboxes and build a link to changesets
148 //Monitor range checkboxes and build a link to changesets
149 //ranges
149 //ranges
150 var checkboxes = YUD.getElementsByClassName('changeset_range');
150 var checkboxes = YUD.getElementsByClassName('changeset_range');
151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
152 var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
153
152
154 var checkbox_checker = function(e){
153 var checkbox_checker = function(e){
155 var checked_checkboxes = [];
154 var checked_checkboxes = [];
156 for (pos in checkboxes){
155 for (pos in checkboxes){
157 if(checkboxes[pos].checked){
156 if(checkboxes[pos].checked){
158 checked_checkboxes.push(checkboxes[pos]);
157 checked_checkboxes.push(checkboxes[pos]);
159 }
158 }
160 }
159 }
161 if(YUD.get('open_new_pr')){
160 if(YUD.get('open_new_pr')){
162 if(checked_checkboxes.length>1){
161 if(checked_checkboxes.length>1){
163 YUD.setStyle('open_new_pr','display','none');
162 YUD.setStyle('open_new_pr','display','none');
164 } else {
163 } else {
165 YUD.setStyle('open_new_pr','display','');
164 YUD.setStyle('open_new_pr','display','');
166 if(checked_checkboxes.length>0){
165 if(checked_checkboxes.length>0){
167 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
166 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
168 }else{
167 }else{
169 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
168 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
170 }
169 }
171 }
170 }
172 }
171 }
173
172
174 if(checked_checkboxes.length>0){
173 if(checked_checkboxes.length>0){
175 var rev_end = checked_checkboxes[0].name;
174 var rev_end = checked_checkboxes[0].name;
176 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
175 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
177 var url = url_tmpl.replace('__REVRANGE__',
176 var url = url_tmpl.replace('__REVRANGE__',
178 rev_start+'...'+rev_end);
177 rev_start+'...'+rev_end);
179
178
180 var link = (rev_start == rev_end)
179 var link = (rev_start == rev_end)
181 ? _TM['Show selected changeset __S']
180 ? _TM['Show selected changeset __S']
182 : _TM['Show selected changesets __S -> __E'];
181 : _TM['Show selected changesets __S -> __E'];
183
182
184 link = link.replace('__S',rev_start.substr(0,6));
183 link = link.replace('__S',rev_start.substr(0,6));
185 link = link.replace('__E',rev_end.substr(0,6));
184 link = link.replace('__E',rev_end.substr(0,6));
186 YUD.get('rev_range_container').href = url;
185 YUD.get('rev_range_container').href = url;
187 YUD.get('rev_range_container').innerHTML = link;
186 YUD.get('rev_range_container').innerHTML = link;
188 YUD.setStyle('rev_range_container','display','');
187 YUD.setStyle('rev_range_container','display','');
189 YUD.setStyle('rev_range_clear','display','');
188 YUD.setStyle('rev_range_clear','display','');
190
189
191 YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
190 var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name,rev_start='{0}',rev_end='{1}')}";
191 YUD.get('open_new_pr').href = pr_tmpl.format(rev_start,rev_end);
192 YUD.setStyle('compare_fork','display','none');
192 YUD.setStyle('compare_fork','display','none');
193 }else{
193 }else{
194 YUD.setStyle('rev_range_container','display','none');
194 YUD.setStyle('rev_range_container','display','none');
195 YUD.setStyle('rev_range_clear','display','none');
195 YUD.setStyle('rev_range_clear','display','none');
196 if (checkboxes){
196 %if c.branch_name:
197 YUD.get('open_new_pr').href = pr_tmpl + '?rev_end={0}'.format(checkboxes[0].name);
197 YUD.get('open_new_pr').href = "${h.url('pullrequest_home',repo_name=c.repo_name,branch=c.branch_name)}";
198 }
198 %else:
199 YUD.get('open_new_pr').href = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
200 %endif
199 YUD.setStyle('compare_fork','display','');
201 YUD.setStyle('compare_fork','display','');
200 }
202 }
201 };
203 };
202 YUE.onDOMReady(checkbox_checker);
204 YUE.onDOMReady(checkbox_checker);
203 YUE.on(checkboxes,'click', checkbox_checker);
205 YUE.on(checkboxes,'click', checkbox_checker);
204
206
205 YUE.on('rev_range_clear','click',function(e){
207 YUE.on('rev_range_clear','click',function(e){
206 for (var i=0; i<checkboxes.length; i++){
208 for (var i=0; i<checkboxes.length; i++){
207 var cb = checkboxes[i];
209 var cb = checkboxes[i];
208 cb.checked = false;
210 cb.checked = false;
209 }
211 }
210 checkbox_checker();
212 checkbox_checker();
211 YUE.preventDefault(e);
213 YUE.preventDefault(e);
212 });
214 });
213
215
214 var msgs = YUQ('.message');
216 var msgs = YUQ('.message');
215 // get first element height
217 // get first element height
216 var el = YUQ('#graph_content .container')[0];
218 var el = YUQ('#graph_content .container')[0];
217 var row_h = el.clientHeight;
219 var row_h = el.clientHeight;
218 for(var i=0;i<msgs.length;i++){
220 for(var i=0;i<msgs.length;i++){
219 var m = msgs[i];
221 var m = msgs[i];
220
222
221 var h = m.clientHeight;
223 var h = m.clientHeight;
222 var pad = YUD.getStyle(m,'padding');
224 var pad = YUD.getStyle(m,'padding');
223 if(h > row_h){
225 if(h > row_h){
224 var offset = row_h - (h+12);
226 var offset = row_h - (h+12);
225 YUD.setStyle(m.nextElementSibling,'display','block');
227 YUD.setStyle(m.nextElementSibling,'display','block');
226 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
228 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
227 };
229 };
228 }
230 }
229 YUE.on(YUQ('.expand'),'click',function(e){
231 YUE.on(YUQ('.expand'),'click',function(e){
230 var elem = e.currentTarget.parentNode.parentNode;
232 var elem = e.currentTarget.parentNode.parentNode;
231 YUD.setStyle(e.currentTarget,'display','none');
233 YUD.setStyle(e.currentTarget,'display','none');
232 YUD.setStyle(elem,'height','auto');
234 YUD.setStyle(elem,'height','auto');
233
235
234 //redraw the graph, line_count and jsdata are global vars
236 //redraw the graph, line_count and jsdata are global vars
235 set_canvas(100);
237 set_canvas(100);
236
238
237 var r = new BranchRenderer();
239 var r = new BranchRenderer();
238 r.render(jsdata,100,line_count);
240 r.render(jsdata,100,line_count);
239
241
240 });
242 });
241
243
242 // change branch filter
244 // change branch filter
243 YUE.on(YUD.get('branch_filter'),'change',function(e){
245 YUE.on(YUD.get('branch_filter'),'change',function(e){
244 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
246 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
245 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
247 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
246 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
248 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
247 var url = url.replace('__BRANCH__', encodeURIComponent(selected_branch));
249 var url = url.replace('__BRANCH__', encodeURIComponent(selected_branch));
248 if(selected_branch != ''){
250 if(selected_branch != ''){
249 window.location = url;
251 window.location = url;
250 }else{
252 }else{
251 window.location = url_main;
253 window.location = url_main;
252 }
254 }
253
255
254 });
256 });
255
257
256 function set_canvas(width) {
258 function set_canvas(width) {
257 var c = document.getElementById('graph_nodes');
259 var c = document.getElementById('graph_nodes');
258 var t = document.getElementById('graph_content');
260 var t = document.getElementById('graph_content');
259 canvas = document.getElementById('graph_canvas');
261 canvas = document.getElementById('graph_canvas');
260 var div_h = t.clientHeight;
262 var div_h = t.clientHeight;
261 canvas.setAttribute('height',div_h);
263 canvas.setAttribute('height',div_h);
262 canvas.setAttribute('width',width);
264 canvas.setAttribute('width',width);
263 };
265 };
264 var heads = 1;
266 var heads = 1;
265 var line_count = 0;
267 var line_count = 0;
266 var jsdata = ${c.jsdata|n};
268 var jsdata = ${c.jsdata|n};
267
269
268 for (var i=0;i<jsdata.length;i++) {
270 for (var i=0;i<jsdata.length;i++) {
269 var in_l = jsdata[i][2];
271 var in_l = jsdata[i][2];
270 for (var j in in_l) {
272 for (var j in in_l) {
271 var m = in_l[j][1];
273 var m = in_l[j][1];
272 if (m > line_count)
274 if (m > line_count)
273 line_count = m;
275 line_count = m;
274 }
276 }
275 }
277 }
276 set_canvas(100);
278 set_canvas(100);
277
279
278 var r = new BranchRenderer();
280 var r = new BranchRenderer();
279 r.render(jsdata,100,line_count);
281 r.render(jsdata,100,line_count);
280
282
281 });
283 });
282 </script>
284 </script>
283 %else:
285 %else:
284 ${_('There are no changes yet')}
286 ${_('There are no changes yet')}
285 %endif
287 %endif
286 </div>
288 </div>
287 </div>
289 </div>
288 </%def>
290 </%def>
General Comments 0
You need to be logged in to leave comments. Login now