##// END OF EJS Templates
drafts: support draft in commits view...
milka -
r4555:57bb7bdc default
parent child Browse files
Show More
@@ -1,816 +1,852 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 import logging
21 import logging
22 import collections
22 import collections
23
23
24 from pyramid.httpexceptions import (
24 from pyramid.httpexceptions import (
25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
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, channelstream
34 from rhodecode.lib import diffs, codeblocks, channelstream
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
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, CommentVersionMismatch
42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
43 import rhodecode.lib.helpers as h
43 import rhodecode.lib.helpers as h
44 from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
44 from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
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 ChangesetCommentHistory
49 ChangesetCommentHistory
50 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.comment import CommentsModel
51 from rhodecode.model.comment import CommentsModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 def _update_with_GET(params, request):
58 def _update_with_GET(params, request):
59 for k in ['diff1', 'diff2', 'diff']:
59 for k in ['diff1', 'diff2', 'diff']:
60 params[k] += request.GET.getall(k)
60 params[k] += request.GET.getall(k)
61
61
62
62
63 class RepoCommitsView(RepoAppView):
63 class RepoCommitsView(RepoAppView):
64 def load_default_context(self):
64 def load_default_context(self):
65 c = self._get_local_tmpl_context(include_app_defaults=True)
65 c = self._get_local_tmpl_context(include_app_defaults=True)
66 c.rhodecode_repo = self.rhodecode_vcs_repo
66 c.rhodecode_repo = self.rhodecode_vcs_repo
67
67
68 return c
68 return c
69
69
70 def _is_diff_cache_enabled(self, target_repo):
70 def _is_diff_cache_enabled(self, target_repo):
71 caching_enabled = self._get_general_setting(
71 caching_enabled = self._get_general_setting(
72 target_repo, 'rhodecode_diff_cache')
72 target_repo, 'rhodecode_diff_cache')
73 log.debug('Diff caching enabled: %s', caching_enabled)
73 log.debug('Diff caching enabled: %s', caching_enabled)
74 return caching_enabled
74 return caching_enabled
75
75
76 def _commit(self, commit_id_range, method):
76 def _commit(self, commit_id_range, method):
77 _ = self.request.translate
77 _ = self.request.translate
78 c = self.load_default_context()
78 c = self.load_default_context()
79 c.fulldiff = self.request.GET.get('fulldiff')
79 c.fulldiff = self.request.GET.get('fulldiff')
80 redirect_to_combined = str2bool(self.request.GET.get('redirect_combined'))
80 redirect_to_combined = str2bool(self.request.GET.get('redirect_combined'))
81
81
82 # fetch global flags of ignore ws or context lines
82 # fetch global flags of ignore ws or context lines
83 diff_context = get_diff_context(self.request)
83 diff_context = get_diff_context(self.request)
84 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
84 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
85
85
86 # diff_limit will cut off the whole diff if the limit is applied
86 # diff_limit will cut off the whole diff if the limit is applied
87 # otherwise it will just hide the big files from the front-end
87 # otherwise it will just hide the big files from the front-end
88 diff_limit = c.visual.cut_off_limit_diff
88 diff_limit = c.visual.cut_off_limit_diff
89 file_limit = c.visual.cut_off_limit_file
89 file_limit = c.visual.cut_off_limit_file
90
90
91 # get ranges of commit ids if preset
91 # get ranges of commit ids if preset
92 commit_range = commit_id_range.split('...')[:2]
92 commit_range = commit_id_range.split('...')[:2]
93
93
94 try:
94 try:
95 pre_load = ['affected_files', 'author', 'branch', 'date',
95 pre_load = ['affected_files', 'author', 'branch', 'date',
96 'message', 'parents']
96 'message', 'parents']
97 if self.rhodecode_vcs_repo.alias == 'hg':
97 if self.rhodecode_vcs_repo.alias == 'hg':
98 pre_load += ['hidden', 'obsolete', 'phase']
98 pre_load += ['hidden', 'obsolete', 'phase']
99
99
100 if len(commit_range) == 2:
100 if len(commit_range) == 2:
101 commits = self.rhodecode_vcs_repo.get_commits(
101 commits = self.rhodecode_vcs_repo.get_commits(
102 start_id=commit_range[0], end_id=commit_range[1],
102 start_id=commit_range[0], end_id=commit_range[1],
103 pre_load=pre_load, translate_tags=False)
103 pre_load=pre_load, translate_tags=False)
104 commits = list(commits)
104 commits = list(commits)
105 else:
105 else:
106 commits = [self.rhodecode_vcs_repo.get_commit(
106 commits = [self.rhodecode_vcs_repo.get_commit(
107 commit_id=commit_id_range, pre_load=pre_load)]
107 commit_id=commit_id_range, pre_load=pre_load)]
108
108
109 c.commit_ranges = commits
109 c.commit_ranges = commits
110 if not c.commit_ranges:
110 if not c.commit_ranges:
111 raise RepositoryError('The commit range returned an empty result')
111 raise RepositoryError('The commit range returned an empty result')
112 except CommitDoesNotExistError as e:
112 except CommitDoesNotExistError as e:
113 msg = _('No such commit exists. Org exception: `{}`').format(e)
113 msg = _('No such commit exists. Org exception: `{}`').format(e)
114 h.flash(msg, category='error')
114 h.flash(msg, category='error')
115 raise HTTPNotFound()
115 raise HTTPNotFound()
116 except Exception:
116 except Exception:
117 log.exception("General failure")
117 log.exception("General failure")
118 raise HTTPNotFound()
118 raise HTTPNotFound()
119 single_commit = len(c.commit_ranges) == 1
119 single_commit = len(c.commit_ranges) == 1
120
120
121 if redirect_to_combined and not single_commit:
121 if redirect_to_combined and not single_commit:
122 source_ref = getattr(c.commit_ranges[0].parents[0]
122 source_ref = getattr(c.commit_ranges[0].parents[0]
123 if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id')
123 if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id')
124 target_ref = c.commit_ranges[-1].raw_id
124 target_ref = c.commit_ranges[-1].raw_id
125 next_url = h.route_path(
125 next_url = h.route_path(
126 'repo_compare',
126 'repo_compare',
127 repo_name=c.repo_name,
127 repo_name=c.repo_name,
128 source_ref_type='rev',
128 source_ref_type='rev',
129 source_ref=source_ref,
129 source_ref=source_ref,
130 target_ref_type='rev',
130 target_ref_type='rev',
131 target_ref=target_ref)
131 target_ref=target_ref)
132 raise HTTPFound(next_url)
132 raise HTTPFound(next_url)
133
133
134 c.changes = OrderedDict()
134 c.changes = OrderedDict()
135 c.lines_added = 0
135 c.lines_added = 0
136 c.lines_deleted = 0
136 c.lines_deleted = 0
137
137
138 # auto collapse if we have more than limit
138 # auto collapse if we have more than limit
139 collapse_limit = diffs.DiffProcessor._collapse_commits_over
139 collapse_limit = diffs.DiffProcessor._collapse_commits_over
140 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
140 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
141
141
142 c.commit_statuses = ChangesetStatus.STATUSES
142 c.commit_statuses = ChangesetStatus.STATUSES
143 c.inline_comments = []
143 c.inline_comments = []
144 c.files = []
144 c.files = []
145
145
146 c.comments = []
146 c.comments = []
147 c.unresolved_comments = []
147 c.unresolved_comments = []
148 c.resolved_comments = []
148 c.resolved_comments = []
149
149
150 # Single commit
150 # Single commit
151 if single_commit:
151 if single_commit:
152 commit = c.commit_ranges[0]
152 commit = c.commit_ranges[0]
153 c.comments = CommentsModel().get_comments(
153 c.comments = CommentsModel().get_comments(
154 self.db_repo.repo_id,
154 self.db_repo.repo_id,
155 revision=commit.raw_id)
155 revision=commit.raw_id)
156
156
157 # comments from PR
157 # comments from PR
158 statuses = ChangesetStatusModel().get_statuses(
158 statuses = ChangesetStatusModel().get_statuses(
159 self.db_repo.repo_id, commit.raw_id,
159 self.db_repo.repo_id, commit.raw_id,
160 with_revisions=True)
160 with_revisions=True)
161
161
162 prs = set()
162 prs = set()
163 reviewers = list()
163 reviewers = list()
164 reviewers_duplicates = set() # to not have duplicates from multiple votes
164 reviewers_duplicates = set() # to not have duplicates from multiple votes
165 for c_status in statuses:
165 for c_status in statuses:
166
166
167 # extract associated pull-requests from votes
167 # extract associated pull-requests from votes
168 if c_status.pull_request:
168 if c_status.pull_request:
169 prs.add(c_status.pull_request)
169 prs.add(c_status.pull_request)
170
170
171 # extract reviewers
171 # extract reviewers
172 _user_id = c_status.author.user_id
172 _user_id = c_status.author.user_id
173 if _user_id not in reviewers_duplicates:
173 if _user_id not in reviewers_duplicates:
174 reviewers.append(
174 reviewers.append(
175 StrictAttributeDict({
175 StrictAttributeDict({
176 'user': c_status.author,
176 'user': c_status.author,
177
177
178 # fake attributed for commit, page that we don't have
178 # fake attributed for commit, page that we don't have
179 # but we share the display with PR page
179 # but we share the display with PR page
180 'mandatory': False,
180 'mandatory': False,
181 'reasons': [],
181 'reasons': [],
182 'rule_user_group_data': lambda: None
182 'rule_user_group_data': lambda: None
183 })
183 })
184 )
184 )
185 reviewers_duplicates.add(_user_id)
185 reviewers_duplicates.add(_user_id)
186
186
187 c.reviewers_count = len(reviewers)
187 c.reviewers_count = len(reviewers)
188 c.observers_count = 0
188 c.observers_count = 0
189
189
190 # from associated statuses, check the pull requests, and
190 # from associated statuses, check the pull requests, and
191 # show comments from them
191 # show comments from them
192 for pr in prs:
192 for pr in prs:
193 c.comments.extend(pr.comments)
193 c.comments.extend(pr.comments)
194
194
195 c.unresolved_comments = CommentsModel()\
195 c.unresolved_comments = CommentsModel()\
196 .get_commit_unresolved_todos(commit.raw_id)
196 .get_commit_unresolved_todos(commit.raw_id)
197 c.resolved_comments = CommentsModel()\
197 c.resolved_comments = CommentsModel()\
198 .get_commit_resolved_todos(commit.raw_id)
198 .get_commit_resolved_todos(commit.raw_id)
199
199
200 c.inline_comments_flat = CommentsModel()\
200 c.inline_comments_flat = CommentsModel()\
201 .get_commit_inline_comments(commit.raw_id)
201 .get_commit_inline_comments(commit.raw_id)
202
202
203 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
203 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
204 statuses, reviewers)
204 statuses, reviewers)
205
205
206 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
206 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
207
207
208 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
208 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
209
209
210 for review_obj, member, reasons, mandatory, status in review_statuses:
210 for review_obj, member, reasons, mandatory, status in review_statuses:
211 member_reviewer = h.reviewer_as_json(
211 member_reviewer = h.reviewer_as_json(
212 member, reasons=reasons, mandatory=mandatory, role=None,
212 member, reasons=reasons, mandatory=mandatory, role=None,
213 user_group=None
213 user_group=None
214 )
214 )
215
215
216 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
216 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
217 member_reviewer['review_status'] = current_review_status
217 member_reviewer['review_status'] = current_review_status
218 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
218 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
219 member_reviewer['allowed_to_update'] = False
219 member_reviewer['allowed_to_update'] = False
220 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
220 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
221
221
222 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
222 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
223
223
224 # NOTE(marcink): this uses the same voting logic as in pull-requests
224 # NOTE(marcink): this uses the same voting logic as in pull-requests
225 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
225 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
226 c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
226 c.commit_broadcast_channel = channelstream.comment_channel(c.repo_name, commit_obj=commit)
227
227
228 diff = None
228 diff = None
229 # Iterate over ranges (default commit view is always one commit)
229 # Iterate over ranges (default commit view is always one commit)
230 for commit in c.commit_ranges:
230 for commit in c.commit_ranges:
231 c.changes[commit.raw_id] = []
231 c.changes[commit.raw_id] = []
232
232
233 commit2 = commit
233 commit2 = commit
234 commit1 = commit.first_parent
234 commit1 = commit.first_parent
235
235
236 if method == 'show':
236 if method == 'show':
237 inline_comments = CommentsModel().get_inline_comments(
237 inline_comments = CommentsModel().get_inline_comments(
238 self.db_repo.repo_id, revision=commit.raw_id)
238 self.db_repo.repo_id, revision=commit.raw_id)
239 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
239 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
240 inline_comments))
240 inline_comments))
241 c.inline_comments = inline_comments
241 c.inline_comments = inline_comments
242
242
243 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
243 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
244 self.db_repo)
244 self.db_repo)
245 cache_file_path = diff_cache_exist(
245 cache_file_path = diff_cache_exist(
246 cache_path, 'diff', commit.raw_id,
246 cache_path, 'diff', commit.raw_id,
247 hide_whitespace_changes, diff_context, c.fulldiff)
247 hide_whitespace_changes, diff_context, c.fulldiff)
248
248
249 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
249 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
250 force_recache = str2bool(self.request.GET.get('force_recache'))
250 force_recache = str2bool(self.request.GET.get('force_recache'))
251
251
252 cached_diff = None
252 cached_diff = None
253 if caching_enabled:
253 if caching_enabled:
254 cached_diff = load_cached_diff(cache_file_path)
254 cached_diff = load_cached_diff(cache_file_path)
255
255
256 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
256 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
257 if not force_recache and has_proper_diff_cache:
257 if not force_recache and has_proper_diff_cache:
258 diffset = cached_diff['diff']
258 diffset = cached_diff['diff']
259 else:
259 else:
260 vcs_diff = self.rhodecode_vcs_repo.get_diff(
260 vcs_diff = self.rhodecode_vcs_repo.get_diff(
261 commit1, commit2,
261 commit1, commit2,
262 ignore_whitespace=hide_whitespace_changes,
262 ignore_whitespace=hide_whitespace_changes,
263 context=diff_context)
263 context=diff_context)
264
264
265 diff_processor = diffs.DiffProcessor(
265 diff_processor = diffs.DiffProcessor(
266 vcs_diff, format='newdiff', diff_limit=diff_limit,
266 vcs_diff, format='newdiff', diff_limit=diff_limit,
267 file_limit=file_limit, show_full_diff=c.fulldiff)
267 file_limit=file_limit, show_full_diff=c.fulldiff)
268
268
269 _parsed = diff_processor.prepare()
269 _parsed = diff_processor.prepare()
270
270
271 diffset = codeblocks.DiffSet(
271 diffset = codeblocks.DiffSet(
272 repo_name=self.db_repo_name,
272 repo_name=self.db_repo_name,
273 source_node_getter=codeblocks.diffset_node_getter(commit1),
273 source_node_getter=codeblocks.diffset_node_getter(commit1),
274 target_node_getter=codeblocks.diffset_node_getter(commit2))
274 target_node_getter=codeblocks.diffset_node_getter(commit2))
275
275
276 diffset = self.path_filter.render_patchset_filtered(
276 diffset = self.path_filter.render_patchset_filtered(
277 diffset, _parsed, commit1.raw_id, commit2.raw_id)
277 diffset, _parsed, commit1.raw_id, commit2.raw_id)
278
278
279 # save cached diff
279 # save cached diff
280 if caching_enabled:
280 if caching_enabled:
281 cache_diff(cache_file_path, diffset, None)
281 cache_diff(cache_file_path, diffset, None)
282
282
283 c.limited_diff = diffset.limited_diff
283 c.limited_diff = diffset.limited_diff
284 c.changes[commit.raw_id] = diffset
284 c.changes[commit.raw_id] = diffset
285 else:
285 else:
286 # TODO(marcink): no cache usage here...
286 # TODO(marcink): no cache usage here...
287 _diff = self.rhodecode_vcs_repo.get_diff(
287 _diff = self.rhodecode_vcs_repo.get_diff(
288 commit1, commit2,
288 commit1, commit2,
289 ignore_whitespace=hide_whitespace_changes, context=diff_context)
289 ignore_whitespace=hide_whitespace_changes, context=diff_context)
290 diff_processor = diffs.DiffProcessor(
290 diff_processor = diffs.DiffProcessor(
291 _diff, format='newdiff', diff_limit=diff_limit,
291 _diff, format='newdiff', diff_limit=diff_limit,
292 file_limit=file_limit, show_full_diff=c.fulldiff)
292 file_limit=file_limit, show_full_diff=c.fulldiff)
293 # downloads/raw we only need RAW diff nothing else
293 # downloads/raw we only need RAW diff nothing else
294 diff = self.path_filter.get_raw_patch(diff_processor)
294 diff = self.path_filter.get_raw_patch(diff_processor)
295 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
295 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
296
296
297 # sort comments by how they were generated
297 # sort comments by how they were generated
298 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
298 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
299 c.at_version_num = None
299 c.at_version_num = None
300
300
301 if len(c.commit_ranges) == 1:
301 if len(c.commit_ranges) == 1:
302 c.commit = c.commit_ranges[0]
302 c.commit = c.commit_ranges[0]
303 c.parent_tmpl = ''.join(
303 c.parent_tmpl = ''.join(
304 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
304 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
305
305
306 if method == 'download':
306 if method == 'download':
307 response = Response(diff)
307 response = Response(diff)
308 response.content_type = 'text/plain'
308 response.content_type = 'text/plain'
309 response.content_disposition = (
309 response.content_disposition = (
310 'attachment; filename=%s.diff' % commit_id_range[:12])
310 'attachment; filename=%s.diff' % commit_id_range[:12])
311 return response
311 return response
312 elif method == 'patch':
312 elif method == 'patch':
313 c.diff = safe_unicode(diff)
313 c.diff = safe_unicode(diff)
314 patch = render(
314 patch = render(
315 'rhodecode:templates/changeset/patch_changeset.mako',
315 'rhodecode:templates/changeset/patch_changeset.mako',
316 self._get_template_context(c), self.request)
316 self._get_template_context(c), self.request)
317 response = Response(patch)
317 response = Response(patch)
318 response.content_type = 'text/plain'
318 response.content_type = 'text/plain'
319 return response
319 return response
320 elif method == 'raw':
320 elif method == 'raw':
321 response = Response(diff)
321 response = Response(diff)
322 response.content_type = 'text/plain'
322 response.content_type = 'text/plain'
323 return response
323 return response
324 elif method == 'show':
324 elif method == 'show':
325 if len(c.commit_ranges) == 1:
325 if len(c.commit_ranges) == 1:
326 html = render(
326 html = render(
327 'rhodecode:templates/changeset/changeset.mako',
327 'rhodecode:templates/changeset/changeset.mako',
328 self._get_template_context(c), self.request)
328 self._get_template_context(c), self.request)
329 return Response(html)
329 return Response(html)
330 else:
330 else:
331 c.ancestor = None
331 c.ancestor = None
332 c.target_repo = self.db_repo
332 c.target_repo = self.db_repo
333 html = render(
333 html = render(
334 'rhodecode:templates/changeset/changeset_range.mako',
334 'rhodecode:templates/changeset/changeset_range.mako',
335 self._get_template_context(c), self.request)
335 self._get_template_context(c), self.request)
336 return Response(html)
336 return Response(html)
337
337
338 raise HTTPBadRequest()
338 raise HTTPBadRequest()
339
339
340 @LoginRequired()
340 @LoginRequired()
341 @HasRepoPermissionAnyDecorator(
341 @HasRepoPermissionAnyDecorator(
342 'repository.read', 'repository.write', 'repository.admin')
342 'repository.read', 'repository.write', 'repository.admin')
343 @view_config(
343 @view_config(
344 route_name='repo_commit', request_method='GET',
344 route_name='repo_commit', request_method='GET',
345 renderer=None)
345 renderer=None)
346 def repo_commit_show(self):
346 def repo_commit_show(self):
347 commit_id = self.request.matchdict['commit_id']
347 commit_id = self.request.matchdict['commit_id']
348 return self._commit(commit_id, method='show')
348 return self._commit(commit_id, method='show')
349
349
350 @LoginRequired()
350 @LoginRequired()
351 @HasRepoPermissionAnyDecorator(
351 @HasRepoPermissionAnyDecorator(
352 'repository.read', 'repository.write', 'repository.admin')
352 'repository.read', 'repository.write', 'repository.admin')
353 @view_config(
353 @view_config(
354 route_name='repo_commit_raw', request_method='GET',
354 route_name='repo_commit_raw', request_method='GET',
355 renderer=None)
355 renderer=None)
356 @view_config(
356 @view_config(
357 route_name='repo_commit_raw_deprecated', request_method='GET',
357 route_name='repo_commit_raw_deprecated', request_method='GET',
358 renderer=None)
358 renderer=None)
359 def repo_commit_raw(self):
359 def repo_commit_raw(self):
360 commit_id = self.request.matchdict['commit_id']
360 commit_id = self.request.matchdict['commit_id']
361 return self._commit(commit_id, method='raw')
361 return self._commit(commit_id, method='raw')
362
362
363 @LoginRequired()
363 @LoginRequired()
364 @HasRepoPermissionAnyDecorator(
364 @HasRepoPermissionAnyDecorator(
365 'repository.read', 'repository.write', 'repository.admin')
365 'repository.read', 'repository.write', 'repository.admin')
366 @view_config(
366 @view_config(
367 route_name='repo_commit_patch', request_method='GET',
367 route_name='repo_commit_patch', request_method='GET',
368 renderer=None)
368 renderer=None)
369 def repo_commit_patch(self):
369 def repo_commit_patch(self):
370 commit_id = self.request.matchdict['commit_id']
370 commit_id = self.request.matchdict['commit_id']
371 return self._commit(commit_id, method='patch')
371 return self._commit(commit_id, method='patch')
372
372
373 @LoginRequired()
373 @LoginRequired()
374 @HasRepoPermissionAnyDecorator(
374 @HasRepoPermissionAnyDecorator(
375 'repository.read', 'repository.write', 'repository.admin')
375 'repository.read', 'repository.write', 'repository.admin')
376 @view_config(
376 @view_config(
377 route_name='repo_commit_download', request_method='GET',
377 route_name='repo_commit_download', request_method='GET',
378 renderer=None)
378 renderer=None)
379 def repo_commit_download(self):
379 def repo_commit_download(self):
380 commit_id = self.request.matchdict['commit_id']
380 commit_id = self.request.matchdict['commit_id']
381 return self._commit(commit_id, method='download')
381 return self._commit(commit_id, method='download')
382
382
383 @LoginRequired()
383 def _commit_comments_create(self, commit_id, comments):
384 @NotAnonymous()
385 @HasRepoPermissionAnyDecorator(
386 'repository.read', 'repository.write', 'repository.admin')
387 @CSRFRequired()
388 @view_config(
389 route_name='repo_commit_comment_create', request_method='POST',
390 renderer='json_ext')
391 def repo_commit_comment_create(self):
392 _ = self.request.translate
384 _ = self.request.translate
393 commit_id = self.request.matchdict['commit_id']
385 data = {}
386 if not comments:
387 return
394
388
389 commit = self.db_repo.get_commit(commit_id)
390
391 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
392 for entry in comments:
395 c = self.load_default_context()
393 c = self.load_default_context()
396 status = self.request.POST.get('changeset_status', None)
394 comment_type = entry['comment_type']
397 is_draft = str2bool(self.request.POST.get('draft'))
395 text = entry['text']
398 text = self.request.POST.get('text')
396 status = entry['status']
399 comment_type = self.request.POST.get('comment_type')
397 is_draft = str2bool(entry['is_draft'])
400 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
398 resolves_comment_id = entry['resolves_comment_id']
401 f_path = self.request.POST.get('f_path')
399 f_path = entry['f_path']
402 line_no = self.request.POST.get('line')
400 line_no = entry['line']
403 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
401 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
404
402
405 if status:
403 if status:
406 text = text or (_('Status change %(transition_icon)s %(status)s')
404 text = text or (_('Status change %(transition_icon)s %(status)s')
407 % {'transition_icon': '>',
405 % {'transition_icon': '>',
408 'status': ChangesetStatus.get_status_lbl(status)})
406 'status': ChangesetStatus.get_status_lbl(status)})
409
407
410 multi_commit_ids = []
411 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
412 if _commit_id not in ['', None, EmptyCommit.raw_id]:
413 if _commit_id not in multi_commit_ids:
414 multi_commit_ids.append(_commit_id)
415
416 commit_ids = multi_commit_ids or [commit_id]
417
418 data = {}
419 # Multiple comments for each passed commit id
420 for current_id in filter(None, commit_ids):
421 comment = CommentsModel().create(
408 comment = CommentsModel().create(
422 text=text,
409 text=text,
423 repo=self.db_repo.repo_id,
410 repo=self.db_repo.repo_id,
424 user=self._rhodecode_db_user.user_id,
411 user=self._rhodecode_db_user.user_id,
425 commit_id=current_id,
412 commit_id=commit_id,
426 f_path=f_path,
413 f_path=f_path,
427 line_no=line_no,
414 line_no=line_no,
428 status_change=(ChangesetStatus.get_status_lbl(status)
415 status_change=(ChangesetStatus.get_status_lbl(status)
429 if status else None),
416 if status else None),
430 status_change_type=status,
417 status_change_type=status,
431 comment_type=comment_type,
418 comment_type=comment_type,
432 is_draft=is_draft,
419 is_draft=is_draft,
433 resolves_comment_id=resolves_comment_id,
420 resolves_comment_id=resolves_comment_id,
434 auth_user=self._rhodecode_user,
421 auth_user=self._rhodecode_user,
435 send_email=not is_draft, # skip notification for draft comments
422 send_email=not is_draft, # skip notification for draft comments
436 )
423 )
437 is_inline = comment.is_inline
424 is_inline = comment.is_inline
438
425
439 # get status if set !
426 # get status if set !
440 if status:
427 if status:
428 # `dont_allow_on_closed_pull_request = True` means
441 # if latest status was from pull request and it's closed
429 # if latest status was from pull request and it's closed
442 # disallow changing status !
430 # disallow changing status !
443 # dont_allow_on_closed_pull_request = True !
444
431
445 try:
432 try:
446 ChangesetStatusModel().set_status(
433 ChangesetStatusModel().set_status(
447 self.db_repo.repo_id,
434 self.db_repo.repo_id,
448 status,
435 status,
449 self._rhodecode_db_user.user_id,
436 self._rhodecode_db_user.user_id,
450 comment,
437 comment,
451 revision=current_id,
438 revision=commit_id,
452 dont_allow_on_closed_pull_request=True
439 dont_allow_on_closed_pull_request=True
453 )
440 )
454 except StatusChangeOnClosedPullRequestError:
441 except StatusChangeOnClosedPullRequestError:
455 msg = _('Changing the status of a commit associated with '
442 msg = _('Changing the status of a commit associated with '
456 'a closed pull request is not allowed')
443 'a closed pull request is not allowed')
457 log.exception(msg)
444 log.exception(msg)
458 h.flash(msg, category='warning')
445 h.flash(msg, category='warning')
459 raise HTTPFound(h.route_path(
446 raise HTTPFound(h.route_path(
460 'repo_commit', repo_name=self.db_repo_name,
447 'repo_commit', repo_name=self.db_repo_name,
461 commit_id=current_id))
448 commit_id=commit_id))
449
450 Session().flush()
451 # this is somehow required to get access to some relationship
452 # loaded on comment
453 Session().refresh(comment)
462
454
463 # skip notifications for drafts
455 # skip notifications for drafts
464 if not is_draft:
456 if not is_draft:
465 commit = self.db_repo.get_commit(current_id)
466 CommentsModel().trigger_commit_comment_hook(
457 CommentsModel().trigger_commit_comment_hook(
467 self.db_repo, self._rhodecode_user, 'create',
458 self.db_repo, self._rhodecode_user, 'create',
468 data={'comment': comment, 'commit': commit})
459 data={'comment': comment, 'commit': commit})
469
460
470 comment_id = comment.comment_id
461 comment_id = comment.comment_id
471 data[comment_id] = {
462 data[comment_id] = {
472 'target_id': target_elem_id
463 'target_id': target_elem_id
473 }
464 }
465 Session().flush()
466
474 c.co = comment
467 c.co = comment
475 c.at_version_num = 0
468 c.at_version_num = 0
476 c.is_new = True
469 c.is_new = True
477 rendered_comment = render(
470 rendered_comment = render(
478 'rhodecode:templates/changeset/changeset_comment_block.mako',
471 'rhodecode:templates/changeset/changeset_comment_block.mako',
479 self._get_template_context(c), self.request)
472 self._get_template_context(c), self.request)
480
473
481 data[comment_id].update(comment.get_dict())
474 data[comment_id].update(comment.get_dict())
482 data[comment_id].update({'rendered_text': rendered_comment})
475 data[comment_id].update({'rendered_text': rendered_comment})
483
476
477 # finalize, commit and redirect
478 Session().commit()
479
484 # skip channelstream for draft comments
480 # skip channelstream for draft comments
485 if not is_draft:
481 if not all_drafts:
486 comment_broadcast_channel = channelstream.comment_channel(
482 comment_broadcast_channel = channelstream.comment_channel(
487 self.db_repo_name, commit_obj=commit)
483 self.db_repo_name, commit_obj=commit)
488
484
489 comment_data = data
485 comment_data = data
490 posted_comment_type = 'inline' if is_inline else 'general'
486 posted_comment_type = 'inline' if is_inline else 'general'
487 if len(data) == 1:
488 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
489 else:
490 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
491
491 channelstream.comment_channelstream_push(
492 channelstream.comment_channelstream_push(
492 self.request, comment_broadcast_channel, self._rhodecode_user,
493 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
493 _('posted a new {} comment').format(posted_comment_type),
494 comment_data=comment_data)
494 comment_data=comment_data)
495
495
496 # finalize, commit and redirect
496 return data
497 Session().commit()
497
498 @LoginRequired()
499 @NotAnonymous()
500 @HasRepoPermissionAnyDecorator(
501 'repository.read', 'repository.write', 'repository.admin')
502 @CSRFRequired()
503 @view_config(
504 route_name='repo_commit_comment_create', request_method='POST',
505 renderer='json_ext')
506 def repo_commit_comment_create(self):
507 _ = self.request.translate
508 commit_id = self.request.matchdict['commit_id']
509
510 multi_commit_ids = []
511 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
512 if _commit_id not in ['', None, EmptyCommit.raw_id]:
513 if _commit_id not in multi_commit_ids:
514 multi_commit_ids.append(_commit_id)
498
515
499 return data
516 commit_ids = multi_commit_ids or [commit_id]
517
518 data = []
519 # Multiple comments for each passed commit id
520 for current_id in filter(None, commit_ids):
521 comment_data = {
522 'comment_type': self.request.POST.get('comment_type'),
523 'text': self.request.POST.get('text'),
524 'status': self.request.POST.get('changeset_status', None),
525 'is_draft': self.request.POST.get('draft'),
526 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
527 'close_pull_request': self.request.POST.get('close_pull_request'),
528 'f_path': self.request.POST.get('f_path'),
529 'line': self.request.POST.get('line'),
530 }
531 comment = self._commit_comments_create(commit_id=current_id, comments=[comment_data])
532 data.append(comment)
533
534 return data if len(data) > 1 else data[0]
500
535
501 @LoginRequired()
536 @LoginRequired()
502 @NotAnonymous()
537 @NotAnonymous()
503 @HasRepoPermissionAnyDecorator(
538 @HasRepoPermissionAnyDecorator(
504 'repository.read', 'repository.write', 'repository.admin')
539 'repository.read', 'repository.write', 'repository.admin')
505 @CSRFRequired()
540 @CSRFRequired()
506 @view_config(
541 @view_config(
507 route_name='repo_commit_comment_preview', request_method='POST',
542 route_name='repo_commit_comment_preview', request_method='POST',
508 renderer='string', xhr=True)
543 renderer='string', xhr=True)
509 def repo_commit_comment_preview(self):
544 def repo_commit_comment_preview(self):
510 # Technically a CSRF token is not needed as no state changes with this
545 # Technically a CSRF token is not needed as no state changes with this
511 # call. However, as this is a POST is better to have it, so automated
546 # call. However, as this is a POST is better to have it, so automated
512 # tools don't flag it as potential CSRF.
547 # tools don't flag it as potential CSRF.
513 # Post is required because the payload could be bigger than the maximum
548 # Post is required because the payload could be bigger than the maximum
514 # allowed by GET.
549 # allowed by GET.
515
550
516 text = self.request.POST.get('text')
551 text = self.request.POST.get('text')
517 renderer = self.request.POST.get('renderer') or 'rst'
552 renderer = self.request.POST.get('renderer') or 'rst'
518 if text:
553 if text:
519 return h.render(text, renderer=renderer, mentions=True,
554 return h.render(text, renderer=renderer, mentions=True,
520 repo_name=self.db_repo_name)
555 repo_name=self.db_repo_name)
521 return ''
556 return ''
522
557
523 @LoginRequired()
558 @LoginRequired()
524 @HasRepoPermissionAnyDecorator(
559 @HasRepoPermissionAnyDecorator(
525 'repository.read', 'repository.write', 'repository.admin')
560 'repository.read', 'repository.write', 'repository.admin')
526 @CSRFRequired()
561 @CSRFRequired()
527 @view_config(
562 @view_config(
528 route_name='repo_commit_comment_history_view', request_method='POST',
563 route_name='repo_commit_comment_history_view', request_method='POST',
529 renderer='string', xhr=True)
564 renderer='string', xhr=True)
530 def repo_commit_comment_history_view(self):
565 def repo_commit_comment_history_view(self):
531 c = self.load_default_context()
566 c = self.load_default_context()
532
567
533 comment_history_id = self.request.matchdict['comment_history_id']
568 comment_history_id = self.request.matchdict['comment_history_id']
534 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
569 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
535 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
570 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
536
571
537 if is_repo_comment:
572 if is_repo_comment:
538 c.comment_history = comment_history
573 c.comment_history = comment_history
539
574
540 rendered_comment = render(
575 rendered_comment = render(
541 'rhodecode:templates/changeset/comment_history.mako',
576 'rhodecode:templates/changeset/comment_history.mako',
542 self._get_template_context(c)
577 self._get_template_context(c)
543 , self.request)
578 , self.request)
544 return rendered_comment
579 return rendered_comment
545 else:
580 else:
546 log.warning('No permissions for user %s to show comment_history_id: %s',
581 log.warning('No permissions for user %s to show comment_history_id: %s',
547 self._rhodecode_db_user, comment_history_id)
582 self._rhodecode_db_user, comment_history_id)
548 raise HTTPNotFound()
583 raise HTTPNotFound()
549
584
550 @LoginRequired()
585 @LoginRequired()
551 @NotAnonymous()
586 @NotAnonymous()
552 @HasRepoPermissionAnyDecorator(
587 @HasRepoPermissionAnyDecorator(
553 'repository.read', 'repository.write', 'repository.admin')
588 'repository.read', 'repository.write', 'repository.admin')
554 @CSRFRequired()
589 @CSRFRequired()
555 @view_config(
590 @view_config(
556 route_name='repo_commit_comment_attachment_upload', request_method='POST',
591 route_name='repo_commit_comment_attachment_upload', request_method='POST',
557 renderer='json_ext', xhr=True)
592 renderer='json_ext', xhr=True)
558 def repo_commit_comment_attachment_upload(self):
593 def repo_commit_comment_attachment_upload(self):
559 c = self.load_default_context()
594 c = self.load_default_context()
560 upload_key = 'attachment'
595 upload_key = 'attachment'
561
596
562 file_obj = self.request.POST.get(upload_key)
597 file_obj = self.request.POST.get(upload_key)
563
598
564 if file_obj is None:
599 if file_obj is None:
565 self.request.response.status = 400
600 self.request.response.status = 400
566 return {'store_fid': None,
601 return {'store_fid': None,
567 'access_path': None,
602 'access_path': None,
568 'error': '{} data field is missing'.format(upload_key)}
603 'error': '{} data field is missing'.format(upload_key)}
569
604
570 if not hasattr(file_obj, 'filename'):
605 if not hasattr(file_obj, 'filename'):
571 self.request.response.status = 400
606 self.request.response.status = 400
572 return {'store_fid': None,
607 return {'store_fid': None,
573 'access_path': None,
608 'access_path': None,
574 'error': 'filename cannot be read from the data field'}
609 'error': 'filename cannot be read from the data field'}
575
610
576 filename = file_obj.filename
611 filename = file_obj.filename
577 file_display_name = filename
612 file_display_name = filename
578
613
579 metadata = {
614 metadata = {
580 'user_uploaded': {'username': self._rhodecode_user.username,
615 'user_uploaded': {'username': self._rhodecode_user.username,
581 'user_id': self._rhodecode_user.user_id,
616 'user_id': self._rhodecode_user.user_id,
582 'ip': self._rhodecode_user.ip_addr}}
617 'ip': self._rhodecode_user.ip_addr}}
583
618
584 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
619 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
585 allowed_extensions = [
620 allowed_extensions = [
586 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
621 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
587 '.pptx', '.txt', '.xlsx', '.zip']
622 '.pptx', '.txt', '.xlsx', '.zip']
588 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
623 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
589
624
590 try:
625 try:
591 storage = store_utils.get_file_storage(self.request.registry.settings)
626 storage = store_utils.get_file_storage(self.request.registry.settings)
592 store_uid, metadata = storage.save_file(
627 store_uid, metadata = storage.save_file(
593 file_obj.file, filename, extra_metadata=metadata,
628 file_obj.file, filename, extra_metadata=metadata,
594 extensions=allowed_extensions, max_filesize=max_file_size)
629 extensions=allowed_extensions, max_filesize=max_file_size)
595 except FileNotAllowedException:
630 except FileNotAllowedException:
596 self.request.response.status = 400
631 self.request.response.status = 400
597 permitted_extensions = ', '.join(allowed_extensions)
632 permitted_extensions = ', '.join(allowed_extensions)
598 error_msg = 'File `{}` is not allowed. ' \
633 error_msg = 'File `{}` is not allowed. ' \
599 'Only following extensions are permitted: {}'.format(
634 'Only following extensions are permitted: {}'.format(
600 filename, permitted_extensions)
635 filename, permitted_extensions)
601 return {'store_fid': None,
636 return {'store_fid': None,
602 'access_path': None,
637 'access_path': None,
603 'error': error_msg}
638 'error': error_msg}
604 except FileOverSizeException:
639 except FileOverSizeException:
605 self.request.response.status = 400
640 self.request.response.status = 400
606 limit_mb = h.format_byte_size_binary(max_file_size)
641 limit_mb = h.format_byte_size_binary(max_file_size)
607 return {'store_fid': None,
642 return {'store_fid': None,
608 'access_path': None,
643 'access_path': None,
609 'error': 'File {} is exceeding allowed limit of {}.'.format(
644 'error': 'File {} is exceeding allowed limit of {}.'.format(
610 filename, limit_mb)}
645 filename, limit_mb)}
611
646
612 try:
647 try:
613 entry = FileStore.create(
648 entry = FileStore.create(
614 file_uid=store_uid, filename=metadata["filename"],
649 file_uid=store_uid, filename=metadata["filename"],
615 file_hash=metadata["sha256"], file_size=metadata["size"],
650 file_hash=metadata["sha256"], file_size=metadata["size"],
616 file_display_name=file_display_name,
651 file_display_name=file_display_name,
617 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
652 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
618 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
653 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
619 scope_repo_id=self.db_repo.repo_id
654 scope_repo_id=self.db_repo.repo_id
620 )
655 )
621 Session().add(entry)
656 Session().add(entry)
622 Session().commit()
657 Session().commit()
623 log.debug('Stored upload in DB as %s', entry)
658 log.debug('Stored upload in DB as %s', entry)
624 except Exception:
659 except Exception:
625 log.exception('Failed to store file %s', filename)
660 log.exception('Failed to store file %s', filename)
626 self.request.response.status = 400
661 self.request.response.status = 400
627 return {'store_fid': None,
662 return {'store_fid': None,
628 'access_path': None,
663 'access_path': None,
629 'error': 'File {} failed to store in DB.'.format(filename)}
664 'error': 'File {} failed to store in DB.'.format(filename)}
630
665
631 Session().commit()
666 Session().commit()
632
667
633 return {
668 return {
634 'store_fid': store_uid,
669 'store_fid': store_uid,
635 'access_path': h.route_path(
670 'access_path': h.route_path(
636 'download_file', fid=store_uid),
671 'download_file', fid=store_uid),
637 'fqn_access_path': h.route_url(
672 'fqn_access_path': h.route_url(
638 'download_file', fid=store_uid),
673 'download_file', fid=store_uid),
639 'repo_access_path': h.route_path(
674 'repo_access_path': h.route_path(
640 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
675 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
641 'repo_fqn_access_path': h.route_url(
676 'repo_fqn_access_path': h.route_url(
642 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
677 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
643 }
678 }
644
679
645 @LoginRequired()
680 @LoginRequired()
646 @NotAnonymous()
681 @NotAnonymous()
647 @HasRepoPermissionAnyDecorator(
682 @HasRepoPermissionAnyDecorator(
648 'repository.read', 'repository.write', 'repository.admin')
683 'repository.read', 'repository.write', 'repository.admin')
649 @CSRFRequired()
684 @CSRFRequired()
650 @view_config(
685 @view_config(
651 route_name='repo_commit_comment_delete', request_method='POST',
686 route_name='repo_commit_comment_delete', request_method='POST',
652 renderer='json_ext')
687 renderer='json_ext')
653 def repo_commit_comment_delete(self):
688 def repo_commit_comment_delete(self):
654 commit_id = self.request.matchdict['commit_id']
689 commit_id = self.request.matchdict['commit_id']
655 comment_id = self.request.matchdict['comment_id']
690 comment_id = self.request.matchdict['comment_id']
656
691
657 comment = ChangesetComment.get_or_404(comment_id)
692 comment = ChangesetComment.get_or_404(comment_id)
658 if not comment:
693 if not comment:
659 log.debug('Comment with id:%s not found, skipping', comment_id)
694 log.debug('Comment with id:%s not found, skipping', comment_id)
660 # comment already deleted in another call probably
695 # comment already deleted in another call probably
661 return True
696 return True
662
697
663 if comment.immutable:
698 if comment.immutable:
664 # don't allow deleting comments that are immutable
699 # don't allow deleting comments that are immutable
665 raise HTTPForbidden()
700 raise HTTPForbidden()
666
701
667 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
702 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
668 super_admin = h.HasPermissionAny('hg.admin')()
703 super_admin = h.HasPermissionAny('hg.admin')()
669 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
704 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
670 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
705 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
671 comment_repo_admin = is_repo_admin and is_repo_comment
706 comment_repo_admin = is_repo_admin and is_repo_comment
672
707
673 if super_admin or comment_owner or comment_repo_admin:
708 if super_admin or comment_owner or comment_repo_admin:
674 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
709 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
675 Session().commit()
710 Session().commit()
676 return True
711 return True
677 else:
712 else:
678 log.warning('No permissions for user %s to delete comment_id: %s',
713 log.warning('No permissions for user %s to delete comment_id: %s',
679 self._rhodecode_db_user, comment_id)
714 self._rhodecode_db_user, comment_id)
680 raise HTTPNotFound()
715 raise HTTPNotFound()
681
716
682 @LoginRequired()
717 @LoginRequired()
683 @NotAnonymous()
718 @NotAnonymous()
684 @HasRepoPermissionAnyDecorator(
719 @HasRepoPermissionAnyDecorator(
685 'repository.read', 'repository.write', 'repository.admin')
720 'repository.read', 'repository.write', 'repository.admin')
686 @CSRFRequired()
721 @CSRFRequired()
687 @view_config(
722 @view_config(
688 route_name='repo_commit_comment_edit', request_method='POST',
723 route_name='repo_commit_comment_edit', request_method='POST',
689 renderer='json_ext')
724 renderer='json_ext')
690 def repo_commit_comment_edit(self):
725 def repo_commit_comment_edit(self):
691 self.load_default_context()
726 self.load_default_context()
692
727
728 commit_id = self.request.matchdict['commit_id']
693 comment_id = self.request.matchdict['comment_id']
729 comment_id = self.request.matchdict['comment_id']
694 comment = ChangesetComment.get_or_404(comment_id)
730 comment = ChangesetComment.get_or_404(comment_id)
695
731
696 if comment.immutable:
732 if comment.immutable:
697 # don't allow deleting comments that are immutable
733 # don't allow deleting comments that are immutable
698 raise HTTPForbidden()
734 raise HTTPForbidden()
699
735
700 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
736 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
701 super_admin = h.HasPermissionAny('hg.admin')()
737 super_admin = h.HasPermissionAny('hg.admin')()
702 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
738 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
703 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
739 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
704 comment_repo_admin = is_repo_admin and is_repo_comment
740 comment_repo_admin = is_repo_admin and is_repo_comment
705
741
706 if super_admin or comment_owner or comment_repo_admin:
742 if super_admin or comment_owner or comment_repo_admin:
707 text = self.request.POST.get('text')
743 text = self.request.POST.get('text')
708 version = self.request.POST.get('version')
744 version = self.request.POST.get('version')
709 if text == comment.text:
745 if text == comment.text:
710 log.warning(
746 log.warning(
711 'Comment(repo): '
747 'Comment(repo): '
712 'Trying to create new version '
748 'Trying to create new version '
713 'with the same comment body {}'.format(
749 'with the same comment body {}'.format(
714 comment_id,
750 comment_id,
715 )
751 )
716 )
752 )
717 raise HTTPNotFound()
753 raise HTTPNotFound()
718
754
719 if version.isdigit():
755 if version.isdigit():
720 version = int(version)
756 version = int(version)
721 else:
757 else:
722 log.warning(
758 log.warning(
723 'Comment(repo): Wrong version type {} {} '
759 'Comment(repo): Wrong version type {} {} '
724 'for comment {}'.format(
760 'for comment {}'.format(
725 version,
761 version,
726 type(version),
762 type(version),
727 comment_id,
763 comment_id,
728 )
764 )
729 )
765 )
730 raise HTTPNotFound()
766 raise HTTPNotFound()
731
767
732 try:
768 try:
733 comment_history = CommentsModel().edit(
769 comment_history = CommentsModel().edit(
734 comment_id=comment_id,
770 comment_id=comment_id,
735 text=text,
771 text=text,
736 auth_user=self._rhodecode_user,
772 auth_user=self._rhodecode_user,
737 version=version,
773 version=version,
738 )
774 )
739 except CommentVersionMismatch:
775 except CommentVersionMismatch:
740 raise HTTPConflict()
776 raise HTTPConflict()
741
777
742 if not comment_history:
778 if not comment_history:
743 raise HTTPNotFound()
779 raise HTTPNotFound()
744
780
745 commit_id = self.request.matchdict['commit_id']
781 if not comment.draft:
746 commit = self.db_repo.get_commit(commit_id)
782 commit = self.db_repo.get_commit(commit_id)
747 CommentsModel().trigger_commit_comment_hook(
783 CommentsModel().trigger_commit_comment_hook(
748 self.db_repo, self._rhodecode_user, 'edit',
784 self.db_repo, self._rhodecode_user, 'edit',
749 data={'comment': comment, 'commit': commit})
785 data={'comment': comment, 'commit': commit})
750
786
751 Session().commit()
787 Session().commit()
752 return {
788 return {
753 'comment_history_id': comment_history.comment_history_id,
789 'comment_history_id': comment_history.comment_history_id,
754 'comment_id': comment.comment_id,
790 'comment_id': comment.comment_id,
755 'comment_version': comment_history.version,
791 'comment_version': comment_history.version,
756 'comment_author_username': comment_history.author.username,
792 'comment_author_username': comment_history.author.username,
757 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
793 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
758 'comment_created_on': h.age_component(comment_history.created_on,
794 'comment_created_on': h.age_component(comment_history.created_on,
759 time_is_local=True),
795 time_is_local=True),
760 }
796 }
761 else:
797 else:
762 log.warning('No permissions for user %s to edit comment_id: %s',
798 log.warning('No permissions for user %s to edit comment_id: %s',
763 self._rhodecode_db_user, comment_id)
799 self._rhodecode_db_user, comment_id)
764 raise HTTPNotFound()
800 raise HTTPNotFound()
765
801
766 @LoginRequired()
802 @LoginRequired()
767 @HasRepoPermissionAnyDecorator(
803 @HasRepoPermissionAnyDecorator(
768 'repository.read', 'repository.write', 'repository.admin')
804 'repository.read', 'repository.write', 'repository.admin')
769 @view_config(
805 @view_config(
770 route_name='repo_commit_data', request_method='GET',
806 route_name='repo_commit_data', request_method='GET',
771 renderer='json_ext', xhr=True)
807 renderer='json_ext', xhr=True)
772 def repo_commit_data(self):
808 def repo_commit_data(self):
773 commit_id = self.request.matchdict['commit_id']
809 commit_id = self.request.matchdict['commit_id']
774 self.load_default_context()
810 self.load_default_context()
775
811
776 try:
812 try:
777 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
813 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
778 except CommitDoesNotExistError as e:
814 except CommitDoesNotExistError as e:
779 return EmptyCommit(message=str(e))
815 return EmptyCommit(message=str(e))
780
816
781 @LoginRequired()
817 @LoginRequired()
782 @HasRepoPermissionAnyDecorator(
818 @HasRepoPermissionAnyDecorator(
783 'repository.read', 'repository.write', 'repository.admin')
819 'repository.read', 'repository.write', 'repository.admin')
784 @view_config(
820 @view_config(
785 route_name='repo_commit_children', request_method='GET',
821 route_name='repo_commit_children', request_method='GET',
786 renderer='json_ext', xhr=True)
822 renderer='json_ext', xhr=True)
787 def repo_commit_children(self):
823 def repo_commit_children(self):
788 commit_id = self.request.matchdict['commit_id']
824 commit_id = self.request.matchdict['commit_id']
789 self.load_default_context()
825 self.load_default_context()
790
826
791 try:
827 try:
792 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
828 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
793 children = commit.children
829 children = commit.children
794 except CommitDoesNotExistError:
830 except CommitDoesNotExistError:
795 children = []
831 children = []
796
832
797 result = {"results": children}
833 result = {"results": children}
798 return result
834 return result
799
835
800 @LoginRequired()
836 @LoginRequired()
801 @HasRepoPermissionAnyDecorator(
837 @HasRepoPermissionAnyDecorator(
802 'repository.read', 'repository.write', 'repository.admin')
838 'repository.read', 'repository.write', 'repository.admin')
803 @view_config(
839 @view_config(
804 route_name='repo_commit_parents', request_method='GET',
840 route_name='repo_commit_parents', request_method='GET',
805 renderer='json_ext')
841 renderer='json_ext')
806 def repo_commit_parents(self):
842 def repo_commit_parents(self):
807 commit_id = self.request.matchdict['commit_id']
843 commit_id = self.request.matchdict['commit_id']
808 self.load_default_context()
844 self.load_default_context()
809
845
810 try:
846 try:
811 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
847 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
812 parents = commit.parents
848 parents = commit.parents
813 except CommitDoesNotExistError:
849 except CommitDoesNotExistError:
814 parents = []
850 parents = []
815 result = {"results": parents}
851 result = {"results": parents}
816 return result
852 return result
@@ -1,1857 +1,1854 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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 import logging
21 import logging
22 import collections
22 import collections
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27 from pyramid.httpexceptions import (
27 from pyramid.httpexceptions import (
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest, HTTPConflict)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest, HTTPConflict)
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33
33
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib.base import vcs_operation_context
35 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 from rhodecode.lib.exceptions import CommentVersionMismatch
37 from rhodecode.lib.exceptions import CommentVersionMismatch
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 NotAnonymous, CSRFRequired)
41 NotAnonymous, CSRFRequired)
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist
43 from rhodecode.lib.vcs.backends.base import (
43 from rhodecode.lib.vcs.backends.base import (
44 EmptyCommit, UpdateFailureReason, unicode_to_reference)
44 EmptyCommit, UpdateFailureReason, unicode_to_reference)
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
46 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
47 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.comment import CommentsModel
49 from rhodecode.model.db import (
49 from rhodecode.model.db import (
50 func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
50 func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
51 PullRequestReviewers)
51 PullRequestReviewers)
52 from rhodecode.model.forms import PullRequestForm
52 from rhodecode.model.forms import PullRequestForm
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
54 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
55 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.scm import ScmModel
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class RepoPullRequestsView(RepoAppView, DataGridAppView):
60 class RepoPullRequestsView(RepoAppView, DataGridAppView):
61
61
62 def load_default_context(self):
62 def load_default_context(self):
63 c = self._get_local_tmpl_context(include_app_defaults=True)
63 c = self._get_local_tmpl_context(include_app_defaults=True)
64 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
64 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
65 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
65 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
66 # backward compat., we use for OLD PRs a plain renderer
66 # backward compat., we use for OLD PRs a plain renderer
67 c.renderer = 'plain'
67 c.renderer = 'plain'
68 return c
68 return c
69
69
70 def _get_pull_requests_list(
70 def _get_pull_requests_list(
71 self, repo_name, source, filter_type, opened_by, statuses):
71 self, repo_name, source, filter_type, opened_by, statuses):
72
72
73 draw, start, limit = self._extract_chunk(self.request)
73 draw, start, limit = self._extract_chunk(self.request)
74 search_q, order_by, order_dir = self._extract_ordering(self.request)
74 search_q, order_by, order_dir = self._extract_ordering(self.request)
75 _render = self.request.get_partial_renderer(
75 _render = self.request.get_partial_renderer(
76 'rhodecode:templates/data_table/_dt_elements.mako')
76 'rhodecode:templates/data_table/_dt_elements.mako')
77
77
78 # pagination
78 # pagination
79
79
80 if filter_type == 'awaiting_review':
80 if filter_type == 'awaiting_review':
81 pull_requests = PullRequestModel().get_awaiting_review(
81 pull_requests = PullRequestModel().get_awaiting_review(
82 repo_name, search_q=search_q, source=source, opened_by=opened_by,
82 repo_name, search_q=search_q, source=source, opened_by=opened_by,
83 statuses=statuses, offset=start, length=limit,
83 statuses=statuses, offset=start, length=limit,
84 order_by=order_by, order_dir=order_dir)
84 order_by=order_by, order_dir=order_dir)
85 pull_requests_total_count = PullRequestModel().count_awaiting_review(
85 pull_requests_total_count = PullRequestModel().count_awaiting_review(
86 repo_name, search_q=search_q, source=source, statuses=statuses,
86 repo_name, search_q=search_q, source=source, statuses=statuses,
87 opened_by=opened_by)
87 opened_by=opened_by)
88 elif filter_type == 'awaiting_my_review':
88 elif filter_type == 'awaiting_my_review':
89 pull_requests = PullRequestModel().get_awaiting_my_review(
89 pull_requests = PullRequestModel().get_awaiting_my_review(
90 repo_name, search_q=search_q, source=source, opened_by=opened_by,
90 repo_name, search_q=search_q, source=source, opened_by=opened_by,
91 user_id=self._rhodecode_user.user_id, statuses=statuses,
91 user_id=self._rhodecode_user.user_id, statuses=statuses,
92 offset=start, length=limit, order_by=order_by,
92 offset=start, length=limit, order_by=order_by,
93 order_dir=order_dir)
93 order_dir=order_dir)
94 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
94 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
95 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
95 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
96 statuses=statuses, opened_by=opened_by)
96 statuses=statuses, opened_by=opened_by)
97 else:
97 else:
98 pull_requests = PullRequestModel().get_all(
98 pull_requests = PullRequestModel().get_all(
99 repo_name, search_q=search_q, source=source, opened_by=opened_by,
99 repo_name, search_q=search_q, source=source, opened_by=opened_by,
100 statuses=statuses, offset=start, length=limit,
100 statuses=statuses, offset=start, length=limit,
101 order_by=order_by, order_dir=order_dir)
101 order_by=order_by, order_dir=order_dir)
102 pull_requests_total_count = PullRequestModel().count_all(
102 pull_requests_total_count = PullRequestModel().count_all(
103 repo_name, search_q=search_q, source=source, statuses=statuses,
103 repo_name, search_q=search_q, source=source, statuses=statuses,
104 opened_by=opened_by)
104 opened_by=opened_by)
105
105
106 data = []
106 data = []
107 comments_model = CommentsModel()
107 comments_model = CommentsModel()
108 for pr in pull_requests:
108 for pr in pull_requests:
109 comments_count = comments_model.get_all_comments(
109 comments_count = comments_model.get_all_comments(
110 self.db_repo.repo_id, pull_request=pr,
110 self.db_repo.repo_id, pull_request=pr,
111 include_drafts=False, count_only=True)
111 include_drafts=False, count_only=True)
112
112
113 data.append({
113 data.append({
114 'name': _render('pullrequest_name',
114 'name': _render('pullrequest_name',
115 pr.pull_request_id, pr.pull_request_state,
115 pr.pull_request_id, pr.pull_request_state,
116 pr.work_in_progress, pr.target_repo.repo_name,
116 pr.work_in_progress, pr.target_repo.repo_name,
117 short=True),
117 short=True),
118 'name_raw': pr.pull_request_id,
118 'name_raw': pr.pull_request_id,
119 'status': _render('pullrequest_status',
119 'status': _render('pullrequest_status',
120 pr.calculated_review_status()),
120 pr.calculated_review_status()),
121 'title': _render('pullrequest_title', pr.title, pr.description),
121 'title': _render('pullrequest_title', pr.title, pr.description),
122 'description': h.escape(pr.description),
122 'description': h.escape(pr.description),
123 'updated_on': _render('pullrequest_updated_on',
123 'updated_on': _render('pullrequest_updated_on',
124 h.datetime_to_time(pr.updated_on)),
124 h.datetime_to_time(pr.updated_on)),
125 'updated_on_raw': h.datetime_to_time(pr.updated_on),
125 'updated_on_raw': h.datetime_to_time(pr.updated_on),
126 'created_on': _render('pullrequest_updated_on',
126 'created_on': _render('pullrequest_updated_on',
127 h.datetime_to_time(pr.created_on)),
127 h.datetime_to_time(pr.created_on)),
128 'created_on_raw': h.datetime_to_time(pr.created_on),
128 'created_on_raw': h.datetime_to_time(pr.created_on),
129 'state': pr.pull_request_state,
129 'state': pr.pull_request_state,
130 'author': _render('pullrequest_author',
130 'author': _render('pullrequest_author',
131 pr.author.full_contact, ),
131 pr.author.full_contact, ),
132 'author_raw': pr.author.full_name,
132 'author_raw': pr.author.full_name,
133 'comments': _render('pullrequest_comments', comments_count),
133 'comments': _render('pullrequest_comments', comments_count),
134 'comments_raw': comments_count,
134 'comments_raw': comments_count,
135 'closed': pr.is_closed(),
135 'closed': pr.is_closed(),
136 })
136 })
137
137
138 data = ({
138 data = ({
139 'draw': draw,
139 'draw': draw,
140 'data': data,
140 'data': data,
141 'recordsTotal': pull_requests_total_count,
141 'recordsTotal': pull_requests_total_count,
142 'recordsFiltered': pull_requests_total_count,
142 'recordsFiltered': pull_requests_total_count,
143 })
143 })
144 return data
144 return data
145
145
146 @LoginRequired()
146 @LoginRequired()
147 @HasRepoPermissionAnyDecorator(
147 @HasRepoPermissionAnyDecorator(
148 'repository.read', 'repository.write', 'repository.admin')
148 'repository.read', 'repository.write', 'repository.admin')
149 @view_config(
149 @view_config(
150 route_name='pullrequest_show_all', request_method='GET',
150 route_name='pullrequest_show_all', request_method='GET',
151 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
151 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
152 def pull_request_list(self):
152 def pull_request_list(self):
153 c = self.load_default_context()
153 c = self.load_default_context()
154
154
155 req_get = self.request.GET
155 req_get = self.request.GET
156 c.source = str2bool(req_get.get('source'))
156 c.source = str2bool(req_get.get('source'))
157 c.closed = str2bool(req_get.get('closed'))
157 c.closed = str2bool(req_get.get('closed'))
158 c.my = str2bool(req_get.get('my'))
158 c.my = str2bool(req_get.get('my'))
159 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
159 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
160 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
160 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
161
161
162 c.active = 'open'
162 c.active = 'open'
163 if c.my:
163 if c.my:
164 c.active = 'my'
164 c.active = 'my'
165 if c.closed:
165 if c.closed:
166 c.active = 'closed'
166 c.active = 'closed'
167 if c.awaiting_review and not c.source:
167 if c.awaiting_review and not c.source:
168 c.active = 'awaiting'
168 c.active = 'awaiting'
169 if c.source and not c.awaiting_review:
169 if c.source and not c.awaiting_review:
170 c.active = 'source'
170 c.active = 'source'
171 if c.awaiting_my_review:
171 if c.awaiting_my_review:
172 c.active = 'awaiting_my'
172 c.active = 'awaiting_my'
173
173
174 return self._get_template_context(c)
174 return self._get_template_context(c)
175
175
176 @LoginRequired()
176 @LoginRequired()
177 @HasRepoPermissionAnyDecorator(
177 @HasRepoPermissionAnyDecorator(
178 'repository.read', 'repository.write', 'repository.admin')
178 'repository.read', 'repository.write', 'repository.admin')
179 @view_config(
179 @view_config(
180 route_name='pullrequest_show_all_data', request_method='GET',
180 route_name='pullrequest_show_all_data', request_method='GET',
181 renderer='json_ext', xhr=True)
181 renderer='json_ext', xhr=True)
182 def pull_request_list_data(self):
182 def pull_request_list_data(self):
183 self.load_default_context()
183 self.load_default_context()
184
184
185 # additional filters
185 # additional filters
186 req_get = self.request.GET
186 req_get = self.request.GET
187 source = str2bool(req_get.get('source'))
187 source = str2bool(req_get.get('source'))
188 closed = str2bool(req_get.get('closed'))
188 closed = str2bool(req_get.get('closed'))
189 my = str2bool(req_get.get('my'))
189 my = str2bool(req_get.get('my'))
190 awaiting_review = str2bool(req_get.get('awaiting_review'))
190 awaiting_review = str2bool(req_get.get('awaiting_review'))
191 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
191 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
192
192
193 filter_type = 'awaiting_review' if awaiting_review \
193 filter_type = 'awaiting_review' if awaiting_review \
194 else 'awaiting_my_review' if awaiting_my_review \
194 else 'awaiting_my_review' if awaiting_my_review \
195 else None
195 else None
196
196
197 opened_by = None
197 opened_by = None
198 if my:
198 if my:
199 opened_by = [self._rhodecode_user.user_id]
199 opened_by = [self._rhodecode_user.user_id]
200
200
201 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
201 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
202 if closed:
202 if closed:
203 statuses = [PullRequest.STATUS_CLOSED]
203 statuses = [PullRequest.STATUS_CLOSED]
204
204
205 data = self._get_pull_requests_list(
205 data = self._get_pull_requests_list(
206 repo_name=self.db_repo_name, source=source,
206 repo_name=self.db_repo_name, source=source,
207 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
207 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
208
208
209 return data
209 return data
210
210
211 def _is_diff_cache_enabled(self, target_repo):
211 def _is_diff_cache_enabled(self, target_repo):
212 caching_enabled = self._get_general_setting(
212 caching_enabled = self._get_general_setting(
213 target_repo, 'rhodecode_diff_cache')
213 target_repo, 'rhodecode_diff_cache')
214 log.debug('Diff caching enabled: %s', caching_enabled)
214 log.debug('Diff caching enabled: %s', caching_enabled)
215 return caching_enabled
215 return caching_enabled
216
216
217 def _get_diffset(self, source_repo_name, source_repo,
217 def _get_diffset(self, source_repo_name, source_repo,
218 ancestor_commit,
218 ancestor_commit,
219 source_ref_id, target_ref_id,
219 source_ref_id, target_ref_id,
220 target_commit, source_commit, diff_limit, file_limit,
220 target_commit, source_commit, diff_limit, file_limit,
221 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
221 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
222
222
223 if use_ancestor:
223 if use_ancestor:
224 # we might want to not use it for versions
224 # we might want to not use it for versions
225 target_ref_id = ancestor_commit.raw_id
225 target_ref_id = ancestor_commit.raw_id
226
226
227 vcs_diff = PullRequestModel().get_diff(
227 vcs_diff = PullRequestModel().get_diff(
228 source_repo, source_ref_id, target_ref_id,
228 source_repo, source_ref_id, target_ref_id,
229 hide_whitespace_changes, diff_context)
229 hide_whitespace_changes, diff_context)
230
230
231 diff_processor = diffs.DiffProcessor(
231 diff_processor = diffs.DiffProcessor(
232 vcs_diff, format='newdiff', diff_limit=diff_limit,
232 vcs_diff, format='newdiff', diff_limit=diff_limit,
233 file_limit=file_limit, show_full_diff=fulldiff)
233 file_limit=file_limit, show_full_diff=fulldiff)
234
234
235 _parsed = diff_processor.prepare()
235 _parsed = diff_processor.prepare()
236
236
237 diffset = codeblocks.DiffSet(
237 diffset = codeblocks.DiffSet(
238 repo_name=self.db_repo_name,
238 repo_name=self.db_repo_name,
239 source_repo_name=source_repo_name,
239 source_repo_name=source_repo_name,
240 source_node_getter=codeblocks.diffset_node_getter(target_commit),
240 source_node_getter=codeblocks.diffset_node_getter(target_commit),
241 target_node_getter=codeblocks.diffset_node_getter(source_commit),
241 target_node_getter=codeblocks.diffset_node_getter(source_commit),
242 )
242 )
243 diffset = self.path_filter.render_patchset_filtered(
243 diffset = self.path_filter.render_patchset_filtered(
244 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
244 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
245
245
246 return diffset
246 return diffset
247
247
248 def _get_range_diffset(self, source_scm, source_repo,
248 def _get_range_diffset(self, source_scm, source_repo,
249 commit1, commit2, diff_limit, file_limit,
249 commit1, commit2, diff_limit, file_limit,
250 fulldiff, hide_whitespace_changes, diff_context):
250 fulldiff, hide_whitespace_changes, diff_context):
251 vcs_diff = source_scm.get_diff(
251 vcs_diff = source_scm.get_diff(
252 commit1, commit2,
252 commit1, commit2,
253 ignore_whitespace=hide_whitespace_changes,
253 ignore_whitespace=hide_whitespace_changes,
254 context=diff_context)
254 context=diff_context)
255
255
256 diff_processor = diffs.DiffProcessor(
256 diff_processor = diffs.DiffProcessor(
257 vcs_diff, format='newdiff', diff_limit=diff_limit,
257 vcs_diff, format='newdiff', diff_limit=diff_limit,
258 file_limit=file_limit, show_full_diff=fulldiff)
258 file_limit=file_limit, show_full_diff=fulldiff)
259
259
260 _parsed = diff_processor.prepare()
260 _parsed = diff_processor.prepare()
261
261
262 diffset = codeblocks.DiffSet(
262 diffset = codeblocks.DiffSet(
263 repo_name=source_repo.repo_name,
263 repo_name=source_repo.repo_name,
264 source_node_getter=codeblocks.diffset_node_getter(commit1),
264 source_node_getter=codeblocks.diffset_node_getter(commit1),
265 target_node_getter=codeblocks.diffset_node_getter(commit2))
265 target_node_getter=codeblocks.diffset_node_getter(commit2))
266
266
267 diffset = self.path_filter.render_patchset_filtered(
267 diffset = self.path_filter.render_patchset_filtered(
268 diffset, _parsed, commit1.raw_id, commit2.raw_id)
268 diffset, _parsed, commit1.raw_id, commit2.raw_id)
269
269
270 return diffset
270 return diffset
271
271
272 def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
272 def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
273 comments_model = CommentsModel()
273 comments_model = CommentsModel()
274
274
275 # GENERAL COMMENTS with versions #
275 # GENERAL COMMENTS with versions #
276 q = comments_model._all_general_comments_of_pull_request(pull_request)
276 q = comments_model._all_general_comments_of_pull_request(pull_request)
277 q = q.order_by(ChangesetComment.comment_id.asc())
277 q = q.order_by(ChangesetComment.comment_id.asc())
278 if not include_drafts:
278 if not include_drafts:
279 q = q.filter(ChangesetComment.draft == false())
279 q = q.filter(ChangesetComment.draft == false())
280 general_comments = q
280 general_comments = q
281
281
282 # pick comments we want to render at current version
282 # pick comments we want to render at current version
283 c.comment_versions = comments_model.aggregate_comments(
283 c.comment_versions = comments_model.aggregate_comments(
284 general_comments, versions, c.at_version_num)
284 general_comments, versions, c.at_version_num)
285
285
286 # INLINE COMMENTS with versions #
286 # INLINE COMMENTS with versions #
287 q = comments_model._all_inline_comments_of_pull_request(pull_request)
287 q = comments_model._all_inline_comments_of_pull_request(pull_request)
288 q = q.order_by(ChangesetComment.comment_id.asc())
288 q = q.order_by(ChangesetComment.comment_id.asc())
289 if not include_drafts:
289 if not include_drafts:
290 q = q.filter(ChangesetComment.draft == false())
290 q = q.filter(ChangesetComment.draft == false())
291 inline_comments = q
291 inline_comments = q
292
292
293 c.inline_versions = comments_model.aggregate_comments(
293 c.inline_versions = comments_model.aggregate_comments(
294 inline_comments, versions, c.at_version_num, inline=True)
294 inline_comments, versions, c.at_version_num, inline=True)
295
295
296 # Comments inline+general
296 # Comments inline+general
297 if c.at_version:
297 if c.at_version:
298 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
298 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
299 c.comments = c.comment_versions[c.at_version_num]['display']
299 c.comments = c.comment_versions[c.at_version_num]['display']
300 else:
300 else:
301 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
301 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
302 c.comments = c.comment_versions[c.at_version_num]['until']
302 c.comments = c.comment_versions[c.at_version_num]['until']
303
303
304 return general_comments, inline_comments
304 return general_comments, inline_comments
305
305
306 @LoginRequired()
306 @LoginRequired()
307 @HasRepoPermissionAnyDecorator(
307 @HasRepoPermissionAnyDecorator(
308 'repository.read', 'repository.write', 'repository.admin')
308 'repository.read', 'repository.write', 'repository.admin')
309 @view_config(
309 @view_config(
310 route_name='pullrequest_show', request_method='GET',
310 route_name='pullrequest_show', request_method='GET',
311 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
311 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
312 def pull_request_show(self):
312 def pull_request_show(self):
313 _ = self.request.translate
313 _ = self.request.translate
314 c = self.load_default_context()
314 c = self.load_default_context()
315
315
316 pull_request = PullRequest.get_or_404(
316 pull_request = PullRequest.get_or_404(
317 self.request.matchdict['pull_request_id'])
317 self.request.matchdict['pull_request_id'])
318 pull_request_id = pull_request.pull_request_id
318 pull_request_id = pull_request.pull_request_id
319
319
320 c.state_progressing = pull_request.is_state_changing()
320 c.state_progressing = pull_request.is_state_changing()
321 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
321 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
322
322
323 _new_state = {
323 _new_state = {
324 'created': PullRequest.STATE_CREATED,
324 'created': PullRequest.STATE_CREATED,
325 }.get(self.request.GET.get('force_state'))
325 }.get(self.request.GET.get('force_state'))
326
326
327 if c.is_super_admin and _new_state:
327 if c.is_super_admin and _new_state:
328 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
328 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
329 h.flash(
329 h.flash(
330 _('Pull Request state was force changed to `{}`').format(_new_state),
330 _('Pull Request state was force changed to `{}`').format(_new_state),
331 category='success')
331 category='success')
332 Session().commit()
332 Session().commit()
333
333
334 raise HTTPFound(h.route_path(
334 raise HTTPFound(h.route_path(
335 'pullrequest_show', repo_name=self.db_repo_name,
335 'pullrequest_show', repo_name=self.db_repo_name,
336 pull_request_id=pull_request_id))
336 pull_request_id=pull_request_id))
337
337
338 version = self.request.GET.get('version')
338 version = self.request.GET.get('version')
339 from_version = self.request.GET.get('from_version') or version
339 from_version = self.request.GET.get('from_version') or version
340 merge_checks = self.request.GET.get('merge_checks')
340 merge_checks = self.request.GET.get('merge_checks')
341 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
341 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
342 force_refresh = str2bool(self.request.GET.get('force_refresh'))
342 force_refresh = str2bool(self.request.GET.get('force_refresh'))
343 c.range_diff_on = self.request.GET.get('range-diff') == "1"
343 c.range_diff_on = self.request.GET.get('range-diff') == "1"
344
344
345 # fetch global flags of ignore ws or context lines
345 # fetch global flags of ignore ws or context lines
346 diff_context = diffs.get_diff_context(self.request)
346 diff_context = diffs.get_diff_context(self.request)
347 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
347 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
348
348
349 (pull_request_latest,
349 (pull_request_latest,
350 pull_request_at_ver,
350 pull_request_at_ver,
351 pull_request_display_obj,
351 pull_request_display_obj,
352 at_version) = PullRequestModel().get_pr_version(
352 at_version) = PullRequestModel().get_pr_version(
353 pull_request_id, version=version)
353 pull_request_id, version=version)
354
354
355 pr_closed = pull_request_latest.is_closed()
355 pr_closed = pull_request_latest.is_closed()
356
356
357 if pr_closed and (version or from_version):
357 if pr_closed and (version or from_version):
358 # not allow to browse versions for closed PR
358 # not allow to browse versions for closed PR
359 raise HTTPFound(h.route_path(
359 raise HTTPFound(h.route_path(
360 'pullrequest_show', repo_name=self.db_repo_name,
360 'pullrequest_show', repo_name=self.db_repo_name,
361 pull_request_id=pull_request_id))
361 pull_request_id=pull_request_id))
362
362
363 versions = pull_request_display_obj.versions()
363 versions = pull_request_display_obj.versions()
364 # used to store per-commit range diffs
364 # used to store per-commit range diffs
365 c.changes = collections.OrderedDict()
365 c.changes = collections.OrderedDict()
366
366
367 c.at_version = at_version
367 c.at_version = at_version
368 c.at_version_num = (at_version
368 c.at_version_num = (at_version
369 if at_version and at_version != PullRequest.LATEST_VER
369 if at_version and at_version != PullRequest.LATEST_VER
370 else None)
370 else None)
371
371
372 c.at_version_index = ChangesetComment.get_index_from_version(
372 c.at_version_index = ChangesetComment.get_index_from_version(
373 c.at_version_num, versions)
373 c.at_version_num, versions)
374
374
375 (prev_pull_request_latest,
375 (prev_pull_request_latest,
376 prev_pull_request_at_ver,
376 prev_pull_request_at_ver,
377 prev_pull_request_display_obj,
377 prev_pull_request_display_obj,
378 prev_at_version) = PullRequestModel().get_pr_version(
378 prev_at_version) = PullRequestModel().get_pr_version(
379 pull_request_id, version=from_version)
379 pull_request_id, version=from_version)
380
380
381 c.from_version = prev_at_version
381 c.from_version = prev_at_version
382 c.from_version_num = (prev_at_version
382 c.from_version_num = (prev_at_version
383 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
383 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
384 else None)
384 else None)
385 c.from_version_index = ChangesetComment.get_index_from_version(
385 c.from_version_index = ChangesetComment.get_index_from_version(
386 c.from_version_num, versions)
386 c.from_version_num, versions)
387
387
388 # define if we're in COMPARE mode or VIEW at version mode
388 # define if we're in COMPARE mode or VIEW at version mode
389 compare = at_version != prev_at_version
389 compare = at_version != prev_at_version
390
390
391 # pull_requests repo_name we opened it against
391 # pull_requests repo_name we opened it against
392 # ie. target_repo must match
392 # ie. target_repo must match
393 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
393 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
394 log.warning('Mismatch between the current repo: %s, and target %s',
394 log.warning('Mismatch between the current repo: %s, and target %s',
395 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
395 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
396 raise HTTPNotFound()
396 raise HTTPNotFound()
397
397
398 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
398 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
399
399
400 c.pull_request = pull_request_display_obj
400 c.pull_request = pull_request_display_obj
401 c.renderer = pull_request_at_ver.description_renderer or c.renderer
401 c.renderer = pull_request_at_ver.description_renderer or c.renderer
402 c.pull_request_latest = pull_request_latest
402 c.pull_request_latest = pull_request_latest
403
403
404 # inject latest version
404 # inject latest version
405 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
405 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
406 c.versions = versions + [latest_ver]
406 c.versions = versions + [latest_ver]
407
407
408 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
408 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
409 c.allowed_to_change_status = False
409 c.allowed_to_change_status = False
410 c.allowed_to_update = False
410 c.allowed_to_update = False
411 c.allowed_to_merge = False
411 c.allowed_to_merge = False
412 c.allowed_to_delete = False
412 c.allowed_to_delete = False
413 c.allowed_to_comment = False
413 c.allowed_to_comment = False
414 c.allowed_to_close = False
414 c.allowed_to_close = False
415 else:
415 else:
416 can_change_status = PullRequestModel().check_user_change_status(
416 can_change_status = PullRequestModel().check_user_change_status(
417 pull_request_at_ver, self._rhodecode_user)
417 pull_request_at_ver, self._rhodecode_user)
418 c.allowed_to_change_status = can_change_status and not pr_closed
418 c.allowed_to_change_status = can_change_status and not pr_closed
419
419
420 c.allowed_to_update = PullRequestModel().check_user_update(
420 c.allowed_to_update = PullRequestModel().check_user_update(
421 pull_request_latest, self._rhodecode_user) and not pr_closed
421 pull_request_latest, self._rhodecode_user) and not pr_closed
422 c.allowed_to_merge = PullRequestModel().check_user_merge(
422 c.allowed_to_merge = PullRequestModel().check_user_merge(
423 pull_request_latest, self._rhodecode_user) and not pr_closed
423 pull_request_latest, self._rhodecode_user) and not pr_closed
424 c.allowed_to_delete = PullRequestModel().check_user_delete(
424 c.allowed_to_delete = PullRequestModel().check_user_delete(
425 pull_request_latest, self._rhodecode_user) and not pr_closed
425 pull_request_latest, self._rhodecode_user) and not pr_closed
426 c.allowed_to_comment = not pr_closed
426 c.allowed_to_comment = not pr_closed
427 c.allowed_to_close = c.allowed_to_merge and not pr_closed
427 c.allowed_to_close = c.allowed_to_merge and not pr_closed
428
428
429 c.forbid_adding_reviewers = False
429 c.forbid_adding_reviewers = False
430 c.forbid_author_to_review = False
430 c.forbid_author_to_review = False
431 c.forbid_commit_author_to_review = False
431 c.forbid_commit_author_to_review = False
432
432
433 if pull_request_latest.reviewer_data and \
433 if pull_request_latest.reviewer_data and \
434 'rules' in pull_request_latest.reviewer_data:
434 'rules' in pull_request_latest.reviewer_data:
435 rules = pull_request_latest.reviewer_data['rules'] or {}
435 rules = pull_request_latest.reviewer_data['rules'] or {}
436 try:
436 try:
437 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
437 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
438 c.forbid_author_to_review = rules.get('forbid_author_to_review')
438 c.forbid_author_to_review = rules.get('forbid_author_to_review')
439 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
439 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
440 except Exception:
440 except Exception:
441 pass
441 pass
442
442
443 # check merge capabilities
443 # check merge capabilities
444 _merge_check = MergeCheck.validate(
444 _merge_check = MergeCheck.validate(
445 pull_request_latest, auth_user=self._rhodecode_user,
445 pull_request_latest, auth_user=self._rhodecode_user,
446 translator=self.request.translate,
446 translator=self.request.translate,
447 force_shadow_repo_refresh=force_refresh)
447 force_shadow_repo_refresh=force_refresh)
448
448
449 c.pr_merge_errors = _merge_check.error_details
449 c.pr_merge_errors = _merge_check.error_details
450 c.pr_merge_possible = not _merge_check.failed
450 c.pr_merge_possible = not _merge_check.failed
451 c.pr_merge_message = _merge_check.merge_msg
451 c.pr_merge_message = _merge_check.merge_msg
452 c.pr_merge_source_commit = _merge_check.source_commit
452 c.pr_merge_source_commit = _merge_check.source_commit
453 c.pr_merge_target_commit = _merge_check.target_commit
453 c.pr_merge_target_commit = _merge_check.target_commit
454
454
455 c.pr_merge_info = MergeCheck.get_merge_conditions(
455 c.pr_merge_info = MergeCheck.get_merge_conditions(
456 pull_request_latest, translator=self.request.translate)
456 pull_request_latest, translator=self.request.translate)
457
457
458 c.pull_request_review_status = _merge_check.review_status
458 c.pull_request_review_status = _merge_check.review_status
459 if merge_checks:
459 if merge_checks:
460 self.request.override_renderer = \
460 self.request.override_renderer = \
461 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
461 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
462 return self._get_template_context(c)
462 return self._get_template_context(c)
463
463
464 c.reviewers_count = pull_request.reviewers_count
464 c.reviewers_count = pull_request.reviewers_count
465 c.observers_count = pull_request.observers_count
465 c.observers_count = pull_request.observers_count
466
466
467 # reviewers and statuses
467 # reviewers and statuses
468 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
468 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
469 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
469 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
470 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
470 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
471
471
472 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
472 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
473 member_reviewer = h.reviewer_as_json(
473 member_reviewer = h.reviewer_as_json(
474 member, reasons=reasons, mandatory=mandatory,
474 member, reasons=reasons, mandatory=mandatory,
475 role=review_obj.role,
475 role=review_obj.role,
476 user_group=review_obj.rule_user_group_data()
476 user_group=review_obj.rule_user_group_data()
477 )
477 )
478
478
479 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
479 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
480 member_reviewer['review_status'] = current_review_status
480 member_reviewer['review_status'] = current_review_status
481 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
481 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
482 member_reviewer['allowed_to_update'] = c.allowed_to_update
482 member_reviewer['allowed_to_update'] = c.allowed_to_update
483 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
483 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
484
484
485 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
485 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
486
486
487 for observer_obj, member in pull_request_at_ver.observers():
487 for observer_obj, member in pull_request_at_ver.observers():
488 member_observer = h.reviewer_as_json(
488 member_observer = h.reviewer_as_json(
489 member, reasons=[], mandatory=False,
489 member, reasons=[], mandatory=False,
490 role=observer_obj.role,
490 role=observer_obj.role,
491 user_group=observer_obj.rule_user_group_data()
491 user_group=observer_obj.rule_user_group_data()
492 )
492 )
493 member_observer['allowed_to_update'] = c.allowed_to_update
493 member_observer['allowed_to_update'] = c.allowed_to_update
494 c.pull_request_set_observers_data_json['observers'].append(member_observer)
494 c.pull_request_set_observers_data_json['observers'].append(member_observer)
495
495
496 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
496 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
497
497
498 general_comments, inline_comments = \
498 general_comments, inline_comments = \
499 self.register_comments_vars(c, pull_request_latest, versions)
499 self.register_comments_vars(c, pull_request_latest, versions)
500
500
501 # TODOs
501 # TODOs
502 c.unresolved_comments = CommentsModel() \
502 c.unresolved_comments = CommentsModel() \
503 .get_pull_request_unresolved_todos(pull_request_latest)
503 .get_pull_request_unresolved_todos(pull_request_latest)
504 c.resolved_comments = CommentsModel() \
504 c.resolved_comments = CommentsModel() \
505 .get_pull_request_resolved_todos(pull_request_latest)
505 .get_pull_request_resolved_todos(pull_request_latest)
506
506
507 # if we use version, then do not show later comments
507 # if we use version, then do not show later comments
508 # than current version
508 # than current version
509 display_inline_comments = collections.defaultdict(
509 display_inline_comments = collections.defaultdict(
510 lambda: collections.defaultdict(list))
510 lambda: collections.defaultdict(list))
511 for co in inline_comments:
511 for co in inline_comments:
512 if c.at_version_num:
512 if c.at_version_num:
513 # pick comments that are at least UPTO given version, so we
513 # pick comments that are at least UPTO given version, so we
514 # don't render comments for higher version
514 # don't render comments for higher version
515 should_render = co.pull_request_version_id and \
515 should_render = co.pull_request_version_id and \
516 co.pull_request_version_id <= c.at_version_num
516 co.pull_request_version_id <= c.at_version_num
517 else:
517 else:
518 # showing all, for 'latest'
518 # showing all, for 'latest'
519 should_render = True
519 should_render = True
520
520
521 if should_render:
521 if should_render:
522 display_inline_comments[co.f_path][co.line_no].append(co)
522 display_inline_comments[co.f_path][co.line_no].append(co)
523
523
524 # load diff data into template context, if we use compare mode then
524 # load diff data into template context, if we use compare mode then
525 # diff is calculated based on changes between versions of PR
525 # diff is calculated based on changes between versions of PR
526
526
527 source_repo = pull_request_at_ver.source_repo
527 source_repo = pull_request_at_ver.source_repo
528 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
528 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
529
529
530 target_repo = pull_request_at_ver.target_repo
530 target_repo = pull_request_at_ver.target_repo
531 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
531 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
532
532
533 if compare:
533 if compare:
534 # in compare switch the diff base to latest commit from prev version
534 # in compare switch the diff base to latest commit from prev version
535 target_ref_id = prev_pull_request_display_obj.revisions[0]
535 target_ref_id = prev_pull_request_display_obj.revisions[0]
536
536
537 # despite opening commits for bookmarks/branches/tags, we always
537 # despite opening commits for bookmarks/branches/tags, we always
538 # convert this to rev to prevent changes after bookmark or branch change
538 # convert this to rev to prevent changes after bookmark or branch change
539 c.source_ref_type = 'rev'
539 c.source_ref_type = 'rev'
540 c.source_ref = source_ref_id
540 c.source_ref = source_ref_id
541
541
542 c.target_ref_type = 'rev'
542 c.target_ref_type = 'rev'
543 c.target_ref = target_ref_id
543 c.target_ref = target_ref_id
544
544
545 c.source_repo = source_repo
545 c.source_repo = source_repo
546 c.target_repo = target_repo
546 c.target_repo = target_repo
547
547
548 c.commit_ranges = []
548 c.commit_ranges = []
549 source_commit = EmptyCommit()
549 source_commit = EmptyCommit()
550 target_commit = EmptyCommit()
550 target_commit = EmptyCommit()
551 c.missing_requirements = False
551 c.missing_requirements = False
552
552
553 source_scm = source_repo.scm_instance()
553 source_scm = source_repo.scm_instance()
554 target_scm = target_repo.scm_instance()
554 target_scm = target_repo.scm_instance()
555
555
556 shadow_scm = None
556 shadow_scm = None
557 try:
557 try:
558 shadow_scm = pull_request_latest.get_shadow_repo()
558 shadow_scm = pull_request_latest.get_shadow_repo()
559 except Exception:
559 except Exception:
560 log.debug('Failed to get shadow repo', exc_info=True)
560 log.debug('Failed to get shadow repo', exc_info=True)
561 # try first the existing source_repo, and then shadow
561 # try first the existing source_repo, and then shadow
562 # repo if we can obtain one
562 # repo if we can obtain one
563 commits_source_repo = source_scm
563 commits_source_repo = source_scm
564 if shadow_scm:
564 if shadow_scm:
565 commits_source_repo = shadow_scm
565 commits_source_repo = shadow_scm
566
566
567 c.commits_source_repo = commits_source_repo
567 c.commits_source_repo = commits_source_repo
568 c.ancestor = None # set it to None, to hide it from PR view
568 c.ancestor = None # set it to None, to hide it from PR view
569
569
570 # empty version means latest, so we keep this to prevent
570 # empty version means latest, so we keep this to prevent
571 # double caching
571 # double caching
572 version_normalized = version or PullRequest.LATEST_VER
572 version_normalized = version or PullRequest.LATEST_VER
573 from_version_normalized = from_version or PullRequest.LATEST_VER
573 from_version_normalized = from_version or PullRequest.LATEST_VER
574
574
575 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
575 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
576 cache_file_path = diff_cache_exist(
576 cache_file_path = diff_cache_exist(
577 cache_path, 'pull_request', pull_request_id, version_normalized,
577 cache_path, 'pull_request', pull_request_id, version_normalized,
578 from_version_normalized, source_ref_id, target_ref_id,
578 from_version_normalized, source_ref_id, target_ref_id,
579 hide_whitespace_changes, diff_context, c.fulldiff)
579 hide_whitespace_changes, diff_context, c.fulldiff)
580
580
581 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
581 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
582 force_recache = self.get_recache_flag()
582 force_recache = self.get_recache_flag()
583
583
584 cached_diff = None
584 cached_diff = None
585 if caching_enabled:
585 if caching_enabled:
586 cached_diff = load_cached_diff(cache_file_path)
586 cached_diff = load_cached_diff(cache_file_path)
587
587
588 has_proper_commit_cache = (
588 has_proper_commit_cache = (
589 cached_diff and cached_diff.get('commits')
589 cached_diff and cached_diff.get('commits')
590 and len(cached_diff.get('commits', [])) == 5
590 and len(cached_diff.get('commits', [])) == 5
591 and cached_diff.get('commits')[0]
591 and cached_diff.get('commits')[0]
592 and cached_diff.get('commits')[3])
592 and cached_diff.get('commits')[3])
593
593
594 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
594 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
595 diff_commit_cache = \
595 diff_commit_cache = \
596 (ancestor_commit, commit_cache, missing_requirements,
596 (ancestor_commit, commit_cache, missing_requirements,
597 source_commit, target_commit) = cached_diff['commits']
597 source_commit, target_commit) = cached_diff['commits']
598 else:
598 else:
599 # NOTE(marcink): we reach potentially unreachable errors when a PR has
599 # NOTE(marcink): we reach potentially unreachable errors when a PR has
600 # merge errors resulting in potentially hidden commits in the shadow repo.
600 # merge errors resulting in potentially hidden commits in the shadow repo.
601 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
601 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
602 and _merge_check.merge_response
602 and _merge_check.merge_response
603 maybe_unreachable = maybe_unreachable \
603 maybe_unreachable = maybe_unreachable \
604 and _merge_check.merge_response.metadata.get('unresolved_files')
604 and _merge_check.merge_response.metadata.get('unresolved_files')
605 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
605 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
606 diff_commit_cache = \
606 diff_commit_cache = \
607 (ancestor_commit, commit_cache, missing_requirements,
607 (ancestor_commit, commit_cache, missing_requirements,
608 source_commit, target_commit) = self.get_commits(
608 source_commit, target_commit) = self.get_commits(
609 commits_source_repo,
609 commits_source_repo,
610 pull_request_at_ver,
610 pull_request_at_ver,
611 source_commit,
611 source_commit,
612 source_ref_id,
612 source_ref_id,
613 source_scm,
613 source_scm,
614 target_commit,
614 target_commit,
615 target_ref_id,
615 target_ref_id,
616 target_scm,
616 target_scm,
617 maybe_unreachable=maybe_unreachable)
617 maybe_unreachable=maybe_unreachable)
618
618
619 # register our commit range
619 # register our commit range
620 for comm in commit_cache.values():
620 for comm in commit_cache.values():
621 c.commit_ranges.append(comm)
621 c.commit_ranges.append(comm)
622
622
623 c.missing_requirements = missing_requirements
623 c.missing_requirements = missing_requirements
624 c.ancestor_commit = ancestor_commit
624 c.ancestor_commit = ancestor_commit
625 c.statuses = source_repo.statuses(
625 c.statuses = source_repo.statuses(
626 [x.raw_id for x in c.commit_ranges])
626 [x.raw_id for x in c.commit_ranges])
627
627
628 # auto collapse if we have more than limit
628 # auto collapse if we have more than limit
629 collapse_limit = diffs.DiffProcessor._collapse_commits_over
629 collapse_limit = diffs.DiffProcessor._collapse_commits_over
630 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
630 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
631 c.compare_mode = compare
631 c.compare_mode = compare
632
632
633 # diff_limit is the old behavior, will cut off the whole diff
633 # diff_limit is the old behavior, will cut off the whole diff
634 # if the limit is applied otherwise will just hide the
634 # if the limit is applied otherwise will just hide the
635 # big files from the front-end
635 # big files from the front-end
636 diff_limit = c.visual.cut_off_limit_diff
636 diff_limit = c.visual.cut_off_limit_diff
637 file_limit = c.visual.cut_off_limit_file
637 file_limit = c.visual.cut_off_limit_file
638
638
639 c.missing_commits = False
639 c.missing_commits = False
640 if (c.missing_requirements
640 if (c.missing_requirements
641 or isinstance(source_commit, EmptyCommit)
641 or isinstance(source_commit, EmptyCommit)
642 or source_commit == target_commit):
642 or source_commit == target_commit):
643
643
644 c.missing_commits = True
644 c.missing_commits = True
645 else:
645 else:
646 c.inline_comments = display_inline_comments
646 c.inline_comments = display_inline_comments
647
647
648 use_ancestor = True
648 use_ancestor = True
649 if from_version_normalized != version_normalized:
649 if from_version_normalized != version_normalized:
650 use_ancestor = False
650 use_ancestor = False
651
651
652 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
652 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
653 if not force_recache and has_proper_diff_cache:
653 if not force_recache and has_proper_diff_cache:
654 c.diffset = cached_diff['diff']
654 c.diffset = cached_diff['diff']
655 else:
655 else:
656 try:
656 try:
657 c.diffset = self._get_diffset(
657 c.diffset = self._get_diffset(
658 c.source_repo.repo_name, commits_source_repo,
658 c.source_repo.repo_name, commits_source_repo,
659 c.ancestor_commit,
659 c.ancestor_commit,
660 source_ref_id, target_ref_id,
660 source_ref_id, target_ref_id,
661 target_commit, source_commit,
661 target_commit, source_commit,
662 diff_limit, file_limit, c.fulldiff,
662 diff_limit, file_limit, c.fulldiff,
663 hide_whitespace_changes, diff_context,
663 hide_whitespace_changes, diff_context,
664 use_ancestor=use_ancestor
664 use_ancestor=use_ancestor
665 )
665 )
666
666
667 # save cached diff
667 # save cached diff
668 if caching_enabled:
668 if caching_enabled:
669 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
669 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
670 except CommitDoesNotExistError:
670 except CommitDoesNotExistError:
671 log.exception('Failed to generate diffset')
671 log.exception('Failed to generate diffset')
672 c.missing_commits = True
672 c.missing_commits = True
673
673
674 if not c.missing_commits:
674 if not c.missing_commits:
675
675
676 c.limited_diff = c.diffset.limited_diff
676 c.limited_diff = c.diffset.limited_diff
677
677
678 # calculate removed files that are bound to comments
678 # calculate removed files that are bound to comments
679 comment_deleted_files = [
679 comment_deleted_files = [
680 fname for fname in display_inline_comments
680 fname for fname in display_inline_comments
681 if fname not in c.diffset.file_stats]
681 if fname not in c.diffset.file_stats]
682
682
683 c.deleted_files_comments = collections.defaultdict(dict)
683 c.deleted_files_comments = collections.defaultdict(dict)
684 for fname, per_line_comments in display_inline_comments.items():
684 for fname, per_line_comments in display_inline_comments.items():
685 if fname in comment_deleted_files:
685 if fname in comment_deleted_files:
686 c.deleted_files_comments[fname]['stats'] = 0
686 c.deleted_files_comments[fname]['stats'] = 0
687 c.deleted_files_comments[fname]['comments'] = list()
687 c.deleted_files_comments[fname]['comments'] = list()
688 for lno, comments in per_line_comments.items():
688 for lno, comments in per_line_comments.items():
689 c.deleted_files_comments[fname]['comments'].extend(comments)
689 c.deleted_files_comments[fname]['comments'].extend(comments)
690
690
691 # maybe calculate the range diff
691 # maybe calculate the range diff
692 if c.range_diff_on:
692 if c.range_diff_on:
693 # TODO(marcink): set whitespace/context
693 # TODO(marcink): set whitespace/context
694 context_lcl = 3
694 context_lcl = 3
695 ign_whitespace_lcl = False
695 ign_whitespace_lcl = False
696
696
697 for commit in c.commit_ranges:
697 for commit in c.commit_ranges:
698 commit2 = commit
698 commit2 = commit
699 commit1 = commit.first_parent
699 commit1 = commit.first_parent
700
700
701 range_diff_cache_file_path = diff_cache_exist(
701 range_diff_cache_file_path = diff_cache_exist(
702 cache_path, 'diff', commit.raw_id,
702 cache_path, 'diff', commit.raw_id,
703 ign_whitespace_lcl, context_lcl, c.fulldiff)
703 ign_whitespace_lcl, context_lcl, c.fulldiff)
704
704
705 cached_diff = None
705 cached_diff = None
706 if caching_enabled:
706 if caching_enabled:
707 cached_diff = load_cached_diff(range_diff_cache_file_path)
707 cached_diff = load_cached_diff(range_diff_cache_file_path)
708
708
709 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
709 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
710 if not force_recache and has_proper_diff_cache:
710 if not force_recache and has_proper_diff_cache:
711 diffset = cached_diff['diff']
711 diffset = cached_diff['diff']
712 else:
712 else:
713 diffset = self._get_range_diffset(
713 diffset = self._get_range_diffset(
714 commits_source_repo, source_repo,
714 commits_source_repo, source_repo,
715 commit1, commit2, diff_limit, file_limit,
715 commit1, commit2, diff_limit, file_limit,
716 c.fulldiff, ign_whitespace_lcl, context_lcl
716 c.fulldiff, ign_whitespace_lcl, context_lcl
717 )
717 )
718
718
719 # save cached diff
719 # save cached diff
720 if caching_enabled:
720 if caching_enabled:
721 cache_diff(range_diff_cache_file_path, diffset, None)
721 cache_diff(range_diff_cache_file_path, diffset, None)
722
722
723 c.changes[commit.raw_id] = diffset
723 c.changes[commit.raw_id] = diffset
724
724
725 # this is a hack to properly display links, when creating PR, the
725 # this is a hack to properly display links, when creating PR, the
726 # compare view and others uses different notation, and
726 # compare view and others uses different notation, and
727 # compare_commits.mako renders links based on the target_repo.
727 # compare_commits.mako renders links based on the target_repo.
728 # We need to swap that here to generate it properly on the html side
728 # We need to swap that here to generate it properly on the html side
729 c.target_repo = c.source_repo
729 c.target_repo = c.source_repo
730
730
731 c.commit_statuses = ChangesetStatus.STATUSES
731 c.commit_statuses = ChangesetStatus.STATUSES
732
732
733 c.show_version_changes = not pr_closed
733 c.show_version_changes = not pr_closed
734 if c.show_version_changes:
734 if c.show_version_changes:
735 cur_obj = pull_request_at_ver
735 cur_obj = pull_request_at_ver
736 prev_obj = prev_pull_request_at_ver
736 prev_obj = prev_pull_request_at_ver
737
737
738 old_commit_ids = prev_obj.revisions
738 old_commit_ids = prev_obj.revisions
739 new_commit_ids = cur_obj.revisions
739 new_commit_ids = cur_obj.revisions
740 commit_changes = PullRequestModel()._calculate_commit_id_changes(
740 commit_changes = PullRequestModel()._calculate_commit_id_changes(
741 old_commit_ids, new_commit_ids)
741 old_commit_ids, new_commit_ids)
742 c.commit_changes_summary = commit_changes
742 c.commit_changes_summary = commit_changes
743
743
744 # calculate the diff for commits between versions
744 # calculate the diff for commits between versions
745 c.commit_changes = []
745 c.commit_changes = []
746
746
747 def mark(cs, fw):
747 def mark(cs, fw):
748 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
748 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
749
749
750 for c_type, raw_id in mark(commit_changes.added, 'a') \
750 for c_type, raw_id in mark(commit_changes.added, 'a') \
751 + mark(commit_changes.removed, 'r') \
751 + mark(commit_changes.removed, 'r') \
752 + mark(commit_changes.common, 'c'):
752 + mark(commit_changes.common, 'c'):
753
753
754 if raw_id in commit_cache:
754 if raw_id in commit_cache:
755 commit = commit_cache[raw_id]
755 commit = commit_cache[raw_id]
756 else:
756 else:
757 try:
757 try:
758 commit = commits_source_repo.get_commit(raw_id)
758 commit = commits_source_repo.get_commit(raw_id)
759 except CommitDoesNotExistError:
759 except CommitDoesNotExistError:
760 # in case we fail extracting still use "dummy" commit
760 # in case we fail extracting still use "dummy" commit
761 # for display in commit diff
761 # for display in commit diff
762 commit = h.AttributeDict(
762 commit = h.AttributeDict(
763 {'raw_id': raw_id,
763 {'raw_id': raw_id,
764 'message': 'EMPTY or MISSING COMMIT'})
764 'message': 'EMPTY or MISSING COMMIT'})
765 c.commit_changes.append([c_type, commit])
765 c.commit_changes.append([c_type, commit])
766
766
767 # current user review statuses for each version
767 # current user review statuses for each version
768 c.review_versions = {}
768 c.review_versions = {}
769 is_reviewer = PullRequestModel().is_user_reviewer(
769 is_reviewer = PullRequestModel().is_user_reviewer(
770 pull_request, self._rhodecode_user)
770 pull_request, self._rhodecode_user)
771 if is_reviewer:
771 if is_reviewer:
772 for co in general_comments:
772 for co in general_comments:
773 if co.author.user_id == self._rhodecode_user.user_id:
773 if co.author.user_id == self._rhodecode_user.user_id:
774 status = co.status_change
774 status = co.status_change
775 if status:
775 if status:
776 _ver_pr = status[0].comment.pull_request_version_id
776 _ver_pr = status[0].comment.pull_request_version_id
777 c.review_versions[_ver_pr] = status[0]
777 c.review_versions[_ver_pr] = status[0]
778
778
779 return self._get_template_context(c)
779 return self._get_template_context(c)
780
780
781 def get_commits(
781 def get_commits(
782 self, commits_source_repo, pull_request_at_ver, source_commit,
782 self, commits_source_repo, pull_request_at_ver, source_commit,
783 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
783 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
784 maybe_unreachable=False):
784 maybe_unreachable=False):
785
785
786 commit_cache = collections.OrderedDict()
786 commit_cache = collections.OrderedDict()
787 missing_requirements = False
787 missing_requirements = False
788
788
789 try:
789 try:
790 pre_load = ["author", "date", "message", "branch", "parents"]
790 pre_load = ["author", "date", "message", "branch", "parents"]
791
791
792 pull_request_commits = pull_request_at_ver.revisions
792 pull_request_commits = pull_request_at_ver.revisions
793 log.debug('Loading %s commits from %s',
793 log.debug('Loading %s commits from %s',
794 len(pull_request_commits), commits_source_repo)
794 len(pull_request_commits), commits_source_repo)
795
795
796 for rev in pull_request_commits:
796 for rev in pull_request_commits:
797 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
797 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
798 maybe_unreachable=maybe_unreachable)
798 maybe_unreachable=maybe_unreachable)
799 commit_cache[comm.raw_id] = comm
799 commit_cache[comm.raw_id] = comm
800
800
801 # Order here matters, we first need to get target, and then
801 # Order here matters, we first need to get target, and then
802 # the source
802 # the source
803 target_commit = commits_source_repo.get_commit(
803 target_commit = commits_source_repo.get_commit(
804 commit_id=safe_str(target_ref_id))
804 commit_id=safe_str(target_ref_id))
805
805
806 source_commit = commits_source_repo.get_commit(
806 source_commit = commits_source_repo.get_commit(
807 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
807 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
808 except CommitDoesNotExistError:
808 except CommitDoesNotExistError:
809 log.warning('Failed to get commit from `{}` repo'.format(
809 log.warning('Failed to get commit from `{}` repo'.format(
810 commits_source_repo), exc_info=True)
810 commits_source_repo), exc_info=True)
811 except RepositoryRequirementError:
811 except RepositoryRequirementError:
812 log.warning('Failed to get all required data from repo', exc_info=True)
812 log.warning('Failed to get all required data from repo', exc_info=True)
813 missing_requirements = True
813 missing_requirements = True
814
814
815 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
815 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
816
816
817 try:
817 try:
818 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
818 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
819 except Exception:
819 except Exception:
820 ancestor_commit = None
820 ancestor_commit = None
821
821
822 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
822 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
823
823
824 def assure_not_empty_repo(self):
824 def assure_not_empty_repo(self):
825 _ = self.request.translate
825 _ = self.request.translate
826
826
827 try:
827 try:
828 self.db_repo.scm_instance().get_commit()
828 self.db_repo.scm_instance().get_commit()
829 except EmptyRepositoryError:
829 except EmptyRepositoryError:
830 h.flash(h.literal(_('There are no commits yet')),
830 h.flash(h.literal(_('There are no commits yet')),
831 category='warning')
831 category='warning')
832 raise HTTPFound(
832 raise HTTPFound(
833 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
833 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
834
834
835 @LoginRequired()
835 @LoginRequired()
836 @NotAnonymous()
836 @NotAnonymous()
837 @HasRepoPermissionAnyDecorator(
837 @HasRepoPermissionAnyDecorator(
838 'repository.read', 'repository.write', 'repository.admin')
838 'repository.read', 'repository.write', 'repository.admin')
839 @view_config(
839 @view_config(
840 route_name='pullrequest_new', request_method='GET',
840 route_name='pullrequest_new', request_method='GET',
841 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
841 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
842 def pull_request_new(self):
842 def pull_request_new(self):
843 _ = self.request.translate
843 _ = self.request.translate
844 c = self.load_default_context()
844 c = self.load_default_context()
845
845
846 self.assure_not_empty_repo()
846 self.assure_not_empty_repo()
847 source_repo = self.db_repo
847 source_repo = self.db_repo
848
848
849 commit_id = self.request.GET.get('commit')
849 commit_id = self.request.GET.get('commit')
850 branch_ref = self.request.GET.get('branch')
850 branch_ref = self.request.GET.get('branch')
851 bookmark_ref = self.request.GET.get('bookmark')
851 bookmark_ref = self.request.GET.get('bookmark')
852
852
853 try:
853 try:
854 source_repo_data = PullRequestModel().generate_repo_data(
854 source_repo_data = PullRequestModel().generate_repo_data(
855 source_repo, commit_id=commit_id,
855 source_repo, commit_id=commit_id,
856 branch=branch_ref, bookmark=bookmark_ref,
856 branch=branch_ref, bookmark=bookmark_ref,
857 translator=self.request.translate)
857 translator=self.request.translate)
858 except CommitDoesNotExistError as e:
858 except CommitDoesNotExistError as e:
859 log.exception(e)
859 log.exception(e)
860 h.flash(_('Commit does not exist'), 'error')
860 h.flash(_('Commit does not exist'), 'error')
861 raise HTTPFound(
861 raise HTTPFound(
862 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
862 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
863
863
864 default_target_repo = source_repo
864 default_target_repo = source_repo
865
865
866 if source_repo.parent and c.has_origin_repo_read_perm:
866 if source_repo.parent and c.has_origin_repo_read_perm:
867 parent_vcs_obj = source_repo.parent.scm_instance()
867 parent_vcs_obj = source_repo.parent.scm_instance()
868 if parent_vcs_obj and not parent_vcs_obj.is_empty():
868 if parent_vcs_obj and not parent_vcs_obj.is_empty():
869 # change default if we have a parent repo
869 # change default if we have a parent repo
870 default_target_repo = source_repo.parent
870 default_target_repo = source_repo.parent
871
871
872 target_repo_data = PullRequestModel().generate_repo_data(
872 target_repo_data = PullRequestModel().generate_repo_data(
873 default_target_repo, translator=self.request.translate)
873 default_target_repo, translator=self.request.translate)
874
874
875 selected_source_ref = source_repo_data['refs']['selected_ref']
875 selected_source_ref = source_repo_data['refs']['selected_ref']
876 title_source_ref = ''
876 title_source_ref = ''
877 if selected_source_ref:
877 if selected_source_ref:
878 title_source_ref = selected_source_ref.split(':', 2)[1]
878 title_source_ref = selected_source_ref.split(':', 2)[1]
879 c.default_title = PullRequestModel().generate_pullrequest_title(
879 c.default_title = PullRequestModel().generate_pullrequest_title(
880 source=source_repo.repo_name,
880 source=source_repo.repo_name,
881 source_ref=title_source_ref,
881 source_ref=title_source_ref,
882 target=default_target_repo.repo_name
882 target=default_target_repo.repo_name
883 )
883 )
884
884
885 c.default_repo_data = {
885 c.default_repo_data = {
886 'source_repo_name': source_repo.repo_name,
886 'source_repo_name': source_repo.repo_name,
887 'source_refs_json': json.dumps(source_repo_data),
887 'source_refs_json': json.dumps(source_repo_data),
888 'target_repo_name': default_target_repo.repo_name,
888 'target_repo_name': default_target_repo.repo_name,
889 'target_refs_json': json.dumps(target_repo_data),
889 'target_refs_json': json.dumps(target_repo_data),
890 }
890 }
891 c.default_source_ref = selected_source_ref
891 c.default_source_ref = selected_source_ref
892
892
893 return self._get_template_context(c)
893 return self._get_template_context(c)
894
894
895 @LoginRequired()
895 @LoginRequired()
896 @NotAnonymous()
896 @NotAnonymous()
897 @HasRepoPermissionAnyDecorator(
897 @HasRepoPermissionAnyDecorator(
898 'repository.read', 'repository.write', 'repository.admin')
898 'repository.read', 'repository.write', 'repository.admin')
899 @view_config(
899 @view_config(
900 route_name='pullrequest_repo_refs', request_method='GET',
900 route_name='pullrequest_repo_refs', request_method='GET',
901 renderer='json_ext', xhr=True)
901 renderer='json_ext', xhr=True)
902 def pull_request_repo_refs(self):
902 def pull_request_repo_refs(self):
903 self.load_default_context()
903 self.load_default_context()
904 target_repo_name = self.request.matchdict['target_repo_name']
904 target_repo_name = self.request.matchdict['target_repo_name']
905 repo = Repository.get_by_repo_name(target_repo_name)
905 repo = Repository.get_by_repo_name(target_repo_name)
906 if not repo:
906 if not repo:
907 raise HTTPNotFound()
907 raise HTTPNotFound()
908
908
909 target_perm = HasRepoPermissionAny(
909 target_perm = HasRepoPermissionAny(
910 'repository.read', 'repository.write', 'repository.admin')(
910 'repository.read', 'repository.write', 'repository.admin')(
911 target_repo_name)
911 target_repo_name)
912 if not target_perm:
912 if not target_perm:
913 raise HTTPNotFound()
913 raise HTTPNotFound()
914
914
915 return PullRequestModel().generate_repo_data(
915 return PullRequestModel().generate_repo_data(
916 repo, translator=self.request.translate)
916 repo, translator=self.request.translate)
917
917
918 @LoginRequired()
918 @LoginRequired()
919 @NotAnonymous()
919 @NotAnonymous()
920 @HasRepoPermissionAnyDecorator(
920 @HasRepoPermissionAnyDecorator(
921 'repository.read', 'repository.write', 'repository.admin')
921 'repository.read', 'repository.write', 'repository.admin')
922 @view_config(
922 @view_config(
923 route_name='pullrequest_repo_targets', request_method='GET',
923 route_name='pullrequest_repo_targets', request_method='GET',
924 renderer='json_ext', xhr=True)
924 renderer='json_ext', xhr=True)
925 def pullrequest_repo_targets(self):
925 def pullrequest_repo_targets(self):
926 _ = self.request.translate
926 _ = self.request.translate
927 filter_query = self.request.GET.get('query')
927 filter_query = self.request.GET.get('query')
928
928
929 # get the parents
929 # get the parents
930 parent_target_repos = []
930 parent_target_repos = []
931 if self.db_repo.parent:
931 if self.db_repo.parent:
932 parents_query = Repository.query() \
932 parents_query = Repository.query() \
933 .order_by(func.length(Repository.repo_name)) \
933 .order_by(func.length(Repository.repo_name)) \
934 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
934 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
935
935
936 if filter_query:
936 if filter_query:
937 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
937 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
938 parents_query = parents_query.filter(
938 parents_query = parents_query.filter(
939 Repository.repo_name.ilike(ilike_expression))
939 Repository.repo_name.ilike(ilike_expression))
940 parents = parents_query.limit(20).all()
940 parents = parents_query.limit(20).all()
941
941
942 for parent in parents:
942 for parent in parents:
943 parent_vcs_obj = parent.scm_instance()
943 parent_vcs_obj = parent.scm_instance()
944 if parent_vcs_obj and not parent_vcs_obj.is_empty():
944 if parent_vcs_obj and not parent_vcs_obj.is_empty():
945 parent_target_repos.append(parent)
945 parent_target_repos.append(parent)
946
946
947 # get other forks, and repo itself
947 # get other forks, and repo itself
948 query = Repository.query() \
948 query = Repository.query() \
949 .order_by(func.length(Repository.repo_name)) \
949 .order_by(func.length(Repository.repo_name)) \
950 .filter(
950 .filter(
951 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
951 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
952 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
952 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
953 ) \
953 ) \
954 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
954 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
955
955
956 if filter_query:
956 if filter_query:
957 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
957 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
958 query = query.filter(Repository.repo_name.ilike(ilike_expression))
958 query = query.filter(Repository.repo_name.ilike(ilike_expression))
959
959
960 limit = max(20 - len(parent_target_repos), 5) # not less then 5
960 limit = max(20 - len(parent_target_repos), 5) # not less then 5
961 target_repos = query.limit(limit).all()
961 target_repos = query.limit(limit).all()
962
962
963 all_target_repos = target_repos + parent_target_repos
963 all_target_repos = target_repos + parent_target_repos
964
964
965 repos = []
965 repos = []
966 # This checks permissions to the repositories
966 # This checks permissions to the repositories
967 for obj in ScmModel().get_repos(all_target_repos):
967 for obj in ScmModel().get_repos(all_target_repos):
968 repos.append({
968 repos.append({
969 'id': obj['name'],
969 'id': obj['name'],
970 'text': obj['name'],
970 'text': obj['name'],
971 'type': 'repo',
971 'type': 'repo',
972 'repo_id': obj['dbrepo']['repo_id'],
972 'repo_id': obj['dbrepo']['repo_id'],
973 'repo_type': obj['dbrepo']['repo_type'],
973 'repo_type': obj['dbrepo']['repo_type'],
974 'private': obj['dbrepo']['private'],
974 'private': obj['dbrepo']['private'],
975
975
976 })
976 })
977
977
978 data = {
978 data = {
979 'more': False,
979 'more': False,
980 'results': [{
980 'results': [{
981 'text': _('Repositories'),
981 'text': _('Repositories'),
982 'children': repos
982 'children': repos
983 }] if repos else []
983 }] if repos else []
984 }
984 }
985 return data
985 return data
986
986
987 @classmethod
987 @classmethod
988 def get_comment_ids(cls, post_data):
988 def get_comment_ids(cls, post_data):
989 return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
989 return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
990
990
991 @LoginRequired()
991 @LoginRequired()
992 @NotAnonymous()
992 @NotAnonymous()
993 @HasRepoPermissionAnyDecorator(
993 @HasRepoPermissionAnyDecorator(
994 'repository.read', 'repository.write', 'repository.admin')
994 'repository.read', 'repository.write', 'repository.admin')
995 @view_config(
995 @view_config(
996 route_name='pullrequest_comments', request_method='POST',
996 route_name='pullrequest_comments', request_method='POST',
997 renderer='string_html', xhr=True)
997 renderer='string_html', xhr=True)
998 def pullrequest_comments(self):
998 def pullrequest_comments(self):
999 self.load_default_context()
999 self.load_default_context()
1000
1000
1001 pull_request = PullRequest.get_or_404(
1001 pull_request = PullRequest.get_or_404(
1002 self.request.matchdict['pull_request_id'])
1002 self.request.matchdict['pull_request_id'])
1003 pull_request_id = pull_request.pull_request_id
1003 pull_request_id = pull_request.pull_request_id
1004 version = self.request.GET.get('version')
1004 version = self.request.GET.get('version')
1005
1005
1006 _render = self.request.get_partial_renderer(
1006 _render = self.request.get_partial_renderer(
1007 'rhodecode:templates/base/sidebar.mako')
1007 'rhodecode:templates/base/sidebar.mako')
1008 c = _render.get_call_context()
1008 c = _render.get_call_context()
1009
1009
1010 (pull_request_latest,
1010 (pull_request_latest,
1011 pull_request_at_ver,
1011 pull_request_at_ver,
1012 pull_request_display_obj,
1012 pull_request_display_obj,
1013 at_version) = PullRequestModel().get_pr_version(
1013 at_version) = PullRequestModel().get_pr_version(
1014 pull_request_id, version=version)
1014 pull_request_id, version=version)
1015 versions = pull_request_display_obj.versions()
1015 versions = pull_request_display_obj.versions()
1016 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1016 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1017 c.versions = versions + [latest_ver]
1017 c.versions = versions + [latest_ver]
1018
1018
1019 c.at_version = at_version
1019 c.at_version = at_version
1020 c.at_version_num = (at_version
1020 c.at_version_num = (at_version
1021 if at_version and at_version != PullRequest.LATEST_VER
1021 if at_version and at_version != PullRequest.LATEST_VER
1022 else None)
1022 else None)
1023
1023
1024 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1024 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1025 all_comments = c.inline_comments_flat + c.comments
1025 all_comments = c.inline_comments_flat + c.comments
1026
1026
1027 existing_ids = self.get_comment_ids(self.request.POST)
1027 existing_ids = self.get_comment_ids(self.request.POST)
1028 return _render('comments_table', all_comments, len(all_comments),
1028 return _render('comments_table', all_comments, len(all_comments),
1029 existing_ids=existing_ids)
1029 existing_ids=existing_ids)
1030
1030
1031 @LoginRequired()
1031 @LoginRequired()
1032 @NotAnonymous()
1032 @NotAnonymous()
1033 @HasRepoPermissionAnyDecorator(
1033 @HasRepoPermissionAnyDecorator(
1034 'repository.read', 'repository.write', 'repository.admin')
1034 'repository.read', 'repository.write', 'repository.admin')
1035 @view_config(
1035 @view_config(
1036 route_name='pullrequest_todos', request_method='POST',
1036 route_name='pullrequest_todos', request_method='POST',
1037 renderer='string_html', xhr=True)
1037 renderer='string_html', xhr=True)
1038 def pullrequest_todos(self):
1038 def pullrequest_todos(self):
1039 self.load_default_context()
1039 self.load_default_context()
1040
1040
1041 pull_request = PullRequest.get_or_404(
1041 pull_request = PullRequest.get_or_404(
1042 self.request.matchdict['pull_request_id'])
1042 self.request.matchdict['pull_request_id'])
1043 pull_request_id = pull_request.pull_request_id
1043 pull_request_id = pull_request.pull_request_id
1044 version = self.request.GET.get('version')
1044 version = self.request.GET.get('version')
1045
1045
1046 _render = self.request.get_partial_renderer(
1046 _render = self.request.get_partial_renderer(
1047 'rhodecode:templates/base/sidebar.mako')
1047 'rhodecode:templates/base/sidebar.mako')
1048 c = _render.get_call_context()
1048 c = _render.get_call_context()
1049 (pull_request_latest,
1049 (pull_request_latest,
1050 pull_request_at_ver,
1050 pull_request_at_ver,
1051 pull_request_display_obj,
1051 pull_request_display_obj,
1052 at_version) = PullRequestModel().get_pr_version(
1052 at_version) = PullRequestModel().get_pr_version(
1053 pull_request_id, version=version)
1053 pull_request_id, version=version)
1054 versions = pull_request_display_obj.versions()
1054 versions = pull_request_display_obj.versions()
1055 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1055 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1056 c.versions = versions + [latest_ver]
1056 c.versions = versions + [latest_ver]
1057
1057
1058 c.at_version = at_version
1058 c.at_version = at_version
1059 c.at_version_num = (at_version
1059 c.at_version_num = (at_version
1060 if at_version and at_version != PullRequest.LATEST_VER
1060 if at_version and at_version != PullRequest.LATEST_VER
1061 else None)
1061 else None)
1062
1062
1063 c.unresolved_comments = CommentsModel() \
1063 c.unresolved_comments = CommentsModel() \
1064 .get_pull_request_unresolved_todos(pull_request, include_drafts=False)
1064 .get_pull_request_unresolved_todos(pull_request, include_drafts=False)
1065 c.resolved_comments = CommentsModel() \
1065 c.resolved_comments = CommentsModel() \
1066 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1066 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1067
1067
1068 all_comments = c.unresolved_comments + c.resolved_comments
1068 all_comments = c.unresolved_comments + c.resolved_comments
1069 existing_ids = self.get_comment_ids(self.request.POST)
1069 existing_ids = self.get_comment_ids(self.request.POST)
1070 return _render('comments_table', all_comments, len(c.unresolved_comments),
1070 return _render('comments_table', all_comments, len(c.unresolved_comments),
1071 todo_comments=True, existing_ids=existing_ids)
1071 todo_comments=True, existing_ids=existing_ids)
1072
1072
1073 @LoginRequired()
1073 @LoginRequired()
1074 @NotAnonymous()
1074 @NotAnonymous()
1075 @HasRepoPermissionAnyDecorator(
1075 @HasRepoPermissionAnyDecorator(
1076 'repository.read', 'repository.write', 'repository.admin')
1076 'repository.read', 'repository.write', 'repository.admin')
1077 @CSRFRequired()
1077 @CSRFRequired()
1078 @view_config(
1078 @view_config(
1079 route_name='pullrequest_create', request_method='POST',
1079 route_name='pullrequest_create', request_method='POST',
1080 renderer=None)
1080 renderer=None)
1081 def pull_request_create(self):
1081 def pull_request_create(self):
1082 _ = self.request.translate
1082 _ = self.request.translate
1083 self.assure_not_empty_repo()
1083 self.assure_not_empty_repo()
1084 self.load_default_context()
1084 self.load_default_context()
1085
1085
1086 controls = peppercorn.parse(self.request.POST.items())
1086 controls = peppercorn.parse(self.request.POST.items())
1087
1087
1088 try:
1088 try:
1089 form = PullRequestForm(
1089 form = PullRequestForm(
1090 self.request.translate, self.db_repo.repo_id)()
1090 self.request.translate, self.db_repo.repo_id)()
1091 _form = form.to_python(controls)
1091 _form = form.to_python(controls)
1092 except formencode.Invalid as errors:
1092 except formencode.Invalid as errors:
1093 if errors.error_dict.get('revisions'):
1093 if errors.error_dict.get('revisions'):
1094 msg = 'Revisions: %s' % errors.error_dict['revisions']
1094 msg = 'Revisions: %s' % errors.error_dict['revisions']
1095 elif errors.error_dict.get('pullrequest_title'):
1095 elif errors.error_dict.get('pullrequest_title'):
1096 msg = errors.error_dict.get('pullrequest_title')
1096 msg = errors.error_dict.get('pullrequest_title')
1097 else:
1097 else:
1098 msg = _('Error creating pull request: {}').format(errors)
1098 msg = _('Error creating pull request: {}').format(errors)
1099 log.exception(msg)
1099 log.exception(msg)
1100 h.flash(msg, 'error')
1100 h.flash(msg, 'error')
1101
1101
1102 # would rather just go back to form ...
1102 # would rather just go back to form ...
1103 raise HTTPFound(
1103 raise HTTPFound(
1104 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1104 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1105
1105
1106 source_repo = _form['source_repo']
1106 source_repo = _form['source_repo']
1107 source_ref = _form['source_ref']
1107 source_ref = _form['source_ref']
1108 target_repo = _form['target_repo']
1108 target_repo = _form['target_repo']
1109 target_ref = _form['target_ref']
1109 target_ref = _form['target_ref']
1110 commit_ids = _form['revisions'][::-1]
1110 commit_ids = _form['revisions'][::-1]
1111 common_ancestor_id = _form['common_ancestor']
1111 common_ancestor_id = _form['common_ancestor']
1112
1112
1113 # find the ancestor for this pr
1113 # find the ancestor for this pr
1114 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1114 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1115 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1115 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1116
1116
1117 if not (source_db_repo or target_db_repo):
1117 if not (source_db_repo or target_db_repo):
1118 h.flash(_('source_repo or target repo not found'), category='error')
1118 h.flash(_('source_repo or target repo not found'), category='error')
1119 raise HTTPFound(
1119 raise HTTPFound(
1120 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1120 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1121
1121
1122 # re-check permissions again here
1122 # re-check permissions again here
1123 # source_repo we must have read permissions
1123 # source_repo we must have read permissions
1124
1124
1125 source_perm = HasRepoPermissionAny(
1125 source_perm = HasRepoPermissionAny(
1126 'repository.read', 'repository.write', 'repository.admin')(
1126 'repository.read', 'repository.write', 'repository.admin')(
1127 source_db_repo.repo_name)
1127 source_db_repo.repo_name)
1128 if not source_perm:
1128 if not source_perm:
1129 msg = _('Not Enough permissions to source repo `{}`.'.format(
1129 msg = _('Not Enough permissions to source repo `{}`.'.format(
1130 source_db_repo.repo_name))
1130 source_db_repo.repo_name))
1131 h.flash(msg, category='error')
1131 h.flash(msg, category='error')
1132 # copy the args back to redirect
1132 # copy the args back to redirect
1133 org_query = self.request.GET.mixed()
1133 org_query = self.request.GET.mixed()
1134 raise HTTPFound(
1134 raise HTTPFound(
1135 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1135 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1136 _query=org_query))
1136 _query=org_query))
1137
1137
1138 # target repo we must have read permissions, and also later on
1138 # target repo we must have read permissions, and also later on
1139 # we want to check branch permissions here
1139 # we want to check branch permissions here
1140 target_perm = HasRepoPermissionAny(
1140 target_perm = HasRepoPermissionAny(
1141 'repository.read', 'repository.write', 'repository.admin')(
1141 'repository.read', 'repository.write', 'repository.admin')(
1142 target_db_repo.repo_name)
1142 target_db_repo.repo_name)
1143 if not target_perm:
1143 if not target_perm:
1144 msg = _('Not Enough permissions to target repo `{}`.'.format(
1144 msg = _('Not Enough permissions to target repo `{}`.'.format(
1145 target_db_repo.repo_name))
1145 target_db_repo.repo_name))
1146 h.flash(msg, category='error')
1146 h.flash(msg, category='error')
1147 # copy the args back to redirect
1147 # copy the args back to redirect
1148 org_query = self.request.GET.mixed()
1148 org_query = self.request.GET.mixed()
1149 raise HTTPFound(
1149 raise HTTPFound(
1150 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1150 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1151 _query=org_query))
1151 _query=org_query))
1152
1152
1153 source_scm = source_db_repo.scm_instance()
1153 source_scm = source_db_repo.scm_instance()
1154 target_scm = target_db_repo.scm_instance()
1154 target_scm = target_db_repo.scm_instance()
1155
1155
1156 source_ref_obj = unicode_to_reference(source_ref)
1156 source_ref_obj = unicode_to_reference(source_ref)
1157 target_ref_obj = unicode_to_reference(target_ref)
1157 target_ref_obj = unicode_to_reference(target_ref)
1158
1158
1159 source_commit = source_scm.get_commit(source_ref_obj.commit_id)
1159 source_commit = source_scm.get_commit(source_ref_obj.commit_id)
1160 target_commit = target_scm.get_commit(target_ref_obj.commit_id)
1160 target_commit = target_scm.get_commit(target_ref_obj.commit_id)
1161
1161
1162 ancestor = source_scm.get_common_ancestor(
1162 ancestor = source_scm.get_common_ancestor(
1163 source_commit.raw_id, target_commit.raw_id, target_scm)
1163 source_commit.raw_id, target_commit.raw_id, target_scm)
1164
1164
1165 # recalculate target ref based on ancestor
1165 # recalculate target ref based on ancestor
1166 target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor))
1166 target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor))
1167
1167
1168 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1168 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1169 PullRequestModel().get_reviewer_functions()
1169 PullRequestModel().get_reviewer_functions()
1170
1170
1171 # recalculate reviewers logic, to make sure we can validate this
1171 # recalculate reviewers logic, to make sure we can validate this
1172 reviewer_rules = get_default_reviewers_data(
1172 reviewer_rules = get_default_reviewers_data(
1173 self._rhodecode_db_user,
1173 self._rhodecode_db_user,
1174 source_db_repo,
1174 source_db_repo,
1175 source_ref_obj,
1175 source_ref_obj,
1176 target_db_repo,
1176 target_db_repo,
1177 target_ref_obj,
1177 target_ref_obj,
1178 include_diff_info=False)
1178 include_diff_info=False)
1179
1179
1180 reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules)
1180 reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules)
1181 observers = validate_observers(_form['observer_members'], reviewer_rules)
1181 observers = validate_observers(_form['observer_members'], reviewer_rules)
1182
1182
1183 pullrequest_title = _form['pullrequest_title']
1183 pullrequest_title = _form['pullrequest_title']
1184 title_source_ref = source_ref_obj.name
1184 title_source_ref = source_ref_obj.name
1185 if not pullrequest_title:
1185 if not pullrequest_title:
1186 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1186 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1187 source=source_repo,
1187 source=source_repo,
1188 source_ref=title_source_ref,
1188 source_ref=title_source_ref,
1189 target=target_repo
1189 target=target_repo
1190 )
1190 )
1191
1191
1192 description = _form['pullrequest_desc']
1192 description = _form['pullrequest_desc']
1193 description_renderer = _form['description_renderer']
1193 description_renderer = _form['description_renderer']
1194
1194
1195 try:
1195 try:
1196 pull_request = PullRequestModel().create(
1196 pull_request = PullRequestModel().create(
1197 created_by=self._rhodecode_user.user_id,
1197 created_by=self._rhodecode_user.user_id,
1198 source_repo=source_repo,
1198 source_repo=source_repo,
1199 source_ref=source_ref,
1199 source_ref=source_ref,
1200 target_repo=target_repo,
1200 target_repo=target_repo,
1201 target_ref=target_ref,
1201 target_ref=target_ref,
1202 revisions=commit_ids,
1202 revisions=commit_ids,
1203 common_ancestor_id=common_ancestor_id,
1203 common_ancestor_id=common_ancestor_id,
1204 reviewers=reviewers,
1204 reviewers=reviewers,
1205 observers=observers,
1205 observers=observers,
1206 title=pullrequest_title,
1206 title=pullrequest_title,
1207 description=description,
1207 description=description,
1208 description_renderer=description_renderer,
1208 description_renderer=description_renderer,
1209 reviewer_data=reviewer_rules,
1209 reviewer_data=reviewer_rules,
1210 auth_user=self._rhodecode_user
1210 auth_user=self._rhodecode_user
1211 )
1211 )
1212 Session().commit()
1212 Session().commit()
1213
1213
1214 h.flash(_('Successfully opened new pull request'),
1214 h.flash(_('Successfully opened new pull request'),
1215 category='success')
1215 category='success')
1216 except Exception:
1216 except Exception:
1217 msg = _('Error occurred during creation of this pull request.')
1217 msg = _('Error occurred during creation of this pull request.')
1218 log.exception(msg)
1218 log.exception(msg)
1219 h.flash(msg, category='error')
1219 h.flash(msg, category='error')
1220
1220
1221 # copy the args back to redirect
1221 # copy the args back to redirect
1222 org_query = self.request.GET.mixed()
1222 org_query = self.request.GET.mixed()
1223 raise HTTPFound(
1223 raise HTTPFound(
1224 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1224 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1225 _query=org_query))
1225 _query=org_query))
1226
1226
1227 raise HTTPFound(
1227 raise HTTPFound(
1228 h.route_path('pullrequest_show', repo_name=target_repo,
1228 h.route_path('pullrequest_show', repo_name=target_repo,
1229 pull_request_id=pull_request.pull_request_id))
1229 pull_request_id=pull_request.pull_request_id))
1230
1230
1231 @LoginRequired()
1231 @LoginRequired()
1232 @NotAnonymous()
1232 @NotAnonymous()
1233 @HasRepoPermissionAnyDecorator(
1233 @HasRepoPermissionAnyDecorator(
1234 'repository.read', 'repository.write', 'repository.admin')
1234 'repository.read', 'repository.write', 'repository.admin')
1235 @CSRFRequired()
1235 @CSRFRequired()
1236 @view_config(
1236 @view_config(
1237 route_name='pullrequest_update', request_method='POST',
1237 route_name='pullrequest_update', request_method='POST',
1238 renderer='json_ext')
1238 renderer='json_ext')
1239 def pull_request_update(self):
1239 def pull_request_update(self):
1240 pull_request = PullRequest.get_or_404(
1240 pull_request = PullRequest.get_or_404(
1241 self.request.matchdict['pull_request_id'])
1241 self.request.matchdict['pull_request_id'])
1242 _ = self.request.translate
1242 _ = self.request.translate
1243
1243
1244 c = self.load_default_context()
1244 c = self.load_default_context()
1245 redirect_url = None
1245 redirect_url = None
1246
1246
1247 if pull_request.is_closed():
1247 if pull_request.is_closed():
1248 log.debug('update: forbidden because pull request is closed')
1248 log.debug('update: forbidden because pull request is closed')
1249 msg = _(u'Cannot update closed pull requests.')
1249 msg = _(u'Cannot update closed pull requests.')
1250 h.flash(msg, category='error')
1250 h.flash(msg, category='error')
1251 return {'response': True,
1251 return {'response': True,
1252 'redirect_url': redirect_url}
1252 'redirect_url': redirect_url}
1253
1253
1254 is_state_changing = pull_request.is_state_changing()
1254 is_state_changing = pull_request.is_state_changing()
1255 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
1255 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
1256
1256
1257 # only owner or admin can update it
1257 # only owner or admin can update it
1258 allowed_to_update = PullRequestModel().check_user_update(
1258 allowed_to_update = PullRequestModel().check_user_update(
1259 pull_request, self._rhodecode_user)
1259 pull_request, self._rhodecode_user)
1260
1260
1261 if allowed_to_update:
1261 if allowed_to_update:
1262 controls = peppercorn.parse(self.request.POST.items())
1262 controls = peppercorn.parse(self.request.POST.items())
1263 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1263 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1264
1264
1265 if 'review_members' in controls:
1265 if 'review_members' in controls:
1266 self._update_reviewers(
1266 self._update_reviewers(
1267 c,
1267 c,
1268 pull_request, controls['review_members'],
1268 pull_request, controls['review_members'],
1269 pull_request.reviewer_data,
1269 pull_request.reviewer_data,
1270 PullRequestReviewers.ROLE_REVIEWER)
1270 PullRequestReviewers.ROLE_REVIEWER)
1271 elif 'observer_members' in controls:
1271 elif 'observer_members' in controls:
1272 self._update_reviewers(
1272 self._update_reviewers(
1273 c,
1273 c,
1274 pull_request, controls['observer_members'],
1274 pull_request, controls['observer_members'],
1275 pull_request.reviewer_data,
1275 pull_request.reviewer_data,
1276 PullRequestReviewers.ROLE_OBSERVER)
1276 PullRequestReviewers.ROLE_OBSERVER)
1277 elif str2bool(self.request.POST.get('update_commits', 'false')):
1277 elif str2bool(self.request.POST.get('update_commits', 'false')):
1278 if is_state_changing:
1278 if is_state_changing:
1279 log.debug('commits update: forbidden because pull request is in state %s',
1279 log.debug('commits update: forbidden because pull request is in state %s',
1280 pull_request.pull_request_state)
1280 pull_request.pull_request_state)
1281 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1281 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1282 u'Current state is: `{}`').format(
1282 u'Current state is: `{}`').format(
1283 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1283 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1284 h.flash(msg, category='error')
1284 h.flash(msg, category='error')
1285 return {'response': True,
1285 return {'response': True,
1286 'redirect_url': redirect_url}
1286 'redirect_url': redirect_url}
1287
1287
1288 self._update_commits(c, pull_request)
1288 self._update_commits(c, pull_request)
1289 if force_refresh:
1289 if force_refresh:
1290 redirect_url = h.route_path(
1290 redirect_url = h.route_path(
1291 'pullrequest_show', repo_name=self.db_repo_name,
1291 'pullrequest_show', repo_name=self.db_repo_name,
1292 pull_request_id=pull_request.pull_request_id,
1292 pull_request_id=pull_request.pull_request_id,
1293 _query={"force_refresh": 1})
1293 _query={"force_refresh": 1})
1294 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1294 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1295 self._edit_pull_request(pull_request)
1295 self._edit_pull_request(pull_request)
1296 else:
1296 else:
1297 log.error('Unhandled update data.')
1297 log.error('Unhandled update data.')
1298 raise HTTPBadRequest()
1298 raise HTTPBadRequest()
1299
1299
1300 return {'response': True,
1300 return {'response': True,
1301 'redirect_url': redirect_url}
1301 'redirect_url': redirect_url}
1302 raise HTTPForbidden()
1302 raise HTTPForbidden()
1303
1303
1304 def _edit_pull_request(self, pull_request):
1304 def _edit_pull_request(self, pull_request):
1305 """
1305 """
1306 Edit title and description
1306 Edit title and description
1307 """
1307 """
1308 _ = self.request.translate
1308 _ = self.request.translate
1309
1309
1310 try:
1310 try:
1311 PullRequestModel().edit(
1311 PullRequestModel().edit(
1312 pull_request,
1312 pull_request,
1313 self.request.POST.get('title'),
1313 self.request.POST.get('title'),
1314 self.request.POST.get('description'),
1314 self.request.POST.get('description'),
1315 self.request.POST.get('description_renderer'),
1315 self.request.POST.get('description_renderer'),
1316 self._rhodecode_user)
1316 self._rhodecode_user)
1317 except ValueError:
1317 except ValueError:
1318 msg = _(u'Cannot update closed pull requests.')
1318 msg = _(u'Cannot update closed pull requests.')
1319 h.flash(msg, category='error')
1319 h.flash(msg, category='error')
1320 return
1320 return
1321 else:
1321 else:
1322 Session().commit()
1322 Session().commit()
1323
1323
1324 msg = _(u'Pull request title & description updated.')
1324 msg = _(u'Pull request title & description updated.')
1325 h.flash(msg, category='success')
1325 h.flash(msg, category='success')
1326 return
1326 return
1327
1327
1328 def _update_commits(self, c, pull_request):
1328 def _update_commits(self, c, pull_request):
1329 _ = self.request.translate
1329 _ = self.request.translate
1330
1330
1331 with pull_request.set_state(PullRequest.STATE_UPDATING):
1331 with pull_request.set_state(PullRequest.STATE_UPDATING):
1332 resp = PullRequestModel().update_commits(
1332 resp = PullRequestModel().update_commits(
1333 pull_request, self._rhodecode_db_user)
1333 pull_request, self._rhodecode_db_user)
1334
1334
1335 if resp.executed:
1335 if resp.executed:
1336
1336
1337 if resp.target_changed and resp.source_changed:
1337 if resp.target_changed and resp.source_changed:
1338 changed = 'target and source repositories'
1338 changed = 'target and source repositories'
1339 elif resp.target_changed and not resp.source_changed:
1339 elif resp.target_changed and not resp.source_changed:
1340 changed = 'target repository'
1340 changed = 'target repository'
1341 elif not resp.target_changed and resp.source_changed:
1341 elif not resp.target_changed and resp.source_changed:
1342 changed = 'source repository'
1342 changed = 'source repository'
1343 else:
1343 else:
1344 changed = 'nothing'
1344 changed = 'nothing'
1345
1345
1346 msg = _(u'Pull request updated to "{source_commit_id}" with '
1346 msg = _(u'Pull request updated to "{source_commit_id}" with '
1347 u'{count_added} added, {count_removed} removed commits. '
1347 u'{count_added} added, {count_removed} removed commits. '
1348 u'Source of changes: {change_source}.')
1348 u'Source of changes: {change_source}.')
1349 msg = msg.format(
1349 msg = msg.format(
1350 source_commit_id=pull_request.source_ref_parts.commit_id,
1350 source_commit_id=pull_request.source_ref_parts.commit_id,
1351 count_added=len(resp.changes.added),
1351 count_added=len(resp.changes.added),
1352 count_removed=len(resp.changes.removed),
1352 count_removed=len(resp.changes.removed),
1353 change_source=changed)
1353 change_source=changed)
1354 h.flash(msg, category='success')
1354 h.flash(msg, category='success')
1355 channelstream.pr_update_channelstream_push(
1355 channelstream.pr_update_channelstream_push(
1356 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1356 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1357 else:
1357 else:
1358 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1358 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1359 warning_reasons = [
1359 warning_reasons = [
1360 UpdateFailureReason.NO_CHANGE,
1360 UpdateFailureReason.NO_CHANGE,
1361 UpdateFailureReason.WRONG_REF_TYPE,
1361 UpdateFailureReason.WRONG_REF_TYPE,
1362 ]
1362 ]
1363 category = 'warning' if resp.reason in warning_reasons else 'error'
1363 category = 'warning' if resp.reason in warning_reasons else 'error'
1364 h.flash(msg, category=category)
1364 h.flash(msg, category=category)
1365
1365
1366 def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role):
1366 def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role):
1367 _ = self.request.translate
1367 _ = self.request.translate
1368
1368
1369 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1369 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1370 PullRequestModel().get_reviewer_functions()
1370 PullRequestModel().get_reviewer_functions()
1371
1371
1372 if role == PullRequestReviewers.ROLE_REVIEWER:
1372 if role == PullRequestReviewers.ROLE_REVIEWER:
1373 try:
1373 try:
1374 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1374 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1375 except ValueError as e:
1375 except ValueError as e:
1376 log.error('Reviewers Validation: {}'.format(e))
1376 log.error('Reviewers Validation: {}'.format(e))
1377 h.flash(e, category='error')
1377 h.flash(e, category='error')
1378 return
1378 return
1379
1379
1380 old_calculated_status = pull_request.calculated_review_status()
1380 old_calculated_status = pull_request.calculated_review_status()
1381 PullRequestModel().update_reviewers(
1381 PullRequestModel().update_reviewers(
1382 pull_request, reviewers, self._rhodecode_db_user)
1382 pull_request, reviewers, self._rhodecode_db_user)
1383
1383
1384 Session().commit()
1384 Session().commit()
1385
1385
1386 msg = _('Pull request reviewers updated.')
1386 msg = _('Pull request reviewers updated.')
1387 h.flash(msg, category='success')
1387 h.flash(msg, category='success')
1388 channelstream.pr_update_channelstream_push(
1388 channelstream.pr_update_channelstream_push(
1389 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1389 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1390
1390
1391 # trigger status changed if change in reviewers changes the status
1391 # trigger status changed if change in reviewers changes the status
1392 calculated_status = pull_request.calculated_review_status()
1392 calculated_status = pull_request.calculated_review_status()
1393 if old_calculated_status != calculated_status:
1393 if old_calculated_status != calculated_status:
1394 PullRequestModel().trigger_pull_request_hook(
1394 PullRequestModel().trigger_pull_request_hook(
1395 pull_request, self._rhodecode_user, 'review_status_change',
1395 pull_request, self._rhodecode_user, 'review_status_change',
1396 data={'status': calculated_status})
1396 data={'status': calculated_status})
1397
1397
1398 elif role == PullRequestReviewers.ROLE_OBSERVER:
1398 elif role == PullRequestReviewers.ROLE_OBSERVER:
1399 try:
1399 try:
1400 observers = validate_observers(review_members, reviewer_rules)
1400 observers = validate_observers(review_members, reviewer_rules)
1401 except ValueError as e:
1401 except ValueError as e:
1402 log.error('Observers Validation: {}'.format(e))
1402 log.error('Observers Validation: {}'.format(e))
1403 h.flash(e, category='error')
1403 h.flash(e, category='error')
1404 return
1404 return
1405
1405
1406 PullRequestModel().update_observers(
1406 PullRequestModel().update_observers(
1407 pull_request, observers, self._rhodecode_db_user)
1407 pull_request, observers, self._rhodecode_db_user)
1408
1408
1409 Session().commit()
1409 Session().commit()
1410 msg = _('Pull request observers updated.')
1410 msg = _('Pull request observers updated.')
1411 h.flash(msg, category='success')
1411 h.flash(msg, category='success')
1412 channelstream.pr_update_channelstream_push(
1412 channelstream.pr_update_channelstream_push(
1413 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1413 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1414
1414
1415 @LoginRequired()
1415 @LoginRequired()
1416 @NotAnonymous()
1416 @NotAnonymous()
1417 @HasRepoPermissionAnyDecorator(
1417 @HasRepoPermissionAnyDecorator(
1418 'repository.read', 'repository.write', 'repository.admin')
1418 'repository.read', 'repository.write', 'repository.admin')
1419 @CSRFRequired()
1419 @CSRFRequired()
1420 @view_config(
1420 @view_config(
1421 route_name='pullrequest_merge', request_method='POST',
1421 route_name='pullrequest_merge', request_method='POST',
1422 renderer='json_ext')
1422 renderer='json_ext')
1423 def pull_request_merge(self):
1423 def pull_request_merge(self):
1424 """
1424 """
1425 Merge will perform a server-side merge of the specified
1425 Merge will perform a server-side merge of the specified
1426 pull request, if the pull request is approved and mergeable.
1426 pull request, if the pull request is approved and mergeable.
1427 After successful merging, the pull request is automatically
1427 After successful merging, the pull request is automatically
1428 closed, with a relevant comment.
1428 closed, with a relevant comment.
1429 """
1429 """
1430 pull_request = PullRequest.get_or_404(
1430 pull_request = PullRequest.get_or_404(
1431 self.request.matchdict['pull_request_id'])
1431 self.request.matchdict['pull_request_id'])
1432 _ = self.request.translate
1432 _ = self.request.translate
1433
1433
1434 if pull_request.is_state_changing():
1434 if pull_request.is_state_changing():
1435 log.debug('show: forbidden because pull request is in state %s',
1435 log.debug('show: forbidden because pull request is in state %s',
1436 pull_request.pull_request_state)
1436 pull_request.pull_request_state)
1437 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1437 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1438 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1438 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1439 pull_request.pull_request_state)
1439 pull_request.pull_request_state)
1440 h.flash(msg, category='error')
1440 h.flash(msg, category='error')
1441 raise HTTPFound(
1441 raise HTTPFound(
1442 h.route_path('pullrequest_show',
1442 h.route_path('pullrequest_show',
1443 repo_name=pull_request.target_repo.repo_name,
1443 repo_name=pull_request.target_repo.repo_name,
1444 pull_request_id=pull_request.pull_request_id))
1444 pull_request_id=pull_request.pull_request_id))
1445
1445
1446 self.load_default_context()
1446 self.load_default_context()
1447
1447
1448 with pull_request.set_state(PullRequest.STATE_UPDATING):
1448 with pull_request.set_state(PullRequest.STATE_UPDATING):
1449 check = MergeCheck.validate(
1449 check = MergeCheck.validate(
1450 pull_request, auth_user=self._rhodecode_user,
1450 pull_request, auth_user=self._rhodecode_user,
1451 translator=self.request.translate)
1451 translator=self.request.translate)
1452 merge_possible = not check.failed
1452 merge_possible = not check.failed
1453
1453
1454 for err_type, error_msg in check.errors:
1454 for err_type, error_msg in check.errors:
1455 h.flash(error_msg, category=err_type)
1455 h.flash(error_msg, category=err_type)
1456
1456
1457 if merge_possible:
1457 if merge_possible:
1458 log.debug("Pre-conditions checked, trying to merge.")
1458 log.debug("Pre-conditions checked, trying to merge.")
1459 extras = vcs_operation_context(
1459 extras = vcs_operation_context(
1460 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1460 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1461 username=self._rhodecode_db_user.username, action='push',
1461 username=self._rhodecode_db_user.username, action='push',
1462 scm=pull_request.target_repo.repo_type)
1462 scm=pull_request.target_repo.repo_type)
1463 with pull_request.set_state(PullRequest.STATE_UPDATING):
1463 with pull_request.set_state(PullRequest.STATE_UPDATING):
1464 self._merge_pull_request(
1464 self._merge_pull_request(
1465 pull_request, self._rhodecode_db_user, extras)
1465 pull_request, self._rhodecode_db_user, extras)
1466 else:
1466 else:
1467 log.debug("Pre-conditions failed, NOT merging.")
1467 log.debug("Pre-conditions failed, NOT merging.")
1468
1468
1469 raise HTTPFound(
1469 raise HTTPFound(
1470 h.route_path('pullrequest_show',
1470 h.route_path('pullrequest_show',
1471 repo_name=pull_request.target_repo.repo_name,
1471 repo_name=pull_request.target_repo.repo_name,
1472 pull_request_id=pull_request.pull_request_id))
1472 pull_request_id=pull_request.pull_request_id))
1473
1473
1474 def _merge_pull_request(self, pull_request, user, extras):
1474 def _merge_pull_request(self, pull_request, user, extras):
1475 _ = self.request.translate
1475 _ = self.request.translate
1476 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1476 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1477
1477
1478 if merge_resp.executed:
1478 if merge_resp.executed:
1479 log.debug("The merge was successful, closing the pull request.")
1479 log.debug("The merge was successful, closing the pull request.")
1480 PullRequestModel().close_pull_request(
1480 PullRequestModel().close_pull_request(
1481 pull_request.pull_request_id, user)
1481 pull_request.pull_request_id, user)
1482 Session().commit()
1482 Session().commit()
1483 msg = _('Pull request was successfully merged and closed.')
1483 msg = _('Pull request was successfully merged and closed.')
1484 h.flash(msg, category='success')
1484 h.flash(msg, category='success')
1485 else:
1485 else:
1486 log.debug(
1486 log.debug(
1487 "The merge was not successful. Merge response: %s", merge_resp)
1487 "The merge was not successful. Merge response: %s", merge_resp)
1488 msg = merge_resp.merge_status_message
1488 msg = merge_resp.merge_status_message
1489 h.flash(msg, category='error')
1489 h.flash(msg, category='error')
1490
1490
1491 @LoginRequired()
1491 @LoginRequired()
1492 @NotAnonymous()
1492 @NotAnonymous()
1493 @HasRepoPermissionAnyDecorator(
1493 @HasRepoPermissionAnyDecorator(
1494 'repository.read', 'repository.write', 'repository.admin')
1494 'repository.read', 'repository.write', 'repository.admin')
1495 @CSRFRequired()
1495 @CSRFRequired()
1496 @view_config(
1496 @view_config(
1497 route_name='pullrequest_delete', request_method='POST',
1497 route_name='pullrequest_delete', request_method='POST',
1498 renderer='json_ext')
1498 renderer='json_ext')
1499 def pull_request_delete(self):
1499 def pull_request_delete(self):
1500 _ = self.request.translate
1500 _ = self.request.translate
1501
1501
1502 pull_request = PullRequest.get_or_404(
1502 pull_request = PullRequest.get_or_404(
1503 self.request.matchdict['pull_request_id'])
1503 self.request.matchdict['pull_request_id'])
1504 self.load_default_context()
1504 self.load_default_context()
1505
1505
1506 pr_closed = pull_request.is_closed()
1506 pr_closed = pull_request.is_closed()
1507 allowed_to_delete = PullRequestModel().check_user_delete(
1507 allowed_to_delete = PullRequestModel().check_user_delete(
1508 pull_request, self._rhodecode_user) and not pr_closed
1508 pull_request, self._rhodecode_user) and not pr_closed
1509
1509
1510 # only owner can delete it !
1510 # only owner can delete it !
1511 if allowed_to_delete:
1511 if allowed_to_delete:
1512 PullRequestModel().delete(pull_request, self._rhodecode_user)
1512 PullRequestModel().delete(pull_request, self._rhodecode_user)
1513 Session().commit()
1513 Session().commit()
1514 h.flash(_('Successfully deleted pull request'),
1514 h.flash(_('Successfully deleted pull request'),
1515 category='success')
1515 category='success')
1516 raise HTTPFound(h.route_path('pullrequest_show_all',
1516 raise HTTPFound(h.route_path('pullrequest_show_all',
1517 repo_name=self.db_repo_name))
1517 repo_name=self.db_repo_name))
1518
1518
1519 log.warning('user %s tried to delete pull request without access',
1519 log.warning('user %s tried to delete pull request without access',
1520 self._rhodecode_user)
1520 self._rhodecode_user)
1521 raise HTTPNotFound()
1521 raise HTTPNotFound()
1522
1522
1523 def _pull_request_comments_create(self, pull_request, comments):
1523 def _pull_request_comments_create(self, pull_request, comments):
1524 _ = self.request.translate
1524 _ = self.request.translate
1525 data = {}
1525 data = {}
1526 pull_request_id = pull_request.pull_request_id
1527 if not comments:
1526 if not comments:
1528 return
1527 return
1528 pull_request_id = pull_request.pull_request_id
1529
1529
1530 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
1530 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
1531
1531
1532 for entry in comments:
1532 for entry in comments:
1533 c = self.load_default_context()
1533 c = self.load_default_context()
1534 comment_type = entry['comment_type']
1534 comment_type = entry['comment_type']
1535 text = entry['text']
1535 text = entry['text']
1536 status = entry['status']
1536 status = entry['status']
1537 is_draft = str2bool(entry['is_draft'])
1537 is_draft = str2bool(entry['is_draft'])
1538 resolves_comment_id = entry['resolves_comment_id']
1538 resolves_comment_id = entry['resolves_comment_id']
1539 close_pull_request = entry['close_pull_request']
1539 close_pull_request = entry['close_pull_request']
1540 f_path = entry['f_path']
1540 f_path = entry['f_path']
1541 line_no = entry['line']
1541 line_no = entry['line']
1542 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
1542 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
1543
1543
1544 # the logic here should work like following, if we submit close
1544 # the logic here should work like following, if we submit close
1545 # pr comment, use `close_pull_request_with_comment` function
1545 # pr comment, use `close_pull_request_with_comment` function
1546 # else handle regular comment logic
1546 # else handle regular comment logic
1547
1547
1548 if close_pull_request:
1548 if close_pull_request:
1549 # only owner or admin or person with write permissions
1549 # only owner or admin or person with write permissions
1550 allowed_to_close = PullRequestModel().check_user_update(
1550 allowed_to_close = PullRequestModel().check_user_update(
1551 pull_request, self._rhodecode_user)
1551 pull_request, self._rhodecode_user)
1552 if not allowed_to_close:
1552 if not allowed_to_close:
1553 log.debug('comment: forbidden because not allowed to close '
1553 log.debug('comment: forbidden because not allowed to close '
1554 'pull request %s', pull_request_id)
1554 'pull request %s', pull_request_id)
1555 raise HTTPForbidden()
1555 raise HTTPForbidden()
1556
1556
1557 # This also triggers `review_status_change`
1557 # This also triggers `review_status_change`
1558 comment, status = PullRequestModel().close_pull_request_with_comment(
1558 comment, status = PullRequestModel().close_pull_request_with_comment(
1559 pull_request, self._rhodecode_user, self.db_repo, message=text,
1559 pull_request, self._rhodecode_user, self.db_repo, message=text,
1560 auth_user=self._rhodecode_user)
1560 auth_user=self._rhodecode_user)
1561 Session().flush()
1561 Session().flush()
1562 is_inline = comment.is_inline
1562 is_inline = comment.is_inline
1563
1563
1564 PullRequestModel().trigger_pull_request_hook(
1564 PullRequestModel().trigger_pull_request_hook(
1565 pull_request, self._rhodecode_user, 'comment',
1565 pull_request, self._rhodecode_user, 'comment',
1566 data={'comment': comment})
1566 data={'comment': comment})
1567
1567
1568 else:
1568 else:
1569 # regular comment case, could be inline, or one with status.
1569 # regular comment case, could be inline, or one with status.
1570 # for that one we check also permissions
1570 # for that one we check also permissions
1571 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1571 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1572 allowed_to_change_status = PullRequestModel().check_user_change_status(
1572 allowed_to_change_status = PullRequestModel().check_user_change_status(
1573 pull_request, self._rhodecode_user) and not is_draft
1573 pull_request, self._rhodecode_user) and not is_draft
1574
1574
1575 if status and allowed_to_change_status:
1575 if status and allowed_to_change_status:
1576 message = (_('Status change %(transition_icon)s %(status)s')
1576 message = (_('Status change %(transition_icon)s %(status)s')
1577 % {'transition_icon': '>',
1577 % {'transition_icon': '>',
1578 'status': ChangesetStatus.get_status_lbl(status)})
1578 'status': ChangesetStatus.get_status_lbl(status)})
1579 text = text or message
1579 text = text or message
1580
1580
1581 comment = CommentsModel().create(
1581 comment = CommentsModel().create(
1582 text=text,
1582 text=text,
1583 repo=self.db_repo.repo_id,
1583 repo=self.db_repo.repo_id,
1584 user=self._rhodecode_user.user_id,
1584 user=self._rhodecode_user.user_id,
1585 pull_request=pull_request,
1585 pull_request=pull_request,
1586 f_path=f_path,
1586 f_path=f_path,
1587 line_no=line_no,
1587 line_no=line_no,
1588 status_change=(ChangesetStatus.get_status_lbl(status)
1588 status_change=(ChangesetStatus.get_status_lbl(status)
1589 if status and allowed_to_change_status else None),
1589 if status and allowed_to_change_status else None),
1590 status_change_type=(status
1590 status_change_type=(status
1591 if status and allowed_to_change_status else None),
1591 if status and allowed_to_change_status else None),
1592 comment_type=comment_type,
1592 comment_type=comment_type,
1593 is_draft=is_draft,
1593 is_draft=is_draft,
1594 resolves_comment_id=resolves_comment_id,
1594 resolves_comment_id=resolves_comment_id,
1595 auth_user=self._rhodecode_user,
1595 auth_user=self._rhodecode_user,
1596 send_email=not is_draft, # skip notification for draft comments
1596 send_email=not is_draft, # skip notification for draft comments
1597 )
1597 )
1598 is_inline = comment.is_inline
1598 is_inline = comment.is_inline
1599
1599
1600 if allowed_to_change_status:
1600 if allowed_to_change_status:
1601 # calculate old status before we change it
1601 # calculate old status before we change it
1602 old_calculated_status = pull_request.calculated_review_status()
1602 old_calculated_status = pull_request.calculated_review_status()
1603
1603
1604 # get status if set !
1604 # get status if set !
1605 if status:
1605 if status:
1606 ChangesetStatusModel().set_status(
1606 ChangesetStatusModel().set_status(
1607 self.db_repo.repo_id,
1607 self.db_repo.repo_id,
1608 status,
1608 status,
1609 self._rhodecode_user.user_id,
1609 self._rhodecode_user.user_id,
1610 comment,
1610 comment,
1611 pull_request=pull_request
1611 pull_request=pull_request
1612 )
1612 )
1613
1613
1614 Session().flush()
1614 Session().flush()
1615 # this is somehow required to get access to some relationship
1615 # this is somehow required to get access to some relationship
1616 # loaded on comment
1616 # loaded on comment
1617 Session().refresh(comment)
1617 Session().refresh(comment)
1618
1618
1619 # skip notifications for drafts
1620 if not is_draft:
1619 PullRequestModel().trigger_pull_request_hook(
1621 PullRequestModel().trigger_pull_request_hook(
1620 pull_request, self._rhodecode_user, 'comment',
1622 pull_request, self._rhodecode_user, 'comment',
1621 data={'comment': comment})
1623 data={'comment': comment})
1622
1624
1623 # we now calculate the status of pull request, and based on that
1625 # we now calculate the status of pull request, and based on that
1624 # calculation we set the commits status
1626 # calculation we set the commits status
1625 calculated_status = pull_request.calculated_review_status()
1627 calculated_status = pull_request.calculated_review_status()
1626 if old_calculated_status != calculated_status:
1628 if old_calculated_status != calculated_status:
1627 PullRequestModel().trigger_pull_request_hook(
1629 PullRequestModel().trigger_pull_request_hook(
1628 pull_request, self._rhodecode_user, 'review_status_change',
1630 pull_request, self._rhodecode_user, 'review_status_change',
1629 data={'status': calculated_status})
1631 data={'status': calculated_status})
1630
1632
1631 comment_id = comment.comment_id
1633 comment_id = comment.comment_id
1632 data[comment_id] = {
1634 data[comment_id] = {
1633 'target_id': target_elem_id
1635 'target_id': target_elem_id
1634 }
1636 }
1635 Session().flush()
1637 Session().flush()
1636
1638
1637 c.co = comment
1639 c.co = comment
1638 c.at_version_num = None
1640 c.at_version_num = None
1639 c.is_new = True
1641 c.is_new = True
1640 rendered_comment = render(
1642 rendered_comment = render(
1641 'rhodecode:templates/changeset/changeset_comment_block.mako',
1643 'rhodecode:templates/changeset/changeset_comment_block.mako',
1642 self._get_template_context(c), self.request)
1644 self._get_template_context(c), self.request)
1643
1645
1644 data[comment_id].update(comment.get_dict())
1646 data[comment_id].update(comment.get_dict())
1645 data[comment_id].update({'rendered_text': rendered_comment})
1647 data[comment_id].update({'rendered_text': rendered_comment})
1646
1648
1647 Session().commit()
1649 Session().commit()
1648
1650
1649 # skip channelstream for draft comments
1651 # skip channelstream for draft comments
1650 if all_drafts:
1652 if not all_drafts:
1651 comment_broadcast_channel = channelstream.comment_channel(
1653 comment_broadcast_channel = channelstream.comment_channel(
1652 self.db_repo_name, pull_request_obj=pull_request)
1654 self.db_repo_name, pull_request_obj=pull_request)
1653
1655
1654 comment_data = data
1656 comment_data = data
1655 comment_type = 'inline' if is_inline else 'general'
1657 posted_comment_type = 'inline' if is_inline else 'general'
1656 if len(data) == 1:
1658 if len(data) == 1:
1657 msg = _('posted {} new {} comment').format(len(data), comment_type)
1659 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
1658 else:
1660 else:
1659 msg = _('posted {} new {} comments').format(len(data), comment_type)
1661 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
1660
1662
1661 channelstream.comment_channelstream_push(
1663 channelstream.comment_channelstream_push(
1662 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
1664 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
1663 comment_data=comment_data)
1665 comment_data=comment_data)
1664
1666
1665 return data
1667 return data
1666
1668
1667 @LoginRequired()
1669 @LoginRequired()
1668 @NotAnonymous()
1670 @NotAnonymous()
1669 @HasRepoPermissionAnyDecorator(
1671 @HasRepoPermissionAnyDecorator(
1670 'repository.read', 'repository.write', 'repository.admin')
1672 'repository.read', 'repository.write', 'repository.admin')
1671 @CSRFRequired()
1673 @CSRFRequired()
1672 @view_config(
1674 @view_config(
1673 route_name='pullrequest_comment_create', request_method='POST',
1675 route_name='pullrequest_comment_create', request_method='POST',
1674 renderer='json_ext')
1676 renderer='json_ext')
1675 def pull_request_comment_create(self):
1677 def pull_request_comment_create(self):
1676 _ = self.request.translate
1678 _ = self.request.translate
1677
1679
1678 pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
1680 pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
1679
1681
1680 if pull_request.is_closed():
1682 if pull_request.is_closed():
1681 log.debug('comment: forbidden because pull request is closed')
1683 log.debug('comment: forbidden because pull request is closed')
1682 raise HTTPForbidden()
1684 raise HTTPForbidden()
1683
1685
1684 allowed_to_comment = PullRequestModel().check_user_comment(
1686 allowed_to_comment = PullRequestModel().check_user_comment(
1685 pull_request, self._rhodecode_user)
1687 pull_request, self._rhodecode_user)
1686 if not allowed_to_comment:
1688 if not allowed_to_comment:
1687 log.debug('comment: forbidden because pull request is from forbidden repo')
1689 log.debug('comment: forbidden because pull request is from forbidden repo')
1688 raise HTTPForbidden()
1690 raise HTTPForbidden()
1689
1691
1690 comment_data = {
1692 comment_data = {
1691 'comment_type': self.request.POST.get('comment_type'),
1693 'comment_type': self.request.POST.get('comment_type'),
1692 'text': self.request.POST.get('text'),
1694 'text': self.request.POST.get('text'),
1693 'status': self.request.POST.get('changeset_status', None),
1695 'status': self.request.POST.get('changeset_status', None),
1694 'is_draft': self.request.POST.get('draft'),
1696 'is_draft': self.request.POST.get('draft'),
1695 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
1697 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
1696 'close_pull_request': self.request.POST.get('close_pull_request'),
1698 'close_pull_request': self.request.POST.get('close_pull_request'),
1697 'f_path': self.request.POST.get('f_path'),
1699 'f_path': self.request.POST.get('f_path'),
1698 'line': self.request.POST.get('line'),
1700 'line': self.request.POST.get('line'),
1699 }
1701 }
1700 data = self._pull_request_comments_create(pull_request, [comment_data])
1702 data = self._pull_request_comments_create(pull_request, [comment_data])
1701
1703
1702 return data
1704 return data
1703
1705
1704 @LoginRequired()
1706 @LoginRequired()
1705 @NotAnonymous()
1707 @NotAnonymous()
1706 @HasRepoPermissionAnyDecorator(
1708 @HasRepoPermissionAnyDecorator(
1707 'repository.read', 'repository.write', 'repository.admin')
1709 'repository.read', 'repository.write', 'repository.admin')
1708 @CSRFRequired()
1710 @CSRFRequired()
1709 @view_config(
1711 @view_config(
1710 route_name='pullrequest_comment_delete', request_method='POST',
1712 route_name='pullrequest_comment_delete', request_method='POST',
1711 renderer='json_ext')
1713 renderer='json_ext')
1712 def pull_request_comment_delete(self):
1714 def pull_request_comment_delete(self):
1713 pull_request = PullRequest.get_or_404(
1715 pull_request = PullRequest.get_or_404(
1714 self.request.matchdict['pull_request_id'])
1716 self.request.matchdict['pull_request_id'])
1715
1717
1716 comment = ChangesetComment.get_or_404(
1718 comment = ChangesetComment.get_or_404(
1717 self.request.matchdict['comment_id'])
1719 self.request.matchdict['comment_id'])
1718 comment_id = comment.comment_id
1720 comment_id = comment.comment_id
1719
1721
1720 if comment.immutable:
1722 if comment.immutable:
1721 # don't allow deleting comments that are immutable
1723 # don't allow deleting comments that are immutable
1722 raise HTTPForbidden()
1724 raise HTTPForbidden()
1723
1725
1724 if pull_request.is_closed():
1726 if pull_request.is_closed():
1725 log.debug('comment: forbidden because pull request is closed')
1727 log.debug('comment: forbidden because pull request is closed')
1726 raise HTTPForbidden()
1728 raise HTTPForbidden()
1727
1729
1728 if not comment:
1730 if not comment:
1729 log.debug('Comment with id:%s not found, skipping', comment_id)
1731 log.debug('Comment with id:%s not found, skipping', comment_id)
1730 # comment already deleted in another call probably
1732 # comment already deleted in another call probably
1731 return True
1733 return True
1732
1734
1733 if comment.pull_request.is_closed():
1735 if comment.pull_request.is_closed():
1734 # don't allow deleting comments on closed pull request
1736 # don't allow deleting comments on closed pull request
1735 raise HTTPForbidden()
1737 raise HTTPForbidden()
1736
1738
1737 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1739 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1738 super_admin = h.HasPermissionAny('hg.admin')()
1740 super_admin = h.HasPermissionAny('hg.admin')()
1739 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1741 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1740 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1742 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1741 comment_repo_admin = is_repo_admin and is_repo_comment
1743 comment_repo_admin = is_repo_admin and is_repo_comment
1742
1744
1743 if super_admin or comment_owner or comment_repo_admin:
1745 if super_admin or comment_owner or comment_repo_admin:
1744 old_calculated_status = comment.pull_request.calculated_review_status()
1746 old_calculated_status = comment.pull_request.calculated_review_status()
1745 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1747 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1746 Session().commit()
1748 Session().commit()
1747 calculated_status = comment.pull_request.calculated_review_status()
1749 calculated_status = comment.pull_request.calculated_review_status()
1748 if old_calculated_status != calculated_status:
1750 if old_calculated_status != calculated_status:
1749 PullRequestModel().trigger_pull_request_hook(
1751 PullRequestModel().trigger_pull_request_hook(
1750 comment.pull_request, self._rhodecode_user, 'review_status_change',
1752 comment.pull_request, self._rhodecode_user, 'review_status_change',
1751 data={'status': calculated_status})
1753 data={'status': calculated_status})
1752 return True
1754 return True
1753 else:
1755 else:
1754 log.warning('No permissions for user %s to delete comment_id: %s',
1756 log.warning('No permissions for user %s to delete comment_id: %s',
1755 self._rhodecode_db_user, comment_id)
1757 self._rhodecode_db_user, comment_id)
1756 raise HTTPNotFound()
1758 raise HTTPNotFound()
1757
1759
1758 @LoginRequired()
1760 @LoginRequired()
1759 @NotAnonymous()
1761 @NotAnonymous()
1760 @HasRepoPermissionAnyDecorator(
1762 @HasRepoPermissionAnyDecorator(
1761 'repository.read', 'repository.write', 'repository.admin')
1763 'repository.read', 'repository.write', 'repository.admin')
1762 @CSRFRequired()
1764 @CSRFRequired()
1763 @view_config(
1765 @view_config(
1764 route_name='pullrequest_comment_edit', request_method='POST',
1766 route_name='pullrequest_comment_edit', request_method='POST',
1765 renderer='json_ext')
1767 renderer='json_ext')
1766 def pull_request_comment_edit(self):
1768 def pull_request_comment_edit(self):
1767 self.load_default_context()
1769 self.load_default_context()
1768
1770
1769 pull_request = PullRequest.get_or_404(
1771 pull_request = PullRequest.get_or_404(
1770 self.request.matchdict['pull_request_id']
1772 self.request.matchdict['pull_request_id']
1771 )
1773 )
1772 comment = ChangesetComment.get_or_404(
1774 comment = ChangesetComment.get_or_404(
1773 self.request.matchdict['comment_id']
1775 self.request.matchdict['comment_id']
1774 )
1776 )
1775 comment_id = comment.comment_id
1777 comment_id = comment.comment_id
1776
1778
1777 if comment.immutable:
1779 if comment.immutable:
1778 # don't allow deleting comments that are immutable
1780 # don't allow deleting comments that are immutable
1779 raise HTTPForbidden()
1781 raise HTTPForbidden()
1780
1782
1781 if pull_request.is_closed():
1783 if pull_request.is_closed():
1782 log.debug('comment: forbidden because pull request is closed')
1784 log.debug('comment: forbidden because pull request is closed')
1783 raise HTTPForbidden()
1785 raise HTTPForbidden()
1784
1786
1785 if not comment:
1786 log.debug('Comment with id:%s not found, skipping', comment_id)
1787 # comment already deleted in another call probably
1788 return True
1789
1790 if comment.pull_request.is_closed():
1787 if comment.pull_request.is_closed():
1791 # don't allow deleting comments on closed pull request
1788 # don't allow deleting comments on closed pull request
1792 raise HTTPForbidden()
1789 raise HTTPForbidden()
1793
1790
1794 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1791 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1795 super_admin = h.HasPermissionAny('hg.admin')()
1792 super_admin = h.HasPermissionAny('hg.admin')()
1796 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1793 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1797 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1794 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1798 comment_repo_admin = is_repo_admin and is_repo_comment
1795 comment_repo_admin = is_repo_admin and is_repo_comment
1799
1796
1800 if super_admin or comment_owner or comment_repo_admin:
1797 if super_admin or comment_owner or comment_repo_admin:
1801 text = self.request.POST.get('text')
1798 text = self.request.POST.get('text')
1802 version = self.request.POST.get('version')
1799 version = self.request.POST.get('version')
1803 if text == comment.text:
1800 if text == comment.text:
1804 log.warning(
1801 log.warning(
1805 'Comment(PR): '
1802 'Comment(PR): '
1806 'Trying to create new version '
1803 'Trying to create new version '
1807 'with the same comment body {}'.format(
1804 'with the same comment body {}'.format(
1808 comment_id,
1805 comment_id,
1809 )
1806 )
1810 )
1807 )
1811 raise HTTPNotFound()
1808 raise HTTPNotFound()
1812
1809
1813 if version.isdigit():
1810 if version.isdigit():
1814 version = int(version)
1811 version = int(version)
1815 else:
1812 else:
1816 log.warning(
1813 log.warning(
1817 'Comment(PR): Wrong version type {} {} '
1814 'Comment(PR): Wrong version type {} {} '
1818 'for comment {}'.format(
1815 'for comment {}'.format(
1819 version,
1816 version,
1820 type(version),
1817 type(version),
1821 comment_id,
1818 comment_id,
1822 )
1819 )
1823 )
1820 )
1824 raise HTTPNotFound()
1821 raise HTTPNotFound()
1825
1822
1826 try:
1823 try:
1827 comment_history = CommentsModel().edit(
1824 comment_history = CommentsModel().edit(
1828 comment_id=comment_id,
1825 comment_id=comment_id,
1829 text=text,
1826 text=text,
1830 auth_user=self._rhodecode_user,
1827 auth_user=self._rhodecode_user,
1831 version=version,
1828 version=version,
1832 )
1829 )
1833 except CommentVersionMismatch:
1830 except CommentVersionMismatch:
1834 raise HTTPConflict()
1831 raise HTTPConflict()
1835
1832
1836 if not comment_history:
1833 if not comment_history:
1837 raise HTTPNotFound()
1834 raise HTTPNotFound()
1838
1835
1839 Session().commit()
1836 Session().commit()
1840
1837 if not comment.draft:
1841 PullRequestModel().trigger_pull_request_hook(
1838 PullRequestModel().trigger_pull_request_hook(
1842 pull_request, self._rhodecode_user, 'comment_edit',
1839 pull_request, self._rhodecode_user, 'comment_edit',
1843 data={'comment': comment})
1840 data={'comment': comment})
1844
1841
1845 return {
1842 return {
1846 'comment_history_id': comment_history.comment_history_id,
1843 'comment_history_id': comment_history.comment_history_id,
1847 'comment_id': comment.comment_id,
1844 'comment_id': comment.comment_id,
1848 'comment_version': comment_history.version,
1845 'comment_version': comment_history.version,
1849 'comment_author_username': comment_history.author.username,
1846 'comment_author_username': comment_history.author.username,
1850 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1847 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1851 'comment_created_on': h.age_component(comment_history.created_on,
1848 'comment_created_on': h.age_component(comment_history.created_on,
1852 time_is_local=True),
1849 time_is_local=True),
1853 }
1850 }
1854 else:
1851 else:
1855 log.warning('No permissions for user %s to edit comment_id: %s',
1852 log.warning('No permissions for user %s to edit comment_id: %s',
1856 self._rhodecode_db_user, comment_id)
1853 self._rhodecode_db_user, comment_id)
1857 raise HTTPNotFound()
1854 raise HTTPNotFound()
@@ -1,746 +1,746 b''
1 // comments.less
1 // comments.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5
5
6 // Comments
6 // Comments
7 @comment-outdated-opacity: 1.0;
7 @comment-outdated-opacity: 1.0;
8
8
9 .comments {
9 .comments {
10 width: 100%;
10 width: 100%;
11 }
11 }
12
12
13 .comments-heading {
13 .comments-heading {
14 margin-bottom: -1px;
14 margin-bottom: -1px;
15 background: @grey6;
15 background: @grey6;
16 display: block;
16 display: block;
17 padding: 10px 0px;
17 padding: 10px 0px;
18 font-size: 18px
18 font-size: 18px
19 }
19 }
20
20
21 #comment-tr-show {
21 #comment-tr-show {
22 padding: 5px 0;
22 padding: 5px 0;
23 }
23 }
24
24
25 tr.inline-comments div {
25 tr.inline-comments div {
26 max-width: 100%;
26 max-width: 100%;
27
27
28 p {
28 p {
29 white-space: normal;
29 white-space: normal;
30 }
30 }
31
31
32 code, pre, .code, dd {
32 code, pre, .code, dd {
33 overflow-x: auto;
33 overflow-x: auto;
34 width: 1062px;
34 width: 1062px;
35 }
35 }
36
36
37 dd {
37 dd {
38 width: auto;
38 width: auto;
39 }
39 }
40 }
40 }
41
41
42 #injected_page_comments {
42 #injected_page_comments {
43 .comment-previous-link,
43 .comment-previous-link,
44 .comment-next-link,
44 .comment-next-link,
45 .comment-links-divider {
45 .comment-links-divider {
46 display: none;
46 display: none;
47 }
47 }
48 }
48 }
49
49
50 .add-comment {
50 .add-comment {
51 margin-bottom: 10px;
51 margin-bottom: 10px;
52 }
52 }
53 .hide-comment-button .add-comment {
53 .hide-comment-button .add-comment {
54 display: none;
54 display: none;
55 }
55 }
56
56
57 .comment-bubble {
57 .comment-bubble {
58 color: @grey4;
58 color: @grey4;
59 margin-top: 4px;
59 margin-top: 4px;
60 margin-right: 30px;
60 margin-right: 30px;
61 visibility: hidden;
61 visibility: hidden;
62 }
62 }
63
63
64 .comment-draft {
64 .comment-draft {
65 float: left;
65 float: left;
66 margin-right: 10px;
66 margin-right: 10px;
67 font-weight: 400;
67 font-weight: 400;
68 color: @color-draft;
68 color: @color-draft;
69 }
69 }
70
70
71 .comment-new {
71 .comment-new {
72 float: left;
72 float: left;
73 margin-right: 10px;
73 margin-right: 10px;
74 font-weight: 400;
74 font-weight: 400;
75 color: @color-new;
75 color: @color-new;
76 }
76 }
77
77
78 .comment-label {
78 .comment-label {
79 float: left;
79 float: left;
80
80
81 padding: 0 8px 0 0;
81 padding: 0 8px 0 0;
82 min-height: 0;
82 min-height: 0;
83
83
84 text-align: center;
84 text-align: center;
85 font-size: 10px;
85 font-size: 10px;
86
86
87 font-family: @text-italic;
87 font-family: @text-italic;
88 font-style: italic;
88 font-style: italic;
89 background: #fff none;
89 background: #fff none;
90 color: @grey3;
90 color: @grey3;
91 white-space: nowrap;
91 white-space: nowrap;
92
92
93 text-transform: uppercase;
93 text-transform: uppercase;
94 min-width: 50px;
94 min-width: 50px;
95
95
96 &.todo {
96 &.todo {
97 color: @color5;
97 color: @color5;
98 font-style: italic;
98 font-style: italic;
99 font-weight: @text-bold-italic-weight;
99 font-weight: @text-bold-italic-weight;
100 font-family: @text-bold-italic;
100 font-family: @text-bold-italic;
101 }
101 }
102
102
103 .resolve {
103 .resolve {
104 cursor: pointer;
104 cursor: pointer;
105 text-decoration: underline;
105 text-decoration: underline;
106 }
106 }
107
107
108 .resolved {
108 .resolved {
109 text-decoration: line-through;
109 text-decoration: line-through;
110 color: @color1;
110 color: @color1;
111 }
111 }
112 .resolved a {
112 .resolved a {
113 text-decoration: line-through;
113 text-decoration: line-through;
114 color: @color1;
114 color: @color1;
115 }
115 }
116 .resolve-text {
116 .resolve-text {
117 color: @color1;
117 color: @color1;
118 margin: 2px 8px;
118 margin: 2px 8px;
119 font-family: @text-italic;
119 font-family: @text-italic;
120 font-style: italic;
120 font-style: italic;
121 }
121 }
122 }
122 }
123
123
124 .has-spacer-after {
124 .has-spacer-after {
125 &:after {
125 &:after {
126 content: ' | ';
126 content: ' | ';
127 color: @grey5;
127 color: @grey5;
128 }
128 }
129 }
129 }
130
130
131 .has-spacer-before {
131 .has-spacer-before {
132 &:before {
132 &:before {
133 content: ' | ';
133 content: ' | ';
134 color: @grey5;
134 color: @grey5;
135 }
135 }
136 }
136 }
137
137
138 .comment {
138 .comment {
139
139
140 &.comment-general {
140 &.comment-general {
141 border: 1px solid @grey5;
141 border: 1px solid @grey5;
142 padding: 5px 5px 5px 5px;
142 padding: 5px 5px 5px 5px;
143 }
143 }
144
144
145 margin: @padding 0;
145 margin: @padding 0;
146 padding: 4px 0 0 0;
146 padding: 4px 0 0 0;
147 line-height: 1em;
147 line-height: 1em;
148
148
149 .rc-user {
149 .rc-user {
150 min-width: 0;
150 min-width: 0;
151 margin: 0px .5em 0 0;
151 margin: 0px .5em 0 0;
152
152
153 .user {
153 .user {
154 display: inline;
154 display: inline;
155 }
155 }
156 }
156 }
157
157
158 .meta {
158 .meta {
159 position: relative;
159 position: relative;
160 width: 100%;
160 width: 100%;
161 border-bottom: 1px solid @grey5;
161 border-bottom: 1px solid @grey5;
162 margin: -5px 0px;
162 margin: -5px 0px;
163 line-height: 24px;
163 line-height: 24px;
164
164
165 &:hover .permalink {
165 &:hover .permalink {
166 visibility: visible;
166 visibility: visible;
167 color: @rcblue;
167 color: @rcblue;
168 }
168 }
169 }
169 }
170
170
171 .author,
171 .author,
172 .date {
172 .date {
173 display: inline;
173 display: inline;
174
174
175 &:after {
175 &:after {
176 content: ' | ';
176 content: ' | ';
177 color: @grey5;
177 color: @grey5;
178 }
178 }
179 }
179 }
180
180
181 .author-general img {
181 .author-general img {
182 top: 3px;
182 top: 3px;
183 }
183 }
184 .author-inline img {
184 .author-inline img {
185 top: 3px;
185 top: 3px;
186 }
186 }
187
187
188 .status-change,
188 .status-change,
189 .permalink,
189 .permalink,
190 .changeset-status-lbl {
190 .changeset-status-lbl {
191 display: inline;
191 display: inline;
192 }
192 }
193
193
194 .permalink {
194 .permalink {
195 visibility: hidden;
195 visibility: hidden;
196 }
196 }
197
197
198 .comment-links-divider {
198 .comment-links-divider {
199 display: inline;
199 display: inline;
200 }
200 }
201
201
202 .comment-links-block {
202 .comment-links-block {
203 float:right;
203 float:right;
204 text-align: right;
204 text-align: right;
205 min-width: 85px;
205 min-width: 85px;
206
206
207 [class^="icon-"]:before,
207 [class^="icon-"]:before,
208 [class*=" icon-"]:before {
208 [class*=" icon-"]:before {
209 margin-left: 0;
209 margin-left: 0;
210 margin-right: 0;
210 margin-right: 0;
211 }
211 }
212 }
212 }
213
213
214 .comment-previous-link {
214 .comment-previous-link {
215 display: inline-block;
215 display: inline-block;
216
216
217 .arrow_comment_link{
217 .arrow_comment_link{
218 cursor: pointer;
218 cursor: pointer;
219 i {
219 i {
220 font-size:10px;
220 font-size:10px;
221 }
221 }
222 }
222 }
223 .arrow_comment_link.disabled {
223 .arrow_comment_link.disabled {
224 cursor: default;
224 cursor: default;
225 color: @grey5;
225 color: @grey5;
226 }
226 }
227 }
227 }
228
228
229 .comment-next-link {
229 .comment-next-link {
230 display: inline-block;
230 display: inline-block;
231
231
232 .arrow_comment_link{
232 .arrow_comment_link{
233 cursor: pointer;
233 cursor: pointer;
234 i {
234 i {
235 font-size:10px;
235 font-size:10px;
236 }
236 }
237 }
237 }
238 .arrow_comment_link.disabled {
238 .arrow_comment_link.disabled {
239 cursor: default;
239 cursor: default;
240 color: @grey5;
240 color: @grey5;
241 }
241 }
242 }
242 }
243
243
244 .delete-comment {
244 .delete-comment {
245 display: inline-block;
245 display: inline-block;
246 color: @rcblue;
246 color: @rcblue;
247
247
248 &:hover {
248 &:hover {
249 cursor: pointer;
249 cursor: pointer;
250 }
250 }
251 }
251 }
252
252
253 .text {
253 .text {
254 clear: both;
254 clear: both;
255 .border-radius(@border-radius);
255 .border-radius(@border-radius);
256 .box-sizing(border-box);
256 .box-sizing(border-box);
257
257
258 .markdown-block p,
258 .markdown-block p,
259 .rst-block p {
259 .rst-block p {
260 margin: .5em 0 !important;
260 margin: .5em 0 !important;
261 // TODO: lisa: This is needed because of other rst !important rules :[
261 // TODO: lisa: This is needed because of other rst !important rules :[
262 }
262 }
263 }
263 }
264
264
265 .pr-version {
265 .pr-version {
266 display: inline-block;
266 display: inline-block;
267 }
267 }
268 .pr-version-inline {
268 .pr-version-inline {
269 display: inline-block;
269 display: inline-block;
270 }
270 }
271 .pr-version-num {
271 .pr-version-num {
272 font-size: 10px;
272 font-size: 10px;
273 }
273 }
274 }
274 }
275
275
276 @comment-padding: 5px;
276 @comment-padding: 5px;
277
277
278 .general-comments {
278 .general-comments {
279 .comment-outdated {
279 .comment-outdated {
280 opacity: @comment-outdated-opacity;
280 opacity: @comment-outdated-opacity;
281 }
281 }
282
282
283 .comment-outdated-label {
283 .comment-outdated-label {
284 color: @grey3;
284 color: @grey3;
285 padding-right: 4px;
285 padding-right: 4px;
286 }
286 }
287 }
287 }
288
288
289 .inline-comments {
289 .inline-comments {
290
290
291 .comment {
291 .comment {
292 margin: 0;
292 margin: 0;
293 }
293 }
294
294
295 .comment-outdated {
295 .comment-outdated {
296 opacity: @comment-outdated-opacity;
296 opacity: @comment-outdated-opacity;
297 }
297 }
298
298
299 .comment-outdated-label {
299 .comment-outdated-label {
300 color: @grey3;
300 color: @grey3;
301 padding-right: 4px;
301 padding-right: 4px;
302 }
302 }
303
303
304 .comment-inline {
304 .comment-inline {
305
305
306 &:first-child {
306 &:first-child {
307 margin: 4px 4px 0 4px;
307 margin: 4px 4px 0 4px;
308 border-top: 1px solid @grey5;
308 border-top: 1px solid @grey5;
309 border-bottom: 0 solid @grey5;
309 border-bottom: 0 solid @grey5;
310 border-left: 1px solid @grey5;
310 border-left: 1px solid @grey5;
311 border-right: 1px solid @grey5;
311 border-right: 1px solid @grey5;
312 .border-radius-top(4px);
312 .border-radius-top(4px);
313 }
313 }
314
314
315 &:only-child {
315 &:only-child {
316 margin: 4px 4px 0 4px;
316 margin: 4px 4px 0 4px;
317 border-top: 1px solid @grey5;
317 border-top: 1px solid @grey5;
318 border-bottom: 0 solid @grey5;
318 border-bottom: 0 solid @grey5;
319 border-left: 1px solid @grey5;
319 border-left: 1px solid @grey5;
320 border-right: 1px solid @grey5;
320 border-right: 1px solid @grey5;
321 .border-radius-top(4px);
321 .border-radius-top(4px);
322 }
322 }
323
323
324 background: white;
324 background: white;
325 padding: @comment-padding @comment-padding;
325 padding: @comment-padding @comment-padding;
326 margin: 0 4px 0 4px;
326 margin: 0 4px 0 4px;
327 border-top: 0 solid @grey5;
327 border-top: 0 solid @grey5;
328 border-bottom: 0 solid @grey5;
328 border-bottom: 0 solid @grey5;
329 border-left: 1px solid @grey5;
329 border-left: 1px solid @grey5;
330 border-right: 1px solid @grey5;
330 border-right: 1px solid @grey5;
331
331
332 .text {
332 .text {
333 border: none;
333 border: none;
334 }
334 }
335
335
336 .meta {
336 .meta {
337 border-bottom: 1px solid @grey6;
337 border-bottom: 1px solid @grey6;
338 margin: -5px 0px;
338 margin: -5px 0px;
339 line-height: 24px;
339 line-height: 24px;
340 }
340 }
341
341
342 }
342 }
343 .comment-selected {
343 .comment-selected {
344 border-left: 6px solid @comment-highlight-color;
344 border-left: 6px solid @comment-highlight-color;
345 }
345 }
346
346
347 .comment-inline-form-open {
347 .comment-inline-form-open {
348 display: block !important;
348 display: block !important;
349 }
349 }
350
350
351 .comment-inline-form {
351 .comment-inline-form {
352 display: none;
352 display: none;
353 }
353 }
354
354
355 .comment-inline-form-edit {
355 .comment-inline-form-edit {
356 padding: 0;
356 padding: 0;
357 margin: 0px 4px 2px 4px;
357 margin: 0px 4px 2px 4px;
358 }
358 }
359
359
360 .reply-thread-container {
360 .reply-thread-container {
361 display: table;
361 display: table;
362 width: 100%;
362 width: 100%;
363 padding: 0px 4px 4px 4px;
363 padding: 0px 4px 4px 4px;
364 }
364 }
365
365
366 .reply-thread-container-wrapper {
366 .reply-thread-container-wrapper {
367 margin: 0 4px 4px 4px;
367 margin: 0 4px 4px 4px;
368 border-top: 0 solid @grey5;
368 border-top: 0 solid @grey5;
369 border-bottom: 1px solid @grey5;
369 border-bottom: 1px solid @grey5;
370 border-left: 1px solid @grey5;
370 border-left: 1px solid @grey5;
371 border-right: 1px solid @grey5;
371 border-right: 1px solid @grey5;
372 .border-radius-bottom(4px);
372 .border-radius-bottom(4px);
373 }
373 }
374
374
375 .reply-thread-gravatar {
375 .reply-thread-gravatar {
376 display: table-cell;
376 display: table-cell;
377 width: 24px;
377 width: 24px;
378 height: 24px;
378 height: 24px;
379 padding-top: 10px;
379 padding-top: 10px;
380 padding-left: 10px;
380 padding-left: 10px;
381 background-color: #eeeeee;
381 background-color: #eeeeee;
382 vertical-align: top;
382 vertical-align: top;
383 }
383 }
384
384
385 .reply-thread-reply-button {
385 .reply-thread-reply-button {
386 display: table-cell;
386 display: table-cell;
387 width: 100%;
387 width: 100%;
388 height: 33px;
388 height: 33px;
389 padding: 3px 8px;
389 padding: 3px 8px;
390 margin-left: 8px;
390 margin-left: 8px;
391 background-color: #eeeeee;
391 background-color: #eeeeee;
392 }
392 }
393
393
394 .reply-thread-reply-button .cb-comment-add-button {
394 .reply-thread-reply-button .cb-comment-add-button {
395 border-radius: 4px;
395 border-radius: 4px;
396 width: 100%;
396 width: 100%;
397 padding: 6px 2px;
397 padding: 6px 2px;
398 text-align: left;
398 text-align: left;
399 cursor: text;
399 cursor: text;
400 color: @grey3;
400 color: @grey3;
401 }
401 }
402 .reply-thread-reply-button .cb-comment-add-button:hover {
402 .reply-thread-reply-button .cb-comment-add-button:hover {
403 background-color: white;
403 background-color: white;
404 color: @grey2;
404 color: @grey2;
405 }
405 }
406
406
407 .reply-thread-last {
407 .reply-thread-last {
408 display: table-cell;
408 display: table-cell;
409 width: 10px;
409 width: 10px;
410 }
410 }
411
411
412 /* Hide reply box when it's a first element,
412 /* Hide reply box when it's a first element,
413 can happen when drafts are saved but not shown to specific user,
413 can happen when drafts are saved but not shown to specific user,
414 or there are outdated comments hidden
414 or there are outdated comments hidden
415 */
415 */
416 .reply-thread-container-wrapper:first-child:not(.comment-form-active) {
416 .reply-thread-container-wrapper:first-child:not(.comment-form-active) {
417 display: none;
417 display: none;
418 }
418 }
419
419
420 .reply-thread-container-wrapper.comment-outdated {
420 .reply-thread-container-wrapper.comment-outdated {
421 display: none
421 display: none
422 }
422 }
423
423
424 /* hide add comment button when form is open */
424 /* hide add comment button when form is open */
425 .comment-inline-form-open ~ .cb-comment-add-button {
425 .comment-inline-form-open ~ .cb-comment-add-button {
426 display: none;
426 display: none;
427 }
427 }
428
428
429 /* hide add comment button when only comment is being deleted */
429 /* hide add comment button when only comment is being deleted */
430 .comment-deleting:first-child + .cb-comment-add-button {
430 .comment-deleting:first-child + .cb-comment-add-button {
431 display: none;
431 display: none;
432 }
432 }
433
433
434 /* hide add comment button when form but no comments */
434 /* hide add comment button when form but no comments */
435 .comment-inline-form:first-child + .cb-comment-add-button {
435 .comment-inline-form:first-child + .cb-comment-add-button {
436 display: none;
436 display: none;
437 }
437 }
438
438
439 }
439 }
440
440
441 .show-outdated-comments {
441 .show-outdated-comments {
442 display: inline;
442 display: inline;
443 color: @rcblue;
443 color: @rcblue;
444 }
444 }
445
445
446 // Comment Form
446 // Comment Form
447 div.comment-form {
447 div.comment-form {
448 margin-top: 20px;
448 margin-top: 20px;
449 }
449 }
450
450
451 .comment-form strong {
451 .comment-form strong {
452 display: block;
452 display: block;
453 margin-bottom: 15px;
453 margin-bottom: 15px;
454 }
454 }
455
455
456 .comment-form textarea {
456 .comment-form textarea {
457 width: 100%;
457 width: 100%;
458 height: 100px;
458 height: 100px;
459 font-family: @text-monospace;
459 font-family: @text-monospace;
460 }
460 }
461
461
462 form.comment-form {
462 form.comment-form {
463 margin-top: 10px;
463 margin-top: 10px;
464 margin-left: 10px;
464 margin-left: 10px;
465 }
465 }
466
466
467 .comment-inline-form .comment-block-ta,
467 .comment-inline-form .comment-block-ta,
468 .comment-form .comment-block-ta,
468 .comment-form .comment-block-ta,
469 .comment-form .preview-box {
469 .comment-form .preview-box {
470 .border-radius(@border-radius);
470 .border-radius(@border-radius);
471 .box-sizing(border-box);
471 .box-sizing(border-box);
472 background-color: white;
472 background-color: white;
473 }
473 }
474
474
475 .comment-form-submit {
475 .comment-form-submit {
476 margin-top: 5px;
476 margin-top: 5px;
477 margin-left: 525px;
477 margin-left: 525px;
478 }
478 }
479
479
480 .file-comments {
480 .file-comments {
481 display: none;
481 display: none;
482 }
482 }
483
483
484 .comment-form .preview-box.unloaded,
484 .comment-form .preview-box.unloaded,
485 .comment-inline-form .preview-box.unloaded {
485 .comment-inline-form .preview-box.unloaded {
486 height: 50px;
486 height: 50px;
487 text-align: center;
487 text-align: center;
488 padding: 20px;
488 padding: 20px;
489 background-color: white;
489 background-color: white;
490 }
490 }
491
491
492 .comment-footer {
492 .comment-footer {
493 display: table;
493 display: table;
494 width: 100%;
494 width: 100%;
495 height: 42px;
495 height: 42px;
496
496
497 .comment-status-box,
497 .comment-status-box,
498 .cancel-button {
498 .cancel-button {
499 display: inline-block;
499 display: inline-block;
500 }
500 }
501
501
502 .comment-status-box {
502 .comment-status-box {
503 margin-left: 10px;
503 margin-left: 10px;
504 }
504 }
505
505
506 .action-buttons {
506 .action-buttons {
507 display: table-cell;
507 display: table-cell;
508 padding: 5px 0 5px 2px;
508 padding: 5px 0 5px 2px;
509 }
509 }
510
510
511 .toolbar-text {
511 .toolbar-text {
512 height: 42px;
512 height: 28px;
513 display: table-cell;
513 display: table-cell;
514 vertical-align: bottom;
514 vertical-align: baseline;
515 font-size: 11px;
515 font-size: 11px;
516 color: @grey4;
516 color: @grey4;
517 text-align: right;
517 text-align: right;
518
518
519 a {
519 a {
520 color: @grey4;
520 color: @grey4;
521 }
521 }
522
522
523 }
523 }
524
524
525 .action-buttons-extra {
525 .action-buttons-extra {
526 display: inline-block;
526 display: inline-block;
527 }
527 }
528 }
528 }
529
529
530 .comment-form {
530 .comment-form {
531
531
532 .comment {
532 .comment {
533 margin-left: 10px;
533 margin-left: 10px;
534 }
534 }
535
535
536 .comment-help {
536 .comment-help {
537 color: @grey4;
537 color: @grey4;
538 padding: 5px 0 5px 0;
538 padding: 5px 0 5px 0;
539 }
539 }
540
540
541 .comment-title {
541 .comment-title {
542 padding: 5px 0 5px 0;
542 padding: 5px 0 5px 0;
543 }
543 }
544
544
545 .comment-button {
545 .comment-button {
546 display: inline-block;
546 display: inline-block;
547 }
547 }
548
548
549 .comment-button-input {
549 .comment-button-input {
550 margin-right: 0;
550 margin-right: 0;
551 }
551 }
552
552
553 #save_general {
553 #save_general {
554 margin-left: -6px;
554 margin-left: -6px;
555 }
555 }
556
556
557 }
557 }
558
558
559
559
560 .comment-form-login {
560 .comment-form-login {
561 .comment-help {
561 .comment-help {
562 padding: 0.7em; //same as the button
562 padding: 0.7em; //same as the button
563 }
563 }
564
564
565 div.clearfix {
565 div.clearfix {
566 clear: both;
566 clear: both;
567 width: 100%;
567 width: 100%;
568 display: block;
568 display: block;
569 }
569 }
570 }
570 }
571
571
572 .comment-version-select {
572 .comment-version-select {
573 margin: 0px;
573 margin: 0px;
574 border-radius: inherit;
574 border-radius: inherit;
575 border-color: @grey6;
575 border-color: @grey6;
576 height: 20px;
576 height: 20px;
577 }
577 }
578
578
579 .comment-type {
579 .comment-type {
580 margin: 0px;
580 margin: 0px;
581 border-radius: inherit;
581 border-radius: inherit;
582 border-color: @grey6;
582 border-color: @grey6;
583 }
583 }
584
584
585 .preview-box {
585 .preview-box {
586 min-height: 105px;
586 min-height: 105px;
587 margin-bottom: 15px;
587 margin-bottom: 15px;
588 background-color: white;
588 background-color: white;
589 .border-radius(@border-radius);
589 .border-radius(@border-radius);
590 .box-sizing(border-box);
590 .box-sizing(border-box);
591 }
591 }
592
592
593 .add-another-button {
593 .add-another-button {
594 margin-left: 10px;
594 margin-left: 10px;
595 margin-top: 10px;
595 margin-top: 10px;
596 margin-bottom: 10px;
596 margin-bottom: 10px;
597 }
597 }
598
598
599 .comment .buttons {
599 .comment .buttons {
600 float: right;
600 float: right;
601 margin: -1px 0px 0px 0px;
601 margin: -1px 0px 0px 0px;
602 }
602 }
603
603
604 // Inline Comment Form
604 // Inline Comment Form
605 .injected_diff .comment-inline-form,
605 .injected_diff .comment-inline-form,
606 .comment-inline-form {
606 .comment-inline-form {
607 background-color: white;
607 background-color: white;
608 margin-top: 4px;
608 margin-top: 4px;
609 margin-bottom: 10px;
609 margin-bottom: 10px;
610 }
610 }
611
611
612 .inline-form {
612 .inline-form {
613 padding: 10px 7px;
613 padding: 10px 7px;
614 }
614 }
615
615
616 .inline-form div {
616 .inline-form div {
617 max-width: 100%;
617 max-width: 100%;
618 }
618 }
619
619
620 .overlay {
620 .overlay {
621 display: none;
621 display: none;
622 position: absolute;
622 position: absolute;
623 width: 100%;
623 width: 100%;
624 text-align: center;
624 text-align: center;
625 vertical-align: middle;
625 vertical-align: middle;
626 font-size: 16px;
626 font-size: 16px;
627 background: none repeat scroll 0 0 white;
627 background: none repeat scroll 0 0 white;
628
628
629 &.submitting {
629 &.submitting {
630 display: block;
630 display: block;
631 opacity: 0.5;
631 opacity: 0.5;
632 z-index: 100;
632 z-index: 100;
633 }
633 }
634 }
634 }
635 .comment-inline-form .overlay.submitting .overlay-text {
635 .comment-inline-form .overlay.submitting .overlay-text {
636 margin-top: 5%;
636 margin-top: 5%;
637 }
637 }
638
638
639 .comment-inline-form .clearfix,
639 .comment-inline-form .clearfix,
640 .comment-form .clearfix {
640 .comment-form .clearfix {
641 .border-radius(@border-radius);
641 .border-radius(@border-radius);
642 margin: 0px;
642 margin: 0px;
643 }
643 }
644
644
645
645
646 .hide-inline-form-button {
646 .hide-inline-form-button {
647 margin-left: 5px;
647 margin-left: 5px;
648 }
648 }
649 .comment-button .hide-inline-form {
649 .comment-button .hide-inline-form {
650 background: white;
650 background: white;
651 }
651 }
652
652
653 .comment-area {
653 .comment-area {
654 padding: 6px 8px;
654 padding: 6px 8px;
655 border: 1px solid @grey5;
655 border: 1px solid @grey5;
656 .border-radius(@border-radius);
656 .border-radius(@border-radius);
657
657
658 .resolve-action {
658 .resolve-action {
659 padding: 1px 0px 0px 6px;
659 padding: 1px 0px 0px 6px;
660 }
660 }
661
661
662 }
662 }
663
663
664 comment-area-text {
664 comment-area-text {
665 color: @grey3;
665 color: @grey3;
666 }
666 }
667
667
668 .comment-area-header {
668 .comment-area-header {
669 height: 35px;
669 height: 35px;
670 border-bottom: 1px solid @grey5;
670 border-bottom: 1px solid @grey5;
671 }
671 }
672
672
673 .comment-area-header .nav-links {
673 .comment-area-header .nav-links {
674 display: flex;
674 display: flex;
675 flex-flow: row wrap;
675 flex-flow: row wrap;
676 -webkit-flex-flow: row wrap;
676 -webkit-flex-flow: row wrap;
677 width: 100%;
677 width: 100%;
678 border: none;
678 border: none;
679 }
679 }
680
680
681 .comment-area-footer {
681 .comment-area-footer {
682 min-height: 30px;
682 min-height: 30px;
683 }
683 }
684
684
685 .comment-footer .toolbar {
685 .comment-footer .toolbar {
686
686
687 }
687 }
688
688
689 .comment-attachment-uploader {
689 .comment-attachment-uploader {
690 border: 1px dashed white;
690 border: 1px dashed white;
691 border-radius: @border-radius;
691 border-radius: @border-radius;
692 margin-top: -10px;
692 margin-top: -10px;
693 line-height: 30px;
693 line-height: 30px;
694 &.dz-drag-hover {
694 &.dz-drag-hover {
695 border-color: @grey3;
695 border-color: @grey3;
696 }
696 }
697
697
698 .dz-error-message {
698 .dz-error-message {
699 padding-top: 0;
699 padding-top: 0;
700 }
700 }
701 }
701 }
702
702
703 .comment-attachment-text {
703 .comment-attachment-text {
704 clear: both;
704 clear: both;
705 font-size: 11px;
705 font-size: 11px;
706 color: #8F8F8F;
706 color: #8F8F8F;
707 width: 100%;
707 width: 100%;
708 .pick-attachment {
708 .pick-attachment {
709 color: #8F8F8F;
709 color: #8F8F8F;
710 }
710 }
711 .pick-attachment:hover {
711 .pick-attachment:hover {
712 color: @rcblue;
712 color: @rcblue;
713 }
713 }
714 }
714 }
715
715
716 .nav-links {
716 .nav-links {
717 padding: 0;
717 padding: 0;
718 margin: 0;
718 margin: 0;
719 list-style: none;
719 list-style: none;
720 height: auto;
720 height: auto;
721 border-bottom: 1px solid @grey5;
721 border-bottom: 1px solid @grey5;
722 }
722 }
723 .nav-links li {
723 .nav-links li {
724 display: inline-block;
724 display: inline-block;
725 list-style-type: none;
725 list-style-type: none;
726 }
726 }
727
727
728 .nav-links li a.disabled {
728 .nav-links li a.disabled {
729 cursor: not-allowed;
729 cursor: not-allowed;
730 }
730 }
731
731
732 .nav-links li.active a {
732 .nav-links li.active a {
733 border-bottom: 2px solid @rcblue;
733 border-bottom: 2px solid @rcblue;
734 color: #000;
734 color: #000;
735 font-weight: 600;
735 font-weight: 600;
736 }
736 }
737 .nav-links li a {
737 .nav-links li a {
738 display: inline-block;
738 display: inline-block;
739 padding: 0px 10px 5px 10px;
739 padding: 0px 10px 5px 10px;
740 margin-bottom: -1px;
740 margin-bottom: -1px;
741 font-size: 14px;
741 font-size: 14px;
742 line-height: 28px;
742 line-height: 28px;
743 color: #8f8f8f;
743 color: #8f8f8f;
744 border-bottom: 2px solid transparent;
744 border-bottom: 2px solid transparent;
745 }
745 }
746
746
@@ -1,403 +1,404 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
33 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
34 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
34 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
35 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
35 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
36 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
36 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
37 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
37 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
38 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
38 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
39 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
39 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
40 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
40 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
41 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
41 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
42 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
42 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
43 pyroutes.register('admin_home', '/_admin', []);
43 pyroutes.register('admin_home', '/_admin', []);
44 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
44 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
45 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
45 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
46 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
46 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
47 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
47 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
48 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
48 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
49 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
49 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
50 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
50 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
51 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
51 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
52 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
52 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
53 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
53 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
54 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
54 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
55 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
55 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
56 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
56 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
57 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
57 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
58 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
58 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
59 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
59 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
60 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
60 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
61 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
61 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
62 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
62 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
63 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
63 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
64 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
64 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
65 pyroutes.register('admin_settings', '/_admin/settings', []);
65 pyroutes.register('admin_settings', '/_admin/settings', []);
66 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
66 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
67 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
67 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
68 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
68 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
69 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
69 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
70 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
70 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
71 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
71 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
72 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
72 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
73 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
73 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
74 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
74 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
75 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
75 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
76 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
76 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
77 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
77 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
78 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
78 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
79 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
79 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
80 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
80 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
81 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
81 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
82 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
82 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
83 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
83 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
84 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
84 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
85 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
85 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
86 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
86 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
87 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
87 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
88 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
88 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
89 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
89 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
90 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
90 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
91 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
91 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
92 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
92 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
93 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
93 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
94 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
94 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
95 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
95 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
96 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
96 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
97 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
97 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
98 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
98 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
99 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
99 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
100 pyroutes.register('users', '/_admin/users', []);
100 pyroutes.register('users', '/_admin/users', []);
101 pyroutes.register('users_data', '/_admin/users_data', []);
101 pyroutes.register('users_data', '/_admin/users_data', []);
102 pyroutes.register('users_create', '/_admin/users/create', []);
102 pyroutes.register('users_create', '/_admin/users/create', []);
103 pyroutes.register('users_new', '/_admin/users/new', []);
103 pyroutes.register('users_new', '/_admin/users/new', []);
104 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
104 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
105 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
105 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
106 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
106 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
107 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
107 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
108 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
108 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
109 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
109 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
110 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
110 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
113 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
113 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
114 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
114 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
115 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
115 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
116 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
116 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
117 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
117 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
118 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
118 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
119 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
119 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
120 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
120 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
121 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
121 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
122 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
122 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
123 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
123 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
124 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
124 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
125 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
125 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
126 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
126 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
127 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
127 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
128 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
128 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
129 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
129 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
130 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
130 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
131 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
131 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
132 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
132 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
133 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
133 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
134 pyroutes.register('user_groups', '/_admin/user_groups', []);
134 pyroutes.register('user_groups', '/_admin/user_groups', []);
135 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
135 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
136 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
136 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
137 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
137 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
138 pyroutes.register('repos', '/_admin/repos', []);
138 pyroutes.register('repos', '/_admin/repos', []);
139 pyroutes.register('repos_data', '/_admin/repos_data', []);
139 pyroutes.register('repos_data', '/_admin/repos_data', []);
140 pyroutes.register('repo_new', '/_admin/repos/new', []);
140 pyroutes.register('repo_new', '/_admin/repos/new', []);
141 pyroutes.register('repo_create', '/_admin/repos/create', []);
141 pyroutes.register('repo_create', '/_admin/repos/create', []);
142 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
142 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
143 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
143 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
144 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
144 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
145 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
145 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
146 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
146 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
147 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
147 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
148 pyroutes.register('channelstream_proxy', '/_channelstream', []);
148 pyroutes.register('channelstream_proxy', '/_channelstream', []);
149 pyroutes.register('upload_file', '/_file_store/upload', []);
149 pyroutes.register('upload_file', '/_file_store/upload', []);
150 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
150 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
151 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
151 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
152 pyroutes.register('logout', '/_admin/logout', []);
152 pyroutes.register('logout', '/_admin/logout', []);
153 pyroutes.register('reset_password', '/_admin/password_reset', []);
153 pyroutes.register('reset_password', '/_admin/password_reset', []);
154 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
154 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
155 pyroutes.register('home', '/', []);
155 pyroutes.register('home', '/', []);
156 pyroutes.register('main_page_repos_data', '/_home_repos', []);
156 pyroutes.register('main_page_repos_data', '/_home_repos', []);
157 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
157 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
158 pyroutes.register('user_autocomplete_data', '/_users', []);
158 pyroutes.register('user_autocomplete_data', '/_users', []);
159 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
159 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
160 pyroutes.register('repo_list_data', '/_repos', []);
160 pyroutes.register('repo_list_data', '/_repos', []);
161 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
161 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
162 pyroutes.register('goto_switcher_data', '/_goto_data', []);
162 pyroutes.register('goto_switcher_data', '/_goto_data', []);
163 pyroutes.register('markup_preview', '/_markup_preview', []);
163 pyroutes.register('markup_preview', '/_markup_preview', []);
164 pyroutes.register('file_preview', '/_file_preview', []);
164 pyroutes.register('file_preview', '/_file_preview', []);
165 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
165 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
166 pyroutes.register('journal', '/_admin/journal', []);
166 pyroutes.register('journal', '/_admin/journal', []);
167 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
167 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
168 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
168 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
169 pyroutes.register('journal_public', '/_admin/public_journal', []);
169 pyroutes.register('journal_public', '/_admin/public_journal', []);
170 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
170 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
171 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
171 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
172 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
172 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
173 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
173 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
174 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
174 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
175 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
175 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
176 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
176 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
177 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
177 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
178 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
178 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
179 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
179 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
182 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
183 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
183 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
184 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
184 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
185 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
185 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
186 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
186 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
187 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
187 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
188 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_history_id)s/history_view', ['repo_name', 'commit_id', 'comment_history_id']);
188 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_history_id)s/history_view', ['repo_name', 'commit_id', 'comment_history_id']);
189 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
189 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
190 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
190 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
191 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
191 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
192 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
192 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
193 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
193 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
194 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
194 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
195 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
195 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
196 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
197 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
198 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
198 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
199 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
200 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
201 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
202 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
203 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
204 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
207 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
208 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
209 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
210 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
210 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
211 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
211 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
212 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
212 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
213 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
213 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
214 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
214 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
215 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
215 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
216 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
216 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
217 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
217 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
218 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
218 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
219 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
219 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
220 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
220 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
221 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
221 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
222 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
222 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
223 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
223 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
224 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
224 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
225 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
225 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
226 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
226 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
227 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
227 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
228 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
228 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
229 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
229 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
230 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
230 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
231 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
231 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
232 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
232 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
233 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
233 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
234 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
234 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
235 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
235 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
236 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
236 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
237 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
237 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
238 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
238 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
239 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
239 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
240 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
240 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
241 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
241 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
242 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
242 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
243 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
243 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
244 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
244 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
245 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
245 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
246 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
246 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
247 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
247 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
248 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
248 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
249 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
249 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
250 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
250 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
251 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
251 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
252 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
252 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
253 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
253 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
254 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
254 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
255 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
255 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
256 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
256 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
257 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
257 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
258 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
258 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
259 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
259 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
260 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
260 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
261 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
261 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
262 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
262 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
263 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
263 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
264 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
264 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
265 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
265 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
266 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
266 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
267 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
267 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
268 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
268 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
269 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
269 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
270 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
270 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
271 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
271 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
272 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
272 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
273 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
273 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
274 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
274 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
275 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
275 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
276 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
276 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
277 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
277 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
278 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
278 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
279 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
279 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
280 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
280 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
281 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
281 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
282 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
282 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
283 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
283 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
284 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
284 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
285 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
285 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
286 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
286 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
287 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
287 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
288 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
288 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
289 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
289 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
290 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
290 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
291 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
291 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
292 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
292 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
293 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
293 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
294 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
294 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
295 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
295 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
296 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
296 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
297 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
297 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
298 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
298 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
299 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
299 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
300 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
300 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
301 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
301 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
302 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
302 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
303 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
303 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
304 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
304 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
305 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
305 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
306 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
306 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
307 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
307 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
308 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
308 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
309 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
309 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
310 pyroutes.register('search', '/_admin/search', []);
310 pyroutes.register('search', '/_admin/search', []);
311 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
311 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
312 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
312 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
313 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
313 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
314 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
314 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
315 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
315 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
316 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
316 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
317 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
317 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
318 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
318 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
319 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
319 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
320 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
320 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
321 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
321 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
322 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
322 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
323 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
323 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
324 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
324 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
325 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
325 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
326 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
326 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
327 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
327 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
328 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
328 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
329 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
329 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
330 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
330 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
331 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
331 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
332 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
332 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
333 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
333 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
334 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
334 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
335 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
335 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
336 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
336 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
337 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
337 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
338 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
338 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
339 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
339 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
340 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
340 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
341 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
341 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
342 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
342 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
343 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
343 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
344 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
344 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
345 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
345 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
346 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
346 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
347 pyroutes.register('gists_show', '/_admin/gists', []);
347 pyroutes.register('gists_show', '/_admin/gists', []);
348 pyroutes.register('gists_new', '/_admin/gists/new', []);
348 pyroutes.register('gists_new', '/_admin/gists/new', []);
349 pyroutes.register('gists_create', '/_admin/gists/create', []);
349 pyroutes.register('gists_create', '/_admin/gists/create', []);
350 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
350 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
351 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
351 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
352 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
352 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
353 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
353 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
354 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
354 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
355 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
355 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
356 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
356 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
357 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
357 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
358 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
358 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
359 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
359 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
360 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
360 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
361 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
361 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
362 pyroutes.register('apiv2', '/_admin/api', []);
362 pyroutes.register('apiv2', '/_admin/api', []);
363 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
363 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
364 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
364 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
365 pyroutes.register('login', '/_admin/login', []);
365 pyroutes.register('login', '/_admin/login', []);
366 pyroutes.register('register', '/_admin/register', []);
366 pyroutes.register('register', '/_admin/register', []);
367 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
367 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
368 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
368 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
369 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
369 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
370 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
370 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
371 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
371 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
372 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
372 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
373 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
373 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
374 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
374 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
375 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
375 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
376 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
376 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
377 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
377 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
378 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
378 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
379 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
379 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
380 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
380 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
381 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
381 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
382 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
382 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
383 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
383 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
384 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
384 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
385 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
385 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
386 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
386 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
387 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
387 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
388 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
388 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
389 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
389 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
390 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
390 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
391 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
391 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
392 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
392 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
393 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
393 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
394 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
394 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
395 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
395 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
396 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
396 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
397 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
397 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
398 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
398 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
399 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
399 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
400 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
400 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
401 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
401 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
402 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
402 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
403 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
403 }
404 }
@@ -1,1473 +1,1486 b''
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 var firefoxAnchorFix = function() {
19 var firefoxAnchorFix = function() {
20 // hack to make anchor links behave properly on firefox, in our inline
20 // hack to make anchor links behave properly on firefox, in our inline
21 // comments generation when comments are injected firefox is misbehaving
21 // comments generation when comments are injected firefox is misbehaving
22 // when jumping to anchor links
22 // when jumping to anchor links
23 if (location.href.indexOf('#') > -1) {
23 if (location.href.indexOf('#') > -1) {
24 location.href += '';
24 location.href += '';
25 }
25 }
26 };
26 };
27
27
28 var linkifyComments = function(comments) {
28 var linkifyComments = function(comments) {
29 var firstCommentId = null;
29 var firstCommentId = null;
30 if (comments) {
30 if (comments) {
31 firstCommentId = $(comments[0]).data('comment-id');
31 firstCommentId = $(comments[0]).data('comment-id');
32 }
32 }
33
33
34 if (firstCommentId){
34 if (firstCommentId){
35 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
35 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
36 }
36 }
37 };
37 };
38
38
39 var bindToggleButtons = function() {
39 var bindToggleButtons = function() {
40 $('.comment-toggle').on('click', function() {
40 $('.comment-toggle').on('click', function() {
41 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
41 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
42 });
42 });
43 };
43 };
44
44
45
45
46
46
47 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
47 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
48 failHandler = failHandler || function() {};
48 failHandler = failHandler || function() {};
49 postData = toQueryString(postData);
49 postData = toQueryString(postData);
50 var request = $.ajax({
50 var request = $.ajax({
51 url: url,
51 url: url,
52 type: 'POST',
52 type: 'POST',
53 data: postData,
53 data: postData,
54 headers: {'X-PARTIAL-XHR': true}
54 headers: {'X-PARTIAL-XHR': true}
55 })
55 })
56 .done(function (data) {
56 .done(function (data) {
57 successHandler(data);
57 successHandler(data);
58 })
58 })
59 .fail(function (data, textStatus, errorThrown) {
59 .fail(function (data, textStatus, errorThrown) {
60 failHandler(data, textStatus, errorThrown)
60 failHandler(data, textStatus, errorThrown)
61 });
61 });
62 return request;
62 return request;
63 };
63 };
64
64
65
65
66
66
67
67
68 /* Comment form for main and inline comments */
68 /* Comment form for main and inline comments */
69 (function(mod) {
69 (function(mod) {
70
70
71 if (typeof exports == "object" && typeof module == "object") {
71 if (typeof exports == "object" && typeof module == "object") {
72 // CommonJS
72 // CommonJS
73 module.exports = mod();
73 module.exports = mod();
74 }
74 }
75 else {
75 else {
76 // Plain browser env
76 // Plain browser env
77 (this || window).CommentForm = mod();
77 (this || window).CommentForm = mod();
78 }
78 }
79
79
80 })(function() {
80 })(function() {
81 "use strict";
81 "use strict";
82
82
83 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id) {
83 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id) {
84
84
85 if (!(this instanceof CommentForm)) {
85 if (!(this instanceof CommentForm)) {
86 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id);
86 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId, edit, comment_id);
87 }
87 }
88
88
89 // bind the element instance to our Form
89 // bind the element instance to our Form
90 $(formElement).get(0).CommentForm = this;
90 $(formElement).get(0).CommentForm = this;
91
91
92 this.withLineNo = function(selector) {
92 this.withLineNo = function(selector) {
93 var lineNo = this.lineNo;
93 var lineNo = this.lineNo;
94 if (lineNo === undefined) {
94 if (lineNo === undefined) {
95 return selector
95 return selector
96 } else {
96 } else {
97 return selector + '_' + lineNo;
97 return selector + '_' + lineNo;
98 }
98 }
99 };
99 };
100
100
101 this.commitId = commitId;
101 this.commitId = commitId;
102 this.pullRequestId = pullRequestId;
102 this.pullRequestId = pullRequestId;
103 this.lineNo = lineNo;
103 this.lineNo = lineNo;
104 this.initAutocompleteActions = initAutocompleteActions;
104 this.initAutocompleteActions = initAutocompleteActions;
105
105
106 this.previewButton = this.withLineNo('#preview-btn');
106 this.previewButton = this.withLineNo('#preview-btn');
107 this.previewContainer = this.withLineNo('#preview-container');
107 this.previewContainer = this.withLineNo('#preview-container');
108
108
109 this.previewBoxSelector = this.withLineNo('#preview-box');
109 this.previewBoxSelector = this.withLineNo('#preview-box');
110
110
111 this.editButton = this.withLineNo('#edit-btn');
111 this.editButton = this.withLineNo('#edit-btn');
112 this.editContainer = this.withLineNo('#edit-container');
112 this.editContainer = this.withLineNo('#edit-container');
113 this.cancelButton = this.withLineNo('#cancel-btn');
113 this.cancelButton = this.withLineNo('#cancel-btn');
114 this.commentType = this.withLineNo('#comment_type');
114 this.commentType = this.withLineNo('#comment_type');
115
115
116 this.resolvesId = null;
116 this.resolvesId = null;
117 this.resolvesActionId = null;
117 this.resolvesActionId = null;
118
118
119 this.closesPr = '#close_pull_request';
119 this.closesPr = '#close_pull_request';
120
120
121 this.cmBox = this.withLineNo('#text');
121 this.cmBox = this.withLineNo('#text');
122 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
122 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
123
123
124 this.statusChange = this.withLineNo('#change_status');
124 this.statusChange = this.withLineNo('#change_status');
125
125
126 this.submitForm = formElement;
126 this.submitForm = formElement;
127
127
128 this.submitButton = $(this.submitForm).find('.submit-comment-action');
128 this.submitButton = $(this.submitForm).find('.submit-comment-action');
129 this.submitButtonText = this.submitButton.val();
129 this.submitButtonText = this.submitButton.val();
130
130
131 this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
131 this.submitDraftButton = $(this.submitForm).find('.submit-draft-action');
132 this.submitDraftButtonText = this.submitDraftButton.val();
132 this.submitDraftButtonText = this.submitDraftButton.val();
133
133
134 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
134 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
135 {'repo_name': templateContext.repo_name,
135 {'repo_name': templateContext.repo_name,
136 'commit_id': templateContext.commit_data.commit_id});
136 'commit_id': templateContext.commit_data.commit_id});
137
137
138 if (edit){
138 if (edit){
139 this.submitDraftButton.hide();
139 this.submitDraftButton.hide();
140 this.submitButtonText = _gettext('Update Comment');
140 this.submitButtonText = _gettext('Update Comment');
141 $(this.commentType).prop('disabled', true);
141 $(this.commentType).prop('disabled', true);
142 $(this.commentType).addClass('disabled');
142 $(this.commentType).addClass('disabled');
143 var editInfo =
143 var editInfo =
144 '';
144 '';
145 $(editInfo).insertBefore($(this.editButton).parent());
145 $(editInfo).insertBefore($(this.editButton).parent());
146 }
146 }
147
147
148 if (resolvesCommentId){
148 if (resolvesCommentId){
149 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
149 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
150 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
150 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
151 $(this.commentType).prop('disabled', true);
151 $(this.commentType).prop('disabled', true);
152 $(this.commentType).addClass('disabled');
152 $(this.commentType).addClass('disabled');
153
153
154 // disable select
154 // disable select
155 setTimeout(function() {
155 setTimeout(function() {
156 $(self.statusChange).select2('readonly', true);
156 $(self.statusChange).select2('readonly', true);
157 }, 10);
157 }, 10);
158
158
159 var resolvedInfo = (
159 var resolvedInfo = (
160 '<li class="resolve-action">' +
160 '<li class="resolve-action">' +
161 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
161 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
162 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
162 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
163 '</li>'
163 '</li>'
164 ).format(resolvesCommentId, _gettext('resolve comment'));
164 ).format(resolvesCommentId, _gettext('resolve comment'));
165 $(resolvedInfo).insertAfter($(this.commentType).parent());
165 $(resolvedInfo).insertAfter($(this.commentType).parent());
166 }
166 }
167
167
168 // based on commitId, or pullRequestId decide where do we submit
168 // based on commitId, or pullRequestId decide where do we submit
169 // out data
169 // out data
170 if (this.commitId){
170 if (this.commitId){
171 var pyurl = 'repo_commit_comment_create';
171 var pyurl = 'repo_commit_comment_create';
172 if(edit){
172 if(edit){
173 pyurl = 'repo_commit_comment_edit';
173 pyurl = 'repo_commit_comment_edit';
174 }
174 }
175 this.submitUrl = pyroutes.url(pyurl,
175 this.submitUrl = pyroutes.url(pyurl,
176 {'repo_name': templateContext.repo_name,
176 {'repo_name': templateContext.repo_name,
177 'commit_id': this.commitId,
177 'commit_id': this.commitId,
178 'comment_id': comment_id});
178 'comment_id': comment_id});
179 this.selfUrl = pyroutes.url('repo_commit',
179 this.selfUrl = pyroutes.url('repo_commit',
180 {'repo_name': templateContext.repo_name,
180 {'repo_name': templateContext.repo_name,
181 'commit_id': this.commitId});
181 'commit_id': this.commitId});
182
182
183 } else if (this.pullRequestId) {
183 } else if (this.pullRequestId) {
184 var pyurl = 'pullrequest_comment_create';
184 var pyurl = 'pullrequest_comment_create';
185 if(edit){
185 if(edit){
186 pyurl = 'pullrequest_comment_edit';
186 pyurl = 'pullrequest_comment_edit';
187 }
187 }
188 this.submitUrl = pyroutes.url(pyurl,
188 this.submitUrl = pyroutes.url(pyurl,
189 {'repo_name': templateContext.repo_name,
189 {'repo_name': templateContext.repo_name,
190 'pull_request_id': this.pullRequestId,
190 'pull_request_id': this.pullRequestId,
191 'comment_id': comment_id});
191 'comment_id': comment_id});
192 this.selfUrl = pyroutes.url('pullrequest_show',
192 this.selfUrl = pyroutes.url('pullrequest_show',
193 {'repo_name': templateContext.repo_name,
193 {'repo_name': templateContext.repo_name,
194 'pull_request_id': this.pullRequestId});
194 'pull_request_id': this.pullRequestId});
195
195
196 } else {
196 } else {
197 throw new Error(
197 throw new Error(
198 'CommentForm requires pullRequestId, or commitId to be specified.')
198 'CommentForm requires pullRequestId, or commitId to be specified.')
199 }
199 }
200
200
201 // FUNCTIONS and helpers
201 // FUNCTIONS and helpers
202 var self = this;
202 var self = this;
203
203
204 this.isInline = function(){
204 this.isInline = function(){
205 return this.lineNo && this.lineNo != 'general';
205 return this.lineNo && this.lineNo != 'general';
206 };
206 };
207
207
208 this.getCmInstance = function(){
208 this.getCmInstance = function(){
209 return this.cm
209 return this.cm
210 };
210 };
211
211
212 this.setPlaceholder = function(placeholder) {
212 this.setPlaceholder = function(placeholder) {
213 var cm = this.getCmInstance();
213 var cm = this.getCmInstance();
214 if (cm){
214 if (cm){
215 cm.setOption('placeholder', placeholder);
215 cm.setOption('placeholder', placeholder);
216 }
216 }
217 };
217 };
218
218
219 this.getCommentStatus = function() {
219 this.getCommentStatus = function() {
220 return $(this.submitForm).find(this.statusChange).val();
220 return $(this.submitForm).find(this.statusChange).val();
221 };
221 };
222
222
223 this.getCommentType = function() {
223 this.getCommentType = function() {
224 return $(this.submitForm).find(this.commentType).val();
224 return $(this.submitForm).find(this.commentType).val();
225 };
225 };
226
226
227 this.getDraftState = function () {
227 this.getDraftState = function () {
228 var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
228 var submitterElem = $(this.submitForm).find('input[type="submit"].submitter');
229 var data = $(submitterElem).data('isDraft');
229 var data = $(submitterElem).data('isDraft');
230 return data
230 return data
231 }
231 }
232
232
233 this.getResolvesId = function() {
233 this.getResolvesId = function() {
234 return $(this.submitForm).find(this.resolvesId).val() || null;
234 return $(this.submitForm).find(this.resolvesId).val() || null;
235 };
235 };
236
236
237 this.getClosePr = function() {
237 this.getClosePr = function() {
238 return $(this.submitForm).find(this.closesPr).val() || null;
238 return $(this.submitForm).find(this.closesPr).val() || null;
239 };
239 };
240
240
241 this.markCommentResolved = function(resolvedCommentId){
241 this.markCommentResolved = function(resolvedCommentId){
242 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
242 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
243 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
243 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
244 };
244 };
245
245
246 this.isAllowedToSubmit = function() {
246 this.isAllowedToSubmit = function() {
247 var commentDisabled = $(this.submitButton).prop('disabled');
247 var commentDisabled = $(this.submitButton).prop('disabled');
248 var draftDisabled = $(this.submitDraftButton).prop('disabled');
248 var draftDisabled = $(this.submitDraftButton).prop('disabled');
249 return !commentDisabled && !draftDisabled;
249 return !commentDisabled && !draftDisabled;
250 };
250 };
251
251
252 this.initStatusChangeSelector = function(){
252 this.initStatusChangeSelector = function(){
253 var formatChangeStatus = function(state, escapeMarkup) {
253 var formatChangeStatus = function(state, escapeMarkup) {
254 var originalOption = state.element;
254 var originalOption = state.element;
255 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
255 var tmpl = '<i class="icon-circle review-status-{0}"></i><span>{1}</span>'.format($(originalOption).data('status'), escapeMarkup(state.text));
256 return tmpl
256 return tmpl
257 };
257 };
258 var formatResult = function(result, container, query, escapeMarkup) {
258 var formatResult = function(result, container, query, escapeMarkup) {
259 return formatChangeStatus(result, escapeMarkup);
259 return formatChangeStatus(result, escapeMarkup);
260 };
260 };
261
261
262 var formatSelection = function(data, container, escapeMarkup) {
262 var formatSelection = function(data, container, escapeMarkup) {
263 return formatChangeStatus(data, escapeMarkup);
263 return formatChangeStatus(data, escapeMarkup);
264 };
264 };
265
265
266 $(this.submitForm).find(this.statusChange).select2({
266 $(this.submitForm).find(this.statusChange).select2({
267 placeholder: _gettext('Status Review'),
267 placeholder: _gettext('Status Review'),
268 formatResult: formatResult,
268 formatResult: formatResult,
269 formatSelection: formatSelection,
269 formatSelection: formatSelection,
270 containerCssClass: "drop-menu status_box_menu",
270 containerCssClass: "drop-menu status_box_menu",
271 dropdownCssClass: "drop-menu-dropdown",
271 dropdownCssClass: "drop-menu-dropdown",
272 dropdownAutoWidth: true,
272 dropdownAutoWidth: true,
273 minimumResultsForSearch: -1
273 minimumResultsForSearch: -1
274 });
274 });
275
275
276 $(this.submitForm).find(this.statusChange).on('change', function() {
276 $(this.submitForm).find(this.statusChange).on('change', function() {
277 var status = self.getCommentStatus();
277 var status = self.getCommentStatus();
278
278
279 if (status && !self.isInline()) {
279 if (status && !self.isInline()) {
280 $(self.submitButton).prop('disabled', false);
280 $(self.submitButton).prop('disabled', false);
281 $(self.submitDraftButton).prop('disabled', false);
281 $(self.submitDraftButton).prop('disabled', false);
282 }
282 }
283
283
284 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
284 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
285 self.setPlaceholder(placeholderText)
285 self.setPlaceholder(placeholderText)
286 })
286 })
287 };
287 };
288
288
289 // reset the comment form into it's original state
289 // reset the comment form into it's original state
290 this.resetCommentFormState = function(content) {
290 this.resetCommentFormState = function(content) {
291 content = content || '';
291 content = content || '';
292
292
293 $(this.editContainer).show();
293 $(this.editContainer).show();
294 $(this.editButton).parent().addClass('active');
294 $(this.editButton).parent().addClass('active');
295
295
296 $(this.previewContainer).hide();
296 $(this.previewContainer).hide();
297 $(this.previewButton).parent().removeClass('active');
297 $(this.previewButton).parent().removeClass('active');
298
298
299 this.setActionButtonsDisabled(true);
299 this.setActionButtonsDisabled(true);
300 self.cm.setValue(content);
300 self.cm.setValue(content);
301 self.cm.setOption("readOnly", false);
301 self.cm.setOption("readOnly", false);
302
302
303 if (this.resolvesId) {
303 if (this.resolvesId) {
304 // destroy the resolve action
304 // destroy the resolve action
305 $(this.resolvesId).parent().remove();
305 $(this.resolvesId).parent().remove();
306 }
306 }
307 // reset closingPR flag
307 // reset closingPR flag
308 $('.close-pr-input').remove();
308 $('.close-pr-input').remove();
309
309
310 $(this.statusChange).select2('readonly', false);
310 $(this.statusChange).select2('readonly', false);
311 };
311 };
312
312
313 this.globalSubmitSuccessCallback = function(comment){
313 this.globalSubmitSuccessCallback = function(comment){
314 // default behaviour is to call GLOBAL hook, if it's registered.
314 // default behaviour is to call GLOBAL hook, if it's registered.
315 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
315 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
316 commentFormGlobalSubmitSuccessCallback(comment);
316 commentFormGlobalSubmitSuccessCallback(comment);
317 }
317 }
318 };
318 };
319
319
320 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
320 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
321 return _submitAjaxPOST(url, postData, successHandler, failHandler);
321 return _submitAjaxPOST(url, postData, successHandler, failHandler);
322 };
322 };
323
323
324 // overwrite a submitHandler, we need to do it for inline comments
324 // overwrite a submitHandler, we need to do it for inline comments
325 this.setHandleFormSubmit = function(callback) {
325 this.setHandleFormSubmit = function(callback) {
326 this.handleFormSubmit = callback;
326 this.handleFormSubmit = callback;
327 };
327 };
328
328
329 // overwrite a submitSuccessHandler
329 // overwrite a submitSuccessHandler
330 this.setGlobalSubmitSuccessCallback = function(callback) {
330 this.setGlobalSubmitSuccessCallback = function(callback) {
331 this.globalSubmitSuccessCallback = callback;
331 this.globalSubmitSuccessCallback = callback;
332 };
332 };
333
333
334 // default handler for for submit for main comments
334 // default handler for for submit for main comments
335 this.handleFormSubmit = function() {
335 this.handleFormSubmit = function() {
336 var text = self.cm.getValue();
336 var text = self.cm.getValue();
337 var status = self.getCommentStatus();
337 var status = self.getCommentStatus();
338 var commentType = self.getCommentType();
338 var commentType = self.getCommentType();
339 var isDraft = self.getDraftState();
339 var isDraft = self.getDraftState();
340 var resolvesCommentId = self.getResolvesId();
340 var resolvesCommentId = self.getResolvesId();
341 var closePullRequest = self.getClosePr();
341 var closePullRequest = self.getClosePr();
342
342
343 if (text === "" && !status) {
343 if (text === "" && !status) {
344 return;
344 return;
345 }
345 }
346
346
347 var excludeCancelBtn = false;
347 var excludeCancelBtn = false;
348 var submitEvent = true;
348 var submitEvent = true;
349 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
349 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
350 self.cm.setOption("readOnly", true);
350 self.cm.setOption("readOnly", true);
351
351
352 var postData = {
352 var postData = {
353 'text': text,
353 'text': text,
354 'changeset_status': status,
354 'changeset_status': status,
355 'comment_type': commentType,
355 'comment_type': commentType,
356 'csrf_token': CSRF_TOKEN
356 'csrf_token': CSRF_TOKEN
357 };
357 };
358
358
359 if (resolvesCommentId) {
359 if (resolvesCommentId) {
360 postData['resolves_comment_id'] = resolvesCommentId;
360 postData['resolves_comment_id'] = resolvesCommentId;
361 }
361 }
362
362
363 if (closePullRequest) {
363 if (closePullRequest) {
364 postData['close_pull_request'] = true;
364 postData['close_pull_request'] = true;
365 }
365 }
366
366
367 // submitSuccess for general comments
367 // submitSuccess for general comments
368 var submitSuccessCallback = function(json_data) {
368 var submitSuccessCallback = function(json_data) {
369 // reload page if we change status for single commit.
369 // reload page if we change status for single commit.
370 if (status && self.commitId) {
370 if (status && self.commitId) {
371 location.reload(true);
371 location.reload(true);
372 } else {
372 } else {
373 // inject newly created comments, json_data is {<comment_id>: {}}
373 // inject newly created comments, json_data is {<comment_id>: {}}
374 self.attachGeneralComment(json_data)
374 self.attachGeneralComment(json_data)
375
375
376 self.resetCommentFormState();
376 self.resetCommentFormState();
377 timeagoActivate();
377 timeagoActivate();
378 tooltipActivate();
378 tooltipActivate();
379
379
380 // mark visually which comment was resolved
380 // mark visually which comment was resolved
381 if (resolvesCommentId) {
381 if (resolvesCommentId) {
382 self.markCommentResolved(resolvesCommentId);
382 self.markCommentResolved(resolvesCommentId);
383 }
383 }
384 }
384 }
385
385
386 // run global callback on submit
386 // run global callback on submit
387 self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
387 self.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
388
388
389 };
389 };
390 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
390 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
391 var prefix = "Error while submitting comment.\n"
391 var prefix = "Error while submitting comment.\n"
392 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
392 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
393 ajaxErrorSwal(message);
393 ajaxErrorSwal(message);
394 self.resetCommentFormState(text);
394 self.resetCommentFormState(text);
395 };
395 };
396 self.submitAjaxPOST(
396 self.submitAjaxPOST(
397 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
397 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
398 };
398 };
399
399
400 this.previewSuccessCallback = function(o) {
400 this.previewSuccessCallback = function(o) {
401 $(self.previewBoxSelector).html(o);
401 $(self.previewBoxSelector).html(o);
402 $(self.previewBoxSelector).removeClass('unloaded');
402 $(self.previewBoxSelector).removeClass('unloaded');
403
403
404 // swap buttons, making preview active
404 // swap buttons, making preview active
405 $(self.previewButton).parent().addClass('active');
405 $(self.previewButton).parent().addClass('active');
406 $(self.editButton).parent().removeClass('active');
406 $(self.editButton).parent().removeClass('active');
407
407
408 // unlock buttons
408 // unlock buttons
409 self.setActionButtonsDisabled(false);
409 self.setActionButtonsDisabled(false);
410 };
410 };
411
411
412 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
412 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
413 excludeCancelBtn = excludeCancelBtn || false;
413 excludeCancelBtn = excludeCancelBtn || false;
414 submitEvent = submitEvent || false;
414 submitEvent = submitEvent || false;
415
415
416 $(this.editButton).prop('disabled', state);
416 $(this.editButton).prop('disabled', state);
417 $(this.previewButton).prop('disabled', state);
417 $(this.previewButton).prop('disabled', state);
418
418
419 if (!excludeCancelBtn) {
419 if (!excludeCancelBtn) {
420 $(this.cancelButton).prop('disabled', state);
420 $(this.cancelButton).prop('disabled', state);
421 }
421 }
422
422
423 var submitState = state;
423 var submitState = state;
424 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
424 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
425 // if the value of commit review status is set, we allow
425 // if the value of commit review status is set, we allow
426 // submit button, but only on Main form, isInline means inline
426 // submit button, but only on Main form, isInline means inline
427 submitState = false
427 submitState = false
428 }
428 }
429
429
430 $(this.submitButton).prop('disabled', submitState);
430 $(this.submitButton).prop('disabled', submitState);
431 $(this.submitDraftButton).prop('disabled', submitState);
431 $(this.submitDraftButton).prop('disabled', submitState);
432
432
433 if (submitEvent) {
433 if (submitEvent) {
434 var isDraft = self.getDraftState();
434 var isDraft = self.getDraftState();
435
435
436 if (isDraft) {
436 if (isDraft) {
437 $(this.submitDraftButton).val(_gettext('Saving Draft...'));
437 $(this.submitDraftButton).val(_gettext('Saving Draft...'));
438 } else {
438 } else {
439 $(this.submitButton).val(_gettext('Submitting...'));
439 $(this.submitButton).val(_gettext('Submitting...'));
440 }
440 }
441
441
442 } else {
442 } else {
443 $(this.submitButton).val(this.submitButtonText);
443 $(this.submitButton).val(this.submitButtonText);
444 $(this.submitDraftButton).val(this.submitDraftButtonText);
444 $(this.submitDraftButton).val(this.submitDraftButtonText);
445 }
445 }
446
446
447 };
447 };
448
448
449 // lock preview/edit/submit buttons on load, but exclude cancel button
449 // lock preview/edit/submit buttons on load, but exclude cancel button
450 var excludeCancelBtn = true;
450 var excludeCancelBtn = true;
451 this.setActionButtonsDisabled(true, excludeCancelBtn);
451 this.setActionButtonsDisabled(true, excludeCancelBtn);
452
452
453 // anonymous users don't have access to initialized CM instance
453 // anonymous users don't have access to initialized CM instance
454 if (this.cm !== undefined){
454 if (this.cm !== undefined){
455 this.cm.on('change', function(cMirror) {
455 this.cm.on('change', function(cMirror) {
456 if (cMirror.getValue() === "") {
456 if (cMirror.getValue() === "") {
457 self.setActionButtonsDisabled(true, excludeCancelBtn)
457 self.setActionButtonsDisabled(true, excludeCancelBtn)
458 } else {
458 } else {
459 self.setActionButtonsDisabled(false, excludeCancelBtn)
459 self.setActionButtonsDisabled(false, excludeCancelBtn)
460 }
460 }
461 });
461 });
462 }
462 }
463
463
464 $(this.editButton).on('click', function(e) {
464 $(this.editButton).on('click', function(e) {
465 e.preventDefault();
465 e.preventDefault();
466
466
467 $(self.previewButton).parent().removeClass('active');
467 $(self.previewButton).parent().removeClass('active');
468 $(self.previewContainer).hide();
468 $(self.previewContainer).hide();
469
469
470 $(self.editButton).parent().addClass('active');
470 $(self.editButton).parent().addClass('active');
471 $(self.editContainer).show();
471 $(self.editContainer).show();
472
472
473 });
473 });
474
474
475 $(this.previewButton).on('click', function(e) {
475 $(this.previewButton).on('click', function(e) {
476 e.preventDefault();
476 e.preventDefault();
477 var text = self.cm.getValue();
477 var text = self.cm.getValue();
478
478
479 if (text === "") {
479 if (text === "") {
480 return;
480 return;
481 }
481 }
482
482
483 var postData = {
483 var postData = {
484 'text': text,
484 'text': text,
485 'renderer': templateContext.visual.default_renderer,
485 'renderer': templateContext.visual.default_renderer,
486 'csrf_token': CSRF_TOKEN
486 'csrf_token': CSRF_TOKEN
487 };
487 };
488
488
489 // lock ALL buttons on preview
489 // lock ALL buttons on preview
490 self.setActionButtonsDisabled(true);
490 self.setActionButtonsDisabled(true);
491
491
492 $(self.previewBoxSelector).addClass('unloaded');
492 $(self.previewBoxSelector).addClass('unloaded');
493 $(self.previewBoxSelector).html(_gettext('Loading ...'));
493 $(self.previewBoxSelector).html(_gettext('Loading ...'));
494
494
495 $(self.editContainer).hide();
495 $(self.editContainer).hide();
496 $(self.previewContainer).show();
496 $(self.previewContainer).show();
497
497
498 // by default we reset state of comment preserving the text
498 // by default we reset state of comment preserving the text
499 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
499 var previewFailCallback = function(jqXHR, textStatus, errorThrown) {
500 var prefix = "Error while preview of comment.\n"
500 var prefix = "Error while preview of comment.\n"
501 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
501 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
502 ajaxErrorSwal(message);
502 ajaxErrorSwal(message);
503
503
504 self.resetCommentFormState(text)
504 self.resetCommentFormState(text)
505 };
505 };
506 self.submitAjaxPOST(
506 self.submitAjaxPOST(
507 self.previewUrl, postData, self.previewSuccessCallback,
507 self.previewUrl, postData, self.previewSuccessCallback,
508 previewFailCallback);
508 previewFailCallback);
509
509
510 $(self.previewButton).parent().addClass('active');
510 $(self.previewButton).parent().addClass('active');
511 $(self.editButton).parent().removeClass('active');
511 $(self.editButton).parent().removeClass('active');
512 });
512 });
513
513
514 $(this.submitForm).submit(function(e) {
514 $(this.submitForm).submit(function(e) {
515 e.preventDefault();
515 e.preventDefault();
516 var allowedToSubmit = self.isAllowedToSubmit();
516 var allowedToSubmit = self.isAllowedToSubmit();
517 if (!allowedToSubmit){
517 if (!allowedToSubmit){
518 return false;
518 return false;
519 }
519 }
520
520
521 self.handleFormSubmit();
521 self.handleFormSubmit();
522 });
522 });
523
523
524 }
524 }
525
525
526 return CommentForm;
526 return CommentForm;
527 });
527 });
528
528
529 /* selector for comment versions */
529 /* selector for comment versions */
530 var initVersionSelector = function(selector, initialData) {
530 var initVersionSelector = function(selector, initialData) {
531
531
532 var formatResult = function(result, container, query, escapeMarkup) {
532 var formatResult = function(result, container, query, escapeMarkup) {
533
533
534 return renderTemplate('commentVersion', {
534 return renderTemplate('commentVersion', {
535 show_disabled: true,
535 show_disabled: true,
536 version: result.comment_version,
536 version: result.comment_version,
537 user_name: result.comment_author_username,
537 user_name: result.comment_author_username,
538 gravatar_url: result.comment_author_gravatar,
538 gravatar_url: result.comment_author_gravatar,
539 size: 16,
539 size: 16,
540 timeago_component: result.comment_created_on,
540 timeago_component: result.comment_created_on,
541 })
541 })
542 };
542 };
543
543
544 $(selector).select2({
544 $(selector).select2({
545 placeholder: "Edited",
545 placeholder: "Edited",
546 containerCssClass: "drop-menu-comment-history",
546 containerCssClass: "drop-menu-comment-history",
547 dropdownCssClass: "drop-menu-dropdown",
547 dropdownCssClass: "drop-menu-dropdown",
548 dropdownAutoWidth: true,
548 dropdownAutoWidth: true,
549 minimumResultsForSearch: -1,
549 minimumResultsForSearch: -1,
550 data: initialData,
550 data: initialData,
551 formatResult: formatResult,
551 formatResult: formatResult,
552 });
552 });
553
553
554 $(selector).on('select2-selecting', function (e) {
554 $(selector).on('select2-selecting', function (e) {
555 // hide the mast as we later do preventDefault()
555 // hide the mast as we later do preventDefault()
556 $("#select2-drop-mask").click();
556 $("#select2-drop-mask").click();
557 e.preventDefault();
557 e.preventDefault();
558 e.choice.action();
558 e.choice.action();
559 });
559 });
560
560
561 $(selector).on("select2-open", function() {
561 $(selector).on("select2-open", function() {
562 timeagoActivate();
562 timeagoActivate();
563 });
563 });
564 };
564 };
565
565
566 /* comments controller */
566 /* comments controller */
567 var CommentsController = function() {
567 var CommentsController = function() {
568 var mainComment = '#text';
568 var mainComment = '#text';
569 var self = this;
569 var self = this;
570
570
571 this.showVersion = function (comment_id, comment_history_id) {
571 this.showVersion = function (comment_id, comment_history_id) {
572
572
573 var historyViewUrl = pyroutes.url(
573 var historyViewUrl = pyroutes.url(
574 'repo_commit_comment_history_view',
574 'repo_commit_comment_history_view',
575 {
575 {
576 'repo_name': templateContext.repo_name,
576 'repo_name': templateContext.repo_name,
577 'commit_id': comment_id,
577 'commit_id': comment_id,
578 'comment_history_id': comment_history_id,
578 'comment_history_id': comment_history_id,
579 }
579 }
580 );
580 );
581 successRenderCommit = function (data) {
581 successRenderCommit = function (data) {
582 SwalNoAnimation.fire({
582 SwalNoAnimation.fire({
583 html: data,
583 html: data,
584 title: '',
584 title: '',
585 });
585 });
586 };
586 };
587 failRenderCommit = function () {
587 failRenderCommit = function () {
588 SwalNoAnimation.fire({
588 SwalNoAnimation.fire({
589 html: 'Error while loading comment history',
589 html: 'Error while loading comment history',
590 title: '',
590 title: '',
591 });
591 });
592 };
592 };
593 _submitAjaxPOST(
593 _submitAjaxPOST(
594 historyViewUrl, {'csrf_token': CSRF_TOKEN},
594 historyViewUrl, {'csrf_token': CSRF_TOKEN},
595 successRenderCommit,
595 successRenderCommit,
596 failRenderCommit
596 failRenderCommit
597 );
597 );
598 };
598 };
599
599
600 this.getLineNumber = function(node) {
600 this.getLineNumber = function(node) {
601 var $node = $(node);
601 var $node = $(node);
602 var lineNo = $node.closest('td').attr('data-line-no');
602 var lineNo = $node.closest('td').attr('data-line-no');
603 if (lineNo === undefined && $node.data('commentInline')){
603 if (lineNo === undefined && $node.data('commentInline')){
604 lineNo = $node.data('commentLineNo')
604 lineNo = $node.data('commentLineNo')
605 }
605 }
606
606
607 return lineNo
607 return lineNo
608 };
608 };
609
609
610 this.scrollToComment = function(node, offset, outdated) {
610 this.scrollToComment = function(node, offset, outdated) {
611 if (offset === undefined) {
611 if (offset === undefined) {
612 offset = 0;
612 offset = 0;
613 }
613 }
614 var outdated = outdated || false;
614 var outdated = outdated || false;
615 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
615 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
616
616
617 if (!node) {
617 if (!node) {
618 node = $('.comment-selected');
618 node = $('.comment-selected');
619 if (!node.length) {
619 if (!node.length) {
620 node = $('comment-current')
620 node = $('comment-current')
621 }
621 }
622 }
622 }
623
623
624 $wrapper = $(node).closest('div.comment');
624 $wrapper = $(node).closest('div.comment');
625
625
626 // show hidden comment when referenced.
626 // show hidden comment when referenced.
627 if (!$wrapper.is(':visible')){
627 if (!$wrapper.is(':visible')){
628 $wrapper.show();
628 $wrapper.show();
629 }
629 }
630
630
631 $comment = $(node).closest(klass);
631 $comment = $(node).closest(klass);
632 $comments = $(klass);
632 $comments = $(klass);
633
633
634 $('.comment-selected').removeClass('comment-selected');
634 $('.comment-selected').removeClass('comment-selected');
635
635
636 var nextIdx = $(klass).index($comment) + offset;
636 var nextIdx = $(klass).index($comment) + offset;
637 if (nextIdx >= $comments.length) {
637 if (nextIdx >= $comments.length) {
638 nextIdx = 0;
638 nextIdx = 0;
639 }
639 }
640 var $next = $(klass).eq(nextIdx);
640 var $next = $(klass).eq(nextIdx);
641
641
642 var $cb = $next.closest('.cb');
642 var $cb = $next.closest('.cb');
643 $cb.removeClass('cb-collapsed');
643 $cb.removeClass('cb-collapsed');
644
644
645 var $filediffCollapseState = $cb.closest('.filediff').prev();
645 var $filediffCollapseState = $cb.closest('.filediff').prev();
646 $filediffCollapseState.prop('checked', false);
646 $filediffCollapseState.prop('checked', false);
647 $next.addClass('comment-selected');
647 $next.addClass('comment-selected');
648 scrollToElement($next);
648 scrollToElement($next);
649 return false;
649 return false;
650 };
650 };
651
651
652 this.nextComment = function(node) {
652 this.nextComment = function(node) {
653 return self.scrollToComment(node, 1);
653 return self.scrollToComment(node, 1);
654 };
654 };
655
655
656 this.prevComment = function(node) {
656 this.prevComment = function(node) {
657 return self.scrollToComment(node, -1);
657 return self.scrollToComment(node, -1);
658 };
658 };
659
659
660 this.nextOutdatedComment = function(node) {
660 this.nextOutdatedComment = function(node) {
661 return self.scrollToComment(node, 1, true);
661 return self.scrollToComment(node, 1, true);
662 };
662 };
663
663
664 this.prevOutdatedComment = function(node) {
664 this.prevOutdatedComment = function(node) {
665 return self.scrollToComment(node, -1, true);
665 return self.scrollToComment(node, -1, true);
666 };
666 };
667
667
668 this.cancelComment = function (node) {
668 this.cancelComment = function (node) {
669 var $node = $(node);
669 var $node = $(node);
670 var edit = $(this).attr('edit');
670 var edit = $(this).attr('edit');
671 var $inlineComments = $node.closest('div.inline-comments');
671 var $inlineComments = $node.closest('div.inline-comments');
672
672
673 if (edit) {
673 if (edit) {
674 var $general_comments = null;
674 var $general_comments = null;
675 if (!$inlineComments.length) {
675 if (!$inlineComments.length) {
676 $general_comments = $('#comments');
676 $general_comments = $('#comments');
677 var $comment = $general_comments.parent().find('div.comment:hidden');
677 var $comment = $general_comments.parent().find('div.comment:hidden');
678 // show hidden general comment form
678 // show hidden general comment form
679 $('#cb-comment-general-form-placeholder').show();
679 $('#cb-comment-general-form-placeholder').show();
680 } else {
680 } else {
681 var $comment = $inlineComments.find('div.comment:hidden');
681 var $comment = $inlineComments.find('div.comment:hidden');
682 }
682 }
683 $comment.show();
683 $comment.show();
684 }
684 }
685 var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper')
685 var $replyWrapper = $node.closest('.comment-inline-form').closest('.reply-thread-container-wrapper')
686 $replyWrapper.removeClass('comment-form-active');
686 $replyWrapper.removeClass('comment-form-active');
687
687
688 var lastComment = $inlineComments.find('.comment-inline').last();
688 var lastComment = $inlineComments.find('.comment-inline').last();
689 if ($(lastComment).hasClass('comment-outdated')) {
689 if ($(lastComment).hasClass('comment-outdated')) {
690 $replyWrapper.hide();
690 $replyWrapper.hide();
691 }
691 }
692
692
693 $node.closest('.comment-inline-form').remove();
693 $node.closest('.comment-inline-form').remove();
694 return false;
694 return false;
695 };
695 };
696
696
697 this._deleteComment = function(node) {
697 this._deleteComment = function(node) {
698 var $node = $(node);
698 var $node = $(node);
699 var $td = $node.closest('td');
699 var $td = $node.closest('td');
700 var $comment = $node.closest('.comment');
700 var $comment = $node.closest('.comment');
701 var comment_id = $($comment).data('commentId');
701 var comment_id = $($comment).data('commentId');
702 var isDraft = $($comment).data('commentDraft');
702 var isDraft = $($comment).data('commentDraft');
703 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
703
704 var pullRequestId = templateContext.pull_request_data.pull_request_id;
705 var commitId = templateContext.commit_data.commit_id;
706
707 if (pullRequestId) {
708 var url = pyroutes.url('pullrequest_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
709 } else if (commitId) {
710 var url = pyroutes.url('repo_commit_comment_delete', {"comment_id": comment_id, "repo_name": templateContext.repo_name, "commit_id": commitId})
711 }
712
704 var postData = {
713 var postData = {
705 'csrf_token': CSRF_TOKEN
714 'csrf_token': CSRF_TOKEN
706 };
715 };
707
716
708 $comment.addClass('comment-deleting');
717 $comment.addClass('comment-deleting');
709 $comment.hide('fast');
718 $comment.hide('fast');
710
719
711 var success = function(response) {
720 var success = function(response) {
712 $comment.remove();
721 $comment.remove();
713
722
714 if (window.updateSticky !== undefined) {
723 if (window.updateSticky !== undefined) {
715 // potentially our comments change the active window size, so we
724 // potentially our comments change the active window size, so we
716 // notify sticky elements
725 // notify sticky elements
717 updateSticky()
726 updateSticky()
718 }
727 }
719
728
720 if (window.refreshAllComments !== undefined && !isDraft) {
729 if (window.refreshAllComments !== undefined && !isDraft) {
721 // if we have this handler, run it, and refresh all comments boxes
730 // if we have this handler, run it, and refresh all comments boxes
722 refreshAllComments()
731 refreshAllComments()
723 }
732 }
724 return false;
733 return false;
725 };
734 };
726
735
727 var failure = function(jqXHR, textStatus, errorThrown) {
736 var failure = function(jqXHR, textStatus, errorThrown) {
728 var prefix = "Error while deleting this comment.\n"
737 var prefix = "Error while deleting this comment.\n"
729 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
738 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
730 ajaxErrorSwal(message);
739 ajaxErrorSwal(message);
731
740
732 $comment.show('fast');
741 $comment.show('fast');
733 $comment.removeClass('comment-deleting');
742 $comment.removeClass('comment-deleting');
734 return false;
743 return false;
735 };
744 };
736 ajaxPOST(url, postData, success, failure);
745 ajaxPOST(url, postData, success, failure);
737
746
738 }
747 }
739
748
740 this.deleteComment = function(node) {
749 this.deleteComment = function(node) {
741 var $comment = $(node).closest('.comment');
750 var $comment = $(node).closest('.comment');
742 var comment_id = $comment.attr('data-comment-id');
751 var comment_id = $comment.attr('data-comment-id');
743
752
744 SwalNoAnimation.fire({
753 SwalNoAnimation.fire({
745 title: 'Delete this comment?',
754 title: 'Delete this comment?',
746 icon: 'warning',
755 icon: 'warning',
747 showCancelButton: true,
756 showCancelButton: true,
748 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
757 confirmButtonText: _gettext('Yes, delete comment #{0}!').format(comment_id),
749
758
750 }).then(function(result) {
759 }).then(function(result) {
751 if (result.value) {
760 if (result.value) {
752 self._deleteComment(node);
761 self._deleteComment(node);
753 }
762 }
754 })
763 })
755 };
764 };
756
765
757 this._finalizeDrafts = function(commentIds) {
766 this._finalizeDrafts = function(commentIds) {
758
767
768 var pullRequestId = templateContext.pull_request_data.pull_request_id;
769 var commitId = templateContext.commit_data.commit_id;
770
771 if (pullRequestId) {
772 var url = pyroutes.url('pullrequest_draft_comments_submit', {"repo_name": templateContext.repo_name, "pull_request_id": pullRequestId})
773 } else if (commitId) {
774 var url = pyroutes.url('commit_draft_comments_submit', {"repo_name": templateContext.repo_name, "commit_id": commitId})
775 }
776
759 // remove the drafts so we can lock them before submit.
777 // remove the drafts so we can lock them before submit.
760 $.each(commentIds, function(idx, val){
778 $.each(commentIds, function(idx, val){
761 $('#comment-{0}'.format(val)).remove();
779 $('#comment-{0}'.format(val)).remove();
762 })
780 })
763
781
764 var params = {
765 'pull_request_id': templateContext.pull_request_data.pull_request_id,
766 'repo_name': templateContext.repo_name,
767 };
768 var url = pyroutes.url('pullrequest_draft_comments_submit', params)
769 var postData = {'comments': commentIds, 'csrf_token': CSRF_TOKEN};
782 var postData = {'comments': commentIds, 'csrf_token': CSRF_TOKEN};
770
783
771 var submitSuccessCallback = function(json_data) {
784 var submitSuccessCallback = function(json_data) {
772 self.attachInlineComment(json_data);
785 self.attachInlineComment(json_data);
773
786
774 if (window.refreshDraftComments !== undefined) {
787 if (window.refreshDraftComments !== undefined) {
775 // if we have this handler, run it, and refresh all comments boxes
788 // if we have this handler, run it, and refresh all comments boxes
776 refreshDraftComments()
789 refreshDraftComments()
777 }
790 }
778
791
779 return false;
792 return false;
780 };
793 };
781
794
782 ajaxPOST(url, postData, submitSuccessCallback)
795 ajaxPOST(url, postData, submitSuccessCallback)
783
796
784 }
797 }
785
798
786 this.finalizeDrafts = function(commentIds) {
799 this.finalizeDrafts = function(commentIds) {
787
800
788 SwalNoAnimation.fire({
801 SwalNoAnimation.fire({
789 title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length),
802 title: _ngettext('Submit {0} draft comment.', 'Submit {0} draft comments.', commentIds.length).format(commentIds.length),
790 icon: 'warning',
803 icon: 'warning',
791 showCancelButton: true,
804 showCancelButton: true,
792 confirmButtonText: _gettext('Yes'),
805 confirmButtonText: _gettext('Yes'),
793
806
794 }).then(function(result) {
807 }).then(function(result) {
795 if (result.value) {
808 if (result.value) {
796 self._finalizeDrafts(commentIds);
809 self._finalizeDrafts(commentIds);
797 }
810 }
798 })
811 })
799 };
812 };
800
813
801 this.toggleWideMode = function (node) {
814 this.toggleWideMode = function (node) {
802
815
803 if ($('#content').hasClass('wrapper')) {
816 if ($('#content').hasClass('wrapper')) {
804 $('#content').removeClass("wrapper");
817 $('#content').removeClass("wrapper");
805 $('#content').addClass("wide-mode-wrapper");
818 $('#content').addClass("wide-mode-wrapper");
806 $(node).addClass('btn-success');
819 $(node).addClass('btn-success');
807 return true
820 return true
808 } else {
821 } else {
809 $('#content').removeClass("wide-mode-wrapper");
822 $('#content').removeClass("wide-mode-wrapper");
810 $('#content').addClass("wrapper");
823 $('#content').addClass("wrapper");
811 $(node).removeClass('btn-success');
824 $(node).removeClass('btn-success');
812 return false
825 return false
813 }
826 }
814
827
815 };
828 };
816
829
817 /**
830 /**
818 * Turn off/on all comments in file diff
831 * Turn off/on all comments in file diff
819 */
832 */
820 this.toggleDiffComments = function(node) {
833 this.toggleDiffComments = function(node) {
821 // Find closes filediff container
834 // Find closes filediff container
822 var $filediff = $(node).closest('.filediff');
835 var $filediff = $(node).closest('.filediff');
823 if ($(node).hasClass('toggle-on')) {
836 if ($(node).hasClass('toggle-on')) {
824 var show = false;
837 var show = false;
825 } else if ($(node).hasClass('toggle-off')) {
838 } else if ($(node).hasClass('toggle-off')) {
826 var show = true;
839 var show = true;
827 }
840 }
828
841
829 // Toggle each individual comment block, so we can un-toggle single ones
842 // Toggle each individual comment block, so we can un-toggle single ones
830 $.each($filediff.find('.toggle-comment-action'), function(idx, val) {
843 $.each($filediff.find('.toggle-comment-action'), function(idx, val) {
831 self.toggleLineComments($(val), show)
844 self.toggleLineComments($(val), show)
832 })
845 })
833
846
834 // since we change the height of the diff container that has anchor points for upper
847 // since we change the height of the diff container that has anchor points for upper
835 // sticky header, we need to tell it to re-calculate those
848 // sticky header, we need to tell it to re-calculate those
836 if (window.updateSticky !== undefined) {
849 if (window.updateSticky !== undefined) {
837 // potentially our comments change the active window size, so we
850 // potentially our comments change the active window size, so we
838 // notify sticky elements
851 // notify sticky elements
839 updateSticky()
852 updateSticky()
840 }
853 }
841
854
842 return false;
855 return false;
843 }
856 }
844
857
845 this.toggleLineComments = function(node, show) {
858 this.toggleLineComments = function(node, show) {
846
859
847 var trElem = $(node).closest('tr')
860 var trElem = $(node).closest('tr')
848
861
849 if (show === true) {
862 if (show === true) {
850 // mark outdated comments as visible before the toggle;
863 // mark outdated comments as visible before the toggle;
851 $(trElem).find('.comment-outdated').show();
864 $(trElem).find('.comment-outdated').show();
852 $(trElem).removeClass('hide-line-comments');
865 $(trElem).removeClass('hide-line-comments');
853 } else if (show === false) {
866 } else if (show === false) {
854 $(trElem).find('.comment-outdated').hide();
867 $(trElem).find('.comment-outdated').hide();
855 $(trElem).addClass('hide-line-comments');
868 $(trElem).addClass('hide-line-comments');
856 } else {
869 } else {
857 // mark outdated comments as visible before the toggle;
870 // mark outdated comments as visible before the toggle;
858 $(trElem).find('.comment-outdated').show();
871 $(trElem).find('.comment-outdated').show();
859 $(trElem).toggleClass('hide-line-comments');
872 $(trElem).toggleClass('hide-line-comments');
860 }
873 }
861
874
862 // since we change the height of the diff container that has anchor points for upper
875 // since we change the height of the diff container that has anchor points for upper
863 // sticky header, we need to tell it to re-calculate those
876 // sticky header, we need to tell it to re-calculate those
864 if (window.updateSticky !== undefined) {
877 if (window.updateSticky !== undefined) {
865 // potentially our comments change the active window size, so we
878 // potentially our comments change the active window size, so we
866 // notify sticky elements
879 // notify sticky elements
867 updateSticky()
880 updateSticky()
868 }
881 }
869
882
870 };
883 };
871
884
872 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
885 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId, edit, comment_id){
873 var pullRequestId = templateContext.pull_request_data.pull_request_id;
886 var pullRequestId = templateContext.pull_request_data.pull_request_id;
874 var commitId = templateContext.commit_data.commit_id;
887 var commitId = templateContext.commit_data.commit_id;
875
888
876 var commentForm = new CommentForm(
889 var commentForm = new CommentForm(
877 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
890 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId, edit, comment_id);
878 var cm = commentForm.getCmInstance();
891 var cm = commentForm.getCmInstance();
879
892
880 if (resolvesCommentId){
893 if (resolvesCommentId){
881 placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
894 placeholderText = _gettext('Leave a resolution comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
882 }
895 }
883
896
884 setTimeout(function() {
897 setTimeout(function() {
885 // callbacks
898 // callbacks
886 if (cm !== undefined) {
899 if (cm !== undefined) {
887 commentForm.setPlaceholder(placeholderText);
900 commentForm.setPlaceholder(placeholderText);
888 if (commentForm.isInline()) {
901 if (commentForm.isInline()) {
889 cm.focus();
902 cm.focus();
890 cm.refresh();
903 cm.refresh();
891 }
904 }
892 }
905 }
893 }, 10);
906 }, 10);
894
907
895 // trigger scrolldown to the resolve comment, since it might be away
908 // trigger scrolldown to the resolve comment, since it might be away
896 // from the clicked
909 // from the clicked
897 if (resolvesCommentId){
910 if (resolvesCommentId){
898 var actionNode = $(commentForm.resolvesActionId).offset();
911 var actionNode = $(commentForm.resolvesActionId).offset();
899
912
900 setTimeout(function() {
913 setTimeout(function() {
901 if (actionNode) {
914 if (actionNode) {
902 $('body, html').animate({scrollTop: actionNode.top}, 10);
915 $('body, html').animate({scrollTop: actionNode.top}, 10);
903 }
916 }
904 }, 100);
917 }, 100);
905 }
918 }
906
919
907 // add dropzone support
920 // add dropzone support
908 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
921 var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) {
909 var renderer = templateContext.visual.default_renderer;
922 var renderer = templateContext.visual.default_renderer;
910 if (renderer == 'rst') {
923 if (renderer == 'rst') {
911 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
924 var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl);
912 if (isRendered){
925 if (isRendered){
913 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
926 attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl);
914 }
927 }
915 } else if (renderer == 'markdown') {
928 } else if (renderer == 'markdown') {
916 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
929 var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl);
917 if (isRendered){
930 if (isRendered){
918 attachmentUrl = '!' + attachmentUrl;
931 attachmentUrl = '!' + attachmentUrl;
919 }
932 }
920 } else {
933 } else {
921 var attachmentUrl = '{}'.format(attachmentStoreUrl);
934 var attachmentUrl = '{}'.format(attachmentStoreUrl);
922 }
935 }
923 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
936 cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine()));
924
937
925 return false;
938 return false;
926 };
939 };
927
940
928 //see: https://www.dropzonejs.com/#configuration
941 //see: https://www.dropzonejs.com/#configuration
929 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
942 var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload',
930 {'repo_name': templateContext.repo_name,
943 {'repo_name': templateContext.repo_name,
931 'commit_id': templateContext.commit_data.commit_id})
944 'commit_id': templateContext.commit_data.commit_id})
932
945
933 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
946 var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0);
934 if (previewTmpl !== undefined){
947 if (previewTmpl !== undefined){
935 var selectLink = $(formElement).find('.pick-attachment').get(0);
948 var selectLink = $(formElement).find('.pick-attachment').get(0);
936 $(formElement).find('.comment-attachment-uploader').dropzone({
949 $(formElement).find('.comment-attachment-uploader').dropzone({
937 url: storeUrl,
950 url: storeUrl,
938 headers: {"X-CSRF-Token": CSRF_TOKEN},
951 headers: {"X-CSRF-Token": CSRF_TOKEN},
939 paramName: function () {
952 paramName: function () {
940 return "attachment"
953 return "attachment"
941 }, // The name that will be used to transfer the file
954 }, // The name that will be used to transfer the file
942 clickable: selectLink,
955 clickable: selectLink,
943 parallelUploads: 1,
956 parallelUploads: 1,
944 maxFiles: 10,
957 maxFiles: 10,
945 maxFilesize: templateContext.attachment_store.max_file_size_mb,
958 maxFilesize: templateContext.attachment_store.max_file_size_mb,
946 uploadMultiple: false,
959 uploadMultiple: false,
947 autoProcessQueue: true, // if false queue will not be processed automatically.
960 autoProcessQueue: true, // if false queue will not be processed automatically.
948 createImageThumbnails: false,
961 createImageThumbnails: false,
949 previewTemplate: previewTmpl.innerHTML,
962 previewTemplate: previewTmpl.innerHTML,
950
963
951 accept: function (file, done) {
964 accept: function (file, done) {
952 done();
965 done();
953 },
966 },
954 init: function () {
967 init: function () {
955
968
956 this.on("sending", function (file, xhr, formData) {
969 this.on("sending", function (file, xhr, formData) {
957 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
970 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide();
958 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
971 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show();
959 });
972 });
960
973
961 this.on("success", function (file, response) {
974 this.on("success", function (file, response) {
962 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
975 $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show();
963 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
976 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
964
977
965 var isRendered = false;
978 var isRendered = false;
966 var ext = file.name.split('.').pop();
979 var ext = file.name.split('.').pop();
967 var imageExts = templateContext.attachment_store.image_ext;
980 var imageExts = templateContext.attachment_store.image_ext;
968 if (imageExts.indexOf(ext) !== -1){
981 if (imageExts.indexOf(ext) !== -1){
969 isRendered = true;
982 isRendered = true;
970 }
983 }
971
984
972 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
985 insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered)
973 });
986 });
974
987
975 this.on("error", function (file, errorMessage, xhr) {
988 this.on("error", function (file, errorMessage, xhr) {
976 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
989 $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide();
977
990
978 var error = null;
991 var error = null;
979
992
980 if (xhr !== undefined){
993 if (xhr !== undefined){
981 var httpStatus = xhr.status + " " + xhr.statusText;
994 var httpStatus = xhr.status + " " + xhr.statusText;
982 if (xhr !== undefined && xhr.status >= 500) {
995 if (xhr !== undefined && xhr.status >= 500) {
983 error = httpStatus;
996 error = httpStatus;
984 }
997 }
985 }
998 }
986
999
987 if (error === null) {
1000 if (error === null) {
988 error = errorMessage.error || errorMessage || httpStatus;
1001 error = errorMessage.error || errorMessage || httpStatus;
989 }
1002 }
990 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
1003 $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error));
991
1004
992 });
1005 });
993 }
1006 }
994 });
1007 });
995 }
1008 }
996 return commentForm;
1009 return commentForm;
997 };
1010 };
998
1011
999 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
1012 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
1000
1013
1001 var tmpl = $('#cb-comment-general-form-template').html();
1014 var tmpl = $('#cb-comment-general-form-template').html();
1002 tmpl = tmpl.format(null, 'general');
1015 tmpl = tmpl.format(null, 'general');
1003 var $form = $(tmpl);
1016 var $form = $(tmpl);
1004
1017
1005 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
1018 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
1006 var curForm = $formPlaceholder.find('form');
1019 var curForm = $formPlaceholder.find('form');
1007 if (curForm){
1020 if (curForm){
1008 curForm.remove();
1021 curForm.remove();
1009 }
1022 }
1010 $formPlaceholder.append($form);
1023 $formPlaceholder.append($form);
1011
1024
1012 var _form = $($form[0]);
1025 var _form = $($form[0]);
1013 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
1026 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
1014 var edit = false;
1027 var edit = false;
1015 var comment_id = null;
1028 var comment_id = null;
1016 var commentForm = this.createCommentForm(
1029 var commentForm = this.createCommentForm(
1017 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
1030 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId, edit, comment_id);
1018 commentForm.initStatusChangeSelector();
1031 commentForm.initStatusChangeSelector();
1019
1032
1020 return commentForm;
1033 return commentForm;
1021 };
1034 };
1022
1035
1023 this.editComment = function(node, line_no, f_path) {
1036 this.editComment = function(node, line_no, f_path) {
1024 self.edit = true;
1037 self.edit = true;
1025 var $node = $(node);
1038 var $node = $(node);
1026 var $td = $node.closest('td');
1039 var $td = $node.closest('td');
1027
1040
1028 var $comment = $(node).closest('.comment');
1041 var $comment = $(node).closest('.comment');
1029 var comment_id = $($comment).data('commentId');
1042 var comment_id = $($comment).data('commentId');
1030 var isDraft = $($comment).data('commentDraft');
1043 var isDraft = $($comment).data('commentDraft');
1031 var $editForm = null
1044 var $editForm = null
1032
1045
1033 var $comments = $node.closest('div.inline-comments');
1046 var $comments = $node.closest('div.inline-comments');
1034 var $general_comments = null;
1047 var $general_comments = null;
1035
1048
1036 if($comments.length){
1049 if($comments.length){
1037 // inline comments setup
1050 // inline comments setup
1038 $editForm = $comments.find('.comment-inline-form');
1051 $editForm = $comments.find('.comment-inline-form');
1039 line_no = self.getLineNumber(node)
1052 line_no = self.getLineNumber(node)
1040 }
1053 }
1041 else{
1054 else{
1042 // general comments setup
1055 // general comments setup
1043 $comments = $('#comments');
1056 $comments = $('#comments');
1044 $editForm = $comments.find('.comment-inline-form');
1057 $editForm = $comments.find('.comment-inline-form');
1045 line_no = $comment[0].id
1058 line_no = $comment[0].id
1046 $('#cb-comment-general-form-placeholder').hide();
1059 $('#cb-comment-general-form-placeholder').hide();
1047 }
1060 }
1048
1061
1049 if ($editForm.length === 0) {
1062 if ($editForm.length === 0) {
1050
1063
1051 // unhide all comments if they are hidden for a proper REPLY mode
1064 // unhide all comments if they are hidden for a proper REPLY mode
1052 var $filediff = $node.closest('.filediff');
1065 var $filediff = $node.closest('.filediff');
1053 $filediff.removeClass('hide-comments');
1066 $filediff.removeClass('hide-comments');
1054
1067
1055 $editForm = self.createNewFormWrapper(f_path, line_no);
1068 $editForm = self.createNewFormWrapper(f_path, line_no);
1056 if(f_path && line_no) {
1069 if(f_path && line_no) {
1057 $editForm.addClass('comment-inline-form-edit')
1070 $editForm.addClass('comment-inline-form-edit')
1058 }
1071 }
1059
1072
1060 $comment.after($editForm)
1073 $comment.after($editForm)
1061
1074
1062 var _form = $($editForm[0]).find('form');
1075 var _form = $($editForm[0]).find('form');
1063 var autocompleteActions = ['as_note',];
1076 var autocompleteActions = ['as_note',];
1064 var commentForm = this.createCommentForm(
1077 var commentForm = this.createCommentForm(
1065 _form, line_no, '', autocompleteActions, resolvesCommentId,
1078 _form, line_no, '', autocompleteActions, resolvesCommentId,
1066 this.edit, comment_id);
1079 this.edit, comment_id);
1067 var old_comment_text_binary = $comment.attr('data-comment-text');
1080 var old_comment_text_binary = $comment.attr('data-comment-text');
1068 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
1081 var old_comment_text = b64DecodeUnicode(old_comment_text_binary);
1069 commentForm.cm.setValue(old_comment_text);
1082 commentForm.cm.setValue(old_comment_text);
1070 $comment.hide();
1083 $comment.hide();
1071 tooltipActivate();
1084 tooltipActivate();
1072
1085
1073 // set a CUSTOM submit handler for inline comment edit action.
1086 // set a CUSTOM submit handler for inline comment edit action.
1074 commentForm.setHandleFormSubmit(function(o) {
1087 commentForm.setHandleFormSubmit(function(o) {
1075 var text = commentForm.cm.getValue();
1088 var text = commentForm.cm.getValue();
1076 var commentType = commentForm.getCommentType();
1089 var commentType = commentForm.getCommentType();
1077
1090
1078 if (text === "") {
1091 if (text === "") {
1079 return;
1092 return;
1080 }
1093 }
1081
1094
1082 if (old_comment_text == text) {
1095 if (old_comment_text == text) {
1083 SwalNoAnimation.fire({
1096 SwalNoAnimation.fire({
1084 title: 'Unable to edit comment',
1097 title: 'Unable to edit comment',
1085 html: _gettext('Comment body was not changed.'),
1098 html: _gettext('Comment body was not changed.'),
1086 });
1099 });
1087 return;
1100 return;
1088 }
1101 }
1089 var excludeCancelBtn = false;
1102 var excludeCancelBtn = false;
1090 var submitEvent = true;
1103 var submitEvent = true;
1091 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1104 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1092 commentForm.cm.setOption("readOnly", true);
1105 commentForm.cm.setOption("readOnly", true);
1093
1106
1094 // Read last version known
1107 // Read last version known
1095 var versionSelector = $('#comment_versions_{0}'.format(comment_id));
1108 var versionSelector = $('#comment_versions_{0}'.format(comment_id));
1096 var version = versionSelector.data('lastVersion');
1109 var version = versionSelector.data('lastVersion');
1097
1110
1098 if (!version) {
1111 if (!version) {
1099 version = 0;
1112 version = 0;
1100 }
1113 }
1101
1114
1102 var postData = {
1115 var postData = {
1103 'text': text,
1116 'text': text,
1104 'f_path': f_path,
1117 'f_path': f_path,
1105 'line': line_no,
1118 'line': line_no,
1106 'comment_type': commentType,
1119 'comment_type': commentType,
1107 'draft': isDraft,
1120 'draft': isDraft,
1108 'version': version,
1121 'version': version,
1109 'csrf_token': CSRF_TOKEN
1122 'csrf_token': CSRF_TOKEN
1110 };
1123 };
1111
1124
1112 var submitSuccessCallback = function(json_data) {
1125 var submitSuccessCallback = function(json_data) {
1113 $editForm.remove();
1126 $editForm.remove();
1114 $comment.show();
1127 $comment.show();
1115 var postData = {
1128 var postData = {
1116 'text': text,
1129 'text': text,
1117 'renderer': $comment.attr('data-comment-renderer'),
1130 'renderer': $comment.attr('data-comment-renderer'),
1118 'csrf_token': CSRF_TOKEN
1131 'csrf_token': CSRF_TOKEN
1119 };
1132 };
1120
1133
1121 /* Inject new edited version selector */
1134 /* Inject new edited version selector */
1122 var updateCommentVersionDropDown = function () {
1135 var updateCommentVersionDropDown = function () {
1123 var versionSelectId = '#comment_versions_'+comment_id;
1136 var versionSelectId = '#comment_versions_'+comment_id;
1124 var preLoadVersionData = [
1137 var preLoadVersionData = [
1125 {
1138 {
1126 id: json_data['comment_version'],
1139 id: json_data['comment_version'],
1127 text: "v{0}".format(json_data['comment_version']),
1140 text: "v{0}".format(json_data['comment_version']),
1128 action: function () {
1141 action: function () {
1129 Rhodecode.comments.showVersion(
1142 Rhodecode.comments.showVersion(
1130 json_data['comment_id'],
1143 json_data['comment_id'],
1131 json_data['comment_history_id']
1144 json_data['comment_history_id']
1132 )
1145 )
1133 },
1146 },
1134 comment_version: json_data['comment_version'],
1147 comment_version: json_data['comment_version'],
1135 comment_author_username: json_data['comment_author_username'],
1148 comment_author_username: json_data['comment_author_username'],
1136 comment_author_gravatar: json_data['comment_author_gravatar'],
1149 comment_author_gravatar: json_data['comment_author_gravatar'],
1137 comment_created_on: json_data['comment_created_on'],
1150 comment_created_on: json_data['comment_created_on'],
1138 },
1151 },
1139 ]
1152 ]
1140
1153
1141
1154
1142 if ($(versionSelectId).data('select2')) {
1155 if ($(versionSelectId).data('select2')) {
1143 var oldData = $(versionSelectId).data('select2').opts.data.results;
1156 var oldData = $(versionSelectId).data('select2').opts.data.results;
1144 $(versionSelectId).select2("destroy");
1157 $(versionSelectId).select2("destroy");
1145 preLoadVersionData = oldData.concat(preLoadVersionData)
1158 preLoadVersionData = oldData.concat(preLoadVersionData)
1146 }
1159 }
1147
1160
1148 initVersionSelector(versionSelectId, {results: preLoadVersionData});
1161 initVersionSelector(versionSelectId, {results: preLoadVersionData});
1149
1162
1150 $comment.attr('data-comment-text', utf8ToB64(text));
1163 $comment.attr('data-comment-text', utf8ToB64(text));
1151
1164
1152 var versionSelector = $('#comment_versions_'+comment_id);
1165 var versionSelector = $('#comment_versions_'+comment_id);
1153
1166
1154 // set lastVersion so we know our last edit version
1167 // set lastVersion so we know our last edit version
1155 versionSelector.data('lastVersion', json_data['comment_version'])
1168 versionSelector.data('lastVersion', json_data['comment_version'])
1156 versionSelector.parent().show();
1169 versionSelector.parent().show();
1157 }
1170 }
1158 updateCommentVersionDropDown();
1171 updateCommentVersionDropDown();
1159
1172
1160 // by default we reset state of comment preserving the text
1173 // by default we reset state of comment preserving the text
1161 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
1174 var failRenderCommit = function(jqXHR, textStatus, errorThrown) {
1162 var prefix = "Error while editing this comment.\n"
1175 var prefix = "Error while editing this comment.\n"
1163 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1176 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1164 ajaxErrorSwal(message);
1177 ajaxErrorSwal(message);
1165 };
1178 };
1166
1179
1167 var successRenderCommit = function(o){
1180 var successRenderCommit = function(o){
1168 $comment.show();
1181 $comment.show();
1169 $comment[0].lastElementChild.innerHTML = o;
1182 $comment[0].lastElementChild.innerHTML = o;
1170 };
1183 };
1171
1184
1172 var previewUrl = pyroutes.url(
1185 var previewUrl = pyroutes.url(
1173 'repo_commit_comment_preview',
1186 'repo_commit_comment_preview',
1174 {'repo_name': templateContext.repo_name,
1187 {'repo_name': templateContext.repo_name,
1175 'commit_id': templateContext.commit_data.commit_id});
1188 'commit_id': templateContext.commit_data.commit_id});
1176
1189
1177 _submitAjaxPOST(
1190 _submitAjaxPOST(
1178 previewUrl, postData, successRenderCommit, failRenderCommit
1191 previewUrl, postData, successRenderCommit, failRenderCommit
1179 );
1192 );
1180
1193
1181 try {
1194 try {
1182 var html = json_data.rendered_text;
1195 var html = json_data.rendered_text;
1183 var lineno = json_data.line_no;
1196 var lineno = json_data.line_no;
1184 var target_id = json_data.target_id;
1197 var target_id = json_data.target_id;
1185
1198
1186 $comments.find('.cb-comment-add-button').before(html);
1199 $comments.find('.cb-comment-add-button').before(html);
1187
1200
1188 // run global callback on submit
1201 // run global callback on submit
1189 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1202 commentForm.globalSubmitSuccessCallback({draft: isDraft, comment_id: comment_id});
1190
1203
1191 } catch (e) {
1204 } catch (e) {
1192 console.error(e);
1205 console.error(e);
1193 }
1206 }
1194
1207
1195 // re trigger the linkification of next/prev navigation
1208 // re trigger the linkification of next/prev navigation
1196 linkifyComments($('.inline-comment-injected'));
1209 linkifyComments($('.inline-comment-injected'));
1197 timeagoActivate();
1210 timeagoActivate();
1198 tooltipActivate();
1211 tooltipActivate();
1199
1212
1200 if (window.updateSticky !== undefined) {
1213 if (window.updateSticky !== undefined) {
1201 // potentially our comments change the active window size, so we
1214 // potentially our comments change the active window size, so we
1202 // notify sticky elements
1215 // notify sticky elements
1203 updateSticky()
1216 updateSticky()
1204 }
1217 }
1205
1218
1206 if (window.refreshAllComments !== undefined && !isDraft) {
1219 if (window.refreshAllComments !== undefined && !isDraft) {
1207 // if we have this handler, run it, and refresh all comments boxes
1220 // if we have this handler, run it, and refresh all comments boxes
1208 refreshAllComments()
1221 refreshAllComments()
1209 }
1222 }
1210
1223
1211 commentForm.setActionButtonsDisabled(false);
1224 commentForm.setActionButtonsDisabled(false);
1212
1225
1213 };
1226 };
1214
1227
1215 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1228 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1216 var prefix = "Error while editing comment.\n"
1229 var prefix = "Error while editing comment.\n"
1217 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1230 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1218 if (jqXHR.status == 409){
1231 if (jqXHR.status == 409){
1219 message = 'This comment was probably changed somewhere else. Please reload the content of this comment.'
1232 message = 'This comment was probably changed somewhere else. Please reload the content of this comment.'
1220 ajaxErrorSwal(message, 'Comment version mismatch.');
1233 ajaxErrorSwal(message, 'Comment version mismatch.');
1221 } else {
1234 } else {
1222 ajaxErrorSwal(message);
1235 ajaxErrorSwal(message);
1223 }
1236 }
1224
1237
1225 commentForm.resetCommentFormState(text)
1238 commentForm.resetCommentFormState(text)
1226 };
1239 };
1227 commentForm.submitAjaxPOST(
1240 commentForm.submitAjaxPOST(
1228 commentForm.submitUrl, postData,
1241 commentForm.submitUrl, postData,
1229 submitSuccessCallback,
1242 submitSuccessCallback,
1230 submitFailCallback);
1243 submitFailCallback);
1231 });
1244 });
1232 }
1245 }
1233
1246
1234 $editForm.addClass('comment-inline-form-open');
1247 $editForm.addClass('comment-inline-form-open');
1235 };
1248 };
1236
1249
1237 this.attachComment = function(json_data) {
1250 this.attachComment = function(json_data) {
1238 var self = this;
1251 var self = this;
1239 $.each(json_data, function(idx, val) {
1252 $.each(json_data, function(idx, val) {
1240 var json_data_elem = [val]
1253 var json_data_elem = [val]
1241 var isInline = val.comment_f_path && val.comment_lineno
1254 var isInline = val.comment_f_path && val.comment_lineno
1242
1255
1243 if (isInline) {
1256 if (isInline) {
1244 self.attachInlineComment(json_data_elem)
1257 self.attachInlineComment(json_data_elem)
1245 } else {
1258 } else {
1246 self.attachGeneralComment(json_data_elem)
1259 self.attachGeneralComment(json_data_elem)
1247 }
1260 }
1248 })
1261 })
1249
1262
1250 }
1263 }
1251
1264
1252 this.attachGeneralComment = function(json_data) {
1265 this.attachGeneralComment = function(json_data) {
1253 $.each(json_data, function(idx, val) {
1266 $.each(json_data, function(idx, val) {
1254 $('#injected_page_comments').append(val.rendered_text);
1267 $('#injected_page_comments').append(val.rendered_text);
1255 })
1268 })
1256 }
1269 }
1257
1270
1258 this.attachInlineComment = function(json_data) {
1271 this.attachInlineComment = function(json_data) {
1259
1272
1260 $.each(json_data, function (idx, val) {
1273 $.each(json_data, function (idx, val) {
1261 var line_qry = '*[data-line-no="{0}"]'.format(val.line_no);
1274 var line_qry = '*[data-line-no="{0}"]'.format(val.line_no);
1262 var html = val.rendered_text;
1275 var html = val.rendered_text;
1263 var $inlineComments = $('#' + val.target_id)
1276 var $inlineComments = $('#' + val.target_id)
1264 .find(line_qry)
1277 .find(line_qry)
1265 .find('.inline-comments');
1278 .find('.inline-comments');
1266
1279
1267 var lastComment = $inlineComments.find('.comment-inline').last();
1280 var lastComment = $inlineComments.find('.comment-inline').last();
1268
1281
1269 if (lastComment.length === 0) {
1282 if (lastComment.length === 0) {
1270 // first comment, we append simply
1283 // first comment, we append simply
1271 $inlineComments.find('.reply-thread-container-wrapper').before(html);
1284 $inlineComments.find('.reply-thread-container-wrapper').before(html);
1272 } else {
1285 } else {
1273 $(lastComment).after(html)
1286 $(lastComment).after(html)
1274 }
1287 }
1275
1288
1276 })
1289 })
1277
1290
1278 };
1291 };
1279
1292
1280 this.createNewFormWrapper = function(f_path, line_no) {
1293 this.createNewFormWrapper = function(f_path, line_no) {
1281 // create a new reply HTML form from template
1294 // create a new reply HTML form from template
1282 var tmpl = $('#cb-comment-inline-form-template').html();
1295 var tmpl = $('#cb-comment-inline-form-template').html();
1283 tmpl = tmpl.format(escapeHtml(f_path), line_no);
1296 tmpl = tmpl.format(escapeHtml(f_path), line_no);
1284 return $(tmpl);
1297 return $(tmpl);
1285 }
1298 }
1286
1299
1287 this.createComment = function(node, f_path, line_no, resolutionComment) {
1300 this.createComment = function(node, f_path, line_no, resolutionComment) {
1288 self.edit = false;
1301 self.edit = false;
1289 var $node = $(node);
1302 var $node = $(node);
1290 var $td = $node.closest('td');
1303 var $td = $node.closest('td');
1291 var resolvesCommentId = resolutionComment || null;
1304 var resolvesCommentId = resolutionComment || null;
1292
1305
1293 var $replyForm = $td.find('.comment-inline-form');
1306 var $replyForm = $td.find('.comment-inline-form');
1294
1307
1295 // if form isn't existing, we're generating a new one and injecting it.
1308 // if form isn't existing, we're generating a new one and injecting it.
1296 if ($replyForm.length === 0) {
1309 if ($replyForm.length === 0) {
1297
1310
1298 // unhide/expand all comments if they are hidden for a proper REPLY mode
1311 // unhide/expand all comments if they are hidden for a proper REPLY mode
1299 self.toggleLineComments($node, true);
1312 self.toggleLineComments($node, true);
1300
1313
1301 $replyForm = self.createNewFormWrapper(f_path, line_no);
1314 $replyForm = self.createNewFormWrapper(f_path, line_no);
1302
1315
1303 var $comments = $td.find('.inline-comments');
1316 var $comments = $td.find('.inline-comments');
1304
1317
1305 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1318 // There aren't any comments, we init the `.inline-comments` with `reply-thread-container` first
1306 if ($comments.length===0) {
1319 if ($comments.length===0) {
1307 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(f_path, line_no)
1320 var replBtn = '<button class="cb-comment-add-button" onclick="return Rhodecode.comments.createComment(this, \'{0}\', \'{1}\', null)">Reply...</button>'.format(f_path, line_no)
1308 var $reply_container = $('#cb-comments-inline-container-template')
1321 var $reply_container = $('#cb-comments-inline-container-template')
1309 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1322 $reply_container.find('button.cb-comment-add-button').replaceWith(replBtn);
1310 $td.append($($reply_container).html());
1323 $td.append($($reply_container).html());
1311 }
1324 }
1312
1325
1313 // default comment button exists, so we prepend the form for leaving initial comment
1326 // default comment button exists, so we prepend the form for leaving initial comment
1314 $td.find('.cb-comment-add-button').before($replyForm);
1327 $td.find('.cb-comment-add-button').before($replyForm);
1315 // set marker, that we have a open form
1328 // set marker, that we have a open form
1316 var $replyWrapper = $td.find('.reply-thread-container-wrapper')
1329 var $replyWrapper = $td.find('.reply-thread-container-wrapper')
1317 $replyWrapper.addClass('comment-form-active');
1330 $replyWrapper.addClass('comment-form-active');
1318
1331
1319 var lastComment = $comments.find('.comment-inline').last();
1332 var lastComment = $comments.find('.comment-inline').last();
1320 if ($(lastComment).hasClass('comment-outdated')) {
1333 if ($(lastComment).hasClass('comment-outdated')) {
1321 $replyWrapper.show();
1334 $replyWrapper.show();
1322 }
1335 }
1323
1336
1324 var _form = $($replyForm[0]).find('form');
1337 var _form = $($replyForm[0]).find('form');
1325 var autocompleteActions = ['as_note', 'as_todo'];
1338 var autocompleteActions = ['as_note', 'as_todo'];
1326 var comment_id=null;
1339 var comment_id=null;
1327 var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no);
1340 var placeholderText = _gettext('Leave a comment on file {0} line {1}.').format(f_path, line_no);
1328 var commentForm = self.createCommentForm(
1341 var commentForm = self.createCommentForm(
1329 _form, line_no, placeholderText, autocompleteActions, resolvesCommentId,
1342 _form, line_no, placeholderText, autocompleteActions, resolvesCommentId,
1330 self.edit, comment_id);
1343 self.edit, comment_id);
1331
1344
1332 // set a CUSTOM submit handler for inline comments.
1345 // set a CUSTOM submit handler for inline comments.
1333 commentForm.setHandleFormSubmit(function(o) {
1346 commentForm.setHandleFormSubmit(function(o) {
1334 var text = commentForm.cm.getValue();
1347 var text = commentForm.cm.getValue();
1335 var commentType = commentForm.getCommentType();
1348 var commentType = commentForm.getCommentType();
1336 var resolvesCommentId = commentForm.getResolvesId();
1349 var resolvesCommentId = commentForm.getResolvesId();
1337 var isDraft = commentForm.getDraftState();
1350 var isDraft = commentForm.getDraftState();
1338
1351
1339 if (text === "") {
1352 if (text === "") {
1340 return;
1353 return;
1341 }
1354 }
1342
1355
1343 if (line_no === undefined) {
1356 if (line_no === undefined) {
1344 alert('Error: unable to fetch line number for this inline comment !');
1357 alert('Error: unable to fetch line number for this inline comment !');
1345 return;
1358 return;
1346 }
1359 }
1347
1360
1348 if (f_path === undefined) {
1361 if (f_path === undefined) {
1349 alert('Error: unable to fetch file path for this inline comment !');
1362 alert('Error: unable to fetch file path for this inline comment !');
1350 return;
1363 return;
1351 }
1364 }
1352
1365
1353 var excludeCancelBtn = false;
1366 var excludeCancelBtn = false;
1354 var submitEvent = true;
1367 var submitEvent = true;
1355 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1368 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
1356 commentForm.cm.setOption("readOnly", true);
1369 commentForm.cm.setOption("readOnly", true);
1357 var postData = {
1370 var postData = {
1358 'text': text,
1371 'text': text,
1359 'f_path': f_path,
1372 'f_path': f_path,
1360 'line': line_no,
1373 'line': line_no,
1361 'comment_type': commentType,
1374 'comment_type': commentType,
1362 'draft': isDraft,
1375 'draft': isDraft,
1363 'csrf_token': CSRF_TOKEN
1376 'csrf_token': CSRF_TOKEN
1364 };
1377 };
1365 if (resolvesCommentId){
1378 if (resolvesCommentId){
1366 postData['resolves_comment_id'] = resolvesCommentId;
1379 postData['resolves_comment_id'] = resolvesCommentId;
1367 }
1380 }
1368
1381
1369 // submitSuccess for inline commits
1382 // submitSuccess for inline commits
1370 var submitSuccessCallback = function(json_data) {
1383 var submitSuccessCallback = function(json_data) {
1371
1384
1372 $replyForm.remove();
1385 $replyForm.remove();
1373 $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active');
1386 $td.find('.reply-thread-container-wrapper').removeClass('comment-form-active');
1374
1387
1375 try {
1388 try {
1376
1389
1377 // inject newly created comments, json_data is {<comment_id>: {}}
1390 // inject newly created comments, json_data is {<comment_id>: {}}
1378 self.attachInlineComment(json_data)
1391 self.attachInlineComment(json_data)
1379
1392
1380 //mark visually which comment was resolved
1393 //mark visually which comment was resolved
1381 if (resolvesCommentId) {
1394 if (resolvesCommentId) {
1382 commentForm.markCommentResolved(resolvesCommentId);
1395 commentForm.markCommentResolved(resolvesCommentId);
1383 }
1396 }
1384
1397
1385 // run global callback on submit
1398 // run global callback on submit
1386 commentForm.globalSubmitSuccessCallback({
1399 commentForm.globalSubmitSuccessCallback({
1387 draft: isDraft,
1400 draft: isDraft,
1388 comment_id: comment_id
1401 comment_id: comment_id
1389 });
1402 });
1390
1403
1391 } catch (e) {
1404 } catch (e) {
1392 console.error(e);
1405 console.error(e);
1393 }
1406 }
1394
1407
1395 if (window.updateSticky !== undefined) {
1408 if (window.updateSticky !== undefined) {
1396 // potentially our comments change the active window size, so we
1409 // potentially our comments change the active window size, so we
1397 // notify sticky elements
1410 // notify sticky elements
1398 updateSticky()
1411 updateSticky()
1399 }
1412 }
1400
1413
1401 if (window.refreshAllComments !== undefined && !isDraft) {
1414 if (window.refreshAllComments !== undefined && !isDraft) {
1402 // if we have this handler, run it, and refresh all comments boxes
1415 // if we have this handler, run it, and refresh all comments boxes
1403 refreshAllComments()
1416 refreshAllComments()
1404 }
1417 }
1405
1418
1406 commentForm.setActionButtonsDisabled(false);
1419 commentForm.setActionButtonsDisabled(false);
1407
1420
1408 // re trigger the linkification of next/prev navigation
1421 // re trigger the linkification of next/prev navigation
1409 linkifyComments($('.inline-comment-injected'));
1422 linkifyComments($('.inline-comment-injected'));
1410 timeagoActivate();
1423 timeagoActivate();
1411 tooltipActivate();
1424 tooltipActivate();
1412 };
1425 };
1413
1426
1414 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1427 var submitFailCallback = function(jqXHR, textStatus, errorThrown) {
1415 var prefix = "Error while submitting comment.\n"
1428 var prefix = "Error while submitting comment.\n"
1416 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1429 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
1417 ajaxErrorSwal(message);
1430 ajaxErrorSwal(message);
1418 commentForm.resetCommentFormState(text)
1431 commentForm.resetCommentFormState(text)
1419 };
1432 };
1420
1433
1421 commentForm.submitAjaxPOST(
1434 commentForm.submitAjaxPOST(
1422 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1435 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
1423 });
1436 });
1424 }
1437 }
1425
1438
1426 // Finally "open" our reply form, since we know there are comments and we have the "attached" old form
1439 // Finally "open" our reply form, since we know there are comments and we have the "attached" old form
1427 $replyForm.addClass('comment-inline-form-open');
1440 $replyForm.addClass('comment-inline-form-open');
1428 tooltipActivate();
1441 tooltipActivate();
1429 };
1442 };
1430
1443
1431 this.createResolutionComment = function(commentId){
1444 this.createResolutionComment = function(commentId){
1432 // hide the trigger text
1445 // hide the trigger text
1433 $('#resolve-comment-{0}'.format(commentId)).hide();
1446 $('#resolve-comment-{0}'.format(commentId)).hide();
1434
1447
1435 var comment = $('#comment-'+commentId);
1448 var comment = $('#comment-'+commentId);
1436 var commentData = comment.data();
1449 var commentData = comment.data();
1437 if (commentData.commentInline) {
1450 if (commentData.commentInline) {
1438 var f_path = commentData.fPath;
1451 var f_path = commentData.fPath;
1439 var line_no = commentData.lineNo;
1452 var line_no = commentData.lineNo;
1440 //TODO check this if we need to give f_path/line_no
1453 //TODO check this if we need to give f_path/line_no
1441 this.createComment(comment, f_path, line_no, commentId)
1454 this.createComment(comment, f_path, line_no, commentId)
1442 } else {
1455 } else {
1443 this.createGeneralComment('general', "$placeholder", commentId)
1456 this.createGeneralComment('general', "$placeholder", commentId)
1444 }
1457 }
1445
1458
1446 return false;
1459 return false;
1447 };
1460 };
1448
1461
1449 this.submitResolution = function(commentId){
1462 this.submitResolution = function(commentId){
1450 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1463 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
1451 var commentForm = form.get(0).CommentForm;
1464 var commentForm = form.get(0).CommentForm;
1452
1465
1453 var cm = commentForm.getCmInstance();
1466 var cm = commentForm.getCmInstance();
1454 var renderer = templateContext.visual.default_renderer;
1467 var renderer = templateContext.visual.default_renderer;
1455 if (renderer == 'rst'){
1468 if (renderer == 'rst'){
1456 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1469 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
1457 } else if (renderer == 'markdown') {
1470 } else if (renderer == 'markdown') {
1458 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1471 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
1459 } else {
1472 } else {
1460 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1473 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
1461 }
1474 }
1462
1475
1463 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1476 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
1464 form.submit();
1477 form.submit();
1465 return false;
1478 return false;
1466 };
1479 };
1467
1480
1468 };
1481 };
1469
1482
1470 window.commentHelp = function(renderer) {
1483 window.commentHelp = function(renderer) {
1471 var funcData = {'renderer': renderer}
1484 var funcData = {'renderer': renderer}
1472 return renderTemplate('commentHelpHovercard', funcData)
1485 return renderTemplate('commentHelpHovercard', funcData)
1473 } No newline at end of file
1486 }
@@ -1,432 +1,430 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
7 <%namespace name="sidebar" file="/base/sidebar.mako"/>
7 <%namespace name="sidebar" file="/base/sidebar.mako"/>
8
8
9
9
10 <%def name="title()">
10 <%def name="title()">
11 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
11 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 &middot; ${h.branding(c.rhodecode_name)}
13 &middot; ${h.branding(c.rhodecode_name)}
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <%def name="menu_bar_nav()">
17 <%def name="menu_bar_nav()">
18 ${self.menu_items(active='repositories')}
18 ${self.menu_items(active='repositories')}
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_subnav()">
21 <%def name="menu_bar_subnav()">
22 ${self.repo_menu(active='commits')}
22 ${self.repo_menu(active='commits')}
23 </%def>
23 </%def>
24
24
25 <%def name="main()">
25 <%def name="main()">
26 <script type="text/javascript">
26 <script type="text/javascript">
27 // TODO: marcink switch this to pyroutes
28 AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}";
29 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
27 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
30 </script>
28 </script>
31
29
32 <div class="box">
30 <div class="box">
33
31
34 <div class="summary">
32 <div class="summary">
35
33
36 <div class="fieldset">
34 <div class="fieldset">
37 <div class="left-content">
35 <div class="left-content">
38 <%
36 <%
39 rc_user = h.discover_user(c.commit.author_email)
37 rc_user = h.discover_user(c.commit.author_email)
40 %>
38 %>
41 <div class="left-content-avatar">
39 <div class="left-content-avatar">
42 ${base.gravatar(c.commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
40 ${base.gravatar(c.commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
43 </div>
41 </div>
44
42
45 <div class="left-content-message">
43 <div class="left-content-message">
46 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
44 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
47 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
45 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
48 </div>
46 </div>
49
47
50 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
48 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
51 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
49 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
52 </div>
50 </div>
53
51
54 <div class="fieldset" data-toggle="summary-details">
52 <div class="fieldset" data-toggle="summary-details">
55 <div class="">
53 <div class="">
56 <table>
54 <table>
57 <tr class="file_author">
55 <tr class="file_author">
58
56
59 <td>
57 <td>
60 <span class="user commit-author">${h.link_to_user(rc_user or c.commit.author)}</span>
58 <span class="user commit-author">${h.link_to_user(rc_user or c.commit.author)}</span>
61 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
59 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
62 </td>
60 </td>
63
61
64 <td>
62 <td>
65 ## second cell for consistency with files
63 ## second cell for consistency with files
66 </td>
64 </td>
67 </tr>
65 </tr>
68 </table>
66 </table>
69 </div>
67 </div>
70 </div>
68 </div>
71
69
72 </div>
70 </div>
73 </div>
71 </div>
74
72
75 <div class="right-content">
73 <div class="right-content">
76
74
77 <div data-toggle="summary-details">
75 <div data-toggle="summary-details">
78 <div class="tags tags-main">
76 <div class="tags tags-main">
79 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code>
77 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code>
80 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
78 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
81 ${file_base.refs(c.commit)}
79 ${file_base.refs(c.commit)}
82
80
83 ## phase
81 ## phase
84 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
82 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
85 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
83 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
86 <i class="icon-info"></i>${c.commit.phase}
84 <i class="icon-info"></i>${c.commit.phase}
87 </span>
85 </span>
88 % endif
86 % endif
89
87
90 ## obsolete commits
88 ## obsolete commits
91 % if getattr(c.commit, 'obsolete', False):
89 % if getattr(c.commit, 'obsolete', False):
92 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
90 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
93 ${_('obsolete')}
91 ${_('obsolete')}
94 </span>
92 </span>
95 % endif
93 % endif
96
94
97 ## hidden commits
95 ## hidden commits
98 % if getattr(c.commit, 'hidden', False):
96 % if getattr(c.commit, 'hidden', False):
99 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
97 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
100 ${_('hidden')}
98 ${_('hidden')}
101 </span>
99 </span>
102 % endif
100 % endif
103 </div>
101 </div>
104
102
105 <span id="parent_link" class="tag tagtag">
103 <span id="parent_link" class="tag tagtag">
106 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
104 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
107 </span>
105 </span>
108
106
109 <span id="child_link" class="tag tagtag">
107 <span id="child_link" class="tag tagtag">
110 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
108 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
111 </span>
109 </span>
112
110
113 </div>
111 </div>
114
112
115 </div>
113 </div>
116 </div>
114 </div>
117
115
118 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
116 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
119 <div class="left-label-summary">
117 <div class="left-label-summary">
120 <p>${_('Diff options')}:</p>
118 <p>${_('Diff options')}:</p>
121 <div class="right-label-summary">
119 <div class="right-label-summary">
122 <div class="diff-actions">
120 <div class="diff-actions">
123 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
121 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
124 ${_('Raw Diff')}
122 ${_('Raw Diff')}
125 </a>
123 </a>
126 |
124 |
127 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
125 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
128 ${_('Patch Diff')}
126 ${_('Patch Diff')}
129 </a>
127 </a>
130 |
128 |
131 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}">
129 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}">
132 ${_('Download Diff')}
130 ${_('Download Diff')}
133 </a>
131 </a>
134 </div>
132 </div>
135 </div>
133 </div>
136 </div>
134 </div>
137 </div>
135 </div>
138
136
139 <div class="clear-fix"></div>
137 <div class="clear-fix"></div>
140
138
141 <div class="btn-collapse" data-toggle="summary-details">
139 <div class="btn-collapse" data-toggle="summary-details">
142 ${_('Show More')}
140 ${_('Show More')}
143 </div>
141 </div>
144
142
145 </div>
143 </div>
146
144
147 <div class="cs_files">
145 <div class="cs_files">
148 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
146 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
149 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
147 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
150 ${cbdiffs.render_diffset(
148 ${cbdiffs.render_diffset(
151 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,
149 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,
152 inline_comments=c.inline_comments,
150 inline_comments=c.inline_comments,
153 show_todos=False)}
151 show_todos=False)}
154 </div>
152 </div>
155
153
156 ## template for inline comment form
154 ## template for inline comment form
157 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
155 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
158
156
159 ## comments heading with count
157 ## comments heading with count
160 <div class="comments-heading">
158 <div class="comments-heading">
161 <i class="icon-comment"></i>
159 <i class="icon-comment"></i>
162 ${_('General Comments')} ${len(c.comments)}
160 ${_('General Comments')} ${len(c.comments)}
163 </div>
161 </div>
164
162
165 ## render comments
163 ## render comments
166 ${comment.generate_comments(c.comments)}
164 ${comment.generate_comments(c.comments)}
167
165
168 ## main comment form and it status
166 ## main comment form and it status
169 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
167 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
170 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
168 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
171 </div>
169 </div>
172
170
173 ### NAV SIDEBAR
171 ### NAV SIDEBAR
174 <aside class="right-sidebar right-sidebar-expanded" id="commit-nav-sticky" style="display: none">
172 <aside class="right-sidebar right-sidebar-expanded" id="commit-nav-sticky" style="display: none">
175 <div class="sidenav navbar__inner" >
173 <div class="sidenav navbar__inner" >
176 ## TOGGLE
174 ## TOGGLE
177 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
175 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
178 <a href="#toggleSidebar" class="grey-link-action">
176 <a href="#toggleSidebar" class="grey-link-action">
179
177
180 </a>
178 </a>
181 </div>
179 </div>
182
180
183 ## CONTENT
181 ## CONTENT
184 <div class="sidebar-content">
182 <div class="sidebar-content">
185
183
186 ## RULES SUMMARY/RULES
184 ## RULES SUMMARY/RULES
187 <div class="sidebar-element clear-both">
185 <div class="sidebar-element clear-both">
188 <% vote_title = _ungettext(
186 <% vote_title = _ungettext(
189 'Status calculated based on votes from {} reviewer',
187 'Status calculated based on votes from {} reviewer',
190 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
188 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
191 %>
189 %>
192
190
193 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
191 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
194 <i class="icon-circle review-status-${c.commit_review_status}"></i>
192 <i class="icon-circle review-status-${c.commit_review_status}"></i>
195 ${c.reviewers_count}
193 ${c.reviewers_count}
196 </div>
194 </div>
197 </div>
195 </div>
198
196
199 ## REVIEWERS
197 ## REVIEWERS
200 <div class="right-sidebar-expanded-state pr-details-title">
198 <div class="right-sidebar-expanded-state pr-details-title">
201 <span class="tooltip sidebar-heading" title="${vote_title}">
199 <span class="tooltip sidebar-heading" title="${vote_title}">
202 <i class="icon-circle review-status-${c.commit_review_status}"></i>
200 <i class="icon-circle review-status-${c.commit_review_status}"></i>
203 ${_('Reviewers')}
201 ${_('Reviewers')}
204 </span>
202 </span>
205 </div>
203 </div>
206
204
207 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
205 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
208
206
209 <table id="review_members" class="group_members">
207 <table id="review_members" class="group_members">
210 ## This content is loaded via JS and ReviewersPanel
208 ## This content is loaded via JS and ReviewersPanel
211 </table>
209 </table>
212
210
213 </div>
211 </div>
214
212
215 ## TODOs
213 ## TODOs
216 <div class="sidebar-element clear-both">
214 <div class="sidebar-element clear-both">
217 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
215 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
218 <i class="icon-flag-filled"></i>
216 <i class="icon-flag-filled"></i>
219 <span id="todos-count">${len(c.unresolved_comments)}</span>
217 <span id="todos-count">${len(c.unresolved_comments)}</span>
220 </div>
218 </div>
221
219
222 <div class="right-sidebar-expanded-state pr-details-title">
220 <div class="right-sidebar-expanded-state pr-details-title">
223 ## Only show unresolved, that is only what matters
221 ## Only show unresolved, that is only what matters
224 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
222 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
225 <i class="icon-flag-filled"></i>
223 <i class="icon-flag-filled"></i>
226 TODOs
224 TODOs
227 </span>
225 </span>
228
226
229 % if c.resolved_comments:
227 % if c.resolved_comments:
230 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
228 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
231 % else:
229 % else:
232 <span class="block-right last-item noselect">Show resolved</span>
230 <span class="block-right last-item noselect">Show resolved</span>
233 % endif
231 % endif
234
232
235 </div>
233 </div>
236
234
237 <div class="right-sidebar-expanded-state pr-details-content">
235 <div class="right-sidebar-expanded-state pr-details-content">
238 % if c.unresolved_comments + c.resolved_comments:
236 % if c.unresolved_comments + c.resolved_comments:
239 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True, is_pr=False)}
237 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True, is_pr=False)}
240 % else:
238 % else:
241 <table>
239 <table>
242 <tr>
240 <tr>
243 <td>
241 <td>
244 ${_('No TODOs yet')}
242 ${_('No TODOs yet')}
245 </td>
243 </td>
246 </tr>
244 </tr>
247 </table>
245 </table>
248 % endif
246 % endif
249 </div>
247 </div>
250 </div>
248 </div>
251
249
252 ## COMMENTS
250 ## COMMENTS
253 <div class="sidebar-element clear-both">
251 <div class="sidebar-element clear-both">
254 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
252 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
255 <i class="icon-comment" style="color: #949494"></i>
253 <i class="icon-comment" style="color: #949494"></i>
256 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
254 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
257 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
255 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
258 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
256 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
259 </div>
257 </div>
260
258
261 <div class="right-sidebar-expanded-state pr-details-title">
259 <div class="right-sidebar-expanded-state pr-details-title">
262 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
260 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
263 <i class="icon-comment" style="color: #949494"></i>
261 <i class="icon-comment" style="color: #949494"></i>
264 ${_('Comments')}
262 ${_('Comments')}
265 </span>
263 </span>
266
264
267 </div>
265 </div>
268
266
269 <div class="right-sidebar-expanded-state pr-details-content">
267 <div class="right-sidebar-expanded-state pr-details-content">
270 % if c.inline_comments_flat + c.comments:
268 % if c.inline_comments_flat + c.comments:
271 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments), is_pr=False)}
269 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments), is_pr=False)}
272 % else:
270 % else:
273 <table>
271 <table>
274 <tr>
272 <tr>
275 <td>
273 <td>
276 ${_('No Comments yet')}
274 ${_('No Comments yet')}
277 </td>
275 </td>
278 </tr>
276 </tr>
279 </table>
277 </table>
280 % endif
278 % endif
281 </div>
279 </div>
282
280
283 </div>
281 </div>
284
282
285 </div>
283 </div>
286
284
287 </div>
285 </div>
288 </aside>
286 </aside>
289
287
290 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
288 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
291 <script type="text/javascript">
289 <script type="text/javascript">
292 window.setReviewersData = ${c.commit_set_reviewers_data_json | n};
290 window.setReviewersData = ${c.commit_set_reviewers_data_json | n};
293
291
294 $(document).ready(function () {
292 $(document).ready(function () {
295 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
293 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
296
294
297 if ($('#trimmed_message_box').height() === boxmax) {
295 if ($('#trimmed_message_box').height() === boxmax) {
298 $('#message_expand').show();
296 $('#message_expand').show();
299 }
297 }
300
298
301 $('#message_expand').on('click', function (e) {
299 $('#message_expand').on('click', function (e) {
302 $('#trimmed_message_box').css('max-height', 'none');
300 $('#trimmed_message_box').css('max-height', 'none');
303 $(this).hide();
301 $(this).hide();
304 });
302 });
305
303
306 $('.show-inline-comments').on('click', function (e) {
304 $('.show-inline-comments').on('click', function (e) {
307 var boxid = $(this).attr('data-comment-id');
305 var boxid = $(this).attr('data-comment-id');
308 var button = $(this);
306 var button = $(this);
309
307
310 if (button.hasClass("comments-visible")) {
308 if (button.hasClass("comments-visible")) {
311 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
309 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
312 $(this).hide();
310 $(this).hide();
313 });
311 });
314 button.removeClass("comments-visible");
312 button.removeClass("comments-visible");
315 } else {
313 } else {
316 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
314 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
317 $(this).show();
315 $(this).show();
318 });
316 });
319 button.addClass("comments-visible");
317 button.addClass("comments-visible");
320 }
318 }
321 });
319 });
322
320
323 // next links
321 // next links
324 $('#child_link').on('click', function (e) {
322 $('#child_link').on('click', function (e) {
325 // fetch via ajax what is going to be the next link, if we have
323 // fetch via ajax what is going to be the next link, if we have
326 // >1 links show them to user to choose
324 // >1 links show them to user to choose
327 if (!$('#child_link').hasClass('disabled')) {
325 if (!$('#child_link').hasClass('disabled')) {
328 $.ajax({
326 $.ajax({
329 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
327 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
330 success: function (data) {
328 success: function (data) {
331 if (data.results.length === 0) {
329 if (data.results.length === 0) {
332 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
330 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
333 }
331 }
334 if (data.results.length === 1) {
332 if (data.results.length === 1) {
335 var commit = data.results[0];
333 var commit = data.results[0];
336 window.location = pyroutes.url('repo_commit', {
334 window.location = pyroutes.url('repo_commit', {
337 'repo_name': '${c.repo_name}',
335 'repo_name': '${c.repo_name}',
338 'commit_id': commit.raw_id
336 'commit_id': commit.raw_id
339 });
337 });
340 } else if (data.results.length === 2) {
338 } else if (data.results.length === 2) {
341 $('#child_link').addClass('disabled');
339 $('#child_link').addClass('disabled');
342 $('#child_link').addClass('double');
340 $('#child_link').addClass('double');
343
341
344 var _html = '';
342 var _html = '';
345 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
343 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
346 .replace('__branch__', data.results[0].branch)
344 .replace('__branch__', data.results[0].branch)
347 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
345 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
348 .replace('__title__', data.results[0].message)
346 .replace('__title__', data.results[0].message)
349 .replace('__url__', pyroutes.url('repo_commit', {
347 .replace('__url__', pyroutes.url('repo_commit', {
350 'repo_name': '${c.repo_name}',
348 'repo_name': '${c.repo_name}',
351 'commit_id': data.results[0].raw_id
349 'commit_id': data.results[0].raw_id
352 }));
350 }));
353 _html += ' | ';
351 _html += ' | ';
354 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
352 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
355 .replace('__branch__', data.results[1].branch)
353 .replace('__branch__', data.results[1].branch)
356 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
354 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
357 .replace('__title__', data.results[1].message)
355 .replace('__title__', data.results[1].message)
358 .replace('__url__', pyroutes.url('repo_commit', {
356 .replace('__url__', pyroutes.url('repo_commit', {
359 'repo_name': '${c.repo_name}',
357 'repo_name': '${c.repo_name}',
360 'commit_id': data.results[1].raw_id
358 'commit_id': data.results[1].raw_id
361 }));
359 }));
362 $('#child_link').html(_html);
360 $('#child_link').html(_html);
363 }
361 }
364 }
362 }
365 });
363 });
366 e.preventDefault();
364 e.preventDefault();
367 }
365 }
368 });
366 });
369
367
370 // prev links
368 // prev links
371 $('#parent_link').on('click', function (e) {
369 $('#parent_link').on('click', function (e) {
372 // fetch via ajax what is going to be the next link, if we have
370 // fetch via ajax what is going to be the next link, if we have
373 // >1 links show them to user to choose
371 // >1 links show them to user to choose
374 if (!$('#parent_link').hasClass('disabled')) {
372 if (!$('#parent_link').hasClass('disabled')) {
375 $.ajax({
373 $.ajax({
376 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
374 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
377 success: function (data) {
375 success: function (data) {
378 if (data.results.length === 0) {
376 if (data.results.length === 0) {
379 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
377 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
380 }
378 }
381 if (data.results.length === 1) {
379 if (data.results.length === 1) {
382 var commit = data.results[0];
380 var commit = data.results[0];
383 window.location = pyroutes.url('repo_commit', {
381 window.location = pyroutes.url('repo_commit', {
384 'repo_name': '${c.repo_name}',
382 'repo_name': '${c.repo_name}',
385 'commit_id': commit.raw_id
383 'commit_id': commit.raw_id
386 });
384 });
387 } else if (data.results.length === 2) {
385 } else if (data.results.length === 2) {
388 $('#parent_link').addClass('disabled');
386 $('#parent_link').addClass('disabled');
389 $('#parent_link').addClass('double');
387 $('#parent_link').addClass('double');
390
388
391 var _html = '';
389 var _html = '';
392 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
390 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
393 .replace('__branch__', data.results[0].branch)
391 .replace('__branch__', data.results[0].branch)
394 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
392 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
395 .replace('__title__', data.results[0].message)
393 .replace('__title__', data.results[0].message)
396 .replace('__url__', pyroutes.url('repo_commit', {
394 .replace('__url__', pyroutes.url('repo_commit', {
397 'repo_name': '${c.repo_name}',
395 'repo_name': '${c.repo_name}',
398 'commit_id': data.results[0].raw_id
396 'commit_id': data.results[0].raw_id
399 }));
397 }));
400 _html += ' | ';
398 _html += ' | ';
401 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
399 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
402 .replace('__branch__', data.results[1].branch)
400 .replace('__branch__', data.results[1].branch)
403 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
401 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
404 .replace('__title__', data.results[1].message)
402 .replace('__title__', data.results[1].message)
405 .replace('__url__', pyroutes.url('repo_commit', {
403 .replace('__url__', pyroutes.url('repo_commit', {
406 'repo_name': '${c.repo_name}',
404 'repo_name': '${c.repo_name}',
407 'commit_id': data.results[1].raw_id
405 'commit_id': data.results[1].raw_id
408 }));
406 }));
409 $('#parent_link').html(_html);
407 $('#parent_link').html(_html);
410 }
408 }
411 }
409 }
412 });
410 });
413 e.preventDefault();
411 e.preventDefault();
414 }
412 }
415 });
413 });
416
414
417 // browse tree @ revision
415 // browse tree @ revision
418 $('#files_link').on('click', function (e) {
416 $('#files_link').on('click', function (e) {
419 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
417 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
420 e.preventDefault();
418 e.preventDefault();
421 });
419 });
422
420
423 reviewersController = new ReviewersController();
421 reviewersController = new ReviewersController();
424 ReviewersPanel.init(reviewersController, null, setReviewersData);
422 ReviewersPanel.init(reviewersController, null, setReviewersData);
425
423
426 var channel = '${c.commit_broadcast_channel}';
424 var channel = '${c.commit_broadcast_channel}';
427 new ReviewerPresenceController(channel)
425 new ReviewerPresenceController(channel)
428
426
429 })
427 })
430 </script>
428 </script>
431
429
432 </%def>
430 </%def>
@@ -1,557 +1,557 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 <%!
8 <%!
9 from rhodecode.lib import html_filters
9 from rhodecode.lib import html_filters
10 %>
10 %>
11
11
12
12
13 <%def name="comment_block(comment, inline=False, active_pattern_entries=None, is_new=False)">
13 <%def name="comment_block(comment, inline=False, active_pattern_entries=None, is_new=False)">
14
14
15 <%
15 <%
16 from rhodecode.model.comment import CommentsModel
16 from rhodecode.model.comment import CommentsModel
17 comment_model = CommentsModel()
17 comment_model = CommentsModel()
18
18
19 comment_ver = comment.get_index_version(getattr(c, 'versions', []))
19 comment_ver = comment.get_index_version(getattr(c, 'versions', []))
20 latest_ver = len(getattr(c, 'versions', []))
20 latest_ver = len(getattr(c, 'versions', []))
21 visible_for_user = True
21 visible_for_user = True
22 if comment.draft:
22 if comment.draft:
23 visible_for_user = comment.user_id == c.rhodecode_user.user_id
23 visible_for_user = comment.user_id == c.rhodecode_user.user_id
24 %>
24 %>
25
25
26 % if inline:
26 % if inline:
27 <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
27 <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
28 % else:
28 % else:
29 <% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
29 <% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
30 % endif
30 % endif
31
31
32 % if visible_for_user:
32 % if visible_for_user:
33 <div class="comment
33 <div class="comment
34 ${'comment-inline' if inline else 'comment-general'}
34 ${'comment-inline' if inline else 'comment-general'}
35 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
35 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
36 id="comment-${comment.comment_id}"
36 id="comment-${comment.comment_id}"
37 line="${comment.line_no}"
37 line="${comment.line_no}"
38 data-comment-id="${comment.comment_id}"
38 data-comment-id="${comment.comment_id}"
39 data-comment-type="${comment.comment_type}"
39 data-comment-type="${comment.comment_type}"
40 data-comment-draft=${h.json.dumps(comment.draft)}
40 data-comment-draft=${h.json.dumps(comment.draft)}
41 data-comment-renderer="${comment.renderer}"
41 data-comment-renderer="${comment.renderer}"
42 data-comment-text="${comment.text | html_filters.base64,n}"
42 data-comment-text="${comment.text | html_filters.base64,n}"
43 data-comment-f-path="${comment.f_path}"
43 data-comment-f-path="${comment.f_path}"
44 data-comment-line-no="${comment.line_no}"
44 data-comment-line-no="${comment.line_no}"
45 data-comment-inline=${h.json.dumps(inline)}
45 data-comment-inline=${h.json.dumps(inline)}
46 style="${'display: none;' if outdated_at_ver else ''}">
46 style="${'display: none;' if outdated_at_ver else ''}">
47
47
48 <div class="meta">
48 <div class="meta">
49 <div class="comment-type-label">
49 <div class="comment-type-label">
50 % if comment.draft:
50 % if comment.draft:
51 <div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}.">
51 <div class="tooltip comment-draft" title="${_('Draft comments are only visible to the author until submitted')}.">
52 DRAFT
52 DRAFT
53 </div>
53 </div>
54 % elif is_new:
54 % elif is_new:
55 <div class="tooltip comment-new" title="${_('This comment was added while you browsed this page')}.">
55 <div class="tooltip comment-new" title="${_('This comment was added while you browsed this page')}.">
56 NEW
56 NEW
57 </div>
57 </div>
58 % endif
58 % endif
59
59
60 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
60 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
61
61
62 ## TODO COMMENT
62 ## TODO COMMENT
63 % if comment.comment_type == 'todo':
63 % if comment.comment_type == 'todo':
64 % if comment.resolved:
64 % if comment.resolved:
65 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
65 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
66 <i class="icon-flag-filled"></i>
66 <i class="icon-flag-filled"></i>
67 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
67 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
68 </div>
68 </div>
69 % else:
69 % else:
70 <div class="resolved tooltip" style="display: none">
70 <div class="resolved tooltip" style="display: none">
71 <span>${comment.comment_type}</span>
71 <span>${comment.comment_type}</span>
72 </div>
72 </div>
73 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to create resolution comment.')}">
73 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to create resolution comment.')}">
74 <i class="icon-flag-filled"></i>
74 <i class="icon-flag-filled"></i>
75 ${comment.comment_type}
75 ${comment.comment_type}
76 </div>
76 </div>
77 % endif
77 % endif
78 ## NOTE COMMENT
78 ## NOTE COMMENT
79 % else:
79 % else:
80 ## RESOLVED NOTE
80 ## RESOLVED NOTE
81 % if comment.resolved_comment:
81 % if comment.resolved_comment:
82 <div class="tooltip" title="${_('This comment resolves TODO #{}').format(comment.resolved_comment.comment_id)}">
82 <div class="tooltip" title="${_('This comment resolves TODO #{}').format(comment.resolved_comment.comment_id)}">
83 fix
83 fix
84 <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)})">
84 <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)})">
85 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
85 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
86 </a>
86 </a>
87 </div>
87 </div>
88 ## STATUS CHANGE NOTE
88 ## STATUS CHANGE NOTE
89 % elif not comment.is_inline and comment.status_change:
89 % elif not comment.is_inline and comment.status_change:
90 <%
90 <%
91 if comment.pull_request:
91 if comment.pull_request:
92 status_change_title = 'Status of review for pull request !{}'.format(comment.pull_request.pull_request_id)
92 status_change_title = 'Status of review for pull request !{}'.format(comment.pull_request.pull_request_id)
93 else:
93 else:
94 status_change_title = 'Status of review for commit {}'.format(h.short_id(comment.commit_id))
94 status_change_title = 'Status of review for commit {}'.format(h.short_id(comment.commit_id))
95 %>
95 %>
96
96
97 <i class="icon-circle review-status-${comment.review_status}"></i>
97 <i class="icon-circle review-status-${comment.review_status}"></i>
98 <div class="changeset-status-lbl tooltip" title="${status_change_title}">
98 <div class="changeset-status-lbl tooltip" title="${status_change_title}">
99 ${comment.review_status_lbl}
99 ${comment.review_status_lbl}
100 </div>
100 </div>
101 % else:
101 % else:
102 <div>
102 <div>
103 <i class="icon-comment"></i>
103 <i class="icon-comment"></i>
104 ${(comment.comment_type or 'note')}
104 ${(comment.comment_type or 'note')}
105 </div>
105 </div>
106 % endif
106 % endif
107 % endif
107 % endif
108
108
109 </div>
109 </div>
110 </div>
110 </div>
111 ## NOTE 0 and .. => because we disable it for now until UI ready
111 ## NOTE 0 and .. => because we disable it for now until UI ready
112 % if 0 and comment.status_change:
112 % if 0 and comment.status_change:
113 <div class="pull-left">
113 <div class="pull-left">
114 <span class="tag authortag tooltip" title="${_('Status from pull request.')}">
114 <span class="tag authortag tooltip" title="${_('Status from pull request.')}">
115 <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)}">
115 <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)}">
116 ${'!{}'.format(comment.pull_request.pull_request_id)}
116 ${'!{}'.format(comment.pull_request.pull_request_id)}
117 </a>
117 </a>
118 </span>
118 </span>
119 </div>
119 </div>
120 % endif
120 % endif
121 ## Since only author can see drafts, we don't show it
121 ## Since only author can see drafts, we don't show it
122 % if not comment.draft:
122 % if not comment.draft:
123 <div class="author ${'author-inline' if inline else 'author-general'}">
123 <div class="author ${'author-inline' if inline else 'author-general'}">
124 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
124 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
125 </div>
125 </div>
126 % endif
126 % endif
127
127
128 <div class="date">
128 <div class="date">
129 ${h.age_component(comment.modified_at, time_is_local=True)}
129 ${h.age_component(comment.modified_at, time_is_local=True)}
130 </div>
130 </div>
131
131
132 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
132 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
133 <span class="tag authortag tooltip" title="${_('Pull request author')}">
133 <span class="tag authortag tooltip" title="${_('Pull request author')}">
134 ${_('author')}
134 ${_('author')}
135 </span>
135 </span>
136 % endif
136 % endif
137
137
138 <%
138 <%
139 comment_version_selector = 'comment_versions_{}'.format(comment.comment_id)
139 comment_version_selector = 'comment_versions_{}'.format(comment.comment_id)
140 %>
140 %>
141
141
142 % if comment.history:
142 % if comment.history:
143 <div class="date">
143 <div class="date">
144
144
145 <input id="${comment_version_selector}" name="${comment_version_selector}"
145 <input id="${comment_version_selector}" name="${comment_version_selector}"
146 type="hidden"
146 type="hidden"
147 data-last-version="${comment.history[-1].version}">
147 data-last-version="${comment.history[-1].version}">
148
148
149 <script type="text/javascript">
149 <script type="text/javascript">
150
150
151 var preLoadVersionData = [
151 var preLoadVersionData = [
152 % for comment_history in comment.history:
152 % for comment_history in comment.history:
153 {
153 {
154 id: ${comment_history.comment_history_id},
154 id: ${comment_history.comment_history_id},
155 text: 'v${comment_history.version}',
155 text: 'v${comment_history.version}',
156 action: function () {
156 action: function () {
157 Rhodecode.comments.showVersion(
157 Rhodecode.comments.showVersion(
158 "${comment.comment_id}",
158 "${comment.comment_id}",
159 "${comment_history.comment_history_id}"
159 "${comment_history.comment_history_id}"
160 )
160 )
161 },
161 },
162 comment_version: "${comment_history.version}",
162 comment_version: "${comment_history.version}",
163 comment_author_username: "${comment_history.author.username}",
163 comment_author_username: "${comment_history.author.username}",
164 comment_author_gravatar: "${h.gravatar_url(comment_history.author.email, 16)}",
164 comment_author_gravatar: "${h.gravatar_url(comment_history.author.email, 16)}",
165 comment_created_on: '${h.age_component(comment_history.created_on, time_is_local=True)}',
165 comment_created_on: '${h.age_component(comment_history.created_on, time_is_local=True)}',
166 },
166 },
167 % endfor
167 % endfor
168 ]
168 ]
169 initVersionSelector("#${comment_version_selector}", {results: preLoadVersionData});
169 initVersionSelector("#${comment_version_selector}", {results: preLoadVersionData});
170
170
171 </script>
171 </script>
172
172
173 </div>
173 </div>
174 % else:
174 % else:
175 <div class="date" style="display: none">
175 <div class="date" style="display: none">
176 <input id="${comment_version_selector}" name="${comment_version_selector}"
176 <input id="${comment_version_selector}" name="${comment_version_selector}"
177 type="hidden"
177 type="hidden"
178 data-last-version="0">
178 data-last-version="0">
179 </div>
179 </div>
180 %endif
180 %endif
181
181
182 <div class="comment-links-block">
182 <div class="comment-links-block">
183
183
184 % if inline:
184 % if inline:
185 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
185 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
186 % if outdated_at_ver:
186 % if outdated_at_ver:
187 <strong class="comment-outdated-label">outdated</strong> <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
187 <strong class="comment-outdated-label">outdated</strong> <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
188 <code class="action-divider">|</code>
188 <code class="action-divider">|</code>
189 % elif comment_ver:
189 % elif comment_ver:
190 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
190 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
191 <code class="action-divider">|</code>
191 <code class="action-divider">|</code>
192 % endif
192 % endif
193 </a>
193 </a>
194 % else:
194 % else:
195 % if comment_ver:
195 % if comment_ver:
196
196
197 % if comment.outdated:
197 % if comment.outdated:
198 <a class="pr-version"
198 <a class="pr-version"
199 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
199 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
200 >
200 >
201 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}
201 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}
202 </a>
202 </a>
203 <code class="action-divider">|</code>
203 <code class="action-divider">|</code>
204 % else:
204 % else:
205 <a class="tooltip pr-version"
205 <a class="tooltip pr-version"
206 title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
206 title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
207 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)}"
207 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)}"
208 >
208 >
209 <code class="pr-version-num">${'v{}'.format(comment_ver)}</code>
209 <code class="pr-version-num">${'v{}'.format(comment_ver)}</code>
210 </a>
210 </a>
211 <code class="action-divider">|</code>
211 <code class="action-divider">|</code>
212 % endif
212 % endif
213
213
214 % endif
214 % endif
215 % endif
215 % endif
216
216
217 <details class="details-reset details-inline-block">
217 <details class="details-reset details-inline-block">
218 <summary class="noselect"><i class="icon-options cursor-pointer"></i></summary>
218 <summary class="noselect"><i class="icon-options cursor-pointer"></i></summary>
219 <details-menu class="details-dropdown">
219 <details-menu class="details-dropdown">
220
220
221 <div class="dropdown-item">
221 <div class="dropdown-item">
222 ${_('Comment')} #${comment.comment_id}
222 ${_('Comment')} #${comment.comment_id}
223 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${comment_model.get_url(comment,request, permalink=True, anchor='comment-{}'.format(comment.comment_id))}" title="${_('Copy permalink')}"></span>
223 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${comment_model.get_url(comment,request, permalink=True, anchor='comment-{}'.format(comment.comment_id))}" title="${_('Copy permalink')}"></span>
224 </div>
224 </div>
225
225
226 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
226 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
227 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
227 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
228 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
228 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
229 ## permissions to delete
229 ## permissions to delete
230 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
230 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
231 <div class="dropdown-divider"></div>
231 <div class="dropdown-divider"></div>
232 <div class="dropdown-item">
232 <div class="dropdown-item">
233 <a onclick="return Rhodecode.comments.editComment(this, '${comment.line_no}', '${comment.f_path}');" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a>
233 <a onclick="return Rhodecode.comments.editComment(this, '${comment.line_no}', '${comment.f_path}');" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a>
234 </div>
234 </div>
235 <div class="dropdown-item">
235 <div class="dropdown-item">
236 <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
236 <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
237 </div>
237 </div>
238 ## Only available in EE edition
238 ## Only available in EE edition
239 % if comment.draft and c.rhodecode_edition_id == 'EE':
239 % if comment.draft and c.rhodecode_edition_id == 'EE':
240 <div class="dropdown-item">
240 <div class="dropdown-item">
241 <a onclick="return Rhodecode.comments.finalizeDrafts([${comment.comment_id}]);" class="btn btn-link btn-sm finalize-draft-comment">${_('Submit draft')}</a>
241 <a onclick="return Rhodecode.comments.finalizeDrafts([${comment.comment_id}]);" class="btn btn-link btn-sm finalize-draft-comment">${_('Submit draft')}</a>
242 </div>
242 </div>
243 % endif
243 % endif
244 %else:
244 %else:
245 <div class="dropdown-divider"></div>
245 <div class="dropdown-divider"></div>
246 <div class="dropdown-item">
246 <div class="dropdown-item">
247 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
247 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
248 </div>
248 </div>
249 <div class="dropdown-item">
249 <div class="dropdown-item">
250 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
250 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
251 </div>
251 </div>
252 %endif
252 %endif
253 %else:
253 %else:
254 <div class="dropdown-divider"></div>
254 <div class="dropdown-divider"></div>
255 <div class="dropdown-item">
255 <div class="dropdown-item">
256 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
256 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
257 </div>
257 </div>
258 <div class="dropdown-item">
258 <div class="dropdown-item">
259 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
259 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
260 </div>
260 </div>
261 %endif
261 %endif
262 </details-menu>
262 </details-menu>
263 </details>
263 </details>
264
264
265 <code class="action-divider">|</code>
265 <code class="action-divider">|</code>
266 % if outdated_at_ver:
266 % if outdated_at_ver:
267 <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
267 <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
268 <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
268 <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
269 % else:
269 % else:
270 <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
270 <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
271 <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
271 <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
272 % endif
272 % endif
273
273
274 </div>
274 </div>
275 </div>
275 </div>
276 <div class="text">
276 <div class="text">
277 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None), active_pattern_entries=active_pattern_entries)}
277 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None), active_pattern_entries=active_pattern_entries)}
278 </div>
278 </div>
279
279
280 </div>
280 </div>
281 % endif
281 % endif
282 </%def>
282 </%def>
283
283
284 ## generate main comments
284 ## generate main comments
285 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
285 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
286 <%
286 <%
287 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
287 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
288 %>
288 %>
289
289
290 <div class="general-comments" id="comments">
290 <div class="general-comments" id="comments">
291 %for comment in comments:
291 %for comment in comments:
292 <div id="comment-tr-${comment.comment_id}">
292 <div id="comment-tr-${comment.comment_id}">
293 ## only render comments that are not from pull request, or from
293 ## only render comments that are not from pull request, or from
294 ## pull request and a status change
294 ## pull request and a status change
295 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
295 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
296 ${comment_block(comment, active_pattern_entries=active_pattern_entries)}
296 ${comment_block(comment, active_pattern_entries=active_pattern_entries)}
297 %endif
297 %endif
298 </div>
298 </div>
299 %endfor
299 %endfor
300 ## to anchor ajax comments
300 ## to anchor ajax comments
301 <div id="injected_page_comments"></div>
301 <div id="injected_page_comments"></div>
302 </div>
302 </div>
303 </%def>
303 </%def>
304
304
305
305
306 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
306 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
307
307
308 <div class="comments">
308 <div class="comments">
309 <%
309 <%
310 if is_pull_request:
310 if is_pull_request:
311 placeholder = _('Leave a comment on this Pull Request.')
311 placeholder = _('Leave a comment on this Pull Request.')
312 elif is_compare:
312 elif is_compare:
313 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
313 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
314 else:
314 else:
315 placeholder = _('Leave a comment on this Commit.')
315 placeholder = _('Leave a comment on this Commit.')
316 %>
316 %>
317
317
318 % if c.rhodecode_user.username != h.DEFAULT_USER:
318 % if c.rhodecode_user.username != h.DEFAULT_USER:
319 <div class="js-template" id="cb-comment-general-form-template">
319 <div class="js-template" id="cb-comment-general-form-template">
320 ## template generated for injection
320 ## template generated for injection
321 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
321 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
322 </div>
322 </div>
323
323
324 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
324 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
325 ## inject form here
325 ## inject form here
326 </div>
326 </div>
327 <script type="text/javascript">
327 <script type="text/javascript">
328 var resolvesCommentId = null;
328 var resolvesCommentId = null;
329 var generalCommentForm = Rhodecode.comments.createGeneralComment(
329 var generalCommentForm = Rhodecode.comments.createGeneralComment(
330 'general', "${placeholder}", resolvesCommentId);
330 'general', "${placeholder}", resolvesCommentId);
331
331
332 // set custom success callback on rangeCommit
332 // set custom success callback on rangeCommit
333 % if is_compare:
333 % if is_compare:
334 generalCommentForm.setHandleFormSubmit(function(o) {
334 generalCommentForm.setHandleFormSubmit(function(o) {
335 var self = generalCommentForm;
335 var self = generalCommentForm;
336
336
337 var text = self.cm.getValue();
337 var text = self.cm.getValue();
338 var status = self.getCommentStatus();
338 var status = self.getCommentStatus();
339 var commentType = self.getCommentType();
339 var commentType = self.getCommentType();
340 var isDraft = self.getDraftState();
340 var isDraft = self.getDraftState();
341
341
342 if (text === "" && !status) {
342 if (text === "" && !status) {
343 return;
343 return;
344 }
344 }
345
345
346 // we can pick which commits we want to make the comment by
346 // we can pick which commits we want to make the comment by
347 // selecting them via click on preview pane, this will alter the hidden inputs
347 // selecting them via click on preview pane, this will alter the hidden inputs
348 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
348 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
349
349
350 var commitIds = [];
350 var commitIds = [];
351 $('#changeset_compare_view_content .compare_select').each(function(el) {
351 $('#changeset_compare_view_content .compare_select').each(function(el) {
352 var commitId = this.id.replace('row-', '');
352 var commitId = this.id.replace('row-', '');
353 if ($(this).hasClass('hl') || !cherryPicked) {
353 if ($(this).hasClass('hl') || !cherryPicked) {
354 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
354 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
355 commitIds.push(commitId);
355 commitIds.push(commitId);
356 } else {
356 } else {
357 $("input[data-commit-id='{0}']".format(commitId)).val('')
357 $("input[data-commit-id='{0}']".format(commitId)).val('')
358 }
358 }
359 });
359 });
360
360
361 self.setActionButtonsDisabled(true);
361 self.setActionButtonsDisabled(true);
362 self.cm.setOption("readOnly", true);
362 self.cm.setOption("readOnly", true);
363 var postData = {
363 var postData = {
364 'text': text,
364 'text': text,
365 'changeset_status': status,
365 'changeset_status': status,
366 'comment_type': commentType,
366 'comment_type': commentType,
367 'draft': isDraft,
367 'draft': isDraft,
368 'commit_ids': commitIds,
368 'commit_ids': commitIds,
369 'csrf_token': CSRF_TOKEN
369 'csrf_token': CSRF_TOKEN
370 };
370 };
371
371
372 var submitSuccessCallback = function(o) {
372 var submitSuccessCallback = function(o) {
373 location.reload(true);
373 location.reload(true);
374 };
374 };
375 var submitFailCallback = function(){
375 var submitFailCallback = function(){
376 self.resetCommentFormState(text)
376 self.resetCommentFormState(text)
377 };
377 };
378 self.submitAjaxPOST(
378 self.submitAjaxPOST(
379 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
379 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
380 });
380 });
381 % endif
381 % endif
382
382
383 </script>
383 </script>
384 % else:
384 % else:
385 ## form state when not logged in
385 ## form state when not logged in
386 <div class="comment-form ac">
386 <div class="comment-form ac">
387
387
388 <div class="comment-area">
388 <div class="comment-area">
389 <div class="comment-area-header">
389 <div class="comment-area-header">
390 <ul class="nav-links clearfix">
390 <ul class="nav-links clearfix">
391 <li class="active">
391 <li class="active">
392 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
392 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
393 </li>
393 </li>
394 <li class="">
394 <li class="">
395 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
395 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
396 </li>
396 </li>
397 </ul>
397 </ul>
398 </div>
398 </div>
399
399
400 <div class="comment-area-write" style="display: block;">
400 <div class="comment-area-write" style="display: block;">
401 <div id="edit-container">
401 <div id="edit-container">
402 <div style="padding: 20px 0px 0px 0;">
402 <div style="padding: 20px 0px 0px 0;">
403 ${_('You need to be logged in to leave comments.')}
403 ${_('You need to be logged in to leave comments.')}
404 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
404 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
405 </div>
405 </div>
406 </div>
406 </div>
407 <div id="preview-container" class="clearfix" style="display: none;">
407 <div id="preview-container" class="clearfix" style="display: none;">
408 <div id="preview-box" class="preview-box"></div>
408 <div id="preview-box" class="preview-box"></div>
409 </div>
409 </div>
410 </div>
410 </div>
411
411
412 <div class="comment-area-footer">
412 <div class="comment-area-footer">
413 <div class="toolbar">
413 <div class="toolbar">
414 <div class="toolbar-text">
414 <div class="toolbar-text">
415 </div>
415 </div>
416 </div>
416 </div>
417 </div>
417 </div>
418 </div>
418 </div>
419
419
420 <div class="comment-footer">
420 <div class="comment-footer">
421 </div>
421 </div>
422
422
423 </div>
423 </div>
424 % endif
424 % endif
425
425
426 <script type="text/javascript">
426 <script type="text/javascript">
427 bindToggleButtons();
427 bindToggleButtons();
428 </script>
428 </script>
429 </div>
429 </div>
430 </%def>
430 </%def>
431
431
432
432
433 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
433 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
434
434
435 ## comment injected based on assumption that user is logged in
435 ## comment injected based on assumption that user is logged in
436 <form ${('id="{}"'.format(form_id) if form_id else '') |n} action="#" method="GET">
436 <form ${('id="{}"'.format(form_id) if form_id else '') |n} action="#" method="GET">
437
437
438 <div class="comment-area">
438 <div class="comment-area">
439 <div class="comment-area-header">
439 <div class="comment-area-header">
440 <div class="pull-left">
440 <div class="pull-left">
441 <ul class="nav-links clearfix">
441 <ul class="nav-links clearfix">
442 <li class="active">
442 <li class="active">
443 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
443 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
444 </li>
444 </li>
445 <li class="">
445 <li class="">
446 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
446 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
447 </li>
447 </li>
448 </ul>
448 </ul>
449 </div>
449 </div>
450 <div class="pull-right">
450 <div class="pull-right">
451 <span class="comment-area-text">${_('Mark as')}:</span>
451 <span class="comment-area-text">${_('Mark as')}:</span>
452 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
452 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
453 % for val in c.visual.comment_types:
453 % for val in c.visual.comment_types:
454 <option value="${val}">${val.upper()}</option>
454 <option value="${val}">${val.upper()}</option>
455 % endfor
455 % endfor
456 </select>
456 </select>
457 </div>
457 </div>
458 </div>
458 </div>
459
459
460 <div class="comment-area-write" style="display: block;">
460 <div class="comment-area-write" style="display: block;">
461 <div id="edit-container_${lineno_id}" style="margin-top: -1px">
461 <div id="edit-container_${lineno_id}" style="margin-top: -1px">
462 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
462 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
463 </div>
463 </div>
464 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
464 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
465 <div id="preview-box_${lineno_id}" class="preview-box"></div>
465 <div id="preview-box_${lineno_id}" class="preview-box"></div>
466 </div>
466 </div>
467 </div>
467 </div>
468
468
469 <div class="comment-area-footer comment-attachment-uploader">
469 <div class="comment-area-footer comment-attachment-uploader">
470 <div class="toolbar">
470 <div class="toolbar">
471
471
472 <div class="comment-attachment-text">
472 <div class="comment-attachment-text">
473 <div class="dropzone-text">
473 <div class="dropzone-text">
474 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
474 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
475 </div>
475 </div>
476 <div class="dropzone-upload" style="display:none">
476 <div class="dropzone-upload" style="display:none">
477 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
477 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
478 </div>
478 </div>
479 </div>
479 </div>
480
480
481 ## comments dropzone template, empty on purpose
481 ## comments dropzone template, empty on purpose
482 <div style="display: none" class="comment-attachment-uploader-template">
482 <div style="display: none" class="comment-attachment-uploader-template">
483 <div class="dz-file-preview" style="margin: 0">
483 <div class="dz-file-preview" style="margin: 0">
484 <div class="dz-error-message"></div>
484 <div class="dz-error-message"></div>
485 </div>
485 </div>
486 </div>
486 </div>
487
487
488 </div>
488 </div>
489 </div>
489 </div>
490 </div>
490 </div>
491
491
492 <div class="comment-footer">
492 <div class="comment-footer">
493
493
494 ## inject extra inputs into the form
494 ## inject extra inputs into the form
495 % if form_extras and isinstance(form_extras, (list, tuple)):
495 % if form_extras and isinstance(form_extras, (list, tuple)):
496 <div id="comment_form_extras">
496 <div id="comment_form_extras">
497 % for form_ex_el in form_extras:
497 % for form_ex_el in form_extras:
498 ${form_ex_el|n}
498 ${form_ex_el|n}
499 % endfor
499 % endfor
500 </div>
500 </div>
501 % endif
501 % endif
502
502
503 <div class="action-buttons">
503 <div class="action-buttons">
504 % if form_type != 'inline':
504 % if form_type != 'inline':
505 <div class="action-buttons-extra"></div>
505 <div class="action-buttons-extra"></div>
506 % endif
506 % endif
507
507
508 <input class="btn btn-success comment-button-input submit-comment-action" id="save_${lineno_id}" name="save" type="submit" value="${_('Add comment')}" data-is-draft=false onclick="$(this).addClass('submitter')">
508 <input class="btn btn-success comment-button-input submit-comment-action" id="save_${lineno_id}" name="save" type="submit" value="${_('Add comment')}" data-is-draft=false onclick="$(this).addClass('submitter')">
509
509
510 % if form_type == 'inline':
510 % if form_type == 'inline':
511 % if c.rhodecode_edition_id == 'EE':
511 % if c.rhodecode_edition_id == 'EE':
512 ## Disable the button for CE, the "real" validation is in the backend code anyway
512 ## Disable the button for CE, the "real" validation is in the backend code anyway
513 <input class="btn btn-warning comment-button-input submit-draft-action" id="save_draft_${lineno_id}" name="save_draft" type="submit" value="${_('Add draft')}" data-is-draft=true onclick="$(this).addClass('submitter')">
513 <input class="btn btn-warning comment-button-input submit-draft-action" id="save_draft_${lineno_id}" name="save_draft" type="submit" value="${_('Add draft')}" data-is-draft=true onclick="$(this).addClass('submitter')">
514 % else:
514 % else:
515 <input class="btn btn-warning comment-button-input submit-draft-action disabled" disabled="disabled" type="submit" value="${_('Add draft')}" onclick="return false;" title="Draft comments only available in EE edition of RhodeCode">
515 <input class="btn btn-warning comment-button-input submit-draft-action disabled" disabled="disabled" type="submit" value="${_('Add draft')}" onclick="return false;" title="Draft comments only available in EE edition of RhodeCode">
516 % endif
516 % endif
517 % endif
517 % endif
518
518
519 % if review_statuses:
519 % if review_statuses:
520 <div class="comment-status-box">
520 <div class="comment-status-box">
521 <select id="change_status_${lineno_id}" name="changeset_status">
521 <select id="change_status_${lineno_id}" name="changeset_status">
522 <option></option> ## Placeholder
522 <option></option> ## Placeholder
523 % for status, lbl in review_statuses:
523 % for status, lbl in review_statuses:
524 <option value="${status}" data-status="${status}">${lbl}</option>
524 <option value="${status}" data-status="${status}">${lbl}</option>
525 %if is_pull_request and change_status and status in ('approved', 'rejected'):
525 %if is_pull_request and change_status and status in ('approved', 'rejected'):
526 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
526 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
527 %endif
527 %endif
528 % endfor
528 % endfor
529 </select>
529 </select>
530 </div>
530 </div>
531 % endif
531 % endif
532
532
533 ## inline for has a file, and line-number together with cancel hide button.
533 ## inline for has a file, and line-number together with cancel hide button.
534 % if form_type == 'inline':
534 % if form_type == 'inline':
535 <input type="hidden" name="f_path" value="{0}">
535 <input type="hidden" name="f_path" value="{0}">
536 <input type="hidden" name="line" value="${lineno_id}">
536 <input type="hidden" name="line" value="${lineno_id}">
537 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
537 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
538 <i class="icon-cancel-circled2"></i>
538 <i class="icon-cancel-circled2"></i>
539 </button>
539 </button>
540 % endif
540 % endif
541 </div>
541 </div>
542
542
543 <div class="toolbar-text">
543 <div class="toolbar-text">
544 <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %>
544 <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %>
545 <span>${_('Styling with {} is supported.').format(renderer_url)|n}
545 <span>${_('{} is supported.').format(renderer_url)|n}
546
546
547 <i class="icon-info-circled tooltip-hovercard"
547 <i class="icon-info-circled tooltip-hovercard"
548 data-hovercard-alt="ALT"
548 data-hovercard-alt="ALT"
549 data-hovercard-url="javascript:commentHelp('${c.visual.default_renderer.upper()}')"
549 data-hovercard-url="javascript:commentHelp('${c.visual.default_renderer.upper()}')"
550 data-comment-json-b64='${h.b64(h.json.dumps({}))}'></i>
550 data-comment-json-b64='${h.b64(h.json.dumps({}))}'></i>
551 </span>
551 </span>
552 </div>
552 </div>
553 </div>
553 </div>
554
554
555 </form>
555 </form>
556
556
557 </%def> No newline at end of file
557 </%def>
@@ -1,1035 +1,1033 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 <%namespace name="sidebar" file="/base/sidebar.mako"/>
4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
5
5
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15
15
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='showpullrequest')}
23 ${self.repo_menu(active='showpullrequest')}
24 </%def>
24 </%def>
25
25
26
26
27 <%def name="main()">
27 <%def name="main()">
28 ## Container to gather extracted Tickets
28 ## Container to gather extracted Tickets
29 <%
29 <%
30 c.referenced_commit_issues = []
30 c.referenced_commit_issues = []
31 c.referenced_desc_issues = []
31 c.referenced_desc_issues = []
32 %>
32 %>
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
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__')}";
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
35 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
36 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
39 </script>
37 </script>
40
38
41 <div class="box">
39 <div class="box">
42
40
43 <div class="box pr-summary">
41 <div class="box pr-summary">
44
42
45 <div class="summary-details block-left">
43 <div class="summary-details block-left">
46 <div id="pr-title">
44 <div id="pr-title">
47 % if c.pull_request.is_closed():
45 % if c.pull_request.is_closed():
48 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
46 <span class="pr-title-closed-tag tag">${_('Closed')}</span>
49 % endif
47 % endif
50 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
48 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${c.pull_request.title}">
51 </div>
49 </div>
52 <div id="pr-title-edit" class="input" style="display: none;">
50 <div id="pr-title-edit" class="input" style="display: none;">
53 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
51 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
54 </div>
52 </div>
55
53
56 <% summary = lambda n:{False:'summary-short'}.get(n) %>
54 <% summary = lambda n:{False:'summary-short'}.get(n) %>
57 <div class="pr-details-title">
55 <div class="pr-details-title">
58 <div class="pull-left">
56 <div class="pull-left">
59 <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>
57 <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>
60 ${_('Created on')}
58 ${_('Created on')}
61 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
59 <span class="tooltip" title="${_('Last updated on')} ${h.format_date(c.pull_request.updated_on)}">${h.format_date(c.pull_request.created_on)},</span>
62 <span class="pr-details-title-author-pref">${_('by')}</span>
60 <span class="pr-details-title-author-pref">${_('by')}</span>
63 </div>
61 </div>
64
62
65 <div class="pull-left">
63 <div class="pull-left">
66 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
64 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
67 </div>
65 </div>
68
66
69 %if c.allowed_to_update:
67 %if c.allowed_to_update:
70 <div class="pull-right">
68 <div class="pull-right">
71 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
69 <div id="edit_pull_request" class="action_button pr-save" style="display: none;">${_('Update title & description')}</div>
72 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
70 <div id="delete_pullrequest" class="action_button pr-save ${('' if c.allowed_to_delete else 'disabled' )}" style="display: none;">
73 % if c.allowed_to_delete:
71 % if c.allowed_to_delete:
74 ${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)}
72 ${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)}
75 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
73 <input class="btn btn-link btn-danger no-margin" id="remove_${c.pull_request.pull_request_id}" name="remove_${c.pull_request.pull_request_id}"
76 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
74 onclick="submitConfirm(event, this, _gettext('Confirm to delete this pull request'), _gettext('Delete'), '${'!{}'.format(c.pull_request.pull_request_id)}')"
77 type="submit" value="${_('Delete pull request')}">
75 type="submit" value="${_('Delete pull request')}">
78 ${h.end_form()}
76 ${h.end_form()}
79 % else:
77 % else:
80 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
78 <span class="tooltip" title="${_('Not allowed to delete this pull request')}">${_('Delete pull request')}</span>
81 % endif
79 % endif
82 </div>
80 </div>
83 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
81 <div id="open_edit_pullrequest" class="action_button">${_('Edit')}</div>
84 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
82 <div id="close_edit_pullrequest" class="action_button" style="display: none;">${_('Cancel')}</div>
85 </div>
83 </div>
86
84
87 %endif
85 %endif
88 </div>
86 </div>
89
87
90 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
88 <div id="pr-desc" class="input" title="${_('Rendered using {} renderer').format(c.renderer)}">
91 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
89 ${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name, issues_container=c.referenced_desc_issues)}
92 </div>
90 </div>
93
91
94 <div id="pr-desc-edit" class="input textarea" style="display: none;">
92 <div id="pr-desc-edit" class="input textarea" style="display: none;">
95 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
93 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
96 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
94 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
97 </div>
95 </div>
98
96
99 <div id="summary" class="fields pr-details-content">
97 <div id="summary" class="fields pr-details-content">
100
98
101 ## source
99 ## source
102 <div class="field">
100 <div class="field">
103 <div class="label-pr-detail">
101 <div class="label-pr-detail">
104 <label>${_('Commit flow')}:</label>
102 <label>${_('Commit flow')}:</label>
105 </div>
103 </div>
106 <div class="input">
104 <div class="input">
107 <div class="pr-commit-flow">
105 <div class="pr-commit-flow">
108 ## Source
106 ## Source
109 %if c.pull_request.source_ref_parts.type == 'branch':
107 %if c.pull_request.source_ref_parts.type == 'branch':
110 <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))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
108 <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))}"><code class="pr-source-info">${c.pull_request.source_ref_parts.type}:${c.pull_request.source_ref_parts.name}</code></a>
111 %else:
109 %else:
112 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
110 <code class="pr-source-info">${'{}:{}'.format(c.pull_request.source_ref_parts.type, c.pull_request.source_ref_parts.name)}</code>
113 %endif
111 %endif
114 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
112 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.repo_name}</a>
115 &rarr;
113 &rarr;
116 ## Target
114 ## Target
117 %if c.pull_request.target_ref_parts.type == 'branch':
115 %if c.pull_request.target_ref_parts.type == 'branch':
118 <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))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
116 <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))}"><code class="pr-target-info">${c.pull_request.target_ref_parts.type}:${c.pull_request.target_ref_parts.name}</code></a>
119 %else:
117 %else:
120 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
118 <code class="pr-target-info">${'{}:{}'.format(c.pull_request.target_ref_parts.type, c.pull_request.target_ref_parts.name)}</code>
121 %endif
119 %endif
122
120
123 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
121 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
124
122
125 <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
123 <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
126 <i class="icon-angle-down">more details</i>
124 <i class="icon-angle-down">more details</i>
127 </a>
125 </a>
128
126
129 </div>
127 </div>
130
128
131 <div class="source-details" style="display: none">
129 <div class="source-details" style="display: none">
132
130
133 <ul>
131 <ul>
134
132
135 ## common ancestor
133 ## common ancestor
136 <li>
134 <li>
137 ${_('Common ancestor')}:
135 ${_('Common ancestor')}:
138 % if c.ancestor_commit:
136 % if c.ancestor_commit:
139 <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>
137 <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>
140 % else:
138 % else:
141 ${_('not available')}
139 ${_('not available')}
142 % endif
140 % endif
143 </li>
141 </li>
144
142
145 ## pull url
143 ## pull url
146 <li>
144 <li>
147 %if h.is_hg(c.pull_request.source_repo):
145 %if h.is_hg(c.pull_request.source_repo):
148 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
146 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
149 %elif h.is_git(c.pull_request.source_repo):
147 %elif h.is_git(c.pull_request.source_repo):
150 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
148 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
151 %endif
149 %endif
152
150
153 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
151 <span>${_('Pull changes from source')}</span>: <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
154 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
152 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
155 </li>
153 </li>
156
154
157 ## Shadow repo
155 ## Shadow repo
158 <li>
156 <li>
159 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
157 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
160 %if h.is_hg(c.pull_request.target_repo):
158 %if h.is_hg(c.pull_request.target_repo):
161 <% 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) %>
159 <% 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) %>
162 %elif h.is_git(c.pull_request.target_repo):
160 %elif h.is_git(c.pull_request.target_repo):
163 <% 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) %>
161 <% 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) %>
164 %endif
162 %endif
165
163
166 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
164 <span class="tooltip" title="${_('Clone repository in its merged state using shadow repository')}">${_('Clone from shadow repository')}</span>: <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
167 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
165 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
168
166
169 % else:
167 % else:
170 <div class="">
168 <div class="">
171 ${_('Shadow repository data not available')}.
169 ${_('Shadow repository data not available')}.
172 </div>
170 </div>
173 % endif
171 % endif
174 </li>
172 </li>
175
173
176 </ul>
174 </ul>
177
175
178 </div>
176 </div>
179
177
180 </div>
178 </div>
181
179
182 </div>
180 </div>
183
181
184 ## versions
182 ## versions
185 <div class="field">
183 <div class="field">
186 <div class="label-pr-detail">
184 <div class="label-pr-detail">
187 <label>${_('Versions')}:</label>
185 <label>${_('Versions')}:</label>
188 </div>
186 </div>
189
187
190 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
188 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
191 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
189 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
192
190
193 <div class="pr-versions">
191 <div class="pr-versions">
194 % if c.show_version_changes:
192 % if c.show_version_changes:
195 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
193 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
196 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
194 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
197 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
195 ${_ungettext('{} version available for this pull request, ', '{} versions available for this pull request, ', len(c.versions)).format(len(c.versions))}
198 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
196 <a id="show-pr-versions" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
199 data-toggle-on="${_('show versions')}."
197 data-toggle-on="${_('show versions')}."
200 data-toggle-off="${_('hide versions')}.">
198 data-toggle-off="${_('hide versions')}.">
201 ${_('show versions')}.
199 ${_('show versions')}.
202 </a>
200 </a>
203 <table>
201 <table>
204 ## SHOW ALL VERSIONS OF PR
202 ## SHOW ALL VERSIONS OF PR
205 <% ver_pr = None %>
203 <% ver_pr = None %>
206
204
207 % for data in reversed(list(enumerate(c.versions, 1))):
205 % for data in reversed(list(enumerate(c.versions, 1))):
208 <% ver_pos = data[0] %>
206 <% ver_pos = data[0] %>
209 <% ver = data[1] %>
207 <% ver = data[1] %>
210 <% ver_pr = ver.pull_request_version_id %>
208 <% ver_pr = ver.pull_request_version_id %>
211 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
209 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
212
210
213 <tr class="version-pr" style="display: ${display_row}">
211 <tr class="version-pr" style="display: ${display_row}">
214 <td>
212 <td>
215 <code>
213 <code>
216 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
214 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
217 </code>
215 </code>
218 </td>
216 </td>
219 <td>
217 <td>
220 <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
218 <input ${('checked="checked"' if c.from_version_index == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
221 <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}"/>
219 <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}"/>
222 </td>
220 </td>
223 <td>
221 <td>
224 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
222 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
225 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
223 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
226
224
227 </td>
225 </td>
228 <td>
226 <td>
229 % if c.at_version_num != ver_pr:
227 % if c.at_version_num != ver_pr:
230 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
228 <i class="tooltip icon-comment" title="${_('Comments from pull request version v{0}').format(ver_pos)}"></i>
231 <code>
229 <code>
232 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
230 General:${len(c.comment_versions[ver_pr]['at'])} / Inline:${len(c.inline_versions[ver_pr]['at'])}
233 </code>
231 </code>
234 % endif
232 % endif
235 </td>
233 </td>
236 <td>
234 <td>
237 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
235 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
238 </td>
236 </td>
239 <td>
237 <td>
240 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
238 <code>${h.age_component(ver.updated_on, time_is_local=True, tooltip=False)}</code>
241 </td>
239 </td>
242 </tr>
240 </tr>
243 % endfor
241 % endfor
244
242
245 <tr>
243 <tr>
246 <td colspan="6">
244 <td colspan="6">
247 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
245 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
248 data-label-text-locked="${_('select versions to show changes')}"
246 data-label-text-locked="${_('select versions to show changes')}"
249 data-label-text-diff="${_('show changes between versions')}"
247 data-label-text-diff="${_('show changes between versions')}"
250 data-label-text-show="${_('show pull request for this version')}"
248 data-label-text-show="${_('show pull request for this version')}"
251 >
249 >
252 ${_('select versions to show changes')}
250 ${_('select versions to show changes')}
253 </button>
251 </button>
254 </td>
252 </td>
255 </tr>
253 </tr>
256 </table>
254 </table>
257 % else:
255 % else:
258 <div>
256 <div>
259 ${_('Pull request versions not available')}.
257 ${_('Pull request versions not available')}.
260 </div>
258 </div>
261 % endif
259 % endif
262 </div>
260 </div>
263 </div>
261 </div>
264
262
265 </div>
263 </div>
266
264
267 </div>
265 </div>
268
266
269
267
270 </div>
268 </div>
271
269
272 </div>
270 </div>
273
271
274 <div class="box">
272 <div class="box">
275
273
276 % if c.state_progressing:
274 % if c.state_progressing:
277
275
278 <h2 style="text-align: center">
276 <h2 style="text-align: center">
279 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
277 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
280
278
281 % if c.is_super_admin:
279 % if c.is_super_admin:
282 <br/>
280 <br/>
283 If you think this is an error try <a href="${h.current_route_path(request, force_state='created')}">forced state reset</a> to <span class="tag tag-merge-state-created">created</span> state.
281 If you think this is an error try <a href="${h.current_route_path(request, force_state='created')}">forced state reset</a> to <span class="tag tag-merge-state-created">created</span> state.
284 % endif
282 % endif
285 </h2>
283 </h2>
286
284
287 % else:
285 % else:
288
286
289 ## Diffs rendered here
287 ## Diffs rendered here
290 <div class="table" >
288 <div class="table" >
291 <div id="changeset_compare_view_content">
289 <div id="changeset_compare_view_content">
292 ##CS
290 ##CS
293 % if c.missing_requirements:
291 % if c.missing_requirements:
294 <div class="box">
292 <div class="box">
295 <div class="alert alert-warning">
293 <div class="alert alert-warning">
296 <div>
294 <div>
297 <strong>${_('Missing requirements:')}</strong>
295 <strong>${_('Missing requirements:')}</strong>
298 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
296 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
299 </div>
297 </div>
300 </div>
298 </div>
301 </div>
299 </div>
302 % elif c.missing_commits:
300 % elif c.missing_commits:
303 <div class="box">
301 <div class="box">
304 <div class="alert alert-warning">
302 <div class="alert alert-warning">
305 <div>
303 <div>
306 <strong>${_('Missing commits')}:</strong>
304 <strong>${_('Missing commits')}:</strong>
307 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
305 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}<br/>
308 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
306 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}<br/>
309 ${_('Consider doing a `force update commits` in case you think this is an error.')}
307 ${_('Consider doing a `force update commits` in case you think this is an error.')}
310 </div>
308 </div>
311 </div>
309 </div>
312 </div>
310 </div>
313 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
311 % elif c.pr_merge_source_commit.changed and not c.pull_request.is_closed():
314 <div class="box">
312 <div class="box">
315 <div class="alert alert-info">
313 <div class="alert alert-info">
316 <div>
314 <div>
317 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
315 <strong>${_('There are new changes for `{}:{}` in source repository, please consider updating this pull request.').format(c.pr_merge_source_commit.ref_spec.type, c.pr_merge_source_commit.ref_spec.name)}</strong>
318 </div>
316 </div>
319 </div>
317 </div>
320 </div>
318 </div>
321 % endif
319 % endif
322
320
323 <div class="compare_view_commits_title">
321 <div class="compare_view_commits_title">
324 % if not c.compare_mode:
322 % if not c.compare_mode:
325
323
326 % if c.at_version_index:
324 % if c.at_version_index:
327 <h4>
325 <h4>
328 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
326 ${_('Showing changes at v{}, commenting is disabled.').format(c.at_version_index)}
329 </h4>
327 </h4>
330 % endif
328 % endif
331
329
332 <div class="pull-left">
330 <div class="pull-left">
333 <div class="btn-group">
331 <div class="btn-group">
334 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
332 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
335 % if c.collapse_all_commits:
333 % if c.collapse_all_commits:
336 <i class="icon-plus-squared-alt icon-no-margin"></i>
334 <i class="icon-plus-squared-alt icon-no-margin"></i>
337 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
335 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
338 % else:
336 % else:
339 <i class="icon-minus-squared-alt icon-no-margin"></i>
337 <i class="icon-minus-squared-alt icon-no-margin"></i>
340 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
338 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
341 % endif
339 % endif
342 </a>
340 </a>
343 </div>
341 </div>
344 </div>
342 </div>
345
343
346 <div class="pull-right">
344 <div class="pull-right">
347 % if c.allowed_to_update and not c.pull_request.is_closed():
345 % if c.allowed_to_update and not c.pull_request.is_closed():
348
346
349 <div class="btn-group btn-group-actions">
347 <div class="btn-group btn-group-actions">
350 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
348 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
351 ${_('Update commits')}
349 ${_('Update commits')}
352 </a>
350 </a>
353
351
354 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
352 <a id="update_commits_switcher" class="tooltip btn btn-primary btn-more-option" data-toggle="dropdown" aria-pressed="false" role="button" title="${_('more update options')}">
355 <i class="icon-down"></i>
353 <i class="icon-down"></i>
356 </a>
354 </a>
357
355
358 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
356 <div class="btn-action-switcher-container right-align" id="update-commits-switcher">
359 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
357 <ul class="btn-action-switcher" role="menu" style="min-width: 300px;">
360 <li>
358 <li>
361 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
359 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
362 ${_('Force update commits')}
360 ${_('Force update commits')}
363 </a>
361 </a>
364 <div class="action-help-block">
362 <div class="action-help-block">
365 ${_('Update commits and force refresh this pull request.')}
363 ${_('Update commits and force refresh this pull request.')}
366 </div>
364 </div>
367 </li>
365 </li>
368 </ul>
366 </ul>
369 </div>
367 </div>
370 </div>
368 </div>
371
369
372 % else:
370 % else:
373 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
371 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
374 % endif
372 % endif
375
373
376 </div>
374 </div>
377 % endif
375 % endif
378 </div>
376 </div>
379
377
380 % if not c.missing_commits:
378 % if not c.missing_commits:
381 ## COMPARE RANGE DIFF MODE
379 ## COMPARE RANGE DIFF MODE
382 % if c.compare_mode:
380 % if c.compare_mode:
383 % if c.at_version:
381 % if c.at_version:
384 <h4>
382 <h4>
385 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
383 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_index, ver_to=c.at_version_index if c.at_version_index else 'latest')}:
386 </h4>
384 </h4>
387
385
388 <div class="subtitle-compare">
386 <div class="subtitle-compare">
389 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
387 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
390 </div>
388 </div>
391
389
392 <div class="container">
390 <div class="container">
393 <table class="rctable compare_view_commits">
391 <table class="rctable compare_view_commits">
394 <tr>
392 <tr>
395 <th></th>
393 <th></th>
396 <th>${_('Time')}</th>
394 <th>${_('Time')}</th>
397 <th>${_('Author')}</th>
395 <th>${_('Author')}</th>
398 <th>${_('Commit')}</th>
396 <th>${_('Commit')}</th>
399 <th></th>
397 <th></th>
400 <th>${_('Description')}</th>
398 <th>${_('Description')}</th>
401 </tr>
399 </tr>
402
400
403 % for c_type, commit in c.commit_changes:
401 % for c_type, commit in c.commit_changes:
404 % if c_type in ['a', 'r']:
402 % if c_type in ['a', 'r']:
405 <%
403 <%
406 if c_type == 'a':
404 if c_type == 'a':
407 cc_title = _('Commit added in displayed changes')
405 cc_title = _('Commit added in displayed changes')
408 elif c_type == 'r':
406 elif c_type == 'r':
409 cc_title = _('Commit removed in displayed changes')
407 cc_title = _('Commit removed in displayed changes')
410 else:
408 else:
411 cc_title = ''
409 cc_title = ''
412 %>
410 %>
413 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
411 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
414 <td>
412 <td>
415 <div class="commit-change-indicator color-${c_type}-border">
413 <div class="commit-change-indicator color-${c_type}-border">
416 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
414 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
417 ${c_type.upper()}
415 ${c_type.upper()}
418 </div>
416 </div>
419 </div>
417 </div>
420 </td>
418 </td>
421 <td class="td-time">
419 <td class="td-time">
422 ${h.age_component(commit.date)}
420 ${h.age_component(commit.date)}
423 </td>
421 </td>
424 <td class="td-user">
422 <td class="td-user">
425 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
423 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
426 </td>
424 </td>
427 <td class="td-hash">
425 <td class="td-hash">
428 <code>
426 <code>
429 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
427 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
430 r${commit.idx}:${h.short_id(commit.raw_id)}
428 r${commit.idx}:${h.short_id(commit.raw_id)}
431 </a>
429 </a>
432 ${h.hidden('revisions', commit.raw_id)}
430 ${h.hidden('revisions', commit.raw_id)}
433 </code>
431 </code>
434 </td>
432 </td>
435 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
433 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
436 <i class="icon-expand-linked"></i>
434 <i class="icon-expand-linked"></i>
437 </td>
435 </td>
438 <td class="mid td-description">
436 <td class="mid td-description">
439 <div class="log-container truncate-wrap">
437 <div class="log-container truncate-wrap">
440 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container=c.referenced_commit_issues)}</div>
438 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name, issues_container=c.referenced_commit_issues)}</div>
441 </div>
439 </div>
442 </td>
440 </td>
443 </tr>
441 </tr>
444 % endif
442 % endif
445 % endfor
443 % endfor
446 </table>
444 </table>
447 </div>
445 </div>
448
446
449 % endif
447 % endif
450
448
451 ## Regular DIFF
449 ## Regular DIFF
452 % else:
450 % else:
453 <%include file="/compare/compare_commits.mako" />
451 <%include file="/compare/compare_commits.mako" />
454 % endif
452 % endif
455
453
456 <div class="cs_files">
454 <div class="cs_files">
457 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
455 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
458
456
459 <%
457 <%
460 pr_menu_data = {
458 pr_menu_data = {
461 'outdated_comm_count_ver': outdated_comm_count_ver,
459 'outdated_comm_count_ver': outdated_comm_count_ver,
462 'pull_request': c.pull_request
460 'pull_request': c.pull_request
463 }
461 }
464 %>
462 %>
465
463
466 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
464 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on, pull_request_menu=pr_menu_data)}
467
465
468 % if c.range_diff_on:
466 % if c.range_diff_on:
469 % for commit in c.commit_ranges:
467 % for commit in c.commit_ranges:
470 ${cbdiffs.render_diffset(
468 ${cbdiffs.render_diffset(
471 c.changes[commit.raw_id],
469 c.changes[commit.raw_id],
472 commit=commit, use_comments=True,
470 commit=commit, use_comments=True,
473 collapse_when_files_over=5,
471 collapse_when_files_over=5,
474 disable_new_comments=True,
472 disable_new_comments=True,
475 deleted_files_comments=c.deleted_files_comments,
473 deleted_files_comments=c.deleted_files_comments,
476 inline_comments=c.inline_comments,
474 inline_comments=c.inline_comments,
477 pull_request_menu=pr_menu_data, show_todos=False)}
475 pull_request_menu=pr_menu_data, show_todos=False)}
478 % endfor
476 % endfor
479 % else:
477 % else:
480 ${cbdiffs.render_diffset(
478 ${cbdiffs.render_diffset(
481 c.diffset, use_comments=True,
479 c.diffset, use_comments=True,
482 collapse_when_files_over=30,
480 collapse_when_files_over=30,
483 disable_new_comments=not c.allowed_to_comment,
481 disable_new_comments=not c.allowed_to_comment,
484 deleted_files_comments=c.deleted_files_comments,
482 deleted_files_comments=c.deleted_files_comments,
485 inline_comments=c.inline_comments,
483 inline_comments=c.inline_comments,
486 pull_request_menu=pr_menu_data, show_todos=False)}
484 pull_request_menu=pr_menu_data, show_todos=False)}
487 % endif
485 % endif
488
486
489 </div>
487 </div>
490 % else:
488 % else:
491 ## skipping commits we need to clear the view for missing commits
489 ## skipping commits we need to clear the view for missing commits
492 <div style="clear:both;"></div>
490 <div style="clear:both;"></div>
493 % endif
491 % endif
494
492
495 </div>
493 </div>
496 </div>
494 </div>
497
495
498 ## template for inline comment form
496 ## template for inline comment form
499 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
497 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
500
498
501 ## comments heading with count
499 ## comments heading with count
502 <div class="comments-heading">
500 <div class="comments-heading">
503 <i class="icon-comment"></i>
501 <i class="icon-comment"></i>
504 ${_('General Comments')} ${len(c.comments)}
502 ${_('General Comments')} ${len(c.comments)}
505 </div>
503 </div>
506
504
507 ## render general comments
505 ## render general comments
508 <div id="comment-tr-show">
506 <div id="comment-tr-show">
509 % if general_outdated_comm_count_ver:
507 % if general_outdated_comm_count_ver:
510 <div class="info-box">
508 <div class="info-box">
511 % if general_outdated_comm_count_ver == 1:
509 % if general_outdated_comm_count_ver == 1:
512 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
510 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
513 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
511 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
514 % else:
512 % else:
515 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
513 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
516 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
514 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
517 % endif
515 % endif
518 </div>
516 </div>
519 % endif
517 % endif
520 </div>
518 </div>
521
519
522 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
520 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
523
521
524 % if not c.pull_request.is_closed():
522 % if not c.pull_request.is_closed():
525 ## main comment form and it status
523 ## main comment form and it status
526 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
524 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
527 pull_request_id=c.pull_request.pull_request_id),
525 pull_request_id=c.pull_request.pull_request_id),
528 c.pull_request_review_status,
526 c.pull_request_review_status,
529 is_pull_request=True, change_status=c.allowed_to_change_status)}
527 is_pull_request=True, change_status=c.allowed_to_change_status)}
530
528
531 ## merge status, and merge action
529 ## merge status, and merge action
532 <div class="pull-request-merge">
530 <div class="pull-request-merge">
533 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
531 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
534 </div>
532 </div>
535
533
536 %endif
534 %endif
537
535
538 % endif
536 % endif
539 </div>
537 </div>
540
538
541
539
542 ### NAV SIDEBAR
540 ### NAV SIDEBAR
543 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
541 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
544 <div class="sidenav navbar__inner" >
542 <div class="sidenav navbar__inner" >
545 ## TOGGLE
543 ## TOGGLE
546 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
544 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
547 <a href="#toggleSidebar" class="grey-link-action">
545 <a href="#toggleSidebar" class="grey-link-action">
548
546
549 </a>
547 </a>
550 </div>
548 </div>
551
549
552 ## CONTENT
550 ## CONTENT
553 <div class="sidebar-content">
551 <div class="sidebar-content">
554
552
555 ## Drafts
553 ## Drafts
556 % if c.rhodecode_edition_id == 'EE':
554 % if c.rhodecode_edition_id == 'EE':
557 <div class="sidebar-element clear-both">
555 <div class="sidebar-element clear-both">
558 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Drafts')}">
556 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Drafts')}">
559 <i class="icon-comment icon-draft"></i>
557 <i class="icon-comment icon-draft"></i>
560 <span id="comments-count">${0}</span>
558 <span id="comments-count">${0}</span>
561 </div>
559 </div>
562
560
563 <div class="right-sidebar-expanded-state pr-details-title">
561 <div class="right-sidebar-expanded-state pr-details-title">
564 <span class="sidebar-heading noselect">
562 <span class="sidebar-heading noselect">
565 <i class="icon-comment icon-draft"></i>
563 <i class="icon-comment icon-draft"></i>
566 ${_('Drafts')}
564 ${_('Drafts')}
567 </span>
565 </span>
568 </div>
566 </div>
569
567
570 <div id="drafts" class="right-sidebar-expanded-state pr-details-content reviewers">
568 <div id="drafts" class="right-sidebar-expanded-state pr-details-content reviewers">
571 ## members redering block
569 ## members redering block
572
570
573
571
574 ???
572 ???
575
573
576
574
577 ## end members redering block
575 ## end members redering block
578
576
579 </div>
577 </div>
580
578
581 </div>
579 </div>
582 % endif
580 % endif
583
581
584 ## RULES SUMMARY/RULES
582 ## RULES SUMMARY/RULES
585 <div class="sidebar-element clear-both">
583 <div class="sidebar-element clear-both">
586 <% vote_title = _ungettext(
584 <% vote_title = _ungettext(
587 'Status calculated based on votes from {} reviewer',
585 'Status calculated based on votes from {} reviewer',
588 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
586 'Status calculated based on votes from {} reviewers', c.reviewers_count).format(c.reviewers_count)
589 %>
587 %>
590
588
591 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
589 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
592 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
590 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
593 ${c.reviewers_count}
591 ${c.reviewers_count}
594 </div>
592 </div>
595
593
596 ## REVIEW RULES
594 ## REVIEW RULES
597 <div id="review_rules" style="display: none" class="">
595 <div id="review_rules" style="display: none" class="">
598 <div class="right-sidebar-expanded-state pr-details-title">
596 <div class="right-sidebar-expanded-state pr-details-title">
599 <span class="sidebar-heading">
597 <span class="sidebar-heading">
600 ${_('Reviewer rules')}
598 ${_('Reviewer rules')}
601 </span>
599 </span>
602
600
603 </div>
601 </div>
604 <div class="pr-reviewer-rules">
602 <div class="pr-reviewer-rules">
605 ## review rules will be appended here, by default reviewers logic
603 ## review rules will be appended here, by default reviewers logic
606 </div>
604 </div>
607 <input id="review_data" type="hidden" name="review_data" value="">
605 <input id="review_data" type="hidden" name="review_data" value="">
608 </div>
606 </div>
609
607
610 ## REVIEWERS
608 ## REVIEWERS
611 <div class="right-sidebar-expanded-state pr-details-title">
609 <div class="right-sidebar-expanded-state pr-details-title">
612 <span class="tooltip sidebar-heading" title="${vote_title}">
610 <span class="tooltip sidebar-heading" title="${vote_title}">
613 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
611 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
614 ${_('Reviewers')}
612 ${_('Reviewers')}
615 </span>
613 </span>
616 %if c.allowed_to_update:
614 %if c.allowed_to_update:
617 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
615 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
618 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
616 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
619 %else:
617 %else:
620 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
618 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Show rules')}</span>
621 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
619 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
622 %endif
620 %endif
623 </div>
621 </div>
624
622
625 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
623 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
626
624
627 ## members redering block
625 ## members redering block
628 <input type="hidden" name="__start__" value="review_members:sequence">
626 <input type="hidden" name="__start__" value="review_members:sequence">
629
627
630 <table id="review_members" class="group_members">
628 <table id="review_members" class="group_members">
631 ## This content is loaded via JS and ReviewersPanel
629 ## This content is loaded via JS and ReviewersPanel
632 </table>
630 </table>
633
631
634 <input type="hidden" name="__end__" value="review_members:sequence">
632 <input type="hidden" name="__end__" value="review_members:sequence">
635 ## end members redering block
633 ## end members redering block
636
634
637 %if not c.pull_request.is_closed():
635 %if not c.pull_request.is_closed():
638 <div id="add_reviewer" class="ac" style="display: none;">
636 <div id="add_reviewer" class="ac" style="display: none;">
639 %if c.allowed_to_update:
637 %if c.allowed_to_update:
640 % if not c.forbid_adding_reviewers:
638 % if not c.forbid_adding_reviewers:
641 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
639 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px">
642 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
640 <input class="ac-input" id="user" name="user" placeholder="${_('Add reviewer or reviewer group')}" type="text" autocomplete="off">
643 <div id="reviewers_container"></div>
641 <div id="reviewers_container"></div>
644 </div>
642 </div>
645 % endif
643 % endif
646 <div class="pull-right" style="margin-bottom: 15px">
644 <div class="pull-right" style="margin-bottom: 15px">
647 <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
645 <button data-role="reviewer" id="update_reviewers" class="btn btn-small no-margin">${_('Save Changes')}</button>
648 </div>
646 </div>
649 %endif
647 %endif
650 </div>
648 </div>
651 %endif
649 %endif
652 </div>
650 </div>
653 </div>
651 </div>
654
652
655 ## OBSERVERS
653 ## OBSERVERS
656 % if c.rhodecode_edition_id == 'EE':
654 % if c.rhodecode_edition_id == 'EE':
657 <div class="sidebar-element clear-both">
655 <div class="sidebar-element clear-both">
658 <% vote_title = _ungettext(
656 <% vote_title = _ungettext(
659 '{} observer without voting right.',
657 '{} observer without voting right.',
660 '{} observers without voting right.', c.observers_count).format(c.observers_count)
658 '{} observers without voting right.', c.observers_count).format(c.observers_count)
661 %>
659 %>
662
660
663 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
661 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
664 <i class="icon-circle-thin"></i>
662 <i class="icon-circle-thin"></i>
665 ${c.observers_count}
663 ${c.observers_count}
666 </div>
664 </div>
667
665
668 <div class="right-sidebar-expanded-state pr-details-title">
666 <div class="right-sidebar-expanded-state pr-details-title">
669 <span class="tooltip sidebar-heading" title="${vote_title}">
667 <span class="tooltip sidebar-heading" title="${vote_title}">
670 <i class="icon-circle-thin"></i>
668 <i class="icon-circle-thin"></i>
671 ${_('Observers')}
669 ${_('Observers')}
672 </span>
670 </span>
673 %if c.allowed_to_update:
671 %if c.allowed_to_update:
674 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
672 <span id="open_edit_observers" class="block-right action_button last-item">${_('Edit')}</span>
675 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
673 <span id="close_edit_observers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
676 %endif
674 %endif
677 </div>
675 </div>
678
676
679 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
677 <div id="observers" class="right-sidebar-expanded-state pr-details-content reviewers">
680 ## members redering block
678 ## members redering block
681 <input type="hidden" name="__start__" value="observer_members:sequence">
679 <input type="hidden" name="__start__" value="observer_members:sequence">
682
680
683 <table id="observer_members" class="group_members">
681 <table id="observer_members" class="group_members">
684 ## This content is loaded via JS and ReviewersPanel
682 ## This content is loaded via JS and ReviewersPanel
685 </table>
683 </table>
686
684
687 <input type="hidden" name="__end__" value="observer_members:sequence">
685 <input type="hidden" name="__end__" value="observer_members:sequence">
688 ## end members redering block
686 ## end members redering block
689
687
690 %if not c.pull_request.is_closed():
688 %if not c.pull_request.is_closed():
691 <div id="add_observer" class="ac" style="display: none;">
689 <div id="add_observer" class="ac" style="display: none;">
692 %if c.allowed_to_update:
690 %if c.allowed_to_update:
693 % if not c.forbid_adding_reviewers or 1:
691 % if not c.forbid_adding_reviewers or 1:
694 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
692 <div id="add_reviewer_input" class="reviewer_ac" style="width: 240px" >
695 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
693 <input class="ac-input" id="observer" name="observer" placeholder="${_('Add observer or observer group')}" type="text" autocomplete="off">
696 <div id="observers_container"></div>
694 <div id="observers_container"></div>
697 </div>
695 </div>
698 % endif
696 % endif
699 <div class="pull-right" style="margin-bottom: 15px">
697 <div class="pull-right" style="margin-bottom: 15px">
700 <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
698 <button data-role="observer" id="update_observers" class="btn btn-small no-margin">${_('Save Changes')}</button>
701 </div>
699 </div>
702 %endif
700 %endif
703 </div>
701 </div>
704 %endif
702 %endif
705 </div>
703 </div>
706 </div>
704 </div>
707 % endif
705 % endif
708
706
709 ## TODOs
707 ## TODOs
710 <div class="sidebar-element clear-both">
708 <div class="sidebar-element clear-both">
711 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
709 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
712 <i class="icon-flag-filled"></i>
710 <i class="icon-flag-filled"></i>
713 <span id="todos-count">${len(c.unresolved_comments)}</span>
711 <span id="todos-count">${len(c.unresolved_comments)}</span>
714 </div>
712 </div>
715
713
716 <div class="right-sidebar-expanded-state pr-details-title">
714 <div class="right-sidebar-expanded-state pr-details-title">
717 ## Only show unresolved, that is only what matters
715 ## Only show unresolved, that is only what matters
718 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
716 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
719 <i class="icon-flag-filled"></i>
717 <i class="icon-flag-filled"></i>
720 TODOs
718 TODOs
721 </span>
719 </span>
722
720
723 % if not c.at_version:
721 % if not c.at_version:
724 % if c.resolved_comments:
722 % if c.resolved_comments:
725 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
723 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
726 % else:
724 % else:
727 <span class="block-right last-item noselect">Show resolved</span>
725 <span class="block-right last-item noselect">Show resolved</span>
728 % endif
726 % endif
729 % endif
727 % endif
730 </div>
728 </div>
731
729
732 <div class="right-sidebar-expanded-state pr-details-content">
730 <div class="right-sidebar-expanded-state pr-details-content">
733
731
734 % if c.at_version:
732 % if c.at_version:
735 <table>
733 <table>
736 <tr>
734 <tr>
737 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
735 <td class="unresolved-todo-text">${_('TODOs unavailable when browsing versions')}.</td>
738 </tr>
736 </tr>
739 </table>
737 </table>
740 % else:
738 % else:
741 % if c.unresolved_comments + c.resolved_comments:
739 % if c.unresolved_comments + c.resolved_comments:
742 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
740 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
743 % else:
741 % else:
744 <table>
742 <table>
745 <tr>
743 <tr>
746 <td>
744 <td>
747 ${_('No TODOs yet')}
745 ${_('No TODOs yet')}
748 </td>
746 </td>
749 </tr>
747 </tr>
750 </table>
748 </table>
751 % endif
749 % endif
752 % endif
750 % endif
753 </div>
751 </div>
754 </div>
752 </div>
755
753
756 ## COMMENTS
754 ## COMMENTS
757 <div class="sidebar-element clear-both">
755 <div class="sidebar-element clear-both">
758 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
756 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
759 <i class="icon-comment" style="color: #949494"></i>
757 <i class="icon-comment" style="color: #949494"></i>
760 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
758 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
761 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
759 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
762 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
760 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
763 </div>
761 </div>
764
762
765 <div class="right-sidebar-expanded-state pr-details-title">
763 <div class="right-sidebar-expanded-state pr-details-title">
766 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
764 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
767 <i class="icon-comment" style="color: #949494"></i>
765 <i class="icon-comment" style="color: #949494"></i>
768 ${_('Comments')}
766 ${_('Comments')}
769
767
770 ## % if outdated_comm_count_ver:
768 ## % if outdated_comm_count_ver:
771 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
769 ## <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
772 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
770 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
773 ## </a>
771 ## </a>
774 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
772 ## <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
775 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
773 ## <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
776
774
777 ## % else:
775 ## % else:
778 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
776 ## (${_("{} Outdated").format(outdated_comm_count_ver)})
779 ## % endif
777 ## % endif
780
778
781 </span>
779 </span>
782
780
783 % if outdated_comm_count_ver:
781 % if outdated_comm_count_ver:
784 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
782 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
785 % else:
783 % else:
786 <span class="block-right last-item noselect">Show hidden</span>
784 <span class="block-right last-item noselect">Show hidden</span>
787 % endif
785 % endif
788
786
789 </div>
787 </div>
790
788
791 <div class="right-sidebar-expanded-state pr-details-content">
789 <div class="right-sidebar-expanded-state pr-details-content">
792 % if c.inline_comments_flat + c.comments:
790 % if c.inline_comments_flat + c.comments:
793 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
791 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
794 % else:
792 % else:
795 <table>
793 <table>
796 <tr>
794 <tr>
797 <td>
795 <td>
798 ${_('No Comments yet')}
796 ${_('No Comments yet')}
799 </td>
797 </td>
800 </tr>
798 </tr>
801 </table>
799 </table>
802 % endif
800 % endif
803 </div>
801 </div>
804
802
805 </div>
803 </div>
806
804
807 ## Referenced Tickets
805 ## Referenced Tickets
808 <div class="sidebar-element clear-both">
806 <div class="sidebar-element clear-both">
809 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
807 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Referenced Tickets')}">
810 <i class="icon-info-circled"></i>
808 <i class="icon-info-circled"></i>
811 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
809 ${(len(c.referenced_desc_issues) + len(c.referenced_commit_issues))}
812 </div>
810 </div>
813
811
814 <div class="right-sidebar-expanded-state pr-details-title">
812 <div class="right-sidebar-expanded-state pr-details-title">
815 <span class="sidebar-heading">
813 <span class="sidebar-heading">
816 <i class="icon-info-circled"></i>
814 <i class="icon-info-circled"></i>
817 ${_('Referenced Tickets')}
815 ${_('Referenced Tickets')}
818 </span>
816 </span>
819 </div>
817 </div>
820 <div class="right-sidebar-expanded-state pr-details-content">
818 <div class="right-sidebar-expanded-state pr-details-content">
821 <table>
819 <table>
822
820
823 <tr><td><code>${_('In pull request description')}:</code></td></tr>
821 <tr><td><code>${_('In pull request description')}:</code></td></tr>
824 % if c.referenced_desc_issues:
822 % if c.referenced_desc_issues:
825 % for ticket_dict in sorted(c.referenced_desc_issues):
823 % for ticket_dict in sorted(c.referenced_desc_issues):
826 <tr>
824 <tr>
827 <td>
825 <td>
828 <a href="${ticket_dict.get('url')}">
826 <a href="${ticket_dict.get('url')}">
829 ${ticket_dict.get('id')}
827 ${ticket_dict.get('id')}
830 </a>
828 </a>
831 </td>
829 </td>
832 </tr>
830 </tr>
833 % endfor
831 % endfor
834 % else:
832 % else:
835 <tr>
833 <tr>
836 <td>
834 <td>
837 ${_('No Ticket data found.')}
835 ${_('No Ticket data found.')}
838 </td>
836 </td>
839 </tr>
837 </tr>
840 % endif
838 % endif
841
839
842 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
840 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
843 % if c.referenced_commit_issues:
841 % if c.referenced_commit_issues:
844 % for ticket_dict in sorted(c.referenced_commit_issues):
842 % for ticket_dict in sorted(c.referenced_commit_issues):
845 <tr>
843 <tr>
846 <td>
844 <td>
847 <a href="${ticket_dict.get('url')}">
845 <a href="${ticket_dict.get('url')}">
848 ${ticket_dict.get('id')}
846 ${ticket_dict.get('id')}
849 </a>
847 </a>
850 </td>
848 </td>
851 </tr>
849 </tr>
852 % endfor
850 % endfor
853 % else:
851 % else:
854 <tr>
852 <tr>
855 <td>
853 <td>
856 ${_('No Ticket data found.')}
854 ${_('No Ticket data found.')}
857 </td>
855 </td>
858 </tr>
856 </tr>
859 % endif
857 % endif
860 </table>
858 </table>
861
859
862 </div>
860 </div>
863 </div>
861 </div>
864
862
865 </div>
863 </div>
866
864
867 </div>
865 </div>
868 </aside>
866 </aside>
869
867
870 ## This JS needs to be at the end
868 ## This JS needs to be at the end
871 <script type="text/javascript">
869 <script type="text/javascript">
872
870
873 versionController = new VersionController();
871 versionController = new VersionController();
874 versionController.init();
872 versionController.init();
875
873
876 reviewersController = new ReviewersController();
874 reviewersController = new ReviewersController();
877 commitsController = new CommitsController();
875 commitsController = new CommitsController();
878 commentsController = new CommentsController();
876 commentsController = new CommentsController();
879
877
880 updateController = new UpdatePrController();
878 updateController = new UpdatePrController();
881
879
882 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
880 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
883 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
881 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
884 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
882 window.setObserversData = ${c.pull_request_set_observers_data_json | n};
885
883
886 (function () {
884 (function () {
887 "use strict";
885 "use strict";
888
886
889 // custom code mirror
887 // custom code mirror
890 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
888 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
891
889
892 PRDetails.init();
890 PRDetails.init();
893 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
891 ReviewersPanel.init(reviewersController, reviewerRulesData, setReviewersData);
894 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
892 ObserversPanel.init(reviewersController, reviewerRulesData, setObserversData);
895
893
896 window.showOutdated = function (self) {
894 window.showOutdated = function (self) {
897 $('.comment-inline.comment-outdated').show();
895 $('.comment-inline.comment-outdated').show();
898 $('.filediff-outdated').show();
896 $('.filediff-outdated').show();
899 $('.showOutdatedComments').hide();
897 $('.showOutdatedComments').hide();
900 $('.hideOutdatedComments').show();
898 $('.hideOutdatedComments').show();
901 };
899 };
902
900
903 window.hideOutdated = function (self) {
901 window.hideOutdated = function (self) {
904 $('.comment-inline.comment-outdated').hide();
902 $('.comment-inline.comment-outdated').hide();
905 $('.filediff-outdated').hide();
903 $('.filediff-outdated').hide();
906 $('.hideOutdatedComments').hide();
904 $('.hideOutdatedComments').hide();
907 $('.showOutdatedComments').show();
905 $('.showOutdatedComments').show();
908 };
906 };
909
907
910 window.refreshMergeChecks = function () {
908 window.refreshMergeChecks = function () {
911 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
909 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
912 $('.pull-request-merge').css('opacity', 0.3);
910 $('.pull-request-merge').css('opacity', 0.3);
913 $('.action-buttons-extra').css('opacity', 0.3);
911 $('.action-buttons-extra').css('opacity', 0.3);
914
912
915 $('.pull-request-merge').load(
913 $('.pull-request-merge').load(
916 loadUrl, function () {
914 loadUrl, function () {
917 $('.pull-request-merge').css('opacity', 1);
915 $('.pull-request-merge').css('opacity', 1);
918
916
919 $('.action-buttons-extra').css('opacity', 1);
917 $('.action-buttons-extra').css('opacity', 1);
920 }
918 }
921 );
919 );
922 };
920 };
923
921
924 window.closePullRequest = function (status) {
922 window.closePullRequest = function (status) {
925 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
923 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
926 return false;
924 return false;
927 }
925 }
928 // inject closing flag
926 // inject closing flag
929 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
927 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
930 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
928 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
931 $(generalCommentForm.submitForm).submit();
929 $(generalCommentForm.submitForm).submit();
932 };
930 };
933
931
934 //TODO this functionality is now missing
932 //TODO this functionality is now missing
935 $('#show-outdated-comments').on('click', function (e) {
933 $('#show-outdated-comments').on('click', function (e) {
936 var button = $(this);
934 var button = $(this);
937 var outdated = $('.comment-outdated');
935 var outdated = $('.comment-outdated');
938
936
939 if (button.html() === "(Show)") {
937 if (button.html() === "(Show)") {
940 button.html("(Hide)");
938 button.html("(Hide)");
941 outdated.show();
939 outdated.show();
942 } else {
940 } else {
943 button.html("(Show)");
941 button.html("(Show)");
944 outdated.hide();
942 outdated.hide();
945 }
943 }
946 });
944 });
947
945
948 $('#merge_pull_request_form').submit(function () {
946 $('#merge_pull_request_form').submit(function () {
949 if (!$('#merge_pull_request').attr('disabled')) {
947 if (!$('#merge_pull_request').attr('disabled')) {
950 $('#merge_pull_request').attr('disabled', 'disabled');
948 $('#merge_pull_request').attr('disabled', 'disabled');
951 }
949 }
952 return true;
950 return true;
953 });
951 });
954
952
955 $('#edit_pull_request').on('click', function (e) {
953 $('#edit_pull_request').on('click', function (e) {
956 var title = $('#pr-title-input').val();
954 var title = $('#pr-title-input').val();
957 var description = codeMirrorInstance.getValue();
955 var description = codeMirrorInstance.getValue();
958 var renderer = $('#pr-renderer-input').val();
956 var renderer = $('#pr-renderer-input').val();
959 editPullRequest(
957 editPullRequest(
960 "${c.repo_name}", "${c.pull_request.pull_request_id}",
958 "${c.repo_name}", "${c.pull_request.pull_request_id}",
961 title, description, renderer);
959 title, description, renderer);
962 });
960 });
963
961
964 var $updateButtons = $('#update_reviewers,#update_observers');
962 var $updateButtons = $('#update_reviewers,#update_observers');
965 $updateButtons.on('click', function (e) {
963 $updateButtons.on('click', function (e) {
966 var role = $(this).data('role');
964 var role = $(this).data('role');
967 $updateButtons.attr('disabled', 'disabled');
965 $updateButtons.attr('disabled', 'disabled');
968 $updateButtons.addClass('disabled');
966 $updateButtons.addClass('disabled');
969 $updateButtons.html(_gettext('Saving...'));
967 $updateButtons.html(_gettext('Saving...'));
970 reviewersController.updateReviewers(
968 reviewersController.updateReviewers(
971 templateContext.repo_name,
969 templateContext.repo_name,
972 templateContext.pull_request_data.pull_request_id,
970 templateContext.pull_request_data.pull_request_id,
973 role
971 role
974 );
972 );
975 });
973 });
976
974
977 // fixing issue with caches on firefox
975 // fixing issue with caches on firefox
978 $('#update_commits').removeAttr("disabled");
976 $('#update_commits').removeAttr("disabled");
979
977
980 $('.show-inline-comments').on('click', function (e) {
978 $('.show-inline-comments').on('click', function (e) {
981 var boxid = $(this).attr('data-comment-id');
979 var boxid = $(this).attr('data-comment-id');
982 var button = $(this);
980 var button = $(this);
983
981
984 if (button.hasClass("comments-visible")) {
982 if (button.hasClass("comments-visible")) {
985 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
983 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
986 $(this).hide();
984 $(this).hide();
987 });
985 });
988 button.removeClass("comments-visible");
986 button.removeClass("comments-visible");
989 } else {
987 } else {
990 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
988 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
991 $(this).show();
989 $(this).show();
992 });
990 });
993 button.addClass("comments-visible");
991 button.addClass("comments-visible");
994 }
992 }
995 });
993 });
996
994
997 $('.show-inline-comments').on('change', function (e) {
995 $('.show-inline-comments').on('change', function (e) {
998 var show = 'none';
996 var show = 'none';
999 var target = e.currentTarget;
997 var target = e.currentTarget;
1000 if (target.checked) {
998 if (target.checked) {
1001 show = ''
999 show = ''
1002 }
1000 }
1003 var boxid = $(target).attr('id_for');
1001 var boxid = $(target).attr('id_for');
1004 var comments = $('#{0} .inline-comments'.format(boxid));
1002 var comments = $('#{0} .inline-comments'.format(boxid));
1005 var fn_display = function (idx) {
1003 var fn_display = function (idx) {
1006 $(this).css('display', show);
1004 $(this).css('display', show);
1007 };
1005 };
1008 $(comments).each(fn_display);
1006 $(comments).each(fn_display);
1009 var btns = $('#{0} .inline-comments-button'.format(boxid));
1007 var btns = $('#{0} .inline-comments-button'.format(boxid));
1010 $(btns).each(fn_display);
1008 $(btns).each(fn_display);
1011 });
1009 });
1012
1010
1013 // register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions
1011 // register submit callback on commentForm form to track TODOs, and refresh mergeChecks conditions
1014 window.commentFormGlobalSubmitSuccessCallback = function (comment) {
1012 window.commentFormGlobalSubmitSuccessCallback = function (comment) {
1015 if (!comment.draft) {
1013 if (!comment.draft) {
1016 refreshMergeChecks();
1014 refreshMergeChecks();
1017 }
1015 }
1018 };
1016 };
1019
1017
1020 ReviewerAutoComplete('#user', reviewersController);
1018 ReviewerAutoComplete('#user', reviewersController);
1021 ObserverAutoComplete('#observer', reviewersController);
1019 ObserverAutoComplete('#observer', reviewersController);
1022
1020
1023 })();
1021 })();
1024
1022
1025 $(document).ready(function () {
1023 $(document).ready(function () {
1026
1024
1027 var channel = '${c.pr_broadcast_channel}';
1025 var channel = '${c.pr_broadcast_channel}';
1028 new ReviewerPresenceController(channel)
1026 new ReviewerPresenceController(channel)
1029 // register globally so inject comment logic can re-use it.
1027 // register globally so inject comment logic can re-use it.
1030 window.commentsController = commentsController;
1028 window.commentsController = commentsController;
1031
1029
1032 })
1030 })
1033 </script>
1031 </script>
1034
1032
1035 </%def>
1033 </%def>
General Comments 0
You need to be logged in to leave comments. Login now