##// END OF EJS Templates
pullrequest: pullrequest from changelog view...
Mads Kiilerich -
r3485:b19b1723 beta
parent child Browse files
Show More
@@ -1,485 +1,479 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 import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib import diffs
42 from rhodecode.lib import diffs
43 from rhodecode.lib.utils import action_logger, jsonify
43 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
44 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.diffs import LimitedDiffContainer
46 from rhodecode.lib.diffs import LimitedDiffContainer
47 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
47 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
48 ChangesetComment
48 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
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class PullrequestsController(BaseRepoController):
59 class PullrequestsController(BaseRepoController):
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
62 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
63 'repository.admin')
63 'repository.admin')
64 def __before__(self):
64 def __before__(self):
65 super(PullrequestsController, self).__before__()
65 super(PullrequestsController, self).__before__()
66 repo_model = RepoModel()
66 repo_model = RepoModel()
67 c.users_array = repo_model.get_users_js()
67 c.users_array = repo_model.get_users_js()
68 c.users_groups_array = repo_model.get_users_groups_js()
68 c.users_groups_array = repo_model.get_users_groups_js()
69
69
70 def _get_repo_refs(self, repo, rev=None):
70 def _get_repo_refs(self, repo, rev=None):
71 """return a structure with repo's interesting changesets, suitable for
71 """return a structure with repo's interesting changesets, suitable for
72 the selectors in pullrequest.html"""
72 the selectors in pullrequest.html"""
73 branches = [('branch:%s:%s' % (k, v), k)
73 branches = [('branch:%s:%s' % (k, v), k)
74 for k, v in repo.branches.iteritems()]
74 for k, v in repo.branches.iteritems()]
75 bookmarks = [('book:%s:%s' % (k, v), k)
75 bookmarks = [('book:%s:%s' % (k, v), k)
76 for k, v in repo.bookmarks.iteritems()]
76 for k, v in repo.bookmarks.iteritems()]
77 tags = [('tag:%s:%s' % (k, v), k)
77 tags = [('tag:%s:%s' % (k, v), k)
78 for k, v in repo.tags.iteritems()
78 for k, v in repo.tags.iteritems()
79 if k != 'tip']
79 if k != 'tip']
80
80
81 tip = repo.tags['tip']
81 tip = repo.tags['tip']
82 colontip = ':' + tip
82 colontip = ':' + tip
83 tips = [x[1] for x in branches + bookmarks + tags
83 tips = [x[1] for x in branches + bookmarks + tags
84 if x[0].endswith(colontip)]
84 if x[0].endswith(colontip)]
85 selected = 'tag:tip:%s' % tip
85 selected = 'tag:tip:%s' % tip
86 special = [(selected, 'tip (%s)' % ', '.join(tips))]
86 special = [(selected, 'tip (%s)' % ', '.join(tips))]
87
87
88 if rev:
88 if rev:
89 selected = 'rev:%s:%s' % (rev, rev)
89 selected = 'rev:%s:%s' % (rev, rev)
90 special.append((selected, rev))
90 special.append((selected, rev))
91
91
92 return [(special, _("Special")),
92 return [(special, _("Special")),
93 (bookmarks, _("Bookmarks")),
93 (bookmarks, _("Bookmarks")),
94 (branches, _("Branches")),
94 (branches, _("Branches")),
95 (tags, _("Tags")),
95 (tags, _("Tags")),
96 ], selected
96 ], selected
97
97
98 def _get_is_allowed_change_status(self, pull_request):
98 def _get_is_allowed_change_status(self, pull_request):
99 owner = self.rhodecode_user.user_id == pull_request.user_id
99 owner = self.rhodecode_user.user_id == pull_request.user_id
100 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
100 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
101 pull_request.reviewers]
101 pull_request.reviewers]
102 return (self.rhodecode_user.admin or owner or reviewer)
102 return (self.rhodecode_user.admin or owner or reviewer)
103
103
104 def show_all(self, repo_name):
104 def show_all(self, repo_name):
105 c.pull_requests = PullRequestModel().get_all(repo_name)
105 c.pull_requests = PullRequestModel().get_all(repo_name)
106 c.repo_name = repo_name
106 c.repo_name = repo_name
107 return render('/pullrequests/pullrequest_show_all.html')
107 return render('/pullrequests/pullrequest_show_all.html')
108
108
109 @NotAnonymous()
109 @NotAnonymous()
110 def index(self):
110 def index(self):
111 org_repo = c.rhodecode_db_repo
111 org_repo = c.rhodecode_db_repo
112
112
113 if org_repo.scm_instance.alias != 'hg':
113 if org_repo.scm_instance.alias != 'hg':
114 log.error('Review not available for GIT REPOS')
114 log.error('Review not available for GIT REPOS')
115 raise HTTPNotFound
115 raise HTTPNotFound
116
116
117 try:
117 try:
118 org_repo.scm_instance.get_changeset()
118 org_repo.scm_instance.get_changeset()
119 except EmptyRepositoryError, e:
119 except EmptyRepositoryError, e:
120 h.flash(h.literal(_('There are no changesets yet')),
120 h.flash(h.literal(_('There are no changesets yet')),
121 category='warning')
121 category='warning')
122 redirect(url('summary_home', repo_name=org_repo.repo_name))
122 redirect(url('summary_home', repo_name=org_repo.repo_name))
123
123
124 org_rev = request.GET.get('rev_end')
125 # rev_start is not directly useful - its parent could however be used
126 # as default for other and thus give a simple compare view
127 #other_rev = request.POST.get('rev_start')
128
124 other_repos_info = {}
129 other_repos_info = {}
125
130
126 c.org_repos = []
131 c.org_repos = []
127 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
132 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
128 c.default_org_repo = org_repo.repo_name
133 c.default_org_repo = org_repo.repo_name
129 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance)
134 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
130
135
131 c.other_repos = []
136 c.other_repos = []
132 # add org repo to other so we can open pull request against itself
137 # add org repo to other so we can open pull request against itself
133 c.other_repos.extend(c.org_repos)
138 c.other_repos.extend(c.org_repos)
134 c.default_other_repo = org_repo.repo_name
139 c.default_other_repo = org_repo.repo_name
135 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.scm_instance)
140 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.scm_instance)
136 usr_data = lambda usr: dict(user_id=usr.user_id,
141 usr_data = lambda usr: dict(user_id=usr.user_id,
137 username=usr.username,
142 username=usr.username,
138 firstname=usr.firstname,
143 firstname=usr.firstname,
139 lastname=usr.lastname,
144 lastname=usr.lastname,
140 gravatar_link=h.gravatar_url(usr.email, 14))
145 gravatar_link=h.gravatar_url(usr.email, 14))
141 other_repos_info[org_repo.repo_name] = {
146 other_repos_info[org_repo.repo_name] = {
142 'user': usr_data(org_repo.user),
147 'user': usr_data(org_repo.user),
143 'description': org_repo.description,
148 'description': org_repo.description,
144 'revs': h.select('other_ref', c.default_other_ref,
149 'revs': h.select('other_ref', c.default_other_ref,
145 c.default_other_refs, class_='refs')
150 c.default_other_refs, class_='refs')
146 }
151 }
147
152
148 # gather forks and add to this list ... even though it is rare to
153 # gather forks and add to this list ... even though it is rare to
149 # request forks to pull their parent
154 # request forks to pull their parent
150 for fork in org_repo.forks:
155 for fork in org_repo.forks:
151 c.other_repos.append((fork.repo_name, fork.repo_name))
156 c.other_repos.append((fork.repo_name, fork.repo_name))
152 refs, default_ref = self._get_repo_refs(fork.scm_instance)
157 refs, default_ref = self._get_repo_refs(fork.scm_instance)
153 other_repos_info[fork.repo_name] = {
158 other_repos_info[fork.repo_name] = {
154 'user': usr_data(fork.user),
159 'user': usr_data(fork.user),
155 'description': fork.description,
160 'description': fork.description,
156 'revs': h.select('other_ref', default_ref, refs, class_='refs')
161 'revs': h.select('other_ref', default_ref, refs, class_='refs')
157 }
162 }
158
163
159 # add parents of this fork also, but only if it's not empty
164 # add parents of this fork also, but only if it's not empty
160 if org_repo.parent and org_repo.parent.scm_instance.revisions:
165 if org_repo.parent and org_repo.parent.scm_instance.revisions:
161 c.default_other_repo = org_repo.parent.repo_name
166 c.default_other_repo = org_repo.parent.repo_name
162 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.parent.scm_instance)
167 c.default_other_refs, c.default_other_ref = self._get_repo_refs(org_repo.parent.scm_instance)
163 c.other_repos.append((org_repo.parent.repo_name, org_repo.parent.repo_name))
168 c.other_repos.append((org_repo.parent.repo_name, org_repo.parent.repo_name))
164 other_repos_info[org_repo.parent.repo_name] = {
169 other_repos_info[org_repo.parent.repo_name] = {
165 'user': usr_data(org_repo.parent.user),
170 'user': usr_data(org_repo.parent.user),
166 'description': org_repo.parent.description,
171 'description': org_repo.parent.description,
167 'revs': h.select('other_ref', c.default_other_ref,
172 'revs': h.select('other_ref', c.default_other_ref,
168 c.default_other_refs, class_='refs')
173 c.default_other_refs, class_='refs')
169 }
174 }
170
175
171 c.other_repos_info = json.dumps(other_repos_info)
176 c.other_repos_info = json.dumps(other_repos_info)
172 # other repo owner
177 # other repo owner
173 c.review_members = []
178 c.review_members = []
174 return render('/pullrequests/pullrequest.html')
179 return render('/pullrequests/pullrequest.html')
175
180
176 @NotAnonymous()
181 @NotAnonymous()
177 def create(self, repo_name):
182 def create(self, repo_name):
178 repo = RepoModel()._get_repo(repo_name)
183 repo = RepoModel()._get_repo(repo_name)
179 try:
184 try:
180 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
185 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
181 except formencode.Invalid, errors:
186 except formencode.Invalid, errors:
182 log.error(traceback.format_exc())
187 log.error(traceback.format_exc())
183 if errors.error_dict.get('revisions'):
188 if errors.error_dict.get('revisions'):
184 msg = 'Revisions: %s' % errors.error_dict['revisions']
189 msg = 'Revisions: %s' % errors.error_dict['revisions']
185 elif errors.error_dict.get('pullrequest_title'):
190 elif errors.error_dict.get('pullrequest_title'):
186 msg = _('Pull request requires a title with min. 3 chars')
191 msg = _('Pull request requires a title with min. 3 chars')
187 else:
192 else:
188 msg = _('error during creation of pull request')
193 msg = _('error during creation of pull request')
189
194
190 h.flash(msg, 'error')
195 h.flash(msg, 'error')
191 return redirect(url('pullrequest_home', repo_name=repo_name))
196 return redirect(url('pullrequest_home', repo_name=repo_name))
192
197
193 org_repo = _form['org_repo']
198 org_repo = _form['org_repo']
194 org_ref = _form['org_ref']
199 org_ref = _form['org_ref']
195 other_repo = _form['other_repo']
200 other_repo = _form['other_repo']
196 other_ref = _form['other_ref']
201 other_ref = _form['other_ref']
197 revisions = _form['revisions']
202 revisions = _form['revisions']
198 reviewers = _form['review_members']
203 reviewers = _form['review_members']
199
204
200 # if we have cherry picked pull request we don't care what is in
201 # org_ref/other_ref
202 rev_start = request.POST.get('rev_start')
203 rev_end = request.POST.get('rev_end')
204
205 if rev_start and rev_end:
206 # this is swapped to simulate that rev_end is a revision from
207 # parent of the fork
208 org_ref = 'rev:%s:%s' % (rev_end, rev_end)
209 other_ref = 'rev:%s:%s' % (rev_start, rev_start)
210
211 title = _form['pullrequest_title']
205 title = _form['pullrequest_title']
212 description = _form['pullrequest_desc']
206 description = _form['pullrequest_desc']
213
207
214 try:
208 try:
215 pull_request = PullRequestModel().create(
209 pull_request = PullRequestModel().create(
216 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
210 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
217 other_ref, revisions, reviewers, title, description
211 other_ref, revisions, reviewers, title, description
218 )
212 )
219 Session().commit()
213 Session().commit()
220 h.flash(_('Successfully opened new pull request'),
214 h.flash(_('Successfully opened new pull request'),
221 category='success')
215 category='success')
222 except Exception:
216 except Exception:
223 h.flash(_('Error occurred during sending pull request'),
217 h.flash(_('Error occurred during sending pull request'),
224 category='error')
218 category='error')
225 log.error(traceback.format_exc())
219 log.error(traceback.format_exc())
226 return redirect(url('pullrequest_home', repo_name=repo_name))
220 return redirect(url('pullrequest_home', repo_name=repo_name))
227
221
228 return redirect(url('pullrequest_show', repo_name=other_repo,
222 return redirect(url('pullrequest_show', repo_name=other_repo,
229 pull_request_id=pull_request.pull_request_id))
223 pull_request_id=pull_request.pull_request_id))
230
224
231 @NotAnonymous()
225 @NotAnonymous()
232 @jsonify
226 @jsonify
233 def update(self, repo_name, pull_request_id):
227 def update(self, repo_name, pull_request_id):
234 pull_request = PullRequest.get_or_404(pull_request_id)
228 pull_request = PullRequest.get_or_404(pull_request_id)
235 if pull_request.is_closed():
229 if pull_request.is_closed():
236 raise HTTPForbidden()
230 raise HTTPForbidden()
237 #only owner or admin can update it
231 #only owner or admin can update it
238 owner = pull_request.author.user_id == c.rhodecode_user.user_id
232 owner = pull_request.author.user_id == c.rhodecode_user.user_id
239 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
233 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
240 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
234 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
241 request.POST.get('reviewers_ids', '').split(',')))
235 request.POST.get('reviewers_ids', '').split(',')))
242
236
243 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
237 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
244 Session().commit()
238 Session().commit()
245 return True
239 return True
246 raise HTTPForbidden()
240 raise HTTPForbidden()
247
241
248 @NotAnonymous()
242 @NotAnonymous()
249 @jsonify
243 @jsonify
250 def delete(self, repo_name, pull_request_id):
244 def delete(self, repo_name, pull_request_id):
251 pull_request = PullRequest.get_or_404(pull_request_id)
245 pull_request = PullRequest.get_or_404(pull_request_id)
252 #only owner can delete it !
246 #only owner can delete it !
253 if pull_request.author.user_id == c.rhodecode_user.user_id:
247 if pull_request.author.user_id == c.rhodecode_user.user_id:
254 PullRequestModel().delete(pull_request)
248 PullRequestModel().delete(pull_request)
255 Session().commit()
249 Session().commit()
256 h.flash(_('Successfully deleted pull request'),
250 h.flash(_('Successfully deleted pull request'),
257 category='success')
251 category='success')
258 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
252 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
259 raise HTTPForbidden()
253 raise HTTPForbidden()
260
254
261 def _load_compare_data(self, pull_request, enable_comments=True):
255 def _load_compare_data(self, pull_request, enable_comments=True):
262 """
256 """
263 Load context data needed for generating compare diff
257 Load context data needed for generating compare diff
264
258
265 :param pull_request:
259 :param pull_request:
266 :type pull_request:
260 :type pull_request:
267 """
261 """
268 org_repo = pull_request.org_repo
262 org_repo = pull_request.org_repo
269 (org_ref_type,
263 (org_ref_type,
270 org_ref_name,
264 org_ref_name,
271 org_ref_rev) = pull_request.org_ref.split(':')
265 org_ref_rev) = pull_request.org_ref.split(':')
272
266
273 other_repo = org_repo
267 other_repo = org_repo
274 (other_ref_type,
268 (other_ref_type,
275 other_ref_name,
269 other_ref_name,
276 other_ref_rev) = pull_request.other_ref.split(':')
270 other_ref_rev) = pull_request.other_ref.split(':')
277
271
278 # despite opening revisions for bookmarks/branches/tags, we always
272 # despite opening revisions for bookmarks/branches/tags, we always
279 # convert this to rev to prevent changes after bookmark or branch change
273 # convert this to rev to prevent changes after bookmark or branch change
280 org_ref = ('rev', org_ref_rev)
274 org_ref = ('rev', org_ref_rev)
281 other_ref = ('rev', other_ref_rev)
275 other_ref = ('rev', other_ref_rev)
282
276
283 c.org_repo = org_repo
277 c.org_repo = org_repo
284 c.other_repo = other_repo
278 c.other_repo = other_repo
285
279
286 c.fulldiff = fulldiff = request.GET.get('fulldiff')
280 c.fulldiff = fulldiff = request.GET.get('fulldiff')
287
281
288 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
282 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
289
283
290 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
284 other_ref = ('rev', getattr(c.cs_ranges[0].parents[0]
291 if c.cs_ranges[0].parents
285 if c.cs_ranges[0].parents
292 else EmptyChangeset(), 'raw_id'))
286 else EmptyChangeset(), 'raw_id'))
293
287
294 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
288 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
295
289
296 c.org_ref = org_ref[1]
290 c.org_ref = org_ref[1]
297 c.org_ref_type = org_ref[0]
291 c.org_ref_type = org_ref[0]
298 c.other_ref = other_ref[1]
292 c.other_ref = other_ref[1]
299 c.other_ref_type = other_ref[0]
293 c.other_ref_type = other_ref[0]
300
294
301 diff_limit = self.cut_off_limit if not fulldiff else None
295 diff_limit = self.cut_off_limit if not fulldiff else None
302
296
303 #we swap org/other ref since we run a simple diff on one repo
297 #we swap org/other ref since we run a simple diff on one repo
304 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
298 _diff = diffs.differ(org_repo, other_ref, other_repo, org_ref)
305
299
306 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
300 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
307 diff_limit=diff_limit)
301 diff_limit=diff_limit)
308 _parsed = diff_processor.prepare()
302 _parsed = diff_processor.prepare()
309
303
310 c.limited_diff = False
304 c.limited_diff = False
311 if isinstance(_parsed, LimitedDiffContainer):
305 if isinstance(_parsed, LimitedDiffContainer):
312 c.limited_diff = True
306 c.limited_diff = True
313
307
314 c.files = []
308 c.files = []
315 c.changes = {}
309 c.changes = {}
316 c.lines_added = 0
310 c.lines_added = 0
317 c.lines_deleted = 0
311 c.lines_deleted = 0
318 for f in _parsed:
312 for f in _parsed:
319 st = f['stats']
313 st = f['stats']
320 if st[0] != 'b':
314 if st[0] != 'b':
321 c.lines_added += st[0]
315 c.lines_added += st[0]
322 c.lines_deleted += st[1]
316 c.lines_deleted += st[1]
323 fid = h.FID('', f['filename'])
317 fid = h.FID('', f['filename'])
324 c.files.append([fid, f['operation'], f['filename'], f['stats']])
318 c.files.append([fid, f['operation'], f['filename'], f['stats']])
325 diff = diff_processor.as_html(enable_comments=enable_comments,
319 diff = diff_processor.as_html(enable_comments=enable_comments,
326 parsed_lines=[f])
320 parsed_lines=[f])
327 c.changes[fid] = [f['operation'], f['filename'], diff]
321 c.changes[fid] = [f['operation'], f['filename'], diff]
328
322
329 def show(self, repo_name, pull_request_id):
323 def show(self, repo_name, pull_request_id):
330 repo_model = RepoModel()
324 repo_model = RepoModel()
331 c.users_array = repo_model.get_users_js()
325 c.users_array = repo_model.get_users_js()
332 c.users_groups_array = repo_model.get_users_groups_js()
326 c.users_groups_array = repo_model.get_users_groups_js()
333 c.pull_request = PullRequest.get_or_404(pull_request_id)
327 c.pull_request = PullRequest.get_or_404(pull_request_id)
334 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
328 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
335 cc_model = ChangesetCommentsModel()
329 cc_model = ChangesetCommentsModel()
336 cs_model = ChangesetStatusModel()
330 cs_model = ChangesetStatusModel()
337 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
331 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
338 pull_request=c.pull_request,
332 pull_request=c.pull_request,
339 with_revisions=True)
333 with_revisions=True)
340
334
341 cs_statuses = defaultdict(list)
335 cs_statuses = defaultdict(list)
342 for st in _cs_statuses:
336 for st in _cs_statuses:
343 cs_statuses[st.author.username] += [st]
337 cs_statuses[st.author.username] += [st]
344
338
345 c.pull_request_reviewers = []
339 c.pull_request_reviewers = []
346 c.pull_request_pending_reviewers = []
340 c.pull_request_pending_reviewers = []
347 for o in c.pull_request.reviewers:
341 for o in c.pull_request.reviewers:
348 st = cs_statuses.get(o.user.username, None)
342 st = cs_statuses.get(o.user.username, None)
349 if st:
343 if st:
350 sorter = lambda k: k.version
344 sorter = lambda k: k.version
351 st = [(x, list(y)[0])
345 st = [(x, list(y)[0])
352 for x, y in (groupby(sorted(st, key=sorter), sorter))]
346 for x, y in (groupby(sorted(st, key=sorter), sorter))]
353 else:
347 else:
354 c.pull_request_pending_reviewers.append(o.user)
348 c.pull_request_pending_reviewers.append(o.user)
355 c.pull_request_reviewers.append([o.user, st])
349 c.pull_request_reviewers.append([o.user, st])
356
350
357 # pull_requests repo_name we opened it against
351 # pull_requests repo_name we opened it against
358 # ie. other_repo must match
352 # ie. other_repo must match
359 if repo_name != c.pull_request.other_repo.repo_name:
353 if repo_name != c.pull_request.other_repo.repo_name:
360 raise HTTPNotFound
354 raise HTTPNotFound
361
355
362 # load compare data into template context
356 # load compare data into template context
363 enable_comments = not c.pull_request.is_closed()
357 enable_comments = not c.pull_request.is_closed()
364 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
358 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
365
359
366 # inline comments
360 # inline comments
367 c.inline_cnt = 0
361 c.inline_cnt = 0
368 c.inline_comments = cc_model.get_inline_comments(
362 c.inline_comments = cc_model.get_inline_comments(
369 c.rhodecode_db_repo.repo_id,
363 c.rhodecode_db_repo.repo_id,
370 pull_request=pull_request_id)
364 pull_request=pull_request_id)
371 # count inline comments
365 # count inline comments
372 for __, lines in c.inline_comments:
366 for __, lines in c.inline_comments:
373 for comments in lines.values():
367 for comments in lines.values():
374 c.inline_cnt += len(comments)
368 c.inline_cnt += len(comments)
375 # comments
369 # comments
376 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
370 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
377 pull_request=pull_request_id)
371 pull_request=pull_request_id)
378
372
379 try:
373 try:
380 cur_status = c.statuses[c.pull_request.revisions[0]][0]
374 cur_status = c.statuses[c.pull_request.revisions[0]][0]
381 except:
375 except:
382 log.error(traceback.format_exc())
376 log.error(traceback.format_exc())
383 cur_status = 'undefined'
377 cur_status = 'undefined'
384 if c.pull_request.is_closed() and 0:
378 if c.pull_request.is_closed() and 0:
385 c.current_changeset_status = cur_status
379 c.current_changeset_status = cur_status
386 else:
380 else:
387 # changeset(pull-request) status calulation based on reviewers
381 # changeset(pull-request) status calulation based on reviewers
388 c.current_changeset_status = cs_model.calculate_status(
382 c.current_changeset_status = cs_model.calculate_status(
389 c.pull_request_reviewers,
383 c.pull_request_reviewers,
390 )
384 )
391 c.changeset_statuses = ChangesetStatus.STATUSES
385 c.changeset_statuses = ChangesetStatus.STATUSES
392
386
393 c.as_form = False
387 c.as_form = False
394 return render('/pullrequests/pullrequest_show.html')
388 return render('/pullrequests/pullrequest_show.html')
395
389
396 @NotAnonymous()
390 @NotAnonymous()
397 @jsonify
391 @jsonify
398 def comment(self, repo_name, pull_request_id):
392 def comment(self, repo_name, pull_request_id):
399 pull_request = PullRequest.get_or_404(pull_request_id)
393 pull_request = PullRequest.get_or_404(pull_request_id)
400 if pull_request.is_closed():
394 if pull_request.is_closed():
401 raise HTTPForbidden()
395 raise HTTPForbidden()
402
396
403 status = request.POST.get('changeset_status')
397 status = request.POST.get('changeset_status')
404 change_status = request.POST.get('change_changeset_status')
398 change_status = request.POST.get('change_changeset_status')
405 text = request.POST.get('text')
399 text = request.POST.get('text')
406 close_pr = request.POST.get('save_close')
400 close_pr = request.POST.get('save_close')
407
401
408 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
402 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
409 if status and change_status and allowed_to_change_status:
403 if status and change_status and allowed_to_change_status:
410 _def = (_('status change -> %s')
404 _def = (_('status change -> %s')
411 % ChangesetStatus.get_status_lbl(status))
405 % ChangesetStatus.get_status_lbl(status))
412 if close_pr:
406 if close_pr:
413 _def = _('Closing with') + ' ' + _def
407 _def = _('Closing with') + ' ' + _def
414 text = text or _def
408 text = text or _def
415 comm = ChangesetCommentsModel().create(
409 comm = ChangesetCommentsModel().create(
416 text=text,
410 text=text,
417 repo=c.rhodecode_db_repo.repo_id,
411 repo=c.rhodecode_db_repo.repo_id,
418 user=c.rhodecode_user.user_id,
412 user=c.rhodecode_user.user_id,
419 pull_request=pull_request_id,
413 pull_request=pull_request_id,
420 f_path=request.POST.get('f_path'),
414 f_path=request.POST.get('f_path'),
421 line_no=request.POST.get('line'),
415 line_no=request.POST.get('line'),
422 status_change=(ChangesetStatus.get_status_lbl(status)
416 status_change=(ChangesetStatus.get_status_lbl(status)
423 if status and change_status
417 if status and change_status
424 and allowed_to_change_status else None),
418 and allowed_to_change_status else None),
425 closing_pr=close_pr
419 closing_pr=close_pr
426 )
420 )
427
421
428 action_logger(self.rhodecode_user,
422 action_logger(self.rhodecode_user,
429 'user_commented_pull_request:%s' % pull_request_id,
423 'user_commented_pull_request:%s' % pull_request_id,
430 c.rhodecode_db_repo, self.ip_addr, self.sa)
424 c.rhodecode_db_repo, self.ip_addr, self.sa)
431
425
432 if allowed_to_change_status:
426 if allowed_to_change_status:
433 # get status if set !
427 # get status if set !
434 if status and change_status:
428 if status and change_status:
435 ChangesetStatusModel().set_status(
429 ChangesetStatusModel().set_status(
436 c.rhodecode_db_repo.repo_id,
430 c.rhodecode_db_repo.repo_id,
437 status,
431 status,
438 c.rhodecode_user.user_id,
432 c.rhodecode_user.user_id,
439 comm,
433 comm,
440 pull_request=pull_request_id
434 pull_request=pull_request_id
441 )
435 )
442
436
443 if close_pr:
437 if close_pr:
444 if status in ['rejected', 'approved']:
438 if status in ['rejected', 'approved']:
445 PullRequestModel().close_pull_request(pull_request_id)
439 PullRequestModel().close_pull_request(pull_request_id)
446 action_logger(self.rhodecode_user,
440 action_logger(self.rhodecode_user,
447 'user_closed_pull_request:%s' % pull_request_id,
441 'user_closed_pull_request:%s' % pull_request_id,
448 c.rhodecode_db_repo, self.ip_addr, self.sa)
442 c.rhodecode_db_repo, self.ip_addr, self.sa)
449 else:
443 else:
450 h.flash(_('Closing pull request on other statuses than '
444 h.flash(_('Closing pull request on other statuses than '
451 'rejected or approved forbidden'),
445 'rejected or approved forbidden'),
452 category='warning')
446 category='warning')
453
447
454 Session().commit()
448 Session().commit()
455
449
456 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
450 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
457 return redirect(h.url('pullrequest_show', repo_name=repo_name,
451 return redirect(h.url('pullrequest_show', repo_name=repo_name,
458 pull_request_id=pull_request_id))
452 pull_request_id=pull_request_id))
459
453
460 data = {
454 data = {
461 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
455 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
462 }
456 }
463 if comm:
457 if comm:
464 c.co = comm
458 c.co = comm
465 data.update(comm.get_dict())
459 data.update(comm.get_dict())
466 data.update({'rendered_text':
460 data.update({'rendered_text':
467 render('changeset/changeset_comment_block.html')})
461 render('changeset/changeset_comment_block.html')})
468
462
469 return data
463 return data
470
464
471 @NotAnonymous()
465 @NotAnonymous()
472 @jsonify
466 @jsonify
473 def delete_comment(self, repo_name, comment_id):
467 def delete_comment(self, repo_name, comment_id):
474 co = ChangesetComment.get(comment_id)
468 co = ChangesetComment.get(comment_id)
475 if co.pull_request.is_closed():
469 if co.pull_request.is_closed():
476 #don't allow deleting comments on closed pull request
470 #don't allow deleting comments on closed pull request
477 raise HTTPForbidden()
471 raise HTTPForbidden()
478
472
479 owner = co.author.user_id == c.rhodecode_user.user_id
473 owner = co.author.user_id == c.rhodecode_user.user_id
480 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
474 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
481 ChangesetCommentsModel().delete(comment=co)
475 ChangesetCommentsModel().delete(comment=co)
482 Session().commit()
476 Session().commit()
483 return True
477 return True
484 else:
478 else:
485 raise HTTPForbidden()
479 raise HTTPForbidden()
@@ -1,291 +1,296 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} - ${c.rhodecode_name}
6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_(u'Home'),h.url('/'))}
10 ${h.link_to(_(u'Home'),h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
12 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
13 &raquo;
13 &raquo;
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')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
15 ${_('changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
16 </%def>
16 </%def>
17
17
18 <%def name="page_nav()">
18 <%def name="page_nav()">
19 ${self.menu('changelog')}
19 ${self.menu('changelog')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box">
23 <div class="box">
24 <!-- box / title -->
24 <!-- box / title -->
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <div class="table">
28 <div class="table">
29 % if c.pagination:
29 % if c.pagination:
30 <div id="graph">
30 <div id="graph">
31 <div id="graph_nodes">
31 <div id="graph_nodes">
32 <canvas id="graph_canvas"></canvas>
32 <canvas id="graph_canvas"></canvas>
33 </div>
33 </div>
34 <div id="graph_content">
34 <div id="graph_content">
35 <div class="info_box" style="clear: both;padding: 10px 6px;text-align: right;">
35 <div class="info_box" style="clear: both;padding: 10px 6px;min-height: 12px;text-align: right;">
36 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
36 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
37 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
37 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
38
38
39 %if c.rhodecode_db_repo.fork:
39 %if c.rhodecode_db_repo.fork:
40 <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')}" class="ui-btn small">${_('Compare fork with parent')}</a>
40 <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')}" class="ui-btn small">${_('Compare fork with parent')}</a>
41 %endif
41 %endif
42 %if h.is_hg(c.rhodecode_repo):
42 %if h.is_hg(c.rhodecode_repo):
43 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
43 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
44 %endif
44 %endif
45 </div>
45 </div>
46 <div class="container_header">
46 <div class="container_header">
47 ${h.form(h.url.current(),method='get')}
47 ${h.form(h.url.current(),method='get')}
48 <div class="info_box" style="float:left">
48 <div class="info_box" style="float:left">
49 ${h.submit('set',_('Show'),class_="ui-btn")}
49 ${h.submit('set',_('Show'),class_="ui-btn")}
50 ${h.text('size',size=1,value=c.size)}
50 ${h.text('size',size=1,value=c.size)}
51 ${_('revisions')}
51 ${_('revisions')}
52 </div>
52 </div>
53 ${h.end_form()}
53 ${h.end_form()}
54 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
54 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
55 </div>
55 </div>
56
56
57 %for cnt,cs in enumerate(c.pagination):
57 %for cnt,cs in enumerate(c.pagination):
58 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
58 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
59 <div class="left">
59 <div class="left">
60 <div>
60 <div>
61 ${h.checkbox(cs.raw_id,class_="changeset_range")}
61 ${h.checkbox(cs.raw_id,class_="changeset_range")}
62 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
62 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
63 </div>
63 </div>
64 <div class="author">
64 <div class="author">
65 <div class="gravatar">
65 <div class="gravatar">
66 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
66 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
67 </div>
67 </div>
68 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
68 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
69 </div>
69 </div>
70 <div class="date">${h.fmt_date(cs.date)}</div>
70 <div class="date">${h.fmt_date(cs.date)}</div>
71 </div>
71 </div>
72 <div class="mid">
72 <div class="mid">
73 <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>
73 <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>
74 <div class="expand"><span class="expandtext">&darr; ${_('Show more')} &darr;</span></div>
74 <div class="expand"><span class="expandtext">&darr; ${_('Show more')} &darr;</span></div>
75 </div>
75 </div>
76 <div class="right">
76 <div class="right">
77 <div class="changes">
77 <div class="changes">
78 <div id="changed_total_${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
78 <div id="changed_total_${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
79 <div class="comments-container">
79 <div class="comments-container">
80 %if len(c.comments.get(cs.raw_id,[])) > 0:
80 %if len(c.comments.get(cs.raw_id,[])) > 0:
81 <div class="comments-cnt" title="${('comments')}">
81 <div class="comments-cnt" title="${('comments')}">
82 <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)}">
82 <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)}">
83 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
83 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
84 <img src="${h.url('/images/icons/comments.png')}">
84 <img src="${h.url('/images/icons/comments.png')}">
85 </a>
85 </a>
86 </div>
86 </div>
87 %endif
87 %endif
88 </div>
88 </div>
89 <div class="changeset-status-container">
89 <div class="changeset-status-container">
90 %if c.statuses.get(cs.raw_id):
90 %if c.statuses.get(cs.raw_id):
91 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
91 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
92 <div class="changeset-status-ico">
92 <div class="changeset-status-ico">
93 %if c.statuses.get(cs.raw_id)[2]:
93 %if c.statuses.get(cs.raw_id)[2]:
94 <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])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
94 <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])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
95 %else:
95 %else:
96 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
96 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
97 %endif
97 %endif
98 </div>
98 </div>
99 %endif
99 %endif
100 </div>
100 </div>
101 </div>
101 </div>
102 %if cs.parents:
102 %if cs.parents:
103 %for p_cs in reversed(cs.parents):
103 %for p_cs in reversed(cs.parents):
104 <div class="parent">${_('Parent')}
104 <div class="parent">${_('Parent')}
105 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
105 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
106 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
106 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
107 </div>
107 </div>
108 %endfor
108 %endfor
109 %else:
109 %else:
110 <div class="parent">${_('No parents')}</div>
110 <div class="parent">${_('No parents')}</div>
111 %endif
111 %endif
112
112
113 <span class="logtags">
113 <span class="logtags">
114 %if len(cs.parents)>1:
114 %if len(cs.parents)>1:
115 <span class="merge">${_('merge')}</span>
115 <span class="merge">${_('merge')}</span>
116 %endif
116 %endif
117 %if cs.branch:
117 %if cs.branch:
118 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
118 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
119 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
119 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
120 </span>
120 </span>
121 %endif
121 %endif
122 %if h.is_hg(c.rhodecode_repo):
122 %if h.is_hg(c.rhodecode_repo):
123 %for book in cs.bookmarks:
123 %for book in cs.bookmarks:
124 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
124 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
125 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
125 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
126 </span>
126 </span>
127 %endfor
127 %endfor
128 %endif
128 %endif
129 %for tag in cs.tags:
129 %for tag in cs.tags:
130 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
130 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
131 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
131 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
132 %endfor
132 %endfor
133 </span>
133 </span>
134 </div>
134 </div>
135 </div>
135 </div>
136
136
137 %endfor
137 %endfor
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)}";
152 var pr_tmpl = "${h.url('pullrequest_home',repo_name=c.repo_name)}";
153
153
154 var checkbox_checker = function(e){
154 var checkbox_checker = function(e){
155 var clicked_cb = e.currentTarget;
155 var clicked_cb = e.currentTarget;
156 var checked_checkboxes = [];
156 var checked_checkboxes = [];
157 for (pos in checkboxes){
157 for (pos in checkboxes){
158 if(checkboxes[pos].checked){
158 if(checkboxes[pos].checked){
159 checked_checkboxes.push(checkboxes[pos]);
159 checked_checkboxes.push(checkboxes[pos]);
160 }
160 }
161 }
161 }
162 if(YUD.get('open_new_pr')){
162 if(YUD.get('open_new_pr')){
163 if(checked_checkboxes.length>1){
164 YUD.setStyle('open_new_pr','display','none');
165 } else {
166 YUD.setStyle('open_new_pr','display','');
163 if(checked_checkboxes.length>0){
167 if(checked_checkboxes.length>0){
164 // modify open pull request to show we have selected cs
165 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
168 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
166 }else{
169 }else{
167 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
170 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
168 }
171 }
169 }
172 }
173 }
170
174
171 if(checked_checkboxes.length>0){
175 if(checked_checkboxes.length>0){
172 var rev_end = checked_checkboxes[0].name;
176 var rev_end = checked_checkboxes[0].name;
173 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
177 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
174 var url = url_tmpl.replace('__REVRANGE__',
178 var url = url_tmpl.replace('__REVRANGE__',
175 rev_start+'...'+rev_end);
179 rev_start+'...'+rev_end);
176
180
177 var link = (rev_start == rev_end)
181 var link = (rev_start == rev_end)
178 ? _TM['Show selected change __S']
182 ? _TM['Show selected change __S']
179 : _TM['Show selected changes __S -> __E'];
183 : _TM['Show selected changes __S -> __E'];
180
184
181 link = link.replace('__S',rev_start.substr(0,6));
185 link = link.replace('__S',rev_start.substr(0,6));
182 link = link.replace('__E',rev_end.substr(0,6));
186 link = link.replace('__E',rev_end.substr(0,6));
183 YUD.get('rev_range_container').href = url;
187 YUD.get('rev_range_container').href = url;
184 YUD.get('rev_range_container').innerHTML = link;
188 YUD.get('rev_range_container').innerHTML = link;
185 YUD.setStyle('rev_range_container','display','');
189 YUD.setStyle('rev_range_container','display','');
186 YUD.setStyle('rev_range_clear','display','');
190 YUD.setStyle('rev_range_clear','display','');
187
191
188 YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
192 YUD.get('open_new_pr').href = pr_tmpl + '?rev_start={0}&rev_end={1}'.format(rev_start,rev_end);
189 YUD.setStyle('compare_fork','display','none');
193 YUD.setStyle('compare_fork','display','none');
190 }
194 }
191 else{
195 else{
192 YUD.setStyle('rev_range_container','display','none');
196 YUD.setStyle('rev_range_container','display','none');
193 YUD.setStyle('rev_range_clear','display','none');
197 YUD.setStyle('rev_range_clear','display','none');
198 YUD.get('open_new_pr').href = pr_tmpl
194 YUD.setStyle('compare_fork','display','');
199 YUD.setStyle('compare_fork','display','');
195 }
200 }
196 };
201 };
197 YUE.onDOMReady(checkbox_checker);
202 YUE.onDOMReady(checkbox_checker);
198 YUE.on(checkboxes,'click', checkbox_checker);
203 YUE.on(checkboxes,'click', checkbox_checker);
199
204
200 YUE.on('rev_range_clear','click',function(e){
205 YUE.on('rev_range_clear','click',function(e){
201 for (var i=0; i<checkboxes.length; i++){
206 for (var i=0; i<checkboxes.length; i++){
202 var cb = checkboxes[i];
207 var cb = checkboxes[i];
203 cb.checked = false;
208 cb.checked = false;
204 }
209 }
205 YUE.preventDefault(e);
210 YUE.preventDefault(e);
206 })
211 })
207 var msgs = YUQ('.message');
212 var msgs = YUQ('.message');
208 // get first element height
213 // get first element height
209 var el = YUQ('#graph_content .container')[0];
214 var el = YUQ('#graph_content .container')[0];
210 var row_h = el.clientHeight;
215 var row_h = el.clientHeight;
211 for(var i=0;i<msgs.length;i++){
216 for(var i=0;i<msgs.length;i++){
212 var m = msgs[i];
217 var m = msgs[i];
213
218
214 var h = m.clientHeight;
219 var h = m.clientHeight;
215 var pad = YUD.getStyle(m,'padding');
220 var pad = YUD.getStyle(m,'padding');
216 if(h > row_h){
221 if(h > row_h){
217 var offset = row_h - (h+12);
222 var offset = row_h - (h+12);
218 YUD.setStyle(m.nextElementSibling,'display','block');
223 YUD.setStyle(m.nextElementSibling,'display','block');
219 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
224 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
220 };
225 };
221 }
226 }
222 YUE.on(YUQ('.expand'),'click',function(e){
227 YUE.on(YUQ('.expand'),'click',function(e){
223 var elem = e.currentTarget.parentNode.parentNode;
228 var elem = e.currentTarget.parentNode.parentNode;
224 YUD.setStyle(e.currentTarget,'display','none');
229 YUD.setStyle(e.currentTarget,'display','none');
225 YUD.setStyle(elem,'height','auto');
230 YUD.setStyle(elem,'height','auto');
226
231
227 //redraw the graph, line_count and jsdata are global vars
232 //redraw the graph, line_count and jsdata are global vars
228 set_canvas(100);
233 set_canvas(100);
229
234
230 var r = new BranchRenderer();
235 var r = new BranchRenderer();
231 r.render(jsdata,100,line_count);
236 r.render(jsdata,100,line_count);
232
237
233 })
238 })
234
239
235 // Fetch changeset details
240 // Fetch changeset details
236 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
241 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
237 var id = e.currentTarget.id;
242 var id = e.currentTarget.id;
238 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}";
243 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}";
239 var url = url.replace('__CS__',id.replace('changed_total_',''));
244 var url = url.replace('__CS__',id.replace('changed_total_',''));
240 ypjax(url,id,function(){tooltip_activate()});
245 ypjax(url,id,function(){tooltip_activate()});
241 });
246 });
242
247
243 // change branch filter
248 // change branch filter
244 YUE.on(YUD.get('branch_filter'),'change',function(e){
249 YUE.on(YUD.get('branch_filter'),'change',function(e){
245 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
250 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
246 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
251 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
247 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
252 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
248 var url = url.replace('__BRANCH__',selected_branch);
253 var url = url.replace('__BRANCH__',selected_branch);
249 if(selected_branch != ''){
254 if(selected_branch != ''){
250 window.location = url;
255 window.location = url;
251 }else{
256 }else{
252 window.location = url_main;
257 window.location = url_main;
253 }
258 }
254
259
255 });
260 });
256
261
257 function set_canvas(width) {
262 function set_canvas(width) {
258 var c = document.getElementById('graph_nodes');
263 var c = document.getElementById('graph_nodes');
259 var t = document.getElementById('graph_content');
264 var t = document.getElementById('graph_content');
260 canvas = document.getElementById('graph_canvas');
265 canvas = document.getElementById('graph_canvas');
261 var div_h = t.clientHeight;
266 var div_h = t.clientHeight;
262 c.style.height=div_h+'px';
267 c.style.height=div_h+'px';
263 canvas.setAttribute('height',div_h);
268 canvas.setAttribute('height',div_h);
264 c.style.height=width+'px';
269 c.style.height=width+'px';
265 canvas.setAttribute('width',width);
270 canvas.setAttribute('width',width);
266 };
271 };
267 var heads = 1;
272 var heads = 1;
268 var line_count = 0;
273 var line_count = 0;
269 var jsdata = ${c.jsdata|n};
274 var jsdata = ${c.jsdata|n};
270
275
271 for (var i=0;i<jsdata.length;i++) {
276 for (var i=0;i<jsdata.length;i++) {
272 var in_l = jsdata[i][2];
277 var in_l = jsdata[i][2];
273 for (var j in in_l) {
278 for (var j in in_l) {
274 var m = in_l[j][1];
279 var m = in_l[j][1];
275 if (m > line_count)
280 if (m > line_count)
276 line_count = m;
281 line_count = m;
277 }
282 }
278 }
283 }
279 set_canvas(100);
284 set_canvas(100);
280
285
281 var r = new BranchRenderer();
286 var r = new BranchRenderer();
282 r.render(jsdata,100,line_count);
287 r.render(jsdata,100,line_count);
283
288
284 });
289 });
285 </script>
290 </script>
286 %else:
291 %else:
287 ${_('There are no changes yet')}
292 ${_('There are no changes yet')}
288 %endif
293 %endif
289 </div>
294 </div>
290 </div>
295 </div>
291 </%def>
296 </%def>
General Comments 0
You need to be logged in to leave comments. Login now