##// END OF EJS Templates
comments: enable urlify functions on comments in repo/pr scopes....
dan -
r4042:34d7ca19 default
parent child Browse files
Show More
@@ -1,600 +1,601 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
25 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31 from rhodecode.apps.file_store import utils as store_utils
31 from rhodecode.apps.file_store import utils as store_utils
32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
33
33
34 from rhodecode.lib import diffs, codeblocks
34 from rhodecode.lib import diffs, codeblocks
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
37
37
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.diffs import (
39 from rhodecode.lib.diffs import (
40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
41 get_diff_whitespace_flag)
41 get_diff_whitespace_flag)
42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
43 import rhodecode.lib.helpers as h
43 import rhodecode.lib.helpers as h
44 from rhodecode.lib.utils2 import safe_unicode, str2bool
44 from rhodecode.lib.utils2 import safe_unicode, str2bool
45 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 from rhodecode.lib.vcs.exceptions import (
46 from rhodecode.lib.vcs.exceptions import (
47 RepositoryError, CommitDoesNotExistError)
47 RepositoryError, CommitDoesNotExistError)
48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore
48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore
49 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.comment import CommentsModel
50 from rhodecode.model.comment import CommentsModel
51 from rhodecode.model.meta import Session
51 from rhodecode.model.meta import Session
52 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.settings import VcsSettingsModel
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 def _update_with_GET(params, request):
57 def _update_with_GET(params, request):
58 for k in ['diff1', 'diff2', 'diff']:
58 for k in ['diff1', 'diff2', 'diff']:
59 params[k] += request.GET.getall(k)
59 params[k] += request.GET.getall(k)
60
60
61
61
62 class RepoCommitsView(RepoAppView):
62 class RepoCommitsView(RepoAppView):
63 def load_default_context(self):
63 def load_default_context(self):
64 c = self._get_local_tmpl_context(include_app_defaults=True)
64 c = self._get_local_tmpl_context(include_app_defaults=True)
65 c.rhodecode_repo = self.rhodecode_vcs_repo
65 c.rhodecode_repo = self.rhodecode_vcs_repo
66
66
67 return c
67 return c
68
68
69 def _is_diff_cache_enabled(self, target_repo):
69 def _is_diff_cache_enabled(self, target_repo):
70 caching_enabled = self._get_general_setting(
70 caching_enabled = self._get_general_setting(
71 target_repo, 'rhodecode_diff_cache')
71 target_repo, 'rhodecode_diff_cache')
72 log.debug('Diff caching enabled: %s', caching_enabled)
72 log.debug('Diff caching enabled: %s', caching_enabled)
73 return caching_enabled
73 return caching_enabled
74
74
75 def _commit(self, commit_id_range, method):
75 def _commit(self, commit_id_range, method):
76 _ = self.request.translate
76 _ = self.request.translate
77 c = self.load_default_context()
77 c = self.load_default_context()
78 c.fulldiff = self.request.GET.get('fulldiff')
78 c.fulldiff = self.request.GET.get('fulldiff')
79
79
80 # fetch global flags of ignore ws or context lines
80 # fetch global flags of ignore ws or context lines
81 diff_context = get_diff_context(self.request)
81 diff_context = get_diff_context(self.request)
82 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
82 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
83
83
84 # diff_limit will cut off the whole diff if the limit is applied
84 # diff_limit will cut off the whole diff if the limit is applied
85 # otherwise it will just hide the big files from the front-end
85 # otherwise it will just hide the big files from the front-end
86 diff_limit = c.visual.cut_off_limit_diff
86 diff_limit = c.visual.cut_off_limit_diff
87 file_limit = c.visual.cut_off_limit_file
87 file_limit = c.visual.cut_off_limit_file
88
88
89 # get ranges of commit ids if preset
89 # get ranges of commit ids if preset
90 commit_range = commit_id_range.split('...')[:2]
90 commit_range = commit_id_range.split('...')[:2]
91
91
92 try:
92 try:
93 pre_load = ['affected_files', 'author', 'branch', 'date',
93 pre_load = ['affected_files', 'author', 'branch', 'date',
94 'message', 'parents']
94 'message', 'parents']
95 if self.rhodecode_vcs_repo.alias == 'hg':
95 if self.rhodecode_vcs_repo.alias == 'hg':
96 pre_load += ['hidden', 'obsolete', 'phase']
96 pre_load += ['hidden', 'obsolete', 'phase']
97
97
98 if len(commit_range) == 2:
98 if len(commit_range) == 2:
99 commits = self.rhodecode_vcs_repo.get_commits(
99 commits = self.rhodecode_vcs_repo.get_commits(
100 start_id=commit_range[0], end_id=commit_range[1],
100 start_id=commit_range[0], end_id=commit_range[1],
101 pre_load=pre_load, translate_tags=False)
101 pre_load=pre_load, translate_tags=False)
102 commits = list(commits)
102 commits = list(commits)
103 else:
103 else:
104 commits = [self.rhodecode_vcs_repo.get_commit(
104 commits = [self.rhodecode_vcs_repo.get_commit(
105 commit_id=commit_id_range, pre_load=pre_load)]
105 commit_id=commit_id_range, pre_load=pre_load)]
106
106
107 c.commit_ranges = commits
107 c.commit_ranges = commits
108 if not c.commit_ranges:
108 if not c.commit_ranges:
109 raise RepositoryError('The commit range returned an empty result')
109 raise RepositoryError('The commit range returned an empty result')
110 except CommitDoesNotExistError as e:
110 except CommitDoesNotExistError as e:
111 msg = _('No such commit exists. Org exception: `{}`').format(e)
111 msg = _('No such commit exists. Org exception: `{}`').format(e)
112 h.flash(msg, category='error')
112 h.flash(msg, category='error')
113 raise HTTPNotFound()
113 raise HTTPNotFound()
114 except Exception:
114 except Exception:
115 log.exception("General failure")
115 log.exception("General failure")
116 raise HTTPNotFound()
116 raise HTTPNotFound()
117
117
118 c.changes = OrderedDict()
118 c.changes = OrderedDict()
119 c.lines_added = 0
119 c.lines_added = 0
120 c.lines_deleted = 0
120 c.lines_deleted = 0
121
121
122 # auto collapse if we have more than limit
122 # auto collapse if we have more than limit
123 collapse_limit = diffs.DiffProcessor._collapse_commits_over
123 collapse_limit = diffs.DiffProcessor._collapse_commits_over
124 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
124 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
125
125
126 c.commit_statuses = ChangesetStatus.STATUSES
126 c.commit_statuses = ChangesetStatus.STATUSES
127 c.inline_comments = []
127 c.inline_comments = []
128 c.files = []
128 c.files = []
129
129
130 c.statuses = []
130 c.statuses = []
131 c.comments = []
131 c.comments = []
132 c.unresolved_comments = []
132 c.unresolved_comments = []
133 c.resolved_comments = []
133 c.resolved_comments = []
134 if len(c.commit_ranges) == 1:
134 if len(c.commit_ranges) == 1:
135 commit = c.commit_ranges[0]
135 commit = c.commit_ranges[0]
136 c.comments = CommentsModel().get_comments(
136 c.comments = CommentsModel().get_comments(
137 self.db_repo.repo_id,
137 self.db_repo.repo_id,
138 revision=commit.raw_id)
138 revision=commit.raw_id)
139 c.statuses.append(ChangesetStatusModel().get_status(
139 c.statuses.append(ChangesetStatusModel().get_status(
140 self.db_repo.repo_id, commit.raw_id))
140 self.db_repo.repo_id, commit.raw_id))
141 # comments from PR
141 # comments from PR
142 statuses = ChangesetStatusModel().get_statuses(
142 statuses = ChangesetStatusModel().get_statuses(
143 self.db_repo.repo_id, commit.raw_id,
143 self.db_repo.repo_id, commit.raw_id,
144 with_revisions=True)
144 with_revisions=True)
145 prs = set(st.pull_request for st in statuses
145 prs = set(st.pull_request for st in statuses
146 if st.pull_request is not None)
146 if st.pull_request is not None)
147 # from associated statuses, check the pull requests, and
147 # from associated statuses, check the pull requests, and
148 # show comments from them
148 # show comments from them
149 for pr in prs:
149 for pr in prs:
150 c.comments.extend(pr.comments)
150 c.comments.extend(pr.comments)
151
151
152 c.unresolved_comments = CommentsModel()\
152 c.unresolved_comments = CommentsModel()\
153 .get_commit_unresolved_todos(commit.raw_id)
153 .get_commit_unresolved_todos(commit.raw_id)
154 c.resolved_comments = CommentsModel()\
154 c.resolved_comments = CommentsModel()\
155 .get_commit_resolved_todos(commit.raw_id)
155 .get_commit_resolved_todos(commit.raw_id)
156
156
157 diff = None
157 diff = None
158 # Iterate over ranges (default commit view is always one commit)
158 # Iterate over ranges (default commit view is always one commit)
159 for commit in c.commit_ranges:
159 for commit in c.commit_ranges:
160 c.changes[commit.raw_id] = []
160 c.changes[commit.raw_id] = []
161
161
162 commit2 = commit
162 commit2 = commit
163 commit1 = commit.first_parent
163 commit1 = commit.first_parent
164
164
165 if method == 'show':
165 if method == 'show':
166 inline_comments = CommentsModel().get_inline_comments(
166 inline_comments = CommentsModel().get_inline_comments(
167 self.db_repo.repo_id, revision=commit.raw_id)
167 self.db_repo.repo_id, revision=commit.raw_id)
168 c.inline_cnt = CommentsModel().get_inline_comments_count(
168 c.inline_cnt = CommentsModel().get_inline_comments_count(
169 inline_comments)
169 inline_comments)
170 c.inline_comments = inline_comments
170 c.inline_comments = inline_comments
171
171
172 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
172 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
173 self.db_repo)
173 self.db_repo)
174 cache_file_path = diff_cache_exist(
174 cache_file_path = diff_cache_exist(
175 cache_path, 'diff', commit.raw_id,
175 cache_path, 'diff', commit.raw_id,
176 hide_whitespace_changes, diff_context, c.fulldiff)
176 hide_whitespace_changes, diff_context, c.fulldiff)
177
177
178 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
178 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
179 force_recache = str2bool(self.request.GET.get('force_recache'))
179 force_recache = str2bool(self.request.GET.get('force_recache'))
180
180
181 cached_diff = None
181 cached_diff = None
182 if caching_enabled:
182 if caching_enabled:
183 cached_diff = load_cached_diff(cache_file_path)
183 cached_diff = load_cached_diff(cache_file_path)
184
184
185 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
185 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
186 if not force_recache and has_proper_diff_cache:
186 if not force_recache and has_proper_diff_cache:
187 diffset = cached_diff['diff']
187 diffset = cached_diff['diff']
188 else:
188 else:
189 vcs_diff = self.rhodecode_vcs_repo.get_diff(
189 vcs_diff = self.rhodecode_vcs_repo.get_diff(
190 commit1, commit2,
190 commit1, commit2,
191 ignore_whitespace=hide_whitespace_changes,
191 ignore_whitespace=hide_whitespace_changes,
192 context=diff_context)
192 context=diff_context)
193
193
194 diff_processor = diffs.DiffProcessor(
194 diff_processor = diffs.DiffProcessor(
195 vcs_diff, format='newdiff', diff_limit=diff_limit,
195 vcs_diff, format='newdiff', diff_limit=diff_limit,
196 file_limit=file_limit, show_full_diff=c.fulldiff)
196 file_limit=file_limit, show_full_diff=c.fulldiff)
197
197
198 _parsed = diff_processor.prepare()
198 _parsed = diff_processor.prepare()
199
199
200 diffset = codeblocks.DiffSet(
200 diffset = codeblocks.DiffSet(
201 repo_name=self.db_repo_name,
201 repo_name=self.db_repo_name,
202 source_node_getter=codeblocks.diffset_node_getter(commit1),
202 source_node_getter=codeblocks.diffset_node_getter(commit1),
203 target_node_getter=codeblocks.diffset_node_getter(commit2))
203 target_node_getter=codeblocks.diffset_node_getter(commit2))
204
204
205 diffset = self.path_filter.render_patchset_filtered(
205 diffset = self.path_filter.render_patchset_filtered(
206 diffset, _parsed, commit1.raw_id, commit2.raw_id)
206 diffset, _parsed, commit1.raw_id, commit2.raw_id)
207
207
208 # save cached diff
208 # save cached diff
209 if caching_enabled:
209 if caching_enabled:
210 cache_diff(cache_file_path, diffset, None)
210 cache_diff(cache_file_path, diffset, None)
211
211
212 c.limited_diff = diffset.limited_diff
212 c.limited_diff = diffset.limited_diff
213 c.changes[commit.raw_id] = diffset
213 c.changes[commit.raw_id] = diffset
214 else:
214 else:
215 # TODO(marcink): no cache usage here...
215 # TODO(marcink): no cache usage here...
216 _diff = self.rhodecode_vcs_repo.get_diff(
216 _diff = self.rhodecode_vcs_repo.get_diff(
217 commit1, commit2,
217 commit1, commit2,
218 ignore_whitespace=hide_whitespace_changes, context=diff_context)
218 ignore_whitespace=hide_whitespace_changes, context=diff_context)
219 diff_processor = diffs.DiffProcessor(
219 diff_processor = diffs.DiffProcessor(
220 _diff, format='newdiff', diff_limit=diff_limit,
220 _diff, format='newdiff', diff_limit=diff_limit,
221 file_limit=file_limit, show_full_diff=c.fulldiff)
221 file_limit=file_limit, show_full_diff=c.fulldiff)
222 # downloads/raw we only need RAW diff nothing else
222 # downloads/raw we only need RAW diff nothing else
223 diff = self.path_filter.get_raw_patch(diff_processor)
223 diff = self.path_filter.get_raw_patch(diff_processor)
224 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
224 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
225
225
226 # sort comments by how they were generated
226 # sort comments by how they were generated
227 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
227 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
228
228
229 if len(c.commit_ranges) == 1:
229 if len(c.commit_ranges) == 1:
230 c.commit = c.commit_ranges[0]
230 c.commit = c.commit_ranges[0]
231 c.parent_tmpl = ''.join(
231 c.parent_tmpl = ''.join(
232 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
232 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
233
233
234 if method == 'download':
234 if method == 'download':
235 response = Response(diff)
235 response = Response(diff)
236 response.content_type = 'text/plain'
236 response.content_type = 'text/plain'
237 response.content_disposition = (
237 response.content_disposition = (
238 'attachment; filename=%s.diff' % commit_id_range[:12])
238 'attachment; filename=%s.diff' % commit_id_range[:12])
239 return response
239 return response
240 elif method == 'patch':
240 elif method == 'patch':
241 c.diff = safe_unicode(diff)
241 c.diff = safe_unicode(diff)
242 patch = render(
242 patch = render(
243 'rhodecode:templates/changeset/patch_changeset.mako',
243 'rhodecode:templates/changeset/patch_changeset.mako',
244 self._get_template_context(c), self.request)
244 self._get_template_context(c), self.request)
245 response = Response(patch)
245 response = Response(patch)
246 response.content_type = 'text/plain'
246 response.content_type = 'text/plain'
247 return response
247 return response
248 elif method == 'raw':
248 elif method == 'raw':
249 response = Response(diff)
249 response = Response(diff)
250 response.content_type = 'text/plain'
250 response.content_type = 'text/plain'
251 return response
251 return response
252 elif method == 'show':
252 elif method == 'show':
253 if len(c.commit_ranges) == 1:
253 if len(c.commit_ranges) == 1:
254 html = render(
254 html = render(
255 'rhodecode:templates/changeset/changeset.mako',
255 'rhodecode:templates/changeset/changeset.mako',
256 self._get_template_context(c), self.request)
256 self._get_template_context(c), self.request)
257 return Response(html)
257 return Response(html)
258 else:
258 else:
259 c.ancestor = None
259 c.ancestor = None
260 c.target_repo = self.db_repo
260 c.target_repo = self.db_repo
261 html = render(
261 html = render(
262 'rhodecode:templates/changeset/changeset_range.mako',
262 'rhodecode:templates/changeset/changeset_range.mako',
263 self._get_template_context(c), self.request)
263 self._get_template_context(c), self.request)
264 return Response(html)
264 return Response(html)
265
265
266 raise HTTPBadRequest()
266 raise HTTPBadRequest()
267
267
268 @LoginRequired()
268 @LoginRequired()
269 @HasRepoPermissionAnyDecorator(
269 @HasRepoPermissionAnyDecorator(
270 'repository.read', 'repository.write', 'repository.admin')
270 'repository.read', 'repository.write', 'repository.admin')
271 @view_config(
271 @view_config(
272 route_name='repo_commit', request_method='GET',
272 route_name='repo_commit', request_method='GET',
273 renderer=None)
273 renderer=None)
274 def repo_commit_show(self):
274 def repo_commit_show(self):
275 commit_id = self.request.matchdict['commit_id']
275 commit_id = self.request.matchdict['commit_id']
276 return self._commit(commit_id, method='show')
276 return self._commit(commit_id, method='show')
277
277
278 @LoginRequired()
278 @LoginRequired()
279 @HasRepoPermissionAnyDecorator(
279 @HasRepoPermissionAnyDecorator(
280 'repository.read', 'repository.write', 'repository.admin')
280 'repository.read', 'repository.write', 'repository.admin')
281 @view_config(
281 @view_config(
282 route_name='repo_commit_raw', request_method='GET',
282 route_name='repo_commit_raw', request_method='GET',
283 renderer=None)
283 renderer=None)
284 @view_config(
284 @view_config(
285 route_name='repo_commit_raw_deprecated', request_method='GET',
285 route_name='repo_commit_raw_deprecated', request_method='GET',
286 renderer=None)
286 renderer=None)
287 def repo_commit_raw(self):
287 def repo_commit_raw(self):
288 commit_id = self.request.matchdict['commit_id']
288 commit_id = self.request.matchdict['commit_id']
289 return self._commit(commit_id, method='raw')
289 return self._commit(commit_id, method='raw')
290
290
291 @LoginRequired()
291 @LoginRequired()
292 @HasRepoPermissionAnyDecorator(
292 @HasRepoPermissionAnyDecorator(
293 'repository.read', 'repository.write', 'repository.admin')
293 'repository.read', 'repository.write', 'repository.admin')
294 @view_config(
294 @view_config(
295 route_name='repo_commit_patch', request_method='GET',
295 route_name='repo_commit_patch', request_method='GET',
296 renderer=None)
296 renderer=None)
297 def repo_commit_patch(self):
297 def repo_commit_patch(self):
298 commit_id = self.request.matchdict['commit_id']
298 commit_id = self.request.matchdict['commit_id']
299 return self._commit(commit_id, method='patch')
299 return self._commit(commit_id, method='patch')
300
300
301 @LoginRequired()
301 @LoginRequired()
302 @HasRepoPermissionAnyDecorator(
302 @HasRepoPermissionAnyDecorator(
303 'repository.read', 'repository.write', 'repository.admin')
303 'repository.read', 'repository.write', 'repository.admin')
304 @view_config(
304 @view_config(
305 route_name='repo_commit_download', request_method='GET',
305 route_name='repo_commit_download', request_method='GET',
306 renderer=None)
306 renderer=None)
307 def repo_commit_download(self):
307 def repo_commit_download(self):
308 commit_id = self.request.matchdict['commit_id']
308 commit_id = self.request.matchdict['commit_id']
309 return self._commit(commit_id, method='download')
309 return self._commit(commit_id, method='download')
310
310
311 @LoginRequired()
311 @LoginRequired()
312 @NotAnonymous()
312 @NotAnonymous()
313 @HasRepoPermissionAnyDecorator(
313 @HasRepoPermissionAnyDecorator(
314 'repository.read', 'repository.write', 'repository.admin')
314 'repository.read', 'repository.write', 'repository.admin')
315 @CSRFRequired()
315 @CSRFRequired()
316 @view_config(
316 @view_config(
317 route_name='repo_commit_comment_create', request_method='POST',
317 route_name='repo_commit_comment_create', request_method='POST',
318 renderer='json_ext')
318 renderer='json_ext')
319 def repo_commit_comment_create(self):
319 def repo_commit_comment_create(self):
320 _ = self.request.translate
320 _ = self.request.translate
321 commit_id = self.request.matchdict['commit_id']
321 commit_id = self.request.matchdict['commit_id']
322
322
323 c = self.load_default_context()
323 c = self.load_default_context()
324 status = self.request.POST.get('changeset_status', None)
324 status = self.request.POST.get('changeset_status', None)
325 text = self.request.POST.get('text')
325 text = self.request.POST.get('text')
326 comment_type = self.request.POST.get('comment_type')
326 comment_type = self.request.POST.get('comment_type')
327 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
327 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
328
328
329 if status:
329 if status:
330 text = text or (_('Status change %(transition_icon)s %(status)s')
330 text = text or (_('Status change %(transition_icon)s %(status)s')
331 % {'transition_icon': '>',
331 % {'transition_icon': '>',
332 'status': ChangesetStatus.get_status_lbl(status)})
332 'status': ChangesetStatus.get_status_lbl(status)})
333
333
334 multi_commit_ids = []
334 multi_commit_ids = []
335 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
335 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
336 if _commit_id not in ['', None, EmptyCommit.raw_id]:
336 if _commit_id not in ['', None, EmptyCommit.raw_id]:
337 if _commit_id not in multi_commit_ids:
337 if _commit_id not in multi_commit_ids:
338 multi_commit_ids.append(_commit_id)
338 multi_commit_ids.append(_commit_id)
339
339
340 commit_ids = multi_commit_ids or [commit_id]
340 commit_ids = multi_commit_ids or [commit_id]
341
341
342 comment = None
342 comment = None
343 for current_id in filter(None, commit_ids):
343 for current_id in filter(None, commit_ids):
344 comment = CommentsModel().create(
344 comment = CommentsModel().create(
345 text=text,
345 text=text,
346 repo=self.db_repo.repo_id,
346 repo=self.db_repo.repo_id,
347 user=self._rhodecode_db_user.user_id,
347 user=self._rhodecode_db_user.user_id,
348 commit_id=current_id,
348 commit_id=current_id,
349 f_path=self.request.POST.get('f_path'),
349 f_path=self.request.POST.get('f_path'),
350 line_no=self.request.POST.get('line'),
350 line_no=self.request.POST.get('line'),
351 status_change=(ChangesetStatus.get_status_lbl(status)
351 status_change=(ChangesetStatus.get_status_lbl(status)
352 if status else None),
352 if status else None),
353 status_change_type=status,
353 status_change_type=status,
354 comment_type=comment_type,
354 comment_type=comment_type,
355 resolves_comment_id=resolves_comment_id,
355 resolves_comment_id=resolves_comment_id,
356 auth_user=self._rhodecode_user
356 auth_user=self._rhodecode_user
357 )
357 )
358
358
359 # get status if set !
359 # get status if set !
360 if status:
360 if status:
361 # if latest status was from pull request and it's closed
361 # if latest status was from pull request and it's closed
362 # disallow changing status !
362 # disallow changing status !
363 # dont_allow_on_closed_pull_request = True !
363 # dont_allow_on_closed_pull_request = True !
364
364
365 try:
365 try:
366 ChangesetStatusModel().set_status(
366 ChangesetStatusModel().set_status(
367 self.db_repo.repo_id,
367 self.db_repo.repo_id,
368 status,
368 status,
369 self._rhodecode_db_user.user_id,
369 self._rhodecode_db_user.user_id,
370 comment,
370 comment,
371 revision=current_id,
371 revision=current_id,
372 dont_allow_on_closed_pull_request=True
372 dont_allow_on_closed_pull_request=True
373 )
373 )
374 except StatusChangeOnClosedPullRequestError:
374 except StatusChangeOnClosedPullRequestError:
375 msg = _('Changing the status of a commit associated with '
375 msg = _('Changing the status of a commit associated with '
376 'a closed pull request is not allowed')
376 'a closed pull request is not allowed')
377 log.exception(msg)
377 log.exception(msg)
378 h.flash(msg, category='warning')
378 h.flash(msg, category='warning')
379 raise HTTPFound(h.route_path(
379 raise HTTPFound(h.route_path(
380 'repo_commit', repo_name=self.db_repo_name,
380 'repo_commit', repo_name=self.db_repo_name,
381 commit_id=current_id))
381 commit_id=current_id))
382
382
383 # finalize, commit and redirect
383 # finalize, commit and redirect
384 Session().commit()
384 Session().commit()
385
385
386 data = {
386 data = {
387 'target_id': h.safeid(h.safe_unicode(
387 'target_id': h.safeid(h.safe_unicode(
388 self.request.POST.get('f_path'))),
388 self.request.POST.get('f_path'))),
389 }
389 }
390 if comment:
390 if comment:
391 c.co = comment
391 c.co = comment
392 rendered_comment = render(
392 rendered_comment = render(
393 'rhodecode:templates/changeset/changeset_comment_block.mako',
393 'rhodecode:templates/changeset/changeset_comment_block.mako',
394 self._get_template_context(c), self.request)
394 self._get_template_context(c), self.request)
395
395
396 data.update(comment.get_dict())
396 data.update(comment.get_dict())
397 data.update({'rendered_text': rendered_comment})
397 data.update({'rendered_text': rendered_comment})
398
398
399 return data
399 return data
400
400
401 @LoginRequired()
401 @LoginRequired()
402 @NotAnonymous()
402 @NotAnonymous()
403 @HasRepoPermissionAnyDecorator(
403 @HasRepoPermissionAnyDecorator(
404 'repository.read', 'repository.write', 'repository.admin')
404 'repository.read', 'repository.write', 'repository.admin')
405 @CSRFRequired()
405 @CSRFRequired()
406 @view_config(
406 @view_config(
407 route_name='repo_commit_comment_preview', request_method='POST',
407 route_name='repo_commit_comment_preview', request_method='POST',
408 renderer='string', xhr=True)
408 renderer='string', xhr=True)
409 def repo_commit_comment_preview(self):
409 def repo_commit_comment_preview(self):
410 # Technically a CSRF token is not needed as no state changes with this
410 # Technically a CSRF token is not needed as no state changes with this
411 # call. However, as this is a POST is better to have it, so automated
411 # call. However, as this is a POST is better to have it, so automated
412 # tools don't flag it as potential CSRF.
412 # tools don't flag it as potential CSRF.
413 # Post is required because the payload could be bigger than the maximum
413 # Post is required because the payload could be bigger than the maximum
414 # allowed by GET.
414 # allowed by GET.
415
415
416 text = self.request.POST.get('text')
416 text = self.request.POST.get('text')
417 renderer = self.request.POST.get('renderer') or 'rst'
417 renderer = self.request.POST.get('renderer') or 'rst'
418 if text:
418 if text:
419 return h.render(text, renderer=renderer, mentions=True)
419 return h.render(text, renderer=renderer, mentions=True,
420 repo_name=self.db_repo_name)
420 return ''
421 return ''
421
422
422 @LoginRequired()
423 @LoginRequired()
423 @NotAnonymous()
424 @NotAnonymous()
424 @HasRepoPermissionAnyDecorator(
425 @HasRepoPermissionAnyDecorator(
425 'repository.read', 'repository.write', 'repository.admin')
426 'repository.read', 'repository.write', 'repository.admin')
426 @CSRFRequired()
427 @CSRFRequired()
427 @view_config(
428 @view_config(
428 route_name='repo_commit_comment_attachment_upload', request_method='POST',
429 route_name='repo_commit_comment_attachment_upload', request_method='POST',
429 renderer='json_ext', xhr=True)
430 renderer='json_ext', xhr=True)
430 def repo_commit_comment_attachment_upload(self):
431 def repo_commit_comment_attachment_upload(self):
431 c = self.load_default_context()
432 c = self.load_default_context()
432 upload_key = 'attachment'
433 upload_key = 'attachment'
433
434
434 file_obj = self.request.POST.get(upload_key)
435 file_obj = self.request.POST.get(upload_key)
435
436
436 if file_obj is None:
437 if file_obj is None:
437 self.request.response.status = 400
438 self.request.response.status = 400
438 return {'store_fid': None,
439 return {'store_fid': None,
439 'access_path': None,
440 'access_path': None,
440 'error': '{} data field is missing'.format(upload_key)}
441 'error': '{} data field is missing'.format(upload_key)}
441
442
442 if not hasattr(file_obj, 'filename'):
443 if not hasattr(file_obj, 'filename'):
443 self.request.response.status = 400
444 self.request.response.status = 400
444 return {'store_fid': None,
445 return {'store_fid': None,
445 'access_path': None,
446 'access_path': None,
446 'error': 'filename cannot be read from the data field'}
447 'error': 'filename cannot be read from the data field'}
447
448
448 filename = file_obj.filename
449 filename = file_obj.filename
449 file_display_name = filename
450 file_display_name = filename
450
451
451 metadata = {
452 metadata = {
452 'user_uploaded': {'username': self._rhodecode_user.username,
453 'user_uploaded': {'username': self._rhodecode_user.username,
453 'user_id': self._rhodecode_user.user_id,
454 'user_id': self._rhodecode_user.user_id,
454 'ip': self._rhodecode_user.ip_addr}}
455 'ip': self._rhodecode_user.ip_addr}}
455
456
456 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
457 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
457 allowed_extensions = [
458 allowed_extensions = [
458 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
459 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
459 '.pptx', '.txt', '.xlsx', '.zip']
460 '.pptx', '.txt', '.xlsx', '.zip']
460 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
461 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
461
462
462 try:
463 try:
463 storage = store_utils.get_file_storage(self.request.registry.settings)
464 storage = store_utils.get_file_storage(self.request.registry.settings)
464 store_uid, metadata = storage.save_file(
465 store_uid, metadata = storage.save_file(
465 file_obj.file, filename, extra_metadata=metadata,
466 file_obj.file, filename, extra_metadata=metadata,
466 extensions=allowed_extensions, max_filesize=max_file_size)
467 extensions=allowed_extensions, max_filesize=max_file_size)
467 except FileNotAllowedException:
468 except FileNotAllowedException:
468 self.request.response.status = 400
469 self.request.response.status = 400
469 permitted_extensions = ', '.join(allowed_extensions)
470 permitted_extensions = ', '.join(allowed_extensions)
470 error_msg = 'File `{}` is not allowed. ' \
471 error_msg = 'File `{}` is not allowed. ' \
471 'Only following extensions are permitted: {}'.format(
472 'Only following extensions are permitted: {}'.format(
472 filename, permitted_extensions)
473 filename, permitted_extensions)
473 return {'store_fid': None,
474 return {'store_fid': None,
474 'access_path': None,
475 'access_path': None,
475 'error': error_msg}
476 'error': error_msg}
476 except FileOverSizeException:
477 except FileOverSizeException:
477 self.request.response.status = 400
478 self.request.response.status = 400
478 limit_mb = h.format_byte_size_binary(max_file_size)
479 limit_mb = h.format_byte_size_binary(max_file_size)
479 return {'store_fid': None,
480 return {'store_fid': None,
480 'access_path': None,
481 'access_path': None,
481 'error': 'File {} is exceeding allowed limit of {}.'.format(
482 'error': 'File {} is exceeding allowed limit of {}.'.format(
482 filename, limit_mb)}
483 filename, limit_mb)}
483
484
484 try:
485 try:
485 entry = FileStore.create(
486 entry = FileStore.create(
486 file_uid=store_uid, filename=metadata["filename"],
487 file_uid=store_uid, filename=metadata["filename"],
487 file_hash=metadata["sha256"], file_size=metadata["size"],
488 file_hash=metadata["sha256"], file_size=metadata["size"],
488 file_display_name=file_display_name,
489 file_display_name=file_display_name,
489 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
490 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
490 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
491 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
491 scope_repo_id=self.db_repo.repo_id
492 scope_repo_id=self.db_repo.repo_id
492 )
493 )
493 Session().add(entry)
494 Session().add(entry)
494 Session().commit()
495 Session().commit()
495 log.debug('Stored upload in DB as %s', entry)
496 log.debug('Stored upload in DB as %s', entry)
496 except Exception:
497 except Exception:
497 log.exception('Failed to store file %s', filename)
498 log.exception('Failed to store file %s', filename)
498 self.request.response.status = 400
499 self.request.response.status = 400
499 return {'store_fid': None,
500 return {'store_fid': None,
500 'access_path': None,
501 'access_path': None,
501 'error': 'File {} failed to store in DB.'.format(filename)}
502 'error': 'File {} failed to store in DB.'.format(filename)}
502
503
503 Session().commit()
504 Session().commit()
504
505
505 return {
506 return {
506 'store_fid': store_uid,
507 'store_fid': store_uid,
507 'access_path': h.route_path(
508 'access_path': h.route_path(
508 'download_file', fid=store_uid),
509 'download_file', fid=store_uid),
509 'fqn_access_path': h.route_url(
510 'fqn_access_path': h.route_url(
510 'download_file', fid=store_uid),
511 'download_file', fid=store_uid),
511 'repo_access_path': h.route_path(
512 'repo_access_path': h.route_path(
512 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
513 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
513 'repo_fqn_access_path': h.route_url(
514 'repo_fqn_access_path': h.route_url(
514 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
515 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
515 }
516 }
516
517
517 @LoginRequired()
518 @LoginRequired()
518 @NotAnonymous()
519 @NotAnonymous()
519 @HasRepoPermissionAnyDecorator(
520 @HasRepoPermissionAnyDecorator(
520 'repository.read', 'repository.write', 'repository.admin')
521 'repository.read', 'repository.write', 'repository.admin')
521 @CSRFRequired()
522 @CSRFRequired()
522 @view_config(
523 @view_config(
523 route_name='repo_commit_comment_delete', request_method='POST',
524 route_name='repo_commit_comment_delete', request_method='POST',
524 renderer='json_ext')
525 renderer='json_ext')
525 def repo_commit_comment_delete(self):
526 def repo_commit_comment_delete(self):
526 commit_id = self.request.matchdict['commit_id']
527 commit_id = self.request.matchdict['commit_id']
527 comment_id = self.request.matchdict['comment_id']
528 comment_id = self.request.matchdict['comment_id']
528
529
529 comment = ChangesetComment.get_or_404(comment_id)
530 comment = ChangesetComment.get_or_404(comment_id)
530 if not comment:
531 if not comment:
531 log.debug('Comment with id:%s not found, skipping', comment_id)
532 log.debug('Comment with id:%s not found, skipping', comment_id)
532 # comment already deleted in another call probably
533 # comment already deleted in another call probably
533 return True
534 return True
534
535
535 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
536 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
536 super_admin = h.HasPermissionAny('hg.admin')()
537 super_admin = h.HasPermissionAny('hg.admin')()
537 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
538 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
538 is_repo_comment = comment.repo.repo_name == self.db_repo_name
539 is_repo_comment = comment.repo.repo_name == self.db_repo_name
539 comment_repo_admin = is_repo_admin and is_repo_comment
540 comment_repo_admin = is_repo_admin and is_repo_comment
540
541
541 if super_admin or comment_owner or comment_repo_admin:
542 if super_admin or comment_owner or comment_repo_admin:
542 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
543 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
543 Session().commit()
544 Session().commit()
544 return True
545 return True
545 else:
546 else:
546 log.warning('No permissions for user %s to delete comment_id: %s',
547 log.warning('No permissions for user %s to delete comment_id: %s',
547 self._rhodecode_db_user, comment_id)
548 self._rhodecode_db_user, comment_id)
548 raise HTTPNotFound()
549 raise HTTPNotFound()
549
550
550 @LoginRequired()
551 @LoginRequired()
551 @HasRepoPermissionAnyDecorator(
552 @HasRepoPermissionAnyDecorator(
552 'repository.read', 'repository.write', 'repository.admin')
553 'repository.read', 'repository.write', 'repository.admin')
553 @view_config(
554 @view_config(
554 route_name='repo_commit_data', request_method='GET',
555 route_name='repo_commit_data', request_method='GET',
555 renderer='json_ext', xhr=True)
556 renderer='json_ext', xhr=True)
556 def repo_commit_data(self):
557 def repo_commit_data(self):
557 commit_id = self.request.matchdict['commit_id']
558 commit_id = self.request.matchdict['commit_id']
558 self.load_default_context()
559 self.load_default_context()
559
560
560 try:
561 try:
561 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
562 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
562 except CommitDoesNotExistError as e:
563 except CommitDoesNotExistError as e:
563 return EmptyCommit(message=str(e))
564 return EmptyCommit(message=str(e))
564
565
565 @LoginRequired()
566 @LoginRequired()
566 @HasRepoPermissionAnyDecorator(
567 @HasRepoPermissionAnyDecorator(
567 'repository.read', 'repository.write', 'repository.admin')
568 'repository.read', 'repository.write', 'repository.admin')
568 @view_config(
569 @view_config(
569 route_name='repo_commit_children', request_method='GET',
570 route_name='repo_commit_children', request_method='GET',
570 renderer='json_ext', xhr=True)
571 renderer='json_ext', xhr=True)
571 def repo_commit_children(self):
572 def repo_commit_children(self):
572 commit_id = self.request.matchdict['commit_id']
573 commit_id = self.request.matchdict['commit_id']
573 self.load_default_context()
574 self.load_default_context()
574
575
575 try:
576 try:
576 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
577 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
577 children = commit.children
578 children = commit.children
578 except CommitDoesNotExistError:
579 except CommitDoesNotExistError:
579 children = []
580 children = []
580
581
581 result = {"results": children}
582 result = {"results": children}
582 return result
583 return result
583
584
584 @LoginRequired()
585 @LoginRequired()
585 @HasRepoPermissionAnyDecorator(
586 @HasRepoPermissionAnyDecorator(
586 'repository.read', 'repository.write', 'repository.admin')
587 'repository.read', 'repository.write', 'repository.admin')
587 @view_config(
588 @view_config(
588 route_name='repo_commit_parents', request_method='GET',
589 route_name='repo_commit_parents', request_method='GET',
589 renderer='json_ext')
590 renderer='json_ext')
590 def repo_commit_parents(self):
591 def repo_commit_parents(self):
591 commit_id = self.request.matchdict['commit_id']
592 commit_id = self.request.matchdict['commit_id']
592 self.load_default_context()
593 self.load_default_context()
593
594
594 try:
595 try:
595 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
596 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
596 parents = commit.parents
597 parents = commit.parents
597 except CommitDoesNotExistError:
598 except CommitDoesNotExistError:
598 parents = []
599 parents = []
599 result = {"results": parents}
600 result = {"results": parents}
600 return result
601 return result
@@ -1,420 +1,419 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 ## ${comment.comment_block(comment)}
4 ## ${comment.comment_block(comment)}
5 ##
5 ##
6 <%namespace name="base" file="/base/base.mako"/>
6 <%namespace name="base" file="/base/base.mako"/>
7
7
8 <%def name="comment_block(comment, inline=False)">
8 <%def name="comment_block(comment, inline=False)">
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
10 <% latest_ver = len(getattr(c, 'versions', [])) %>
10 <% latest_ver = len(getattr(c, 'versions', [])) %>
11 % if inline:
11 % if inline:
12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
13 % else:
13 % else:
14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
15 % endif
15 % endif
16
16
17
18 <div class="comment
17 <div class="comment
19 ${'comment-inline' if inline else 'comment-general'}
18 ${'comment-inline' if inline else 'comment-general'}
20 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
19 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
21 id="comment-${comment.comment_id}"
20 id="comment-${comment.comment_id}"
22 line="${comment.line_no}"
21 line="${comment.line_no}"
23 data-comment-id="${comment.comment_id}"
22 data-comment-id="${comment.comment_id}"
24 data-comment-type="${comment.comment_type}"
23 data-comment-type="${comment.comment_type}"
25 data-comment-line-no="${comment.line_no}"
24 data-comment-line-no="${comment.line_no}"
26 data-comment-inline=${h.json.dumps(inline)}
25 data-comment-inline=${h.json.dumps(inline)}
27 style="${'display: none;' if outdated_at_ver else ''}">
26 style="${'display: none;' if outdated_at_ver else ''}">
28
27
29 <div class="meta">
28 <div class="meta">
30 <div class="comment-type-label">
29 <div class="comment-type-label">
31 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
30 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
32 % if comment.comment_type == 'todo':
31 % if comment.comment_type == 'todo':
33 % if comment.resolved:
32 % if comment.resolved:
34 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
33 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
35 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
34 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
36 </div>
35 </div>
37 % else:
36 % else:
38 <div class="resolved tooltip" style="display: none">
37 <div class="resolved tooltip" style="display: none">
39 <span>${comment.comment_type}</span>
38 <span>${comment.comment_type}</span>
40 </div>
39 </div>
41 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
40 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
42 ${comment.comment_type}
41 ${comment.comment_type}
43 </div>
42 </div>
44 % endif
43 % endif
45 % else:
44 % else:
46 % if comment.resolved_comment:
45 % if comment.resolved_comment:
47 fix
46 fix
48 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
47 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
49 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
48 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
50 </a>
49 </a>
51 % else:
50 % else:
52 ${comment.comment_type or 'note'}
51 ${comment.comment_type or 'note'}
53 % endif
52 % endif
54 % endif
53 % endif
55 </div>
54 </div>
56 </div>
55 </div>
57
56
58 <div class="author ${'author-inline' if inline else 'author-general'}">
57 <div class="author ${'author-inline' if inline else 'author-general'}">
59 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
58 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
60 </div>
59 </div>
61 <div class="date">
60 <div class="date">
62 ${h.age_component(comment.modified_at, time_is_local=True)}
61 ${h.age_component(comment.modified_at, time_is_local=True)}
63 </div>
62 </div>
64 % if inline:
63 % if inline:
65 <span></span>
64 <span></span>
66 % else:
65 % else:
67 <div class="status-change">
66 <div class="status-change">
68 % if comment.pull_request:
67 % if comment.pull_request:
69 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
68 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
70 % if comment.status_change:
69 % if comment.status_change:
71 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}:
70 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}:
72 % else:
71 % else:
73 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}
72 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}
74 % endif
73 % endif
75 </a>
74 </a>
76 % else:
75 % else:
77 % if comment.status_change:
76 % if comment.status_change:
78 ${_('Status change on commit')}:
77 ${_('Status change on commit')}:
79 % endif
78 % endif
80 % endif
79 % endif
81 </div>
80 </div>
82 % endif
81 % endif
83
82
84 % if comment.status_change:
83 % if comment.status_change:
85 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
84 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
86 <div title="${_('Commit status')}" class="changeset-status-lbl">
85 <div title="${_('Commit status')}" class="changeset-status-lbl">
87 ${comment.status_change[0].status_lbl}
86 ${comment.status_change[0].status_lbl}
88 </div>
87 </div>
89 % endif
88 % endif
90
89
91 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
90 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
92
91
93 <div class="comment-links-block">
92 <div class="comment-links-block">
94 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
93 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
95 <span class="tag authortag tooltip" title="${_('Pull request author')}">
94 <span class="tag authortag tooltip" title="${_('Pull request author')}">
96 ${_('author')}
95 ${_('author')}
97 </span>
96 </span>
98 |
97 |
99 % endif
98 % endif
100 % if inline:
99 % if inline:
101 <div class="pr-version-inline">
100 <div class="pr-version-inline">
102 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
101 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
103 % if outdated_at_ver:
102 % if outdated_at_ver:
104 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
103 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
105 outdated ${'v{}'.format(pr_index_ver)} |
104 outdated ${'v{}'.format(pr_index_ver)} |
106 </code>
105 </code>
107 % elif pr_index_ver:
106 % elif pr_index_ver:
108 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
107 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
109 ${'v{}'.format(pr_index_ver)} |
108 ${'v{}'.format(pr_index_ver)} |
110 </code>
109 </code>
111 % endif
110 % endif
112 </a>
111 </a>
113 </div>
112 </div>
114 % else:
113 % else:
115 % if comment.pull_request_version_id and pr_index_ver:
114 % if comment.pull_request_version_id and pr_index_ver:
116 |
115 |
117 <div class="pr-version">
116 <div class="pr-version">
118 % if comment.outdated:
117 % if comment.outdated:
119 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
118 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
120 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
119 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
121 </a>
120 </a>
122 % else:
121 % else:
123 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
122 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
124 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
123 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
125 <code class="pr-version-num">
124 <code class="pr-version-num">
126 ${'v{}'.format(pr_index_ver)}
125 ${'v{}'.format(pr_index_ver)}
127 </code>
126 </code>
128 </a>
127 </a>
129 </div>
128 </div>
130 % endif
129 % endif
131 </div>
130 </div>
132 % endif
131 % endif
133 % endif
132 % endif
134
133
135 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
134 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
136 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
135 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
137 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
136 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
138 ## permissions to delete
137 ## permissions to delete
139 %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
138 %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
140 ## TODO: dan: add edit comment here
139 ## TODO: dan: add edit comment here
141 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
140 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
142 %else:
141 %else:
143 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
142 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
144 %endif
143 %endif
145 %else:
144 %else:
146 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
145 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
147 %endif
146 %endif
148
147
149 % if outdated_at_ver:
148 % if outdated_at_ver:
150 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
149 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
151 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
150 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
152 % else:
151 % else:
153 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
152 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
154 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
153 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
155 % endif
154 % endif
156
155
157 </div>
156 </div>
158 </div>
157 </div>
159 <div class="text">
158 <div class="text">
160 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
159 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None))}
161 </div>
160 </div>
162
161
163 </div>
162 </div>
164 </%def>
163 </%def>
165
164
166 ## generate main comments
165 ## generate main comments
167 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
166 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
168 <div class="general-comments" id="comments">
167 <div class="general-comments" id="comments">
169 %for comment in comments:
168 %for comment in comments:
170 <div id="comment-tr-${comment.comment_id}">
169 <div id="comment-tr-${comment.comment_id}">
171 ## only render comments that are not from pull request, or from
170 ## only render comments that are not from pull request, or from
172 ## pull request and a status change
171 ## pull request and a status change
173 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
172 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
174 ${comment_block(comment)}
173 ${comment_block(comment)}
175 %endif
174 %endif
176 </div>
175 </div>
177 %endfor
176 %endfor
178 ## to anchor ajax comments
177 ## to anchor ajax comments
179 <div id="injected_page_comments"></div>
178 <div id="injected_page_comments"></div>
180 </div>
179 </div>
181 </%def>
180 </%def>
182
181
183
182
184 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
183 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
185
184
186 <div class="comments">
185 <div class="comments">
187 <%
186 <%
188 if is_pull_request:
187 if is_pull_request:
189 placeholder = _('Leave a comment on this Pull Request.')
188 placeholder = _('Leave a comment on this Pull Request.')
190 elif is_compare:
189 elif is_compare:
191 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
190 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
192 else:
191 else:
193 placeholder = _('Leave a comment on this Commit.')
192 placeholder = _('Leave a comment on this Commit.')
194 %>
193 %>
195
194
196 % if c.rhodecode_user.username != h.DEFAULT_USER:
195 % if c.rhodecode_user.username != h.DEFAULT_USER:
197 <div class="js-template" id="cb-comment-general-form-template">
196 <div class="js-template" id="cb-comment-general-form-template">
198 ## template generated for injection
197 ## template generated for injection
199 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
198 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
200 </div>
199 </div>
201
200
202 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
201 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
203 ## inject form here
202 ## inject form here
204 </div>
203 </div>
205 <script type="text/javascript">
204 <script type="text/javascript">
206 var lineNo = 'general';
205 var lineNo = 'general';
207 var resolvesCommentId = null;
206 var resolvesCommentId = null;
208 var generalCommentForm = Rhodecode.comments.createGeneralComment(
207 var generalCommentForm = Rhodecode.comments.createGeneralComment(
209 lineNo, "${placeholder}", resolvesCommentId);
208 lineNo, "${placeholder}", resolvesCommentId);
210
209
211 // set custom success callback on rangeCommit
210 // set custom success callback on rangeCommit
212 % if is_compare:
211 % if is_compare:
213 generalCommentForm.setHandleFormSubmit(function(o) {
212 generalCommentForm.setHandleFormSubmit(function(o) {
214 var self = generalCommentForm;
213 var self = generalCommentForm;
215
214
216 var text = self.cm.getValue();
215 var text = self.cm.getValue();
217 var status = self.getCommentStatus();
216 var status = self.getCommentStatus();
218 var commentType = self.getCommentType();
217 var commentType = self.getCommentType();
219
218
220 if (text === "" && !status) {
219 if (text === "" && !status) {
221 return;
220 return;
222 }
221 }
223
222
224 // we can pick which commits we want to make the comment by
223 // we can pick which commits we want to make the comment by
225 // selecting them via click on preview pane, this will alter the hidden inputs
224 // selecting them via click on preview pane, this will alter the hidden inputs
226 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
225 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
227
226
228 var commitIds = [];
227 var commitIds = [];
229 $('#changeset_compare_view_content .compare_select').each(function(el) {
228 $('#changeset_compare_view_content .compare_select').each(function(el) {
230 var commitId = this.id.replace('row-', '');
229 var commitId = this.id.replace('row-', '');
231 if ($(this).hasClass('hl') || !cherryPicked) {
230 if ($(this).hasClass('hl') || !cherryPicked) {
232 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
231 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
233 commitIds.push(commitId);
232 commitIds.push(commitId);
234 } else {
233 } else {
235 $("input[data-commit-id='{0}']".format(commitId)).val('')
234 $("input[data-commit-id='{0}']".format(commitId)).val('')
236 }
235 }
237 });
236 });
238
237
239 self.setActionButtonsDisabled(true);
238 self.setActionButtonsDisabled(true);
240 self.cm.setOption("readOnly", true);
239 self.cm.setOption("readOnly", true);
241 var postData = {
240 var postData = {
242 'text': text,
241 'text': text,
243 'changeset_status': status,
242 'changeset_status': status,
244 'comment_type': commentType,
243 'comment_type': commentType,
245 'commit_ids': commitIds,
244 'commit_ids': commitIds,
246 'csrf_token': CSRF_TOKEN
245 'csrf_token': CSRF_TOKEN
247 };
246 };
248
247
249 var submitSuccessCallback = function(o) {
248 var submitSuccessCallback = function(o) {
250 location.reload(true);
249 location.reload(true);
251 };
250 };
252 var submitFailCallback = function(){
251 var submitFailCallback = function(){
253 self.resetCommentFormState(text)
252 self.resetCommentFormState(text)
254 };
253 };
255 self.submitAjaxPOST(
254 self.submitAjaxPOST(
256 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
255 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
257 });
256 });
258 % endif
257 % endif
259
258
260 </script>
259 </script>
261 % else:
260 % else:
262 ## form state when not logged in
261 ## form state when not logged in
263 <div class="comment-form ac">
262 <div class="comment-form ac">
264
263
265 <div class="comment-area">
264 <div class="comment-area">
266 <div class="comment-area-header">
265 <div class="comment-area-header">
267 <ul class="nav-links clearfix">
266 <ul class="nav-links clearfix">
268 <li class="active">
267 <li class="active">
269 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
268 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
270 </li>
269 </li>
271 <li class="">
270 <li class="">
272 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
271 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
273 </li>
272 </li>
274 </ul>
273 </ul>
275 </div>
274 </div>
276
275
277 <div class="comment-area-write" style="display: block;">
276 <div class="comment-area-write" style="display: block;">
278 <div id="edit-container">
277 <div id="edit-container">
279 <div style="padding: 40px 0">
278 <div style="padding: 40px 0">
280 ${_('You need to be logged in to leave comments.')}
279 ${_('You need to be logged in to leave comments.')}
281 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
280 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
282 </div>
281 </div>
283 </div>
282 </div>
284 <div id="preview-container" class="clearfix" style="display: none;">
283 <div id="preview-container" class="clearfix" style="display: none;">
285 <div id="preview-box" class="preview-box"></div>
284 <div id="preview-box" class="preview-box"></div>
286 </div>
285 </div>
287 </div>
286 </div>
288
287
289 <div class="comment-area-footer">
288 <div class="comment-area-footer">
290 <div class="toolbar">
289 <div class="toolbar">
291 <div class="toolbar-text">
290 <div class="toolbar-text">
292 </div>
291 </div>
293 </div>
292 </div>
294 </div>
293 </div>
295 </div>
294 </div>
296
295
297 <div class="comment-footer">
296 <div class="comment-footer">
298 </div>
297 </div>
299
298
300 </div>
299 </div>
301 % endif
300 % endif
302
301
303 <script type="text/javascript">
302 <script type="text/javascript">
304 bindToggleButtons();
303 bindToggleButtons();
305 </script>
304 </script>
306 </div>
305 </div>
307 </%def>
306 </%def>
308
307
309
308
310 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
309 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
311
310
312 ## comment injected based on assumption that user is logged in
311 ## comment injected based on assumption that user is logged in
313 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
312 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
314
313
315 <div class="comment-area">
314 <div class="comment-area">
316 <div class="comment-area-header">
315 <div class="comment-area-header">
317 <ul class="nav-links clearfix">
316 <ul class="nav-links clearfix">
318 <li class="active">
317 <li class="active">
319 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
318 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
320 </li>
319 </li>
321 <li class="">
320 <li class="">
322 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
321 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
323 </li>
322 </li>
324 <li class="pull-right">
323 <li class="pull-right">
325 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
324 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
326 % for val in c.visual.comment_types:
325 % for val in c.visual.comment_types:
327 <option value="${val}">${val.upper()}</option>
326 <option value="${val}">${val.upper()}</option>
328 % endfor
327 % endfor
329 </select>
328 </select>
330 </li>
329 </li>
331 </ul>
330 </ul>
332 </div>
331 </div>
333
332
334 <div class="comment-area-write" style="display: block;">
333 <div class="comment-area-write" style="display: block;">
335 <div id="edit-container_${lineno_id}">
334 <div id="edit-container_${lineno_id}">
336 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
335 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
337 </div>
336 </div>
338 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
337 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
339 <div id="preview-box_${lineno_id}" class="preview-box"></div>
338 <div id="preview-box_${lineno_id}" class="preview-box"></div>
340 </div>
339 </div>
341 </div>
340 </div>
342
341
343 <div class="comment-area-footer comment-attachment-uploader">
342 <div class="comment-area-footer comment-attachment-uploader">
344 <div class="toolbar">
343 <div class="toolbar">
345 <div class="toolbar-text">
344 <div class="toolbar-text">
346 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
345 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
347 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
346 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
348 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
347 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
349 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
348 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
350 )
349 )
351 )|n}
350 )|n}
352 </div>
351 </div>
353
352
354 <div class="comment-attachment-text">
353 <div class="comment-attachment-text">
355 <div class="dropzone-text">
354 <div class="dropzone-text">
356 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
355 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
357 </div>
356 </div>
358 <div class="dropzone-upload" style="display:none">
357 <div class="dropzone-upload" style="display:none">
359 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
358 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
360 </div>
359 </div>
361 </div>
360 </div>
362
361
363 ## comments dropzone template, empty on purpose
362 ## comments dropzone template, empty on purpose
364 <div style="display: none" class="comment-attachment-uploader-template">
363 <div style="display: none" class="comment-attachment-uploader-template">
365 <div class="dz-file-preview" style="margin: 0">
364 <div class="dz-file-preview" style="margin: 0">
366 <div class="dz-error-message"></div>
365 <div class="dz-error-message"></div>
367 </div>
366 </div>
368 </div>
367 </div>
369
368
370 </div>
369 </div>
371 </div>
370 </div>
372 </div>
371 </div>
373
372
374 <div class="comment-footer">
373 <div class="comment-footer">
375
374
376 % if review_statuses:
375 % if review_statuses:
377 <div class="status_box">
376 <div class="status_box">
378 <select id="change_status_${lineno_id}" name="changeset_status">
377 <select id="change_status_${lineno_id}" name="changeset_status">
379 <option></option> ## Placeholder
378 <option></option> ## Placeholder
380 % for status, lbl in review_statuses:
379 % for status, lbl in review_statuses:
381 <option value="${status}" data-status="${status}">${lbl}</option>
380 <option value="${status}" data-status="${status}">${lbl}</option>
382 %if is_pull_request and change_status and status in ('approved', 'rejected'):
381 %if is_pull_request and change_status and status in ('approved', 'rejected'):
383 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
382 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
384 %endif
383 %endif
385 % endfor
384 % endfor
386 </select>
385 </select>
387 </div>
386 </div>
388 % endif
387 % endif
389
388
390 ## inject extra inputs into the form
389 ## inject extra inputs into the form
391 % if form_extras and isinstance(form_extras, (list, tuple)):
390 % if form_extras and isinstance(form_extras, (list, tuple)):
392 <div id="comment_form_extras">
391 <div id="comment_form_extras">
393 % for form_ex_el in form_extras:
392 % for form_ex_el in form_extras:
394 ${form_ex_el|n}
393 ${form_ex_el|n}
395 % endfor
394 % endfor
396 </div>
395 </div>
397 % endif
396 % endif
398
397
399 <div class="action-buttons">
398 <div class="action-buttons">
400 ## inline for has a file, and line-number together with cancel hide button.
399 ## inline for has a file, and line-number together with cancel hide button.
401 % if form_type == 'inline':
400 % if form_type == 'inline':
402 <input type="hidden" name="f_path" value="{0}">
401 <input type="hidden" name="f_path" value="{0}">
403 <input type="hidden" name="line" value="${lineno_id}">
402 <input type="hidden" name="line" value="${lineno_id}">
404 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
403 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
405 ${_('Cancel')}
404 ${_('Cancel')}
406 </button>
405 </button>
407 % endif
406 % endif
408
407
409 % if form_type != 'inline':
408 % if form_type != 'inline':
410 <div class="action-buttons-extra"></div>
409 <div class="action-buttons-extra"></div>
411 % endif
410 % endif
412
411
413 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
412 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
414
413
415 </div>
414 </div>
416 </div>
415 </div>
417
416
418 </form>
417 </form>
419
418
420 </%def> No newline at end of file
419 </%def>
@@ -1,804 +1,804 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
7 %if c.rhodecode_name:
7 %if c.rhodecode_name:
8 &middot; ${h.branding(c.rhodecode_name)}
8 &middot; ${h.branding(c.rhodecode_name)}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <%def name="breadcrumbs_links()">
12 <%def name="breadcrumbs_links()">
13 <span id="pr-title">
13 <span id="pr-title">
14 ${c.pull_request.title}
14 ${c.pull_request.title}
15 %if c.pull_request.is_closed():
15 %if c.pull_request.is_closed():
16 (${_('Closed')})
16 (${_('Closed')})
17 %endif
17 %endif
18 </span>
18 </span>
19 <div id="pr-title-edit" class="input" style="display: none;">
19 <div id="pr-title-edit" class="input" style="display: none;">
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
21 </div>
21 </div>
22 </%def>
22 </%def>
23
23
24 <%def name="menu_bar_nav()">
24 <%def name="menu_bar_nav()">
25 ${self.menu_items(active='repositories')}
25 ${self.menu_items(active='repositories')}
26 </%def>
26 </%def>
27
27
28 <%def name="menu_bar_subnav()">
28 <%def name="menu_bar_subnav()">
29 ${self.repo_menu(active='showpullrequest')}
29 ${self.repo_menu(active='showpullrequest')}
30 </%def>
30 </%def>
31
31
32 <%def name="main()">
32 <%def name="main()">
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
35 // TODO: marcink switch this to pyroutes
35 // TODO: marcink switch this to pyroutes
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 </script>
38 </script>
39 <div class="box">
39 <div class="box">
40
40
41 ${self.breadcrumbs()}
41 ${self.breadcrumbs()}
42
42
43 <div class="box pr-summary">
43 <div class="box pr-summary">
44
44
45 <div class="summary-details block-left">
45 <div class="summary-details block-left">
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
47 <div class="pr-details-title">
47 <div class="pr-details-title">
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 %if c.allowed_to_update:
49 %if c.allowed_to_update:
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
51 % if c.allowed_to_delete:
51 % if c.allowed_to_delete:
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
55 ${h.end_form()}
55 ${h.end_form()}
56 % else:
56 % else:
57 ${_('Delete')}
57 ${_('Delete')}
58 % endif
58 % endif
59 </div>
59 </div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
62 %endif
62 %endif
63 </div>
63 </div>
64
64
65 <div id="summary" class="fields pr-details-content">
65 <div id="summary" class="fields pr-details-content">
66 <div class="field">
66 <div class="field">
67 <div class="label-summary">
67 <div class="label-summary">
68 <label>${_('Source')}:</label>
68 <label>${_('Source')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 <div class="pr-origininfo">
71 <div class="pr-origininfo">
72 ## branch link is only valid if it is a branch
72 ## branch link is only valid if it is a branch
73 <span class="tag">
73 <span class="tag">
74 %if c.pull_request.source_ref_parts.type == 'branch':
74 %if c.pull_request.source_ref_parts.type == 'branch':
75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
76 %else:
76 %else:
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
78 %endif
78 %endif
79 </span>
79 </span>
80 <span class="clone-url">
80 <span class="clone-url">
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
82 </span>
82 </span>
83 <br/>
83 <br/>
84 % if c.ancestor_commit:
84 % if c.ancestor_commit:
85 ${_('Common ancestor')}:
85 ${_('Common ancestor')}:
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
87 % endif
87 % endif
88 </div>
88 </div>
89 %if h.is_hg(c.pull_request.source_repo):
89 %if h.is_hg(c.pull_request.source_repo):
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
91 %elif h.is_git(c.pull_request.source_repo):
91 %elif h.is_git(c.pull_request.source_repo):
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
93 %endif
93 %endif
94
94
95 <div class="">
95 <div class="">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
98 </div>
98 </div>
99
99
100 </div>
100 </div>
101 </div>
101 </div>
102 <div class="field">
102 <div class="field">
103 <div class="label-summary">
103 <div class="label-summary">
104 <label>${_('Target')}:</label>
104 <label>${_('Target')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 <div class="pr-targetinfo">
107 <div class="pr-targetinfo">
108 ## branch link is only valid if it is a branch
108 ## branch link is only valid if it is a branch
109 <span class="tag">
109 <span class="tag">
110 %if c.pull_request.target_ref_parts.type == 'branch':
110 %if c.pull_request.target_ref_parts.type == 'branch':
111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
112 %else:
112 %else:
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
114 %endif
114 %endif
115 </span>
115 </span>
116 <span class="clone-url">
116 <span class="clone-url">
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
118 </span>
118 </span>
119 </div>
119 </div>
120 </div>
120 </div>
121 </div>
121 </div>
122
122
123 ## Link to the shadow repository.
123 ## Link to the shadow repository.
124 <div class="field">
124 <div class="field">
125 <div class="label-summary">
125 <div class="label-summary">
126 <label>${_('Merge')}:</label>
126 <label>${_('Merge')}:</label>
127 </div>
127 </div>
128 <div class="input">
128 <div class="input">
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
130 %if h.is_hg(c.pull_request.target_repo):
130 %if h.is_hg(c.pull_request.target_repo):
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
132 %elif h.is_git(c.pull_request.target_repo):
132 %elif h.is_git(c.pull_request.target_repo):
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
134 %endif
134 %endif
135 <div class="">
135 <div class="">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
138 </div>
138 </div>
139 % else:
139 % else:
140 <div class="">
140 <div class="">
141 ${_('Shadow repository data not available')}.
141 ${_('Shadow repository data not available')}.
142 </div>
142 </div>
143 % endif
143 % endif
144 </div>
144 </div>
145 </div>
145 </div>
146
146
147 <div class="field">
147 <div class="field">
148 <div class="label-summary">
148 <div class="label-summary">
149 <label>${_('Review')}:</label>
149 <label>${_('Review')}:</label>
150 </div>
150 </div>
151 <div class="input">
151 <div class="input">
152 %if c.pull_request_review_status:
152 %if c.pull_request_review_status:
153 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
153 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
154 <span class="changeset-status-lbl tooltip">
154 <span class="changeset-status-lbl tooltip">
155 %if c.pull_request.is_closed():
155 %if c.pull_request.is_closed():
156 ${_('Closed')},
156 ${_('Closed')},
157 %endif
157 %endif
158 ${h.commit_status_lbl(c.pull_request_review_status)}
158 ${h.commit_status_lbl(c.pull_request_review_status)}
159 </span>
159 </span>
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
161 %endif
161 %endif
162 </div>
162 </div>
163 </div>
163 </div>
164 <div class="field">
164 <div class="field">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
166 <label>${_('Description')}:</label>
166 <label>${_('Description')}:</label>
167 </div>
167 </div>
168 <div id="pr-desc" class="input">
168 <div id="pr-desc" class="input">
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer)}</div>
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name)}</div>
170 </div>
170 </div>
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
174 </div>
174 </div>
175 </div>
175 </div>
176
176
177 <div class="field">
177 <div class="field">
178 <div class="label-summary">
178 <div class="label-summary">
179 <label>${_('Versions')}:</label>
179 <label>${_('Versions')}:</label>
180 </div>
180 </div>
181
181
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
184
184
185 <div class="pr-versions">
185 <div class="pr-versions">
186 % if c.show_version_changes:
186 % if c.show_version_changes:
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
191 data-toggle-off="${_('Hide all versions of this pull request')}">
191 data-toggle-off="${_('Hide all versions of this pull request')}">
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
193 </a>
193 </a>
194 <table>
194 <table>
195 ## SHOW ALL VERSIONS OF PR
195 ## SHOW ALL VERSIONS OF PR
196 <% ver_pr = None %>
196 <% ver_pr = None %>
197
197
198 % for data in reversed(list(enumerate(c.versions, 1))):
198 % for data in reversed(list(enumerate(c.versions, 1))):
199 <% ver_pos = data[0] %>
199 <% ver_pos = data[0] %>
200 <% ver = data[1] %>
200 <% ver = data[1] %>
201 <% ver_pr = ver.pull_request_version_id %>
201 <% ver_pr = ver.pull_request_version_id %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
203
203
204 <tr class="version-pr" style="display: ${display_row}">
204 <tr class="version-pr" style="display: ${display_row}">
205 <td>
205 <td>
206 <code>
206 <code>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
208 </code>
208 </code>
209 </td>
209 </td>
210 <td>
210 <td>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
213 </td>
213 </td>
214 <td>
214 <td>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
216 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
216 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
217 </div>
217 </div>
218 </td>
218 </td>
219 <td>
219 <td>
220 % if c.at_version_num != ver_pr:
220 % if c.at_version_num != ver_pr:
221 <i class="icon-comment"></i>
221 <i class="icon-comment"></i>
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
224 </code>
224 </code>
225 % endif
225 % endif
226 </td>
226 </td>
227 <td>
227 <td>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
229 </td>
229 </td>
230 <td>
230 <td>
231 ${h.age_component(ver.updated_on, time_is_local=True)}
231 ${h.age_component(ver.updated_on, time_is_local=True)}
232 </td>
232 </td>
233 </tr>
233 </tr>
234 % endfor
234 % endfor
235
235
236 <tr>
236 <tr>
237 <td colspan="6">
237 <td colspan="6">
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
239 data-label-text-locked="${_('select versions to show changes')}"
239 data-label-text-locked="${_('select versions to show changes')}"
240 data-label-text-diff="${_('show changes between versions')}"
240 data-label-text-diff="${_('show changes between versions')}"
241 data-label-text-show="${_('show pull request for this version')}"
241 data-label-text-show="${_('show pull request for this version')}"
242 >
242 >
243 ${_('select versions to show changes')}
243 ${_('select versions to show changes')}
244 </button>
244 </button>
245 </td>
245 </td>
246 </tr>
246 </tr>
247 </table>
247 </table>
248 % else:
248 % else:
249 <div class="input">
249 <div class="input">
250 ${_('Pull request versions not available')}.
250 ${_('Pull request versions not available')}.
251 </div>
251 </div>
252 % endif
252 % endif
253 </div>
253 </div>
254 </div>
254 </div>
255
255
256 <div id="pr-save" class="field" style="display: none;">
256 <div id="pr-save" class="field" style="display: none;">
257 <div class="label-summary"></div>
257 <div class="label-summary"></div>
258 <div class="input">
258 <div class="input">
259 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
259 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
260 </div>
260 </div>
261 </div>
261 </div>
262 </div>
262 </div>
263 </div>
263 </div>
264 <div>
264 <div>
265 ## AUTHOR
265 ## AUTHOR
266 <div class="reviewers-title block-right">
266 <div class="reviewers-title block-right">
267 <div class="pr-details-title">
267 <div class="pr-details-title">
268 ${_('Author of this pull request')}
268 ${_('Author of this pull request')}
269 </div>
269 </div>
270 </div>
270 </div>
271 <div class="block-right pr-details-content reviewers">
271 <div class="block-right pr-details-content reviewers">
272 <ul class="group_members">
272 <ul class="group_members">
273 <li>
273 <li>
274 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
274 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
275 </li>
275 </li>
276 </ul>
276 </ul>
277 </div>
277 </div>
278
278
279 ## REVIEW RULES
279 ## REVIEW RULES
280 <div id="review_rules" style="display: none" class="reviewers-title block-right">
280 <div id="review_rules" style="display: none" class="reviewers-title block-right">
281 <div class="pr-details-title">
281 <div class="pr-details-title">
282 ${_('Reviewer rules')}
282 ${_('Reviewer rules')}
283 %if c.allowed_to_update:
283 %if c.allowed_to_update:
284 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
284 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
285 %endif
285 %endif
286 </div>
286 </div>
287 <div class="pr-reviewer-rules">
287 <div class="pr-reviewer-rules">
288 ## review rules will be appended here, by default reviewers logic
288 ## review rules will be appended here, by default reviewers logic
289 </div>
289 </div>
290 <input id="review_data" type="hidden" name="review_data" value="">
290 <input id="review_data" type="hidden" name="review_data" value="">
291 </div>
291 </div>
292
292
293 ## REVIEWERS
293 ## REVIEWERS
294 <div class="reviewers-title block-right">
294 <div class="reviewers-title block-right">
295 <div class="pr-details-title">
295 <div class="pr-details-title">
296 ${_('Pull request reviewers')}
296 ${_('Pull request reviewers')}
297 %if c.allowed_to_update:
297 %if c.allowed_to_update:
298 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
298 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
299 %endif
299 %endif
300 </div>
300 </div>
301 </div>
301 </div>
302 <div id="reviewers" class="block-right pr-details-content reviewers">
302 <div id="reviewers" class="block-right pr-details-content reviewers">
303
303
304 ## members redering block
304 ## members redering block
305 <input type="hidden" name="__start__" value="review_members:sequence">
305 <input type="hidden" name="__start__" value="review_members:sequence">
306 <ul id="review_members" class="group_members">
306 <ul id="review_members" class="group_members">
307
307
308 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
308 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
309 <script>
309 <script>
310 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
310 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
311 var status = "${(status[0][1].status if status else 'not_reviewed')}";
311 var status = "${(status[0][1].status if status else 'not_reviewed')}";
312 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
312 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
313 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
313 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
314
314
315 var entry = renderTemplate('reviewMemberEntry', {
315 var entry = renderTemplate('reviewMemberEntry', {
316 'member': member,
316 'member': member,
317 'mandatory': member.mandatory,
317 'mandatory': member.mandatory,
318 'reasons': member.reasons,
318 'reasons': member.reasons,
319 'allowed_to_update': allowed_to_update,
319 'allowed_to_update': allowed_to_update,
320 'review_status': status,
320 'review_status': status,
321 'review_status_label': status_lbl,
321 'review_status_label': status_lbl,
322 'user_group': member.user_group,
322 'user_group': member.user_group,
323 'create': false
323 'create': false
324 });
324 });
325 $('#review_members').append(entry)
325 $('#review_members').append(entry)
326 </script>
326 </script>
327
327
328 % endfor
328 % endfor
329
329
330 </ul>
330 </ul>
331
331
332 <input type="hidden" name="__end__" value="review_members:sequence">
332 <input type="hidden" name="__end__" value="review_members:sequence">
333 ## end members redering block
333 ## end members redering block
334
334
335 %if not c.pull_request.is_closed():
335 %if not c.pull_request.is_closed():
336 <div id="add_reviewer" class="ac" style="display: none;">
336 <div id="add_reviewer" class="ac" style="display: none;">
337 %if c.allowed_to_update:
337 %if c.allowed_to_update:
338 % if not c.forbid_adding_reviewers:
338 % if not c.forbid_adding_reviewers:
339 <div id="add_reviewer_input" class="reviewer_ac">
339 <div id="add_reviewer_input" class="reviewer_ac">
340 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
340 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
341 <div id="reviewers_container"></div>
341 <div id="reviewers_container"></div>
342 </div>
342 </div>
343 % endif
343 % endif
344 <div class="pull-right">
344 <div class="pull-right">
345 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
345 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
346 </div>
346 </div>
347 %endif
347 %endif
348 </div>
348 </div>
349 %endif
349 %endif
350 </div>
350 </div>
351 </div>
351 </div>
352 </div>
352 </div>
353 <div class="box">
353 <div class="box">
354 ##DIFF
354 ##DIFF
355 <div class="table" >
355 <div class="table" >
356 <div id="changeset_compare_view_content">
356 <div id="changeset_compare_view_content">
357 ##CS
357 ##CS
358 % if c.missing_requirements:
358 % if c.missing_requirements:
359 <div class="box">
359 <div class="box">
360 <div class="alert alert-warning">
360 <div class="alert alert-warning">
361 <div>
361 <div>
362 <strong>${_('Missing requirements:')}</strong>
362 <strong>${_('Missing requirements:')}</strong>
363 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
363 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
364 </div>
364 </div>
365 </div>
365 </div>
366 </div>
366 </div>
367 % elif c.missing_commits:
367 % elif c.missing_commits:
368 <div class="box">
368 <div class="box">
369 <div class="alert alert-warning">
369 <div class="alert alert-warning">
370 <div>
370 <div>
371 <strong>${_('Missing commits')}:</strong>
371 <strong>${_('Missing commits')}:</strong>
372 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
372 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
373 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
373 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
374 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
374 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
375 </div>
375 </div>
376 </div>
376 </div>
377 </div>
377 </div>
378 % endif
378 % endif
379
379
380 <div class="compare_view_commits_title">
380 <div class="compare_view_commits_title">
381 % if not c.compare_mode:
381 % if not c.compare_mode:
382
382
383 % if c.at_version_pos:
383 % if c.at_version_pos:
384 <h4>
384 <h4>
385 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
385 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
386 </h4>
386 </h4>
387 % endif
387 % endif
388
388
389 <div class="pull-left">
389 <div class="pull-left">
390 <div class="btn-group">
390 <div class="btn-group">
391 <a
391 <a
392 class="btn"
392 class="btn"
393 href="#"
393 href="#"
394 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
394 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
395 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
395 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
396 </a>
396 </a>
397 <a
397 <a
398 class="btn"
398 class="btn"
399 href="#"
399 href="#"
400 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
400 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
401 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
401 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
402 </a>
402 </a>
403 </div>
403 </div>
404 </div>
404 </div>
405
405
406 <div class="pull-right">
406 <div class="pull-right">
407 % if c.allowed_to_update and not c.pull_request.is_closed():
407 % if c.allowed_to_update and not c.pull_request.is_closed():
408 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
408 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
409 % else:
409 % else:
410 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
410 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
411 % endif
411 % endif
412
412
413 </div>
413 </div>
414 % endif
414 % endif
415 </div>
415 </div>
416
416
417 % if not c.missing_commits:
417 % if not c.missing_commits:
418 % if c.compare_mode:
418 % if c.compare_mode:
419 % if c.at_version:
419 % if c.at_version:
420 <h4>
420 <h4>
421 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
421 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
422 </h4>
422 </h4>
423
423
424 <div class="subtitle-compare">
424 <div class="subtitle-compare">
425 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
425 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
426 </div>
426 </div>
427
427
428 <div class="container">
428 <div class="container">
429 <table class="rctable compare_view_commits">
429 <table class="rctable compare_view_commits">
430 <tr>
430 <tr>
431 <th></th>
431 <th></th>
432 <th>${_('Time')}</th>
432 <th>${_('Time')}</th>
433 <th>${_('Author')}</th>
433 <th>${_('Author')}</th>
434 <th>${_('Commit')}</th>
434 <th>${_('Commit')}</th>
435 <th></th>
435 <th></th>
436 <th>${_('Description')}</th>
436 <th>${_('Description')}</th>
437 </tr>
437 </tr>
438
438
439 % for c_type, commit in c.commit_changes:
439 % for c_type, commit in c.commit_changes:
440 % if c_type in ['a', 'r']:
440 % if c_type in ['a', 'r']:
441 <%
441 <%
442 if c_type == 'a':
442 if c_type == 'a':
443 cc_title = _('Commit added in displayed changes')
443 cc_title = _('Commit added in displayed changes')
444 elif c_type == 'r':
444 elif c_type == 'r':
445 cc_title = _('Commit removed in displayed changes')
445 cc_title = _('Commit removed in displayed changes')
446 else:
446 else:
447 cc_title = ''
447 cc_title = ''
448 %>
448 %>
449 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
449 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
450 <td>
450 <td>
451 <div class="commit-change-indicator color-${c_type}-border">
451 <div class="commit-change-indicator color-${c_type}-border">
452 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
452 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
453 ${c_type.upper()}
453 ${c_type.upper()}
454 </div>
454 </div>
455 </div>
455 </div>
456 </td>
456 </td>
457 <td class="td-time">
457 <td class="td-time">
458 ${h.age_component(commit.date)}
458 ${h.age_component(commit.date)}
459 </td>
459 </td>
460 <td class="td-user">
460 <td class="td-user">
461 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
461 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
462 </td>
462 </td>
463 <td class="td-hash">
463 <td class="td-hash">
464 <code>
464 <code>
465 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
465 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
466 r${commit.idx}:${h.short_id(commit.raw_id)}
466 r${commit.idx}:${h.short_id(commit.raw_id)}
467 </a>
467 </a>
468 ${h.hidden('revisions', commit.raw_id)}
468 ${h.hidden('revisions', commit.raw_id)}
469 </code>
469 </code>
470 </td>
470 </td>
471 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
471 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
472 <i class="icon-expand-linked"></i>
472 <i class="icon-expand-linked"></i>
473 </td>
473 </td>
474 <td class="mid td-description">
474 <td class="mid td-description">
475 <div class="log-container truncate-wrap">
475 <div class="log-container truncate-wrap">
476 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
476 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
477 </div>
477 </div>
478 </td>
478 </td>
479 </tr>
479 </tr>
480 % endif
480 % endif
481 % endfor
481 % endfor
482 </table>
482 </table>
483 </div>
483 </div>
484
484
485 % endif
485 % endif
486
486
487 % else:
487 % else:
488 <%include file="/compare/compare_commits.mako" />
488 <%include file="/compare/compare_commits.mako" />
489 % endif
489 % endif
490
490
491 <div class="cs_files">
491 <div class="cs_files">
492 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
492 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
493 % if c.at_version:
493 % if c.at_version:
494 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
494 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
495 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
495 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
496 % else:
496 % else:
497 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
497 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
498 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
498 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
499 % endif
499 % endif
500
500
501 <%
501 <%
502 pr_menu_data = {
502 pr_menu_data = {
503 'outdated_comm_count_ver': outdated_comm_count_ver
503 'outdated_comm_count_ver': outdated_comm_count_ver
504 }
504 }
505 %>
505 %>
506
506
507 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
507 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
508
508
509 % if c.range_diff_on:
509 % if c.range_diff_on:
510 % for commit in c.commit_ranges:
510 % for commit in c.commit_ranges:
511 ${cbdiffs.render_diffset(
511 ${cbdiffs.render_diffset(
512 c.changes[commit.raw_id],
512 c.changes[commit.raw_id],
513 commit=commit, use_comments=True,
513 commit=commit, use_comments=True,
514 collapse_when_files_over=5,
514 collapse_when_files_over=5,
515 disable_new_comments=True,
515 disable_new_comments=True,
516 deleted_files_comments=c.deleted_files_comments,
516 deleted_files_comments=c.deleted_files_comments,
517 inline_comments=c.inline_comments,
517 inline_comments=c.inline_comments,
518 pull_request_menu=pr_menu_data)}
518 pull_request_menu=pr_menu_data)}
519 % endfor
519 % endfor
520 % else:
520 % else:
521 ${cbdiffs.render_diffset(
521 ${cbdiffs.render_diffset(
522 c.diffset, use_comments=True,
522 c.diffset, use_comments=True,
523 collapse_when_files_over=30,
523 collapse_when_files_over=30,
524 disable_new_comments=not c.allowed_to_comment,
524 disable_new_comments=not c.allowed_to_comment,
525 deleted_files_comments=c.deleted_files_comments,
525 deleted_files_comments=c.deleted_files_comments,
526 inline_comments=c.inline_comments,
526 inline_comments=c.inline_comments,
527 pull_request_menu=pr_menu_data)}
527 pull_request_menu=pr_menu_data)}
528 % endif
528 % endif
529
529
530 </div>
530 </div>
531 % else:
531 % else:
532 ## skipping commits we need to clear the view for missing commits
532 ## skipping commits we need to clear the view for missing commits
533 <div style="clear:both;"></div>
533 <div style="clear:both;"></div>
534 % endif
534 % endif
535
535
536 </div>
536 </div>
537 </div>
537 </div>
538
538
539 ## template for inline comment form
539 ## template for inline comment form
540 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
540 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
541
541
542 ## comments heading with count
542 ## comments heading with count
543 <div class="comments-heading">
543 <div class="comments-heading">
544 <i class="icon-comment"></i>
544 <i class="icon-comment"></i>
545 ${_('Comments')} ${len(c.comments)}
545 ${_('Comments')} ${len(c.comments)}
546 </div>
546 </div>
547
547
548 ## render general comments
548 ## render general comments
549 <div id="comment-tr-show">
549 <div id="comment-tr-show">
550 % if general_outdated_comm_count_ver:
550 % if general_outdated_comm_count_ver:
551 <div class="info-box">
551 <div class="info-box">
552 % if general_outdated_comm_count_ver == 1:
552 % if general_outdated_comm_count_ver == 1:
553 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
553 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
554 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
554 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
555 % else:
555 % else:
556 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
556 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
557 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
557 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
558 % endif
558 % endif
559 </div>
559 </div>
560 % endif
560 % endif
561 </div>
561 </div>
562
562
563 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
563 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
564
564
565 % if not c.pull_request.is_closed():
565 % if not c.pull_request.is_closed():
566 ## merge status, and merge action
566 ## merge status, and merge action
567 <div class="pull-request-merge">
567 <div class="pull-request-merge">
568 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
568 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
569 </div>
569 </div>
570
570
571 ## main comment form and it status
571 ## main comment form and it status
572 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
572 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
573 pull_request_id=c.pull_request.pull_request_id),
573 pull_request_id=c.pull_request.pull_request_id),
574 c.pull_request_review_status,
574 c.pull_request_review_status,
575 is_pull_request=True, change_status=c.allowed_to_change_status)}
575 is_pull_request=True, change_status=c.allowed_to_change_status)}
576 %endif
576 %endif
577
577
578 <script type="text/javascript">
578 <script type="text/javascript">
579 if (location.hash) {
579 if (location.hash) {
580 var result = splitDelimitedHash(location.hash);
580 var result = splitDelimitedHash(location.hash);
581 var line = $('html').find(result.loc);
581 var line = $('html').find(result.loc);
582 // show hidden comments if we use location.hash
582 // show hidden comments if we use location.hash
583 if (line.hasClass('comment-general')) {
583 if (line.hasClass('comment-general')) {
584 $(line).show();
584 $(line).show();
585 } else if (line.hasClass('comment-inline')) {
585 } else if (line.hasClass('comment-inline')) {
586 $(line).show();
586 $(line).show();
587 var $cb = $(line).closest('.cb');
587 var $cb = $(line).closest('.cb');
588 $cb.removeClass('cb-collapsed')
588 $cb.removeClass('cb-collapsed')
589 }
589 }
590 if (line.length > 0){
590 if (line.length > 0){
591 offsetScroll(line, 70);
591 offsetScroll(line, 70);
592 }
592 }
593 }
593 }
594
594
595 versionController = new VersionController();
595 versionController = new VersionController();
596 versionController.init();
596 versionController.init();
597
597
598 reviewersController = new ReviewersController();
598 reviewersController = new ReviewersController();
599 commitsController = new CommitsController();
599 commitsController = new CommitsController();
600
600
601 $(function(){
601 $(function(){
602
602
603 // custom code mirror
603 // custom code mirror
604 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
604 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
605
605
606 var PRDetails = {
606 var PRDetails = {
607 editButton: $('#open_edit_pullrequest'),
607 editButton: $('#open_edit_pullrequest'),
608 closeButton: $('#close_edit_pullrequest'),
608 closeButton: $('#close_edit_pullrequest'),
609 deleteButton: $('#delete_pullrequest'),
609 deleteButton: $('#delete_pullrequest'),
610 viewFields: $('#pr-desc, #pr-title'),
610 viewFields: $('#pr-desc, #pr-title'),
611 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
611 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
612
612
613 init: function() {
613 init: function() {
614 var that = this;
614 var that = this;
615 this.editButton.on('click', function(e) { that.edit(); });
615 this.editButton.on('click', function(e) { that.edit(); });
616 this.closeButton.on('click', function(e) { that.view(); });
616 this.closeButton.on('click', function(e) { that.view(); });
617 },
617 },
618
618
619 edit: function(event) {
619 edit: function(event) {
620 this.viewFields.hide();
620 this.viewFields.hide();
621 this.editButton.hide();
621 this.editButton.hide();
622 this.deleteButton.hide();
622 this.deleteButton.hide();
623 this.closeButton.show();
623 this.closeButton.show();
624 this.editFields.show();
624 this.editFields.show();
625 codeMirrorInstance.refresh();
625 codeMirrorInstance.refresh();
626 },
626 },
627
627
628 view: function(event) {
628 view: function(event) {
629 this.editButton.show();
629 this.editButton.show();
630 this.deleteButton.show();
630 this.deleteButton.show();
631 this.editFields.hide();
631 this.editFields.hide();
632 this.closeButton.hide();
632 this.closeButton.hide();
633 this.viewFields.show();
633 this.viewFields.show();
634 }
634 }
635 };
635 };
636
636
637 var ReviewersPanel = {
637 var ReviewersPanel = {
638 editButton: $('#open_edit_reviewers'),
638 editButton: $('#open_edit_reviewers'),
639 closeButton: $('#close_edit_reviewers'),
639 closeButton: $('#close_edit_reviewers'),
640 addButton: $('#add_reviewer'),
640 addButton: $('#add_reviewer'),
641 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
641 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
642
642
643 init: function() {
643 init: function() {
644 var self = this;
644 var self = this;
645 this.editButton.on('click', function(e) { self.edit(); });
645 this.editButton.on('click', function(e) { self.edit(); });
646 this.closeButton.on('click', function(e) { self.close(); });
646 this.closeButton.on('click', function(e) { self.close(); });
647 },
647 },
648
648
649 edit: function(event) {
649 edit: function(event) {
650 this.editButton.hide();
650 this.editButton.hide();
651 this.closeButton.show();
651 this.closeButton.show();
652 this.addButton.show();
652 this.addButton.show();
653 this.removeButtons.css('visibility', 'visible');
653 this.removeButtons.css('visibility', 'visible');
654 // review rules
654 // review rules
655 reviewersController.loadReviewRules(
655 reviewersController.loadReviewRules(
656 ${c.pull_request.reviewer_data_json | n});
656 ${c.pull_request.reviewer_data_json | n});
657 },
657 },
658
658
659 close: function(event) {
659 close: function(event) {
660 this.editButton.show();
660 this.editButton.show();
661 this.closeButton.hide();
661 this.closeButton.hide();
662 this.addButton.hide();
662 this.addButton.hide();
663 this.removeButtons.css('visibility', 'hidden');
663 this.removeButtons.css('visibility', 'hidden');
664 // hide review rules
664 // hide review rules
665 reviewersController.hideReviewRules()
665 reviewersController.hideReviewRules()
666 }
666 }
667 };
667 };
668
668
669 PRDetails.init();
669 PRDetails.init();
670 ReviewersPanel.init();
670 ReviewersPanel.init();
671
671
672 showOutdated = function(self){
672 showOutdated = function(self){
673 $('.comment-inline.comment-outdated').show();
673 $('.comment-inline.comment-outdated').show();
674 $('.filediff-outdated').show();
674 $('.filediff-outdated').show();
675 $('.showOutdatedComments').hide();
675 $('.showOutdatedComments').hide();
676 $('.hideOutdatedComments').show();
676 $('.hideOutdatedComments').show();
677 };
677 };
678
678
679 hideOutdated = function(self){
679 hideOutdated = function(self){
680 $('.comment-inline.comment-outdated').hide();
680 $('.comment-inline.comment-outdated').hide();
681 $('.filediff-outdated').hide();
681 $('.filediff-outdated').hide();
682 $('.hideOutdatedComments').hide();
682 $('.hideOutdatedComments').hide();
683 $('.showOutdatedComments').show();
683 $('.showOutdatedComments').show();
684 };
684 };
685
685
686 refreshMergeChecks = function(){
686 refreshMergeChecks = function(){
687 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
687 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
688 $('.pull-request-merge').css('opacity', 0.3);
688 $('.pull-request-merge').css('opacity', 0.3);
689 $('.action-buttons-extra').css('opacity', 0.3);
689 $('.action-buttons-extra').css('opacity', 0.3);
690
690
691 $('.pull-request-merge').load(
691 $('.pull-request-merge').load(
692 loadUrl, function() {
692 loadUrl, function() {
693 $('.pull-request-merge').css('opacity', 1);
693 $('.pull-request-merge').css('opacity', 1);
694
694
695 $('.action-buttons-extra').css('opacity', 1);
695 $('.action-buttons-extra').css('opacity', 1);
696 }
696 }
697 );
697 );
698 };
698 };
699
699
700 closePullRequest = function (status) {
700 closePullRequest = function (status) {
701 // inject closing flag
701 // inject closing flag
702 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
702 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
703 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
703 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
704 $(generalCommentForm.submitForm).submit();
704 $(generalCommentForm.submitForm).submit();
705 };
705 };
706
706
707 $('#show-outdated-comments').on('click', function(e){
707 $('#show-outdated-comments').on('click', function(e){
708 var button = $(this);
708 var button = $(this);
709 var outdated = $('.comment-outdated');
709 var outdated = $('.comment-outdated');
710
710
711 if (button.html() === "(Show)") {
711 if (button.html() === "(Show)") {
712 button.html("(Hide)");
712 button.html("(Hide)");
713 outdated.show();
713 outdated.show();
714 } else {
714 } else {
715 button.html("(Show)");
715 button.html("(Show)");
716 outdated.hide();
716 outdated.hide();
717 }
717 }
718 });
718 });
719
719
720 $('.show-inline-comments').on('change', function(e){
720 $('.show-inline-comments').on('change', function(e){
721 var show = 'none';
721 var show = 'none';
722 var target = e.currentTarget;
722 var target = e.currentTarget;
723 if(target.checked){
723 if(target.checked){
724 show = ''
724 show = ''
725 }
725 }
726 var boxid = $(target).attr('id_for');
726 var boxid = $(target).attr('id_for');
727 var comments = $('#{0} .inline-comments'.format(boxid));
727 var comments = $('#{0} .inline-comments'.format(boxid));
728 var fn_display = function(idx){
728 var fn_display = function(idx){
729 $(this).css('display', show);
729 $(this).css('display', show);
730 };
730 };
731 $(comments).each(fn_display);
731 $(comments).each(fn_display);
732 var btns = $('#{0} .inline-comments-button'.format(boxid));
732 var btns = $('#{0} .inline-comments-button'.format(boxid));
733 $(btns).each(fn_display);
733 $(btns).each(fn_display);
734 });
734 });
735
735
736 $('#merge_pull_request_form').submit(function() {
736 $('#merge_pull_request_form').submit(function() {
737 if (!$('#merge_pull_request').attr('disabled')) {
737 if (!$('#merge_pull_request').attr('disabled')) {
738 $('#merge_pull_request').attr('disabled', 'disabled');
738 $('#merge_pull_request').attr('disabled', 'disabled');
739 }
739 }
740 return true;
740 return true;
741 });
741 });
742
742
743 $('#edit_pull_request').on('click', function(e){
743 $('#edit_pull_request').on('click', function(e){
744 var title = $('#pr-title-input').val();
744 var title = $('#pr-title-input').val();
745 var description = codeMirrorInstance.getValue();
745 var description = codeMirrorInstance.getValue();
746 var renderer = $('#pr-renderer-input').val();
746 var renderer = $('#pr-renderer-input').val();
747 editPullRequest(
747 editPullRequest(
748 "${c.repo_name}", "${c.pull_request.pull_request_id}",
748 "${c.repo_name}", "${c.pull_request.pull_request_id}",
749 title, description, renderer);
749 title, description, renderer);
750 });
750 });
751
751
752 $('#update_pull_request').on('click', function(e){
752 $('#update_pull_request').on('click', function(e){
753 $(this).attr('disabled', 'disabled');
753 $(this).attr('disabled', 'disabled');
754 $(this).addClass('disabled');
754 $(this).addClass('disabled');
755 $(this).html(_gettext('Saving...'));
755 $(this).html(_gettext('Saving...'));
756 reviewersController.updateReviewers(
756 reviewersController.updateReviewers(
757 "${c.repo_name}", "${c.pull_request.pull_request_id}");
757 "${c.repo_name}", "${c.pull_request.pull_request_id}");
758 });
758 });
759
759
760 $('#update_commits').on('click', function(e){
760 $('#update_commits').on('click', function(e){
761 var isDisabled = !$(e.currentTarget).attr('disabled');
761 var isDisabled = !$(e.currentTarget).attr('disabled');
762 $(e.currentTarget).attr('disabled', 'disabled');
762 $(e.currentTarget).attr('disabled', 'disabled');
763 $(e.currentTarget).addClass('disabled');
763 $(e.currentTarget).addClass('disabled');
764 $(e.currentTarget).removeClass('btn-primary');
764 $(e.currentTarget).removeClass('btn-primary');
765 $(e.currentTarget).text(_gettext('Updating...'));
765 $(e.currentTarget).text(_gettext('Updating...'));
766 if(isDisabled){
766 if(isDisabled){
767 updateCommits(
767 updateCommits(
768 "${c.repo_name}", "${c.pull_request.pull_request_id}");
768 "${c.repo_name}", "${c.pull_request.pull_request_id}");
769 }
769 }
770 });
770 });
771 // fixing issue with caches on firefox
771 // fixing issue with caches on firefox
772 $('#update_commits').removeAttr("disabled");
772 $('#update_commits').removeAttr("disabled");
773
773
774 $('.show-inline-comments').on('click', function(e){
774 $('.show-inline-comments').on('click', function(e){
775 var boxid = $(this).attr('data-comment-id');
775 var boxid = $(this).attr('data-comment-id');
776 var button = $(this);
776 var button = $(this);
777
777
778 if(button.hasClass("comments-visible")) {
778 if(button.hasClass("comments-visible")) {
779 $('#{0} .inline-comments'.format(boxid)).each(function(index){
779 $('#{0} .inline-comments'.format(boxid)).each(function(index){
780 $(this).hide();
780 $(this).hide();
781 });
781 });
782 button.removeClass("comments-visible");
782 button.removeClass("comments-visible");
783 } else {
783 } else {
784 $('#{0} .inline-comments'.format(boxid)).each(function(index){
784 $('#{0} .inline-comments'.format(boxid)).each(function(index){
785 $(this).show();
785 $(this).show();
786 });
786 });
787 button.addClass("comments-visible");
787 button.addClass("comments-visible");
788 }
788 }
789 });
789 });
790
790
791 // register submit callback on commentForm form to track TODOs
791 // register submit callback on commentForm form to track TODOs
792 window.commentFormGlobalSubmitSuccessCallback = function(){
792 window.commentFormGlobalSubmitSuccessCallback = function(){
793 refreshMergeChecks();
793 refreshMergeChecks();
794 };
794 };
795
795
796 ReviewerAutoComplete('#user');
796 ReviewerAutoComplete('#user');
797
797
798 })
798 })
799 </script>
799 </script>
800
800
801 </div>
801 </div>
802 </div>
802 </div>
803
803
804 </%def>
804 </%def>
General Comments 0
You need to be logged in to leave comments. Login now