##// END OF EJS Templates
comments: re-implemented diff and comments/todos in pull-requests.
marcink -
r3884:761273d6 default
parent child Browse files
Show More
@@ -1,1464 +1,1470 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 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)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
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.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 NotAnonymous, CSRFRequired)
40 NotAnonymous, CSRFRequired)
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
44 RepositoryRequirementError, EmptyRepositoryError)
44 RepositoryRequirementError, EmptyRepositoryError)
45 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
48 ChangesetComment, ChangesetStatus, Repository)
48 ChangesetComment, ChangesetStatus, Repository)
49 from rhodecode.model.forms import PullRequestForm
49 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58
58
59 def load_default_context(self):
59 def load_default_context(self):
60 c = self._get_local_tmpl_context(include_app_defaults=True)
60 c = self._get_local_tmpl_context(include_app_defaults=True)
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63 # backward compat., we use for OLD PRs a plain renderer
63 # backward compat., we use for OLD PRs a plain renderer
64 c.renderer = 'plain'
64 c.renderer = 'plain'
65 return c
65 return c
66
66
67 def _get_pull_requests_list(
67 def _get_pull_requests_list(
68 self, repo_name, source, filter_type, opened_by, statuses):
68 self, repo_name, source, filter_type, opened_by, statuses):
69
69
70 draw, start, limit = self._extract_chunk(self.request)
70 draw, start, limit = self._extract_chunk(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 _render = self.request.get_partial_renderer(
72 _render = self.request.get_partial_renderer(
73 'rhodecode:templates/data_table/_dt_elements.mako')
73 'rhodecode:templates/data_table/_dt_elements.mako')
74
74
75 # pagination
75 # pagination
76
76
77 if filter_type == 'awaiting_review':
77 if filter_type == 'awaiting_review':
78 pull_requests = PullRequestModel().get_awaiting_review(
78 pull_requests = PullRequestModel().get_awaiting_review(
79 repo_name, source=source, opened_by=opened_by,
79 repo_name, source=source, opened_by=opened_by,
80 statuses=statuses, offset=start, length=limit,
80 statuses=statuses, offset=start, length=limit,
81 order_by=order_by, order_dir=order_dir)
81 order_by=order_by, order_dir=order_dir)
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 repo_name, source=source, statuses=statuses,
83 repo_name, source=source, statuses=statuses,
84 opened_by=opened_by)
84 opened_by=opened_by)
85 elif filter_type == 'awaiting_my_review':
85 elif filter_type == 'awaiting_my_review':
86 pull_requests = PullRequestModel().get_awaiting_my_review(
86 pull_requests = PullRequestModel().get_awaiting_my_review(
87 repo_name, source=source, opened_by=opened_by,
87 repo_name, source=source, opened_by=opened_by,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 offset=start, length=limit, order_by=order_by,
89 offset=start, length=limit, order_by=order_by,
90 order_dir=order_dir)
90 order_dir=order_dir)
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 repo_name, source=source, user_id=self._rhodecode_user.user_id,
92 repo_name, source=source, user_id=self._rhodecode_user.user_id,
93 statuses=statuses, opened_by=opened_by)
93 statuses=statuses, opened_by=opened_by)
94 else:
94 else:
95 pull_requests = PullRequestModel().get_all(
95 pull_requests = PullRequestModel().get_all(
96 repo_name, source=source, opened_by=opened_by,
96 repo_name, source=source, opened_by=opened_by,
97 statuses=statuses, offset=start, length=limit,
97 statuses=statuses, offset=start, length=limit,
98 order_by=order_by, order_dir=order_dir)
98 order_by=order_by, order_dir=order_dir)
99 pull_requests_total_count = PullRequestModel().count_all(
99 pull_requests_total_count = PullRequestModel().count_all(
100 repo_name, source=source, statuses=statuses,
100 repo_name, source=source, statuses=statuses,
101 opened_by=opened_by)
101 opened_by=opened_by)
102
102
103 data = []
103 data = []
104 comments_model = CommentsModel()
104 comments_model = CommentsModel()
105 for pr in pull_requests:
105 for pr in pull_requests:
106 comments = comments_model.get_all_comments(
106 comments = comments_model.get_all_comments(
107 self.db_repo.repo_id, pull_request=pr)
107 self.db_repo.repo_id, pull_request=pr)
108
108
109 data.append({
109 data.append({
110 'name': _render('pullrequest_name',
110 'name': _render('pullrequest_name',
111 pr.pull_request_id, pr.target_repo.repo_name),
111 pr.pull_request_id, pr.target_repo.repo_name),
112 'name_raw': pr.pull_request_id,
112 'name_raw': pr.pull_request_id,
113 'status': _render('pullrequest_status',
113 'status': _render('pullrequest_status',
114 pr.calculated_review_status()),
114 pr.calculated_review_status()),
115 'title': _render('pullrequest_title', pr.title, pr.description),
115 'title': _render('pullrequest_title', pr.title, pr.description),
116 'description': h.escape(pr.description),
116 'description': h.escape(pr.description),
117 'updated_on': _render('pullrequest_updated_on',
117 'updated_on': _render('pullrequest_updated_on',
118 h.datetime_to_time(pr.updated_on)),
118 h.datetime_to_time(pr.updated_on)),
119 'updated_on_raw': h.datetime_to_time(pr.updated_on),
119 'updated_on_raw': h.datetime_to_time(pr.updated_on),
120 'created_on': _render('pullrequest_updated_on',
120 'created_on': _render('pullrequest_updated_on',
121 h.datetime_to_time(pr.created_on)),
121 h.datetime_to_time(pr.created_on)),
122 'created_on_raw': h.datetime_to_time(pr.created_on),
122 'created_on_raw': h.datetime_to_time(pr.created_on),
123 'state': pr.pull_request_state,
123 'state': pr.pull_request_state,
124 'author': _render('pullrequest_author',
124 'author': _render('pullrequest_author',
125 pr.author.full_contact, ),
125 pr.author.full_contact, ),
126 'author_raw': pr.author.full_name,
126 'author_raw': pr.author.full_name,
127 'comments': _render('pullrequest_comments', len(comments)),
127 'comments': _render('pullrequest_comments', len(comments)),
128 'comments_raw': len(comments),
128 'comments_raw': len(comments),
129 'closed': pr.is_closed(),
129 'closed': pr.is_closed(),
130 })
130 })
131
131
132 data = ({
132 data = ({
133 'draw': draw,
133 'draw': draw,
134 'data': data,
134 'data': data,
135 'recordsTotal': pull_requests_total_count,
135 'recordsTotal': pull_requests_total_count,
136 'recordsFiltered': pull_requests_total_count,
136 'recordsFiltered': pull_requests_total_count,
137 })
137 })
138 return data
138 return data
139
139
140 @LoginRequired()
140 @LoginRequired()
141 @HasRepoPermissionAnyDecorator(
141 @HasRepoPermissionAnyDecorator(
142 'repository.read', 'repository.write', 'repository.admin')
142 'repository.read', 'repository.write', 'repository.admin')
143 @view_config(
143 @view_config(
144 route_name='pullrequest_show_all', request_method='GET',
144 route_name='pullrequest_show_all', request_method='GET',
145 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
145 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
146 def pull_request_list(self):
146 def pull_request_list(self):
147 c = self.load_default_context()
147 c = self.load_default_context()
148
148
149 req_get = self.request.GET
149 req_get = self.request.GET
150 c.source = str2bool(req_get.get('source'))
150 c.source = str2bool(req_get.get('source'))
151 c.closed = str2bool(req_get.get('closed'))
151 c.closed = str2bool(req_get.get('closed'))
152 c.my = str2bool(req_get.get('my'))
152 c.my = str2bool(req_get.get('my'))
153 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
153 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
154 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
154 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
155
155
156 c.active = 'open'
156 c.active = 'open'
157 if c.my:
157 if c.my:
158 c.active = 'my'
158 c.active = 'my'
159 if c.closed:
159 if c.closed:
160 c.active = 'closed'
160 c.active = 'closed'
161 if c.awaiting_review and not c.source:
161 if c.awaiting_review and not c.source:
162 c.active = 'awaiting'
162 c.active = 'awaiting'
163 if c.source and not c.awaiting_review:
163 if c.source and not c.awaiting_review:
164 c.active = 'source'
164 c.active = 'source'
165 if c.awaiting_my_review:
165 if c.awaiting_my_review:
166 c.active = 'awaiting_my'
166 c.active = 'awaiting_my'
167
167
168 return self._get_template_context(c)
168 return self._get_template_context(c)
169
169
170 @LoginRequired()
170 @LoginRequired()
171 @HasRepoPermissionAnyDecorator(
171 @HasRepoPermissionAnyDecorator(
172 'repository.read', 'repository.write', 'repository.admin')
172 'repository.read', 'repository.write', 'repository.admin')
173 @view_config(
173 @view_config(
174 route_name='pullrequest_show_all_data', request_method='GET',
174 route_name='pullrequest_show_all_data', request_method='GET',
175 renderer='json_ext', xhr=True)
175 renderer='json_ext', xhr=True)
176 def pull_request_list_data(self):
176 def pull_request_list_data(self):
177 self.load_default_context()
177 self.load_default_context()
178
178
179 # additional filters
179 # additional filters
180 req_get = self.request.GET
180 req_get = self.request.GET
181 source = str2bool(req_get.get('source'))
181 source = str2bool(req_get.get('source'))
182 closed = str2bool(req_get.get('closed'))
182 closed = str2bool(req_get.get('closed'))
183 my = str2bool(req_get.get('my'))
183 my = str2bool(req_get.get('my'))
184 awaiting_review = str2bool(req_get.get('awaiting_review'))
184 awaiting_review = str2bool(req_get.get('awaiting_review'))
185 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
185 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
186
186
187 filter_type = 'awaiting_review' if awaiting_review \
187 filter_type = 'awaiting_review' if awaiting_review \
188 else 'awaiting_my_review' if awaiting_my_review \
188 else 'awaiting_my_review' if awaiting_my_review \
189 else None
189 else None
190
190
191 opened_by = None
191 opened_by = None
192 if my:
192 if my:
193 opened_by = [self._rhodecode_user.user_id]
193 opened_by = [self._rhodecode_user.user_id]
194
194
195 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
195 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
196 if closed:
196 if closed:
197 statuses = [PullRequest.STATUS_CLOSED]
197 statuses = [PullRequest.STATUS_CLOSED]
198
198
199 data = self._get_pull_requests_list(
199 data = self._get_pull_requests_list(
200 repo_name=self.db_repo_name, source=source,
200 repo_name=self.db_repo_name, source=source,
201 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
201 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
202
202
203 return data
203 return data
204
204
205 def _is_diff_cache_enabled(self, target_repo):
205 def _is_diff_cache_enabled(self, target_repo):
206 caching_enabled = self._get_general_setting(
206 caching_enabled = self._get_general_setting(
207 target_repo, 'rhodecode_diff_cache')
207 target_repo, 'rhodecode_diff_cache')
208 log.debug('Diff caching enabled: %s', caching_enabled)
208 log.debug('Diff caching enabled: %s', caching_enabled)
209 return caching_enabled
209 return caching_enabled
210
210
211 def _get_diffset(self, source_repo_name, source_repo,
211 def _get_diffset(self, source_repo_name, source_repo,
212 source_ref_id, target_ref_id,
212 source_ref_id, target_ref_id,
213 target_commit, source_commit, diff_limit, file_limit,
213 target_commit, source_commit, diff_limit, file_limit,
214 fulldiff, hide_whitespace_changes, diff_context):
214 fulldiff, hide_whitespace_changes, diff_context):
215
215
216 vcs_diff = PullRequestModel().get_diff(
216 vcs_diff = PullRequestModel().get_diff(
217 source_repo, source_ref_id, target_ref_id,
217 source_repo, source_ref_id, target_ref_id,
218 hide_whitespace_changes, diff_context)
218 hide_whitespace_changes, diff_context)
219
219
220 diff_processor = diffs.DiffProcessor(
220 diff_processor = diffs.DiffProcessor(
221 vcs_diff, format='newdiff', diff_limit=diff_limit,
221 vcs_diff, format='newdiff', diff_limit=diff_limit,
222 file_limit=file_limit, show_full_diff=fulldiff)
222 file_limit=file_limit, show_full_diff=fulldiff)
223
223
224 _parsed = diff_processor.prepare()
224 _parsed = diff_processor.prepare()
225
225
226 diffset = codeblocks.DiffSet(
226 diffset = codeblocks.DiffSet(
227 repo_name=self.db_repo_name,
227 repo_name=self.db_repo_name,
228 source_repo_name=source_repo_name,
228 source_repo_name=source_repo_name,
229 source_node_getter=codeblocks.diffset_node_getter(target_commit),
229 source_node_getter=codeblocks.diffset_node_getter(target_commit),
230 target_node_getter=codeblocks.diffset_node_getter(source_commit),
230 target_node_getter=codeblocks.diffset_node_getter(source_commit),
231 )
231 )
232 diffset = self.path_filter.render_patchset_filtered(
232 diffset = self.path_filter.render_patchset_filtered(
233 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
233 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
234
234
235 return diffset
235 return diffset
236
236
237 def _get_range_diffset(self, source_scm, source_repo,
237 def _get_range_diffset(self, source_scm, source_repo,
238 commit1, commit2, diff_limit, file_limit,
238 commit1, commit2, diff_limit, file_limit,
239 fulldiff, hide_whitespace_changes, diff_context):
239 fulldiff, hide_whitespace_changes, diff_context):
240 vcs_diff = source_scm.get_diff(
240 vcs_diff = source_scm.get_diff(
241 commit1, commit2,
241 commit1, commit2,
242 ignore_whitespace=hide_whitespace_changes,
242 ignore_whitespace=hide_whitespace_changes,
243 context=diff_context)
243 context=diff_context)
244
244
245 diff_processor = diffs.DiffProcessor(
245 diff_processor = diffs.DiffProcessor(
246 vcs_diff, format='newdiff', diff_limit=diff_limit,
246 vcs_diff, format='newdiff', diff_limit=diff_limit,
247 file_limit=file_limit, show_full_diff=fulldiff)
247 file_limit=file_limit, show_full_diff=fulldiff)
248
248
249 _parsed = diff_processor.prepare()
249 _parsed = diff_processor.prepare()
250
250
251 diffset = codeblocks.DiffSet(
251 diffset = codeblocks.DiffSet(
252 repo_name=source_repo.repo_name,
252 repo_name=source_repo.repo_name,
253 source_node_getter=codeblocks.diffset_node_getter(commit1),
253 source_node_getter=codeblocks.diffset_node_getter(commit1),
254 target_node_getter=codeblocks.diffset_node_getter(commit2))
254 target_node_getter=codeblocks.diffset_node_getter(commit2))
255
255
256 diffset = self.path_filter.render_patchset_filtered(
256 diffset = self.path_filter.render_patchset_filtered(
257 diffset, _parsed, commit1.raw_id, commit2.raw_id)
257 diffset, _parsed, commit1.raw_id, commit2.raw_id)
258
258
259 return diffset
259 return diffset
260
260
261 @LoginRequired()
261 @LoginRequired()
262 @HasRepoPermissionAnyDecorator(
262 @HasRepoPermissionAnyDecorator(
263 'repository.read', 'repository.write', 'repository.admin')
263 'repository.read', 'repository.write', 'repository.admin')
264 @view_config(
264 @view_config(
265 route_name='pullrequest_show', request_method='GET',
265 route_name='pullrequest_show', request_method='GET',
266 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
266 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
267 def pull_request_show(self):
267 def pull_request_show(self):
268 _ = self.request.translate
268 _ = self.request.translate
269 c = self.load_default_context()
269 c = self.load_default_context()
270
270
271 pull_request = PullRequest.get_or_404(
271 pull_request = PullRequest.get_or_404(
272 self.request.matchdict['pull_request_id'])
272 self.request.matchdict['pull_request_id'])
273 pull_request_id = pull_request.pull_request_id
273 pull_request_id = pull_request.pull_request_id
274
274
275 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
275 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
276 log.debug('show: forbidden because pull request is in state %s',
276 log.debug('show: forbidden because pull request is in state %s',
277 pull_request.pull_request_state)
277 pull_request.pull_request_state)
278 msg = _(u'Cannot show pull requests in state other than `{}`. '
278 msg = _(u'Cannot show pull requests in state other than `{}`. '
279 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
279 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
280 pull_request.pull_request_state)
280 pull_request.pull_request_state)
281 h.flash(msg, category='error')
281 h.flash(msg, category='error')
282 raise HTTPFound(h.route_path('pullrequest_show_all',
282 raise HTTPFound(h.route_path('pullrequest_show_all',
283 repo_name=self.db_repo_name))
283 repo_name=self.db_repo_name))
284
284
285 version = self.request.GET.get('version')
285 version = self.request.GET.get('version')
286 from_version = self.request.GET.get('from_version') or version
286 from_version = self.request.GET.get('from_version') or version
287 merge_checks = self.request.GET.get('merge_checks')
287 merge_checks = self.request.GET.get('merge_checks')
288 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
288 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
289
289
290 # fetch global flags of ignore ws or context lines
290 # fetch global flags of ignore ws or context lines
291 diff_context = diffs.get_diff_context(self.request)
291 diff_context = diffs.get_diff_context(self.request)
292 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
292 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
293
293
294 force_refresh = str2bool(self.request.GET.get('force_refresh'))
294 force_refresh = str2bool(self.request.GET.get('force_refresh'))
295
295
296 (pull_request_latest,
296 (pull_request_latest,
297 pull_request_at_ver,
297 pull_request_at_ver,
298 pull_request_display_obj,
298 pull_request_display_obj,
299 at_version) = PullRequestModel().get_pr_version(
299 at_version) = PullRequestModel().get_pr_version(
300 pull_request_id, version=version)
300 pull_request_id, version=version)
301 pr_closed = pull_request_latest.is_closed()
301 pr_closed = pull_request_latest.is_closed()
302
302
303 if pr_closed and (version or from_version):
303 if pr_closed and (version or from_version):
304 # not allow to browse versions
304 # not allow to browse versions
305 raise HTTPFound(h.route_path(
305 raise HTTPFound(h.route_path(
306 'pullrequest_show', repo_name=self.db_repo_name,
306 'pullrequest_show', repo_name=self.db_repo_name,
307 pull_request_id=pull_request_id))
307 pull_request_id=pull_request_id))
308
308
309 versions = pull_request_display_obj.versions()
309 versions = pull_request_display_obj.versions()
310 # used to store per-commit range diffs
310 # used to store per-commit range diffs
311 c.changes = collections.OrderedDict()
311 c.changes = collections.OrderedDict()
312 c.range_diff_on = self.request.GET.get('range-diff') == "1"
312 c.range_diff_on = self.request.GET.get('range-diff') == "1"
313
313
314 c.at_version = at_version
314 c.at_version = at_version
315 c.at_version_num = (at_version
315 c.at_version_num = (at_version
316 if at_version and at_version != 'latest'
316 if at_version and at_version != 'latest'
317 else None)
317 else None)
318 c.at_version_pos = ChangesetComment.get_index_from_version(
318 c.at_version_pos = ChangesetComment.get_index_from_version(
319 c.at_version_num, versions)
319 c.at_version_num, versions)
320
320
321 (prev_pull_request_latest,
321 (prev_pull_request_latest,
322 prev_pull_request_at_ver,
322 prev_pull_request_at_ver,
323 prev_pull_request_display_obj,
323 prev_pull_request_display_obj,
324 prev_at_version) = PullRequestModel().get_pr_version(
324 prev_at_version) = PullRequestModel().get_pr_version(
325 pull_request_id, version=from_version)
325 pull_request_id, version=from_version)
326
326
327 c.from_version = prev_at_version
327 c.from_version = prev_at_version
328 c.from_version_num = (prev_at_version
328 c.from_version_num = (prev_at_version
329 if prev_at_version and prev_at_version != 'latest'
329 if prev_at_version and prev_at_version != 'latest'
330 else None)
330 else None)
331 c.from_version_pos = ChangesetComment.get_index_from_version(
331 c.from_version_pos = ChangesetComment.get_index_from_version(
332 c.from_version_num, versions)
332 c.from_version_num, versions)
333
333
334 # define if we're in COMPARE mode or VIEW at version mode
334 # define if we're in COMPARE mode or VIEW at version mode
335 compare = at_version != prev_at_version
335 compare = at_version != prev_at_version
336
336
337 # pull_requests repo_name we opened it against
337 # pull_requests repo_name we opened it against
338 # ie. target_repo must match
338 # ie. target_repo must match
339 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
339 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
340 raise HTTPNotFound()
340 raise HTTPNotFound()
341
341
342 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
342 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
343 pull_request_at_ver)
343 pull_request_at_ver)
344
344
345 c.pull_request = pull_request_display_obj
345 c.pull_request = pull_request_display_obj
346 c.renderer = pull_request_at_ver.description_renderer or c.renderer
346 c.renderer = pull_request_at_ver.description_renderer or c.renderer
347 c.pull_request_latest = pull_request_latest
347 c.pull_request_latest = pull_request_latest
348
348
349 if compare or (at_version and not at_version == 'latest'):
349 if compare or (at_version and not at_version == 'latest'):
350 c.allowed_to_change_status = False
350 c.allowed_to_change_status = False
351 c.allowed_to_update = False
351 c.allowed_to_update = False
352 c.allowed_to_merge = False
352 c.allowed_to_merge = False
353 c.allowed_to_delete = False
353 c.allowed_to_delete = False
354 c.allowed_to_comment = False
354 c.allowed_to_comment = False
355 c.allowed_to_close = False
355 c.allowed_to_close = False
356 else:
356 else:
357 can_change_status = PullRequestModel().check_user_change_status(
357 can_change_status = PullRequestModel().check_user_change_status(
358 pull_request_at_ver, self._rhodecode_user)
358 pull_request_at_ver, self._rhodecode_user)
359 c.allowed_to_change_status = can_change_status and not pr_closed
359 c.allowed_to_change_status = can_change_status and not pr_closed
360
360
361 c.allowed_to_update = PullRequestModel().check_user_update(
361 c.allowed_to_update = PullRequestModel().check_user_update(
362 pull_request_latest, self._rhodecode_user) and not pr_closed
362 pull_request_latest, self._rhodecode_user) and not pr_closed
363 c.allowed_to_merge = PullRequestModel().check_user_merge(
363 c.allowed_to_merge = PullRequestModel().check_user_merge(
364 pull_request_latest, self._rhodecode_user) and not pr_closed
364 pull_request_latest, self._rhodecode_user) and not pr_closed
365 c.allowed_to_delete = PullRequestModel().check_user_delete(
365 c.allowed_to_delete = PullRequestModel().check_user_delete(
366 pull_request_latest, self._rhodecode_user) and not pr_closed
366 pull_request_latest, self._rhodecode_user) and not pr_closed
367 c.allowed_to_comment = not pr_closed
367 c.allowed_to_comment = not pr_closed
368 c.allowed_to_close = c.allowed_to_merge and not pr_closed
368 c.allowed_to_close = c.allowed_to_merge and not pr_closed
369
369
370 c.forbid_adding_reviewers = False
370 c.forbid_adding_reviewers = False
371 c.forbid_author_to_review = False
371 c.forbid_author_to_review = False
372 c.forbid_commit_author_to_review = False
372 c.forbid_commit_author_to_review = False
373
373
374 if pull_request_latest.reviewer_data and \
374 if pull_request_latest.reviewer_data and \
375 'rules' in pull_request_latest.reviewer_data:
375 'rules' in pull_request_latest.reviewer_data:
376 rules = pull_request_latest.reviewer_data['rules'] or {}
376 rules = pull_request_latest.reviewer_data['rules'] or {}
377 try:
377 try:
378 c.forbid_adding_reviewers = rules.get(
378 c.forbid_adding_reviewers = rules.get(
379 'forbid_adding_reviewers')
379 'forbid_adding_reviewers')
380 c.forbid_author_to_review = rules.get(
380 c.forbid_author_to_review = rules.get(
381 'forbid_author_to_review')
381 'forbid_author_to_review')
382 c.forbid_commit_author_to_review = rules.get(
382 c.forbid_commit_author_to_review = rules.get(
383 'forbid_commit_author_to_review')
383 'forbid_commit_author_to_review')
384 except Exception:
384 except Exception:
385 pass
385 pass
386
386
387 # check merge capabilities
387 # check merge capabilities
388 _merge_check = MergeCheck.validate(
388 _merge_check = MergeCheck.validate(
389 pull_request_latest, auth_user=self._rhodecode_user,
389 pull_request_latest, auth_user=self._rhodecode_user,
390 translator=self.request.translate,
390 translator=self.request.translate,
391 force_shadow_repo_refresh=force_refresh)
391 force_shadow_repo_refresh=force_refresh)
392 c.pr_merge_errors = _merge_check.error_details
392 c.pr_merge_errors = _merge_check.error_details
393 c.pr_merge_possible = not _merge_check.failed
393 c.pr_merge_possible = not _merge_check.failed
394 c.pr_merge_message = _merge_check.merge_msg
394 c.pr_merge_message = _merge_check.merge_msg
395
395
396 c.pr_merge_info = MergeCheck.get_merge_conditions(
396 c.pr_merge_info = MergeCheck.get_merge_conditions(
397 pull_request_latest, translator=self.request.translate)
397 pull_request_latest, translator=self.request.translate)
398
398
399 c.pull_request_review_status = _merge_check.review_status
399 c.pull_request_review_status = _merge_check.review_status
400 if merge_checks:
400 if merge_checks:
401 self.request.override_renderer = \
401 self.request.override_renderer = \
402 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
402 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
403 return self._get_template_context(c)
403 return self._get_template_context(c)
404
404
405 comments_model = CommentsModel()
405 comments_model = CommentsModel()
406
406
407 # reviewers and statuses
407 # reviewers and statuses
408 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
408 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
409 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
409 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
410
410
411 # GENERAL COMMENTS with versions #
411 # GENERAL COMMENTS with versions #
412 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
412 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
413 q = q.order_by(ChangesetComment.comment_id.asc())
413 q = q.order_by(ChangesetComment.comment_id.asc())
414 general_comments = q
414 general_comments = q
415
415
416 # pick comments we want to render at current version
416 # pick comments we want to render at current version
417 c.comment_versions = comments_model.aggregate_comments(
417 c.comment_versions = comments_model.aggregate_comments(
418 general_comments, versions, c.at_version_num)
418 general_comments, versions, c.at_version_num)
419 c.comments = c.comment_versions[c.at_version_num]['until']
419 c.comments = c.comment_versions[c.at_version_num]['until']
420
420
421 # INLINE COMMENTS with versions #
421 # INLINE COMMENTS with versions #
422 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
422 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
423 q = q.order_by(ChangesetComment.comment_id.asc())
423 q = q.order_by(ChangesetComment.comment_id.asc())
424 inline_comments = q
424 inline_comments = q
425
425
426 c.inline_versions = comments_model.aggregate_comments(
426 c.inline_versions = comments_model.aggregate_comments(
427 inline_comments, versions, c.at_version_num, inline=True)
427 inline_comments, versions, c.at_version_num, inline=True)
428
428
429 # TODOs
430 c.unresolved_comments = CommentsModel() \
431 .get_pull_request_unresolved_todos(pull_request)
432 c.resolved_comments = CommentsModel() \
433 .get_pull_request_resolved_todos(pull_request)
434
429 # inject latest version
435 # inject latest version
430 latest_ver = PullRequest.get_pr_display_object(
436 latest_ver = PullRequest.get_pr_display_object(
431 pull_request_latest, pull_request_latest)
437 pull_request_latest, pull_request_latest)
432
438
433 c.versions = versions + [latest_ver]
439 c.versions = versions + [latest_ver]
434
440
435 # if we use version, then do not show later comments
441 # if we use version, then do not show later comments
436 # than current version
442 # than current version
437 display_inline_comments = collections.defaultdict(
443 display_inline_comments = collections.defaultdict(
438 lambda: collections.defaultdict(list))
444 lambda: collections.defaultdict(list))
439 for co in inline_comments:
445 for co in inline_comments:
440 if c.at_version_num:
446 if c.at_version_num:
441 # pick comments that are at least UPTO given version, so we
447 # pick comments that are at least UPTO given version, so we
442 # don't render comments for higher version
448 # don't render comments for higher version
443 should_render = co.pull_request_version_id and \
449 should_render = co.pull_request_version_id and \
444 co.pull_request_version_id <= c.at_version_num
450 co.pull_request_version_id <= c.at_version_num
445 else:
451 else:
446 # showing all, for 'latest'
452 # showing all, for 'latest'
447 should_render = True
453 should_render = True
448
454
449 if should_render:
455 if should_render:
450 display_inline_comments[co.f_path][co.line_no].append(co)
456 display_inline_comments[co.f_path][co.line_no].append(co)
451
457
452 # load diff data into template context, if we use compare mode then
458 # load diff data into template context, if we use compare mode then
453 # diff is calculated based on changes between versions of PR
459 # diff is calculated based on changes between versions of PR
454
460
455 source_repo = pull_request_at_ver.source_repo
461 source_repo = pull_request_at_ver.source_repo
456 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
462 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
457
463
458 target_repo = pull_request_at_ver.target_repo
464 target_repo = pull_request_at_ver.target_repo
459 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
465 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
460
466
461 if compare:
467 if compare:
462 # in compare switch the diff base to latest commit from prev version
468 # in compare switch the diff base to latest commit from prev version
463 target_ref_id = prev_pull_request_display_obj.revisions[0]
469 target_ref_id = prev_pull_request_display_obj.revisions[0]
464
470
465 # despite opening commits for bookmarks/branches/tags, we always
471 # despite opening commits for bookmarks/branches/tags, we always
466 # convert this to rev to prevent changes after bookmark or branch change
472 # convert this to rev to prevent changes after bookmark or branch change
467 c.source_ref_type = 'rev'
473 c.source_ref_type = 'rev'
468 c.source_ref = source_ref_id
474 c.source_ref = source_ref_id
469
475
470 c.target_ref_type = 'rev'
476 c.target_ref_type = 'rev'
471 c.target_ref = target_ref_id
477 c.target_ref = target_ref_id
472
478
473 c.source_repo = source_repo
479 c.source_repo = source_repo
474 c.target_repo = target_repo
480 c.target_repo = target_repo
475
481
476 c.commit_ranges = []
482 c.commit_ranges = []
477 source_commit = EmptyCommit()
483 source_commit = EmptyCommit()
478 target_commit = EmptyCommit()
484 target_commit = EmptyCommit()
479 c.missing_requirements = False
485 c.missing_requirements = False
480
486
481 source_scm = source_repo.scm_instance()
487 source_scm = source_repo.scm_instance()
482 target_scm = target_repo.scm_instance()
488 target_scm = target_repo.scm_instance()
483
489
484 shadow_scm = None
490 shadow_scm = None
485 try:
491 try:
486 shadow_scm = pull_request_latest.get_shadow_repo()
492 shadow_scm = pull_request_latest.get_shadow_repo()
487 except Exception:
493 except Exception:
488 log.debug('Failed to get shadow repo', exc_info=True)
494 log.debug('Failed to get shadow repo', exc_info=True)
489 # try first the existing source_repo, and then shadow
495 # try first the existing source_repo, and then shadow
490 # repo if we can obtain one
496 # repo if we can obtain one
491 commits_source_repo = source_scm or shadow_scm
497 commits_source_repo = source_scm or shadow_scm
492
498
493 c.commits_source_repo = commits_source_repo
499 c.commits_source_repo = commits_source_repo
494 c.ancestor = None # set it to None, to hide it from PR view
500 c.ancestor = None # set it to None, to hide it from PR view
495
501
496 # empty version means latest, so we keep this to prevent
502 # empty version means latest, so we keep this to prevent
497 # double caching
503 # double caching
498 version_normalized = version or 'latest'
504 version_normalized = version or 'latest'
499 from_version_normalized = from_version or 'latest'
505 from_version_normalized = from_version or 'latest'
500
506
501 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
507 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
502 cache_file_path = diff_cache_exist(
508 cache_file_path = diff_cache_exist(
503 cache_path, 'pull_request', pull_request_id, version_normalized,
509 cache_path, 'pull_request', pull_request_id, version_normalized,
504 from_version_normalized, source_ref_id, target_ref_id,
510 from_version_normalized, source_ref_id, target_ref_id,
505 hide_whitespace_changes, diff_context, c.fulldiff)
511 hide_whitespace_changes, diff_context, c.fulldiff)
506
512
507 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
513 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
508 force_recache = self.get_recache_flag()
514 force_recache = self.get_recache_flag()
509
515
510 cached_diff = None
516 cached_diff = None
511 if caching_enabled:
517 if caching_enabled:
512 cached_diff = load_cached_diff(cache_file_path)
518 cached_diff = load_cached_diff(cache_file_path)
513
519
514 has_proper_commit_cache = (
520 has_proper_commit_cache = (
515 cached_diff and cached_diff.get('commits')
521 cached_diff and cached_diff.get('commits')
516 and len(cached_diff.get('commits', [])) == 5
522 and len(cached_diff.get('commits', [])) == 5
517 and cached_diff.get('commits')[0]
523 and cached_diff.get('commits')[0]
518 and cached_diff.get('commits')[3])
524 and cached_diff.get('commits')[3])
519
525
520 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
526 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
521 diff_commit_cache = \
527 diff_commit_cache = \
522 (ancestor_commit, commit_cache, missing_requirements,
528 (ancestor_commit, commit_cache, missing_requirements,
523 source_commit, target_commit) = cached_diff['commits']
529 source_commit, target_commit) = cached_diff['commits']
524 else:
530 else:
525 diff_commit_cache = \
531 diff_commit_cache = \
526 (ancestor_commit, commit_cache, missing_requirements,
532 (ancestor_commit, commit_cache, missing_requirements,
527 source_commit, target_commit) = self.get_commits(
533 source_commit, target_commit) = self.get_commits(
528 commits_source_repo,
534 commits_source_repo,
529 pull_request_at_ver,
535 pull_request_at_ver,
530 source_commit,
536 source_commit,
531 source_ref_id,
537 source_ref_id,
532 source_scm,
538 source_scm,
533 target_commit,
539 target_commit,
534 target_ref_id,
540 target_ref_id,
535 target_scm)
541 target_scm)
536
542
537 # register our commit range
543 # register our commit range
538 for comm in commit_cache.values():
544 for comm in commit_cache.values():
539 c.commit_ranges.append(comm)
545 c.commit_ranges.append(comm)
540
546
541 c.missing_requirements = missing_requirements
547 c.missing_requirements = missing_requirements
542 c.ancestor_commit = ancestor_commit
548 c.ancestor_commit = ancestor_commit
543 c.statuses = source_repo.statuses(
549 c.statuses = source_repo.statuses(
544 [x.raw_id for x in c.commit_ranges])
550 [x.raw_id for x in c.commit_ranges])
545
551
546 # auto collapse if we have more than limit
552 # auto collapse if we have more than limit
547 collapse_limit = diffs.DiffProcessor._collapse_commits_over
553 collapse_limit = diffs.DiffProcessor._collapse_commits_over
548 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
554 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
549 c.compare_mode = compare
555 c.compare_mode = compare
550
556
551 # diff_limit is the old behavior, will cut off the whole diff
557 # diff_limit is the old behavior, will cut off the whole diff
552 # if the limit is applied otherwise will just hide the
558 # if the limit is applied otherwise will just hide the
553 # big files from the front-end
559 # big files from the front-end
554 diff_limit = c.visual.cut_off_limit_diff
560 diff_limit = c.visual.cut_off_limit_diff
555 file_limit = c.visual.cut_off_limit_file
561 file_limit = c.visual.cut_off_limit_file
556
562
557 c.missing_commits = False
563 c.missing_commits = False
558 if (c.missing_requirements
564 if (c.missing_requirements
559 or isinstance(source_commit, EmptyCommit)
565 or isinstance(source_commit, EmptyCommit)
560 or source_commit == target_commit):
566 or source_commit == target_commit):
561
567
562 c.missing_commits = True
568 c.missing_commits = True
563 else:
569 else:
564 c.inline_comments = display_inline_comments
570 c.inline_comments = display_inline_comments
565
571
566 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
572 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
567 if not force_recache and has_proper_diff_cache:
573 if not force_recache and has_proper_diff_cache:
568 c.diffset = cached_diff['diff']
574 c.diffset = cached_diff['diff']
569 (ancestor_commit, commit_cache, missing_requirements,
575 (ancestor_commit, commit_cache, missing_requirements,
570 source_commit, target_commit) = cached_diff['commits']
576 source_commit, target_commit) = cached_diff['commits']
571 else:
577 else:
572 c.diffset = self._get_diffset(
578 c.diffset = self._get_diffset(
573 c.source_repo.repo_name, commits_source_repo,
579 c.source_repo.repo_name, commits_source_repo,
574 source_ref_id, target_ref_id,
580 source_ref_id, target_ref_id,
575 target_commit, source_commit,
581 target_commit, source_commit,
576 diff_limit, file_limit, c.fulldiff,
582 diff_limit, file_limit, c.fulldiff,
577 hide_whitespace_changes, diff_context)
583 hide_whitespace_changes, diff_context)
578
584
579 # save cached diff
585 # save cached diff
580 if caching_enabled:
586 if caching_enabled:
581 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
587 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
582
588
583 c.limited_diff = c.diffset.limited_diff
589 c.limited_diff = c.diffset.limited_diff
584
590
585 # calculate removed files that are bound to comments
591 # calculate removed files that are bound to comments
586 comment_deleted_files = [
592 comment_deleted_files = [
587 fname for fname in display_inline_comments
593 fname for fname in display_inline_comments
588 if fname not in c.diffset.file_stats]
594 if fname not in c.diffset.file_stats]
589
595
590 c.deleted_files_comments = collections.defaultdict(dict)
596 c.deleted_files_comments = collections.defaultdict(dict)
591 for fname, per_line_comments in display_inline_comments.items():
597 for fname, per_line_comments in display_inline_comments.items():
592 if fname in comment_deleted_files:
598 if fname in comment_deleted_files:
593 c.deleted_files_comments[fname]['stats'] = 0
599 c.deleted_files_comments[fname]['stats'] = 0
594 c.deleted_files_comments[fname]['comments'] = list()
600 c.deleted_files_comments[fname]['comments'] = list()
595 for lno, comments in per_line_comments.items():
601 for lno, comments in per_line_comments.items():
596 c.deleted_files_comments[fname]['comments'].extend(comments)
602 c.deleted_files_comments[fname]['comments'].extend(comments)
597
603
598 # maybe calculate the range diff
604 # maybe calculate the range diff
599 if c.range_diff_on:
605 if c.range_diff_on:
600 # TODO(marcink): set whitespace/context
606 # TODO(marcink): set whitespace/context
601 context_lcl = 3
607 context_lcl = 3
602 ign_whitespace_lcl = False
608 ign_whitespace_lcl = False
603
609
604 for commit in c.commit_ranges:
610 for commit in c.commit_ranges:
605 commit2 = commit
611 commit2 = commit
606 commit1 = commit.first_parent
612 commit1 = commit.first_parent
607
613
608 range_diff_cache_file_path = diff_cache_exist(
614 range_diff_cache_file_path = diff_cache_exist(
609 cache_path, 'diff', commit.raw_id,
615 cache_path, 'diff', commit.raw_id,
610 ign_whitespace_lcl, context_lcl, c.fulldiff)
616 ign_whitespace_lcl, context_lcl, c.fulldiff)
611
617
612 cached_diff = None
618 cached_diff = None
613 if caching_enabled:
619 if caching_enabled:
614 cached_diff = load_cached_diff(range_diff_cache_file_path)
620 cached_diff = load_cached_diff(range_diff_cache_file_path)
615
621
616 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
622 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
617 if not force_recache and has_proper_diff_cache:
623 if not force_recache and has_proper_diff_cache:
618 diffset = cached_diff['diff']
624 diffset = cached_diff['diff']
619 else:
625 else:
620 diffset = self._get_range_diffset(
626 diffset = self._get_range_diffset(
621 source_scm, source_repo,
627 source_scm, source_repo,
622 commit1, commit2, diff_limit, file_limit,
628 commit1, commit2, diff_limit, file_limit,
623 c.fulldiff, ign_whitespace_lcl, context_lcl
629 c.fulldiff, ign_whitespace_lcl, context_lcl
624 )
630 )
625
631
626 # save cached diff
632 # save cached diff
627 if caching_enabled:
633 if caching_enabled:
628 cache_diff(range_diff_cache_file_path, diffset, None)
634 cache_diff(range_diff_cache_file_path, diffset, None)
629
635
630 c.changes[commit.raw_id] = diffset
636 c.changes[commit.raw_id] = diffset
631
637
632 # this is a hack to properly display links, when creating PR, the
638 # this is a hack to properly display links, when creating PR, the
633 # compare view and others uses different notation, and
639 # compare view and others uses different notation, and
634 # compare_commits.mako renders links based on the target_repo.
640 # compare_commits.mako renders links based on the target_repo.
635 # We need to swap that here to generate it properly on the html side
641 # We need to swap that here to generate it properly on the html side
636 c.target_repo = c.source_repo
642 c.target_repo = c.source_repo
637
643
638 c.commit_statuses = ChangesetStatus.STATUSES
644 c.commit_statuses = ChangesetStatus.STATUSES
639
645
640 c.show_version_changes = not pr_closed
646 c.show_version_changes = not pr_closed
641 if c.show_version_changes:
647 if c.show_version_changes:
642 cur_obj = pull_request_at_ver
648 cur_obj = pull_request_at_ver
643 prev_obj = prev_pull_request_at_ver
649 prev_obj = prev_pull_request_at_ver
644
650
645 old_commit_ids = prev_obj.revisions
651 old_commit_ids = prev_obj.revisions
646 new_commit_ids = cur_obj.revisions
652 new_commit_ids = cur_obj.revisions
647 commit_changes = PullRequestModel()._calculate_commit_id_changes(
653 commit_changes = PullRequestModel()._calculate_commit_id_changes(
648 old_commit_ids, new_commit_ids)
654 old_commit_ids, new_commit_ids)
649 c.commit_changes_summary = commit_changes
655 c.commit_changes_summary = commit_changes
650
656
651 # calculate the diff for commits between versions
657 # calculate the diff for commits between versions
652 c.commit_changes = []
658 c.commit_changes = []
653 mark = lambda cs, fw: list(
659 mark = lambda cs, fw: list(
654 h.itertools.izip_longest([], cs, fillvalue=fw))
660 h.itertools.izip_longest([], cs, fillvalue=fw))
655 for c_type, raw_id in mark(commit_changes.added, 'a') \
661 for c_type, raw_id in mark(commit_changes.added, 'a') \
656 + mark(commit_changes.removed, 'r') \
662 + mark(commit_changes.removed, 'r') \
657 + mark(commit_changes.common, 'c'):
663 + mark(commit_changes.common, 'c'):
658
664
659 if raw_id in commit_cache:
665 if raw_id in commit_cache:
660 commit = commit_cache[raw_id]
666 commit = commit_cache[raw_id]
661 else:
667 else:
662 try:
668 try:
663 commit = commits_source_repo.get_commit(raw_id)
669 commit = commits_source_repo.get_commit(raw_id)
664 except CommitDoesNotExistError:
670 except CommitDoesNotExistError:
665 # in case we fail extracting still use "dummy" commit
671 # in case we fail extracting still use "dummy" commit
666 # for display in commit diff
672 # for display in commit diff
667 commit = h.AttributeDict(
673 commit = h.AttributeDict(
668 {'raw_id': raw_id,
674 {'raw_id': raw_id,
669 'message': 'EMPTY or MISSING COMMIT'})
675 'message': 'EMPTY or MISSING COMMIT'})
670 c.commit_changes.append([c_type, commit])
676 c.commit_changes.append([c_type, commit])
671
677
672 # current user review statuses for each version
678 # current user review statuses for each version
673 c.review_versions = {}
679 c.review_versions = {}
674 if self._rhodecode_user.user_id in allowed_reviewers:
680 if self._rhodecode_user.user_id in allowed_reviewers:
675 for co in general_comments:
681 for co in general_comments:
676 if co.author.user_id == self._rhodecode_user.user_id:
682 if co.author.user_id == self._rhodecode_user.user_id:
677 status = co.status_change
683 status = co.status_change
678 if status:
684 if status:
679 _ver_pr = status[0].comment.pull_request_version_id
685 _ver_pr = status[0].comment.pull_request_version_id
680 c.review_versions[_ver_pr] = status[0]
686 c.review_versions[_ver_pr] = status[0]
681
687
682 return self._get_template_context(c)
688 return self._get_template_context(c)
683
689
684 def get_commits(
690 def get_commits(
685 self, commits_source_repo, pull_request_at_ver, source_commit,
691 self, commits_source_repo, pull_request_at_ver, source_commit,
686 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
692 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
687 commit_cache = collections.OrderedDict()
693 commit_cache = collections.OrderedDict()
688 missing_requirements = False
694 missing_requirements = False
689 try:
695 try:
690 pre_load = ["author", "date", "message", "branch", "parents"]
696 pre_load = ["author", "date", "message", "branch", "parents"]
691 show_revs = pull_request_at_ver.revisions
697 show_revs = pull_request_at_ver.revisions
692 for rev in show_revs:
698 for rev in show_revs:
693 comm = commits_source_repo.get_commit(
699 comm = commits_source_repo.get_commit(
694 commit_id=rev, pre_load=pre_load)
700 commit_id=rev, pre_load=pre_load)
695 commit_cache[comm.raw_id] = comm
701 commit_cache[comm.raw_id] = comm
696
702
697 # Order here matters, we first need to get target, and then
703 # Order here matters, we first need to get target, and then
698 # the source
704 # the source
699 target_commit = commits_source_repo.get_commit(
705 target_commit = commits_source_repo.get_commit(
700 commit_id=safe_str(target_ref_id))
706 commit_id=safe_str(target_ref_id))
701
707
702 source_commit = commits_source_repo.get_commit(
708 source_commit = commits_source_repo.get_commit(
703 commit_id=safe_str(source_ref_id))
709 commit_id=safe_str(source_ref_id))
704 except CommitDoesNotExistError:
710 except CommitDoesNotExistError:
705 log.warning(
711 log.warning(
706 'Failed to get commit from `{}` repo'.format(
712 'Failed to get commit from `{}` repo'.format(
707 commits_source_repo), exc_info=True)
713 commits_source_repo), exc_info=True)
708 except RepositoryRequirementError:
714 except RepositoryRequirementError:
709 log.warning(
715 log.warning(
710 'Failed to get all required data from repo', exc_info=True)
716 'Failed to get all required data from repo', exc_info=True)
711 missing_requirements = True
717 missing_requirements = True
712 ancestor_commit = None
718 ancestor_commit = None
713 try:
719 try:
714 ancestor_id = source_scm.get_common_ancestor(
720 ancestor_id = source_scm.get_common_ancestor(
715 source_commit.raw_id, target_commit.raw_id, target_scm)
721 source_commit.raw_id, target_commit.raw_id, target_scm)
716 ancestor_commit = source_scm.get_commit(ancestor_id)
722 ancestor_commit = source_scm.get_commit(ancestor_id)
717 except Exception:
723 except Exception:
718 ancestor_commit = None
724 ancestor_commit = None
719 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
725 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
720
726
721 def assure_not_empty_repo(self):
727 def assure_not_empty_repo(self):
722 _ = self.request.translate
728 _ = self.request.translate
723
729
724 try:
730 try:
725 self.db_repo.scm_instance().get_commit()
731 self.db_repo.scm_instance().get_commit()
726 except EmptyRepositoryError:
732 except EmptyRepositoryError:
727 h.flash(h.literal(_('There are no commits yet')),
733 h.flash(h.literal(_('There are no commits yet')),
728 category='warning')
734 category='warning')
729 raise HTTPFound(
735 raise HTTPFound(
730 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
736 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
731
737
732 @LoginRequired()
738 @LoginRequired()
733 @NotAnonymous()
739 @NotAnonymous()
734 @HasRepoPermissionAnyDecorator(
740 @HasRepoPermissionAnyDecorator(
735 'repository.read', 'repository.write', 'repository.admin')
741 'repository.read', 'repository.write', 'repository.admin')
736 @view_config(
742 @view_config(
737 route_name='pullrequest_new', request_method='GET',
743 route_name='pullrequest_new', request_method='GET',
738 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
744 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
739 def pull_request_new(self):
745 def pull_request_new(self):
740 _ = self.request.translate
746 _ = self.request.translate
741 c = self.load_default_context()
747 c = self.load_default_context()
742
748
743 self.assure_not_empty_repo()
749 self.assure_not_empty_repo()
744 source_repo = self.db_repo
750 source_repo = self.db_repo
745
751
746 commit_id = self.request.GET.get('commit')
752 commit_id = self.request.GET.get('commit')
747 branch_ref = self.request.GET.get('branch')
753 branch_ref = self.request.GET.get('branch')
748 bookmark_ref = self.request.GET.get('bookmark')
754 bookmark_ref = self.request.GET.get('bookmark')
749
755
750 try:
756 try:
751 source_repo_data = PullRequestModel().generate_repo_data(
757 source_repo_data = PullRequestModel().generate_repo_data(
752 source_repo, commit_id=commit_id,
758 source_repo, commit_id=commit_id,
753 branch=branch_ref, bookmark=bookmark_ref,
759 branch=branch_ref, bookmark=bookmark_ref,
754 translator=self.request.translate)
760 translator=self.request.translate)
755 except CommitDoesNotExistError as e:
761 except CommitDoesNotExistError as e:
756 log.exception(e)
762 log.exception(e)
757 h.flash(_('Commit does not exist'), 'error')
763 h.flash(_('Commit does not exist'), 'error')
758 raise HTTPFound(
764 raise HTTPFound(
759 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
765 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
760
766
761 default_target_repo = source_repo
767 default_target_repo = source_repo
762
768
763 if source_repo.parent and c.has_origin_repo_read_perm:
769 if source_repo.parent and c.has_origin_repo_read_perm:
764 parent_vcs_obj = source_repo.parent.scm_instance()
770 parent_vcs_obj = source_repo.parent.scm_instance()
765 if parent_vcs_obj and not parent_vcs_obj.is_empty():
771 if parent_vcs_obj and not parent_vcs_obj.is_empty():
766 # change default if we have a parent repo
772 # change default if we have a parent repo
767 default_target_repo = source_repo.parent
773 default_target_repo = source_repo.parent
768
774
769 target_repo_data = PullRequestModel().generate_repo_data(
775 target_repo_data = PullRequestModel().generate_repo_data(
770 default_target_repo, translator=self.request.translate)
776 default_target_repo, translator=self.request.translate)
771
777
772 selected_source_ref = source_repo_data['refs']['selected_ref']
778 selected_source_ref = source_repo_data['refs']['selected_ref']
773 title_source_ref = ''
779 title_source_ref = ''
774 if selected_source_ref:
780 if selected_source_ref:
775 title_source_ref = selected_source_ref.split(':', 2)[1]
781 title_source_ref = selected_source_ref.split(':', 2)[1]
776 c.default_title = PullRequestModel().generate_pullrequest_title(
782 c.default_title = PullRequestModel().generate_pullrequest_title(
777 source=source_repo.repo_name,
783 source=source_repo.repo_name,
778 source_ref=title_source_ref,
784 source_ref=title_source_ref,
779 target=default_target_repo.repo_name
785 target=default_target_repo.repo_name
780 )
786 )
781
787
782 c.default_repo_data = {
788 c.default_repo_data = {
783 'source_repo_name': source_repo.repo_name,
789 'source_repo_name': source_repo.repo_name,
784 'source_refs_json': json.dumps(source_repo_data),
790 'source_refs_json': json.dumps(source_repo_data),
785 'target_repo_name': default_target_repo.repo_name,
791 'target_repo_name': default_target_repo.repo_name,
786 'target_refs_json': json.dumps(target_repo_data),
792 'target_refs_json': json.dumps(target_repo_data),
787 }
793 }
788 c.default_source_ref = selected_source_ref
794 c.default_source_ref = selected_source_ref
789
795
790 return self._get_template_context(c)
796 return self._get_template_context(c)
791
797
792 @LoginRequired()
798 @LoginRequired()
793 @NotAnonymous()
799 @NotAnonymous()
794 @HasRepoPermissionAnyDecorator(
800 @HasRepoPermissionAnyDecorator(
795 'repository.read', 'repository.write', 'repository.admin')
801 'repository.read', 'repository.write', 'repository.admin')
796 @view_config(
802 @view_config(
797 route_name='pullrequest_repo_refs', request_method='GET',
803 route_name='pullrequest_repo_refs', request_method='GET',
798 renderer='json_ext', xhr=True)
804 renderer='json_ext', xhr=True)
799 def pull_request_repo_refs(self):
805 def pull_request_repo_refs(self):
800 self.load_default_context()
806 self.load_default_context()
801 target_repo_name = self.request.matchdict['target_repo_name']
807 target_repo_name = self.request.matchdict['target_repo_name']
802 repo = Repository.get_by_repo_name(target_repo_name)
808 repo = Repository.get_by_repo_name(target_repo_name)
803 if not repo:
809 if not repo:
804 raise HTTPNotFound()
810 raise HTTPNotFound()
805
811
806 target_perm = HasRepoPermissionAny(
812 target_perm = HasRepoPermissionAny(
807 'repository.read', 'repository.write', 'repository.admin')(
813 'repository.read', 'repository.write', 'repository.admin')(
808 target_repo_name)
814 target_repo_name)
809 if not target_perm:
815 if not target_perm:
810 raise HTTPNotFound()
816 raise HTTPNotFound()
811
817
812 return PullRequestModel().generate_repo_data(
818 return PullRequestModel().generate_repo_data(
813 repo, translator=self.request.translate)
819 repo, translator=self.request.translate)
814
820
815 @LoginRequired()
821 @LoginRequired()
816 @NotAnonymous()
822 @NotAnonymous()
817 @HasRepoPermissionAnyDecorator(
823 @HasRepoPermissionAnyDecorator(
818 'repository.read', 'repository.write', 'repository.admin')
824 'repository.read', 'repository.write', 'repository.admin')
819 @view_config(
825 @view_config(
820 route_name='pullrequest_repo_targets', request_method='GET',
826 route_name='pullrequest_repo_targets', request_method='GET',
821 renderer='json_ext', xhr=True)
827 renderer='json_ext', xhr=True)
822 def pullrequest_repo_targets(self):
828 def pullrequest_repo_targets(self):
823 _ = self.request.translate
829 _ = self.request.translate
824 filter_query = self.request.GET.get('query')
830 filter_query = self.request.GET.get('query')
825
831
826 # get the parents
832 # get the parents
827 parent_target_repos = []
833 parent_target_repos = []
828 if self.db_repo.parent:
834 if self.db_repo.parent:
829 parents_query = Repository.query() \
835 parents_query = Repository.query() \
830 .order_by(func.length(Repository.repo_name)) \
836 .order_by(func.length(Repository.repo_name)) \
831 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
837 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
832
838
833 if filter_query:
839 if filter_query:
834 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
840 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
835 parents_query = parents_query.filter(
841 parents_query = parents_query.filter(
836 Repository.repo_name.ilike(ilike_expression))
842 Repository.repo_name.ilike(ilike_expression))
837 parents = parents_query.limit(20).all()
843 parents = parents_query.limit(20).all()
838
844
839 for parent in parents:
845 for parent in parents:
840 parent_vcs_obj = parent.scm_instance()
846 parent_vcs_obj = parent.scm_instance()
841 if parent_vcs_obj and not parent_vcs_obj.is_empty():
847 if parent_vcs_obj and not parent_vcs_obj.is_empty():
842 parent_target_repos.append(parent)
848 parent_target_repos.append(parent)
843
849
844 # get other forks, and repo itself
850 # get other forks, and repo itself
845 query = Repository.query() \
851 query = Repository.query() \
846 .order_by(func.length(Repository.repo_name)) \
852 .order_by(func.length(Repository.repo_name)) \
847 .filter(
853 .filter(
848 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
854 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
849 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
855 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
850 ) \
856 ) \
851 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
857 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
852
858
853 if filter_query:
859 if filter_query:
854 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
860 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
855 query = query.filter(Repository.repo_name.ilike(ilike_expression))
861 query = query.filter(Repository.repo_name.ilike(ilike_expression))
856
862
857 limit = max(20 - len(parent_target_repos), 5) # not less then 5
863 limit = max(20 - len(parent_target_repos), 5) # not less then 5
858 target_repos = query.limit(limit).all()
864 target_repos = query.limit(limit).all()
859
865
860 all_target_repos = target_repos + parent_target_repos
866 all_target_repos = target_repos + parent_target_repos
861
867
862 repos = []
868 repos = []
863 # This checks permissions to the repositories
869 # This checks permissions to the repositories
864 for obj in ScmModel().get_repos(all_target_repos):
870 for obj in ScmModel().get_repos(all_target_repos):
865 repos.append({
871 repos.append({
866 'id': obj['name'],
872 'id': obj['name'],
867 'text': obj['name'],
873 'text': obj['name'],
868 'type': 'repo',
874 'type': 'repo',
869 'repo_id': obj['dbrepo']['repo_id'],
875 'repo_id': obj['dbrepo']['repo_id'],
870 'repo_type': obj['dbrepo']['repo_type'],
876 'repo_type': obj['dbrepo']['repo_type'],
871 'private': obj['dbrepo']['private'],
877 'private': obj['dbrepo']['private'],
872
878
873 })
879 })
874
880
875 data = {
881 data = {
876 'more': False,
882 'more': False,
877 'results': [{
883 'results': [{
878 'text': _('Repositories'),
884 'text': _('Repositories'),
879 'children': repos
885 'children': repos
880 }] if repos else []
886 }] if repos else []
881 }
887 }
882 return data
888 return data
883
889
884 @LoginRequired()
890 @LoginRequired()
885 @NotAnonymous()
891 @NotAnonymous()
886 @HasRepoPermissionAnyDecorator(
892 @HasRepoPermissionAnyDecorator(
887 'repository.read', 'repository.write', 'repository.admin')
893 'repository.read', 'repository.write', 'repository.admin')
888 @CSRFRequired()
894 @CSRFRequired()
889 @view_config(
895 @view_config(
890 route_name='pullrequest_create', request_method='POST',
896 route_name='pullrequest_create', request_method='POST',
891 renderer=None)
897 renderer=None)
892 def pull_request_create(self):
898 def pull_request_create(self):
893 _ = self.request.translate
899 _ = self.request.translate
894 self.assure_not_empty_repo()
900 self.assure_not_empty_repo()
895 self.load_default_context()
901 self.load_default_context()
896
902
897 controls = peppercorn.parse(self.request.POST.items())
903 controls = peppercorn.parse(self.request.POST.items())
898
904
899 try:
905 try:
900 form = PullRequestForm(
906 form = PullRequestForm(
901 self.request.translate, self.db_repo.repo_id)()
907 self.request.translate, self.db_repo.repo_id)()
902 _form = form.to_python(controls)
908 _form = form.to_python(controls)
903 except formencode.Invalid as errors:
909 except formencode.Invalid as errors:
904 if errors.error_dict.get('revisions'):
910 if errors.error_dict.get('revisions'):
905 msg = 'Revisions: %s' % errors.error_dict['revisions']
911 msg = 'Revisions: %s' % errors.error_dict['revisions']
906 elif errors.error_dict.get('pullrequest_title'):
912 elif errors.error_dict.get('pullrequest_title'):
907 msg = errors.error_dict.get('pullrequest_title')
913 msg = errors.error_dict.get('pullrequest_title')
908 else:
914 else:
909 msg = _('Error creating pull request: {}').format(errors)
915 msg = _('Error creating pull request: {}').format(errors)
910 log.exception(msg)
916 log.exception(msg)
911 h.flash(msg, 'error')
917 h.flash(msg, 'error')
912
918
913 # would rather just go back to form ...
919 # would rather just go back to form ...
914 raise HTTPFound(
920 raise HTTPFound(
915 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
921 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
916
922
917 source_repo = _form['source_repo']
923 source_repo = _form['source_repo']
918 source_ref = _form['source_ref']
924 source_ref = _form['source_ref']
919 target_repo = _form['target_repo']
925 target_repo = _form['target_repo']
920 target_ref = _form['target_ref']
926 target_ref = _form['target_ref']
921 commit_ids = _form['revisions'][::-1]
927 commit_ids = _form['revisions'][::-1]
922
928
923 # find the ancestor for this pr
929 # find the ancestor for this pr
924 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
930 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
925 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
931 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
926
932
927 if not (source_db_repo or target_db_repo):
933 if not (source_db_repo or target_db_repo):
928 h.flash(_('source_repo or target repo not found'), category='error')
934 h.flash(_('source_repo or target repo not found'), category='error')
929 raise HTTPFound(
935 raise HTTPFound(
930 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
936 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
931
937
932 # re-check permissions again here
938 # re-check permissions again here
933 # source_repo we must have read permissions
939 # source_repo we must have read permissions
934
940
935 source_perm = HasRepoPermissionAny(
941 source_perm = HasRepoPermissionAny(
936 'repository.read', 'repository.write', 'repository.admin')(
942 'repository.read', 'repository.write', 'repository.admin')(
937 source_db_repo.repo_name)
943 source_db_repo.repo_name)
938 if not source_perm:
944 if not source_perm:
939 msg = _('Not Enough permissions to source repo `{}`.'.format(
945 msg = _('Not Enough permissions to source repo `{}`.'.format(
940 source_db_repo.repo_name))
946 source_db_repo.repo_name))
941 h.flash(msg, category='error')
947 h.flash(msg, category='error')
942 # copy the args back to redirect
948 # copy the args back to redirect
943 org_query = self.request.GET.mixed()
949 org_query = self.request.GET.mixed()
944 raise HTTPFound(
950 raise HTTPFound(
945 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
951 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
946 _query=org_query))
952 _query=org_query))
947
953
948 # target repo we must have read permissions, and also later on
954 # target repo we must have read permissions, and also later on
949 # we want to check branch permissions here
955 # we want to check branch permissions here
950 target_perm = HasRepoPermissionAny(
956 target_perm = HasRepoPermissionAny(
951 'repository.read', 'repository.write', 'repository.admin')(
957 'repository.read', 'repository.write', 'repository.admin')(
952 target_db_repo.repo_name)
958 target_db_repo.repo_name)
953 if not target_perm:
959 if not target_perm:
954 msg = _('Not Enough permissions to target repo `{}`.'.format(
960 msg = _('Not Enough permissions to target repo `{}`.'.format(
955 target_db_repo.repo_name))
961 target_db_repo.repo_name))
956 h.flash(msg, category='error')
962 h.flash(msg, category='error')
957 # copy the args back to redirect
963 # copy the args back to redirect
958 org_query = self.request.GET.mixed()
964 org_query = self.request.GET.mixed()
959 raise HTTPFound(
965 raise HTTPFound(
960 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
966 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
961 _query=org_query))
967 _query=org_query))
962
968
963 source_scm = source_db_repo.scm_instance()
969 source_scm = source_db_repo.scm_instance()
964 target_scm = target_db_repo.scm_instance()
970 target_scm = target_db_repo.scm_instance()
965
971
966 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
972 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
967 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
973 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
968
974
969 ancestor = source_scm.get_common_ancestor(
975 ancestor = source_scm.get_common_ancestor(
970 source_commit.raw_id, target_commit.raw_id, target_scm)
976 source_commit.raw_id, target_commit.raw_id, target_scm)
971
977
972 # recalculate target ref based on ancestor
978 # recalculate target ref based on ancestor
973 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
979 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
974 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
980 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
975
981
976 get_default_reviewers_data, validate_default_reviewers = \
982 get_default_reviewers_data, validate_default_reviewers = \
977 PullRequestModel().get_reviewer_functions()
983 PullRequestModel().get_reviewer_functions()
978
984
979 # recalculate reviewers logic, to make sure we can validate this
985 # recalculate reviewers logic, to make sure we can validate this
980 reviewer_rules = get_default_reviewers_data(
986 reviewer_rules = get_default_reviewers_data(
981 self._rhodecode_db_user, source_db_repo,
987 self._rhodecode_db_user, source_db_repo,
982 source_commit, target_db_repo, target_commit)
988 source_commit, target_db_repo, target_commit)
983
989
984 given_reviewers = _form['review_members']
990 given_reviewers = _form['review_members']
985 reviewers = validate_default_reviewers(
991 reviewers = validate_default_reviewers(
986 given_reviewers, reviewer_rules)
992 given_reviewers, reviewer_rules)
987
993
988 pullrequest_title = _form['pullrequest_title']
994 pullrequest_title = _form['pullrequest_title']
989 title_source_ref = source_ref.split(':', 2)[1]
995 title_source_ref = source_ref.split(':', 2)[1]
990 if not pullrequest_title:
996 if not pullrequest_title:
991 pullrequest_title = PullRequestModel().generate_pullrequest_title(
997 pullrequest_title = PullRequestModel().generate_pullrequest_title(
992 source=source_repo,
998 source=source_repo,
993 source_ref=title_source_ref,
999 source_ref=title_source_ref,
994 target=target_repo
1000 target=target_repo
995 )
1001 )
996
1002
997 description = _form['pullrequest_desc']
1003 description = _form['pullrequest_desc']
998 description_renderer = _form['description_renderer']
1004 description_renderer = _form['description_renderer']
999
1005
1000 try:
1006 try:
1001 pull_request = PullRequestModel().create(
1007 pull_request = PullRequestModel().create(
1002 created_by=self._rhodecode_user.user_id,
1008 created_by=self._rhodecode_user.user_id,
1003 source_repo=source_repo,
1009 source_repo=source_repo,
1004 source_ref=source_ref,
1010 source_ref=source_ref,
1005 target_repo=target_repo,
1011 target_repo=target_repo,
1006 target_ref=target_ref,
1012 target_ref=target_ref,
1007 revisions=commit_ids,
1013 revisions=commit_ids,
1008 reviewers=reviewers,
1014 reviewers=reviewers,
1009 title=pullrequest_title,
1015 title=pullrequest_title,
1010 description=description,
1016 description=description,
1011 description_renderer=description_renderer,
1017 description_renderer=description_renderer,
1012 reviewer_data=reviewer_rules,
1018 reviewer_data=reviewer_rules,
1013 auth_user=self._rhodecode_user
1019 auth_user=self._rhodecode_user
1014 )
1020 )
1015 Session().commit()
1021 Session().commit()
1016
1022
1017 h.flash(_('Successfully opened new pull request'),
1023 h.flash(_('Successfully opened new pull request'),
1018 category='success')
1024 category='success')
1019 except Exception:
1025 except Exception:
1020 msg = _('Error occurred during creation of this pull request.')
1026 msg = _('Error occurred during creation of this pull request.')
1021 log.exception(msg)
1027 log.exception(msg)
1022 h.flash(msg, category='error')
1028 h.flash(msg, category='error')
1023
1029
1024 # copy the args back to redirect
1030 # copy the args back to redirect
1025 org_query = self.request.GET.mixed()
1031 org_query = self.request.GET.mixed()
1026 raise HTTPFound(
1032 raise HTTPFound(
1027 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1033 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1028 _query=org_query))
1034 _query=org_query))
1029
1035
1030 raise HTTPFound(
1036 raise HTTPFound(
1031 h.route_path('pullrequest_show', repo_name=target_repo,
1037 h.route_path('pullrequest_show', repo_name=target_repo,
1032 pull_request_id=pull_request.pull_request_id))
1038 pull_request_id=pull_request.pull_request_id))
1033
1039
1034 @LoginRequired()
1040 @LoginRequired()
1035 @NotAnonymous()
1041 @NotAnonymous()
1036 @HasRepoPermissionAnyDecorator(
1042 @HasRepoPermissionAnyDecorator(
1037 'repository.read', 'repository.write', 'repository.admin')
1043 'repository.read', 'repository.write', 'repository.admin')
1038 @CSRFRequired()
1044 @CSRFRequired()
1039 @view_config(
1045 @view_config(
1040 route_name='pullrequest_update', request_method='POST',
1046 route_name='pullrequest_update', request_method='POST',
1041 renderer='json_ext')
1047 renderer='json_ext')
1042 def pull_request_update(self):
1048 def pull_request_update(self):
1043 pull_request = PullRequest.get_or_404(
1049 pull_request = PullRequest.get_or_404(
1044 self.request.matchdict['pull_request_id'])
1050 self.request.matchdict['pull_request_id'])
1045 _ = self.request.translate
1051 _ = self.request.translate
1046
1052
1047 self.load_default_context()
1053 self.load_default_context()
1048
1054
1049 if pull_request.is_closed():
1055 if pull_request.is_closed():
1050 log.debug('update: forbidden because pull request is closed')
1056 log.debug('update: forbidden because pull request is closed')
1051 msg = _(u'Cannot update closed pull requests.')
1057 msg = _(u'Cannot update closed pull requests.')
1052 h.flash(msg, category='error')
1058 h.flash(msg, category='error')
1053 return True
1059 return True
1054
1060
1055 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1061 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1056 log.debug('update: forbidden because pull request is in state %s',
1062 log.debug('update: forbidden because pull request is in state %s',
1057 pull_request.pull_request_state)
1063 pull_request.pull_request_state)
1058 msg = _(u'Cannot update pull requests in state other than `{}`. '
1064 msg = _(u'Cannot update pull requests in state other than `{}`. '
1059 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1065 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1060 pull_request.pull_request_state)
1066 pull_request.pull_request_state)
1061 h.flash(msg, category='error')
1067 h.flash(msg, category='error')
1062 return True
1068 return True
1063
1069
1064 # only owner or admin can update it
1070 # only owner or admin can update it
1065 allowed_to_update = PullRequestModel().check_user_update(
1071 allowed_to_update = PullRequestModel().check_user_update(
1066 pull_request, self._rhodecode_user)
1072 pull_request, self._rhodecode_user)
1067 if allowed_to_update:
1073 if allowed_to_update:
1068 controls = peppercorn.parse(self.request.POST.items())
1074 controls = peppercorn.parse(self.request.POST.items())
1069
1075
1070 if 'review_members' in controls:
1076 if 'review_members' in controls:
1071 self._update_reviewers(
1077 self._update_reviewers(
1072 pull_request, controls['review_members'],
1078 pull_request, controls['review_members'],
1073 pull_request.reviewer_data)
1079 pull_request.reviewer_data)
1074 elif str2bool(self.request.POST.get('update_commits', 'false')):
1080 elif str2bool(self.request.POST.get('update_commits', 'false')):
1075 self._update_commits(pull_request)
1081 self._update_commits(pull_request)
1076 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1082 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1077 self._edit_pull_request(pull_request)
1083 self._edit_pull_request(pull_request)
1078 else:
1084 else:
1079 raise HTTPBadRequest()
1085 raise HTTPBadRequest()
1080 return True
1086 return True
1081 raise HTTPForbidden()
1087 raise HTTPForbidden()
1082
1088
1083 def _edit_pull_request(self, pull_request):
1089 def _edit_pull_request(self, pull_request):
1084 _ = self.request.translate
1090 _ = self.request.translate
1085
1091
1086 try:
1092 try:
1087 PullRequestModel().edit(
1093 PullRequestModel().edit(
1088 pull_request,
1094 pull_request,
1089 self.request.POST.get('title'),
1095 self.request.POST.get('title'),
1090 self.request.POST.get('description'),
1096 self.request.POST.get('description'),
1091 self.request.POST.get('description_renderer'),
1097 self.request.POST.get('description_renderer'),
1092 self._rhodecode_user)
1098 self._rhodecode_user)
1093 except ValueError:
1099 except ValueError:
1094 msg = _(u'Cannot update closed pull requests.')
1100 msg = _(u'Cannot update closed pull requests.')
1095 h.flash(msg, category='error')
1101 h.flash(msg, category='error')
1096 return
1102 return
1097 else:
1103 else:
1098 Session().commit()
1104 Session().commit()
1099
1105
1100 msg = _(u'Pull request title & description updated.')
1106 msg = _(u'Pull request title & description updated.')
1101 h.flash(msg, category='success')
1107 h.flash(msg, category='success')
1102 return
1108 return
1103
1109
1104 def _update_commits(self, pull_request):
1110 def _update_commits(self, pull_request):
1105 _ = self.request.translate
1111 _ = self.request.translate
1106
1112
1107 with pull_request.set_state(PullRequest.STATE_UPDATING):
1113 with pull_request.set_state(PullRequest.STATE_UPDATING):
1108 resp = PullRequestModel().update_commits(pull_request)
1114 resp = PullRequestModel().update_commits(pull_request)
1109
1115
1110 if resp.executed:
1116 if resp.executed:
1111
1117
1112 if resp.target_changed and resp.source_changed:
1118 if resp.target_changed and resp.source_changed:
1113 changed = 'target and source repositories'
1119 changed = 'target and source repositories'
1114 elif resp.target_changed and not resp.source_changed:
1120 elif resp.target_changed and not resp.source_changed:
1115 changed = 'target repository'
1121 changed = 'target repository'
1116 elif not resp.target_changed and resp.source_changed:
1122 elif not resp.target_changed and resp.source_changed:
1117 changed = 'source repository'
1123 changed = 'source repository'
1118 else:
1124 else:
1119 changed = 'nothing'
1125 changed = 'nothing'
1120
1126
1121 msg = _(u'Pull request updated to "{source_commit_id}" with '
1127 msg = _(u'Pull request updated to "{source_commit_id}" with '
1122 u'{count_added} added, {count_removed} removed commits. '
1128 u'{count_added} added, {count_removed} removed commits. '
1123 u'Source of changes: {change_source}')
1129 u'Source of changes: {change_source}')
1124 msg = msg.format(
1130 msg = msg.format(
1125 source_commit_id=pull_request.source_ref_parts.commit_id,
1131 source_commit_id=pull_request.source_ref_parts.commit_id,
1126 count_added=len(resp.changes.added),
1132 count_added=len(resp.changes.added),
1127 count_removed=len(resp.changes.removed),
1133 count_removed=len(resp.changes.removed),
1128 change_source=changed)
1134 change_source=changed)
1129 h.flash(msg, category='success')
1135 h.flash(msg, category='success')
1130
1136
1131 channel = '/repo${}$/pr/{}'.format(
1137 channel = '/repo${}$/pr/{}'.format(
1132 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1138 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1133 message = msg + (
1139 message = msg + (
1134 ' - <a onclick="window.location.reload()">'
1140 ' - <a onclick="window.location.reload()">'
1135 '<strong>{}</strong></a>'.format(_('Reload page')))
1141 '<strong>{}</strong></a>'.format(_('Reload page')))
1136 channelstream.post_message(
1142 channelstream.post_message(
1137 channel, message, self._rhodecode_user.username,
1143 channel, message, self._rhodecode_user.username,
1138 registry=self.request.registry)
1144 registry=self.request.registry)
1139 else:
1145 else:
1140 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1146 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1141 warning_reasons = [
1147 warning_reasons = [
1142 UpdateFailureReason.NO_CHANGE,
1148 UpdateFailureReason.NO_CHANGE,
1143 UpdateFailureReason.WRONG_REF_TYPE,
1149 UpdateFailureReason.WRONG_REF_TYPE,
1144 ]
1150 ]
1145 category = 'warning' if resp.reason in warning_reasons else 'error'
1151 category = 'warning' if resp.reason in warning_reasons else 'error'
1146 h.flash(msg, category=category)
1152 h.flash(msg, category=category)
1147
1153
1148 @LoginRequired()
1154 @LoginRequired()
1149 @NotAnonymous()
1155 @NotAnonymous()
1150 @HasRepoPermissionAnyDecorator(
1156 @HasRepoPermissionAnyDecorator(
1151 'repository.read', 'repository.write', 'repository.admin')
1157 'repository.read', 'repository.write', 'repository.admin')
1152 @CSRFRequired()
1158 @CSRFRequired()
1153 @view_config(
1159 @view_config(
1154 route_name='pullrequest_merge', request_method='POST',
1160 route_name='pullrequest_merge', request_method='POST',
1155 renderer='json_ext')
1161 renderer='json_ext')
1156 def pull_request_merge(self):
1162 def pull_request_merge(self):
1157 """
1163 """
1158 Merge will perform a server-side merge of the specified
1164 Merge will perform a server-side merge of the specified
1159 pull request, if the pull request is approved and mergeable.
1165 pull request, if the pull request is approved and mergeable.
1160 After successful merging, the pull request is automatically
1166 After successful merging, the pull request is automatically
1161 closed, with a relevant comment.
1167 closed, with a relevant comment.
1162 """
1168 """
1163 pull_request = PullRequest.get_or_404(
1169 pull_request = PullRequest.get_or_404(
1164 self.request.matchdict['pull_request_id'])
1170 self.request.matchdict['pull_request_id'])
1165 _ = self.request.translate
1171 _ = self.request.translate
1166
1172
1167 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1173 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1168 log.debug('show: forbidden because pull request is in state %s',
1174 log.debug('show: forbidden because pull request is in state %s',
1169 pull_request.pull_request_state)
1175 pull_request.pull_request_state)
1170 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1176 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1171 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1177 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1172 pull_request.pull_request_state)
1178 pull_request.pull_request_state)
1173 h.flash(msg, category='error')
1179 h.flash(msg, category='error')
1174 raise HTTPFound(
1180 raise HTTPFound(
1175 h.route_path('pullrequest_show',
1181 h.route_path('pullrequest_show',
1176 repo_name=pull_request.target_repo.repo_name,
1182 repo_name=pull_request.target_repo.repo_name,
1177 pull_request_id=pull_request.pull_request_id))
1183 pull_request_id=pull_request.pull_request_id))
1178
1184
1179 self.load_default_context()
1185 self.load_default_context()
1180
1186
1181 with pull_request.set_state(PullRequest.STATE_UPDATING):
1187 with pull_request.set_state(PullRequest.STATE_UPDATING):
1182 check = MergeCheck.validate(
1188 check = MergeCheck.validate(
1183 pull_request, auth_user=self._rhodecode_user,
1189 pull_request, auth_user=self._rhodecode_user,
1184 translator=self.request.translate)
1190 translator=self.request.translate)
1185 merge_possible = not check.failed
1191 merge_possible = not check.failed
1186
1192
1187 for err_type, error_msg in check.errors:
1193 for err_type, error_msg in check.errors:
1188 h.flash(error_msg, category=err_type)
1194 h.flash(error_msg, category=err_type)
1189
1195
1190 if merge_possible:
1196 if merge_possible:
1191 log.debug("Pre-conditions checked, trying to merge.")
1197 log.debug("Pre-conditions checked, trying to merge.")
1192 extras = vcs_operation_context(
1198 extras = vcs_operation_context(
1193 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1199 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1194 username=self._rhodecode_db_user.username, action='push',
1200 username=self._rhodecode_db_user.username, action='push',
1195 scm=pull_request.target_repo.repo_type)
1201 scm=pull_request.target_repo.repo_type)
1196 with pull_request.set_state(PullRequest.STATE_UPDATING):
1202 with pull_request.set_state(PullRequest.STATE_UPDATING):
1197 self._merge_pull_request(
1203 self._merge_pull_request(
1198 pull_request, self._rhodecode_db_user, extras)
1204 pull_request, self._rhodecode_db_user, extras)
1199 else:
1205 else:
1200 log.debug("Pre-conditions failed, NOT merging.")
1206 log.debug("Pre-conditions failed, NOT merging.")
1201
1207
1202 raise HTTPFound(
1208 raise HTTPFound(
1203 h.route_path('pullrequest_show',
1209 h.route_path('pullrequest_show',
1204 repo_name=pull_request.target_repo.repo_name,
1210 repo_name=pull_request.target_repo.repo_name,
1205 pull_request_id=pull_request.pull_request_id))
1211 pull_request_id=pull_request.pull_request_id))
1206
1212
1207 def _merge_pull_request(self, pull_request, user, extras):
1213 def _merge_pull_request(self, pull_request, user, extras):
1208 _ = self.request.translate
1214 _ = self.request.translate
1209 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1215 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1210
1216
1211 if merge_resp.executed:
1217 if merge_resp.executed:
1212 log.debug("The merge was successful, closing the pull request.")
1218 log.debug("The merge was successful, closing the pull request.")
1213 PullRequestModel().close_pull_request(
1219 PullRequestModel().close_pull_request(
1214 pull_request.pull_request_id, user)
1220 pull_request.pull_request_id, user)
1215 Session().commit()
1221 Session().commit()
1216 msg = _('Pull request was successfully merged and closed.')
1222 msg = _('Pull request was successfully merged and closed.')
1217 h.flash(msg, category='success')
1223 h.flash(msg, category='success')
1218 else:
1224 else:
1219 log.debug(
1225 log.debug(
1220 "The merge was not successful. Merge response: %s", merge_resp)
1226 "The merge was not successful. Merge response: %s", merge_resp)
1221 msg = merge_resp.merge_status_message
1227 msg = merge_resp.merge_status_message
1222 h.flash(msg, category='error')
1228 h.flash(msg, category='error')
1223
1229
1224 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1230 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1225 _ = self.request.translate
1231 _ = self.request.translate
1226
1232
1227 get_default_reviewers_data, validate_default_reviewers = \
1233 get_default_reviewers_data, validate_default_reviewers = \
1228 PullRequestModel().get_reviewer_functions()
1234 PullRequestModel().get_reviewer_functions()
1229
1235
1230 try:
1236 try:
1231 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1237 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1232 except ValueError as e:
1238 except ValueError as e:
1233 log.error('Reviewers Validation: {}'.format(e))
1239 log.error('Reviewers Validation: {}'.format(e))
1234 h.flash(e, category='error')
1240 h.flash(e, category='error')
1235 return
1241 return
1236
1242
1237 old_calculated_status = pull_request.calculated_review_status()
1243 old_calculated_status = pull_request.calculated_review_status()
1238 PullRequestModel().update_reviewers(
1244 PullRequestModel().update_reviewers(
1239 pull_request, reviewers, self._rhodecode_user)
1245 pull_request, reviewers, self._rhodecode_user)
1240 h.flash(_('Pull request reviewers updated.'), category='success')
1246 h.flash(_('Pull request reviewers updated.'), category='success')
1241 Session().commit()
1247 Session().commit()
1242
1248
1243 # trigger status changed if change in reviewers changes the status
1249 # trigger status changed if change in reviewers changes the status
1244 calculated_status = pull_request.calculated_review_status()
1250 calculated_status = pull_request.calculated_review_status()
1245 if old_calculated_status != calculated_status:
1251 if old_calculated_status != calculated_status:
1246 PullRequestModel().trigger_pull_request_hook(
1252 PullRequestModel().trigger_pull_request_hook(
1247 pull_request, self._rhodecode_user, 'review_status_change',
1253 pull_request, self._rhodecode_user, 'review_status_change',
1248 data={'status': calculated_status})
1254 data={'status': calculated_status})
1249
1255
1250 @LoginRequired()
1256 @LoginRequired()
1251 @NotAnonymous()
1257 @NotAnonymous()
1252 @HasRepoPermissionAnyDecorator(
1258 @HasRepoPermissionAnyDecorator(
1253 'repository.read', 'repository.write', 'repository.admin')
1259 'repository.read', 'repository.write', 'repository.admin')
1254 @CSRFRequired()
1260 @CSRFRequired()
1255 @view_config(
1261 @view_config(
1256 route_name='pullrequest_delete', request_method='POST',
1262 route_name='pullrequest_delete', request_method='POST',
1257 renderer='json_ext')
1263 renderer='json_ext')
1258 def pull_request_delete(self):
1264 def pull_request_delete(self):
1259 _ = self.request.translate
1265 _ = self.request.translate
1260
1266
1261 pull_request = PullRequest.get_or_404(
1267 pull_request = PullRequest.get_or_404(
1262 self.request.matchdict['pull_request_id'])
1268 self.request.matchdict['pull_request_id'])
1263 self.load_default_context()
1269 self.load_default_context()
1264
1270
1265 pr_closed = pull_request.is_closed()
1271 pr_closed = pull_request.is_closed()
1266 allowed_to_delete = PullRequestModel().check_user_delete(
1272 allowed_to_delete = PullRequestModel().check_user_delete(
1267 pull_request, self._rhodecode_user) and not pr_closed
1273 pull_request, self._rhodecode_user) and not pr_closed
1268
1274
1269 # only owner can delete it !
1275 # only owner can delete it !
1270 if allowed_to_delete:
1276 if allowed_to_delete:
1271 PullRequestModel().delete(pull_request, self._rhodecode_user)
1277 PullRequestModel().delete(pull_request, self._rhodecode_user)
1272 Session().commit()
1278 Session().commit()
1273 h.flash(_('Successfully deleted pull request'),
1279 h.flash(_('Successfully deleted pull request'),
1274 category='success')
1280 category='success')
1275 raise HTTPFound(h.route_path('pullrequest_show_all',
1281 raise HTTPFound(h.route_path('pullrequest_show_all',
1276 repo_name=self.db_repo_name))
1282 repo_name=self.db_repo_name))
1277
1283
1278 log.warning('user %s tried to delete pull request without access',
1284 log.warning('user %s tried to delete pull request without access',
1279 self._rhodecode_user)
1285 self._rhodecode_user)
1280 raise HTTPNotFound()
1286 raise HTTPNotFound()
1281
1287
1282 @LoginRequired()
1288 @LoginRequired()
1283 @NotAnonymous()
1289 @NotAnonymous()
1284 @HasRepoPermissionAnyDecorator(
1290 @HasRepoPermissionAnyDecorator(
1285 'repository.read', 'repository.write', 'repository.admin')
1291 'repository.read', 'repository.write', 'repository.admin')
1286 @CSRFRequired()
1292 @CSRFRequired()
1287 @view_config(
1293 @view_config(
1288 route_name='pullrequest_comment_create', request_method='POST',
1294 route_name='pullrequest_comment_create', request_method='POST',
1289 renderer='json_ext')
1295 renderer='json_ext')
1290 def pull_request_comment_create(self):
1296 def pull_request_comment_create(self):
1291 _ = self.request.translate
1297 _ = self.request.translate
1292
1298
1293 pull_request = PullRequest.get_or_404(
1299 pull_request = PullRequest.get_or_404(
1294 self.request.matchdict['pull_request_id'])
1300 self.request.matchdict['pull_request_id'])
1295 pull_request_id = pull_request.pull_request_id
1301 pull_request_id = pull_request.pull_request_id
1296
1302
1297 if pull_request.is_closed():
1303 if pull_request.is_closed():
1298 log.debug('comment: forbidden because pull request is closed')
1304 log.debug('comment: forbidden because pull request is closed')
1299 raise HTTPForbidden()
1305 raise HTTPForbidden()
1300
1306
1301 allowed_to_comment = PullRequestModel().check_user_comment(
1307 allowed_to_comment = PullRequestModel().check_user_comment(
1302 pull_request, self._rhodecode_user)
1308 pull_request, self._rhodecode_user)
1303 if not allowed_to_comment:
1309 if not allowed_to_comment:
1304 log.debug(
1310 log.debug(
1305 'comment: forbidden because pull request is from forbidden repo')
1311 'comment: forbidden because pull request is from forbidden repo')
1306 raise HTTPForbidden()
1312 raise HTTPForbidden()
1307
1313
1308 c = self.load_default_context()
1314 c = self.load_default_context()
1309
1315
1310 status = self.request.POST.get('changeset_status', None)
1316 status = self.request.POST.get('changeset_status', None)
1311 text = self.request.POST.get('text')
1317 text = self.request.POST.get('text')
1312 comment_type = self.request.POST.get('comment_type')
1318 comment_type = self.request.POST.get('comment_type')
1313 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1319 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1314 close_pull_request = self.request.POST.get('close_pull_request')
1320 close_pull_request = self.request.POST.get('close_pull_request')
1315
1321
1316 # the logic here should work like following, if we submit close
1322 # the logic here should work like following, if we submit close
1317 # pr comment, use `close_pull_request_with_comment` function
1323 # pr comment, use `close_pull_request_with_comment` function
1318 # else handle regular comment logic
1324 # else handle regular comment logic
1319
1325
1320 if close_pull_request:
1326 if close_pull_request:
1321 # only owner or admin or person with write permissions
1327 # only owner or admin or person with write permissions
1322 allowed_to_close = PullRequestModel().check_user_update(
1328 allowed_to_close = PullRequestModel().check_user_update(
1323 pull_request, self._rhodecode_user)
1329 pull_request, self._rhodecode_user)
1324 if not allowed_to_close:
1330 if not allowed_to_close:
1325 log.debug('comment: forbidden because not allowed to close '
1331 log.debug('comment: forbidden because not allowed to close '
1326 'pull request %s', pull_request_id)
1332 'pull request %s', pull_request_id)
1327 raise HTTPForbidden()
1333 raise HTTPForbidden()
1328
1334
1329 # This also triggers `review_status_change`
1335 # This also triggers `review_status_change`
1330 comment, status = PullRequestModel().close_pull_request_with_comment(
1336 comment, status = PullRequestModel().close_pull_request_with_comment(
1331 pull_request, self._rhodecode_user, self.db_repo, message=text,
1337 pull_request, self._rhodecode_user, self.db_repo, message=text,
1332 auth_user=self._rhodecode_user)
1338 auth_user=self._rhodecode_user)
1333 Session().flush()
1339 Session().flush()
1334
1340
1335 PullRequestModel().trigger_pull_request_hook(
1341 PullRequestModel().trigger_pull_request_hook(
1336 pull_request, self._rhodecode_user, 'comment',
1342 pull_request, self._rhodecode_user, 'comment',
1337 data={'comment': comment})
1343 data={'comment': comment})
1338
1344
1339 else:
1345 else:
1340 # regular comment case, could be inline, or one with status.
1346 # regular comment case, could be inline, or one with status.
1341 # for that one we check also permissions
1347 # for that one we check also permissions
1342
1348
1343 allowed_to_change_status = PullRequestModel().check_user_change_status(
1349 allowed_to_change_status = PullRequestModel().check_user_change_status(
1344 pull_request, self._rhodecode_user)
1350 pull_request, self._rhodecode_user)
1345
1351
1346 if status and allowed_to_change_status:
1352 if status and allowed_to_change_status:
1347 message = (_('Status change %(transition_icon)s %(status)s')
1353 message = (_('Status change %(transition_icon)s %(status)s')
1348 % {'transition_icon': '>',
1354 % {'transition_icon': '>',
1349 'status': ChangesetStatus.get_status_lbl(status)})
1355 'status': ChangesetStatus.get_status_lbl(status)})
1350 text = text or message
1356 text = text or message
1351
1357
1352 comment = CommentsModel().create(
1358 comment = CommentsModel().create(
1353 text=text,
1359 text=text,
1354 repo=self.db_repo.repo_id,
1360 repo=self.db_repo.repo_id,
1355 user=self._rhodecode_user.user_id,
1361 user=self._rhodecode_user.user_id,
1356 pull_request=pull_request,
1362 pull_request=pull_request,
1357 f_path=self.request.POST.get('f_path'),
1363 f_path=self.request.POST.get('f_path'),
1358 line_no=self.request.POST.get('line'),
1364 line_no=self.request.POST.get('line'),
1359 status_change=(ChangesetStatus.get_status_lbl(status)
1365 status_change=(ChangesetStatus.get_status_lbl(status)
1360 if status and allowed_to_change_status else None),
1366 if status and allowed_to_change_status else None),
1361 status_change_type=(status
1367 status_change_type=(status
1362 if status and allowed_to_change_status else None),
1368 if status and allowed_to_change_status else None),
1363 comment_type=comment_type,
1369 comment_type=comment_type,
1364 resolves_comment_id=resolves_comment_id,
1370 resolves_comment_id=resolves_comment_id,
1365 auth_user=self._rhodecode_user
1371 auth_user=self._rhodecode_user
1366 )
1372 )
1367
1373
1368 if allowed_to_change_status:
1374 if allowed_to_change_status:
1369 # calculate old status before we change it
1375 # calculate old status before we change it
1370 old_calculated_status = pull_request.calculated_review_status()
1376 old_calculated_status = pull_request.calculated_review_status()
1371
1377
1372 # get status if set !
1378 # get status if set !
1373 if status:
1379 if status:
1374 ChangesetStatusModel().set_status(
1380 ChangesetStatusModel().set_status(
1375 self.db_repo.repo_id,
1381 self.db_repo.repo_id,
1376 status,
1382 status,
1377 self._rhodecode_user.user_id,
1383 self._rhodecode_user.user_id,
1378 comment,
1384 comment,
1379 pull_request=pull_request
1385 pull_request=pull_request
1380 )
1386 )
1381
1387
1382 Session().flush()
1388 Session().flush()
1383 # this is somehow required to get access to some relationship
1389 # this is somehow required to get access to some relationship
1384 # loaded on comment
1390 # loaded on comment
1385 Session().refresh(comment)
1391 Session().refresh(comment)
1386
1392
1387 PullRequestModel().trigger_pull_request_hook(
1393 PullRequestModel().trigger_pull_request_hook(
1388 pull_request, self._rhodecode_user, 'comment',
1394 pull_request, self._rhodecode_user, 'comment',
1389 data={'comment': comment})
1395 data={'comment': comment})
1390
1396
1391 # we now calculate the status of pull request, and based on that
1397 # we now calculate the status of pull request, and based on that
1392 # calculation we set the commits status
1398 # calculation we set the commits status
1393 calculated_status = pull_request.calculated_review_status()
1399 calculated_status = pull_request.calculated_review_status()
1394 if old_calculated_status != calculated_status:
1400 if old_calculated_status != calculated_status:
1395 PullRequestModel().trigger_pull_request_hook(
1401 PullRequestModel().trigger_pull_request_hook(
1396 pull_request, self._rhodecode_user, 'review_status_change',
1402 pull_request, self._rhodecode_user, 'review_status_change',
1397 data={'status': calculated_status})
1403 data={'status': calculated_status})
1398
1404
1399 Session().commit()
1405 Session().commit()
1400
1406
1401 data = {
1407 data = {
1402 'target_id': h.safeid(h.safe_unicode(
1408 'target_id': h.safeid(h.safe_unicode(
1403 self.request.POST.get('f_path'))),
1409 self.request.POST.get('f_path'))),
1404 }
1410 }
1405 if comment:
1411 if comment:
1406 c.co = comment
1412 c.co = comment
1407 rendered_comment = render(
1413 rendered_comment = render(
1408 'rhodecode:templates/changeset/changeset_comment_block.mako',
1414 'rhodecode:templates/changeset/changeset_comment_block.mako',
1409 self._get_template_context(c), self.request)
1415 self._get_template_context(c), self.request)
1410
1416
1411 data.update(comment.get_dict())
1417 data.update(comment.get_dict())
1412 data.update({'rendered_text': rendered_comment})
1418 data.update({'rendered_text': rendered_comment})
1413
1419
1414 return data
1420 return data
1415
1421
1416 @LoginRequired()
1422 @LoginRequired()
1417 @NotAnonymous()
1423 @NotAnonymous()
1418 @HasRepoPermissionAnyDecorator(
1424 @HasRepoPermissionAnyDecorator(
1419 'repository.read', 'repository.write', 'repository.admin')
1425 'repository.read', 'repository.write', 'repository.admin')
1420 @CSRFRequired()
1426 @CSRFRequired()
1421 @view_config(
1427 @view_config(
1422 route_name='pullrequest_comment_delete', request_method='POST',
1428 route_name='pullrequest_comment_delete', request_method='POST',
1423 renderer='json_ext')
1429 renderer='json_ext')
1424 def pull_request_comment_delete(self):
1430 def pull_request_comment_delete(self):
1425 pull_request = PullRequest.get_or_404(
1431 pull_request = PullRequest.get_or_404(
1426 self.request.matchdict['pull_request_id'])
1432 self.request.matchdict['pull_request_id'])
1427
1433
1428 comment = ChangesetComment.get_or_404(
1434 comment = ChangesetComment.get_or_404(
1429 self.request.matchdict['comment_id'])
1435 self.request.matchdict['comment_id'])
1430 comment_id = comment.comment_id
1436 comment_id = comment.comment_id
1431
1437
1432 if pull_request.is_closed():
1438 if pull_request.is_closed():
1433 log.debug('comment: forbidden because pull request is closed')
1439 log.debug('comment: forbidden because pull request is closed')
1434 raise HTTPForbidden()
1440 raise HTTPForbidden()
1435
1441
1436 if not comment:
1442 if not comment:
1437 log.debug('Comment with id:%s not found, skipping', comment_id)
1443 log.debug('Comment with id:%s not found, skipping', comment_id)
1438 # comment already deleted in another call probably
1444 # comment already deleted in another call probably
1439 return True
1445 return True
1440
1446
1441 if comment.pull_request.is_closed():
1447 if comment.pull_request.is_closed():
1442 # don't allow deleting comments on closed pull request
1448 # don't allow deleting comments on closed pull request
1443 raise HTTPForbidden()
1449 raise HTTPForbidden()
1444
1450
1445 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1451 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1446 super_admin = h.HasPermissionAny('hg.admin')()
1452 super_admin = h.HasPermissionAny('hg.admin')()
1447 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1453 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1448 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1454 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1449 comment_repo_admin = is_repo_admin and is_repo_comment
1455 comment_repo_admin = is_repo_admin and is_repo_comment
1450
1456
1451 if super_admin or comment_owner or comment_repo_admin:
1457 if super_admin or comment_owner or comment_repo_admin:
1452 old_calculated_status = comment.pull_request.calculated_review_status()
1458 old_calculated_status = comment.pull_request.calculated_review_status()
1453 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1459 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1454 Session().commit()
1460 Session().commit()
1455 calculated_status = comment.pull_request.calculated_review_status()
1461 calculated_status = comment.pull_request.calculated_review_status()
1456 if old_calculated_status != calculated_status:
1462 if old_calculated_status != calculated_status:
1457 PullRequestModel().trigger_pull_request_hook(
1463 PullRequestModel().trigger_pull_request_hook(
1458 comment.pull_request, self._rhodecode_user, 'review_status_change',
1464 comment.pull_request, self._rhodecode_user, 'review_status_change',
1459 data={'status': calculated_status})
1465 data={'status': calculated_status})
1460 return True
1466 return True
1461 else:
1467 else:
1462 log.warning('No permissions for user %s to delete comment_id: %s',
1468 log.warning('No permissions for user %s to delete comment_id: %s',
1463 self._rhodecode_db_user, comment_id)
1469 self._rhodecode_db_user, comment_id)
1464 raise HTTPNotFound()
1470 raise HTTPNotFound()
@@ -1,717 +1,734 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 comments model for RhodeCode
22 comments model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import collections
27 import collections
28
28
29 from pyramid.threadlocal import get_current_registry, get_current_request
29 from pyramid.threadlocal import get_current_registry, get_current_request
30 from sqlalchemy.sql.expression import null
30 from sqlalchemy.sql.expression import null
31 from sqlalchemy.sql.functions import coalesce
31 from sqlalchemy.sql.functions import coalesce
32
32
33 from rhodecode.lib import helpers as h, diffs, channelstream
33 from rhodecode.lib import helpers as h, diffs, channelstream
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str
35 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str
36 from rhodecode.model import BaseModel
36 from rhodecode.model import BaseModel
37 from rhodecode.model.db import (
37 from rhodecode.model.db import (
38 ChangesetComment, User, Notification, PullRequest, AttributeDict)
38 ChangesetComment, User, Notification, PullRequest, AttributeDict)
39 from rhodecode.model.notification import NotificationModel
39 from rhodecode.model.notification import NotificationModel
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.settings import VcsSettingsModel
41 from rhodecode.model.settings import VcsSettingsModel
42 from rhodecode.model.notification import EmailNotificationModel
42 from rhodecode.model.notification import EmailNotificationModel
43 from rhodecode.model.validation_schema.schemas import comment_schema
43 from rhodecode.model.validation_schema.schemas import comment_schema
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class CommentsModel(BaseModel):
49 class CommentsModel(BaseModel):
50
50
51 cls = ChangesetComment
51 cls = ChangesetComment
52
52
53 DIFF_CONTEXT_BEFORE = 3
53 DIFF_CONTEXT_BEFORE = 3
54 DIFF_CONTEXT_AFTER = 3
54 DIFF_CONTEXT_AFTER = 3
55
55
56 def __get_commit_comment(self, changeset_comment):
56 def __get_commit_comment(self, changeset_comment):
57 return self._get_instance(ChangesetComment, changeset_comment)
57 return self._get_instance(ChangesetComment, changeset_comment)
58
58
59 def __get_pull_request(self, pull_request):
59 def __get_pull_request(self, pull_request):
60 return self._get_instance(PullRequest, pull_request)
60 return self._get_instance(PullRequest, pull_request)
61
61
62 def _extract_mentions(self, s):
62 def _extract_mentions(self, s):
63 user_objects = []
63 user_objects = []
64 for username in extract_mentioned_users(s):
64 for username in extract_mentioned_users(s):
65 user_obj = User.get_by_username(username, case_insensitive=True)
65 user_obj = User.get_by_username(username, case_insensitive=True)
66 if user_obj:
66 if user_obj:
67 user_objects.append(user_obj)
67 user_objects.append(user_obj)
68 return user_objects
68 return user_objects
69
69
70 def _get_renderer(self, global_renderer='rst', request=None):
70 def _get_renderer(self, global_renderer='rst', request=None):
71 request = request or get_current_request()
71 request = request or get_current_request()
72
72
73 try:
73 try:
74 global_renderer = request.call_context.visual.default_renderer
74 global_renderer = request.call_context.visual.default_renderer
75 except AttributeError:
75 except AttributeError:
76 log.debug("Renderer not set, falling back "
76 log.debug("Renderer not set, falling back "
77 "to default renderer '%s'", global_renderer)
77 "to default renderer '%s'", global_renderer)
78 except Exception:
78 except Exception:
79 log.error(traceback.format_exc())
79 log.error(traceback.format_exc())
80 return global_renderer
80 return global_renderer
81
81
82 def aggregate_comments(self, comments, versions, show_version, inline=False):
82 def aggregate_comments(self, comments, versions, show_version, inline=False):
83 # group by versions, and count until, and display objects
83 # group by versions, and count until, and display objects
84
84
85 comment_groups = collections.defaultdict(list)
85 comment_groups = collections.defaultdict(list)
86 [comment_groups[
86 [comment_groups[
87 _co.pull_request_version_id].append(_co) for _co in comments]
87 _co.pull_request_version_id].append(_co) for _co in comments]
88
88
89 def yield_comments(pos):
89 def yield_comments(pos):
90 for co in comment_groups[pos]:
90 for co in comment_groups[pos]:
91 yield co
91 yield co
92
92
93 comment_versions = collections.defaultdict(
93 comment_versions = collections.defaultdict(
94 lambda: collections.defaultdict(list))
94 lambda: collections.defaultdict(list))
95 prev_prvid = -1
95 prev_prvid = -1
96 # fake last entry with None, to aggregate on "latest" version which
96 # fake last entry with None, to aggregate on "latest" version which
97 # doesn't have an pull_request_version_id
97 # doesn't have an pull_request_version_id
98 for ver in versions + [AttributeDict({'pull_request_version_id': None})]:
98 for ver in versions + [AttributeDict({'pull_request_version_id': None})]:
99 prvid = ver.pull_request_version_id
99 prvid = ver.pull_request_version_id
100 if prev_prvid == -1:
100 if prev_prvid == -1:
101 prev_prvid = prvid
101 prev_prvid = prvid
102
102
103 for co in yield_comments(prvid):
103 for co in yield_comments(prvid):
104 comment_versions[prvid]['at'].append(co)
104 comment_versions[prvid]['at'].append(co)
105
105
106 # save until
106 # save until
107 current = comment_versions[prvid]['at']
107 current = comment_versions[prvid]['at']
108 prev_until = comment_versions[prev_prvid]['until']
108 prev_until = comment_versions[prev_prvid]['until']
109 cur_until = prev_until + current
109 cur_until = prev_until + current
110 comment_versions[prvid]['until'].extend(cur_until)
110 comment_versions[prvid]['until'].extend(cur_until)
111
111
112 # save outdated
112 # save outdated
113 if inline:
113 if inline:
114 outdated = [x for x in cur_until
114 outdated = [x for x in cur_until
115 if x.outdated_at_version(show_version)]
115 if x.outdated_at_version(show_version)]
116 else:
116 else:
117 outdated = [x for x in cur_until
117 outdated = [x for x in cur_until
118 if x.older_than_version(show_version)]
118 if x.older_than_version(show_version)]
119 display = [x for x in cur_until if x not in outdated]
119 display = [x for x in cur_until if x not in outdated]
120
120
121 comment_versions[prvid]['outdated'] = outdated
121 comment_versions[prvid]['outdated'] = outdated
122 comment_versions[prvid]['display'] = display
122 comment_versions[prvid]['display'] = display
123
123
124 prev_prvid = prvid
124 prev_prvid = prvid
125
125
126 return comment_versions
126 return comment_versions
127
127
128 def get_repository_comments(self, repo, comment_type=None, user=None, commit_id=None):
128 def get_repository_comments(self, repo, comment_type=None, user=None, commit_id=None):
129 qry = Session().query(ChangesetComment) \
129 qry = Session().query(ChangesetComment) \
130 .filter(ChangesetComment.repo == repo)
130 .filter(ChangesetComment.repo == repo)
131
131
132 if comment_type and comment_type in ChangesetComment.COMMENT_TYPES:
132 if comment_type and comment_type in ChangesetComment.COMMENT_TYPES:
133 qry = qry.filter(ChangesetComment.comment_type == comment_type)
133 qry = qry.filter(ChangesetComment.comment_type == comment_type)
134
134
135 if user:
135 if user:
136 user = self._get_user(user)
136 user = self._get_user(user)
137 if user:
137 if user:
138 qry = qry.filter(ChangesetComment.user_id == user.user_id)
138 qry = qry.filter(ChangesetComment.user_id == user.user_id)
139
139
140 if commit_id:
140 if commit_id:
141 qry = qry.filter(ChangesetComment.revision == commit_id)
141 qry = qry.filter(ChangesetComment.revision == commit_id)
142
142
143 qry = qry.order_by(ChangesetComment.created_on)
143 qry = qry.order_by(ChangesetComment.created_on)
144 return qry.all()
144 return qry.all()
145
145
146 def get_repository_unresolved_todos(self, repo):
146 def get_repository_unresolved_todos(self, repo):
147 todos = Session().query(ChangesetComment) \
147 todos = Session().query(ChangesetComment) \
148 .filter(ChangesetComment.repo == repo) \
148 .filter(ChangesetComment.repo == repo) \
149 .filter(ChangesetComment.resolved_by == None) \
149 .filter(ChangesetComment.resolved_by == None) \
150 .filter(ChangesetComment.comment_type
150 .filter(ChangesetComment.comment_type
151 == ChangesetComment.COMMENT_TYPE_TODO)
151 == ChangesetComment.COMMENT_TYPE_TODO)
152 todos = todos.all()
152 todos = todos.all()
153
153
154 return todos
154 return todos
155
155
156 def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True):
156 def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True):
157
157
158 todos = Session().query(ChangesetComment) \
158 todos = Session().query(ChangesetComment) \
159 .filter(ChangesetComment.pull_request == pull_request) \
159 .filter(ChangesetComment.pull_request == pull_request) \
160 .filter(ChangesetComment.resolved_by == None) \
160 .filter(ChangesetComment.resolved_by == None) \
161 .filter(ChangesetComment.comment_type
161 .filter(ChangesetComment.comment_type
162 == ChangesetComment.COMMENT_TYPE_TODO)
162 == ChangesetComment.COMMENT_TYPE_TODO)
163
163
164 if not show_outdated:
164 if not show_outdated:
165 todos = todos.filter(
165 todos = todos.filter(
166 coalesce(ChangesetComment.display_state, '') !=
166 coalesce(ChangesetComment.display_state, '') !=
167 ChangesetComment.COMMENT_OUTDATED)
167 ChangesetComment.COMMENT_OUTDATED)
168
168
169 todos = todos.all()
169 todos = todos.all()
170
170
171 return todos
171 return todos
172
172
173 def get_pull_request_resolved_todos(self, pull_request, show_outdated=True):
174
175 todos = Session().query(ChangesetComment) \
176 .filter(ChangesetComment.pull_request == pull_request) \
177 .filter(ChangesetComment.resolved_by != None) \
178 .filter(ChangesetComment.comment_type
179 == ChangesetComment.COMMENT_TYPE_TODO)
180
181 if not show_outdated:
182 todos = todos.filter(
183 coalesce(ChangesetComment.display_state, '') !=
184 ChangesetComment.COMMENT_OUTDATED)
185
186 todos = todos.all()
187
188 return todos
189
173 def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
190 def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
174
191
175 todos = Session().query(ChangesetComment) \
192 todos = Session().query(ChangesetComment) \
176 .filter(ChangesetComment.revision == commit_id) \
193 .filter(ChangesetComment.revision == commit_id) \
177 .filter(ChangesetComment.resolved_by == None) \
194 .filter(ChangesetComment.resolved_by == None) \
178 .filter(ChangesetComment.comment_type
195 .filter(ChangesetComment.comment_type
179 == ChangesetComment.COMMENT_TYPE_TODO)
196 == ChangesetComment.COMMENT_TYPE_TODO)
180
197
181 if not show_outdated:
198 if not show_outdated:
182 todos = todos.filter(
199 todos = todos.filter(
183 coalesce(ChangesetComment.display_state, '') !=
200 coalesce(ChangesetComment.display_state, '') !=
184 ChangesetComment.COMMENT_OUTDATED)
201 ChangesetComment.COMMENT_OUTDATED)
185
202
186 todos = todos.all()
203 todos = todos.all()
187
204
188 return todos
205 return todos
189
206
190 def get_commit_resolved_todos(self, commit_id, show_outdated=True):
207 def get_commit_resolved_todos(self, commit_id, show_outdated=True):
191
208
192 todos = Session().query(ChangesetComment) \
209 todos = Session().query(ChangesetComment) \
193 .filter(ChangesetComment.revision == commit_id) \
210 .filter(ChangesetComment.revision == commit_id) \
194 .filter(ChangesetComment.resolved_by != None) \
211 .filter(ChangesetComment.resolved_by != None) \
195 .filter(ChangesetComment.comment_type
212 .filter(ChangesetComment.comment_type
196 == ChangesetComment.COMMENT_TYPE_TODO)
213 == ChangesetComment.COMMENT_TYPE_TODO)
197
214
198 if not show_outdated:
215 if not show_outdated:
199 todos = todos.filter(
216 todos = todos.filter(
200 coalesce(ChangesetComment.display_state, '') !=
217 coalesce(ChangesetComment.display_state, '') !=
201 ChangesetComment.COMMENT_OUTDATED)
218 ChangesetComment.COMMENT_OUTDATED)
202
219
203 todos = todos.all()
220 todos = todos.all()
204
221
205 return todos
222 return todos
206
223
207 def _log_audit_action(self, action, action_data, auth_user, comment):
224 def _log_audit_action(self, action, action_data, auth_user, comment):
208 audit_logger.store(
225 audit_logger.store(
209 action=action,
226 action=action,
210 action_data=action_data,
227 action_data=action_data,
211 user=auth_user,
228 user=auth_user,
212 repo=comment.repo)
229 repo=comment.repo)
213
230
214 def create(self, text, repo, user, commit_id=None, pull_request=None,
231 def create(self, text, repo, user, commit_id=None, pull_request=None,
215 f_path=None, line_no=None, status_change=None,
232 f_path=None, line_no=None, status_change=None,
216 status_change_type=None, comment_type=None,
233 status_change_type=None, comment_type=None,
217 resolves_comment_id=None, closing_pr=False, send_email=True,
234 resolves_comment_id=None, closing_pr=False, send_email=True,
218 renderer=None, auth_user=None):
235 renderer=None, auth_user=None):
219 """
236 """
220 Creates new comment for commit or pull request.
237 Creates new comment for commit or pull request.
221 IF status_change is not none this comment is associated with a
238 IF status_change is not none this comment is associated with a
222 status change of commit or commit associated with pull request
239 status change of commit or commit associated with pull request
223
240
224 :param text:
241 :param text:
225 :param repo:
242 :param repo:
226 :param user:
243 :param user:
227 :param commit_id:
244 :param commit_id:
228 :param pull_request:
245 :param pull_request:
229 :param f_path:
246 :param f_path:
230 :param line_no:
247 :param line_no:
231 :param status_change: Label for status change
248 :param status_change: Label for status change
232 :param comment_type: Type of comment
249 :param comment_type: Type of comment
233 :param status_change_type: type of status change
250 :param status_change_type: type of status change
234 :param closing_pr:
251 :param closing_pr:
235 :param send_email:
252 :param send_email:
236 :param renderer: pick renderer for this comment
253 :param renderer: pick renderer for this comment
237 """
254 """
238
255
239 if not text:
256 if not text:
240 log.warning('Missing text for comment, skipping...')
257 log.warning('Missing text for comment, skipping...')
241 return
258 return
242 request = get_current_request()
259 request = get_current_request()
243 _ = request.translate
260 _ = request.translate
244
261
245 if not renderer:
262 if not renderer:
246 renderer = self._get_renderer(request=request)
263 renderer = self._get_renderer(request=request)
247
264
248 repo = self._get_repo(repo)
265 repo = self._get_repo(repo)
249 user = self._get_user(user)
266 user = self._get_user(user)
250 auth_user = auth_user or user
267 auth_user = auth_user or user
251
268
252 schema = comment_schema.CommentSchema()
269 schema = comment_schema.CommentSchema()
253 validated_kwargs = schema.deserialize(dict(
270 validated_kwargs = schema.deserialize(dict(
254 comment_body=text,
271 comment_body=text,
255 comment_type=comment_type,
272 comment_type=comment_type,
256 comment_file=f_path,
273 comment_file=f_path,
257 comment_line=line_no,
274 comment_line=line_no,
258 renderer_type=renderer,
275 renderer_type=renderer,
259 status_change=status_change_type,
276 status_change=status_change_type,
260 resolves_comment_id=resolves_comment_id,
277 resolves_comment_id=resolves_comment_id,
261 repo=repo.repo_id,
278 repo=repo.repo_id,
262 user=user.user_id,
279 user=user.user_id,
263 ))
280 ))
264
281
265 comment = ChangesetComment()
282 comment = ChangesetComment()
266 comment.renderer = validated_kwargs['renderer_type']
283 comment.renderer = validated_kwargs['renderer_type']
267 comment.text = validated_kwargs['comment_body']
284 comment.text = validated_kwargs['comment_body']
268 comment.f_path = validated_kwargs['comment_file']
285 comment.f_path = validated_kwargs['comment_file']
269 comment.line_no = validated_kwargs['comment_line']
286 comment.line_no = validated_kwargs['comment_line']
270 comment.comment_type = validated_kwargs['comment_type']
287 comment.comment_type = validated_kwargs['comment_type']
271
288
272 comment.repo = repo
289 comment.repo = repo
273 comment.author = user
290 comment.author = user
274 resolved_comment = self.__get_commit_comment(
291 resolved_comment = self.__get_commit_comment(
275 validated_kwargs['resolves_comment_id'])
292 validated_kwargs['resolves_comment_id'])
276 # check if the comment actually belongs to this PR
293 # check if the comment actually belongs to this PR
277 if resolved_comment and resolved_comment.pull_request and \
294 if resolved_comment and resolved_comment.pull_request and \
278 resolved_comment.pull_request != pull_request:
295 resolved_comment.pull_request != pull_request:
279 log.warning('Comment tried to resolved unrelated todo comment: %s',
296 log.warning('Comment tried to resolved unrelated todo comment: %s',
280 resolved_comment)
297 resolved_comment)
281 # comment not bound to this pull request, forbid
298 # comment not bound to this pull request, forbid
282 resolved_comment = None
299 resolved_comment = None
283
300
284 elif resolved_comment and resolved_comment.repo and \
301 elif resolved_comment and resolved_comment.repo and \
285 resolved_comment.repo != repo:
302 resolved_comment.repo != repo:
286 log.warning('Comment tried to resolved unrelated todo comment: %s',
303 log.warning('Comment tried to resolved unrelated todo comment: %s',
287 resolved_comment)
304 resolved_comment)
288 # comment not bound to this repo, forbid
305 # comment not bound to this repo, forbid
289 resolved_comment = None
306 resolved_comment = None
290
307
291 comment.resolved_comment = resolved_comment
308 comment.resolved_comment = resolved_comment
292
309
293 pull_request_id = pull_request
310 pull_request_id = pull_request
294
311
295 commit_obj = None
312 commit_obj = None
296 pull_request_obj = None
313 pull_request_obj = None
297
314
298 if commit_id:
315 if commit_id:
299 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
316 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
300 # do a lookup, so we don't pass something bad here
317 # do a lookup, so we don't pass something bad here
301 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
318 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
302 comment.revision = commit_obj.raw_id
319 comment.revision = commit_obj.raw_id
303
320
304 elif pull_request_id:
321 elif pull_request_id:
305 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
322 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
306 pull_request_obj = self.__get_pull_request(pull_request_id)
323 pull_request_obj = self.__get_pull_request(pull_request_id)
307 comment.pull_request = pull_request_obj
324 comment.pull_request = pull_request_obj
308 else:
325 else:
309 raise Exception('Please specify commit or pull_request_id')
326 raise Exception('Please specify commit or pull_request_id')
310
327
311 Session().add(comment)
328 Session().add(comment)
312 Session().flush()
329 Session().flush()
313 kwargs = {
330 kwargs = {
314 'user': user,
331 'user': user,
315 'renderer_type': renderer,
332 'renderer_type': renderer,
316 'repo_name': repo.repo_name,
333 'repo_name': repo.repo_name,
317 'status_change': status_change,
334 'status_change': status_change,
318 'status_change_type': status_change_type,
335 'status_change_type': status_change_type,
319 'comment_body': text,
336 'comment_body': text,
320 'comment_file': f_path,
337 'comment_file': f_path,
321 'comment_line': line_no,
338 'comment_line': line_no,
322 'comment_type': comment_type or 'note'
339 'comment_type': comment_type or 'note'
323 }
340 }
324
341
325 if commit_obj:
342 if commit_obj:
326 recipients = ChangesetComment.get_users(
343 recipients = ChangesetComment.get_users(
327 revision=commit_obj.raw_id)
344 revision=commit_obj.raw_id)
328 # add commit author if it's in RhodeCode system
345 # add commit author if it's in RhodeCode system
329 cs_author = User.get_from_cs_author(commit_obj.author)
346 cs_author = User.get_from_cs_author(commit_obj.author)
330 if not cs_author:
347 if not cs_author:
331 # use repo owner if we cannot extract the author correctly
348 # use repo owner if we cannot extract the author correctly
332 cs_author = repo.user
349 cs_author = repo.user
333 recipients += [cs_author]
350 recipients += [cs_author]
334
351
335 commit_comment_url = self.get_url(comment, request=request)
352 commit_comment_url = self.get_url(comment, request=request)
336
353
337 target_repo_url = h.link_to(
354 target_repo_url = h.link_to(
338 repo.repo_name,
355 repo.repo_name,
339 h.route_url('repo_summary', repo_name=repo.repo_name))
356 h.route_url('repo_summary', repo_name=repo.repo_name))
340
357
341 # commit specifics
358 # commit specifics
342 kwargs.update({
359 kwargs.update({
343 'commit': commit_obj,
360 'commit': commit_obj,
344 'commit_message': commit_obj.message,
361 'commit_message': commit_obj.message,
345 'commit_target_repo': target_repo_url,
362 'commit_target_repo': target_repo_url,
346 'commit_comment_url': commit_comment_url,
363 'commit_comment_url': commit_comment_url,
347 })
364 })
348
365
349 elif pull_request_obj:
366 elif pull_request_obj:
350 # get the current participants of this pull request
367 # get the current participants of this pull request
351 recipients = ChangesetComment.get_users(
368 recipients = ChangesetComment.get_users(
352 pull_request_id=pull_request_obj.pull_request_id)
369 pull_request_id=pull_request_obj.pull_request_id)
353 # add pull request author
370 # add pull request author
354 recipients += [pull_request_obj.author]
371 recipients += [pull_request_obj.author]
355
372
356 # add the reviewers to notification
373 # add the reviewers to notification
357 recipients += [x.user for x in pull_request_obj.reviewers]
374 recipients += [x.user for x in pull_request_obj.reviewers]
358
375
359 pr_target_repo = pull_request_obj.target_repo
376 pr_target_repo = pull_request_obj.target_repo
360 pr_source_repo = pull_request_obj.source_repo
377 pr_source_repo = pull_request_obj.source_repo
361
378
362 pr_comment_url = h.route_url(
379 pr_comment_url = h.route_url(
363 'pullrequest_show',
380 'pullrequest_show',
364 repo_name=pr_target_repo.repo_name,
381 repo_name=pr_target_repo.repo_name,
365 pull_request_id=pull_request_obj.pull_request_id,
382 pull_request_id=pull_request_obj.pull_request_id,
366 _anchor='comment-%s' % comment.comment_id)
383 _anchor='comment-%s' % comment.comment_id)
367
384
368 # set some variables for email notification
385 # set some variables for email notification
369 pr_target_repo_url = h.route_url(
386 pr_target_repo_url = h.route_url(
370 'repo_summary', repo_name=pr_target_repo.repo_name)
387 'repo_summary', repo_name=pr_target_repo.repo_name)
371
388
372 pr_source_repo_url = h.route_url(
389 pr_source_repo_url = h.route_url(
373 'repo_summary', repo_name=pr_source_repo.repo_name)
390 'repo_summary', repo_name=pr_source_repo.repo_name)
374
391
375 # pull request specifics
392 # pull request specifics
376 kwargs.update({
393 kwargs.update({
377 'pull_request': pull_request_obj,
394 'pull_request': pull_request_obj,
378 'pr_id': pull_request_obj.pull_request_id,
395 'pr_id': pull_request_obj.pull_request_id,
379 'pr_target_repo': pr_target_repo,
396 'pr_target_repo': pr_target_repo,
380 'pr_target_repo_url': pr_target_repo_url,
397 'pr_target_repo_url': pr_target_repo_url,
381 'pr_source_repo': pr_source_repo,
398 'pr_source_repo': pr_source_repo,
382 'pr_source_repo_url': pr_source_repo_url,
399 'pr_source_repo_url': pr_source_repo_url,
383 'pr_comment_url': pr_comment_url,
400 'pr_comment_url': pr_comment_url,
384 'pr_closing': closing_pr,
401 'pr_closing': closing_pr,
385 })
402 })
386 if send_email:
403 if send_email:
387 # pre-generate the subject for notification itself
404 # pre-generate the subject for notification itself
388 (subject,
405 (subject,
389 _h, _e, # we don't care about those
406 _h, _e, # we don't care about those
390 body_plaintext) = EmailNotificationModel().render_email(
407 body_plaintext) = EmailNotificationModel().render_email(
391 notification_type, **kwargs)
408 notification_type, **kwargs)
392
409
393 mention_recipients = set(
410 mention_recipients = set(
394 self._extract_mentions(text)).difference(recipients)
411 self._extract_mentions(text)).difference(recipients)
395
412
396 # create notification objects, and emails
413 # create notification objects, and emails
397 NotificationModel().create(
414 NotificationModel().create(
398 created_by=user,
415 created_by=user,
399 notification_subject=subject,
416 notification_subject=subject,
400 notification_body=body_plaintext,
417 notification_body=body_plaintext,
401 notification_type=notification_type,
418 notification_type=notification_type,
402 recipients=recipients,
419 recipients=recipients,
403 mention_recipients=mention_recipients,
420 mention_recipients=mention_recipients,
404 email_kwargs=kwargs,
421 email_kwargs=kwargs,
405 )
422 )
406
423
407 Session().flush()
424 Session().flush()
408 if comment.pull_request:
425 if comment.pull_request:
409 action = 'repo.pull_request.comment.create'
426 action = 'repo.pull_request.comment.create'
410 else:
427 else:
411 action = 'repo.commit.comment.create'
428 action = 'repo.commit.comment.create'
412
429
413 comment_data = comment.get_api_data()
430 comment_data = comment.get_api_data()
414 self._log_audit_action(
431 self._log_audit_action(
415 action, {'data': comment_data}, auth_user, comment)
432 action, {'data': comment_data}, auth_user, comment)
416
433
417 msg_url = ''
434 msg_url = ''
418 channel = None
435 channel = None
419 if commit_obj:
436 if commit_obj:
420 msg_url = commit_comment_url
437 msg_url = commit_comment_url
421 repo_name = repo.repo_name
438 repo_name = repo.repo_name
422 channel = u'/repo${}$/commit/{}'.format(
439 channel = u'/repo${}$/commit/{}'.format(
423 repo_name,
440 repo_name,
424 commit_obj.raw_id
441 commit_obj.raw_id
425 )
442 )
426 elif pull_request_obj:
443 elif pull_request_obj:
427 msg_url = pr_comment_url
444 msg_url = pr_comment_url
428 repo_name = pr_target_repo.repo_name
445 repo_name = pr_target_repo.repo_name
429 channel = u'/repo${}$/pr/{}'.format(
446 channel = u'/repo${}$/pr/{}'.format(
430 repo_name,
447 repo_name,
431 pull_request_id
448 pull_request_id
432 )
449 )
433
450
434 message = '<strong>{}</strong> {} - ' \
451 message = '<strong>{}</strong> {} - ' \
435 '<a onclick="window.location=\'{}\';' \
452 '<a onclick="window.location=\'{}\';' \
436 'window.location.reload()">' \
453 'window.location.reload()">' \
437 '<strong>{}</strong></a>'
454 '<strong>{}</strong></a>'
438 message = message.format(
455 message = message.format(
439 user.username, _('made a comment'), msg_url,
456 user.username, _('made a comment'), msg_url,
440 _('Show it now'))
457 _('Show it now'))
441
458
442 channelstream.post_message(
459 channelstream.post_message(
443 channel, message, user.username,
460 channel, message, user.username,
444 registry=get_current_registry())
461 registry=get_current_registry())
445
462
446 return comment
463 return comment
447
464
448 def delete(self, comment, auth_user):
465 def delete(self, comment, auth_user):
449 """
466 """
450 Deletes given comment
467 Deletes given comment
451 """
468 """
452 comment = self.__get_commit_comment(comment)
469 comment = self.__get_commit_comment(comment)
453 old_data = comment.get_api_data()
470 old_data = comment.get_api_data()
454 Session().delete(comment)
471 Session().delete(comment)
455
472
456 if comment.pull_request:
473 if comment.pull_request:
457 action = 'repo.pull_request.comment.delete'
474 action = 'repo.pull_request.comment.delete'
458 else:
475 else:
459 action = 'repo.commit.comment.delete'
476 action = 'repo.commit.comment.delete'
460
477
461 self._log_audit_action(
478 self._log_audit_action(
462 action, {'old_data': old_data}, auth_user, comment)
479 action, {'old_data': old_data}, auth_user, comment)
463
480
464 return comment
481 return comment
465
482
466 def get_all_comments(self, repo_id, revision=None, pull_request=None):
483 def get_all_comments(self, repo_id, revision=None, pull_request=None):
467 q = ChangesetComment.query()\
484 q = ChangesetComment.query()\
468 .filter(ChangesetComment.repo_id == repo_id)
485 .filter(ChangesetComment.repo_id == repo_id)
469 if revision:
486 if revision:
470 q = q.filter(ChangesetComment.revision == revision)
487 q = q.filter(ChangesetComment.revision == revision)
471 elif pull_request:
488 elif pull_request:
472 pull_request = self.__get_pull_request(pull_request)
489 pull_request = self.__get_pull_request(pull_request)
473 q = q.filter(ChangesetComment.pull_request == pull_request)
490 q = q.filter(ChangesetComment.pull_request == pull_request)
474 else:
491 else:
475 raise Exception('Please specify commit or pull_request')
492 raise Exception('Please specify commit or pull_request')
476 q = q.order_by(ChangesetComment.created_on)
493 q = q.order_by(ChangesetComment.created_on)
477 return q.all()
494 return q.all()
478
495
479 def get_url(self, comment, request=None, permalink=False):
496 def get_url(self, comment, request=None, permalink=False):
480 if not request:
497 if not request:
481 request = get_current_request()
498 request = get_current_request()
482
499
483 comment = self.__get_commit_comment(comment)
500 comment = self.__get_commit_comment(comment)
484 if comment.pull_request:
501 if comment.pull_request:
485 pull_request = comment.pull_request
502 pull_request = comment.pull_request
486 if permalink:
503 if permalink:
487 return request.route_url(
504 return request.route_url(
488 'pull_requests_global',
505 'pull_requests_global',
489 pull_request_id=pull_request.pull_request_id,
506 pull_request_id=pull_request.pull_request_id,
490 _anchor='comment-%s' % comment.comment_id)
507 _anchor='comment-%s' % comment.comment_id)
491 else:
508 else:
492 return request.route_url(
509 return request.route_url(
493 'pullrequest_show',
510 'pullrequest_show',
494 repo_name=safe_str(pull_request.target_repo.repo_name),
511 repo_name=safe_str(pull_request.target_repo.repo_name),
495 pull_request_id=pull_request.pull_request_id,
512 pull_request_id=pull_request.pull_request_id,
496 _anchor='comment-%s' % comment.comment_id)
513 _anchor='comment-%s' % comment.comment_id)
497
514
498 else:
515 else:
499 repo = comment.repo
516 repo = comment.repo
500 commit_id = comment.revision
517 commit_id = comment.revision
501
518
502 if permalink:
519 if permalink:
503 return request.route_url(
520 return request.route_url(
504 'repo_commit', repo_name=safe_str(repo.repo_id),
521 'repo_commit', repo_name=safe_str(repo.repo_id),
505 commit_id=commit_id,
522 commit_id=commit_id,
506 _anchor='comment-%s' % comment.comment_id)
523 _anchor='comment-%s' % comment.comment_id)
507
524
508 else:
525 else:
509 return request.route_url(
526 return request.route_url(
510 'repo_commit', repo_name=safe_str(repo.repo_name),
527 'repo_commit', repo_name=safe_str(repo.repo_name),
511 commit_id=commit_id,
528 commit_id=commit_id,
512 _anchor='comment-%s' % comment.comment_id)
529 _anchor='comment-%s' % comment.comment_id)
513
530
514 def get_comments(self, repo_id, revision=None, pull_request=None):
531 def get_comments(self, repo_id, revision=None, pull_request=None):
515 """
532 """
516 Gets main comments based on revision or pull_request_id
533 Gets main comments based on revision or pull_request_id
517
534
518 :param repo_id:
535 :param repo_id:
519 :param revision:
536 :param revision:
520 :param pull_request:
537 :param pull_request:
521 """
538 """
522
539
523 q = ChangesetComment.query()\
540 q = ChangesetComment.query()\
524 .filter(ChangesetComment.repo_id == repo_id)\
541 .filter(ChangesetComment.repo_id == repo_id)\
525 .filter(ChangesetComment.line_no == None)\
542 .filter(ChangesetComment.line_no == None)\
526 .filter(ChangesetComment.f_path == None)
543 .filter(ChangesetComment.f_path == None)
527 if revision:
544 if revision:
528 q = q.filter(ChangesetComment.revision == revision)
545 q = q.filter(ChangesetComment.revision == revision)
529 elif pull_request:
546 elif pull_request:
530 pull_request = self.__get_pull_request(pull_request)
547 pull_request = self.__get_pull_request(pull_request)
531 q = q.filter(ChangesetComment.pull_request == pull_request)
548 q = q.filter(ChangesetComment.pull_request == pull_request)
532 else:
549 else:
533 raise Exception('Please specify commit or pull_request')
550 raise Exception('Please specify commit or pull_request')
534 q = q.order_by(ChangesetComment.created_on)
551 q = q.order_by(ChangesetComment.created_on)
535 return q.all()
552 return q.all()
536
553
537 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
554 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
538 q = self._get_inline_comments_query(repo_id, revision, pull_request)
555 q = self._get_inline_comments_query(repo_id, revision, pull_request)
539 return self._group_comments_by_path_and_line_number(q)
556 return self._group_comments_by_path_and_line_number(q)
540
557
541 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
558 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
542 version=None):
559 version=None):
543 inline_cnt = 0
560 inline_cnt = 0
544 for fname, per_line_comments in inline_comments.iteritems():
561 for fname, per_line_comments in inline_comments.iteritems():
545 for lno, comments in per_line_comments.iteritems():
562 for lno, comments in per_line_comments.iteritems():
546 for comm in comments:
563 for comm in comments:
547 if not comm.outdated_at_version(version) and skip_outdated:
564 if not comm.outdated_at_version(version) and skip_outdated:
548 inline_cnt += 1
565 inline_cnt += 1
549
566
550 return inline_cnt
567 return inline_cnt
551
568
552 def get_outdated_comments(self, repo_id, pull_request):
569 def get_outdated_comments(self, repo_id, pull_request):
553 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
570 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
554 # of a pull request.
571 # of a pull request.
555 q = self._all_inline_comments_of_pull_request(pull_request)
572 q = self._all_inline_comments_of_pull_request(pull_request)
556 q = q.filter(
573 q = q.filter(
557 ChangesetComment.display_state ==
574 ChangesetComment.display_state ==
558 ChangesetComment.COMMENT_OUTDATED
575 ChangesetComment.COMMENT_OUTDATED
559 ).order_by(ChangesetComment.comment_id.asc())
576 ).order_by(ChangesetComment.comment_id.asc())
560
577
561 return self._group_comments_by_path_and_line_number(q)
578 return self._group_comments_by_path_and_line_number(q)
562
579
563 def _get_inline_comments_query(self, repo_id, revision, pull_request):
580 def _get_inline_comments_query(self, repo_id, revision, pull_request):
564 # TODO: johbo: Split this into two methods: One for PR and one for
581 # TODO: johbo: Split this into two methods: One for PR and one for
565 # commit.
582 # commit.
566 if revision:
583 if revision:
567 q = Session().query(ChangesetComment).filter(
584 q = Session().query(ChangesetComment).filter(
568 ChangesetComment.repo_id == repo_id,
585 ChangesetComment.repo_id == repo_id,
569 ChangesetComment.line_no != null(),
586 ChangesetComment.line_no != null(),
570 ChangesetComment.f_path != null(),
587 ChangesetComment.f_path != null(),
571 ChangesetComment.revision == revision)
588 ChangesetComment.revision == revision)
572
589
573 elif pull_request:
590 elif pull_request:
574 pull_request = self.__get_pull_request(pull_request)
591 pull_request = self.__get_pull_request(pull_request)
575 if not CommentsModel.use_outdated_comments(pull_request):
592 if not CommentsModel.use_outdated_comments(pull_request):
576 q = self._visible_inline_comments_of_pull_request(pull_request)
593 q = self._visible_inline_comments_of_pull_request(pull_request)
577 else:
594 else:
578 q = self._all_inline_comments_of_pull_request(pull_request)
595 q = self._all_inline_comments_of_pull_request(pull_request)
579
596
580 else:
597 else:
581 raise Exception('Please specify commit or pull_request_id')
598 raise Exception('Please specify commit or pull_request_id')
582 q = q.order_by(ChangesetComment.comment_id.asc())
599 q = q.order_by(ChangesetComment.comment_id.asc())
583 return q
600 return q
584
601
585 def _group_comments_by_path_and_line_number(self, q):
602 def _group_comments_by_path_and_line_number(self, q):
586 comments = q.all()
603 comments = q.all()
587 paths = collections.defaultdict(lambda: collections.defaultdict(list))
604 paths = collections.defaultdict(lambda: collections.defaultdict(list))
588 for co in comments:
605 for co in comments:
589 paths[co.f_path][co.line_no].append(co)
606 paths[co.f_path][co.line_no].append(co)
590 return paths
607 return paths
591
608
592 @classmethod
609 @classmethod
593 def needed_extra_diff_context(cls):
610 def needed_extra_diff_context(cls):
594 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
611 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
595
612
596 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
613 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
597 if not CommentsModel.use_outdated_comments(pull_request):
614 if not CommentsModel.use_outdated_comments(pull_request):
598 return
615 return
599
616
600 comments = self._visible_inline_comments_of_pull_request(pull_request)
617 comments = self._visible_inline_comments_of_pull_request(pull_request)
601 comments_to_outdate = comments.all()
618 comments_to_outdate = comments.all()
602
619
603 for comment in comments_to_outdate:
620 for comment in comments_to_outdate:
604 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
621 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
605
622
606 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
623 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
607 diff_line = _parse_comment_line_number(comment.line_no)
624 diff_line = _parse_comment_line_number(comment.line_no)
608
625
609 try:
626 try:
610 old_context = old_diff_proc.get_context_of_line(
627 old_context = old_diff_proc.get_context_of_line(
611 path=comment.f_path, diff_line=diff_line)
628 path=comment.f_path, diff_line=diff_line)
612 new_context = new_diff_proc.get_context_of_line(
629 new_context = new_diff_proc.get_context_of_line(
613 path=comment.f_path, diff_line=diff_line)
630 path=comment.f_path, diff_line=diff_line)
614 except (diffs.LineNotInDiffException,
631 except (diffs.LineNotInDiffException,
615 diffs.FileNotInDiffException):
632 diffs.FileNotInDiffException):
616 comment.display_state = ChangesetComment.COMMENT_OUTDATED
633 comment.display_state = ChangesetComment.COMMENT_OUTDATED
617 return
634 return
618
635
619 if old_context == new_context:
636 if old_context == new_context:
620 return
637 return
621
638
622 if self._should_relocate_diff_line(diff_line):
639 if self._should_relocate_diff_line(diff_line):
623 new_diff_lines = new_diff_proc.find_context(
640 new_diff_lines = new_diff_proc.find_context(
624 path=comment.f_path, context=old_context,
641 path=comment.f_path, context=old_context,
625 offset=self.DIFF_CONTEXT_BEFORE)
642 offset=self.DIFF_CONTEXT_BEFORE)
626 if not new_diff_lines:
643 if not new_diff_lines:
627 comment.display_state = ChangesetComment.COMMENT_OUTDATED
644 comment.display_state = ChangesetComment.COMMENT_OUTDATED
628 else:
645 else:
629 new_diff_line = self._choose_closest_diff_line(
646 new_diff_line = self._choose_closest_diff_line(
630 diff_line, new_diff_lines)
647 diff_line, new_diff_lines)
631 comment.line_no = _diff_to_comment_line_number(new_diff_line)
648 comment.line_no = _diff_to_comment_line_number(new_diff_line)
632 else:
649 else:
633 comment.display_state = ChangesetComment.COMMENT_OUTDATED
650 comment.display_state = ChangesetComment.COMMENT_OUTDATED
634
651
635 def _should_relocate_diff_line(self, diff_line):
652 def _should_relocate_diff_line(self, diff_line):
636 """
653 """
637 Checks if relocation shall be tried for the given `diff_line`.
654 Checks if relocation shall be tried for the given `diff_line`.
638
655
639 If a comment points into the first lines, then we can have a situation
656 If a comment points into the first lines, then we can have a situation
640 that after an update another line has been added on top. In this case
657 that after an update another line has been added on top. In this case
641 we would find the context still and move the comment around. This
658 we would find the context still and move the comment around. This
642 would be wrong.
659 would be wrong.
643 """
660 """
644 should_relocate = (
661 should_relocate = (
645 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
662 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
646 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
663 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
647 return should_relocate
664 return should_relocate
648
665
649 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
666 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
650 candidate = new_diff_lines[0]
667 candidate = new_diff_lines[0]
651 best_delta = _diff_line_delta(diff_line, candidate)
668 best_delta = _diff_line_delta(diff_line, candidate)
652 for new_diff_line in new_diff_lines[1:]:
669 for new_diff_line in new_diff_lines[1:]:
653 delta = _diff_line_delta(diff_line, new_diff_line)
670 delta = _diff_line_delta(diff_line, new_diff_line)
654 if delta < best_delta:
671 if delta < best_delta:
655 candidate = new_diff_line
672 candidate = new_diff_line
656 best_delta = delta
673 best_delta = delta
657 return candidate
674 return candidate
658
675
659 def _visible_inline_comments_of_pull_request(self, pull_request):
676 def _visible_inline_comments_of_pull_request(self, pull_request):
660 comments = self._all_inline_comments_of_pull_request(pull_request)
677 comments = self._all_inline_comments_of_pull_request(pull_request)
661 comments = comments.filter(
678 comments = comments.filter(
662 coalesce(ChangesetComment.display_state, '') !=
679 coalesce(ChangesetComment.display_state, '') !=
663 ChangesetComment.COMMENT_OUTDATED)
680 ChangesetComment.COMMENT_OUTDATED)
664 return comments
681 return comments
665
682
666 def _all_inline_comments_of_pull_request(self, pull_request):
683 def _all_inline_comments_of_pull_request(self, pull_request):
667 comments = Session().query(ChangesetComment)\
684 comments = Session().query(ChangesetComment)\
668 .filter(ChangesetComment.line_no != None)\
685 .filter(ChangesetComment.line_no != None)\
669 .filter(ChangesetComment.f_path != None)\
686 .filter(ChangesetComment.f_path != None)\
670 .filter(ChangesetComment.pull_request == pull_request)
687 .filter(ChangesetComment.pull_request == pull_request)
671 return comments
688 return comments
672
689
673 def _all_general_comments_of_pull_request(self, pull_request):
690 def _all_general_comments_of_pull_request(self, pull_request):
674 comments = Session().query(ChangesetComment)\
691 comments = Session().query(ChangesetComment)\
675 .filter(ChangesetComment.line_no == None)\
692 .filter(ChangesetComment.line_no == None)\
676 .filter(ChangesetComment.f_path == None)\
693 .filter(ChangesetComment.f_path == None)\
677 .filter(ChangesetComment.pull_request == pull_request)
694 .filter(ChangesetComment.pull_request == pull_request)
678 return comments
695 return comments
679
696
680 @staticmethod
697 @staticmethod
681 def use_outdated_comments(pull_request):
698 def use_outdated_comments(pull_request):
682 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
699 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
683 settings = settings_model.get_general_settings()
700 settings = settings_model.get_general_settings()
684 return settings.get('rhodecode_use_outdated_comments', False)
701 return settings.get('rhodecode_use_outdated_comments', False)
685
702
686
703
687 def _parse_comment_line_number(line_no):
704 def _parse_comment_line_number(line_no):
688 """
705 """
689 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
706 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
690 """
707 """
691 old_line = None
708 old_line = None
692 new_line = None
709 new_line = None
693 if line_no.startswith('o'):
710 if line_no.startswith('o'):
694 old_line = int(line_no[1:])
711 old_line = int(line_no[1:])
695 elif line_no.startswith('n'):
712 elif line_no.startswith('n'):
696 new_line = int(line_no[1:])
713 new_line = int(line_no[1:])
697 else:
714 else:
698 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
715 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
699 return diffs.DiffLineNumber(old_line, new_line)
716 return diffs.DiffLineNumber(old_line, new_line)
700
717
701
718
702 def _diff_to_comment_line_number(diff_line):
719 def _diff_to_comment_line_number(diff_line):
703 if diff_line.new is not None:
720 if diff_line.new is not None:
704 return u'n{}'.format(diff_line.new)
721 return u'n{}'.format(diff_line.new)
705 elif diff_line.old is not None:
722 elif diff_line.old is not None:
706 return u'o{}'.format(diff_line.old)
723 return u'o{}'.format(diff_line.old)
707 return u''
724 return u''
708
725
709
726
710 def _diff_line_delta(a, b):
727 def _diff_line_delta(a, b):
711 if None not in (a.new, b.new):
728 if None not in (a.new, b.new):
712 return abs(a.new - b.new)
729 return abs(a.new - b.new)
713 elif None not in (a.old, b.old):
730 elif None not in (a.old, b.old):
714 return abs(a.old - b.old)
731 return abs(a.old - b.old)
715 else:
732 else:
716 raise ValueError(
733 raise ValueError(
717 "Cannot compute delta between {} and {}".format(a, b))
734 "Cannot compute delta between {} and {}".format(a, b))
@@ -1,581 +1,585 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: 0.6;
7 @comment-outdated-opacity: 0.6;
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 {
22 padding: 5px 0;
23 }
24
21 tr.inline-comments div {
25 tr.inline-comments div {
22 max-width: 100%;
26 max-width: 100%;
23
27
24 p {
28 p {
25 white-space: normal;
29 white-space: normal;
26 }
30 }
27
31
28 code, pre, .code, dd {
32 code, pre, .code, dd {
29 overflow-x: auto;
33 overflow-x: auto;
30 width: 1062px;
34 width: 1062px;
31 }
35 }
32
36
33 dd {
37 dd {
34 width: auto;
38 width: auto;
35 }
39 }
36 }
40 }
37
41
38 #injected_page_comments {
42 #injected_page_comments {
39 .comment-previous-link,
43 .comment-previous-link,
40 .comment-next-link,
44 .comment-next-link,
41 .comment-links-divider {
45 .comment-links-divider {
42 display: none;
46 display: none;
43 }
47 }
44 }
48 }
45
49
46 .add-comment {
50 .add-comment {
47 margin-bottom: 10px;
51 margin-bottom: 10px;
48 }
52 }
49 .hide-comment-button .add-comment {
53 .hide-comment-button .add-comment {
50 display: none;
54 display: none;
51 }
55 }
52
56
53 .comment-bubble {
57 .comment-bubble {
54 color: @grey4;
58 color: @grey4;
55 margin-top: 4px;
59 margin-top: 4px;
56 margin-right: 30px;
60 margin-right: 30px;
57 visibility: hidden;
61 visibility: hidden;
58 }
62 }
59
63
60 .comment-label {
64 .comment-label {
61 float: left;
65 float: left;
62
66
63 padding: 0.4em 0.4em;
67 padding: 0.4em 0.4em;
64 margin: 3px 5px 0px -10px;
68 margin: 3px 5px 0px -10px;
65 display: inline-block;
69 display: inline-block;
66 min-height: 0;
70 min-height: 0;
67
71
68 text-align: center;
72 text-align: center;
69 font-size: 10px;
73 font-size: 10px;
70 line-height: .8em;
74 line-height: .8em;
71
75
72 font-family: @text-italic;
76 font-family: @text-italic;
73 font-style: italic;
77 font-style: italic;
74 background: #fff none;
78 background: #fff none;
75 color: @grey4;
79 color: @grey4;
76 border: 1px solid @grey4;
80 border: 1px solid @grey4;
77 white-space: nowrap;
81 white-space: nowrap;
78
82
79 text-transform: uppercase;
83 text-transform: uppercase;
80 min-width: 40px;
84 min-width: 40px;
81
85
82 &.todo {
86 &.todo {
83 color: @color5;
87 color: @color5;
84 font-style: italic;
88 font-style: italic;
85 font-weight: @text-bold-italic-weight;
89 font-weight: @text-bold-italic-weight;
86 font-family: @text-bold-italic;
90 font-family: @text-bold-italic;
87 }
91 }
88
92
89 .resolve {
93 .resolve {
90 cursor: pointer;
94 cursor: pointer;
91 text-decoration: underline;
95 text-decoration: underline;
92 }
96 }
93
97
94 .resolved {
98 .resolved {
95 text-decoration: line-through;
99 text-decoration: line-through;
96 color: @color1;
100 color: @color1;
97 }
101 }
98 .resolved a {
102 .resolved a {
99 text-decoration: line-through;
103 text-decoration: line-through;
100 color: @color1;
104 color: @color1;
101 }
105 }
102 .resolve-text {
106 .resolve-text {
103 color: @color1;
107 color: @color1;
104 margin: 2px 8px;
108 margin: 2px 8px;
105 font-family: @text-italic;
109 font-family: @text-italic;
106 font-style: italic;
110 font-style: italic;
107 }
111 }
108 }
112 }
109
113
110 .has-spacer-after {
114 .has-spacer-after {
111 &:after {
115 &:after {
112 content: ' | ';
116 content: ' | ';
113 color: @grey5;
117 color: @grey5;
114 }
118 }
115 }
119 }
116
120
117 .has-spacer-before {
121 .has-spacer-before {
118 &:before {
122 &:before {
119 content: ' | ';
123 content: ' | ';
120 color: @grey5;
124 color: @grey5;
121 }
125 }
122 }
126 }
123
127
124 .comment {
128 .comment {
125
129
126 &.comment-general {
130 &.comment-general {
127 border: 1px solid @grey5;
131 border: 1px solid @grey5;
128 padding: 5px 5px 5px 5px;
132 padding: 5px 5px 5px 5px;
129 }
133 }
130
134
131 margin: @padding 0;
135 margin: @padding 0;
132 padding: 4px 0 0 0;
136 padding: 4px 0 0 0;
133 line-height: 1em;
137 line-height: 1em;
134
138
135 .rc-user {
139 .rc-user {
136 min-width: 0;
140 min-width: 0;
137 margin: 0px .5em 0 0;
141 margin: 0px .5em 0 0;
138
142
139 .user {
143 .user {
140 display: inline;
144 display: inline;
141 }
145 }
142 }
146 }
143
147
144 .meta {
148 .meta {
145 position: relative;
149 position: relative;
146 width: 100%;
150 width: 100%;
147 border-bottom: 1px solid @grey5;
151 border-bottom: 1px solid @grey5;
148 margin: -5px 0px;
152 margin: -5px 0px;
149 line-height: 24px;
153 line-height: 24px;
150
154
151 &:hover .permalink {
155 &:hover .permalink {
152 visibility: visible;
156 visibility: visible;
153 color: @rcblue;
157 color: @rcblue;
154 }
158 }
155 }
159 }
156
160
157 .author,
161 .author,
158 .date {
162 .date {
159 display: inline;
163 display: inline;
160
164
161 &:after {
165 &:after {
162 content: ' | ';
166 content: ' | ';
163 color: @grey5;
167 color: @grey5;
164 }
168 }
165 }
169 }
166
170
167 .author-general img {
171 .author-general img {
168 top: 3px;
172 top: 3px;
169 }
173 }
170 .author-inline img {
174 .author-inline img {
171 top: 3px;
175 top: 3px;
172 }
176 }
173
177
174 .status-change,
178 .status-change,
175 .permalink,
179 .permalink,
176 .changeset-status-lbl {
180 .changeset-status-lbl {
177 display: inline;
181 display: inline;
178 }
182 }
179
183
180 .permalink {
184 .permalink {
181 visibility: hidden;
185 visibility: hidden;
182 }
186 }
183
187
184 .comment-links-divider {
188 .comment-links-divider {
185 display: inline;
189 display: inline;
186 }
190 }
187
191
188 .comment-links-block {
192 .comment-links-block {
189 float:right;
193 float:right;
190 text-align: right;
194 text-align: right;
191 min-width: 85px;
195 min-width: 85px;
192
196
193 [class^="icon-"]:before,
197 [class^="icon-"]:before,
194 [class*=" icon-"]:before {
198 [class*=" icon-"]:before {
195 margin-left: 0;
199 margin-left: 0;
196 margin-right: 0;
200 margin-right: 0;
197 }
201 }
198 }
202 }
199
203
200 .comment-previous-link {
204 .comment-previous-link {
201 display: inline-block;
205 display: inline-block;
202
206
203 .arrow_comment_link{
207 .arrow_comment_link{
204 cursor: pointer;
208 cursor: pointer;
205 i {
209 i {
206 font-size:10px;
210 font-size:10px;
207 }
211 }
208 }
212 }
209 .arrow_comment_link.disabled {
213 .arrow_comment_link.disabled {
210 cursor: default;
214 cursor: default;
211 color: @grey5;
215 color: @grey5;
212 }
216 }
213 }
217 }
214
218
215 .comment-next-link {
219 .comment-next-link {
216 display: inline-block;
220 display: inline-block;
217
221
218 .arrow_comment_link{
222 .arrow_comment_link{
219 cursor: pointer;
223 cursor: pointer;
220 i {
224 i {
221 font-size:10px;
225 font-size:10px;
222 }
226 }
223 }
227 }
224 .arrow_comment_link.disabled {
228 .arrow_comment_link.disabled {
225 cursor: default;
229 cursor: default;
226 color: @grey5;
230 color: @grey5;
227 }
231 }
228 }
232 }
229
233
230 .delete-comment {
234 .delete-comment {
231 display: inline-block;
235 display: inline-block;
232 color: @rcblue;
236 color: @rcblue;
233
237
234 &:hover {
238 &:hover {
235 cursor: pointer;
239 cursor: pointer;
236 }
240 }
237 }
241 }
238
242
239 .text {
243 .text {
240 clear: both;
244 clear: both;
241 .border-radius(@border-radius);
245 .border-radius(@border-radius);
242 .box-sizing(border-box);
246 .box-sizing(border-box);
243
247
244 .markdown-block p,
248 .markdown-block p,
245 .rst-block p {
249 .rst-block p {
246 margin: .5em 0 !important;
250 margin: .5em 0 !important;
247 // TODO: lisa: This is needed because of other rst !important rules :[
251 // TODO: lisa: This is needed because of other rst !important rules :[
248 }
252 }
249 }
253 }
250
254
251 .pr-version {
255 .pr-version {
252 float: left;
256 float: left;
253 margin: 0px 4px;
257 margin: 0px 4px;
254 }
258 }
255 .pr-version-inline {
259 .pr-version-inline {
256 float: left;
260 float: left;
257 margin: 0px 4px;
261 margin: 0px 4px;
258 }
262 }
259 .pr-version-num {
263 .pr-version-num {
260 font-size: 10px;
264 font-size: 10px;
261 }
265 }
262 }
266 }
263
267
264 @comment-padding: 5px;
268 @comment-padding: 5px;
265
269
266 .general-comments {
270 .general-comments {
267 .comment-outdated {
271 .comment-outdated {
268 opacity: @comment-outdated-opacity;
272 opacity: @comment-outdated-opacity;
269 }
273 }
270 }
274 }
271
275
272 .inline-comments {
276 .inline-comments {
273 border-radius: @border-radius;
277 border-radius: @border-radius;
274 .comment {
278 .comment {
275 margin: 0;
279 margin: 0;
276 border-radius: @border-radius;
280 border-radius: @border-radius;
277 }
281 }
278 .comment-outdated {
282 .comment-outdated {
279 opacity: @comment-outdated-opacity;
283 opacity: @comment-outdated-opacity;
280 }
284 }
281
285
282 .comment-inline {
286 .comment-inline {
283 background: white;
287 background: white;
284 padding: @comment-padding @comment-padding;
288 padding: @comment-padding @comment-padding;
285 border: @comment-padding solid @grey6;
289 border: @comment-padding solid @grey6;
286
290
287 .text {
291 .text {
288 border: none;
292 border: none;
289 }
293 }
290 .meta {
294 .meta {
291 border-bottom: 1px solid @grey6;
295 border-bottom: 1px solid @grey6;
292 margin: -5px 0px;
296 margin: -5px 0px;
293 line-height: 24px;
297 line-height: 24px;
294 }
298 }
295 }
299 }
296 .comment-selected {
300 .comment-selected {
297 border-left: 6px solid @comment-highlight-color;
301 border-left: 6px solid @comment-highlight-color;
298 }
302 }
299 .comment-inline-form {
303 .comment-inline-form {
300 padding: @comment-padding;
304 padding: @comment-padding;
301 display: none;
305 display: none;
302 }
306 }
303 .cb-comment-add-button {
307 .cb-comment-add-button {
304 margin: @comment-padding;
308 margin: @comment-padding;
305 }
309 }
306 /* hide add comment button when form is open */
310 /* hide add comment button when form is open */
307 .comment-inline-form-open ~ .cb-comment-add-button {
311 .comment-inline-form-open ~ .cb-comment-add-button {
308 display: none;
312 display: none;
309 }
313 }
310 .comment-inline-form-open {
314 .comment-inline-form-open {
311 display: block;
315 display: block;
312 }
316 }
313 /* hide add comment button when form but no comments */
317 /* hide add comment button when form but no comments */
314 .comment-inline-form:first-child + .cb-comment-add-button {
318 .comment-inline-form:first-child + .cb-comment-add-button {
315 display: none;
319 display: none;
316 }
320 }
317 /* hide add comment button when no comments or form */
321 /* hide add comment button when no comments or form */
318 .cb-comment-add-button:first-child {
322 .cb-comment-add-button:first-child {
319 display: none;
323 display: none;
320 }
324 }
321 /* hide add comment button when only comment is being deleted */
325 /* hide add comment button when only comment is being deleted */
322 .comment-deleting:first-child + .cb-comment-add-button {
326 .comment-deleting:first-child + .cb-comment-add-button {
323 display: none;
327 display: none;
324 }
328 }
325 }
329 }
326
330
327
331
328 .show-outdated-comments {
332 .show-outdated-comments {
329 display: inline;
333 display: inline;
330 color: @rcblue;
334 color: @rcblue;
331 }
335 }
332
336
333 // Comment Form
337 // Comment Form
334 div.comment-form {
338 div.comment-form {
335 margin-top: 20px;
339 margin-top: 20px;
336 }
340 }
337
341
338 .comment-form strong {
342 .comment-form strong {
339 display: block;
343 display: block;
340 margin-bottom: 15px;
344 margin-bottom: 15px;
341 }
345 }
342
346
343 .comment-form textarea {
347 .comment-form textarea {
344 width: 100%;
348 width: 100%;
345 height: 100px;
349 height: 100px;
346 font-family: @text-monospace;
350 font-family: @text-monospace;
347 }
351 }
348
352
349 form.comment-form {
353 form.comment-form {
350 margin-top: 10px;
354 margin-top: 10px;
351 margin-left: 10px;
355 margin-left: 10px;
352 }
356 }
353
357
354 .comment-inline-form .comment-block-ta,
358 .comment-inline-form .comment-block-ta,
355 .comment-form .comment-block-ta,
359 .comment-form .comment-block-ta,
356 .comment-form .preview-box {
360 .comment-form .preview-box {
357 .border-radius(@border-radius);
361 .border-radius(@border-radius);
358 .box-sizing(border-box);
362 .box-sizing(border-box);
359 background-color: white;
363 background-color: white;
360 }
364 }
361
365
362 .comment-form-submit {
366 .comment-form-submit {
363 margin-top: 5px;
367 margin-top: 5px;
364 margin-left: 525px;
368 margin-left: 525px;
365 }
369 }
366
370
367 .file-comments {
371 .file-comments {
368 display: none;
372 display: none;
369 }
373 }
370
374
371 .comment-form .preview-box.unloaded,
375 .comment-form .preview-box.unloaded,
372 .comment-inline-form .preview-box.unloaded {
376 .comment-inline-form .preview-box.unloaded {
373 height: 50px;
377 height: 50px;
374 text-align: center;
378 text-align: center;
375 padding: 20px;
379 padding: 20px;
376 background-color: white;
380 background-color: white;
377 }
381 }
378
382
379 .comment-footer {
383 .comment-footer {
380 position: relative;
384 position: relative;
381 width: 100%;
385 width: 100%;
382 min-height: 42px;
386 min-height: 42px;
383
387
384 .status_box,
388 .status_box,
385 .cancel-button {
389 .cancel-button {
386 float: left;
390 float: left;
387 display: inline-block;
391 display: inline-block;
388 }
392 }
389
393
390 .action-buttons {
394 .action-buttons {
391 float: right;
395 float: right;
392 display: inline-block;
396 display: inline-block;
393 }
397 }
394
398
395 .action-buttons-extra {
399 .action-buttons-extra {
396 display: inline-block;
400 display: inline-block;
397 }
401 }
398 }
402 }
399
403
400 .comment-form {
404 .comment-form {
401
405
402 .comment {
406 .comment {
403 margin-left: 10px;
407 margin-left: 10px;
404 }
408 }
405
409
406 .comment-help {
410 .comment-help {
407 color: @grey4;
411 color: @grey4;
408 padding: 5px 0 5px 0;
412 padding: 5px 0 5px 0;
409 }
413 }
410
414
411 .comment-title {
415 .comment-title {
412 padding: 5px 0 5px 0;
416 padding: 5px 0 5px 0;
413 }
417 }
414
418
415 .comment-button {
419 .comment-button {
416 display: inline-block;
420 display: inline-block;
417 }
421 }
418
422
419 .comment-button-input {
423 .comment-button-input {
420 margin-right: 0;
424 margin-right: 0;
421 }
425 }
422
426
423 .comment-footer {
427 .comment-footer {
424 margin-bottom: 110px;
428 margin-bottom: 110px;
425 margin-top: 10px;
429 margin-top: 10px;
426 }
430 }
427 }
431 }
428
432
429
433
430 .comment-form-login {
434 .comment-form-login {
431 .comment-help {
435 .comment-help {
432 padding: 0.7em; //same as the button
436 padding: 0.7em; //same as the button
433 }
437 }
434
438
435 div.clearfix {
439 div.clearfix {
436 clear: both;
440 clear: both;
437 width: 100%;
441 width: 100%;
438 display: block;
442 display: block;
439 }
443 }
440 }
444 }
441
445
442 .comment-type {
446 .comment-type {
443 margin: 0px;
447 margin: 0px;
444 border-radius: inherit;
448 border-radius: inherit;
445 border-color: @grey6;
449 border-color: @grey6;
446 }
450 }
447
451
448 .preview-box {
452 .preview-box {
449 min-height: 105px;
453 min-height: 105px;
450 margin-bottom: 15px;
454 margin-bottom: 15px;
451 background-color: white;
455 background-color: white;
452 .border-radius(@border-radius);
456 .border-radius(@border-radius);
453 .box-sizing(border-box);
457 .box-sizing(border-box);
454 }
458 }
455
459
456 .add-another-button {
460 .add-another-button {
457 margin-left: 10px;
461 margin-left: 10px;
458 margin-top: 10px;
462 margin-top: 10px;
459 margin-bottom: 10px;
463 margin-bottom: 10px;
460 }
464 }
461
465
462 .comment .buttons {
466 .comment .buttons {
463 float: right;
467 float: right;
464 margin: -1px 0px 0px 0px;
468 margin: -1px 0px 0px 0px;
465 }
469 }
466
470
467 // Inline Comment Form
471 // Inline Comment Form
468 .injected_diff .comment-inline-form,
472 .injected_diff .comment-inline-form,
469 .comment-inline-form {
473 .comment-inline-form {
470 background-color: white;
474 background-color: white;
471 margin-top: 10px;
475 margin-top: 10px;
472 margin-bottom: 20px;
476 margin-bottom: 20px;
473 }
477 }
474
478
475 .inline-form {
479 .inline-form {
476 padding: 10px 7px;
480 padding: 10px 7px;
477 }
481 }
478
482
479 .inline-form div {
483 .inline-form div {
480 max-width: 100%;
484 max-width: 100%;
481 }
485 }
482
486
483 .overlay {
487 .overlay {
484 display: none;
488 display: none;
485 position: absolute;
489 position: absolute;
486 width: 100%;
490 width: 100%;
487 text-align: center;
491 text-align: center;
488 vertical-align: middle;
492 vertical-align: middle;
489 font-size: 16px;
493 font-size: 16px;
490 background: none repeat scroll 0 0 white;
494 background: none repeat scroll 0 0 white;
491
495
492 &.submitting {
496 &.submitting {
493 display: block;
497 display: block;
494 opacity: 0.5;
498 opacity: 0.5;
495 z-index: 100;
499 z-index: 100;
496 }
500 }
497 }
501 }
498 .comment-inline-form .overlay.submitting .overlay-text {
502 .comment-inline-form .overlay.submitting .overlay-text {
499 margin-top: 5%;
503 margin-top: 5%;
500 }
504 }
501
505
502 .comment-inline-form .clearfix,
506 .comment-inline-form .clearfix,
503 .comment-form .clearfix {
507 .comment-form .clearfix {
504 .border-radius(@border-radius);
508 .border-radius(@border-radius);
505 margin: 0px;
509 margin: 0px;
506 }
510 }
507
511
508 .comment-inline-form .comment-footer {
512 .comment-inline-form .comment-footer {
509 margin: 10px 0px 0px 0px;
513 margin: 10px 0px 0px 0px;
510 }
514 }
511
515
512 .hide-inline-form-button {
516 .hide-inline-form-button {
513 margin-left: 5px;
517 margin-left: 5px;
514 }
518 }
515 .comment-button .hide-inline-form {
519 .comment-button .hide-inline-form {
516 background: white;
520 background: white;
517 }
521 }
518
522
519 .comment-area {
523 .comment-area {
520 padding: 8px 12px;
524 padding: 8px 12px;
521 border: 1px solid @grey5;
525 border: 1px solid @grey5;
522 .border-radius(@border-radius);
526 .border-radius(@border-radius);
523
527
524 .resolve-action {
528 .resolve-action {
525 padding: 1px 0px 0px 6px;
529 padding: 1px 0px 0px 6px;
526 }
530 }
527
531
528 }
532 }
529
533
530 .comment-area-header .nav-links {
534 .comment-area-header .nav-links {
531 display: flex;
535 display: flex;
532 flex-flow: row wrap;
536 flex-flow: row wrap;
533 -webkit-flex-flow: row wrap;
537 -webkit-flex-flow: row wrap;
534 width: 100%;
538 width: 100%;
535 }
539 }
536
540
537 .comment-area-footer {
541 .comment-area-footer {
538 display: flex;
542 display: flex;
539 }
543 }
540
544
541 .comment-footer .toolbar {
545 .comment-footer .toolbar {
542
546
543 }
547 }
544
548
545 .nav-links {
549 .nav-links {
546 padding: 0;
550 padding: 0;
547 margin: 0;
551 margin: 0;
548 list-style: none;
552 list-style: none;
549 height: auto;
553 height: auto;
550 border-bottom: 1px solid @grey5;
554 border-bottom: 1px solid @grey5;
551 }
555 }
552 .nav-links li {
556 .nav-links li {
553 display: inline-block;
557 display: inline-block;
554 list-style-type: none;
558 list-style-type: none;
555 }
559 }
556
560
557 .nav-links li a.disabled {
561 .nav-links li a.disabled {
558 cursor: not-allowed;
562 cursor: not-allowed;
559 }
563 }
560
564
561 .nav-links li.active a {
565 .nav-links li.active a {
562 border-bottom: 2px solid @rcblue;
566 border-bottom: 2px solid @rcblue;
563 color: #000;
567 color: #000;
564 font-weight: 600;
568 font-weight: 600;
565 }
569 }
566 .nav-links li a {
570 .nav-links li a {
567 display: inline-block;
571 display: inline-block;
568 padding: 0px 10px 5px 10px;
572 padding: 0px 10px 5px 10px;
569 margin-bottom: -1px;
573 margin-bottom: -1px;
570 font-size: 14px;
574 font-size: 14px;
571 line-height: 28px;
575 line-height: 28px;
572 color: #8f8f8f;
576 color: #8f8f8f;
573 border-bottom: 2px solid transparent;
577 border-bottom: 2px solid transparent;
574 }
578 }
575
579
576 .toolbar-text {
580 .toolbar-text {
577 float: left;
581 float: left;
578 margin: -5px 0px 0px 0px;
582 margin: -5px 0px 0px 0px;
579 font-size: 12px;
583 font-size: 12px;
580 }
584 }
581
585
@@ -1,2823 +1,2819 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'variables';
8 @import 'variables';
9 @import 'bootstrap-variables';
9 @import 'bootstrap-variables';
10 @import 'form-bootstrap';
10 @import 'form-bootstrap';
11 @import 'codemirror';
11 @import 'codemirror';
12 @import 'legacy_code_styles';
12 @import 'legacy_code_styles';
13 @import 'readme-box';
13 @import 'readme-box';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29
29
30 //--- BASE ------------------//
30 //--- BASE ------------------//
31 .noscript-error {
31 .noscript-error {
32 top: 0;
32 top: 0;
33 left: 0;
33 left: 0;
34 width: 100%;
34 width: 100%;
35 z-index: 101;
35 z-index: 101;
36 text-align: center;
36 text-align: center;
37 font-size: 120%;
37 font-size: 120%;
38 color: white;
38 color: white;
39 background-color: @alert2;
39 background-color: @alert2;
40 padding: 5px 0 5px 0;
40 padding: 5px 0 5px 0;
41 font-weight: @text-semibold-weight;
41 font-weight: @text-semibold-weight;
42 font-family: @text-semibold;
42 font-family: @text-semibold;
43 }
43 }
44
44
45 html {
45 html {
46 display: table;
46 display: table;
47 height: 100%;
47 height: 100%;
48 width: 100%;
48 width: 100%;
49 }
49 }
50
50
51 body {
51 body {
52 display: table-cell;
52 display: table-cell;
53 width: 100%;
53 width: 100%;
54 }
54 }
55
55
56 //--- LAYOUT ------------------//
56 //--- LAYOUT ------------------//
57
57
58 .hidden{
58 .hidden{
59 display: none !important;
59 display: none !important;
60 }
60 }
61
61
62 .box{
62 .box{
63 float: left;
63 float: left;
64 width: 100%;
64 width: 100%;
65 }
65 }
66
66
67 .browser-header {
67 .browser-header {
68 clear: both;
68 clear: both;
69 }
69 }
70 .main {
70 .main {
71 clear: both;
71 clear: both;
72 padding:0 0 @pagepadding;
72 padding:0 0 @pagepadding;
73 height: auto;
73 height: auto;
74
74
75 &:after { //clearfix
75 &:after { //clearfix
76 content:"";
76 content:"";
77 clear:both;
77 clear:both;
78 width:100%;
78 width:100%;
79 display:block;
79 display:block;
80 }
80 }
81 }
81 }
82
82
83 .action-link{
83 .action-link{
84 margin-left: @padding;
84 margin-left: @padding;
85 padding-left: @padding;
85 padding-left: @padding;
86 border-left: @border-thickness solid @border-default-color;
86 border-left: @border-thickness solid @border-default-color;
87 }
87 }
88
88
89 input + .action-link, .action-link.first{
89 input + .action-link, .action-link.first{
90 border-left: none;
90 border-left: none;
91 }
91 }
92
92
93 .action-link.last{
93 .action-link.last{
94 margin-right: @padding;
94 margin-right: @padding;
95 padding-right: @padding;
95 padding-right: @padding;
96 }
96 }
97
97
98 .action-link.active,
98 .action-link.active,
99 .action-link.active a{
99 .action-link.active a{
100 color: @grey4;
100 color: @grey4;
101 }
101 }
102
102
103 .action-link.disabled {
103 .action-link.disabled {
104 color: @grey4;
104 color: @grey4;
105 cursor: inherit;
105 cursor: inherit;
106 }
106 }
107
107
108 .clipboard-action {
108 .clipboard-action {
109 cursor: pointer;
109 cursor: pointer;
110 color: @grey4;
110 color: @grey4;
111 margin-left: 5px;
111 margin-left: 5px;
112
112
113 &:hover {
113 &:hover {
114 color: @grey2;
114 color: @grey2;
115 }
115 }
116 }
116 }
117
117
118 ul.simple-list{
118 ul.simple-list{
119 list-style: none;
119 list-style: none;
120 margin: 0;
120 margin: 0;
121 padding: 0;
121 padding: 0;
122 }
122 }
123
123
124 .main-content {
124 .main-content {
125 padding-bottom: @pagepadding;
125 padding-bottom: @pagepadding;
126 }
126 }
127
127
128 .wide-mode-wrapper {
128 .wide-mode-wrapper {
129 max-width:4000px !important;
129 max-width:4000px !important;
130 }
130 }
131
131
132 .wrapper {
132 .wrapper {
133 position: relative;
133 position: relative;
134 max-width: @wrapper-maxwidth;
134 max-width: @wrapper-maxwidth;
135 margin: 0 auto;
135 margin: 0 auto;
136 }
136 }
137
137
138 #content {
138 #content {
139 clear: both;
139 clear: both;
140 padding: 0 @contentpadding;
140 padding: 0 @contentpadding;
141 }
141 }
142
142
143 .advanced-settings-fields{
143 .advanced-settings-fields{
144 input{
144 input{
145 margin-left: @textmargin;
145 margin-left: @textmargin;
146 margin-right: @padding/2;
146 margin-right: @padding/2;
147 }
147 }
148 }
148 }
149
149
150 .cs_files_title {
150 .cs_files_title {
151 margin: @pagepadding 0 0;
151 margin: @pagepadding 0 0;
152 }
152 }
153
153
154 input.inline[type="file"] {
154 input.inline[type="file"] {
155 display: inline;
155 display: inline;
156 }
156 }
157
157
158 .error_page {
158 .error_page {
159 margin: 10% auto;
159 margin: 10% auto;
160
160
161 h1 {
161 h1 {
162 color: @grey2;
162 color: @grey2;
163 }
163 }
164
164
165 .alert {
165 .alert {
166 margin: @padding 0;
166 margin: @padding 0;
167 }
167 }
168
168
169 .error-branding {
169 .error-branding {
170 color: @grey4;
170 color: @grey4;
171 font-weight: @text-semibold-weight;
171 font-weight: @text-semibold-weight;
172 font-family: @text-semibold;
172 font-family: @text-semibold;
173 }
173 }
174
174
175 .error_message {
175 .error_message {
176 font-family: @text-regular;
176 font-family: @text-regular;
177 }
177 }
178
178
179 .sidebar {
179 .sidebar {
180 min-height: 275px;
180 min-height: 275px;
181 margin: 0;
181 margin: 0;
182 padding: 0 0 @sidebarpadding @sidebarpadding;
182 padding: 0 0 @sidebarpadding @sidebarpadding;
183 border: none;
183 border: none;
184 }
184 }
185
185
186 .main-content {
186 .main-content {
187 position: relative;
187 position: relative;
188 margin: 0 @sidebarpadding @sidebarpadding;
188 margin: 0 @sidebarpadding @sidebarpadding;
189 padding: 0 0 0 @sidebarpadding;
189 padding: 0 0 0 @sidebarpadding;
190 border-left: @border-thickness solid @grey5;
190 border-left: @border-thickness solid @grey5;
191
191
192 @media (max-width:767px) {
192 @media (max-width:767px) {
193 clear: both;
193 clear: both;
194 width: 100%;
194 width: 100%;
195 margin: 0;
195 margin: 0;
196 border: none;
196 border: none;
197 }
197 }
198 }
198 }
199
199
200 .inner-column {
200 .inner-column {
201 float: left;
201 float: left;
202 width: 29.75%;
202 width: 29.75%;
203 min-height: 150px;
203 min-height: 150px;
204 margin: @sidebarpadding 2% 0 0;
204 margin: @sidebarpadding 2% 0 0;
205 padding: 0 2% 0 0;
205 padding: 0 2% 0 0;
206 border-right: @border-thickness solid @grey5;
206 border-right: @border-thickness solid @grey5;
207
207
208 @media (max-width:767px) {
208 @media (max-width:767px) {
209 clear: both;
209 clear: both;
210 width: 100%;
210 width: 100%;
211 border: none;
211 border: none;
212 }
212 }
213
213
214 ul {
214 ul {
215 padding-left: 1.25em;
215 padding-left: 1.25em;
216 }
216 }
217
217
218 &:last-child {
218 &:last-child {
219 margin: @sidebarpadding 0 0;
219 margin: @sidebarpadding 0 0;
220 border: none;
220 border: none;
221 }
221 }
222
222
223 h4 {
223 h4 {
224 margin: 0 0 @padding;
224 margin: 0 0 @padding;
225 font-weight: @text-semibold-weight;
225 font-weight: @text-semibold-weight;
226 font-family: @text-semibold;
226 font-family: @text-semibold;
227 }
227 }
228 }
228 }
229 }
229 }
230 .error-page-logo {
230 .error-page-logo {
231 width: 130px;
231 width: 130px;
232 height: 160px;
232 height: 160px;
233 }
233 }
234
234
235 // HEADER
235 // HEADER
236 .header {
236 .header {
237
237
238 // TODO: johbo: Fix login pages, so that they work without a min-height
238 // TODO: johbo: Fix login pages, so that they work without a min-height
239 // for the header and then remove the min-height. I chose a smaller value
239 // for the header and then remove the min-height. I chose a smaller value
240 // intentionally here to avoid rendering issues in the main navigation.
240 // intentionally here to avoid rendering issues in the main navigation.
241 min-height: 49px;
241 min-height: 49px;
242 min-width: 1024px;
242 min-width: 1024px;
243
243
244 position: relative;
244 position: relative;
245 vertical-align: bottom;
245 vertical-align: bottom;
246 padding: 0 @header-padding;
246 padding: 0 @header-padding;
247 background-color: @grey1;
247 background-color: @grey1;
248 color: @grey5;
248 color: @grey5;
249
249
250 .title {
250 .title {
251 overflow: visible;
251 overflow: visible;
252 }
252 }
253
253
254 &:before,
254 &:before,
255 &:after {
255 &:after {
256 content: "";
256 content: "";
257 clear: both;
257 clear: both;
258 width: 100%;
258 width: 100%;
259 }
259 }
260
260
261 // TODO: johbo: Avoids breaking "Repositories" chooser
261 // TODO: johbo: Avoids breaking "Repositories" chooser
262 .select2-container .select2-choice .select2-arrow {
262 .select2-container .select2-choice .select2-arrow {
263 display: none;
263 display: none;
264 }
264 }
265 }
265 }
266
266
267 #header-inner {
267 #header-inner {
268 &.title {
268 &.title {
269 margin: 0;
269 margin: 0;
270 }
270 }
271 &:before,
271 &:before,
272 &:after {
272 &:after {
273 content: "";
273 content: "";
274 clear: both;
274 clear: both;
275 }
275 }
276 }
276 }
277
277
278 // Gists
278 // Gists
279 #files_data {
279 #files_data {
280 clear: both; //for firefox
280 clear: both; //for firefox
281 padding-top: 10px;
281 padding-top: 10px;
282 }
282 }
283
283
284 #gistid {
284 #gistid {
285 margin-right: @padding;
285 margin-right: @padding;
286 }
286 }
287
287
288 // Global Settings Editor
288 // Global Settings Editor
289 .textarea.editor {
289 .textarea.editor {
290 float: left;
290 float: left;
291 position: relative;
291 position: relative;
292 max-width: @texteditor-width;
292 max-width: @texteditor-width;
293
293
294 select {
294 select {
295 position: absolute;
295 position: absolute;
296 top:10px;
296 top:10px;
297 right:0;
297 right:0;
298 }
298 }
299
299
300 .CodeMirror {
300 .CodeMirror {
301 margin: 0;
301 margin: 0;
302 }
302 }
303
303
304 .help-block {
304 .help-block {
305 margin: 0 0 @padding;
305 margin: 0 0 @padding;
306 padding:.5em;
306 padding:.5em;
307 background-color: @grey6;
307 background-color: @grey6;
308 &.pre-formatting {
308 &.pre-formatting {
309 white-space: pre;
309 white-space: pre;
310 }
310 }
311 }
311 }
312 }
312 }
313
313
314 ul.auth_plugins {
314 ul.auth_plugins {
315 margin: @padding 0 @padding @legend-width;
315 margin: @padding 0 @padding @legend-width;
316 padding: 0;
316 padding: 0;
317
317
318 li {
318 li {
319 margin-bottom: @padding;
319 margin-bottom: @padding;
320 line-height: 1em;
320 line-height: 1em;
321 list-style-type: none;
321 list-style-type: none;
322
322
323 .auth_buttons .btn {
323 .auth_buttons .btn {
324 margin-right: @padding;
324 margin-right: @padding;
325 }
325 }
326
326
327 }
327 }
328 }
328 }
329
329
330
330
331 // My Account PR list
331 // My Account PR list
332
332
333 #show_closed {
333 #show_closed {
334 margin: 0 1em 0 0;
334 margin: 0 1em 0 0;
335 }
335 }
336
336
337 #pull_request_list_table {
337 #pull_request_list_table {
338 .closed {
338 .closed {
339 background-color: @grey6;
339 background-color: @grey6;
340 }
340 }
341
341
342 .state-creating,
342 .state-creating,
343 .state-updating,
343 .state-updating,
344 .state-merging
344 .state-merging
345 {
345 {
346 background-color: @grey6;
346 background-color: @grey6;
347 }
347 }
348
348
349 .td-status {
349 .td-status {
350 padding-left: .5em;
350 padding-left: .5em;
351 }
351 }
352 .log-container .truncate {
352 .log-container .truncate {
353 height: 2.75em;
353 height: 2.75em;
354 white-space: pre-line;
354 white-space: pre-line;
355 }
355 }
356 table.rctable .user {
356 table.rctable .user {
357 padding-left: 0;
357 padding-left: 0;
358 }
358 }
359 table.rctable {
359 table.rctable {
360 td.td-description,
360 td.td-description,
361 .rc-user {
361 .rc-user {
362 min-width: auto;
362 min-width: auto;
363 }
363 }
364 }
364 }
365 }
365 }
366
366
367 // Pull Requests
367 // Pull Requests
368
368
369 .pullrequests_section_head {
369 .pullrequests_section_head {
370 display: block;
370 display: block;
371 clear: both;
371 clear: both;
372 margin: @padding 0;
372 margin: @padding 0;
373 font-weight: @text-bold-weight;
373 font-weight: @text-bold-weight;
374 font-family: @text-bold;
374 font-family: @text-bold;
375 }
375 }
376
376
377 .pr-origininfo, .pr-targetinfo {
377 .pr-origininfo, .pr-targetinfo {
378 position: relative;
378 position: relative;
379
379
380 .tag {
380 .tag {
381 display: inline-block;
381 display: inline-block;
382 margin: 0 1em .5em 0;
382 margin: 0 1em .5em 0;
383 }
383 }
384
384
385 .clone-url {
385 .clone-url {
386 display: inline-block;
386 display: inline-block;
387 margin: 0 0 .5em 0;
387 margin: 0 0 .5em 0;
388 padding: 0;
388 padding: 0;
389 line-height: 1.2em;
389 line-height: 1.2em;
390 }
390 }
391 }
391 }
392
392
393 .pr-mergeinfo {
393 .pr-mergeinfo {
394 min-width: 95% !important;
394 min-width: 95% !important;
395 padding: 0 !important;
395 padding: 0 !important;
396 border: 0;
396 border: 0;
397 }
397 }
398 .pr-mergeinfo-copy {
398 .pr-mergeinfo-copy {
399 padding: 0 0;
399 padding: 0 0;
400 }
400 }
401
401
402 .pr-pullinfo {
402 .pr-pullinfo {
403 min-width: 95% !important;
403 min-width: 95% !important;
404 padding: 0 !important;
404 padding: 0 !important;
405 border: 0;
405 border: 0;
406 }
406 }
407 .pr-pullinfo-copy {
407 .pr-pullinfo-copy {
408 padding: 0 0;
408 padding: 0 0;
409 }
409 }
410
410
411
411
412 #pr-title-input {
412 #pr-title-input {
413 width: 72%;
413 width: 72%;
414 font-size: 1em;
414 font-size: 1em;
415 margin: 0;
415 margin: 0;
416 padding: 0 0 0 @padding/4;
416 padding: 0 0 0 @padding/4;
417 line-height: 1.7em;
417 line-height: 1.7em;
418 color: @text-color;
418 color: @text-color;
419 letter-spacing: .02em;
419 letter-spacing: .02em;
420 font-weight: @text-bold-weight;
420 font-weight: @text-bold-weight;
421 font-family: @text-bold;
421 font-family: @text-bold;
422 }
422 }
423
423
424 #pullrequest_title {
424 #pullrequest_title {
425 width: 100%;
425 width: 100%;
426 box-sizing: border-box;
426 box-sizing: border-box;
427 }
427 }
428
428
429 #pr_open_message {
429 #pr_open_message {
430 border: @border-thickness solid #fff;
430 border: @border-thickness solid #fff;
431 border-radius: @border-radius;
431 border-radius: @border-radius;
432 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
432 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
433 text-align: left;
433 text-align: left;
434 overflow: hidden;
434 overflow: hidden;
435 }
435 }
436
436
437 .pr-submit-button {
437 .pr-submit-button {
438 float: right;
438 float: right;
439 margin: 0 0 0 5px;
439 margin: 0 0 0 5px;
440 }
440 }
441
441
442 .pr-spacing-container {
442 .pr-spacing-container {
443 padding: 20px;
443 padding: 20px;
444 clear: both
444 clear: both
445 }
445 }
446
446
447 #pr-description-input {
447 #pr-description-input {
448 margin-bottom: 0;
448 margin-bottom: 0;
449 }
449 }
450
450
451 .pr-description-label {
451 .pr-description-label {
452 vertical-align: top;
452 vertical-align: top;
453 }
453 }
454
454
455 .perms_section_head {
455 .perms_section_head {
456 min-width: 625px;
456 min-width: 625px;
457
457
458 h2 {
458 h2 {
459 margin-bottom: 0;
459 margin-bottom: 0;
460 }
460 }
461
461
462 .label-checkbox {
462 .label-checkbox {
463 float: left;
463 float: left;
464 }
464 }
465
465
466 &.field {
466 &.field {
467 margin: @space 0 @padding;
467 margin: @space 0 @padding;
468 }
468 }
469
469
470 &:first-child.field {
470 &:first-child.field {
471 margin-top: 0;
471 margin-top: 0;
472
472
473 .label {
473 .label {
474 margin-top: 0;
474 margin-top: 0;
475 padding-top: 0;
475 padding-top: 0;
476 }
476 }
477
477
478 .radios {
478 .radios {
479 padding-top: 0;
479 padding-top: 0;
480 }
480 }
481 }
481 }
482
482
483 .radios {
483 .radios {
484 position: relative;
484 position: relative;
485 width: 505px;
485 width: 505px;
486 }
486 }
487 }
487 }
488
488
489 //--- MODULES ------------------//
489 //--- MODULES ------------------//
490
490
491
491
492 // Server Announcement
492 // Server Announcement
493 #server-announcement {
493 #server-announcement {
494 width: 95%;
494 width: 95%;
495 margin: @padding auto;
495 margin: @padding auto;
496 padding: @padding;
496 padding: @padding;
497 border-width: 2px;
497 border-width: 2px;
498 border-style: solid;
498 border-style: solid;
499 .border-radius(2px);
499 .border-radius(2px);
500 font-weight: @text-bold-weight;
500 font-weight: @text-bold-weight;
501 font-family: @text-bold;
501 font-family: @text-bold;
502
502
503 &.info { border-color: @alert4; background-color: @alert4-inner; }
503 &.info { border-color: @alert4; background-color: @alert4-inner; }
504 &.warning { border-color: @alert3; background-color: @alert3-inner; }
504 &.warning { border-color: @alert3; background-color: @alert3-inner; }
505 &.error { border-color: @alert2; background-color: @alert2-inner; }
505 &.error { border-color: @alert2; background-color: @alert2-inner; }
506 &.success { border-color: @alert1; background-color: @alert1-inner; }
506 &.success { border-color: @alert1; background-color: @alert1-inner; }
507 &.neutral { border-color: @grey3; background-color: @grey6; }
507 &.neutral { border-color: @grey3; background-color: @grey6; }
508 }
508 }
509
509
510 // Fixed Sidebar Column
510 // Fixed Sidebar Column
511 .sidebar-col-wrapper {
511 .sidebar-col-wrapper {
512 padding-left: @sidebar-all-width;
512 padding-left: @sidebar-all-width;
513
513
514 .sidebar {
514 .sidebar {
515 width: @sidebar-width;
515 width: @sidebar-width;
516 margin-left: -@sidebar-all-width;
516 margin-left: -@sidebar-all-width;
517 }
517 }
518 }
518 }
519
519
520 .sidebar-col-wrapper.scw-small {
520 .sidebar-col-wrapper.scw-small {
521 padding-left: @sidebar-small-all-width;
521 padding-left: @sidebar-small-all-width;
522
522
523 .sidebar {
523 .sidebar {
524 width: @sidebar-small-width;
524 width: @sidebar-small-width;
525 margin-left: -@sidebar-small-all-width;
525 margin-left: -@sidebar-small-all-width;
526 }
526 }
527 }
527 }
528
528
529
529
530 // FOOTER
530 // FOOTER
531 #footer {
531 #footer {
532 padding: 0;
532 padding: 0;
533 text-align: center;
533 text-align: center;
534 vertical-align: middle;
534 vertical-align: middle;
535 color: @grey2;
535 color: @grey2;
536 font-size: 11px;
536 font-size: 11px;
537
537
538 p {
538 p {
539 margin: 0;
539 margin: 0;
540 padding: 1em;
540 padding: 1em;
541 line-height: 1em;
541 line-height: 1em;
542 }
542 }
543
543
544 .server-instance { //server instance
544 .server-instance { //server instance
545 display: none;
545 display: none;
546 }
546 }
547
547
548 .title {
548 .title {
549 float: none;
549 float: none;
550 margin: 0 auto;
550 margin: 0 auto;
551 }
551 }
552 }
552 }
553
553
554 button.close {
554 button.close {
555 padding: 0;
555 padding: 0;
556 cursor: pointer;
556 cursor: pointer;
557 background: transparent;
557 background: transparent;
558 border: 0;
558 border: 0;
559 .box-shadow(none);
559 .box-shadow(none);
560 -webkit-appearance: none;
560 -webkit-appearance: none;
561 }
561 }
562
562
563 .close {
563 .close {
564 float: right;
564 float: right;
565 font-size: 21px;
565 font-size: 21px;
566 font-family: @text-bootstrap;
566 font-family: @text-bootstrap;
567 line-height: 1em;
567 line-height: 1em;
568 font-weight: bold;
568 font-weight: bold;
569 color: @grey2;
569 color: @grey2;
570
570
571 &:hover,
571 &:hover,
572 &:focus {
572 &:focus {
573 color: @grey1;
573 color: @grey1;
574 text-decoration: none;
574 text-decoration: none;
575 cursor: pointer;
575 cursor: pointer;
576 }
576 }
577 }
577 }
578
578
579 // GRID
579 // GRID
580 .sorting,
580 .sorting,
581 .sorting_desc,
581 .sorting_desc,
582 .sorting_asc {
582 .sorting_asc {
583 cursor: pointer;
583 cursor: pointer;
584 }
584 }
585 .sorting_desc:after {
585 .sorting_desc:after {
586 content: "\00A0\25B2";
586 content: "\00A0\25B2";
587 font-size: .75em;
587 font-size: .75em;
588 }
588 }
589 .sorting_asc:after {
589 .sorting_asc:after {
590 content: "\00A0\25BC";
590 content: "\00A0\25BC";
591 font-size: .68em;
591 font-size: .68em;
592 }
592 }
593
593
594
594
595 .user_auth_tokens {
595 .user_auth_tokens {
596
596
597 &.truncate {
597 &.truncate {
598 white-space: nowrap;
598 white-space: nowrap;
599 overflow: hidden;
599 overflow: hidden;
600 text-overflow: ellipsis;
600 text-overflow: ellipsis;
601 }
601 }
602
602
603 .fields .field .input {
603 .fields .field .input {
604 margin: 0;
604 margin: 0;
605 }
605 }
606
606
607 input#description {
607 input#description {
608 width: 100px;
608 width: 100px;
609 margin: 0;
609 margin: 0;
610 }
610 }
611
611
612 .drop-menu {
612 .drop-menu {
613 // TODO: johbo: Remove this, should work out of the box when
613 // TODO: johbo: Remove this, should work out of the box when
614 // having multiple inputs inline
614 // having multiple inputs inline
615 margin: 0 0 0 5px;
615 margin: 0 0 0 5px;
616 }
616 }
617 }
617 }
618 #user_list_table {
618 #user_list_table {
619 .closed {
619 .closed {
620 background-color: @grey6;
620 background-color: @grey6;
621 }
621 }
622 }
622 }
623
623
624
624
625 input, textarea {
625 input, textarea {
626 &.disabled {
626 &.disabled {
627 opacity: .5;
627 opacity: .5;
628 }
628 }
629
629
630 &:hover {
630 &:hover {
631 border-color: @grey3;
631 border-color: @grey3;
632 box-shadow: @button-shadow;
632 box-shadow: @button-shadow;
633 }
633 }
634
634
635 &:focus {
635 &:focus {
636 border-color: @rcblue;
636 border-color: @rcblue;
637 box-shadow: @button-shadow;
637 box-shadow: @button-shadow;
638 }
638 }
639 }
639 }
640
640
641 // remove extra padding in firefox
641 // remove extra padding in firefox
642 input::-moz-focus-inner { border:0; padding:0 }
642 input::-moz-focus-inner { border:0; padding:0 }
643
643
644 .adjacent input {
644 .adjacent input {
645 margin-bottom: @padding;
645 margin-bottom: @padding;
646 }
646 }
647
647
648 .permissions_boxes {
648 .permissions_boxes {
649 display: block;
649 display: block;
650 }
650 }
651
651
652 //FORMS
652 //FORMS
653
653
654 .medium-inline,
654 .medium-inline,
655 input#description.medium-inline {
655 input#description.medium-inline {
656 display: inline;
656 display: inline;
657 width: @medium-inline-input-width;
657 width: @medium-inline-input-width;
658 min-width: 100px;
658 min-width: 100px;
659 }
659 }
660
660
661 select {
661 select {
662 //reset
662 //reset
663 -webkit-appearance: none;
663 -webkit-appearance: none;
664 -moz-appearance: none;
664 -moz-appearance: none;
665
665
666 display: inline-block;
666 display: inline-block;
667 height: 28px;
667 height: 28px;
668 width: auto;
668 width: auto;
669 margin: 0 @padding @padding 0;
669 margin: 0 @padding @padding 0;
670 padding: 0 18px 0 8px;
670 padding: 0 18px 0 8px;
671 line-height:1em;
671 line-height:1em;
672 font-size: @basefontsize;
672 font-size: @basefontsize;
673 border: @border-thickness solid @grey5;
673 border: @border-thickness solid @grey5;
674 border-radius: @border-radius;
674 border-radius: @border-radius;
675 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
675 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
676 color: @grey4;
676 color: @grey4;
677 box-shadow: @button-shadow;
677 box-shadow: @button-shadow;
678
678
679 &:after {
679 &:after {
680 content: "\00A0\25BE";
680 content: "\00A0\25BE";
681 }
681 }
682
682
683 &:focus, &:hover {
683 &:focus, &:hover {
684 outline: none;
684 outline: none;
685 border-color: @grey4;
685 border-color: @grey4;
686 color: @rcdarkblue;
686 color: @rcdarkblue;
687 }
687 }
688 }
688 }
689
689
690 option {
690 option {
691 &:focus {
691 &:focus {
692 outline: none;
692 outline: none;
693 }
693 }
694 }
694 }
695
695
696 input,
696 input,
697 textarea {
697 textarea {
698 padding: @input-padding;
698 padding: @input-padding;
699 border: @input-border-thickness solid @border-highlight-color;
699 border: @input-border-thickness solid @border-highlight-color;
700 .border-radius (@border-radius);
700 .border-radius (@border-radius);
701 font-family: @text-light;
701 font-family: @text-light;
702 font-size: @basefontsize;
702 font-size: @basefontsize;
703
703
704 &.input-sm {
704 &.input-sm {
705 padding: 5px;
705 padding: 5px;
706 }
706 }
707
707
708 &#description {
708 &#description {
709 min-width: @input-description-minwidth;
709 min-width: @input-description-minwidth;
710 min-height: 1em;
710 min-height: 1em;
711 padding: 10px;
711 padding: 10px;
712 }
712 }
713 }
713 }
714
714
715 .field-sm {
715 .field-sm {
716 input,
716 input,
717 textarea {
717 textarea {
718 padding: 5px;
718 padding: 5px;
719 }
719 }
720 }
720 }
721
721
722 textarea {
722 textarea {
723 display: block;
723 display: block;
724 clear: both;
724 clear: both;
725 width: 100%;
725 width: 100%;
726 min-height: 100px;
726 min-height: 100px;
727 margin-bottom: @padding;
727 margin-bottom: @padding;
728 .box-sizing(border-box);
728 .box-sizing(border-box);
729 overflow: auto;
729 overflow: auto;
730 }
730 }
731
731
732 label {
732 label {
733 font-family: @text-light;
733 font-family: @text-light;
734 }
734 }
735
735
736 // GRAVATARS
736 // GRAVATARS
737 // centers gravatar on username to the right
737 // centers gravatar on username to the right
738
738
739 .gravatar {
739 .gravatar {
740 display: inline;
740 display: inline;
741 min-width: 16px;
741 min-width: 16px;
742 min-height: 16px;
742 min-height: 16px;
743 margin: -5px 0;
743 margin: -5px 0;
744 padding: 0;
744 padding: 0;
745 line-height: 1em;
745 line-height: 1em;
746 box-sizing: content-box;
746 box-sizing: content-box;
747 border-radius: 50%;
747 border-radius: 50%;
748
748
749 &.gravatar-large {
749 &.gravatar-large {
750 margin: -0.5em .25em -0.5em 0;
750 margin: -0.5em .25em -0.5em 0;
751 }
751 }
752
752
753 & + .user {
753 & + .user {
754 display: inline;
754 display: inline;
755 margin: 0;
755 margin: 0;
756 padding: 0 0 0 .17em;
756 padding: 0 0 0 .17em;
757 line-height: 1em;
757 line-height: 1em;
758 }
758 }
759 }
759 }
760
760
761 .user-inline-data {
761 .user-inline-data {
762 display: inline-block;
762 display: inline-block;
763 float: left;
763 float: left;
764 padding-left: .5em;
764 padding-left: .5em;
765 line-height: 1.3em;
765 line-height: 1.3em;
766 }
766 }
767
767
768 .rc-user { // gravatar + user wrapper
768 .rc-user { // gravatar + user wrapper
769 float: left;
769 float: left;
770 position: relative;
770 position: relative;
771 min-width: 100px;
771 min-width: 100px;
772 max-width: 200px;
772 max-width: 200px;
773 min-height: (@gravatar-size + @border-thickness * 2); // account for border
773 min-height: (@gravatar-size + @border-thickness * 2); // account for border
774 display: block;
774 display: block;
775 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
775 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
776
776
777
777
778 .gravatar {
778 .gravatar {
779 display: block;
779 display: block;
780 position: absolute;
780 position: absolute;
781 top: 0;
781 top: 0;
782 left: 0;
782 left: 0;
783 min-width: @gravatar-size;
783 min-width: @gravatar-size;
784 min-height: @gravatar-size;
784 min-height: @gravatar-size;
785 margin: 0;
785 margin: 0;
786 }
786 }
787
787
788 .user {
788 .user {
789 display: block;
789 display: block;
790 max-width: 175px;
790 max-width: 175px;
791 padding-top: 2px;
791 padding-top: 2px;
792 overflow: hidden;
792 overflow: hidden;
793 text-overflow: ellipsis;
793 text-overflow: ellipsis;
794 }
794 }
795 }
795 }
796
796
797 .gist-gravatar,
797 .gist-gravatar,
798 .journal_container {
798 .journal_container {
799 .gravatar-large {
799 .gravatar-large {
800 margin: 0 .5em -10px 0;
800 margin: 0 .5em -10px 0;
801 }
801 }
802 }
802 }
803
803
804
804
805 // ADMIN SETTINGS
805 // ADMIN SETTINGS
806
806
807 // Tag Patterns
807 // Tag Patterns
808 .tag_patterns {
808 .tag_patterns {
809 .tag_input {
809 .tag_input {
810 margin-bottom: @padding;
810 margin-bottom: @padding;
811 }
811 }
812 }
812 }
813
813
814 .locked_input {
814 .locked_input {
815 position: relative;
815 position: relative;
816
816
817 input {
817 input {
818 display: inline;
818 display: inline;
819 margin: 3px 5px 0px 0px;
819 margin: 3px 5px 0px 0px;
820 }
820 }
821
821
822 br {
822 br {
823 display: none;
823 display: none;
824 }
824 }
825
825
826 .error-message {
826 .error-message {
827 float: left;
827 float: left;
828 width: 100%;
828 width: 100%;
829 }
829 }
830
830
831 .lock_input_button {
831 .lock_input_button {
832 display: inline;
832 display: inline;
833 }
833 }
834
834
835 .help-block {
835 .help-block {
836 clear: both;
836 clear: both;
837 }
837 }
838 }
838 }
839
839
840 // Notifications
840 // Notifications
841
841
842 .notifications_buttons {
842 .notifications_buttons {
843 margin: 0 0 @space 0;
843 margin: 0 0 @space 0;
844 padding: 0;
844 padding: 0;
845
845
846 .btn {
846 .btn {
847 display: inline-block;
847 display: inline-block;
848 }
848 }
849 }
849 }
850
850
851 .notification-list {
851 .notification-list {
852
852
853 div {
853 div {
854 display: inline-block;
854 display: inline-block;
855 vertical-align: middle;
855 vertical-align: middle;
856 }
856 }
857
857
858 .container {
858 .container {
859 display: block;
859 display: block;
860 margin: 0 0 @padding 0;
860 margin: 0 0 @padding 0;
861 }
861 }
862
862
863 .delete-notifications {
863 .delete-notifications {
864 margin-left: @padding;
864 margin-left: @padding;
865 text-align: right;
865 text-align: right;
866 cursor: pointer;
866 cursor: pointer;
867 }
867 }
868
868
869 .read-notifications {
869 .read-notifications {
870 margin-left: @padding/2;
870 margin-left: @padding/2;
871 text-align: right;
871 text-align: right;
872 width: 35px;
872 width: 35px;
873 cursor: pointer;
873 cursor: pointer;
874 }
874 }
875
875
876 .icon-minus-sign {
876 .icon-minus-sign {
877 color: @alert2;
877 color: @alert2;
878 }
878 }
879
879
880 .icon-ok-sign {
880 .icon-ok-sign {
881 color: @alert1;
881 color: @alert1;
882 }
882 }
883 }
883 }
884
884
885 .user_settings {
885 .user_settings {
886 float: left;
886 float: left;
887 clear: both;
887 clear: both;
888 display: block;
888 display: block;
889 width: 100%;
889 width: 100%;
890
890
891 .gravatar_box {
891 .gravatar_box {
892 margin-bottom: @padding;
892 margin-bottom: @padding;
893
893
894 &:after {
894 &:after {
895 content: " ";
895 content: " ";
896 clear: both;
896 clear: both;
897 width: 100%;
897 width: 100%;
898 }
898 }
899 }
899 }
900
900
901 .fields .field {
901 .fields .field {
902 clear: both;
902 clear: both;
903 }
903 }
904 }
904 }
905
905
906 .advanced_settings {
906 .advanced_settings {
907 margin-bottom: @space;
907 margin-bottom: @space;
908
908
909 .help-block {
909 .help-block {
910 margin-left: 0;
910 margin-left: 0;
911 }
911 }
912
912
913 button + .help-block {
913 button + .help-block {
914 margin-top: @padding;
914 margin-top: @padding;
915 }
915 }
916 }
916 }
917
917
918 // admin settings radio buttons and labels
918 // admin settings radio buttons and labels
919 .label-2 {
919 .label-2 {
920 float: left;
920 float: left;
921 width: @label2-width;
921 width: @label2-width;
922
922
923 label {
923 label {
924 color: @grey1;
924 color: @grey1;
925 }
925 }
926 }
926 }
927 .checkboxes {
927 .checkboxes {
928 float: left;
928 float: left;
929 width: @checkboxes-width;
929 width: @checkboxes-width;
930 margin-bottom: @padding;
930 margin-bottom: @padding;
931
931
932 .checkbox {
932 .checkbox {
933 width: 100%;
933 width: 100%;
934
934
935 label {
935 label {
936 margin: 0;
936 margin: 0;
937 padding: 0;
937 padding: 0;
938 }
938 }
939 }
939 }
940
940
941 .checkbox + .checkbox {
941 .checkbox + .checkbox {
942 display: inline-block;
942 display: inline-block;
943 }
943 }
944
944
945 label {
945 label {
946 margin-right: 1em;
946 margin-right: 1em;
947 }
947 }
948 }
948 }
949
949
950 // CHANGELOG
950 // CHANGELOG
951 .container_header {
951 .container_header {
952 float: left;
952 float: left;
953 display: block;
953 display: block;
954 width: 100%;
954 width: 100%;
955 margin: @padding 0 @padding;
955 margin: @padding 0 @padding;
956
956
957 #filter_changelog {
957 #filter_changelog {
958 float: left;
958 float: left;
959 margin-right: @padding;
959 margin-right: @padding;
960 }
960 }
961
961
962 .breadcrumbs_light {
962 .breadcrumbs_light {
963 display: inline-block;
963 display: inline-block;
964 }
964 }
965 }
965 }
966
966
967 .info_box {
967 .info_box {
968 float: right;
968 float: right;
969 }
969 }
970
970
971
971
972
972
973 #graph_content{
973 #graph_content{
974
974
975 // adjust for table headers so that graph renders properly
975 // adjust for table headers so that graph renders properly
976 // #graph_nodes padding - table cell padding
976 // #graph_nodes padding - table cell padding
977 padding-top: (@space - (@basefontsize * 2.4));
977 padding-top: (@space - (@basefontsize * 2.4));
978
978
979 &.graph_full_width {
979 &.graph_full_width {
980 width: 100%;
980 width: 100%;
981 max-width: 100%;
981 max-width: 100%;
982 }
982 }
983 }
983 }
984
984
985 #graph {
985 #graph {
986
986
987 .pagination-left {
987 .pagination-left {
988 float: left;
988 float: left;
989 clear: both;
989 clear: both;
990 }
990 }
991
991
992 .log-container {
992 .log-container {
993 max-width: 345px;
993 max-width: 345px;
994
994
995 .message{
995 .message{
996 max-width: 340px;
996 max-width: 340px;
997 }
997 }
998 }
998 }
999
999
1000 .graph-col-wrapper {
1000 .graph-col-wrapper {
1001
1001
1002 #graph_nodes {
1002 #graph_nodes {
1003 width: 100px;
1003 width: 100px;
1004 position: absolute;
1004 position: absolute;
1005 left: 70px;
1005 left: 70px;
1006 z-index: -1;
1006 z-index: -1;
1007 }
1007 }
1008 }
1008 }
1009
1009
1010 .load-more-commits {
1010 .load-more-commits {
1011 text-align: center;
1011 text-align: center;
1012 }
1012 }
1013 .load-more-commits:hover {
1013 .load-more-commits:hover {
1014 background-color: @grey7;
1014 background-color: @grey7;
1015 }
1015 }
1016 .load-more-commits {
1016 .load-more-commits {
1017 a {
1017 a {
1018 display: block;
1018 display: block;
1019 }
1019 }
1020 }
1020 }
1021 }
1021 }
1022
1022
1023 .obsolete-toggle {
1023 .obsolete-toggle {
1024 line-height: 30px;
1024 line-height: 30px;
1025 margin-left: -15px;
1025 margin-left: -15px;
1026 }
1026 }
1027
1027
1028 #rev_range_container, #rev_range_clear, #rev_range_more {
1028 #rev_range_container, #rev_range_clear, #rev_range_more {
1029 margin-top: -5px;
1029 margin-top: -5px;
1030 margin-bottom: -5px;
1030 margin-bottom: -5px;
1031 }
1031 }
1032
1032
1033 #filter_changelog {
1033 #filter_changelog {
1034 float: left;
1034 float: left;
1035 }
1035 }
1036
1036
1037
1037
1038 //--- THEME ------------------//
1038 //--- THEME ------------------//
1039
1039
1040 #logo {
1040 #logo {
1041 float: left;
1041 float: left;
1042 margin: 9px 0 0 0;
1042 margin: 9px 0 0 0;
1043
1043
1044 .header {
1044 .header {
1045 background-color: transparent;
1045 background-color: transparent;
1046 }
1046 }
1047
1047
1048 a {
1048 a {
1049 display: inline-block;
1049 display: inline-block;
1050 }
1050 }
1051
1051
1052 img {
1052 img {
1053 height:30px;
1053 height:30px;
1054 }
1054 }
1055 }
1055 }
1056
1056
1057 .logo-wrapper {
1057 .logo-wrapper {
1058 float:left;
1058 float:left;
1059 }
1059 }
1060
1060
1061 .branding {
1061 .branding {
1062 float: left;
1062 float: left;
1063 padding: 9px 2px;
1063 padding: 9px 2px;
1064 line-height: 1em;
1064 line-height: 1em;
1065 font-size: @navigation-fontsize;
1065 font-size: @navigation-fontsize;
1066
1066
1067 a {
1067 a {
1068 color: @grey5
1068 color: @grey5
1069 }
1069 }
1070 @media screen and (max-width: 1200px) {
1070 @media screen and (max-width: 1200px) {
1071 display: none;
1071 display: none;
1072 }
1072 }
1073 }
1073 }
1074
1074
1075 img {
1075 img {
1076 border: none;
1076 border: none;
1077 outline: none;
1077 outline: none;
1078 }
1078 }
1079 user-profile-header
1079 user-profile-header
1080 label {
1080 label {
1081
1081
1082 input[type="checkbox"] {
1082 input[type="checkbox"] {
1083 margin-right: 1em;
1083 margin-right: 1em;
1084 }
1084 }
1085 input[type="radio"] {
1085 input[type="radio"] {
1086 margin-right: 1em;
1086 margin-right: 1em;
1087 }
1087 }
1088 }
1088 }
1089
1089
1090 .review-status {
1090 .review-status {
1091 &.under_review {
1091 &.under_review {
1092 color: @alert3;
1092 color: @alert3;
1093 }
1093 }
1094 &.approved {
1094 &.approved {
1095 color: @alert1;
1095 color: @alert1;
1096 }
1096 }
1097 &.rejected,
1097 &.rejected,
1098 &.forced_closed{
1098 &.forced_closed{
1099 color: @alert2;
1099 color: @alert2;
1100 }
1100 }
1101 &.not_reviewed {
1101 &.not_reviewed {
1102 color: @grey5;
1102 color: @grey5;
1103 }
1103 }
1104 }
1104 }
1105
1105
1106 .review-status-under_review {
1106 .review-status-under_review {
1107 color: @alert3;
1107 color: @alert3;
1108 }
1108 }
1109 .status-tag-under_review {
1109 .status-tag-under_review {
1110 border-color: @alert3;
1110 border-color: @alert3;
1111 }
1111 }
1112
1112
1113 .review-status-approved {
1113 .review-status-approved {
1114 color: @alert1;
1114 color: @alert1;
1115 }
1115 }
1116 .status-tag-approved {
1116 .status-tag-approved {
1117 border-color: @alert1;
1117 border-color: @alert1;
1118 }
1118 }
1119
1119
1120 .review-status-rejected,
1120 .review-status-rejected,
1121 .review-status-forced_closed {
1121 .review-status-forced_closed {
1122 color: @alert2;
1122 color: @alert2;
1123 }
1123 }
1124 .status-tag-rejected,
1124 .status-tag-rejected,
1125 .status-tag-forced_closed {
1125 .status-tag-forced_closed {
1126 border-color: @alert2;
1126 border-color: @alert2;
1127 }
1127 }
1128
1128
1129 .review-status-not_reviewed {
1129 .review-status-not_reviewed {
1130 color: @grey5;
1130 color: @grey5;
1131 }
1131 }
1132 .status-tag-not_reviewed {
1132 .status-tag-not_reviewed {
1133 border-color: @grey5;
1133 border-color: @grey5;
1134 }
1134 }
1135
1135
1136 .test_pattern_preview {
1136 .test_pattern_preview {
1137 margin: @space 0;
1137 margin: @space 0;
1138
1138
1139 p {
1139 p {
1140 margin-bottom: 0;
1140 margin-bottom: 0;
1141 border-bottom: @border-thickness solid @border-default-color;
1141 border-bottom: @border-thickness solid @border-default-color;
1142 color: @grey3;
1142 color: @grey3;
1143 }
1143 }
1144
1144
1145 .btn {
1145 .btn {
1146 margin-bottom: @padding;
1146 margin-bottom: @padding;
1147 }
1147 }
1148 }
1148 }
1149 #test_pattern_result {
1149 #test_pattern_result {
1150 display: none;
1150 display: none;
1151 &:extend(pre);
1151 &:extend(pre);
1152 padding: .9em;
1152 padding: .9em;
1153 color: @grey3;
1153 color: @grey3;
1154 background-color: @grey7;
1154 background-color: @grey7;
1155 border-right: @border-thickness solid @border-default-color;
1155 border-right: @border-thickness solid @border-default-color;
1156 border-bottom: @border-thickness solid @border-default-color;
1156 border-bottom: @border-thickness solid @border-default-color;
1157 border-left: @border-thickness solid @border-default-color;
1157 border-left: @border-thickness solid @border-default-color;
1158 }
1158 }
1159
1159
1160 #repo_vcs_settings {
1160 #repo_vcs_settings {
1161 #inherit_overlay_vcs_default {
1161 #inherit_overlay_vcs_default {
1162 display: none;
1162 display: none;
1163 }
1163 }
1164 #inherit_overlay_vcs_custom {
1164 #inherit_overlay_vcs_custom {
1165 display: custom;
1165 display: custom;
1166 }
1166 }
1167 &.inherited {
1167 &.inherited {
1168 #inherit_overlay_vcs_default {
1168 #inherit_overlay_vcs_default {
1169 display: block;
1169 display: block;
1170 }
1170 }
1171 #inherit_overlay_vcs_custom {
1171 #inherit_overlay_vcs_custom {
1172 display: none;
1172 display: none;
1173 }
1173 }
1174 }
1174 }
1175 }
1175 }
1176
1176
1177 .issue-tracker-link {
1177 .issue-tracker-link {
1178 color: @rcblue;
1178 color: @rcblue;
1179 }
1179 }
1180
1180
1181 // Issue Tracker Table Show/Hide
1181 // Issue Tracker Table Show/Hide
1182 #repo_issue_tracker {
1182 #repo_issue_tracker {
1183 #inherit_overlay {
1183 #inherit_overlay {
1184 display: none;
1184 display: none;
1185 }
1185 }
1186 #custom_overlay {
1186 #custom_overlay {
1187 display: custom;
1187 display: custom;
1188 }
1188 }
1189 &.inherited {
1189 &.inherited {
1190 #inherit_overlay {
1190 #inherit_overlay {
1191 display: block;
1191 display: block;
1192 }
1192 }
1193 #custom_overlay {
1193 #custom_overlay {
1194 display: none;
1194 display: none;
1195 }
1195 }
1196 }
1196 }
1197 }
1197 }
1198 table.issuetracker {
1198 table.issuetracker {
1199 &.readonly {
1199 &.readonly {
1200 tr, td {
1200 tr, td {
1201 color: @grey3;
1201 color: @grey3;
1202 }
1202 }
1203 }
1203 }
1204 .edit {
1204 .edit {
1205 display: none;
1205 display: none;
1206 }
1206 }
1207 .editopen {
1207 .editopen {
1208 .edit {
1208 .edit {
1209 display: inline;
1209 display: inline;
1210 }
1210 }
1211 .entry {
1211 .entry {
1212 display: none;
1212 display: none;
1213 }
1213 }
1214 }
1214 }
1215 tr td.td-action {
1215 tr td.td-action {
1216 min-width: 117px;
1216 min-width: 117px;
1217 }
1217 }
1218 td input {
1218 td input {
1219 max-width: none;
1219 max-width: none;
1220 min-width: 30px;
1220 min-width: 30px;
1221 width: 80%;
1221 width: 80%;
1222 }
1222 }
1223 .issuetracker_pref input {
1223 .issuetracker_pref input {
1224 width: 40%;
1224 width: 40%;
1225 }
1225 }
1226 input.edit_issuetracker_update {
1226 input.edit_issuetracker_update {
1227 margin-right: 0;
1227 margin-right: 0;
1228 width: auto;
1228 width: auto;
1229 }
1229 }
1230 }
1230 }
1231
1231
1232 table.integrations {
1232 table.integrations {
1233 .td-icon {
1233 .td-icon {
1234 width: 20px;
1234 width: 20px;
1235 .integration-icon {
1235 .integration-icon {
1236 height: 20px;
1236 height: 20px;
1237 width: 20px;
1237 width: 20px;
1238 }
1238 }
1239 }
1239 }
1240 }
1240 }
1241
1241
1242 .integrations {
1242 .integrations {
1243 a.integration-box {
1243 a.integration-box {
1244 color: @text-color;
1244 color: @text-color;
1245 &:hover {
1245 &:hover {
1246 .panel {
1246 .panel {
1247 background: #fbfbfb;
1247 background: #fbfbfb;
1248 }
1248 }
1249 }
1249 }
1250 .integration-icon {
1250 .integration-icon {
1251 width: 30px;
1251 width: 30px;
1252 height: 30px;
1252 height: 30px;
1253 margin-right: 20px;
1253 margin-right: 20px;
1254 float: left;
1254 float: left;
1255 }
1255 }
1256
1256
1257 .panel-body {
1257 .panel-body {
1258 padding: 10px;
1258 padding: 10px;
1259 }
1259 }
1260 .panel {
1260 .panel {
1261 margin-bottom: 10px;
1261 margin-bottom: 10px;
1262 }
1262 }
1263 h2 {
1263 h2 {
1264 display: inline-block;
1264 display: inline-block;
1265 margin: 0;
1265 margin: 0;
1266 min-width: 140px;
1266 min-width: 140px;
1267 }
1267 }
1268 }
1268 }
1269 a.integration-box.dummy-integration {
1269 a.integration-box.dummy-integration {
1270 color: @grey4
1270 color: @grey4
1271 }
1271 }
1272 }
1272 }
1273
1273
1274 //Permissions Settings
1274 //Permissions Settings
1275 #add_perm {
1275 #add_perm {
1276 margin: 0 0 @padding;
1276 margin: 0 0 @padding;
1277 cursor: pointer;
1277 cursor: pointer;
1278 }
1278 }
1279
1279
1280 .perm_ac {
1280 .perm_ac {
1281 input {
1281 input {
1282 width: 95%;
1282 width: 95%;
1283 }
1283 }
1284 }
1284 }
1285
1285
1286 .autocomplete-suggestions {
1286 .autocomplete-suggestions {
1287 width: auto !important; // overrides autocomplete.js
1287 width: auto !important; // overrides autocomplete.js
1288 min-width: 278px;
1288 min-width: 278px;
1289 margin: 0;
1289 margin: 0;
1290 border: @border-thickness solid @grey5;
1290 border: @border-thickness solid @grey5;
1291 border-radius: @border-radius;
1291 border-radius: @border-radius;
1292 color: @grey2;
1292 color: @grey2;
1293 background-color: white;
1293 background-color: white;
1294 }
1294 }
1295
1295
1296 .autocomplete-qfilter-suggestions {
1296 .autocomplete-qfilter-suggestions {
1297 width: auto !important; // overrides autocomplete.js
1297 width: auto !important; // overrides autocomplete.js
1298 max-height: 100% !important;
1298 max-height: 100% !important;
1299 min-width: 376px;
1299 min-width: 376px;
1300 margin: 0;
1300 margin: 0;
1301 border: @border-thickness solid @grey5;
1301 border: @border-thickness solid @grey5;
1302 color: @grey2;
1302 color: @grey2;
1303 background-color: white;
1303 background-color: white;
1304 }
1304 }
1305
1305
1306 .autocomplete-selected {
1306 .autocomplete-selected {
1307 background: #F0F0F0;
1307 background: #F0F0F0;
1308 }
1308 }
1309
1309
1310 .ac-container-wrap {
1310 .ac-container-wrap {
1311 margin: 0;
1311 margin: 0;
1312 padding: 8px;
1312 padding: 8px;
1313 border-bottom: @border-thickness solid @grey5;
1313 border-bottom: @border-thickness solid @grey5;
1314 list-style-type: none;
1314 list-style-type: none;
1315 cursor: pointer;
1315 cursor: pointer;
1316
1316
1317 &:hover {
1317 &:hover {
1318 background-color: @grey7;
1318 background-color: @grey7;
1319 }
1319 }
1320
1320
1321 img {
1321 img {
1322 height: @gravatar-size;
1322 height: @gravatar-size;
1323 width: @gravatar-size;
1323 width: @gravatar-size;
1324 margin-right: 1em;
1324 margin-right: 1em;
1325 }
1325 }
1326
1326
1327 strong {
1327 strong {
1328 font-weight: normal;
1328 font-weight: normal;
1329 }
1329 }
1330 }
1330 }
1331
1331
1332 // Settings Dropdown
1332 // Settings Dropdown
1333 .user-menu .container {
1333 .user-menu .container {
1334 padding: 0 4px;
1334 padding: 0 4px;
1335 margin: 0;
1335 margin: 0;
1336 }
1336 }
1337
1337
1338 .user-menu .gravatar {
1338 .user-menu .gravatar {
1339 cursor: pointer;
1339 cursor: pointer;
1340 }
1340 }
1341
1341
1342 .codeblock {
1342 .codeblock {
1343 margin-bottom: @padding;
1343 margin-bottom: @padding;
1344 clear: both;
1344 clear: both;
1345
1345
1346 .stats {
1346 .stats {
1347 overflow: hidden;
1347 overflow: hidden;
1348 }
1348 }
1349
1349
1350 .message{
1350 .message{
1351 textarea{
1351 textarea{
1352 margin: 0;
1352 margin: 0;
1353 }
1353 }
1354 }
1354 }
1355
1355
1356 .code-header {
1356 .code-header {
1357 .stats {
1357 .stats {
1358 line-height: 2em;
1358 line-height: 2em;
1359
1359
1360 .revision_id {
1360 .revision_id {
1361 margin-left: 0;
1361 margin-left: 0;
1362 }
1362 }
1363 .buttons {
1363 .buttons {
1364 padding-right: 0;
1364 padding-right: 0;
1365 }
1365 }
1366 }
1366 }
1367
1367
1368 .item{
1368 .item{
1369 margin-right: 0.5em;
1369 margin-right: 0.5em;
1370 }
1370 }
1371 }
1371 }
1372
1372
1373 #editor_container {
1373 #editor_container {
1374 position: relative;
1374 position: relative;
1375 margin: @padding 10px;
1375 margin: @padding 10px;
1376 }
1376 }
1377 }
1377 }
1378
1378
1379 #file_history_container {
1379 #file_history_container {
1380 display: none;
1380 display: none;
1381 }
1381 }
1382
1382
1383 .file-history-inner {
1383 .file-history-inner {
1384 margin-bottom: 10px;
1384 margin-bottom: 10px;
1385 }
1385 }
1386
1386
1387 // Pull Requests
1387 // Pull Requests
1388 .summary-details {
1388 .summary-details {
1389 width: 72%;
1389 width: 72%;
1390 }
1390 }
1391 .pr-summary {
1391 .pr-summary {
1392 border-bottom: @border-thickness solid @grey5;
1392 border-bottom: @border-thickness solid @grey5;
1393 margin-bottom: @space;
1393 margin-bottom: @space;
1394 }
1394 }
1395 .reviewers-title {
1395 .reviewers-title {
1396 width: 25%;
1396 width: 25%;
1397 min-width: 200px;
1397 min-width: 200px;
1398 }
1398 }
1399 .reviewers {
1399 .reviewers {
1400 width: 25%;
1400 width: 25%;
1401 min-width: 200px;
1401 min-width: 200px;
1402 }
1402 }
1403 .reviewers ul li {
1403 .reviewers ul li {
1404 position: relative;
1404 position: relative;
1405 width: 100%;
1405 width: 100%;
1406 padding-bottom: 8px;
1406 padding-bottom: 8px;
1407 list-style-type: none;
1407 list-style-type: none;
1408 }
1408 }
1409
1409
1410 .reviewer_entry {
1410 .reviewer_entry {
1411 min-height: 55px;
1411 min-height: 55px;
1412 }
1412 }
1413
1413
1414 .reviewers_member {
1414 .reviewers_member {
1415 width: 100%;
1415 width: 100%;
1416 overflow: auto;
1416 overflow: auto;
1417 }
1417 }
1418 .reviewer_reason {
1418 .reviewer_reason {
1419 padding-left: 20px;
1419 padding-left: 20px;
1420 line-height: 1.5em;
1420 line-height: 1.5em;
1421 }
1421 }
1422 .reviewer_status {
1422 .reviewer_status {
1423 display: inline-block;
1423 display: inline-block;
1424 vertical-align: top;
1424 vertical-align: top;
1425 width: 25px;
1425 width: 25px;
1426 min-width: 25px;
1426 min-width: 25px;
1427 height: 1.2em;
1427 height: 1.2em;
1428 margin-top: 3px;
1428 margin-top: 3px;
1429 line-height: 1em;
1429 line-height: 1em;
1430 }
1430 }
1431
1431
1432 .reviewer_name {
1432 .reviewer_name {
1433 display: inline-block;
1433 display: inline-block;
1434 max-width: 83%;
1434 max-width: 83%;
1435 padding-right: 20px;
1435 padding-right: 20px;
1436 vertical-align: middle;
1436 vertical-align: middle;
1437 line-height: 1;
1437 line-height: 1;
1438
1438
1439 .rc-user {
1439 .rc-user {
1440 min-width: 0;
1440 min-width: 0;
1441 margin: -2px 1em 0 0;
1441 margin: -2px 1em 0 0;
1442 }
1442 }
1443
1443
1444 .reviewer {
1444 .reviewer {
1445 float: left;
1445 float: left;
1446 }
1446 }
1447 }
1447 }
1448
1448
1449 .reviewer_member_mandatory {
1449 .reviewer_member_mandatory {
1450 position: absolute;
1450 position: absolute;
1451 left: 15px;
1451 left: 15px;
1452 top: 8px;
1452 top: 8px;
1453 width: 16px;
1453 width: 16px;
1454 font-size: 11px;
1454 font-size: 11px;
1455 margin: 0;
1455 margin: 0;
1456 padding: 0;
1456 padding: 0;
1457 color: black;
1457 color: black;
1458 }
1458 }
1459
1459
1460 .reviewer_member_mandatory_remove,
1460 .reviewer_member_mandatory_remove,
1461 .reviewer_member_remove {
1461 .reviewer_member_remove {
1462 position: absolute;
1462 position: absolute;
1463 right: 0;
1463 right: 0;
1464 top: 0;
1464 top: 0;
1465 width: 16px;
1465 width: 16px;
1466 margin-bottom: 10px;
1466 margin-bottom: 10px;
1467 padding: 0;
1467 padding: 0;
1468 color: black;
1468 color: black;
1469 }
1469 }
1470
1470
1471 .reviewer_member_mandatory_remove {
1471 .reviewer_member_mandatory_remove {
1472 color: @grey4;
1472 color: @grey4;
1473 }
1473 }
1474
1474
1475 .reviewer_member_status {
1475 .reviewer_member_status {
1476 margin-top: 5px;
1476 margin-top: 5px;
1477 }
1477 }
1478 .pr-summary #summary{
1478 .pr-summary #summary{
1479 width: 100%;
1479 width: 100%;
1480 }
1480 }
1481 .pr-summary .action_button:hover {
1481 .pr-summary .action_button:hover {
1482 border: 0;
1482 border: 0;
1483 cursor: pointer;
1483 cursor: pointer;
1484 }
1484 }
1485 .pr-details-title {
1485 .pr-details-title {
1486 padding-bottom: 8px;
1486 padding-bottom: 8px;
1487 border-bottom: @border-thickness solid @grey5;
1487 border-bottom: @border-thickness solid @grey5;
1488
1488
1489 .action_button.disabled {
1489 .action_button.disabled {
1490 color: @grey4;
1490 color: @grey4;
1491 cursor: inherit;
1491 cursor: inherit;
1492 }
1492 }
1493 .action_button {
1493 .action_button {
1494 color: @rcblue;
1494 color: @rcblue;
1495 }
1495 }
1496 }
1496 }
1497 .pr-details-content {
1497 .pr-details-content {
1498 margin-top: @textmargin;
1498 margin-top: @textmargin;
1499 margin-bottom: @textmargin;
1499 margin-bottom: @textmargin;
1500 }
1500 }
1501
1501
1502 .pr-reviewer-rules {
1502 .pr-reviewer-rules {
1503 padding: 10px 0px 20px 0px;
1503 padding: 10px 0px 20px 0px;
1504 }
1504 }
1505
1505
1506 .group_members {
1506 .group_members {
1507 margin-top: 0;
1507 margin-top: 0;
1508 padding: 0;
1508 padding: 0;
1509 list-style: outside none none;
1509 list-style: outside none none;
1510
1510
1511 img {
1511 img {
1512 height: @gravatar-size;
1512 height: @gravatar-size;
1513 width: @gravatar-size;
1513 width: @gravatar-size;
1514 margin-right: .5em;
1514 margin-right: .5em;
1515 margin-left: 3px;
1515 margin-left: 3px;
1516 }
1516 }
1517
1517
1518 .to-delete {
1518 .to-delete {
1519 .user {
1519 .user {
1520 text-decoration: line-through;
1520 text-decoration: line-through;
1521 }
1521 }
1522 }
1522 }
1523 }
1523 }
1524
1524
1525 .compare_view_commits_title {
1525 .compare_view_commits_title {
1526 .disabled {
1526 .disabled {
1527 cursor: inherit;
1527 cursor: inherit;
1528 &:hover{
1528 &:hover{
1529 background-color: inherit;
1529 background-color: inherit;
1530 color: inherit;
1530 color: inherit;
1531 }
1531 }
1532 }
1532 }
1533 }
1533 }
1534
1534
1535 .subtitle-compare {
1535 .subtitle-compare {
1536 margin: -15px 0px 0px 0px;
1536 margin: -15px 0px 0px 0px;
1537 }
1537 }
1538
1538
1539 .comments-summary-td {
1540 border-top: 1px dashed @grey5;
1541 }
1542
1543 // new entry in group_members
1539 // new entry in group_members
1544 .td-author-new-entry {
1540 .td-author-new-entry {
1545 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1541 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1546 }
1542 }
1547
1543
1548 .usergroup_member_remove {
1544 .usergroup_member_remove {
1549 width: 16px;
1545 width: 16px;
1550 margin-bottom: 10px;
1546 margin-bottom: 10px;
1551 padding: 0;
1547 padding: 0;
1552 color: black !important;
1548 color: black !important;
1553 cursor: pointer;
1549 cursor: pointer;
1554 }
1550 }
1555
1551
1556 .reviewer_ac .ac-input {
1552 .reviewer_ac .ac-input {
1557 width: 92%;
1553 width: 92%;
1558 margin-bottom: 1em;
1554 margin-bottom: 1em;
1559 }
1555 }
1560
1556
1561 .compare_view_commits tr{
1557 .compare_view_commits tr{
1562 height: 20px;
1558 height: 20px;
1563 }
1559 }
1564 .compare_view_commits td {
1560 .compare_view_commits td {
1565 vertical-align: top;
1561 vertical-align: top;
1566 padding-top: 10px;
1562 padding-top: 10px;
1567 }
1563 }
1568 .compare_view_commits .author {
1564 .compare_view_commits .author {
1569 margin-left: 5px;
1565 margin-left: 5px;
1570 }
1566 }
1571
1567
1572 .compare_view_commits {
1568 .compare_view_commits {
1573 .color-a {
1569 .color-a {
1574 color: @alert1;
1570 color: @alert1;
1575 }
1571 }
1576
1572
1577 .color-c {
1573 .color-c {
1578 color: @color3;
1574 color: @color3;
1579 }
1575 }
1580
1576
1581 .color-r {
1577 .color-r {
1582 color: @color5;
1578 color: @color5;
1583 }
1579 }
1584
1580
1585 .color-a-bg {
1581 .color-a-bg {
1586 background-color: @alert1;
1582 background-color: @alert1;
1587 }
1583 }
1588
1584
1589 .color-c-bg {
1585 .color-c-bg {
1590 background-color: @alert3;
1586 background-color: @alert3;
1591 }
1587 }
1592
1588
1593 .color-r-bg {
1589 .color-r-bg {
1594 background-color: @alert2;
1590 background-color: @alert2;
1595 }
1591 }
1596
1592
1597 .color-a-border {
1593 .color-a-border {
1598 border: 1px solid @alert1;
1594 border: 1px solid @alert1;
1599 }
1595 }
1600
1596
1601 .color-c-border {
1597 .color-c-border {
1602 border: 1px solid @alert3;
1598 border: 1px solid @alert3;
1603 }
1599 }
1604
1600
1605 .color-r-border {
1601 .color-r-border {
1606 border: 1px solid @alert2;
1602 border: 1px solid @alert2;
1607 }
1603 }
1608
1604
1609 .commit-change-indicator {
1605 .commit-change-indicator {
1610 width: 15px;
1606 width: 15px;
1611 height: 15px;
1607 height: 15px;
1612 position: relative;
1608 position: relative;
1613 left: 15px;
1609 left: 15px;
1614 }
1610 }
1615
1611
1616 .commit-change-content {
1612 .commit-change-content {
1617 text-align: center;
1613 text-align: center;
1618 vertical-align: middle;
1614 vertical-align: middle;
1619 line-height: 15px;
1615 line-height: 15px;
1620 }
1616 }
1621 }
1617 }
1622
1618
1623 .compare_view_filepath {
1619 .compare_view_filepath {
1624 color: @grey1;
1620 color: @grey1;
1625 }
1621 }
1626
1622
1627 .show_more {
1623 .show_more {
1628 display: inline-block;
1624 display: inline-block;
1629 width: 0;
1625 width: 0;
1630 height: 0;
1626 height: 0;
1631 vertical-align: middle;
1627 vertical-align: middle;
1632 content: "";
1628 content: "";
1633 border: 4px solid;
1629 border: 4px solid;
1634 border-right-color: transparent;
1630 border-right-color: transparent;
1635 border-bottom-color: transparent;
1631 border-bottom-color: transparent;
1636 border-left-color: transparent;
1632 border-left-color: transparent;
1637 font-size: 0;
1633 font-size: 0;
1638 }
1634 }
1639
1635
1640 .journal_more .show_more {
1636 .journal_more .show_more {
1641 display: inline;
1637 display: inline;
1642
1638
1643 &:after {
1639 &:after {
1644 content: none;
1640 content: none;
1645 }
1641 }
1646 }
1642 }
1647
1643
1648 .compare_view_commits .collapse_commit:after {
1644 .compare_view_commits .collapse_commit:after {
1649 cursor: pointer;
1645 cursor: pointer;
1650 content: "\00A0\25B4";
1646 content: "\00A0\25B4";
1651 margin-left: -3px;
1647 margin-left: -3px;
1652 font-size: 17px;
1648 font-size: 17px;
1653 color: @grey4;
1649 color: @grey4;
1654 }
1650 }
1655
1651
1656 .diff_links {
1652 .diff_links {
1657 margin-left: 8px;
1653 margin-left: 8px;
1658 }
1654 }
1659
1655
1660 #pull_request_overview {
1656 #pull_request_overview {
1661 div.ancestor {
1657 div.ancestor {
1662 margin: -33px 0;
1658 margin: -33px 0;
1663 }
1659 }
1664 }
1660 }
1665
1661
1666 div.ancestor {
1662 div.ancestor {
1667 line-height: 33px;
1663 line-height: 33px;
1668 }
1664 }
1669
1665
1670 .cs_icon_td input[type="checkbox"] {
1666 .cs_icon_td input[type="checkbox"] {
1671 display: none;
1667 display: none;
1672 }
1668 }
1673
1669
1674 .cs_icon_td .expand_file_icon:after {
1670 .cs_icon_td .expand_file_icon:after {
1675 cursor: pointer;
1671 cursor: pointer;
1676 content: "\00A0\25B6";
1672 content: "\00A0\25B6";
1677 font-size: 12px;
1673 font-size: 12px;
1678 color: @grey4;
1674 color: @grey4;
1679 }
1675 }
1680
1676
1681 .cs_icon_td .collapse_file_icon:after {
1677 .cs_icon_td .collapse_file_icon:after {
1682 cursor: pointer;
1678 cursor: pointer;
1683 content: "\00A0\25BC";
1679 content: "\00A0\25BC";
1684 font-size: 12px;
1680 font-size: 12px;
1685 color: @grey4;
1681 color: @grey4;
1686 }
1682 }
1687
1683
1688 /*new binary
1684 /*new binary
1689 NEW_FILENODE = 1
1685 NEW_FILENODE = 1
1690 DEL_FILENODE = 2
1686 DEL_FILENODE = 2
1691 MOD_FILENODE = 3
1687 MOD_FILENODE = 3
1692 RENAMED_FILENODE = 4
1688 RENAMED_FILENODE = 4
1693 COPIED_FILENODE = 5
1689 COPIED_FILENODE = 5
1694 CHMOD_FILENODE = 6
1690 CHMOD_FILENODE = 6
1695 BIN_FILENODE = 7
1691 BIN_FILENODE = 7
1696 */
1692 */
1697 .cs_files_expand {
1693 .cs_files_expand {
1698 font-size: @basefontsize + 5px;
1694 font-size: @basefontsize + 5px;
1699 line-height: 1.8em;
1695 line-height: 1.8em;
1700 float: right;
1696 float: right;
1701 }
1697 }
1702
1698
1703 .cs_files_expand span{
1699 .cs_files_expand span{
1704 color: @rcblue;
1700 color: @rcblue;
1705 cursor: pointer;
1701 cursor: pointer;
1706 }
1702 }
1707 .cs_files {
1703 .cs_files {
1708 clear: both;
1704 clear: both;
1709 padding-bottom: @padding;
1705 padding-bottom: @padding;
1710
1706
1711 .cur_cs {
1707 .cur_cs {
1712 margin: 10px 2px;
1708 margin: 10px 2px;
1713 font-weight: bold;
1709 font-weight: bold;
1714 }
1710 }
1715
1711
1716 .node {
1712 .node {
1717 float: left;
1713 float: left;
1718 }
1714 }
1719
1715
1720 .changes {
1716 .changes {
1721 float: right;
1717 float: right;
1722 color: white;
1718 color: white;
1723 font-size: @basefontsize - 4px;
1719 font-size: @basefontsize - 4px;
1724 margin-top: 4px;
1720 margin-top: 4px;
1725 opacity: 0.6;
1721 opacity: 0.6;
1726 filter: Alpha(opacity=60); /* IE8 and earlier */
1722 filter: Alpha(opacity=60); /* IE8 and earlier */
1727
1723
1728 .added {
1724 .added {
1729 background-color: @alert1;
1725 background-color: @alert1;
1730 float: left;
1726 float: left;
1731 text-align: center;
1727 text-align: center;
1732 }
1728 }
1733
1729
1734 .deleted {
1730 .deleted {
1735 background-color: @alert2;
1731 background-color: @alert2;
1736 float: left;
1732 float: left;
1737 text-align: center;
1733 text-align: center;
1738 }
1734 }
1739
1735
1740 .bin {
1736 .bin {
1741 background-color: @alert1;
1737 background-color: @alert1;
1742 text-align: center;
1738 text-align: center;
1743 }
1739 }
1744
1740
1745 /*new binary*/
1741 /*new binary*/
1746 .bin.bin1 {
1742 .bin.bin1 {
1747 background-color: @alert1;
1743 background-color: @alert1;
1748 text-align: center;
1744 text-align: center;
1749 }
1745 }
1750
1746
1751 /*deleted binary*/
1747 /*deleted binary*/
1752 .bin.bin2 {
1748 .bin.bin2 {
1753 background-color: @alert2;
1749 background-color: @alert2;
1754 text-align: center;
1750 text-align: center;
1755 }
1751 }
1756
1752
1757 /*mod binary*/
1753 /*mod binary*/
1758 .bin.bin3 {
1754 .bin.bin3 {
1759 background-color: @grey2;
1755 background-color: @grey2;
1760 text-align: center;
1756 text-align: center;
1761 }
1757 }
1762
1758
1763 /*rename file*/
1759 /*rename file*/
1764 .bin.bin4 {
1760 .bin.bin4 {
1765 background-color: @alert4;
1761 background-color: @alert4;
1766 text-align: center;
1762 text-align: center;
1767 }
1763 }
1768
1764
1769 /*copied file*/
1765 /*copied file*/
1770 .bin.bin5 {
1766 .bin.bin5 {
1771 background-color: @alert4;
1767 background-color: @alert4;
1772 text-align: center;
1768 text-align: center;
1773 }
1769 }
1774
1770
1775 /*chmod file*/
1771 /*chmod file*/
1776 .bin.bin6 {
1772 .bin.bin6 {
1777 background-color: @grey2;
1773 background-color: @grey2;
1778 text-align: center;
1774 text-align: center;
1779 }
1775 }
1780 }
1776 }
1781 }
1777 }
1782
1778
1783 .cs_files .cs_added, .cs_files .cs_A,
1779 .cs_files .cs_added, .cs_files .cs_A,
1784 .cs_files .cs_added, .cs_files .cs_M,
1780 .cs_files .cs_added, .cs_files .cs_M,
1785 .cs_files .cs_added, .cs_files .cs_D {
1781 .cs_files .cs_added, .cs_files .cs_D {
1786 height: 16px;
1782 height: 16px;
1787 padding-right: 10px;
1783 padding-right: 10px;
1788 margin-top: 7px;
1784 margin-top: 7px;
1789 text-align: left;
1785 text-align: left;
1790 }
1786 }
1791
1787
1792 .cs_icon_td {
1788 .cs_icon_td {
1793 min-width: 16px;
1789 min-width: 16px;
1794 width: 16px;
1790 width: 16px;
1795 }
1791 }
1796
1792
1797 .pull-request-merge {
1793 .pull-request-merge {
1798 border: 1px solid @grey5;
1794 border: 1px solid @grey5;
1799 padding: 10px 0px 20px;
1795 padding: 10px 0px 20px;
1800 margin-top: 10px;
1796 margin-top: 10px;
1801 margin-bottom: 20px;
1797 margin-bottom: 20px;
1802 }
1798 }
1803
1799
1804 .pull-request-merge ul {
1800 .pull-request-merge ul {
1805 padding: 0px 0px;
1801 padding: 0px 0px;
1806 }
1802 }
1807
1803
1808 .pull-request-merge li {
1804 .pull-request-merge li {
1809 list-style-type: none;
1805 list-style-type: none;
1810 }
1806 }
1811
1807
1812 .pull-request-merge .pull-request-wrap {
1808 .pull-request-merge .pull-request-wrap {
1813 height: auto;
1809 height: auto;
1814 padding: 0px 0px;
1810 padding: 0px 0px;
1815 text-align: right;
1811 text-align: right;
1816 }
1812 }
1817
1813
1818 .pull-request-merge span {
1814 .pull-request-merge span {
1819 margin-right: 5px;
1815 margin-right: 5px;
1820 }
1816 }
1821
1817
1822 .pull-request-merge-actions {
1818 .pull-request-merge-actions {
1823 min-height: 30px;
1819 min-height: 30px;
1824 padding: 0px 0px;
1820 padding: 0px 0px;
1825 }
1821 }
1826
1822
1827 .pull-request-merge-info {
1823 .pull-request-merge-info {
1828 padding: 0px 5px 5px 0px;
1824 padding: 0px 5px 5px 0px;
1829 }
1825 }
1830
1826
1831 .merge-status {
1827 .merge-status {
1832 margin-right: 5px;
1828 margin-right: 5px;
1833 }
1829 }
1834
1830
1835 .merge-message {
1831 .merge-message {
1836 font-size: 1.2em
1832 font-size: 1.2em
1837 }
1833 }
1838
1834
1839 .merge-message.success i,
1835 .merge-message.success i,
1840 .merge-icon.success i {
1836 .merge-icon.success i {
1841 color:@alert1;
1837 color:@alert1;
1842 }
1838 }
1843
1839
1844 .merge-message.warning i,
1840 .merge-message.warning i,
1845 .merge-icon.warning i {
1841 .merge-icon.warning i {
1846 color: @alert3;
1842 color: @alert3;
1847 }
1843 }
1848
1844
1849 .merge-message.error i,
1845 .merge-message.error i,
1850 .merge-icon.error i {
1846 .merge-icon.error i {
1851 color:@alert2;
1847 color:@alert2;
1852 }
1848 }
1853
1849
1854 .pr-versions {
1850 .pr-versions {
1855 font-size: 1.1em;
1851 font-size: 1.1em;
1856
1852
1857 table {
1853 table {
1858 padding: 0px 5px;
1854 padding: 0px 5px;
1859 }
1855 }
1860
1856
1861 td {
1857 td {
1862 line-height: 15px;
1858 line-height: 15px;
1863 }
1859 }
1864
1860
1865 .compare-radio-button {
1861 .compare-radio-button {
1866 position: relative;
1862 position: relative;
1867 top: -3px;
1863 top: -3px;
1868 }
1864 }
1869 }
1865 }
1870
1866
1871
1867
1872 #close_pull_request {
1868 #close_pull_request {
1873 margin-right: 0px;
1869 margin-right: 0px;
1874 }
1870 }
1875
1871
1876 .empty_data {
1872 .empty_data {
1877 color: @grey4;
1873 color: @grey4;
1878 }
1874 }
1879
1875
1880 #changeset_compare_view_content {
1876 #changeset_compare_view_content {
1881 clear: both;
1877 clear: both;
1882 width: 100%;
1878 width: 100%;
1883 box-sizing: border-box;
1879 box-sizing: border-box;
1884 .border-radius(@border-radius);
1880 .border-radius(@border-radius);
1885
1881
1886 .help-block {
1882 .help-block {
1887 margin: @padding 0;
1883 margin: @padding 0;
1888 color: @text-color;
1884 color: @text-color;
1889 &.pre-formatting {
1885 &.pre-formatting {
1890 white-space: pre;
1886 white-space: pre;
1891 }
1887 }
1892 }
1888 }
1893
1889
1894 .empty_data {
1890 .empty_data {
1895 margin: @padding 0;
1891 margin: @padding 0;
1896 }
1892 }
1897
1893
1898 .alert {
1894 .alert {
1899 margin-bottom: @space;
1895 margin-bottom: @space;
1900 }
1896 }
1901 }
1897 }
1902
1898
1903 .table_disp {
1899 .table_disp {
1904 .status {
1900 .status {
1905 width: auto;
1901 width: auto;
1906 }
1902 }
1907 }
1903 }
1908
1904
1909
1905
1910 .creation_in_progress {
1906 .creation_in_progress {
1911 color: @grey4
1907 color: @grey4
1912 }
1908 }
1913
1909
1914 .status_box_menu {
1910 .status_box_menu {
1915 margin: 0;
1911 margin: 0;
1916 }
1912 }
1917
1913
1918 .notification-table{
1914 .notification-table{
1919 margin-bottom: @space;
1915 margin-bottom: @space;
1920 display: table;
1916 display: table;
1921 width: 100%;
1917 width: 100%;
1922
1918
1923 .container{
1919 .container{
1924 display: table-row;
1920 display: table-row;
1925
1921
1926 .notification-header{
1922 .notification-header{
1927 border-bottom: @border-thickness solid @border-default-color;
1923 border-bottom: @border-thickness solid @border-default-color;
1928 }
1924 }
1929
1925
1930 .notification-subject{
1926 .notification-subject{
1931 display: table-cell;
1927 display: table-cell;
1932 }
1928 }
1933 }
1929 }
1934 }
1930 }
1935
1931
1936 // Notifications
1932 // Notifications
1937 .notification-header{
1933 .notification-header{
1938 display: table;
1934 display: table;
1939 width: 100%;
1935 width: 100%;
1940 padding: floor(@basefontsize/2) 0;
1936 padding: floor(@basefontsize/2) 0;
1941 line-height: 1em;
1937 line-height: 1em;
1942
1938
1943 .desc, .delete-notifications, .read-notifications{
1939 .desc, .delete-notifications, .read-notifications{
1944 display: table-cell;
1940 display: table-cell;
1945 text-align: left;
1941 text-align: left;
1946 }
1942 }
1947
1943
1948 .desc{
1944 .desc{
1949 width: 1163px;
1945 width: 1163px;
1950 }
1946 }
1951
1947
1952 .delete-notifications, .read-notifications{
1948 .delete-notifications, .read-notifications{
1953 width: 35px;
1949 width: 35px;
1954 min-width: 35px; //fixes when only one button is displayed
1950 min-width: 35px; //fixes when only one button is displayed
1955 }
1951 }
1956 }
1952 }
1957
1953
1958 .notification-body {
1954 .notification-body {
1959 .markdown-block,
1955 .markdown-block,
1960 .rst-block {
1956 .rst-block {
1961 padding: @padding 0;
1957 padding: @padding 0;
1962 }
1958 }
1963
1959
1964 .notification-subject {
1960 .notification-subject {
1965 padding: @textmargin 0;
1961 padding: @textmargin 0;
1966 border-bottom: @border-thickness solid @border-default-color;
1962 border-bottom: @border-thickness solid @border-default-color;
1967 }
1963 }
1968 }
1964 }
1969
1965
1970
1966
1971 .notifications_buttons{
1967 .notifications_buttons{
1972 float: right;
1968 float: right;
1973 }
1969 }
1974
1970
1975 #notification-status{
1971 #notification-status{
1976 display: inline;
1972 display: inline;
1977 }
1973 }
1978
1974
1979 // Repositories
1975 // Repositories
1980
1976
1981 #summary.fields{
1977 #summary.fields{
1982 display: table;
1978 display: table;
1983
1979
1984 .field{
1980 .field{
1985 display: table-row;
1981 display: table-row;
1986
1982
1987 .label-summary{
1983 .label-summary{
1988 display: table-cell;
1984 display: table-cell;
1989 min-width: @label-summary-minwidth;
1985 min-width: @label-summary-minwidth;
1990 padding-top: @padding/2;
1986 padding-top: @padding/2;
1991 padding-bottom: @padding/2;
1987 padding-bottom: @padding/2;
1992 padding-right: @padding/2;
1988 padding-right: @padding/2;
1993 }
1989 }
1994
1990
1995 .input{
1991 .input{
1996 display: table-cell;
1992 display: table-cell;
1997 padding: @padding/2;
1993 padding: @padding/2;
1998
1994
1999 input{
1995 input{
2000 min-width: 29em;
1996 min-width: 29em;
2001 padding: @padding/4;
1997 padding: @padding/4;
2002 }
1998 }
2003 }
1999 }
2004 .statistics, .downloads{
2000 .statistics, .downloads{
2005 .disabled{
2001 .disabled{
2006 color: @grey4;
2002 color: @grey4;
2007 }
2003 }
2008 }
2004 }
2009 }
2005 }
2010 }
2006 }
2011
2007
2012 #summary{
2008 #summary{
2013 width: 70%;
2009 width: 70%;
2014 }
2010 }
2015
2011
2016
2012
2017 // Journal
2013 // Journal
2018 .journal.title {
2014 .journal.title {
2019 h5 {
2015 h5 {
2020 float: left;
2016 float: left;
2021 margin: 0;
2017 margin: 0;
2022 width: 70%;
2018 width: 70%;
2023 }
2019 }
2024
2020
2025 ul {
2021 ul {
2026 float: right;
2022 float: right;
2027 display: inline-block;
2023 display: inline-block;
2028 margin: 0;
2024 margin: 0;
2029 width: 30%;
2025 width: 30%;
2030 text-align: right;
2026 text-align: right;
2031
2027
2032 li {
2028 li {
2033 display: inline;
2029 display: inline;
2034 font-size: @journal-fontsize;
2030 font-size: @journal-fontsize;
2035 line-height: 1em;
2031 line-height: 1em;
2036
2032
2037 list-style-type: none;
2033 list-style-type: none;
2038 }
2034 }
2039 }
2035 }
2040 }
2036 }
2041
2037
2042 .filterexample {
2038 .filterexample {
2043 position: absolute;
2039 position: absolute;
2044 top: 95px;
2040 top: 95px;
2045 left: @contentpadding;
2041 left: @contentpadding;
2046 color: @rcblue;
2042 color: @rcblue;
2047 font-size: 11px;
2043 font-size: 11px;
2048 font-family: @text-regular;
2044 font-family: @text-regular;
2049 cursor: help;
2045 cursor: help;
2050
2046
2051 &:hover {
2047 &:hover {
2052 color: @rcdarkblue;
2048 color: @rcdarkblue;
2053 }
2049 }
2054
2050
2055 @media (max-width:768px) {
2051 @media (max-width:768px) {
2056 position: relative;
2052 position: relative;
2057 top: auto;
2053 top: auto;
2058 left: auto;
2054 left: auto;
2059 display: block;
2055 display: block;
2060 }
2056 }
2061 }
2057 }
2062
2058
2063
2059
2064 #journal{
2060 #journal{
2065 margin-bottom: @space;
2061 margin-bottom: @space;
2066
2062
2067 .journal_day{
2063 .journal_day{
2068 margin-bottom: @textmargin/2;
2064 margin-bottom: @textmargin/2;
2069 padding-bottom: @textmargin/2;
2065 padding-bottom: @textmargin/2;
2070 font-size: @journal-fontsize;
2066 font-size: @journal-fontsize;
2071 border-bottom: @border-thickness solid @border-default-color;
2067 border-bottom: @border-thickness solid @border-default-color;
2072 }
2068 }
2073
2069
2074 .journal_container{
2070 .journal_container{
2075 margin-bottom: @space;
2071 margin-bottom: @space;
2076
2072
2077 .journal_user{
2073 .journal_user{
2078 display: inline-block;
2074 display: inline-block;
2079 }
2075 }
2080 .journal_action_container{
2076 .journal_action_container{
2081 display: block;
2077 display: block;
2082 margin-top: @textmargin;
2078 margin-top: @textmargin;
2083
2079
2084 div{
2080 div{
2085 display: inline;
2081 display: inline;
2086 }
2082 }
2087
2083
2088 div.journal_action_params{
2084 div.journal_action_params{
2089 display: block;
2085 display: block;
2090 }
2086 }
2091
2087
2092 div.journal_repo:after{
2088 div.journal_repo:after{
2093 content: "\A";
2089 content: "\A";
2094 white-space: pre;
2090 white-space: pre;
2095 }
2091 }
2096
2092
2097 div.date{
2093 div.date{
2098 display: block;
2094 display: block;
2099 margin-bottom: @textmargin;
2095 margin-bottom: @textmargin;
2100 }
2096 }
2101 }
2097 }
2102 }
2098 }
2103 }
2099 }
2104
2100
2105 // Files
2101 // Files
2106 .edit-file-title {
2102 .edit-file-title {
2107 font-size: 16px;
2103 font-size: 16px;
2108
2104
2109 .title-heading {
2105 .title-heading {
2110 padding: 2px;
2106 padding: 2px;
2111 }
2107 }
2112 }
2108 }
2113
2109
2114 .edit-file-fieldset {
2110 .edit-file-fieldset {
2115 margin: @sidebarpadding 0;
2111 margin: @sidebarpadding 0;
2116
2112
2117 .fieldset {
2113 .fieldset {
2118 .left-label {
2114 .left-label {
2119 width: 13%;
2115 width: 13%;
2120 }
2116 }
2121 .right-content {
2117 .right-content {
2122 width: 87%;
2118 width: 87%;
2123 max-width: 100%;
2119 max-width: 100%;
2124 }
2120 }
2125 .filename-label {
2121 .filename-label {
2126 margin-top: 13px;
2122 margin-top: 13px;
2127 }
2123 }
2128 .commit-message-label {
2124 .commit-message-label {
2129 margin-top: 4px;
2125 margin-top: 4px;
2130 }
2126 }
2131 .file-upload-input {
2127 .file-upload-input {
2132 input {
2128 input {
2133 display: none;
2129 display: none;
2134 }
2130 }
2135 margin-top: 10px;
2131 margin-top: 10px;
2136 }
2132 }
2137 .file-upload-label {
2133 .file-upload-label {
2138 margin-top: 10px;
2134 margin-top: 10px;
2139 }
2135 }
2140 p {
2136 p {
2141 margin-top: 5px;
2137 margin-top: 5px;
2142 }
2138 }
2143
2139
2144 }
2140 }
2145 .custom-path-link {
2141 .custom-path-link {
2146 margin-left: 5px;
2142 margin-left: 5px;
2147 }
2143 }
2148 #commit {
2144 #commit {
2149 resize: vertical;
2145 resize: vertical;
2150 }
2146 }
2151 }
2147 }
2152
2148
2153 .delete-file-preview {
2149 .delete-file-preview {
2154 max-height: 250px;
2150 max-height: 250px;
2155 }
2151 }
2156
2152
2157 .new-file,
2153 .new-file,
2158 #filter_activate,
2154 #filter_activate,
2159 #filter_deactivate {
2155 #filter_deactivate {
2160 float: right;
2156 float: right;
2161 margin: 0 0 0 10px;
2157 margin: 0 0 0 10px;
2162 }
2158 }
2163
2159
2164 .file-upload-transaction-wrapper {
2160 .file-upload-transaction-wrapper {
2165 margin-top: 57px;
2161 margin-top: 57px;
2166 clear: both;
2162 clear: both;
2167 }
2163 }
2168
2164
2169 .file-upload-transaction-wrapper .error {
2165 .file-upload-transaction-wrapper .error {
2170 color: @color5;
2166 color: @color5;
2171 }
2167 }
2172
2168
2173 .file-upload-transaction {
2169 .file-upload-transaction {
2174 min-height: 200px;
2170 min-height: 200px;
2175 padding: 54px;
2171 padding: 54px;
2176 border: 1px solid @grey5;
2172 border: 1px solid @grey5;
2177 text-align: center;
2173 text-align: center;
2178 clear: both;
2174 clear: both;
2179 }
2175 }
2180
2176
2181 .file-upload-transaction i {
2177 .file-upload-transaction i {
2182 font-size: 48px
2178 font-size: 48px
2183 }
2179 }
2184
2180
2185 h3.files_location{
2181 h3.files_location{
2186 line-height: 2.4em;
2182 line-height: 2.4em;
2187 }
2183 }
2188
2184
2189 .browser-nav {
2185 .browser-nav {
2190 width: 100%;
2186 width: 100%;
2191 display: table;
2187 display: table;
2192 margin-bottom: 20px;
2188 margin-bottom: 20px;
2193
2189
2194 .info_box {
2190 .info_box {
2195 float: left;
2191 float: left;
2196 display: inline-table;
2192 display: inline-table;
2197 height: 2.5em;
2193 height: 2.5em;
2198
2194
2199 .browser-cur-rev, .info_box_elem {
2195 .browser-cur-rev, .info_box_elem {
2200 display: table-cell;
2196 display: table-cell;
2201 vertical-align: middle;
2197 vertical-align: middle;
2202 }
2198 }
2203
2199
2204 .drop-menu {
2200 .drop-menu {
2205 margin: 0 10px;
2201 margin: 0 10px;
2206 }
2202 }
2207
2203
2208 .info_box_elem {
2204 .info_box_elem {
2209 border-top: @border-thickness solid @grey5;
2205 border-top: @border-thickness solid @grey5;
2210 border-bottom: @border-thickness solid @grey5;
2206 border-bottom: @border-thickness solid @grey5;
2211 box-shadow: @button-shadow;
2207 box-shadow: @button-shadow;
2212
2208
2213 #at_rev, a {
2209 #at_rev, a {
2214 padding: 0.6em 0.4em;
2210 padding: 0.6em 0.4em;
2215 margin: 0;
2211 margin: 0;
2216 .box-shadow(none);
2212 .box-shadow(none);
2217 border: 0;
2213 border: 0;
2218 height: 12px;
2214 height: 12px;
2219 color: @grey2;
2215 color: @grey2;
2220 }
2216 }
2221
2217
2222 input#at_rev {
2218 input#at_rev {
2223 max-width: 50px;
2219 max-width: 50px;
2224 text-align: center;
2220 text-align: center;
2225 }
2221 }
2226
2222
2227 &.previous {
2223 &.previous {
2228 border: @border-thickness solid @grey5;
2224 border: @border-thickness solid @grey5;
2229 border-top-left-radius: @border-radius;
2225 border-top-left-radius: @border-radius;
2230 border-bottom-left-radius: @border-radius;
2226 border-bottom-left-radius: @border-radius;
2231
2227
2232 &:hover {
2228 &:hover {
2233 border-color: @grey4;
2229 border-color: @grey4;
2234 }
2230 }
2235
2231
2236 .disabled {
2232 .disabled {
2237 color: @grey5;
2233 color: @grey5;
2238 cursor: not-allowed;
2234 cursor: not-allowed;
2239 opacity: 0.5;
2235 opacity: 0.5;
2240 }
2236 }
2241 }
2237 }
2242
2238
2243 &.next {
2239 &.next {
2244 border: @border-thickness solid @grey5;
2240 border: @border-thickness solid @grey5;
2245 border-top-right-radius: @border-radius;
2241 border-top-right-radius: @border-radius;
2246 border-bottom-right-radius: @border-radius;
2242 border-bottom-right-radius: @border-radius;
2247
2243
2248 &:hover {
2244 &:hover {
2249 border-color: @grey4;
2245 border-color: @grey4;
2250 }
2246 }
2251
2247
2252 .disabled {
2248 .disabled {
2253 color: @grey5;
2249 color: @grey5;
2254 cursor: not-allowed;
2250 cursor: not-allowed;
2255 opacity: 0.5;
2251 opacity: 0.5;
2256 }
2252 }
2257 }
2253 }
2258 }
2254 }
2259
2255
2260 .browser-cur-rev {
2256 .browser-cur-rev {
2261
2257
2262 span{
2258 span{
2263 margin: 0;
2259 margin: 0;
2264 color: @rcblue;
2260 color: @rcblue;
2265 height: 12px;
2261 height: 12px;
2266 display: inline-block;
2262 display: inline-block;
2267 padding: 0.7em 1em ;
2263 padding: 0.7em 1em ;
2268 border: @border-thickness solid @rcblue;
2264 border: @border-thickness solid @rcblue;
2269 margin-right: @padding;
2265 margin-right: @padding;
2270 }
2266 }
2271 }
2267 }
2272
2268
2273 }
2269 }
2274
2270
2275 .select-index-number {
2271 .select-index-number {
2276 margin: 0 0 0 20px;
2272 margin: 0 0 0 20px;
2277 color: @grey3;
2273 color: @grey3;
2278 }
2274 }
2279
2275
2280 .search_activate {
2276 .search_activate {
2281 display: table-cell;
2277 display: table-cell;
2282 vertical-align: middle;
2278 vertical-align: middle;
2283
2279
2284 input, label{
2280 input, label{
2285 margin: 0;
2281 margin: 0;
2286 padding: 0;
2282 padding: 0;
2287 }
2283 }
2288
2284
2289 input{
2285 input{
2290 margin-left: @textmargin;
2286 margin-left: @textmargin;
2291 }
2287 }
2292
2288
2293 }
2289 }
2294 }
2290 }
2295
2291
2296 .browser-cur-rev{
2292 .browser-cur-rev{
2297 margin-bottom: @textmargin;
2293 margin-bottom: @textmargin;
2298 }
2294 }
2299
2295
2300 #node_filter_box_loading{
2296 #node_filter_box_loading{
2301 .info_text;
2297 .info_text;
2302 }
2298 }
2303
2299
2304 .browser-search {
2300 .browser-search {
2305 margin: -25px 0px 5px 0px;
2301 margin: -25px 0px 5px 0px;
2306 }
2302 }
2307
2303
2308 .files-quick-filter {
2304 .files-quick-filter {
2309 float: right;
2305 float: right;
2310 width: 180px;
2306 width: 180px;
2311 position: relative;
2307 position: relative;
2312 }
2308 }
2313
2309
2314 .files-filter-box {
2310 .files-filter-box {
2315 display: flex;
2311 display: flex;
2316 padding: 0px;
2312 padding: 0px;
2317 border-radius: 3px;
2313 border-radius: 3px;
2318 margin-bottom: 0;
2314 margin-bottom: 0;
2319
2315
2320 a {
2316 a {
2321 border: none !important;
2317 border: none !important;
2322 }
2318 }
2323
2319
2324 li {
2320 li {
2325 list-style-type: none
2321 list-style-type: none
2326 }
2322 }
2327 }
2323 }
2328
2324
2329 .files-filter-box-path {
2325 .files-filter-box-path {
2330 line-height: 33px;
2326 line-height: 33px;
2331 padding: 0;
2327 padding: 0;
2332 width: 20px;
2328 width: 20px;
2333 position: absolute;
2329 position: absolute;
2334 z-index: 11;
2330 z-index: 11;
2335 left: 5px;
2331 left: 5px;
2336 }
2332 }
2337
2333
2338 .files-filter-box-input {
2334 .files-filter-box-input {
2339 margin-right: 0;
2335 margin-right: 0;
2340
2336
2341 input {
2337 input {
2342 border: 1px solid @white;
2338 border: 1px solid @white;
2343 padding-left: 25px;
2339 padding-left: 25px;
2344 width: 145px;
2340 width: 145px;
2345
2341
2346 &:hover {
2342 &:hover {
2347 border-color: @grey6;
2343 border-color: @grey6;
2348 }
2344 }
2349
2345
2350 &:focus {
2346 &:focus {
2351 border-color: @grey5;
2347 border-color: @grey5;
2352 }
2348 }
2353 }
2349 }
2354 }
2350 }
2355
2351
2356 .browser-result{
2352 .browser-result{
2357 td a{
2353 td a{
2358 margin-left: 0.5em;
2354 margin-left: 0.5em;
2359 display: inline-block;
2355 display: inline-block;
2360
2356
2361 em {
2357 em {
2362 font-weight: @text-bold-weight;
2358 font-weight: @text-bold-weight;
2363 font-family: @text-bold;
2359 font-family: @text-bold;
2364 }
2360 }
2365 }
2361 }
2366 }
2362 }
2367
2363
2368 .browser-highlight{
2364 .browser-highlight{
2369 background-color: @grey5-alpha;
2365 background-color: @grey5-alpha;
2370 }
2366 }
2371
2367
2372
2368
2373 .edit-file-fieldset #location,
2369 .edit-file-fieldset #location,
2374 .edit-file-fieldset #filename {
2370 .edit-file-fieldset #filename {
2375 display: flex;
2371 display: flex;
2376 width: -moz-available; /* WebKit-based browsers will ignore this. */
2372 width: -moz-available; /* WebKit-based browsers will ignore this. */
2377 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2373 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2378 width: fill-available;
2374 width: fill-available;
2379 border: 0;
2375 border: 0;
2380 }
2376 }
2381
2377
2382 .path-items {
2378 .path-items {
2383 display: flex;
2379 display: flex;
2384 padding: 0;
2380 padding: 0;
2385 border: 1px solid #eeeeee;
2381 border: 1px solid #eeeeee;
2386 width: 100%;
2382 width: 100%;
2387 float: left;
2383 float: left;
2388
2384
2389 .breadcrumb-path {
2385 .breadcrumb-path {
2390 line-height: 30px;
2386 line-height: 30px;
2391 padding: 0 4px;
2387 padding: 0 4px;
2392 white-space: nowrap;
2388 white-space: nowrap;
2393 }
2389 }
2394
2390
2395 .location-path {
2391 .location-path {
2396 width: -moz-available; /* WebKit-based browsers will ignore this. */
2392 width: -moz-available; /* WebKit-based browsers will ignore this. */
2397 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2393 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2398 width: fill-available;
2394 width: fill-available;
2399
2395
2400 .file-name-input {
2396 .file-name-input {
2401 padding: 0.5em 0;
2397 padding: 0.5em 0;
2402 }
2398 }
2403
2399
2404 }
2400 }
2405
2401
2406 ul {
2402 ul {
2407 display: flex;
2403 display: flex;
2408 margin: 0;
2404 margin: 0;
2409 padding: 0;
2405 padding: 0;
2410 width: 100%;
2406 width: 100%;
2411 }
2407 }
2412
2408
2413 li {
2409 li {
2414 list-style-type: none;
2410 list-style-type: none;
2415 }
2411 }
2416
2412
2417 }
2413 }
2418
2414
2419 .editor-items {
2415 .editor-items {
2420 height: 40px;
2416 height: 40px;
2421 margin: 10px 0 -17px 10px;
2417 margin: 10px 0 -17px 10px;
2422
2418
2423 .editor-action {
2419 .editor-action {
2424 cursor: pointer;
2420 cursor: pointer;
2425 }
2421 }
2426
2422
2427 .editor-action.active {
2423 .editor-action.active {
2428 border-bottom: 2px solid #5C5C5C;
2424 border-bottom: 2px solid #5C5C5C;
2429 }
2425 }
2430
2426
2431 li {
2427 li {
2432 list-style-type: none;
2428 list-style-type: none;
2433 }
2429 }
2434 }
2430 }
2435
2431
2436 .edit-file-fieldset .message textarea {
2432 .edit-file-fieldset .message textarea {
2437 border: 1px solid #eeeeee;
2433 border: 1px solid #eeeeee;
2438 }
2434 }
2439
2435
2440 #files_data .codeblock {
2436 #files_data .codeblock {
2441 background-color: #F5F5F5;
2437 background-color: #F5F5F5;
2442 }
2438 }
2443
2439
2444 #editor_preview {
2440 #editor_preview {
2445 background: white;
2441 background: white;
2446 }
2442 }
2447
2443
2448 .show-editor {
2444 .show-editor {
2449 padding: 10px;
2445 padding: 10px;
2450 background-color: white;
2446 background-color: white;
2451
2447
2452 }
2448 }
2453
2449
2454 .show-preview {
2450 .show-preview {
2455 padding: 10px;
2451 padding: 10px;
2456 background-color: white;
2452 background-color: white;
2457 border-left: 1px solid #eeeeee;
2453 border-left: 1px solid #eeeeee;
2458 }
2454 }
2459 // quick filter
2455 // quick filter
2460 .grid-quick-filter {
2456 .grid-quick-filter {
2461 float: right;
2457 float: right;
2462 position: relative;
2458 position: relative;
2463 }
2459 }
2464
2460
2465 .grid-filter-box {
2461 .grid-filter-box {
2466 display: flex;
2462 display: flex;
2467 padding: 0px;
2463 padding: 0px;
2468 border-radius: 3px;
2464 border-radius: 3px;
2469 margin-bottom: 0;
2465 margin-bottom: 0;
2470
2466
2471 a {
2467 a {
2472 border: none !important;
2468 border: none !important;
2473 }
2469 }
2474
2470
2475 li {
2471 li {
2476 list-style-type: none
2472 list-style-type: none
2477 }
2473 }
2478 }
2474 }
2479
2475
2480 .grid-filter-box-icon {
2476 .grid-filter-box-icon {
2481 line-height: 33px;
2477 line-height: 33px;
2482 padding: 0;
2478 padding: 0;
2483 width: 20px;
2479 width: 20px;
2484 position: absolute;
2480 position: absolute;
2485 z-index: 11;
2481 z-index: 11;
2486 left: 5px;
2482 left: 5px;
2487 }
2483 }
2488
2484
2489 .grid-filter-box-input {
2485 .grid-filter-box-input {
2490 margin-right: 0;
2486 margin-right: 0;
2491
2487
2492 input {
2488 input {
2493 border: 1px solid @white;
2489 border: 1px solid @white;
2494 padding-left: 25px;
2490 padding-left: 25px;
2495 width: 145px;
2491 width: 145px;
2496
2492
2497 &:hover {
2493 &:hover {
2498 border-color: @grey6;
2494 border-color: @grey6;
2499 }
2495 }
2500
2496
2501 &:focus {
2497 &:focus {
2502 border-color: @grey5;
2498 border-color: @grey5;
2503 }
2499 }
2504 }
2500 }
2505 }
2501 }
2506
2502
2507
2503
2508
2504
2509 // Search
2505 // Search
2510
2506
2511 .search-form{
2507 .search-form{
2512 #q {
2508 #q {
2513 width: @search-form-width;
2509 width: @search-form-width;
2514 }
2510 }
2515 .fields{
2511 .fields{
2516 margin: 0 0 @space;
2512 margin: 0 0 @space;
2517 }
2513 }
2518
2514
2519 label{
2515 label{
2520 display: inline-block;
2516 display: inline-block;
2521 margin-right: @textmargin;
2517 margin-right: @textmargin;
2522 padding-top: 0.25em;
2518 padding-top: 0.25em;
2523 }
2519 }
2524
2520
2525
2521
2526 .results{
2522 .results{
2527 clear: both;
2523 clear: both;
2528 margin: 0 0 @padding;
2524 margin: 0 0 @padding;
2529 }
2525 }
2530
2526
2531 .search-tags {
2527 .search-tags {
2532 padding: 5px 0;
2528 padding: 5px 0;
2533 }
2529 }
2534 }
2530 }
2535
2531
2536 div.search-feedback-items {
2532 div.search-feedback-items {
2537 display: inline-block;
2533 display: inline-block;
2538 }
2534 }
2539
2535
2540 div.search-code-body {
2536 div.search-code-body {
2541 background-color: #ffffff; padding: 5px 0 5px 10px;
2537 background-color: #ffffff; padding: 5px 0 5px 10px;
2542 pre {
2538 pre {
2543 .match { background-color: #faffa6;}
2539 .match { background-color: #faffa6;}
2544 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2540 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2545 }
2541 }
2546 }
2542 }
2547
2543
2548 .expand_commit.search {
2544 .expand_commit.search {
2549 .show_more.open {
2545 .show_more.open {
2550 height: auto;
2546 height: auto;
2551 max-height: none;
2547 max-height: none;
2552 }
2548 }
2553 }
2549 }
2554
2550
2555 .search-results {
2551 .search-results {
2556
2552
2557 h2 {
2553 h2 {
2558 margin-bottom: 0;
2554 margin-bottom: 0;
2559 }
2555 }
2560 .codeblock {
2556 .codeblock {
2561 border: none;
2557 border: none;
2562 background: transparent;
2558 background: transparent;
2563 }
2559 }
2564
2560
2565 .codeblock-header {
2561 .codeblock-header {
2566 border: none;
2562 border: none;
2567 background: transparent;
2563 background: transparent;
2568 }
2564 }
2569
2565
2570 .code-body {
2566 .code-body {
2571 border: @border-thickness solid @grey6;
2567 border: @border-thickness solid @grey6;
2572 .border-radius(@border-radius);
2568 .border-radius(@border-radius);
2573 }
2569 }
2574
2570
2575 .td-commit {
2571 .td-commit {
2576 &:extend(pre);
2572 &:extend(pre);
2577 border-bottom: @border-thickness solid @border-default-color;
2573 border-bottom: @border-thickness solid @border-default-color;
2578 }
2574 }
2579
2575
2580 .message {
2576 .message {
2581 height: auto;
2577 height: auto;
2582 max-width: 350px;
2578 max-width: 350px;
2583 white-space: normal;
2579 white-space: normal;
2584 text-overflow: initial;
2580 text-overflow: initial;
2585 overflow: visible;
2581 overflow: visible;
2586
2582
2587 .match { background-color: #faffa6;}
2583 .match { background-color: #faffa6;}
2588 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2584 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2589 }
2585 }
2590
2586
2591 .path {
2587 .path {
2592 border-bottom: none !important;
2588 border-bottom: none !important;
2593 border-left: 1px solid @grey6 !important;
2589 border-left: 1px solid @grey6 !important;
2594 border-right: 1px solid @grey6 !important;
2590 border-right: 1px solid @grey6 !important;
2595 }
2591 }
2596 }
2592 }
2597
2593
2598 table.rctable td.td-search-results div {
2594 table.rctable td.td-search-results div {
2599 max-width: 100%;
2595 max-width: 100%;
2600 }
2596 }
2601
2597
2602 #tip-box, .tip-box{
2598 #tip-box, .tip-box{
2603 padding: @menupadding/2;
2599 padding: @menupadding/2;
2604 display: block;
2600 display: block;
2605 border: @border-thickness solid @border-highlight-color;
2601 border: @border-thickness solid @border-highlight-color;
2606 .border-radius(@border-radius);
2602 .border-radius(@border-radius);
2607 background-color: white;
2603 background-color: white;
2608 z-index: 99;
2604 z-index: 99;
2609 white-space: pre-wrap;
2605 white-space: pre-wrap;
2610 }
2606 }
2611
2607
2612 #linktt {
2608 #linktt {
2613 width: 79px;
2609 width: 79px;
2614 }
2610 }
2615
2611
2616 #help_kb .modal-content{
2612 #help_kb .modal-content{
2617 max-width: 750px;
2613 max-width: 750px;
2618 margin: 10% auto;
2614 margin: 10% auto;
2619
2615
2620 table{
2616 table{
2621 td,th{
2617 td,th{
2622 border-bottom: none;
2618 border-bottom: none;
2623 line-height: 2.5em;
2619 line-height: 2.5em;
2624 }
2620 }
2625 th{
2621 th{
2626 padding-bottom: @textmargin/2;
2622 padding-bottom: @textmargin/2;
2627 }
2623 }
2628 td.keys{
2624 td.keys{
2629 text-align: center;
2625 text-align: center;
2630 }
2626 }
2631 }
2627 }
2632
2628
2633 .block-left{
2629 .block-left{
2634 width: 45%;
2630 width: 45%;
2635 margin-right: 5%;
2631 margin-right: 5%;
2636 }
2632 }
2637 .modal-footer{
2633 .modal-footer{
2638 clear: both;
2634 clear: both;
2639 }
2635 }
2640 .key.tag{
2636 .key.tag{
2641 padding: 0.5em;
2637 padding: 0.5em;
2642 background-color: @rcblue;
2638 background-color: @rcblue;
2643 color: white;
2639 color: white;
2644 border-color: @rcblue;
2640 border-color: @rcblue;
2645 .box-shadow(none);
2641 .box-shadow(none);
2646 }
2642 }
2647 }
2643 }
2648
2644
2649
2645
2650
2646
2651 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2647 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2652
2648
2653 @import 'statistics-graph';
2649 @import 'statistics-graph';
2654 @import 'tables';
2650 @import 'tables';
2655 @import 'forms';
2651 @import 'forms';
2656 @import 'diff';
2652 @import 'diff';
2657 @import 'summary';
2653 @import 'summary';
2658 @import 'navigation';
2654 @import 'navigation';
2659
2655
2660 //--- SHOW/HIDE SECTIONS --//
2656 //--- SHOW/HIDE SECTIONS --//
2661
2657
2662 .btn-collapse {
2658 .btn-collapse {
2663 float: right;
2659 float: right;
2664 text-align: right;
2660 text-align: right;
2665 font-family: @text-light;
2661 font-family: @text-light;
2666 font-size: @basefontsize;
2662 font-size: @basefontsize;
2667 cursor: pointer;
2663 cursor: pointer;
2668 border: none;
2664 border: none;
2669 color: @rcblue;
2665 color: @rcblue;
2670 }
2666 }
2671
2667
2672 table.rctable,
2668 table.rctable,
2673 table.dataTable {
2669 table.dataTable {
2674 .btn-collapse {
2670 .btn-collapse {
2675 float: right;
2671 float: right;
2676 text-align: right;
2672 text-align: right;
2677 }
2673 }
2678 }
2674 }
2679
2675
2680 table.rctable {
2676 table.rctable {
2681 &.permissions {
2677 &.permissions {
2682
2678
2683 th.td-owner {
2679 th.td-owner {
2684 padding: 0;
2680 padding: 0;
2685 }
2681 }
2686
2682
2687 th {
2683 th {
2688 font-weight: normal;
2684 font-weight: normal;
2689 padding: 0 5px;
2685 padding: 0 5px;
2690 }
2686 }
2691
2687
2692 }
2688 }
2693 }
2689 }
2694
2690
2695
2691
2696 // TODO: johbo: Fix for IE10, this avoids that we see a border
2692 // TODO: johbo: Fix for IE10, this avoids that we see a border
2697 // and padding around checkboxes and radio boxes. Move to the right place,
2693 // and padding around checkboxes and radio boxes. Move to the right place,
2698 // or better: Remove this once we did the form refactoring.
2694 // or better: Remove this once we did the form refactoring.
2699 input[type=checkbox],
2695 input[type=checkbox],
2700 input[type=radio] {
2696 input[type=radio] {
2701 padding: 0;
2697 padding: 0;
2702 border: none;
2698 border: none;
2703 }
2699 }
2704
2700
2705 .toggle-ajax-spinner{
2701 .toggle-ajax-spinner{
2706 height: 16px;
2702 height: 16px;
2707 width: 16px;
2703 width: 16px;
2708 }
2704 }
2709
2705
2710
2706
2711 .markup-form .clearfix {
2707 .markup-form .clearfix {
2712 .border-radius(@border-radius);
2708 .border-radius(@border-radius);
2713 margin: 0px;
2709 margin: 0px;
2714 }
2710 }
2715
2711
2716 .markup-form-area {
2712 .markup-form-area {
2717 padding: 8px 12px;
2713 padding: 8px 12px;
2718 border: 1px solid @grey4;
2714 border: 1px solid @grey4;
2719 .border-radius(@border-radius);
2715 .border-radius(@border-radius);
2720 }
2716 }
2721
2717
2722 .markup-form-area-header .nav-links {
2718 .markup-form-area-header .nav-links {
2723 display: flex;
2719 display: flex;
2724 flex-flow: row wrap;
2720 flex-flow: row wrap;
2725 -webkit-flex-flow: row wrap;
2721 -webkit-flex-flow: row wrap;
2726 width: 100%;
2722 width: 100%;
2727 }
2723 }
2728
2724
2729 .markup-form-area-footer {
2725 .markup-form-area-footer {
2730 display: flex;
2726 display: flex;
2731 }
2727 }
2732
2728
2733 .markup-form-area-footer .toolbar {
2729 .markup-form-area-footer .toolbar {
2734
2730
2735 }
2731 }
2736
2732
2737 // markup Form
2733 // markup Form
2738 div.markup-form {
2734 div.markup-form {
2739 margin-top: 20px;
2735 margin-top: 20px;
2740 }
2736 }
2741
2737
2742 .markup-form strong {
2738 .markup-form strong {
2743 display: block;
2739 display: block;
2744 margin-bottom: 15px;
2740 margin-bottom: 15px;
2745 }
2741 }
2746
2742
2747 .markup-form textarea {
2743 .markup-form textarea {
2748 width: 100%;
2744 width: 100%;
2749 height: 100px;
2745 height: 100px;
2750 font-family: @text-monospace;
2746 font-family: @text-monospace;
2751 }
2747 }
2752
2748
2753 form.markup-form {
2749 form.markup-form {
2754 margin-top: 10px;
2750 margin-top: 10px;
2755 margin-left: 10px;
2751 margin-left: 10px;
2756 }
2752 }
2757
2753
2758 .markup-form .comment-block-ta,
2754 .markup-form .comment-block-ta,
2759 .markup-form .preview-box {
2755 .markup-form .preview-box {
2760 .border-radius(@border-radius);
2756 .border-radius(@border-radius);
2761 .box-sizing(border-box);
2757 .box-sizing(border-box);
2762 background-color: white;
2758 background-color: white;
2763 }
2759 }
2764
2760
2765 .markup-form .preview-box.unloaded {
2761 .markup-form .preview-box.unloaded {
2766 height: 50px;
2762 height: 50px;
2767 text-align: center;
2763 text-align: center;
2768 padding: 20px;
2764 padding: 20px;
2769 background-color: white;
2765 background-color: white;
2770 }
2766 }
2771
2767
2772
2768
2773 .dropzone-wrapper {
2769 .dropzone-wrapper {
2774 border: 1px solid @grey5;
2770 border: 1px solid @grey5;
2775 padding: 20px;
2771 padding: 20px;
2776 }
2772 }
2777
2773
2778 .dropzone,
2774 .dropzone,
2779 .dropzone-pure {
2775 .dropzone-pure {
2780 border: 2px dashed @grey5;
2776 border: 2px dashed @grey5;
2781 border-radius: 5px;
2777 border-radius: 5px;
2782 background: white;
2778 background: white;
2783 min-height: 200px;
2779 min-height: 200px;
2784 padding: 54px;
2780 padding: 54px;
2785
2781
2786 .dz-message {
2782 .dz-message {
2787 font-weight: 700;
2783 font-weight: 700;
2788 text-align: center;
2784 text-align: center;
2789 margin: 2em 0;
2785 margin: 2em 0;
2790 }
2786 }
2791
2787
2792 }
2788 }
2793
2789
2794 .dz-preview {
2790 .dz-preview {
2795 margin: 10px 0 !important;
2791 margin: 10px 0 !important;
2796 position: relative;
2792 position: relative;
2797 vertical-align: top;
2793 vertical-align: top;
2798 padding: 10px;
2794 padding: 10px;
2799 border-bottom: 1px solid @grey5;
2795 border-bottom: 1px solid @grey5;
2800 }
2796 }
2801
2797
2802 .dz-filename {
2798 .dz-filename {
2803 font-weight: 700;
2799 font-weight: 700;
2804 float:left;
2800 float:left;
2805 }
2801 }
2806
2802
2807 .dz-sending {
2803 .dz-sending {
2808 float: right;
2804 float: right;
2809 }
2805 }
2810
2806
2811 .dz-response {
2807 .dz-response {
2812 clear:both
2808 clear:both
2813 }
2809 }
2814
2810
2815 .dz-filename-size {
2811 .dz-filename-size {
2816 float:right
2812 float:right
2817 }
2813 }
2818
2814
2819 .dz-error-message {
2815 .dz-error-message {
2820 color: @alert2;
2816 color: @alert2;
2821 padding-top: 10px;
2817 padding-top: 10px;
2822 clear: both;
2818 clear: both;
2823 }
2819 }
@@ -1,141 +1,141 b''
1 // tags.less
1 // tags.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 // TAGS
5 // TAGS
6 .tag,
6 .tag,
7 .tagtag {
7 .tagtag {
8 display: inline-block;
8 display: inline-block;
9 min-height: 0;
9 min-height: 0;
10 margin: 0 auto;
10 margin: 0 auto;
11 padding: .25em;
11 padding: .25em;
12 text-align: center;
12 text-align: center;
13 font-size: (-1 + @basefontsize); //fit in tables
13 font-size: (-1 + @basefontsize); //fit in tables
14 line-height: .9em;
14 line-height: 1.1em;
15 border: none;
15 border: none;
16 box-shadow: @button-shadow;
16 box-shadow: @button-shadow;
17 .border-radius(@border-radius);
17 .border-radius(@border-radius);
18 font-family: @text-regular;
18 font-family: @text-regular;
19 background-image: none;
19 background-image: none;
20 color: @grey4;
20 color: @grey4;
21 .border ( @border-thickness-tags, @grey5 );
21 .border ( @border-thickness-tags, @grey5 );
22 white-space: nowrap;
22 white-space: nowrap;
23 a {
23 a {
24 color: inherit;
24 color: inherit;
25
25
26 &:hover {
26 &:hover {
27 color: @grey2;
27 color: @grey2;
28 }
28 }
29
29
30 i,
30 i,
31 [class^="icon-"]:before,
31 [class^="icon-"]:before,
32 [class*=" icon-"]:before {
32 [class*=" icon-"]:before {
33 text-decoration: none;
33 text-decoration: none;
34 }
34 }
35 }
35 }
36
36
37 &:hover {
37 &:hover {
38 border-color: @grey4;
38 border-color: @grey4;
39 }
39 }
40 }
40 }
41
41
42 .tag0 { .border ( @border-thickness-tags, @grey4 ); color:@grey4; }
42 .tag0 { .border ( @border-thickness-tags, @grey4 ); color:@grey4; }
43 .tag1 { .border ( @border-thickness-tags, @color1 ); color:@color1; }
43 .tag1 { .border ( @border-thickness-tags, @color1 ); color:@color1; }
44 .tag2 { .border ( @border-thickness-tags, @color2 ); color:@color2; }
44 .tag2 { .border ( @border-thickness-tags, @color2 ); color:@color2; }
45 .tag3 { .border ( @border-thickness-tags, @color3 ); color:@color3; }
45 .tag3 { .border ( @border-thickness-tags, @color3 ); color:@color3; }
46 .tag4 { .border ( @border-thickness-tags, @color4 ); color:@color4; }
46 .tag4 { .border ( @border-thickness-tags, @color4 ); color:@color4; }
47 .tag5 { .border ( @border-thickness-tags, @color5 ); color:@color5; }
47 .tag5 { .border ( @border-thickness-tags, @color5 ); color:@color5; }
48 .tag6 { .border ( @border-thickness-tags, @color6 ); color:@color6; }
48 .tag6 { .border ( @border-thickness-tags, @color6 ); color:@color6; }
49 .tag7 { .border ( @border-thickness-tags, @color7 ); color:@color7; }
49 .tag7 { .border ( @border-thickness-tags, @color7 ); color:@color7; }
50 .tag8 { .border ( @border-thickness-tags, @color8 ); color:@color8; }
50 .tag8 { .border ( @border-thickness-tags, @color8 ); color:@color8; }
51
51
52 .metatag-list {
52 .metatag-list {
53 margin: 0;
53 margin: 0;
54 padding: 0;
54 padding: 0;
55
55
56 li {
56 li {
57 margin: 0 0 @padding;
57 margin: 0 0 @padding;
58 line-height: 1em;
58 line-height: 1em;
59 list-style-type: none;
59 list-style-type: none;
60 }
60 }
61 }
61 }
62
62
63 .branchtag, .booktag {
63 .branchtag, .booktag {
64 &:extend(.tag);
64 &:extend(.tag);
65
65
66
66
67 a {
67 a {
68 color:inherit;
68 color:inherit;
69 }
69 }
70 }
70 }
71
71
72 .metatag {
72 .metatag {
73 &:extend(.tag);
73 &:extend(.tag);
74 a {
74 a {
75 color:inherit;
75 color:inherit;
76 text-decoration: underline;
76 text-decoration: underline;
77 }
77 }
78 }
78 }
79
79
80 [tag="generic"] { &:extend(.tag0); }
80 [tag="generic"] { &:extend(.tag0); }
81 [tag="label"] { &:extend(.tag0); }
81 [tag="label"] { &:extend(.tag0); }
82
82
83 [tag="state featured"] { &:extend(.tag1); }
83 [tag="state featured"] { &:extend(.tag1); }
84 [tag="state dev"] { &:extend(.tag1); }
84 [tag="state dev"] { &:extend(.tag1); }
85 [tag="ref base"] { &:extend(.tag1); }
85 [tag="ref base"] { &:extend(.tag1); }
86
86
87 [tag="state stable"] { &:extend(.tag2); }
87 [tag="state stable"] { &:extend(.tag2); }
88 [tag="state stale"] { &:extend(.tag2); }
88 [tag="state stale"] { &:extend(.tag2); }
89
89
90 [tag="ref requires"] { &:extend(.tag3); }
90 [tag="ref requires"] { &:extend(.tag3); }
91
91
92 [tag="state dead"] { &:extend(.tag4); }
92 [tag="state dead"] { &:extend(.tag4); }
93 [tag="state deprecated"] { &:extend(.tag4); }
93 [tag="state deprecated"] { &:extend(.tag4); }
94
94
95 [tag="ref conflicts"] { &:extend(.tag4); }
95 [tag="ref conflicts"] { &:extend(.tag4); }
96
96
97 [tag="license"] { &:extend(.tag6); }
97 [tag="license"] { &:extend(.tag6); }
98
98
99 [tag="lang"] { &:extend(.tag7); }
99 [tag="lang"] { &:extend(.tag7); }
100 [tag="language"] { &:extend(.tag7); }
100 [tag="language"] { &:extend(.tag7); }
101 [tag="ref recommends"] { &:extend(.tag7); }
101 [tag="ref recommends"] { &:extend(.tag7); }
102
102
103 [tag="see"] { &:extend(.tag8); }
103 [tag="see"] { &:extend(.tag8); }
104 [tag="url"] { &:extend(.tag8); }
104 [tag="url"] { &:extend(.tag8); }
105
105
106
106
107 .perm_overriden {
107 .perm_overriden {
108 text-decoration: line-through;
108 text-decoration: line-through;
109 opacity: 0.6;
109 opacity: 0.6;
110 }
110 }
111
111
112 .perm_tag {
112 .perm_tag {
113 &:extend(.tag);
113 &:extend(.tag);
114
114
115 &.read {
115 &.read {
116 &:extend(.tag1);
116 &:extend(.tag1);
117 }
117 }
118 &.write {
118 &.write {
119 &:extend(.tag4);
119 &:extend(.tag4);
120 }
120 }
121 &.admin {
121 &.admin {
122 &:extend(.tag5);
122 &:extend(.tag5);
123 }
123 }
124 &.merge {
124 &.merge {
125 &:extend(.tag1);
125 &:extend(.tag1);
126 }
126 }
127 &.push {
127 &.push {
128 &:extend(.tag4);
128 &:extend(.tag4);
129 }
129 }
130 &.push_force {
130 &.push_force {
131 &:extend(.tag5);
131 &:extend(.tag5);
132 }
132 }
133 }
133 }
134
134
135 .phase-draft {
135 .phase-draft {
136 color: @color3
136 color: @color3
137 }
137 }
138
138
139 .phase-secret {
139 .phase-secret {
140 color:@grey3
140 color:@grey3
141 }
141 }
@@ -1,311 +1,312 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
7
8 <%def name="title()">
8 <%def name="title()">
9 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
9 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_nav()">
15 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='repositories')}
16 ${self.menu_items(active='repositories')}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_subnav()">
19 <%def name="menu_bar_subnav()">
20 ${self.repo_menu(active='commits')}
20 ${self.repo_menu(active='commits')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <script type="text/javascript">
24 <script type="text/javascript">
25 // TODO: marcink switch this to pyroutes
25 // TODO: marcink switch this to pyroutes
26 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__')}";
26 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__')}";
27 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
27 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
28 </script>
28 </script>
29
29
30 <div class="box">
30 <div class="box">
31
31
32 <div class="summary">
32 <div class="summary">
33
33
34 <div class="fieldset">
34 <div class="fieldset">
35 <div class="left-content">
35 <div class="left-content">
36
36
37 <div class="left-content-avatar">
37 <div class="left-content-avatar">
38 ${base.gravatar(c.commit.author_email, 30)}
38 ${base.gravatar(c.commit.author_email, 30)}
39 </div>
39 </div>
40
40
41 <div class="left-content-message">
41 <div class="left-content-message">
42 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
42 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
43 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
43 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
44 </div>
44 </div>
45
45
46 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
46 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
47 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
47 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
48 </div>
48 </div>
49
49
50 <div class="fieldset" data-toggle="summary-details">
50 <div class="fieldset" data-toggle="summary-details">
51 <div class="">
51 <div class="">
52 <table>
52 <table>
53 <tr class="file_author tooltip" title="${h.tooltip(h.author_string(c.commit.author_email))}">
53 <tr class="file_author tooltip" title="${h.tooltip(h.author_string(c.commit.author_email))}">
54
54
55 <td>
55 <td>
56 <span class="user commit-author">${h.link_to_user(c.commit.author)}</span>
56 <span class="user commit-author">${h.link_to_user(c.commit.author)}</span>
57 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
57 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
58 </td>
58 </td>
59
59
60 <td>
60 <td>
61 ## second cell for consistency with files
61 ## second cell for consistency with files
62 </td>
62 </td>
63 </tr>
63 </tr>
64 </table>
64 </table>
65 </div>
65 </div>
66 </div>
66 </div>
67
67
68 </div>
68 </div>
69 </div>
69 </div>
70
70
71 <div class="right-content">
71 <div class="right-content">
72
72
73 <div data-toggle="summary-details">
73 <div data-toggle="summary-details">
74 <div class="tags tags-main">
74 <div class="tags tags-main">
75 <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>
75 <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>
76 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
76 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
77 ${file_base.refs(c.commit)}
77 ${file_base.refs(c.commit)}
78
78
79 ## phase
79 ## phase
80 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
80 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
81 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
81 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
82 <i class="icon-info"></i>${c.commit.phase}
82 <i class="icon-info"></i>${c.commit.phase}
83 </span>
83 </span>
84 % endif
84 % endif
85
85
86 ## obsolete commits
86 ## obsolete commits
87 % if getattr(c.commit, 'obsolete', False):
87 % if getattr(c.commit, 'obsolete', False):
88 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
88 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
89 ${_('obsolete')}
89 ${_('obsolete')}
90 </span>
90 </span>
91 % endif
91 % endif
92
92
93 ## hidden commits
93 ## hidden commits
94 % if getattr(c.commit, 'hidden', False):
94 % if getattr(c.commit, 'hidden', False):
95 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
95 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
96 ${_('hidden')}
96 ${_('hidden')}
97 </span>
97 </span>
98 % endif
98 % endif
99 </div>
99 </div>
100
100
101 %if c.statuses:
101 %if c.statuses:
102 <div class="tag status-tag-${c.statuses[0]} pull-right">
102 <div class="tag status-tag-${c.statuses[0]} pull-right">
103 <i class="icon-circle review-status-${c.statuses[0]}"></i>
103 <i class="icon-circle review-status-${c.statuses[0]}"></i>
104 <div class="pull-right">${h.commit_status_lbl(c.statuses[0])}</div>
104 <div class="pull-right">${h.commit_status_lbl(c.statuses[0])}</div>
105 </div>
105 </div>
106 %endif
106 %endif
107
107
108 </div>
108 </div>
109
109
110 </div>
110 </div>
111 </div>
111 </div>
112
112
113 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
113 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
114 <div class="left-label-summary">
114 <div class="left-label-summary">
115 <p>${_('Commit navigation')}:</p>
115 <p>${_('Commit navigation')}:</p>
116 <div class="right-label-summary">
116 <div class="right-label-summary">
117 <span id="parent_link" class="tag tagtag">
117 <span id="parent_link" class="tag tagtag">
118 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
118 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
119 </span>
119 </span>
120
120
121 <span id="child_link" class="tag tagtag">
121 <span id="child_link" class="tag tagtag">
122 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
122 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
123 </span>
123 </span>
124 </div>
124 </div>
125 </div>
125 </div>
126 </div>
126 </div>
127
127
128 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
128 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
129 <div class="left-label-summary">
129 <div class="left-label-summary">
130 <p>${_('Diff options')}:</p>
130 <p>${_('Diff options')}:</p>
131 <div class="right-label-summary">
131 <div class="right-label-summary">
132 <div class="diff-actions">
132 <div class="diff-actions">
133 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
133 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
134 ${_('Raw Diff')}
134 ${_('Raw Diff')}
135 </a>
135 </a>
136 |
136 |
137 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
137 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
138 ${_('Patch Diff')}
138 ${_('Patch Diff')}
139 </a>
139 </a>
140 |
140 |
141 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
141 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
142 ${_('Download Diff')}
142 ${_('Download Diff')}
143 </a>
143 </a>
144 </div>
144 </div>
145 </div>
145 </div>
146 </div>
146 </div>
147 </div>
147 </div>
148
148
149 <div class="clear-fix"></div>
149 <div class="clear-fix"></div>
150
150
151 <div class="btn-collapse" data-toggle="summary-details">
151 <div class="btn-collapse" data-toggle="summary-details">
152 ${_('Show More')}
152 ${_('Show More')}
153 </div>
153 </div>
154
154
155 </div>
155 </div>
156
156
157 <div class="cs_files">
157 <div class="cs_files">
158 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
158 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
159 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id])}
159 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id])}
160 ${cbdiffs.render_diffset(
160 ${cbdiffs.render_diffset(
161 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,inline_comments=c.inline_comments )}
161 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,inline_comments=c.inline_comments )}
162 </div>
162 </div>
163
163
164 ## template for inline comment form
165 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
166
167 ## comments heading with count
164 <div class="comments-heading">
168 <div class="comments-heading">
165 <i class="icon-comment"></i>
169 <i class="icon-comment"></i>
166 ${_('Comments')} ${len(c.comments)}
170 ${_('Comments')} ${len(c.comments)}
167 </div>
171 </div>
168
172
169 ## template for inline comment form
170 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
171
172 ## render comments
173 ## render comments
173 ${comment.generate_comments(c.comments)}
174 ${comment.generate_comments(c.comments)}
174
175
175 ## main comment form and it status
176 ## main comment form and it status
176 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
177 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
177 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
178 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
178 </div>
179 </div>
179
180
180 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
181 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
181 <script type="text/javascript">
182 <script type="text/javascript">
182
183
183 $(document).ready(function() {
184 $(document).ready(function() {
184
185
185 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
186 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
186 if($('#trimmed_message_box').height() === boxmax){
187 if($('#trimmed_message_box').height() === boxmax){
187 $('#message_expand').show();
188 $('#message_expand').show();
188 }
189 }
189
190
190 $('#message_expand').on('click', function(e){
191 $('#message_expand').on('click', function(e){
191 $('#trimmed_message_box').css('max-height', 'none');
192 $('#trimmed_message_box').css('max-height', 'none');
192 $(this).hide();
193 $(this).hide();
193 });
194 });
194
195
195 $('.show-inline-comments').on('click', function(e){
196 $('.show-inline-comments').on('click', function(e){
196 var boxid = $(this).attr('data-comment-id');
197 var boxid = $(this).attr('data-comment-id');
197 var button = $(this);
198 var button = $(this);
198
199
199 if(button.hasClass("comments-visible")) {
200 if(button.hasClass("comments-visible")) {
200 $('#{0} .inline-comments'.format(boxid)).each(function(index){
201 $('#{0} .inline-comments'.format(boxid)).each(function(index){
201 $(this).hide();
202 $(this).hide();
202 });
203 });
203 button.removeClass("comments-visible");
204 button.removeClass("comments-visible");
204 } else {
205 } else {
205 $('#{0} .inline-comments'.format(boxid)).each(function(index){
206 $('#{0} .inline-comments'.format(boxid)).each(function(index){
206 $(this).show();
207 $(this).show();
207 });
208 });
208 button.addClass("comments-visible");
209 button.addClass("comments-visible");
209 }
210 }
210 });
211 });
211
212
212
213
213 // next links
214 // next links
214 $('#child_link').on('click', function(e){
215 $('#child_link').on('click', function(e){
215 // fetch via ajax what is going to be the next link, if we have
216 // fetch via ajax what is going to be the next link, if we have
216 // >1 links show them to user to choose
217 // >1 links show them to user to choose
217 if(!$('#child_link').hasClass('disabled')){
218 if(!$('#child_link').hasClass('disabled')){
218 $.ajax({
219 $.ajax({
219 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
220 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
220 success: function(data) {
221 success: function(data) {
221 if(data.results.length === 0){
222 if(data.results.length === 0){
222 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
223 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
223 }
224 }
224 if(data.results.length === 1){
225 if(data.results.length === 1){
225 var commit = data.results[0];
226 var commit = data.results[0];
226 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
227 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
227 }
228 }
228 else if(data.results.length === 2){
229 else if(data.results.length === 2){
229 $('#child_link').addClass('disabled');
230 $('#child_link').addClass('disabled');
230 $('#child_link').addClass('double');
231 $('#child_link').addClass('double');
231
232
232 var _html = '';
233 var _html = '';
233 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
234 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
234 .replace('__branch__', data.results[0].branch)
235 .replace('__branch__', data.results[0].branch)
235 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
236 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
236 .replace('__title__', data.results[0].message)
237 .replace('__title__', data.results[0].message)
237 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
238 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
238 _html +=' | ';
239 _html +=' | ';
239 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
240 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
240 .replace('__branch__', data.results[1].branch)
241 .replace('__branch__', data.results[1].branch)
241 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
242 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
242 .replace('__title__', data.results[1].message)
243 .replace('__title__', data.results[1].message)
243 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
244 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
244 $('#child_link').html(_html);
245 $('#child_link').html(_html);
245 }
246 }
246 }
247 }
247 });
248 });
248 e.preventDefault();
249 e.preventDefault();
249 }
250 }
250 });
251 });
251
252
252 // prev links
253 // prev links
253 $('#parent_link').on('click', function(e){
254 $('#parent_link').on('click', function(e){
254 // fetch via ajax what is going to be the next link, if we have
255 // fetch via ajax what is going to be the next link, if we have
255 // >1 links show them to user to choose
256 // >1 links show them to user to choose
256 if(!$('#parent_link').hasClass('disabled')){
257 if(!$('#parent_link').hasClass('disabled')){
257 $.ajax({
258 $.ajax({
258 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
259 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
259 success: function(data) {
260 success: function(data) {
260 if(data.results.length === 0){
261 if(data.results.length === 0){
261 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
262 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
262 }
263 }
263 if(data.results.length === 1){
264 if(data.results.length === 1){
264 var commit = data.results[0];
265 var commit = data.results[0];
265 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
266 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
266 }
267 }
267 else if(data.results.length === 2){
268 else if(data.results.length === 2){
268 $('#parent_link').addClass('disabled');
269 $('#parent_link').addClass('disabled');
269 $('#parent_link').addClass('double');
270 $('#parent_link').addClass('double');
270
271
271 var _html = '';
272 var _html = '';
272 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
273 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
273 .replace('__branch__', data.results[0].branch)
274 .replace('__branch__', data.results[0].branch)
274 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
275 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
275 .replace('__title__', data.results[0].message)
276 .replace('__title__', data.results[0].message)
276 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
277 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
277 _html +=' | ';
278 _html +=' | ';
278 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
279 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
279 .replace('__branch__', data.results[1].branch)
280 .replace('__branch__', data.results[1].branch)
280 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
281 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
281 .replace('__title__', data.results[1].message)
282 .replace('__title__', data.results[1].message)
282 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
283 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
283 $('#parent_link').html(_html);
284 $('#parent_link').html(_html);
284 }
285 }
285 }
286 }
286 });
287 });
287 e.preventDefault();
288 e.preventDefault();
288 }
289 }
289 });
290 });
290
291
291 if (location.hash) {
292 if (location.hash) {
292 var result = splitDelimitedHash(location.hash);
293 var result = splitDelimitedHash(location.hash);
293 var line = $('html').find(result.loc);
294 var line = $('html').find(result.loc);
294 if (line.length > 0){
295 if (line.length > 0){
295 offsetScroll(line, 70);
296 offsetScroll(line, 70);
296 }
297 }
297 }
298 }
298
299
299 // browse tree @ revision
300 // browse tree @ revision
300 $('#files_link').on('click', function(e){
301 $('#files_link').on('click', function(e){
301 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
302 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
302 e.preventDefault();
303 e.preventDefault();
303 });
304 });
304
305
305 // inject comments into their proper positions
306 // inject comments into their proper positions
306 var file_comments = $('.inline-comment-placeholder');
307 var file_comments = $('.inline-comment-placeholder');
307
308
308 })
309 })
309 </script>
310 </script>
310
311
311 </%def>
312 </%def>
@@ -1,407 +1,404 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 ## ${comment.comment_block(comment)}
4 ## ${comment.comment_block(comment)}
5 ##
5 ##
6 <%namespace name="base" file="/base/base.mako"/>
6 <%namespace name="base" file="/base/base.mako"/>
7
7
8 <%def name="comment_block(comment, inline=False)">
8 <%def name="comment_block(comment, inline=False)">
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
10 <% latest_ver = len(getattr(c, 'versions', [])) %>
10 <% latest_ver = len(getattr(c, 'versions', [])) %>
11 % if inline:
11 % if inline:
12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
13 % else:
13 % else:
14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
15 % endif
15 % endif
16
16
17
17
18 <div class="comment
18 <div class="comment
19 ${'comment-inline' if inline else 'comment-general'}
19 ${'comment-inline' if inline else 'comment-general'}
20 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
20 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
21 id="comment-${comment.comment_id}"
21 id="comment-${comment.comment_id}"
22 line="${comment.line_no}"
22 line="${comment.line_no}"
23 data-comment-id="${comment.comment_id}"
23 data-comment-id="${comment.comment_id}"
24 data-comment-type="${comment.comment_type}"
24 data-comment-type="${comment.comment_type}"
25 data-comment-line-no="${comment.line_no}"
25 data-comment-line-no="${comment.line_no}"
26 data-comment-inline=${h.json.dumps(inline)}
26 data-comment-inline=${h.json.dumps(inline)}
27 style="${'display: none;' if outdated_at_ver else ''}">
27 style="${'display: none;' if outdated_at_ver else ''}">
28
28
29 <div class="meta">
29 <div class="meta">
30 <div class="comment-type-label">
30 <div class="comment-type-label">
31 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
31 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
32 % if comment.comment_type == 'todo':
32 % if comment.comment_type == 'todo':
33 % if comment.resolved:
33 % if comment.resolved:
34 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
34 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
35 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
35 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
36 </div>
36 </div>
37 % else:
37 % else:
38 <div class="resolved tooltip" style="display: none">
38 <div class="resolved tooltip" style="display: none">
39 <span>${comment.comment_type}</span>
39 <span>${comment.comment_type}</span>
40 </div>
40 </div>
41 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
41 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
42 ${comment.comment_type}
42 ${comment.comment_type}
43 </div>
43 </div>
44 % endif
44 % endif
45 % else:
45 % else:
46 % if comment.resolved_comment:
46 % if comment.resolved_comment:
47 fix
47 fix
48 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
49 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
50 </a>
48 % else:
51 % else:
49 ${comment.comment_type or 'note'}
52 ${comment.comment_type or 'note'}
50 % endif
53 % endif
51 % endif
54 % endif
52 </div>
55 </div>
53 </div>
56 </div>
54
57
55 <div class="author ${'author-inline' if inline else 'author-general'}">
58 <div class="author ${'author-inline' if inline else 'author-general'}">
56 ${base.gravatar_with_user(comment.author.email, 16)}
59 ${base.gravatar_with_user(comment.author.email, 16)}
57 </div>
60 </div>
58 <div class="date">
61 <div class="date">
59 ${h.age_component(comment.modified_at, time_is_local=True)}
62 ${h.age_component(comment.modified_at, time_is_local=True)}
60 </div>
63 </div>
61 % if inline:
64 % if inline:
62 <span></span>
65 <span></span>
63 % else:
66 % else:
64 <div class="status-change">
67 <div class="status-change">
65 % if comment.pull_request:
68 % if comment.pull_request:
66 <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)}">
69 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
67 % if comment.status_change:
70 % if comment.status_change:
68 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
71 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
69 % else:
72 % else:
70 ${_('pull request #%s') % comment.pull_request.pull_request_id}
73 ${_('pull request #%s') % comment.pull_request.pull_request_id}
71 % endif
74 % endif
72 </a>
75 </a>
73 % else:
76 % else:
74 % if comment.status_change:
77 % if comment.status_change:
75 ${_('Status change on commit')}:
78 ${_('Status change on commit')}:
76 % endif
79 % endif
77 % endif
80 % endif
78 </div>
81 </div>
79 % endif
82 % endif
80
83
81 % if comment.status_change:
84 % if comment.status_change:
82 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
85 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
83 <div title="${_('Commit status')}" class="changeset-status-lbl">
86 <div title="${_('Commit status')}" class="changeset-status-lbl">
84 ${comment.status_change[0].status_lbl}
87 ${comment.status_change[0].status_lbl}
85 </div>
88 </div>
86 % endif
89 % endif
87
90
88 % if comment.resolved_comment:
89 <a class="has-spacer-before" 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)})">
90 ${_('resolves comment #{}').format(comment.resolved_comment.comment_id)}
91 </a>
92 % endif
93
94 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
91 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
95
92
96 <div class="comment-links-block">
93 <div class="comment-links-block">
97 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
94 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
98 <span class="tag authortag tooltip" title="${_('Pull request author')}">
95 <span class="tag authortag tooltip" title="${_('Pull request author')}">
99 ${_('author')}
96 ${_('author')}
100 </span>
97 </span>
101 |
98 |
102 % endif
99 % endif
103 % if inline:
100 % if inline:
104 <div class="pr-version-inline">
101 <div class="pr-version-inline">
105 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
102 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
106 % if outdated_at_ver:
103 % if outdated_at_ver:
107 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
104 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
108 outdated ${'v{}'.format(pr_index_ver)} |
105 outdated ${'v{}'.format(pr_index_ver)} |
109 </code>
106 </code>
110 % elif pr_index_ver:
107 % elif pr_index_ver:
111 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
108 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
112 ${'v{}'.format(pr_index_ver)} |
109 ${'v{}'.format(pr_index_ver)} |
113 </code>
110 </code>
114 % endif
111 % endif
115 </a>
112 </a>
116 </div>
113 </div>
117 % else:
114 % else:
118 % if comment.pull_request_version_id and pr_index_ver:
115 % if comment.pull_request_version_id and pr_index_ver:
119 |
116 |
120 <div class="pr-version">
117 <div class="pr-version">
121 % if comment.outdated:
118 % if comment.outdated:
122 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
119 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
123 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
120 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
124 </a>
121 </a>
125 % else:
122 % else:
126 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
123 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
127 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
124 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
128 <code class="pr-version-num">
125 <code class="pr-version-num">
129 ${'v{}'.format(pr_index_ver)}
126 ${'v{}'.format(pr_index_ver)}
130 </code>
127 </code>
131 </a>
128 </a>
132 </div>
129 </div>
133 % endif
130 % endif
134 </div>
131 </div>
135 % endif
132 % endif
136 % endif
133 % endif
137
134
138 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
135 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
139 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
136 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
140 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
137 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
141 ## permissions to delete
138 ## permissions to delete
142 %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
139 %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
143 ## TODO: dan: add edit comment here
140 ## TODO: dan: add edit comment here
144 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
141 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
145 %else:
142 %else:
146 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
143 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
147 %endif
144 %endif
148 %else:
145 %else:
149 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
146 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
150 %endif
147 %endif
151
148
152 % if outdated_at_ver:
149 % if outdated_at_ver:
153 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
150 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
154 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
151 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
155 % else:
152 % else:
156 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
153 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
157 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
154 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
158 % endif
155 % endif
159
156
160 </div>
157 </div>
161 </div>
158 </div>
162 <div class="text">
159 <div class="text">
163 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
160 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
164 </div>
161 </div>
165
162
166 </div>
163 </div>
167 </%def>
164 </%def>
168
165
169 ## generate main comments
166 ## generate main comments
170 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
167 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
171 <div class="general-comments" id="comments">
168 <div class="general-comments" id="comments">
172 %for comment in comments:
169 %for comment in comments:
173 <div id="comment-tr-${comment.comment_id}">
170 <div id="comment-tr-${comment.comment_id}">
174 ## only render comments that are not from pull request, or from
171 ## only render comments that are not from pull request, or from
175 ## pull request and a status change
172 ## pull request and a status change
176 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
173 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
177 ${comment_block(comment)}
174 ${comment_block(comment)}
178 %endif
175 %endif
179 </div>
176 </div>
180 %endfor
177 %endfor
181 ## to anchor ajax comments
178 ## to anchor ajax comments
182 <div id="injected_page_comments"></div>
179 <div id="injected_page_comments"></div>
183 </div>
180 </div>
184 </%def>
181 </%def>
185
182
186
183
187 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
184 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
188
185
189 <div class="comments">
186 <div class="comments">
190 <%
187 <%
191 if is_pull_request:
188 if is_pull_request:
192 placeholder = _('Leave a comment on this Pull Request.')
189 placeholder = _('Leave a comment on this Pull Request.')
193 elif is_compare:
190 elif is_compare:
194 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
191 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
195 else:
192 else:
196 placeholder = _('Leave a comment on this Commit.')
193 placeholder = _('Leave a comment on this Commit.')
197 %>
194 %>
198
195
199 % if c.rhodecode_user.username != h.DEFAULT_USER:
196 % if c.rhodecode_user.username != h.DEFAULT_USER:
200 <div class="js-template" id="cb-comment-general-form-template">
197 <div class="js-template" id="cb-comment-general-form-template">
201 ## template generated for injection
198 ## template generated for injection
202 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
199 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
203 </div>
200 </div>
204
201
205 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
202 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
206 ## inject form here
203 ## inject form here
207 </div>
204 </div>
208 <script type="text/javascript">
205 <script type="text/javascript">
209 var lineNo = 'general';
206 var lineNo = 'general';
210 var resolvesCommentId = null;
207 var resolvesCommentId = null;
211 var generalCommentForm = Rhodecode.comments.createGeneralComment(
208 var generalCommentForm = Rhodecode.comments.createGeneralComment(
212 lineNo, "${placeholder}", resolvesCommentId);
209 lineNo, "${placeholder}", resolvesCommentId);
213
210
214 // set custom success callback on rangeCommit
211 // set custom success callback on rangeCommit
215 % if is_compare:
212 % if is_compare:
216 generalCommentForm.setHandleFormSubmit(function(o) {
213 generalCommentForm.setHandleFormSubmit(function(o) {
217 var self = generalCommentForm;
214 var self = generalCommentForm;
218
215
219 var text = self.cm.getValue();
216 var text = self.cm.getValue();
220 var status = self.getCommentStatus();
217 var status = self.getCommentStatus();
221 var commentType = self.getCommentType();
218 var commentType = self.getCommentType();
222
219
223 if (text === "" && !status) {
220 if (text === "" && !status) {
224 return;
221 return;
225 }
222 }
226
223
227 // we can pick which commits we want to make the comment by
224 // we can pick which commits we want to make the comment by
228 // selecting them via click on preview pane, this will alter the hidden inputs
225 // selecting them via click on preview pane, this will alter the hidden inputs
229 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
226 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
230
227
231 var commitIds = [];
228 var commitIds = [];
232 $('#changeset_compare_view_content .compare_select').each(function(el) {
229 $('#changeset_compare_view_content .compare_select').each(function(el) {
233 var commitId = this.id.replace('row-', '');
230 var commitId = this.id.replace('row-', '');
234 if ($(this).hasClass('hl') || !cherryPicked) {
231 if ($(this).hasClass('hl') || !cherryPicked) {
235 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
232 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
236 commitIds.push(commitId);
233 commitIds.push(commitId);
237 } else {
234 } else {
238 $("input[data-commit-id='{0}']".format(commitId)).val('')
235 $("input[data-commit-id='{0}']".format(commitId)).val('')
239 }
236 }
240 });
237 });
241
238
242 self.setActionButtonsDisabled(true);
239 self.setActionButtonsDisabled(true);
243 self.cm.setOption("readOnly", true);
240 self.cm.setOption("readOnly", true);
244 var postData = {
241 var postData = {
245 'text': text,
242 'text': text,
246 'changeset_status': status,
243 'changeset_status': status,
247 'comment_type': commentType,
244 'comment_type': commentType,
248 'commit_ids': commitIds,
245 'commit_ids': commitIds,
249 'csrf_token': CSRF_TOKEN
246 'csrf_token': CSRF_TOKEN
250 };
247 };
251
248
252 var submitSuccessCallback = function(o) {
249 var submitSuccessCallback = function(o) {
253 location.reload(true);
250 location.reload(true);
254 };
251 };
255 var submitFailCallback = function(){
252 var submitFailCallback = function(){
256 self.resetCommentFormState(text)
253 self.resetCommentFormState(text)
257 };
254 };
258 self.submitAjaxPOST(
255 self.submitAjaxPOST(
259 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
256 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
260 });
257 });
261 % endif
258 % endif
262
259
263
260
264 </script>
261 </script>
265 % else:
262 % else:
266 ## form state when not logged in
263 ## form state when not logged in
267 <div class="comment-form ac">
264 <div class="comment-form ac">
268
265
269 <div class="comment-area">
266 <div class="comment-area">
270 <div class="comment-area-header">
267 <div class="comment-area-header">
271 <ul class="nav-links clearfix">
268 <ul class="nav-links clearfix">
272 <li class="active">
269 <li class="active">
273 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
270 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
274 </li>
271 </li>
275 <li class="">
272 <li class="">
276 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
273 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
277 </li>
274 </li>
278 </ul>
275 </ul>
279 </div>
276 </div>
280
277
281 <div class="comment-area-write" style="display: block;">
278 <div class="comment-area-write" style="display: block;">
282 <div id="edit-container">
279 <div id="edit-container">
283 <div style="padding: 40px 0">
280 <div style="padding: 40px 0">
284 ${_('You need to be logged in to leave comments.')}
281 ${_('You need to be logged in to leave comments.')}
285 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
282 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
286 </div>
283 </div>
287 </div>
284 </div>
288 <div id="preview-container" class="clearfix" style="display: none;">
285 <div id="preview-container" class="clearfix" style="display: none;">
289 <div id="preview-box" class="preview-box"></div>
286 <div id="preview-box" class="preview-box"></div>
290 </div>
287 </div>
291 </div>
288 </div>
292
289
293 <div class="comment-area-footer">
290 <div class="comment-area-footer">
294 <div class="toolbar">
291 <div class="toolbar">
295 <div class="toolbar-text">
292 <div class="toolbar-text">
296 </div>
293 </div>
297 </div>
294 </div>
298 </div>
295 </div>
299 </div>
296 </div>
300
297
301 <div class="comment-footer">
298 <div class="comment-footer">
302 </div>
299 </div>
303
300
304 </div>
301 </div>
305 % endif
302 % endif
306
303
307 <script type="text/javascript">
304 <script type="text/javascript">
308 bindToggleButtons();
305 bindToggleButtons();
309 </script>
306 </script>
310 </div>
307 </div>
311 </%def>
308 </%def>
312
309
313
310
314 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
311 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
315 ## comment injected based on assumption that user is logged in
312 ## comment injected based on assumption that user is logged in
316
313
317 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
314 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
318
315
319 <div class="comment-area">
316 <div class="comment-area">
320 <div class="comment-area-header">
317 <div class="comment-area-header">
321 <ul class="nav-links clearfix">
318 <ul class="nav-links clearfix">
322 <li class="active">
319 <li class="active">
323 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
320 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
324 </li>
321 </li>
325 <li class="">
322 <li class="">
326 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
323 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
327 </li>
324 </li>
328 <li class="pull-right">
325 <li class="pull-right">
329 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
326 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
330 % for val in c.visual.comment_types:
327 % for val in c.visual.comment_types:
331 <option value="${val}">${val.upper()}</option>
328 <option value="${val}">${val.upper()}</option>
332 % endfor
329 % endfor
333 </select>
330 </select>
334 </li>
331 </li>
335 </ul>
332 </ul>
336 </div>
333 </div>
337
334
338 <div class="comment-area-write" style="display: block;">
335 <div class="comment-area-write" style="display: block;">
339 <div id="edit-container_${lineno_id}">
336 <div id="edit-container_${lineno_id}">
340 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
337 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
341 </div>
338 </div>
342 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
339 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
343 <div id="preview-box_${lineno_id}" class="preview-box"></div>
340 <div id="preview-box_${lineno_id}" class="preview-box"></div>
344 </div>
341 </div>
345 </div>
342 </div>
346
343
347 <div class="comment-area-footer">
344 <div class="comment-area-footer">
348 <div class="toolbar">
345 <div class="toolbar">
349 <div class="toolbar-text">
346 <div class="toolbar-text">
350 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
347 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
351 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
348 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
352 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
349 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
353 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
350 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
354 )
351 )
355 )|n}
352 )|n}
356 </div>
353 </div>
357 </div>
354 </div>
358 </div>
355 </div>
359 </div>
356 </div>
360
357
361 <div class="comment-footer">
358 <div class="comment-footer">
362
359
363 % if review_statuses:
360 % if review_statuses:
364 <div class="status_box">
361 <div class="status_box">
365 <select id="change_status_${lineno_id}" name="changeset_status">
362 <select id="change_status_${lineno_id}" name="changeset_status">
366 <option></option> ## Placeholder
363 <option></option> ## Placeholder
367 % for status, lbl in review_statuses:
364 % for status, lbl in review_statuses:
368 <option value="${status}" data-status="${status}">${lbl}</option>
365 <option value="${status}" data-status="${status}">${lbl}</option>
369 %if is_pull_request and change_status and status in ('approved', 'rejected'):
366 %if is_pull_request and change_status and status in ('approved', 'rejected'):
370 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
367 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
371 %endif
368 %endif
372 % endfor
369 % endfor
373 </select>
370 </select>
374 </div>
371 </div>
375 % endif
372 % endif
376
373
377 ## inject extra inputs into the form
374 ## inject extra inputs into the form
378 % if form_extras and isinstance(form_extras, (list, tuple)):
375 % if form_extras and isinstance(form_extras, (list, tuple)):
379 <div id="comment_form_extras">
376 <div id="comment_form_extras">
380 % for form_ex_el in form_extras:
377 % for form_ex_el in form_extras:
381 ${form_ex_el|n}
378 ${form_ex_el|n}
382 % endfor
379 % endfor
383 </div>
380 </div>
384 % endif
381 % endif
385
382
386 <div class="action-buttons">
383 <div class="action-buttons">
387 ## inline for has a file, and line-number together with cancel hide button.
384 ## inline for has a file, and line-number together with cancel hide button.
388 % if form_type == 'inline':
385 % if form_type == 'inline':
389 <input type="hidden" name="f_path" value="{0}">
386 <input type="hidden" name="f_path" value="{0}">
390 <input type="hidden" name="line" value="${lineno_id}">
387 <input type="hidden" name="line" value="${lineno_id}">
391 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
388 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
392 ${_('Cancel')}
389 ${_('Cancel')}
393 </button>
390 </button>
394 % endif
391 % endif
395
392
396 % if form_type != 'inline':
393 % if form_type != 'inline':
397 <div class="action-buttons-extra"></div>
394 <div class="action-buttons-extra"></div>
398 % endif
395 % endif
399
396
400 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
397 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
401
398
402 </div>
399 </div>
403 </div>
400 </div>
404
401
405 </form>
402 </form>
406
403
407 </%def> No newline at end of file
404 </%def>
@@ -1,1121 +1,1149 b''
1 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
1 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
2
2
3 <%def name="diff_line_anchor(commit, filename, line, type)"><%
3 <%def name="diff_line_anchor(commit, filename, line, type)"><%
4 return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
4 return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
5 %></%def>
5 %></%def>
6
6
7 <%def name="action_class(action)">
7 <%def name="action_class(action)">
8 <%
8 <%
9 return {
9 return {
10 '-': 'cb-deletion',
10 '-': 'cb-deletion',
11 '+': 'cb-addition',
11 '+': 'cb-addition',
12 ' ': 'cb-context',
12 ' ': 'cb-context',
13 }.get(action, 'cb-empty')
13 }.get(action, 'cb-empty')
14 %>
14 %>
15 </%def>
15 </%def>
16
16
17 <%def name="op_class(op_id)">
17 <%def name="op_class(op_id)">
18 <%
18 <%
19 return {
19 return {
20 DEL_FILENODE: 'deletion', # file deleted
20 DEL_FILENODE: 'deletion', # file deleted
21 BIN_FILENODE: 'warning' # binary diff hidden
21 BIN_FILENODE: 'warning' # binary diff hidden
22 }.get(op_id, 'addition')
22 }.get(op_id, 'addition')
23 %>
23 %>
24 </%def>
24 </%def>
25
25
26
26
27
27
28 <%def name="render_diffset(diffset, commit=None,
28 <%def name="render_diffset(diffset, commit=None,
29
29
30 # collapse all file diff entries when there are more than this amount of files in the diff
30 # collapse all file diff entries when there are more than this amount of files in the diff
31 collapse_when_files_over=20,
31 collapse_when_files_over=20,
32
32
33 # collapse lines in the diff when more than this amount of lines changed in the file diff
33 # collapse lines in the diff when more than this amount of lines changed in the file diff
34 lines_changed_limit=500,
34 lines_changed_limit=500,
35
35
36 # add a ruler at to the output
36 # add a ruler at to the output
37 ruler_at_chars=0,
37 ruler_at_chars=0,
38
38
39 # show inline comments
39 # show inline comments
40 use_comments=False,
40 use_comments=False,
41
41
42 # disable new comments
42 # disable new comments
43 disable_new_comments=False,
43 disable_new_comments=False,
44
44
45 # special file-comments that were deleted in previous versions
45 # special file-comments that were deleted in previous versions
46 # it's used for showing outdated comments for deleted files in a PR
46 # it's used for showing outdated comments for deleted files in a PR
47 deleted_files_comments=None,
47 deleted_files_comments=None,
48
48
49 # for cache purpose
49 # for cache purpose
50 inline_comments=None,
50 inline_comments=None,
51
51
52 # additional menu for PRs
53 pull_request_menu=None
54
52 )">
55 )">
53
56
54 <%
57 <%
55 diffset_container_id = h.md5(diffset.target_ref)
58 diffset_container_id = h.md5(diffset.target_ref)
56 collapse_all = len(diffset.files) > collapse_when_files_over
59 collapse_all = len(diffset.files) > collapse_when_files_over
57 %>
60 %>
58
61
59 %if use_comments:
62 %if use_comments:
60 <div id="cb-comments-inline-container-template" class="js-template">
63 <div id="cb-comments-inline-container-template" class="js-template">
61 ${inline_comments_container([], inline_comments)}
64 ${inline_comments_container([], inline_comments)}
62 </div>
65 </div>
63 <div class="js-template" id="cb-comment-inline-form-template">
66 <div class="js-template" id="cb-comment-inline-form-template">
64 <div class="comment-inline-form ac">
67 <div class="comment-inline-form ac">
65
68
66 %if c.rhodecode_user.username != h.DEFAULT_USER:
69 %if c.rhodecode_user.username != h.DEFAULT_USER:
67 ## render template for inline comments
70 ## render template for inline comments
68 ${commentblock.comment_form(form_type='inline')}
71 ${commentblock.comment_form(form_type='inline')}
69 %else:
72 %else:
70 ${h.form('', class_='inline-form comment-form-login', method='get')}
73 ${h.form('', class_='inline-form comment-form-login', method='get')}
71 <div class="pull-left">
74 <div class="pull-left">
72 <div class="comment-help pull-right">
75 <div class="comment-help pull-right">
73 ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
76 ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
74 </div>
77 </div>
75 </div>
78 </div>
76 <div class="comment-button pull-right">
79 <div class="comment-button pull-right">
77 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
80 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
78 ${_('Cancel')}
81 ${_('Cancel')}
79 </button>
82 </button>
80 </div>
83 </div>
81 <div class="clearfix"></div>
84 <div class="clearfix"></div>
82 ${h.end_form()}
85 ${h.end_form()}
83 %endif
86 %endif
84 </div>
87 </div>
85 </div>
88 </div>
86
89
87 %endif
90 %endif
88
91
89 %if c.user_session_attrs["diffmode"] == 'sideside':
92 %if c.user_session_attrs["diffmode"] == 'sideside':
90 <style>
93 <style>
91 .wrapper {
94 .wrapper {
92 max-width: 1600px !important;
95 max-width: 1600px !important;
93 }
96 }
94 </style>
97 </style>
95 %endif
98 %endif
96
99
97 %if ruler_at_chars:
100 %if ruler_at_chars:
98 <style>
101 <style>
99 .diff table.cb .cb-content:after {
102 .diff table.cb .cb-content:after {
100 content: "";
103 content: "";
101 border-left: 1px solid blue;
104 border-left: 1px solid blue;
102 position: absolute;
105 position: absolute;
103 top: 0;
106 top: 0;
104 height: 18px;
107 height: 18px;
105 opacity: .2;
108 opacity: .2;
106 z-index: 10;
109 z-index: 10;
107 //## +5 to account for diff action (+/-)
110 //## +5 to account for diff action (+/-)
108 left: ${ruler_at_chars + 5}ch;
111 left: ${ruler_at_chars + 5}ch;
109 </style>
112 </style>
110 %endif
113 %endif
111
114
112 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
115 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
113
116
114 <div style="height: 20px; line-height: 20px">
117 <div style="height: 20px; line-height: 20px">
115 ## expand/collapse action
118 ## expand/collapse action
116 <div class="pull-left">
119 <div class="pull-left">
117 <a class="${'collapsed' if collapse_all else ''}" href="#expand-files" onclick="toggleExpand(this, '${diffset_container_id}'); return false">
120 <a class="${'collapsed' if collapse_all else ''}" href="#expand-files" onclick="toggleExpand(this, '${diffset_container_id}'); return false">
118 % if collapse_all:
121 % if collapse_all:
119 <i class="icon-plus-squared-alt icon-no-margin"></i>${_('Expand all files')}
122 <i class="icon-plus-squared-alt icon-no-margin"></i>${_('Expand all files')}
120 % else:
123 % else:
121 <i class="icon-minus-squared-alt icon-no-margin"></i>${_('Collapse all files')}
124 <i class="icon-minus-squared-alt icon-no-margin"></i>${_('Collapse all files')}
122 % endif
125 % endif
123 </a>
126 </a>
124
127
125 </div>
128 </div>
126
129
127 ## todos
130 ## todos
131 % if getattr(c, 'at_version', None):
132 <div class="pull-right">
133 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
134 ${_('not available in this view')}
135 </div>
136 % else:
128 <div class="pull-right">
137 <div class="pull-right">
129 <div class="comments-number" style="padding-left: 10px">
138 <div class="comments-number" style="padding-left: 10px">
130 % if hasattr(c, 'unresolved_comments') and hasattr(c, 'resolved_comments'):
139 % if hasattr(c, 'unresolved_comments') and hasattr(c, 'resolved_comments'):
131 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
140 <i class="icon-flag-filled" style="color: #949494">TODOs:</i>
132 % if c.unresolved_comments:
141 % if c.unresolved_comments:
133 <a href="#show-todos" onclick="$('#todo-box').toggle(); return false">
142 <a href="#show-todos" onclick="$('#todo-box').toggle(); return false">
134 ${_('{} unresolved').format(len(c.unresolved_comments))}
143 ${_('{} unresolved').format(len(c.unresolved_comments))}
135 </a>
144 </a>
136 % else:
145 % else:
137 ${_('0 unresolved')}
146 ${_('0 unresolved')}
138 % endif
147 % endif
139
148
140 ${_('{} Resolved').format(len(c.unresolved_comments))}
149 ${_('{} Resolved').format(len(c.resolved_comments))}
141 % endif
150 % endif
142 </div>
151 </div>
143 </div>
152 </div>
153 % endif
144
154
145 ## comments
155 ## comments
146 <div class="pull-right">
156 <div class="pull-right">
147 <div class="comments-number" style="padding-left: 10px">
157 <div class="comments-number" style="padding-left: 10px">
148 % if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'):
158 % if hasattr(c, 'comments') and hasattr(c, 'inline_cnt'):
149 <i class="icon-comment" style="color: #949494">COMMENTS:</i>
159 <i class="icon-comment" style="color: #949494">COMMENTS:</i>
150 % if c.comments:
160 % if c.comments:
151 <a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>,
161 <a href="#comments">${_ungettext("{} General", "{} General", len(c.comments)).format(len(c.comments))}</a>,
152 % else:
162 % else:
153 ${_('0 General')}
163 ${_('0 General')}
154 % endif
164 % endif
155
165
156 % if c.inline_cnt:
166 % if c.inline_cnt:
157 <a href="#" onclick="return Rhodecode.comments.nextComment();"
167 <a href="#" onclick="return Rhodecode.comments.nextComment();"
158 id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)}
168 id="inline-comments-counter">${_ungettext("{} Inline", "{} Inline", c.inline_cnt).format(c.inline_cnt)}
159 </a>
169 </a>
160 % else:
170 % else:
161 ${_('0 Inline')}
171 ${_('0 Inline')}
162 % endif
172 % endif
163 % endif
173 % endif
174
175 % if pull_request_menu:
176 <%
177 outdated_comm_count_ver = pull_request_menu['outdated_comm_count_ver']
178 %>
179
180 % if outdated_comm_count_ver:
181 <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">
182 (${_("{} Outdated").format(outdated_comm_count_ver)})
183 </a>
184 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated')}</a>
185 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated')}</a>
186 % else:
187 (${_("{} Outdated").format(outdated_comm_count_ver)})
188 % endif
189
190 % endif
191
164 </div>
192 </div>
165 </div>
193 </div>
166
194
167 </div>
195 </div>
168
196
169 % if diffset.limited_diff:
197 % if diffset.limited_diff:
170 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
198 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
171 <h2 class="clearinner">
199 <h2 class="clearinner">
172 ${_('The requested changes are too big and content was truncated.')}
200 ${_('The requested changes are too big and content was truncated.')}
173 <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
201 <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
174 </h2>
202 </h2>
175 </div>
203 </div>
176 ## commit range header for each individual diff
204 ## commit range header for each individual diff
177 % elif commit and hasattr(c, 'commit_ranges') and len(c.commit_ranges) > 1:
205 % elif commit and hasattr(c, 'commit_ranges') and len(c.commit_ranges) > 1:
178 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
206 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
179 <div class="clearinner">
207 <div class="clearinner">
180 <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=diffset.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a>
208 <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=diffset.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a>
181 </div>
209 </div>
182 </div>
210 </div>
183 % endif
211 % endif
184
212
185 <div id="todo-box">
213 <div id="todo-box">
186 % if hasattr(c, 'unresolved_comments') and c.unresolved_comments:
214 % if hasattr(c, 'unresolved_comments') and c.unresolved_comments:
187 % for co in c.unresolved_comments:
215 % for co in c.unresolved_comments:
188 <a class="permalink" href="#comment-${co.comment_id}"
216 <a class="permalink" href="#comment-${co.comment_id}"
189 onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))">
217 onclick="Rhodecode.comments.scrollToComment($('#comment-${co.comment_id}'))">
190 <i class="icon-flag-filled-red"></i>
218 <i class="icon-flag-filled-red"></i>
191 ${co.comment_id}</a>${('' if loop.last else ',')}
219 ${co.comment_id}</a>${('' if loop.last else ',')}
192 % endfor
220 % endfor
193 % endif
221 % endif
194 </div>
222 </div>
195 %if diffset.has_hidden_changes:
223 %if diffset.has_hidden_changes:
196 <p class="empty_data">${_('Some changes may be hidden')}</p>
224 <p class="empty_data">${_('Some changes may be hidden')}</p>
197 %elif not diffset.files:
225 %elif not diffset.files:
198 <p class="empty_data">${_('No files')}</p>
226 <p class="empty_data">${_('No files')}</p>
199 %endif
227 %endif
200
228
201 <div class="filediffs">
229 <div class="filediffs">
202
230
203 ## initial value could be marked as False later on
231 ## initial value could be marked as False later on
204 <% over_lines_changed_limit = False %>
232 <% over_lines_changed_limit = False %>
205 %for i, filediff in enumerate(diffset.files):
233 %for i, filediff in enumerate(diffset.files):
206
234
207 <%
235 <%
208 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
236 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
209 over_lines_changed_limit = lines_changed > lines_changed_limit
237 over_lines_changed_limit = lines_changed > lines_changed_limit
210 %>
238 %>
211 ## anchor with support of sticky header
239 ## anchor with support of sticky header
212 <div class="anchor" id="a_${h.FID(filediff.raw_id, filediff.patch['filename'])}"></div>
240 <div class="anchor" id="a_${h.FID(filediff.raw_id, filediff.patch['filename'])}"></div>
213
241
214 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filediff)}" type="checkbox" onchange="updateSticky();">
242 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filediff)}" type="checkbox" onchange="updateSticky();">
215 <div
243 <div
216 class="filediff"
244 class="filediff"
217 data-f-path="${filediff.patch['filename']}"
245 data-f-path="${filediff.patch['filename']}"
218 data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}"
246 data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}"
219 >
247 >
220 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
248 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
221 <div class="filediff-collapse-indicator icon-"></div>
249 <div class="filediff-collapse-indicator icon-"></div>
222 ${diff_ops(filediff)}
250 ${diff_ops(filediff)}
223 </label>
251 </label>
224
252
225 ${diff_menu(filediff, use_comments=use_comments)}
253 ${diff_menu(filediff, use_comments=use_comments)}
226 <table data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
254 <table data-f-path="${filediff.patch['filename']}" data-anchor-id="${h.FID(filediff.raw_id, filediff.patch['filename'])}" class="code-visible-block cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
227
255
228 ## new/deleted/empty content case
256 ## new/deleted/empty content case
229 % if not filediff.hunks:
257 % if not filediff.hunks:
230 ## Comment container, on "fakes" hunk that contains all data to render comments
258 ## Comment container, on "fakes" hunk that contains all data to render comments
231 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], filediff.hunk_ops, use_comments=use_comments, inline_comments=inline_comments)}
259 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], filediff.hunk_ops, use_comments=use_comments, inline_comments=inline_comments)}
232 % endif
260 % endif
233
261
234 %if filediff.limited_diff:
262 %if filediff.limited_diff:
235 <tr class="cb-warning cb-collapser">
263 <tr class="cb-warning cb-collapser">
236 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
264 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
237 ${_('The requested commit or file is too big and content was truncated.')} <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
265 ${_('The requested commit or file is too big and content was truncated.')} <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
238 </td>
266 </td>
239 </tr>
267 </tr>
240 %else:
268 %else:
241 %if over_lines_changed_limit:
269 %if over_lines_changed_limit:
242 <tr class="cb-warning cb-collapser">
270 <tr class="cb-warning cb-collapser">
243 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
271 <td class="cb-text" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=6')}>
244 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
272 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
245 <a href="#" class="cb-expand"
273 <a href="#" class="cb-expand"
246 onclick="$(this).closest('table').removeClass('cb-collapsed'); updateSticky(); return false;">${_('Show them')}
274 onclick="$(this).closest('table').removeClass('cb-collapsed'); updateSticky(); return false;">${_('Show them')}
247 </a>
275 </a>
248 <a href="#" class="cb-collapse"
276 <a href="#" class="cb-collapse"
249 onclick="$(this).closest('table').addClass('cb-collapsed'); updateSticky(); return false;">${_('Hide them')}
277 onclick="$(this).closest('table').addClass('cb-collapsed'); updateSticky(); return false;">${_('Hide them')}
250 </a>
278 </a>
251 </td>
279 </td>
252 </tr>
280 </tr>
253 %endif
281 %endif
254 %endif
282 %endif
255
283
256 % for hunk in filediff.hunks:
284 % for hunk in filediff.hunks:
257 <tr class="cb-hunk">
285 <tr class="cb-hunk">
258 <td ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=3' or '')}>
286 <td ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=3' or '')}>
259 ## TODO: dan: add ajax loading of more context here
287 ## TODO: dan: add ajax loading of more context here
260 ## <a href="#">
288 ## <a href="#">
261 <i class="icon-more"></i>
289 <i class="icon-more"></i>
262 ## </a>
290 ## </a>
263 </td>
291 </td>
264 <td ${(c.user_session_attrs["diffmode"] == 'sideside' and 'colspan=5' or '')}>
292 <td ${(c.user_session_attrs["diffmode"] == 'sideside' and 'colspan=5' or '')}>
265 @@
293 @@
266 -${hunk.source_start},${hunk.source_length}
294 -${hunk.source_start},${hunk.source_length}
267 +${hunk.target_start},${hunk.target_length}
295 +${hunk.target_start},${hunk.target_length}
268 ${hunk.section_header}
296 ${hunk.section_header}
269 </td>
297 </td>
270 </tr>
298 </tr>
271 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], hunk, use_comments=use_comments, inline_comments=inline_comments)}
299 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], hunk, use_comments=use_comments, inline_comments=inline_comments)}
272 % endfor
300 % endfor
273
301
274 <% unmatched_comments = (inline_comments or {}).get(filediff.patch['filename'], {}) %>
302 <% unmatched_comments = (inline_comments or {}).get(filediff.patch['filename'], {}) %>
275
303
276 ## outdated comments that do not fit into currently displayed lines
304 ## outdated comments that do not fit into currently displayed lines
277 % for lineno, comments in unmatched_comments.items():
305 % for lineno, comments in unmatched_comments.items():
278
306
279 %if c.user_session_attrs["diffmode"] == 'unified':
307 %if c.user_session_attrs["diffmode"] == 'unified':
280 % if loop.index == 0:
308 % if loop.index == 0:
281 <tr class="cb-hunk">
309 <tr class="cb-hunk">
282 <td colspan="3"></td>
310 <td colspan="3"></td>
283 <td>
311 <td>
284 <div>
312 <div>
285 ${_('Unmatched inline comments below')}
313 ${_('Unmatched inline comments below')}
286 </div>
314 </div>
287 </td>
315 </td>
288 </tr>
316 </tr>
289 % endif
317 % endif
290 <tr class="cb-line">
318 <tr class="cb-line">
291 <td class="cb-data cb-context"></td>
319 <td class="cb-data cb-context"></td>
292 <td class="cb-lineno cb-context"></td>
320 <td class="cb-lineno cb-context"></td>
293 <td class="cb-lineno cb-context"></td>
321 <td class="cb-lineno cb-context"></td>
294 <td class="cb-content cb-context">
322 <td class="cb-content cb-context">
295 ${inline_comments_container(comments, inline_comments)}
323 ${inline_comments_container(comments, inline_comments)}
296 </td>
324 </td>
297 </tr>
325 </tr>
298 %elif c.user_session_attrs["diffmode"] == 'sideside':
326 %elif c.user_session_attrs["diffmode"] == 'sideside':
299 % if loop.index == 0:
327 % if loop.index == 0:
300 <tr class="cb-comment-info">
328 <tr class="cb-comment-info">
301 <td colspan="2"></td>
329 <td colspan="2"></td>
302 <td class="cb-line">
330 <td class="cb-line">
303 <div>
331 <div>
304 ${_('Unmatched inline comments below')}
332 ${_('Unmatched inline comments below')}
305 </div>
333 </div>
306 </td>
334 </td>
307 <td colspan="2"></td>
335 <td colspan="2"></td>
308 <td class="cb-line">
336 <td class="cb-line">
309 <div>
337 <div>
310 ${_('Unmatched comments below')}
338 ${_('Unmatched comments below')}
311 </div>
339 </div>
312 </td>
340 </td>
313 </tr>
341 </tr>
314 % endif
342 % endif
315 <tr class="cb-line">
343 <tr class="cb-line">
316 <td class="cb-data cb-context"></td>
344 <td class="cb-data cb-context"></td>
317 <td class="cb-lineno cb-context"></td>
345 <td class="cb-lineno cb-context"></td>
318 <td class="cb-content cb-context">
346 <td class="cb-content cb-context">
319 % if lineno.startswith('o'):
347 % if lineno.startswith('o'):
320 ${inline_comments_container(comments, inline_comments)}
348 ${inline_comments_container(comments, inline_comments)}
321 % endif
349 % endif
322 </td>
350 </td>
323
351
324 <td class="cb-data cb-context"></td>
352 <td class="cb-data cb-context"></td>
325 <td class="cb-lineno cb-context"></td>
353 <td class="cb-lineno cb-context"></td>
326 <td class="cb-content cb-context">
354 <td class="cb-content cb-context">
327 % if lineno.startswith('n'):
355 % if lineno.startswith('n'):
328 ${inline_comments_container(comments, inline_comments)}
356 ${inline_comments_container(comments, inline_comments)}
329 % endif
357 % endif
330 </td>
358 </td>
331 </tr>
359 </tr>
332 %endif
360 %endif
333
361
334 % endfor
362 % endfor
335
363
336 </table>
364 </table>
337 </div>
365 </div>
338 %endfor
366 %endfor
339
367
340 ## outdated comments that are made for a file that has been deleted
368 ## outdated comments that are made for a file that has been deleted
341 % for filename, comments_dict in (deleted_files_comments or {}).items():
369 % for filename, comments_dict in (deleted_files_comments or {}).items():
342 <%
370 <%
343 display_state = 'display: none'
371 display_state = 'display: none'
344 open_comments_in_file = [x for x in comments_dict['comments'] if x.outdated is False]
372 open_comments_in_file = [x for x in comments_dict['comments'] if x.outdated is False]
345 if open_comments_in_file:
373 if open_comments_in_file:
346 display_state = ''
374 display_state = ''
347 %>
375 %>
348 <div class="filediffs filediff-outdated" style="${display_state}">
376 <div class="filediffs filediff-outdated" style="${display_state}">
349 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filename)}" type="checkbox" onchange="updateSticky();">
377 <input ${(collapse_all and 'checked' or '')} class="filediff-collapse-state collapse-${diffset_container_id}" id="filediff-collapse-${id(filename)}" type="checkbox" onchange="updateSticky();">
350 <div class="filediff" data-f-path="${filename}" id="a_${h.FID(filediff.raw_id, filename)}">
378 <div class="filediff" data-f-path="${filename}" id="a_${h.FID(filediff.raw_id, filename)}">
351 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
379 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
352 <div class="filediff-collapse-indicator"></div>
380 <div class="filediff-collapse-indicator icon-"></div>
353
381
354 <span class="pill">
382 <span class="pill">
355 ## file was deleted
383 ## file was deleted
356 ${filename}
384 ${filename}
357 </span>
385 </span>
358 <span class="pill-group pull-left" >
386 <span class="pill-group pull-left" >
359 ## file op, doesn't need translation
387 ## file op, doesn't need translation
360 <span class="pill" op="removed">removed in this version</span>
388 <span class="pill" op="removed">removed in this version</span>
361 </span>
389 </span>
362 <a class="pill filediff-anchor" href="#a_${h.FID(filediff.raw_id, filename)}">ΒΆ</a>
390 <a class="pill filediff-anchor" href="#a_${h.FID(filediff.raw_id, filename)}">ΒΆ</a>
363 <span class="pill-group pull-right">
391 <span class="pill-group pull-right">
364 <span class="pill" op="deleted">-${comments_dict['stats']}</span>
392 <span class="pill" op="deleted">-${comments_dict['stats']}</span>
365 </span>
393 </span>
366 </label>
394 </label>
367
395
368 <table class="cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
396 <table class="cb cb-diff-${c.user_session_attrs["diffmode"]} code-highlight ${(over_lines_changed_limit and 'cb-collapsed' or '')}">
369 <tr>
397 <tr>
370 % if c.user_session_attrs["diffmode"] == 'unified':
398 % if c.user_session_attrs["diffmode"] == 'unified':
371 <td></td>
399 <td></td>
372 %endif
400 %endif
373
401
374 <td></td>
402 <td></td>
375 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=5')}>
403 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${(c.user_session_attrs["diffmode"] == 'unified' and 'colspan=4' or 'colspan=5')}>
376 ${_('File was deleted in this version. There are still outdated/unresolved comments attached to it.')}
404 ${_('File was deleted in this version. There are still outdated/unresolved comments attached to it.')}
377 </td>
405 </td>
378 </tr>
406 </tr>
379 %if c.user_session_attrs["diffmode"] == 'unified':
407 %if c.user_session_attrs["diffmode"] == 'unified':
380 <tr class="cb-line">
408 <tr class="cb-line">
381 <td class="cb-data cb-context"></td>
409 <td class="cb-data cb-context"></td>
382 <td class="cb-lineno cb-context"></td>
410 <td class="cb-lineno cb-context"></td>
383 <td class="cb-lineno cb-context"></td>
411 <td class="cb-lineno cb-context"></td>
384 <td class="cb-content cb-context">
412 <td class="cb-content cb-context">
385 ${inline_comments_container(comments_dict['comments'], inline_comments)}
413 ${inline_comments_container(comments_dict['comments'], inline_comments)}
386 </td>
414 </td>
387 </tr>
415 </tr>
388 %elif c.user_session_attrs["diffmode"] == 'sideside':
416 %elif c.user_session_attrs["diffmode"] == 'sideside':
389 <tr class="cb-line">
417 <tr class="cb-line">
390 <td class="cb-data cb-context"></td>
418 <td class="cb-data cb-context"></td>
391 <td class="cb-lineno cb-context"></td>
419 <td class="cb-lineno cb-context"></td>
392 <td class="cb-content cb-context"></td>
420 <td class="cb-content cb-context"></td>
393
421
394 <td class="cb-data cb-context"></td>
422 <td class="cb-data cb-context"></td>
395 <td class="cb-lineno cb-context"></td>
423 <td class="cb-lineno cb-context"></td>
396 <td class="cb-content cb-context">
424 <td class="cb-content cb-context">
397 ${inline_comments_container(comments_dict['comments'], inline_comments)}
425 ${inline_comments_container(comments_dict['comments'], inline_comments)}
398 </td>
426 </td>
399 </tr>
427 </tr>
400 %endif
428 %endif
401 </table>
429 </table>
402 </div>
430 </div>
403 </div>
431 </div>
404 % endfor
432 % endfor
405
433
406 </div>
434 </div>
407 </div>
435 </div>
408 </%def>
436 </%def>
409
437
410 <%def name="diff_ops(filediff)">
438 <%def name="diff_ops(filediff)">
411 <%
439 <%
412 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
440 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
413 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
441 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
414 %>
442 %>
415 <span class="pill">
443 <span class="pill">
416 <i class="icon-file-text"></i>
444 <i class="icon-file-text"></i>
417 %if filediff.source_file_path and filediff.target_file_path:
445 %if filediff.source_file_path and filediff.target_file_path:
418 %if filediff.source_file_path != filediff.target_file_path:
446 %if filediff.source_file_path != filediff.target_file_path:
419 ## file was renamed, or copied
447 ## file was renamed, or copied
420 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
448 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
421 ${filediff.target_file_path} β¬… <del>${filediff.source_file_path}</del>
449 ${filediff.target_file_path} β¬… <del>${filediff.source_file_path}</del>
422 <% final_path = filediff.target_file_path %>
450 <% final_path = filediff.target_file_path %>
423 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
451 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
424 ${filediff.target_file_path} β¬… ${filediff.source_file_path}
452 ${filediff.target_file_path} β¬… ${filediff.source_file_path}
425 <% final_path = filediff.target_file_path %>
453 <% final_path = filediff.target_file_path %>
426 %endif
454 %endif
427 %else:
455 %else:
428 ## file was modified
456 ## file was modified
429 ${filediff.source_file_path}
457 ${filediff.source_file_path}
430 <% final_path = filediff.source_file_path %>
458 <% final_path = filediff.source_file_path %>
431 %endif
459 %endif
432 %else:
460 %else:
433 %if filediff.source_file_path:
461 %if filediff.source_file_path:
434 ## file was deleted
462 ## file was deleted
435 ${filediff.source_file_path}
463 ${filediff.source_file_path}
436 <% final_path = filediff.source_file_path %>
464 <% final_path = filediff.source_file_path %>
437 %else:
465 %else:
438 ## file was added
466 ## file was added
439 ${filediff.target_file_path}
467 ${filediff.target_file_path}
440 <% final_path = filediff.target_file_path %>
468 <% final_path = filediff.target_file_path %>
441 %endif
469 %endif
442 %endif
470 %endif
443 <i style="color: #aaa" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="${_('Copy the full path')}" onclick="return false;"></i>
471 <i style="color: #aaa" class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="${_('Copy the full path')}" onclick="return false;"></i>
444 </span>
472 </span>
445 ## anchor link
473 ## anchor link
446 <a class="pill filediff-anchor" href="#a_${h.FID(filediff.raw_id, filediff.patch['filename'])}">ΒΆ</a>
474 <a class="pill filediff-anchor" href="#a_${h.FID(filediff.raw_id, filediff.patch['filename'])}">ΒΆ</a>
447
475
448 <span class="pill-group pull-right">
476 <span class="pill-group pull-right">
449
477
450 ## ops pills
478 ## ops pills
451 %if filediff.limited_diff:
479 %if filediff.limited_diff:
452 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
480 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
453 %endif
481 %endif
454
482
455 %if NEW_FILENODE in filediff.patch['stats']['ops']:
483 %if NEW_FILENODE in filediff.patch['stats']['ops']:
456 <span class="pill" op="created">created</span>
484 <span class="pill" op="created">created</span>
457 %if filediff['target_mode'].startswith('120'):
485 %if filediff['target_mode'].startswith('120'):
458 <span class="pill" op="symlink">symlink</span>
486 <span class="pill" op="symlink">symlink</span>
459 %else:
487 %else:
460 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
488 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
461 %endif
489 %endif
462 %endif
490 %endif
463
491
464 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
492 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
465 <span class="pill" op="renamed">renamed</span>
493 <span class="pill" op="renamed">renamed</span>
466 %endif
494 %endif
467
495
468 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
496 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
469 <span class="pill" op="copied">copied</span>
497 <span class="pill" op="copied">copied</span>
470 %endif
498 %endif
471
499
472 %if DEL_FILENODE in filediff.patch['stats']['ops']:
500 %if DEL_FILENODE in filediff.patch['stats']['ops']:
473 <span class="pill" op="removed">removed</span>
501 <span class="pill" op="removed">removed</span>
474 %endif
502 %endif
475
503
476 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
504 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
477 <span class="pill" op="mode">
505 <span class="pill" op="mode">
478 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
506 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
479 </span>
507 </span>
480 %endif
508 %endif
481
509
482 %if BIN_FILENODE in filediff.patch['stats']['ops']:
510 %if BIN_FILENODE in filediff.patch['stats']['ops']:
483 <span class="pill" op="binary">binary</span>
511 <span class="pill" op="binary">binary</span>
484 %if MOD_FILENODE in filediff.patch['stats']['ops']:
512 %if MOD_FILENODE in filediff.patch['stats']['ops']:
485 <span class="pill" op="modified">modified</span>
513 <span class="pill" op="modified">modified</span>
486 %endif
514 %endif
487 %endif
515 %endif
488
516
489 <span class="pill" op="added">${('+' if filediff.patch['stats']['added'] else '')}${filediff.patch['stats']['added']}</span>
517 <span class="pill" op="added">${('+' if filediff.patch['stats']['added'] else '')}${filediff.patch['stats']['added']}</span>
490 <span class="pill" op="deleted">${((h.safe_int(filediff.patch['stats']['deleted']) or 0) * -1)}</span>
518 <span class="pill" op="deleted">${((h.safe_int(filediff.patch['stats']['deleted']) or 0) * -1)}</span>
491
519
492 </span>
520 </span>
493
521
494 </%def>
522 </%def>
495
523
496 <%def name="nice_mode(filemode)">
524 <%def name="nice_mode(filemode)">
497 ${(filemode.startswith('100') and filemode[3:] or filemode)}
525 ${(filemode.startswith('100') and filemode[3:] or filemode)}
498 </%def>
526 </%def>
499
527
500 <%def name="diff_menu(filediff, use_comments=False)">
528 <%def name="diff_menu(filediff, use_comments=False)">
501 <div class="filediff-menu">
529 <div class="filediff-menu">
502
530
503 %if filediff.diffset.source_ref:
531 %if filediff.diffset.source_ref:
504
532
505 ## FILE BEFORE CHANGES
533 ## FILE BEFORE CHANGES
506 %if filediff.operation in ['D', 'M']:
534 %if filediff.operation in ['D', 'M']:
507 <a
535 <a
508 class="tooltip"
536 class="tooltip"
509 href="${h.route_path('repo_files',repo_name=filediff.diffset.target_repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
537 href="${h.route_path('repo_files',repo_name=filediff.diffset.target_repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
510 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
538 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
511 >
539 >
512 ${_('Show file before')}
540 ${_('Show file before')}
513 </a> |
541 </a> |
514 %else:
542 %else:
515 <span
543 <span
516 class="tooltip"
544 class="tooltip"
517 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
545 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
518 >
546 >
519 ${_('Show file before')}
547 ${_('Show file before')}
520 </span> |
548 </span> |
521 %endif
549 %endif
522
550
523 ## FILE AFTER CHANGES
551 ## FILE AFTER CHANGES
524 %if filediff.operation in ['A', 'M']:
552 %if filediff.operation in ['A', 'M']:
525 <a
553 <a
526 class="tooltip"
554 class="tooltip"
527 href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}"
555 href="${h.route_path('repo_files',repo_name=filediff.diffset.source_repo_name,commit_id=filediff.diffset.target_ref,f_path=filediff.target_file_path)}"
528 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
556 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
529 >
557 >
530 ${_('Show file after')}
558 ${_('Show file after')}
531 </a>
559 </a>
532 %else:
560 %else:
533 <span
561 <span
534 class="tooltip"
562 class="tooltip"
535 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
563 title="${h.tooltip(_('File not present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
536 >
564 >
537 ${_('Show file after')}
565 ${_('Show file after')}
538 </span>
566 </span>
539 %endif
567 %endif
540
568
541 % if use_comments:
569 % if use_comments:
542 |
570 |
543 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
571 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
544 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
572 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
545 </a>
573 </a>
546 % endif
574 % endif
547
575
548 %endif
576 %endif
549
577
550 </div>
578 </div>
551 </%def>
579 </%def>
552
580
553
581
554 <%def name="inline_comments_container(comments, inline_comments)">
582 <%def name="inline_comments_container(comments, inline_comments)">
555 <div class="inline-comments">
583 <div class="inline-comments">
556 %for comment in comments:
584 %for comment in comments:
557 ${commentblock.comment_block(comment, inline=True)}
585 ${commentblock.comment_block(comment, inline=True)}
558 %endfor
586 %endfor
559 % if comments and comments[-1].outdated:
587 % if comments and comments[-1].outdated:
560 <span class="btn btn-secondary cb-comment-add-button comment-outdated}" style="display: none;}">
588 <span class="btn btn-secondary cb-comment-add-button comment-outdated}" style="display: none;}">
561 ${_('Add another comment')}
589 ${_('Add another comment')}
562 </span>
590 </span>
563 % else:
591 % else:
564 <span onclick="return Rhodecode.comments.createComment(this)" class="btn btn-secondary cb-comment-add-button">
592 <span onclick="return Rhodecode.comments.createComment(this)" class="btn btn-secondary cb-comment-add-button">
565 ${_('Add another comment')}
593 ${_('Add another comment')}
566 </span>
594 </span>
567 % endif
595 % endif
568
596
569 </div>
597 </div>
570 </%def>
598 </%def>
571
599
572 <%!
600 <%!
573 def get_comments_for(diff_type, comments, filename, line_version, line_number):
601 def get_comments_for(diff_type, comments, filename, line_version, line_number):
574 if hasattr(filename, 'unicode_path'):
602 if hasattr(filename, 'unicode_path'):
575 filename = filename.unicode_path
603 filename = filename.unicode_path
576
604
577 if not isinstance(filename, (unicode, str)):
605 if not isinstance(filename, (unicode, str)):
578 return None
606 return None
579
607
580 line_key = '{}{}'.format(line_version, line_number) ## e.g o37, n12
608 line_key = '{}{}'.format(line_version, line_number) ## e.g o37, n12
581
609
582 if comments and filename in comments:
610 if comments and filename in comments:
583 file_comments = comments[filename]
611 file_comments = comments[filename]
584 if line_key in file_comments:
612 if line_key in file_comments:
585 data = file_comments.pop(line_key)
613 data = file_comments.pop(line_key)
586 return data
614 return data
587 %>
615 %>
588
616
589 <%def name="render_hunk_lines_sideside(filediff, hunk, use_comments=False, inline_comments=None)">
617 <%def name="render_hunk_lines_sideside(filediff, hunk, use_comments=False, inline_comments=None)">
590 %for i, line in enumerate(hunk.sideside):
618 %for i, line in enumerate(hunk.sideside):
591 <%
619 <%
592 old_line_anchor, new_line_anchor = None, None
620 old_line_anchor, new_line_anchor = None, None
593
621
594 if line.original.lineno:
622 if line.original.lineno:
595 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, line.original.lineno, 'o')
623 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, line.original.lineno, 'o')
596 if line.modified.lineno:
624 if line.modified.lineno:
597 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, line.modified.lineno, 'n')
625 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, line.modified.lineno, 'n')
598 %>
626 %>
599
627
600 <tr class="cb-line">
628 <tr class="cb-line">
601 <td class="cb-data ${action_class(line.original.action)}"
629 <td class="cb-data ${action_class(line.original.action)}"
602 data-line-no="${line.original.lineno}"
630 data-line-no="${line.original.lineno}"
603 >
631 >
604 <div>
632 <div>
605
633
606 <% line_old_comments = None %>
634 <% line_old_comments = None %>
607 %if line.original.get_comment_args:
635 %if line.original.get_comment_args:
608 <% line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) %>
636 <% line_old_comments = get_comments_for('side-by-side', inline_comments, *line.original.get_comment_args) %>
609 %endif
637 %endif
610 %if line_old_comments:
638 %if line_old_comments:
611 <% has_outdated = any([x.outdated for x in line_old_comments]) %>
639 <% has_outdated = any([x.outdated for x in line_old_comments]) %>
612 % if has_outdated:
640 % if has_outdated:
613 <i title="${_('comments including outdated')}:${len(line_old_comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
641 <i title="${_('comments including outdated')}:${len(line_old_comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
614 % else:
642 % else:
615 <i title="${_('comments')}: ${len(line_old_comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
643 <i title="${_('comments')}: ${len(line_old_comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
616 % endif
644 % endif
617 %endif
645 %endif
618 </div>
646 </div>
619 </td>
647 </td>
620 <td class="cb-lineno ${action_class(line.original.action)}"
648 <td class="cb-lineno ${action_class(line.original.action)}"
621 data-line-no="${line.original.lineno}"
649 data-line-no="${line.original.lineno}"
622 %if old_line_anchor:
650 %if old_line_anchor:
623 id="${old_line_anchor}"
651 id="${old_line_anchor}"
624 %endif
652 %endif
625 >
653 >
626 %if line.original.lineno:
654 %if line.original.lineno:
627 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
655 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
628 %endif
656 %endif
629 </td>
657 </td>
630 <td class="cb-content ${action_class(line.original.action)}"
658 <td class="cb-content ${action_class(line.original.action)}"
631 data-line-no="o${line.original.lineno}"
659 data-line-no="o${line.original.lineno}"
632 >
660 >
633 %if use_comments and line.original.lineno:
661 %if use_comments and line.original.lineno:
634 ${render_add_comment_button()}
662 ${render_add_comment_button()}
635 %endif
663 %endif
636 <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span>
664 <span class="cb-code"><span class="cb-action ${action_class(line.original.action)}"></span>${line.original.content or '' | n}</span>
637
665
638 %if use_comments and line.original.lineno and line_old_comments:
666 %if use_comments and line.original.lineno and line_old_comments:
639 ${inline_comments_container(line_old_comments, inline_comments)}
667 ${inline_comments_container(line_old_comments, inline_comments)}
640 %endif
668 %endif
641
669
642 </td>
670 </td>
643 <td class="cb-data ${action_class(line.modified.action)}"
671 <td class="cb-data ${action_class(line.modified.action)}"
644 data-line-no="${line.modified.lineno}"
672 data-line-no="${line.modified.lineno}"
645 >
673 >
646 <div>
674 <div>
647
675
648 %if line.modified.get_comment_args:
676 %if line.modified.get_comment_args:
649 <% line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) %>
677 <% line_new_comments = get_comments_for('side-by-side', inline_comments, *line.modified.get_comment_args) %>
650 %else:
678 %else:
651 <% line_new_comments = None%>
679 <% line_new_comments = None%>
652 %endif
680 %endif
653 %if line_new_comments:
681 %if line_new_comments:
654 <% has_outdated = any([x.outdated for x in line_new_comments]) %>
682 <% has_outdated = any([x.outdated for x in line_new_comments]) %>
655 % if has_outdated:
683 % if has_outdated:
656 <i title="${_('comments including outdated')}:${len(line_new_comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
684 <i title="${_('comments including outdated')}:${len(line_new_comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
657 % else:
685 % else:
658 <i title="${_('comments')}: ${len(line_new_comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
686 <i title="${_('comments')}: ${len(line_new_comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
659 % endif
687 % endif
660 %endif
688 %endif
661 </div>
689 </div>
662 </td>
690 </td>
663 <td class="cb-lineno ${action_class(line.modified.action)}"
691 <td class="cb-lineno ${action_class(line.modified.action)}"
664 data-line-no="${line.modified.lineno}"
692 data-line-no="${line.modified.lineno}"
665 %if new_line_anchor:
693 %if new_line_anchor:
666 id="${new_line_anchor}"
694 id="${new_line_anchor}"
667 %endif
695 %endif
668 >
696 >
669 %if line.modified.lineno:
697 %if line.modified.lineno:
670 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
698 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
671 %endif
699 %endif
672 </td>
700 </td>
673 <td class="cb-content ${action_class(line.modified.action)}"
701 <td class="cb-content ${action_class(line.modified.action)}"
674 data-line-no="n${line.modified.lineno}"
702 data-line-no="n${line.modified.lineno}"
675 >
703 >
676 %if use_comments and line.modified.lineno:
704 %if use_comments and line.modified.lineno:
677 ${render_add_comment_button()}
705 ${render_add_comment_button()}
678 %endif
706 %endif
679 <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span>
707 <span class="cb-code"><span class="cb-action ${action_class(line.modified.action)}"></span>${line.modified.content or '' | n}</span>
680 %if use_comments and line.modified.lineno and line_new_comments:
708 %if use_comments and line.modified.lineno and line_new_comments:
681 ${inline_comments_container(line_new_comments, inline_comments)}
709 ${inline_comments_container(line_new_comments, inline_comments)}
682 %endif
710 %endif
683 </td>
711 </td>
684 </tr>
712 </tr>
685 %endfor
713 %endfor
686 </%def>
714 </%def>
687
715
688
716
689 <%def name="render_hunk_lines_unified(filediff, hunk, use_comments=False, inline_comments=None)">
717 <%def name="render_hunk_lines_unified(filediff, hunk, use_comments=False, inline_comments=None)">
690 %for old_line_no, new_line_no, action, content, comments_args in hunk.unified:
718 %for old_line_no, new_line_no, action, content, comments_args in hunk.unified:
691
719
692 <%
720 <%
693 old_line_anchor, new_line_anchor = None, None
721 old_line_anchor, new_line_anchor = None, None
694 if old_line_no:
722 if old_line_no:
695 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, old_line_no, 'o')
723 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, old_line_no, 'o')
696 if new_line_no:
724 if new_line_no:
697 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, new_line_no, 'n')
725 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, new_line_no, 'n')
698 %>
726 %>
699 <tr class="cb-line">
727 <tr class="cb-line">
700 <td class="cb-data ${action_class(action)}">
728 <td class="cb-data ${action_class(action)}">
701 <div>
729 <div>
702
730
703 %if comments_args:
731 %if comments_args:
704 <% comments = get_comments_for('unified', inline_comments, *comments_args) %>
732 <% comments = get_comments_for('unified', inline_comments, *comments_args) %>
705 %else:
733 %else:
706 <% comments = None %>
734 <% comments = None %>
707 %endif
735 %endif
708
736
709 % if comments:
737 % if comments:
710 <% has_outdated = any([x.outdated for x in comments]) %>
738 <% has_outdated = any([x.outdated for x in comments]) %>
711 % if has_outdated:
739 % if has_outdated:
712 <i title="${_('comments including outdated')}:${len(comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
740 <i title="${_('comments including outdated')}:${len(comments)}" class="icon-comment-toggle" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
713 % else:
741 % else:
714 <i title="${_('comments')}: ${len(comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
742 <i title="${_('comments')}: ${len(comments)}" class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
715 % endif
743 % endif
716 % endif
744 % endif
717 </div>
745 </div>
718 </td>
746 </td>
719 <td class="cb-lineno ${action_class(action)}"
747 <td class="cb-lineno ${action_class(action)}"
720 data-line-no="${old_line_no}"
748 data-line-no="${old_line_no}"
721 %if old_line_anchor:
749 %if old_line_anchor:
722 id="${old_line_anchor}"
750 id="${old_line_anchor}"
723 %endif
751 %endif
724 >
752 >
725 %if old_line_anchor:
753 %if old_line_anchor:
726 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
754 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
727 %endif
755 %endif
728 </td>
756 </td>
729 <td class="cb-lineno ${action_class(action)}"
757 <td class="cb-lineno ${action_class(action)}"
730 data-line-no="${new_line_no}"
758 data-line-no="${new_line_no}"
731 %if new_line_anchor:
759 %if new_line_anchor:
732 id="${new_line_anchor}"
760 id="${new_line_anchor}"
733 %endif
761 %endif
734 >
762 >
735 %if new_line_anchor:
763 %if new_line_anchor:
736 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
764 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
737 %endif
765 %endif
738 </td>
766 </td>
739 <td class="cb-content ${action_class(action)}"
767 <td class="cb-content ${action_class(action)}"
740 data-line-no="${(new_line_no and 'n' or 'o')}${(new_line_no or old_line_no)}"
768 data-line-no="${(new_line_no and 'n' or 'o')}${(new_line_no or old_line_no)}"
741 >
769 >
742 %if use_comments:
770 %if use_comments:
743 ${render_add_comment_button()}
771 ${render_add_comment_button()}
744 %endif
772 %endif
745 <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span>
773 <span class="cb-code"><span class="cb-action ${action_class(action)}"></span> ${content or '' | n}</span>
746 %if use_comments and comments:
774 %if use_comments and comments:
747 ${inline_comments_container(comments, inline_comments)}
775 ${inline_comments_container(comments, inline_comments)}
748 %endif
776 %endif
749 </td>
777 </td>
750 </tr>
778 </tr>
751 %endfor
779 %endfor
752 </%def>
780 </%def>
753
781
754
782
755 <%def name="render_hunk_lines(filediff, diff_mode, hunk, use_comments, inline_comments)">
783 <%def name="render_hunk_lines(filediff, diff_mode, hunk, use_comments, inline_comments)">
756 % if diff_mode == 'unified':
784 % if diff_mode == 'unified':
757 ${render_hunk_lines_unified(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments)}
785 ${render_hunk_lines_unified(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments)}
758 % elif diff_mode == 'sideside':
786 % elif diff_mode == 'sideside':
759 ${render_hunk_lines_sideside(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments)}
787 ${render_hunk_lines_sideside(filediff, hunk, use_comments=use_comments, inline_comments=inline_comments)}
760 % else:
788 % else:
761 <tr class="cb-line">
789 <tr class="cb-line">
762 <td>unknown diff mode</td>
790 <td>unknown diff mode</td>
763 </tr>
791 </tr>
764 % endif
792 % endif
765 </%def>file changes
793 </%def>file changes
766
794
767
795
768 <%def name="render_add_comment_button()">
796 <%def name="render_add_comment_button()">
769 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)">
797 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)">
770 <span><i class="icon-comment"></i></span>
798 <span><i class="icon-comment"></i></span>
771 </button>
799 </button>
772 </%def>
800 </%def>
773
801
774 <%def name="render_diffset_menu(diffset, range_diff_on=None)">
802 <%def name="render_diffset_menu(diffset, range_diff_on=None)">
775 <% diffset_container_id = h.md5(diffset.target_ref) %>
803 <% diffset_container_id = h.md5(diffset.target_ref) %>
776
804
777 <div id="diff-file-sticky" class="diffset-menu clearinner">
805 <div id="diff-file-sticky" class="diffset-menu clearinner">
778 ## auto adjustable
806 ## auto adjustable
779 <div class="sidebar__inner">
807 <div class="sidebar__inner">
780 <div class="sidebar__bar">
808 <div class="sidebar__bar">
781 <div class="pull-right">
809 <div class="pull-right">
782 <div class="btn-group">
810 <div class="btn-group">
783 <a class="btn tooltip toggle-wide-diff" href="#toggle-wide-diff" onclick="toggleWideDiff(this); return false" title="${h.tooltip(_('Toggle wide diff'))}">
811 <a class="btn tooltip toggle-wide-diff" href="#toggle-wide-diff" onclick="toggleWideDiff(this); return false" title="${h.tooltip(_('Toggle wide diff'))}">
784 <i class="icon-wide-mode"></i>
812 <i class="icon-wide-mode"></i>
785 </a>
813 </a>
786 </div>
814 </div>
787 <div class="btn-group">
815 <div class="btn-group">
788
816
789 <a
817 <a
790 class="btn ${(c.user_session_attrs["diffmode"] == 'sideside' and 'btn-active')} tooltip"
818 class="btn ${(c.user_session_attrs["diffmode"] == 'sideside' and 'btn-active')} tooltip"
791 title="${h.tooltip(_('View diff as side by side'))}"
819 title="${h.tooltip(_('View diff as side by side'))}"
792 href="${h.current_route_path(request, diffmode='sideside')}">
820 href="${h.current_route_path(request, diffmode='sideside')}">
793 <span>${_('Side by Side')}</span>
821 <span>${_('Side by Side')}</span>
794 </a>
822 </a>
795
823
796 <a
824 <a
797 class="btn ${(c.user_session_attrs["diffmode"] == 'unified' and 'btn-active')} tooltip"
825 class="btn ${(c.user_session_attrs["diffmode"] == 'unified' and 'btn-active')} tooltip"
798 title="${h.tooltip(_('View diff as unified'))}" href="${h.current_route_path(request, diffmode='unified')}">
826 title="${h.tooltip(_('View diff as unified'))}" href="${h.current_route_path(request, diffmode='unified')}">
799 <span>${_('Unified')}</span>
827 <span>${_('Unified')}</span>
800 </a>
828 </a>
801
829
802 % if range_diff_on is True:
830 % if range_diff_on is True:
803 <a
831 <a
804 title="${_('Turn off: Show the diff as commit range')}"
832 title="${_('Turn off: Show the diff as commit range')}"
805 class="btn btn-primary"
833 class="btn btn-primary"
806 href="${h.current_route_path(request, **{"range-diff":"0"})}">
834 href="${h.current_route_path(request, **{"range-diff":"0"})}">
807 <span>${_('Range Diff')}</span>
835 <span>${_('Range Diff')}</span>
808 </a>
836 </a>
809 % elif range_diff_on is False:
837 % elif range_diff_on is False:
810 <a
838 <a
811 title="${_('Show the diff as commit range')}"
839 title="${_('Show the diff as commit range')}"
812 class="btn"
840 class="btn"
813 href="${h.current_route_path(request, **{"range-diff":"1"})}">
841 href="${h.current_route_path(request, **{"range-diff":"1"})}">
814 <span>${_('Range Diff')}</span>
842 <span>${_('Range Diff')}</span>
815 </a>
843 </a>
816 % endif
844 % endif
817 </div>
845 </div>
818 <div class="btn-group">
846 <div class="btn-group">
819
847
820 <div class="pull-left">
848 <div class="pull-left">
821 ${h.hidden('diff_menu_{}'.format(diffset_container_id))}
849 ${h.hidden('diff_menu_{}'.format(diffset_container_id))}
822 </div>
850 </div>
823
851
824 </div>
852 </div>
825 </div>
853 </div>
826 <div class="pull-left">
854 <div class="pull-left">
827 <div class="btn-group">
855 <div class="btn-group">
828 <div class="pull-left">
856 <div class="pull-left">
829 ${h.hidden('file_filter_{}'.format(diffset_container_id))}
857 ${h.hidden('file_filter_{}'.format(diffset_container_id))}
830 </div>
858 </div>
831
859
832 </div>
860 </div>
833 </div>
861 </div>
834 </div>
862 </div>
835 <div class="fpath-placeholder">
863 <div class="fpath-placeholder">
836 <i class="icon-file-text"></i>
864 <i class="icon-file-text"></i>
837 <strong class="fpath-placeholder-text">
865 <strong class="fpath-placeholder-text">
838 Context file:
866 Context file:
839 </strong>
867 </strong>
840 </div>
868 </div>
841 <div class="sidebar_inner_shadow"></div>
869 <div class="sidebar_inner_shadow"></div>
842 </div>
870 </div>
843 </div>
871 </div>
844
872
845 % if diffset:
873 % if diffset:
846 %if diffset.limited_diff:
874 %if diffset.limited_diff:
847 <% file_placeholder = _ungettext('%(num)s file changed', '%(num)s files changed', diffset.changed_files) % {'num': diffset.changed_files} %>
875 <% file_placeholder = _ungettext('%(num)s file changed', '%(num)s files changed', diffset.changed_files) % {'num': diffset.changed_files} %>
848 %else:
876 %else:
849 <% file_placeholder = h.literal(_ungettext('%(num)s file changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>', '%(num)s files changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>',
877 <% file_placeholder = h.literal(_ungettext('%(num)s file changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>', '%(num)s files changed: <span class="op-added">%(linesadd)s inserted</span>, <span class="op-deleted">%(linesdel)s deleted</span>',
850 diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}) %>
878 diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}) %>
851
879
852 %endif
880 %endif
853 ## case on range-diff placeholder needs to be updated
881 ## case on range-diff placeholder needs to be updated
854 % if range_diff_on is True:
882 % if range_diff_on is True:
855 <% file_placeholder = _('Disabled on range diff') %>
883 <% file_placeholder = _('Disabled on range diff') %>
856 % endif
884 % endif
857
885
858 <script type="text/javascript">
886 <script type="text/javascript">
859 var feedFilesOptions = function (query, initialData) {
887 var feedFilesOptions = function (query, initialData) {
860 var data = {results: []};
888 var data = {results: []};
861 var isQuery = typeof query.term !== 'undefined';
889 var isQuery = typeof query.term !== 'undefined';
862
890
863 var section = _gettext('Changed files');
891 var section = _gettext('Changed files');
864 var filteredData = [];
892 var filteredData = [];
865
893
866 //filter results
894 //filter results
867 $.each(initialData.results, function (idx, value) {
895 $.each(initialData.results, function (idx, value) {
868
896
869 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
897 if (!isQuery || query.term.length === 0 || value.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
870 filteredData.push({
898 filteredData.push({
871 'id': this.id,
899 'id': this.id,
872 'text': this.text,
900 'text': this.text,
873 "ops": this.ops,
901 "ops": this.ops,
874 })
902 })
875 }
903 }
876
904
877 });
905 });
878
906
879 data.results = filteredData;
907 data.results = filteredData;
880
908
881 query.callback(data);
909 query.callback(data);
882 };
910 };
883
911
884 var selectionFormatter = function(data, escapeMarkup) {
912 var selectionFormatter = function(data, escapeMarkup) {
885 var container = '<div class="filelist" style="padding-right:100px">{0}</div>';
913 var container = '<div class="filelist" style="padding-right:100px">{0}</div>';
886 var tmpl = '<div><strong>{0}</strong></div>'.format(escapeMarkup(data['text']));
914 var tmpl = '<div><strong>{0}</strong></div>'.format(escapeMarkup(data['text']));
887 var pill = '<div class="pill-group" style="position: absolute; top:7px; right: 0">' +
915 var pill = '<div class="pill-group" style="position: absolute; top:7px; right: 0">' +
888 '<span class="pill" op="added">{0}</span>' +
916 '<span class="pill" op="added">{0}</span>' +
889 '<span class="pill" op="deleted">{1}</span>' +
917 '<span class="pill" op="deleted">{1}</span>' +
890 '</div>'
918 '</div>'
891 ;
919 ;
892 var added = data['ops']['added'];
920 var added = data['ops']['added'];
893 if (added === 0) {
921 if (added === 0) {
894 // don't show +0
922 // don't show +0
895 added = 0;
923 added = 0;
896 } else {
924 } else {
897 added = '+' + added;
925 added = '+' + added;
898 }
926 }
899
927
900 var deleted = -1*data['ops']['deleted'];
928 var deleted = -1*data['ops']['deleted'];
901
929
902 tmpl += pill.format(added, deleted);
930 tmpl += pill.format(added, deleted);
903 return container.format(tmpl);
931 return container.format(tmpl);
904 };
932 };
905 var formatFileResult = function(result, container, query, escapeMarkup) {
933 var formatFileResult = function(result, container, query, escapeMarkup) {
906 return selectionFormatter(result, escapeMarkup);
934 return selectionFormatter(result, escapeMarkup);
907 };
935 };
908
936
909 var formatSelection = function (data, container) {
937 var formatSelection = function (data, container) {
910 return '${file_placeholder}'
938 return '${file_placeholder}'
911 };
939 };
912
940
913 if (window.preloadFileFilterData === undefined) {
941 if (window.preloadFileFilterData === undefined) {
914 window.preloadFileFilterData = {}
942 window.preloadFileFilterData = {}
915 }
943 }
916
944
917 preloadFileFilterData["${diffset_container_id}"] = {
945 preloadFileFilterData["${diffset_container_id}"] = {
918 results: [
946 results: [
919 % for filediff in diffset.files:
947 % for filediff in diffset.files:
920 {id:"a_${h.FID(filediff.raw_id, filediff.patch['filename'])}",
948 {id:"a_${h.FID(filediff.raw_id, filediff.patch['filename'])}",
921 text:"${filediff.patch['filename']}",
949 text:"${filediff.patch['filename']}",
922 ops:${h.json.dumps(filediff.patch['stats'])|n}}${('' if loop.last else ',')}
950 ops:${h.json.dumps(filediff.patch['stats'])|n}}${('' if loop.last else ',')}
923 % endfor
951 % endfor
924 ]
952 ]
925 };
953 };
926
954
927 var diffFileFilterId = "#file_filter_" + "${diffset_container_id}";
955 var diffFileFilterId = "#file_filter_" + "${diffset_container_id}";
928 var diffFileFilter = $(diffFileFilterId).select2({
956 var diffFileFilter = $(diffFileFilterId).select2({
929 'dropdownAutoWidth': true,
957 'dropdownAutoWidth': true,
930 'width': 'auto',
958 'width': 'auto',
931
959
932 containerCssClass: "drop-menu",
960 containerCssClass: "drop-menu",
933 dropdownCssClass: "drop-menu-dropdown",
961 dropdownCssClass: "drop-menu-dropdown",
934 data: preloadFileFilterData["${diffset_container_id}"],
962 data: preloadFileFilterData["${diffset_container_id}"],
935 query: function(query) {
963 query: function(query) {
936 feedFilesOptions(query, preloadFileFilterData["${diffset_container_id}"]);
964 feedFilesOptions(query, preloadFileFilterData["${diffset_container_id}"]);
937 },
965 },
938 initSelection: function(element, callback) {
966 initSelection: function(element, callback) {
939 callback({'init': true});
967 callback({'init': true});
940 },
968 },
941 formatResult: formatFileResult,
969 formatResult: formatFileResult,
942 formatSelection: formatSelection
970 formatSelection: formatSelection
943 });
971 });
944
972
945 % if range_diff_on is True:
973 % if range_diff_on is True:
946 diffFileFilter.select2("enable", false);
974 diffFileFilter.select2("enable", false);
947 % endif
975 % endif
948
976
949 $(diffFileFilterId).on('select2-selecting', function (e) {
977 $(diffFileFilterId).on('select2-selecting', function (e) {
950 var idSelector = e.choice.id;
978 var idSelector = e.choice.id;
951
979
952 // expand the container if we quick-select the field
980 // expand the container if we quick-select the field
953 $('#'+idSelector).next().prop('checked', false);
981 $('#'+idSelector).next().prop('checked', false);
954 // hide the mast as we later do preventDefault()
982 // hide the mast as we later do preventDefault()
955 $("#select2-drop-mask").click();
983 $("#select2-drop-mask").click();
956
984
957 window.location.hash = '#'+idSelector;
985 window.location.hash = '#'+idSelector;
958 updateSticky();
986 updateSticky();
959
987
960 e.preventDefault();
988 e.preventDefault();
961 });
989 });
962
990
963 </script>
991 </script>
964 % endif
992 % endif
965
993
966 <script type="text/javascript">
994 <script type="text/javascript">
967 $(document).ready(function () {
995 $(document).ready(function () {
968
996
969 var contextPrefix = _gettext('Context file: ');
997 var contextPrefix = _gettext('Context file: ');
970 ## sticky sidebar
998 ## sticky sidebar
971 var sidebarElement = document.getElementById('diff-file-sticky');
999 var sidebarElement = document.getElementById('diff-file-sticky');
972 sidebar = new StickySidebar(sidebarElement, {
1000 sidebar = new StickySidebar(sidebarElement, {
973 topSpacing: 0,
1001 topSpacing: 0,
974 bottomSpacing: 0,
1002 bottomSpacing: 0,
975 innerWrapperSelector: '.sidebar__inner'
1003 innerWrapperSelector: '.sidebar__inner'
976 });
1004 });
977 sidebarElement.addEventListener('affixed.static.stickySidebar', function () {
1005 sidebarElement.addEventListener('affixed.static.stickySidebar', function () {
978 // reset our file so it's not holding new value
1006 // reset our file so it's not holding new value
979 $('.fpath-placeholder-text').html(contextPrefix + ' - ')
1007 $('.fpath-placeholder-text').html(contextPrefix + ' - ')
980 });
1008 });
981
1009
982 updateSticky = function () {
1010 updateSticky = function () {
983 sidebar.updateSticky();
1011 sidebar.updateSticky();
984 Waypoint.refreshAll();
1012 Waypoint.refreshAll();
985 };
1013 };
986
1014
987 var animateText = function (fPath, anchorId) {
1015 var animateText = function (fPath, anchorId) {
988 fPath = Select2.util.escapeMarkup(fPath);
1016 fPath = Select2.util.escapeMarkup(fPath);
989 $('.fpath-placeholder-text').html(contextPrefix + '<a href="#a_' + anchorId + '">' + fPath + '</a>')
1017 $('.fpath-placeholder-text').html(contextPrefix + '<a href="#a_' + anchorId + '">' + fPath + '</a>')
990 };
1018 };
991
1019
992 ## dynamic file waypoints
1020 ## dynamic file waypoints
993 var setFPathInfo = function(fPath, anchorId){
1021 var setFPathInfo = function(fPath, anchorId){
994 animateText(fPath, anchorId)
1022 animateText(fPath, anchorId)
995 };
1023 };
996
1024
997 var codeBlock = $('.filediff');
1025 var codeBlock = $('.filediff');
998
1026
999 // forward waypoint
1027 // forward waypoint
1000 codeBlock.waypoint(
1028 codeBlock.waypoint(
1001 function(direction) {
1029 function(direction) {
1002 if (direction === "down"){
1030 if (direction === "down"){
1003 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1031 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1004 }
1032 }
1005 }, {
1033 }, {
1006 offset: function () {
1034 offset: function () {
1007 return 70;
1035 return 70;
1008 },
1036 },
1009 context: '.fpath-placeholder'
1037 context: '.fpath-placeholder'
1010 }
1038 }
1011 );
1039 );
1012
1040
1013 // backward waypoint
1041 // backward waypoint
1014 codeBlock.waypoint(
1042 codeBlock.waypoint(
1015 function(direction) {
1043 function(direction) {
1016 if (direction === "up"){
1044 if (direction === "up"){
1017 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1045 setFPathInfo($(this.element).data('fPath'), $(this.element).data('anchorId'))
1018 }
1046 }
1019 }, {
1047 }, {
1020 offset: function () {
1048 offset: function () {
1021 return -this.element.clientHeight + 90;
1049 return -this.element.clientHeight + 90;
1022 },
1050 },
1023 context: '.fpath-placeholder'
1051 context: '.fpath-placeholder'
1024 }
1052 }
1025 );
1053 );
1026
1054
1027 toggleWideDiff = function (el) {
1055 toggleWideDiff = function (el) {
1028 updateSticky();
1056 updateSticky();
1029 var wide = Rhodecode.comments.toggleWideMode(this);
1057 var wide = Rhodecode.comments.toggleWideMode(this);
1030 storeUserSessionAttr('rc_user_session_attr.wide_diff_mode', wide);
1058 storeUserSessionAttr('rc_user_session_attr.wide_diff_mode', wide);
1031 if (wide === true) {
1059 if (wide === true) {
1032 $(el).addClass('btn-active');
1060 $(el).addClass('btn-active');
1033 } else {
1061 } else {
1034 $(el).removeClass('btn-active');
1062 $(el).removeClass('btn-active');
1035 }
1063 }
1036 return null;
1064 return null;
1037 };
1065 };
1038
1066
1039 var preloadDiffMenuData = {
1067 var preloadDiffMenuData = {
1040 results: [
1068 results: [
1041
1069
1042 ## Whitespace change
1070 ## Whitespace change
1043 % if request.GET.get('ignorews', '') == '1':
1071 % if request.GET.get('ignorews', '') == '1':
1044 {
1072 {
1045 id: 2,
1073 id: 2,
1046 text: _gettext('Show whitespace changes'),
1074 text: _gettext('Show whitespace changes'),
1047 action: function () {},
1075 action: function () {},
1048 url: "${h.current_route_path(request, ignorews=0)|n}"
1076 url: "${h.current_route_path(request, ignorews=0)|n}"
1049 },
1077 },
1050 % else:
1078 % else:
1051 {
1079 {
1052 id: 2,
1080 id: 2,
1053 text: _gettext('Hide whitespace changes'),
1081 text: _gettext('Hide whitespace changes'),
1054 action: function () {},
1082 action: function () {},
1055 url: "${h.current_route_path(request, ignorews=1)|n}"
1083 url: "${h.current_route_path(request, ignorews=1)|n}"
1056 },
1084 },
1057 % endif
1085 % endif
1058
1086
1059 ## FULL CONTEXT
1087 ## FULL CONTEXT
1060 % if request.GET.get('fullcontext', '') == '1':
1088 % if request.GET.get('fullcontext', '') == '1':
1061 {
1089 {
1062 id: 3,
1090 id: 3,
1063 text: _gettext('Hide full context diff'),
1091 text: _gettext('Hide full context diff'),
1064 action: function () {},
1092 action: function () {},
1065 url: "${h.current_route_path(request, fullcontext=0)|n}"
1093 url: "${h.current_route_path(request, fullcontext=0)|n}"
1066 },
1094 },
1067 % else:
1095 % else:
1068 {
1096 {
1069 id: 3,
1097 id: 3,
1070 text: _gettext('Show full context diff'),
1098 text: _gettext('Show full context diff'),
1071 action: function () {},
1099 action: function () {},
1072 url: "${h.current_route_path(request, fullcontext=1)|n}"
1100 url: "${h.current_route_path(request, fullcontext=1)|n}"
1073 },
1101 },
1074 % endif
1102 % endif
1075
1103
1076 ]
1104 ]
1077 };
1105 };
1078
1106
1079 // get stored diff mode and pre-enable it
1107 // get stored diff mode and pre-enable it
1080 if (templateContext.session_attrs.wide_diff_mode === "true") {
1108 if (templateContext.session_attrs.wide_diff_mode === "true") {
1081 Rhodecode.comments.toggleWideMode(null);
1109 Rhodecode.comments.toggleWideMode(null);
1082 $('.toggle-wide-diff').addClass('btn-active');
1110 $('.toggle-wide-diff').addClass('btn-active');
1083 }
1111 }
1084
1112
1085 var diffMenuId = "#diff_menu_" + "${diffset_container_id}";
1113 var diffMenuId = "#diff_menu_" + "${diffset_container_id}";
1086 $(diffMenuId).select2({
1114 $(diffMenuId).select2({
1087 minimumResultsForSearch: -1,
1115 minimumResultsForSearch: -1,
1088 containerCssClass: "drop-menu-no-width",
1116 containerCssClass: "drop-menu-no-width",
1089 dropdownCssClass: "drop-menu-dropdown",
1117 dropdownCssClass: "drop-menu-dropdown",
1090 dropdownAutoWidth: true,
1118 dropdownAutoWidth: true,
1091 data: preloadDiffMenuData,
1119 data: preloadDiffMenuData,
1092 placeholder: "${_('...')}",
1120 placeholder: "${_('...')}",
1093 });
1121 });
1094 $(diffMenuId).on('select2-selecting', function (e) {
1122 $(diffMenuId).on('select2-selecting', function (e) {
1095 e.choice.action();
1123 e.choice.action();
1096 if (e.choice.url !== null) {
1124 if (e.choice.url !== null) {
1097 window.location = e.choice.url
1125 window.location = e.choice.url
1098 }
1126 }
1099 });
1127 });
1100 toggleExpand = function (el, diffsetEl) {
1128 toggleExpand = function (el, diffsetEl) {
1101 var el = $(el);
1129 var el = $(el);
1102 if (el.hasClass('collapsed')) {
1130 if (el.hasClass('collapsed')) {
1103 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', false);
1131 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', false);
1104 el.removeClass('collapsed');
1132 el.removeClass('collapsed');
1105 el.html(
1133 el.html(
1106 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1134 '<i class="icon-minus-squared-alt icon-no-margin"></i>' +
1107 _gettext('Collapse all files'));
1135 _gettext('Collapse all files'));
1108 }
1136 }
1109 else {
1137 else {
1110 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', true);
1138 $('.filediff-collapse-state.collapse-{0}'.format(diffsetEl)).prop('checked', true);
1111 el.addClass('collapsed');
1139 el.addClass('collapsed');
1112 el.html(
1140 el.html(
1113 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1141 '<i class="icon-plus-squared-alt icon-no-margin"></i>' +
1114 _gettext('Expand all files'));
1142 _gettext('Expand all files'));
1115 }
1143 }
1116 updateSticky()
1144 updateSticky()
1117 }
1145 }
1118 });
1146 });
1119 </script>
1147 </script>
1120
1148
1121 </%def>
1149 </%def>
@@ -1,841 +1,814 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
6 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
7 %if c.rhodecode_name:
7 %if c.rhodecode_name:
8 &middot; ${h.branding(c.rhodecode_name)}
8 &middot; ${h.branding(c.rhodecode_name)}
9 %endif
9 %endif
10 </%def>
10 </%def>
11
11
12 <%def name="breadcrumbs_links()">
12 <%def name="breadcrumbs_links()">
13 <span id="pr-title">
13 <span id="pr-title">
14 ${c.pull_request.title}
14 ${c.pull_request.title}
15 %if c.pull_request.is_closed():
15 %if c.pull_request.is_closed():
16 (${_('Closed')})
16 (${_('Closed')})
17 %endif
17 %endif
18 </span>
18 </span>
19 <div id="pr-title-edit" class="input" style="display: none;">
19 <div id="pr-title-edit" class="input" style="display: none;">
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
21 </div>
21 </div>
22 </%def>
22 </%def>
23
23
24 <%def name="menu_bar_nav()">
24 <%def name="menu_bar_nav()">
25 ${self.menu_items(active='repositories')}
25 ${self.menu_items(active='repositories')}
26 </%def>
26 </%def>
27
27
28 <%def name="menu_bar_subnav()">
28 <%def name="menu_bar_subnav()">
29 ${self.repo_menu(active='showpullrequest')}
29 ${self.repo_menu(active='showpullrequest')}
30 </%def>
30 </%def>
31
31
32 <%def name="main()">
32 <%def name="main()">
33
33
34 <script type="text/javascript">
34 <script type="text/javascript">
35 // TODO: marcink switch this to pyroutes
35 // TODO: marcink switch this to pyroutes
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 </script>
38 </script>
39 <div class="box">
39 <div class="box">
40
40
41 ${self.breadcrumbs()}
41 ${self.breadcrumbs()}
42
42
43 <div class="box pr-summary">
43 <div class="box pr-summary">
44
44
45 <div class="summary-details block-left">
45 <div class="summary-details block-left">
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
47 <div class="pr-details-title">
47 <div class="pr-details-title">
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 %if c.allowed_to_update:
49 %if c.allowed_to_update:
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
51 % if c.allowed_to_delete:
51 % if c.allowed_to_delete:
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
55 ${h.end_form()}
55 ${h.end_form()}
56 % else:
56 % else:
57 ${_('Delete')}
57 ${_('Delete')}
58 % endif
58 % endif
59 </div>
59 </div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
62 %endif
62 %endif
63 </div>
63 </div>
64
64
65 <div id="summary" class="fields pr-details-content">
65 <div id="summary" class="fields pr-details-content">
66 <div class="field">
66 <div class="field">
67 <div class="label-summary">
67 <div class="label-summary">
68 <label>${_('Source')}:</label>
68 <label>${_('Source')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 <div class="pr-origininfo">
71 <div class="pr-origininfo">
72 ## branch link is only valid if it is a branch
72 ## branch link is only valid if it is a branch
73 <span class="tag">
73 <span class="tag">
74 %if c.pull_request.source_ref_parts.type == 'branch':
74 %if c.pull_request.source_ref_parts.type == 'branch':
75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
76 %else:
76 %else:
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
78 %endif
78 %endif
79 </span>
79 </span>
80 <span class="clone-url">
80 <span class="clone-url">
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
82 </span>
82 </span>
83 <br/>
83 <br/>
84 % if c.ancestor_commit:
84 % if c.ancestor_commit:
85 ${_('Common ancestor')}:
85 ${_('Common ancestor')}:
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
87 % endif
87 % endif
88 </div>
88 </div>
89 %if h.is_hg(c.pull_request.source_repo):
89 %if h.is_hg(c.pull_request.source_repo):
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
91 %elif h.is_git(c.pull_request.source_repo):
91 %elif h.is_git(c.pull_request.source_repo):
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
93 %endif
93 %endif
94
94
95 <div class="">
95 <div class="">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
98 </div>
98 </div>
99
99
100 </div>
100 </div>
101 </div>
101 </div>
102 <div class="field">
102 <div class="field">
103 <div class="label-summary">
103 <div class="label-summary">
104 <label>${_('Target')}:</label>
104 <label>${_('Target')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 <div class="pr-targetinfo">
107 <div class="pr-targetinfo">
108 ## branch link is only valid if it is a branch
108 ## branch link is only valid if it is a branch
109 <span class="tag">
109 <span class="tag">
110 %if c.pull_request.target_ref_parts.type == 'branch':
110 %if c.pull_request.target_ref_parts.type == 'branch':
111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
112 %else:
112 %else:
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
114 %endif
114 %endif
115 </span>
115 </span>
116 <span class="clone-url">
116 <span class="clone-url">
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
118 </span>
118 </span>
119 </div>
119 </div>
120 </div>
120 </div>
121 </div>
121 </div>
122
122
123 ## Link to the shadow repository.
123 ## Link to the shadow repository.
124 <div class="field">
124 <div class="field">
125 <div class="label-summary">
125 <div class="label-summary">
126 <label>${_('Merge')}:</label>
126 <label>${_('Merge')}:</label>
127 </div>
127 </div>
128 <div class="input">
128 <div class="input">
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
130 %if h.is_hg(c.pull_request.target_repo):
130 %if h.is_hg(c.pull_request.target_repo):
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
132 %elif h.is_git(c.pull_request.target_repo):
132 %elif h.is_git(c.pull_request.target_repo):
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
134 %endif
134 %endif
135 <div class="">
135 <div class="">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
138 </div>
138 </div>
139 % else:
139 % else:
140 <div class="">
140 <div class="">
141 ${_('Shadow repository data not available')}.
141 ${_('Shadow repository data not available')}.
142 </div>
142 </div>
143 % endif
143 % endif
144 </div>
144 </div>
145 </div>
145 </div>
146
146
147 <div class="field">
147 <div class="field">
148 <div class="label-summary">
148 <div class="label-summary">
149 <label>${_('Review')}:</label>
149 <label>${_('Review')}:</label>
150 </div>
150 </div>
151 <div class="input">
151 <div class="input">
152 %if c.pull_request_review_status:
152 %if c.pull_request_review_status:
153 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
153 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
154 <span class="changeset-status-lbl tooltip">
154 <span class="changeset-status-lbl tooltip">
155 %if c.pull_request.is_closed():
155 %if c.pull_request.is_closed():
156 ${_('Closed')},
156 ${_('Closed')},
157 %endif
157 %endif
158 ${h.commit_status_lbl(c.pull_request_review_status)}
158 ${h.commit_status_lbl(c.pull_request_review_status)}
159 </span>
159 </span>
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
161 %endif
161 %endif
162 </div>
162 </div>
163 </div>
163 </div>
164 <div class="field">
164 <div class="field">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
166 <label>${_('Description')}:</label>
166 <label>${_('Description')}:</label>
167 </div>
167 </div>
168 <div id="pr-desc" class="input">
168 <div id="pr-desc" class="input">
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer)}</div>
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer)}</div>
170 </div>
170 </div>
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
174 </div>
174 </div>
175 </div>
175 </div>
176
176
177 <div class="field">
177 <div class="field">
178 <div class="label-summary">
178 <div class="label-summary">
179 <label>${_('Versions')}:</label>
179 <label>${_('Versions')}:</label>
180 </div>
180 </div>
181
181
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
184
184
185 <div class="pr-versions">
185 <div class="pr-versions">
186 % if c.show_version_changes:
186 % if c.show_version_changes:
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
191 data-toggle-off="${_('Hide all versions of this pull request')}">
191 data-toggle-off="${_('Hide all versions of this pull request')}">
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
193 </a>
193 </a>
194 <table>
194 <table>
195 ## SHOW ALL VERSIONS OF PR
195 ## SHOW ALL VERSIONS OF PR
196 <% ver_pr = None %>
196 <% ver_pr = None %>
197
197
198 % for data in reversed(list(enumerate(c.versions, 1))):
198 % for data in reversed(list(enumerate(c.versions, 1))):
199 <% ver_pos = data[0] %>
199 <% ver_pos = data[0] %>
200 <% ver = data[1] %>
200 <% ver = data[1] %>
201 <% ver_pr = ver.pull_request_version_id %>
201 <% ver_pr = ver.pull_request_version_id %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
203
203
204 <tr class="version-pr" style="display: ${display_row}">
204 <tr class="version-pr" style="display: ${display_row}">
205 <td>
205 <td>
206 <code>
206 <code>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
208 </code>
208 </code>
209 </td>
209 </td>
210 <td>
210 <td>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
213 </td>
213 </td>
214 <td>
214 <td>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
216 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
216 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
217 </div>
217 </div>
218 </td>
218 </td>
219 <td>
219 <td>
220 % if c.at_version_num != ver_pr:
220 % if c.at_version_num != ver_pr:
221 <i class="icon-comment"></i>
221 <i class="icon-comment"></i>
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
224 </code>
224 </code>
225 % endif
225 % endif
226 </td>
226 </td>
227 <td>
227 <td>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
229 </td>
229 </td>
230 <td>
230 <td>
231 ${h.age_component(ver.updated_on, time_is_local=True)}
231 ${h.age_component(ver.updated_on, time_is_local=True)}
232 </td>
232 </td>
233 </tr>
233 </tr>
234 % endfor
234 % endfor
235
235
236 <tr>
236 <tr>
237 <td colspan="6">
237 <td colspan="6">
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
239 data-label-text-locked="${_('select versions to show changes')}"
239 data-label-text-locked="${_('select versions to show changes')}"
240 data-label-text-diff="${_('show changes between versions')}"
240 data-label-text-diff="${_('show changes between versions')}"
241 data-label-text-show="${_('show pull request for this version')}"
241 data-label-text-show="${_('show pull request for this version')}"
242 >
242 >
243 ${_('select versions to show changes')}
243 ${_('select versions to show changes')}
244 </button>
244 </button>
245 </td>
245 </td>
246 </tr>
246 </tr>
247
248 ## show comment/inline comments summary
249 <%def name="comments_summary()">
250 <tr>
251 <td colspan="6" class="comments-summary-td">
252
253 % if c.at_version:
254 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
255 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
256 ${_('Comments at this version')}:
257 % else:
258 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
259 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
260 ${_('Comments for this pull request')}:
261 % endif
262
263
264 %if general_comm_count_ver:
265 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
266 %else:
267 ${_("%d General ") % general_comm_count_ver}
268 %endif
269
270 %if inline_comm_count_ver:
271 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
272 %else:
273 , ${_("%d Inline") % inline_comm_count_ver}
274 %endif
275
276 %if outdated_comm_count_ver:
277 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
278 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
279 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
280 %else:
281 , ${_("%d Outdated") % outdated_comm_count_ver}
282 %endif
283 </td>
284 </tr>
285 </%def>
286 ${comments_summary()}
287 </table>
247 </table>
288 % else:
248 % else:
289 <div class="input">
249 <div class="input">
290 ${_('Pull request versions not available')}.
250 ${_('Pull request versions not available')}.
291 </div>
251 </div>
292 <div>
293 <table>
294 ${comments_summary()}
295 </table>
296 </div>
297 % endif
252 % endif
298 </div>
253 </div>
299 </div>
254 </div>
300
255
301 <div id="pr-save" class="field" style="display: none;">
256 <div id="pr-save" class="field" style="display: none;">
302 <div class="label-summary"></div>
257 <div class="label-summary"></div>
303 <div class="input">
258 <div class="input">
304 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
259 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
305 </div>
260 </div>
306 </div>
261 </div>
307 </div>
262 </div>
308 </div>
263 </div>
309 <div>
264 <div>
310 ## AUTHOR
265 ## AUTHOR
311 <div class="reviewers-title block-right">
266 <div class="reviewers-title block-right">
312 <div class="pr-details-title">
267 <div class="pr-details-title">
313 ${_('Author of this pull request')}
268 ${_('Author of this pull request')}
314 </div>
269 </div>
315 </div>
270 </div>
316 <div class="block-right pr-details-content reviewers">
271 <div class="block-right pr-details-content reviewers">
317 <ul class="group_members">
272 <ul class="group_members">
318 <li>
273 <li>
319 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
274 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
320 </li>
275 </li>
321 </ul>
276 </ul>
322 </div>
277 </div>
323
278
324 ## REVIEW RULES
279 ## REVIEW RULES
325 <div id="review_rules" style="display: none" class="reviewers-title block-right">
280 <div id="review_rules" style="display: none" class="reviewers-title block-right">
326 <div class="pr-details-title">
281 <div class="pr-details-title">
327 ${_('Reviewer rules')}
282 ${_('Reviewer rules')}
328 %if c.allowed_to_update:
283 %if c.allowed_to_update:
329 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
284 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
330 %endif
285 %endif
331 </div>
286 </div>
332 <div class="pr-reviewer-rules">
287 <div class="pr-reviewer-rules">
333 ## review rules will be appended here, by default reviewers logic
288 ## review rules will be appended here, by default reviewers logic
334 </div>
289 </div>
335 <input id="review_data" type="hidden" name="review_data" value="">
290 <input id="review_data" type="hidden" name="review_data" value="">
336 </div>
291 </div>
337
292
338 ## REVIEWERS
293 ## REVIEWERS
339 <div class="reviewers-title block-right">
294 <div class="reviewers-title block-right">
340 <div class="pr-details-title">
295 <div class="pr-details-title">
341 ${_('Pull request reviewers')}
296 ${_('Pull request reviewers')}
342 %if c.allowed_to_update:
297 %if c.allowed_to_update:
343 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
298 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
344 %endif
299 %endif
345 </div>
300 </div>
346 </div>
301 </div>
347 <div id="reviewers" class="block-right pr-details-content reviewers">
302 <div id="reviewers" class="block-right pr-details-content reviewers">
348
303
349 ## members redering block
304 ## members redering block
350 <input type="hidden" name="__start__" value="review_members:sequence">
305 <input type="hidden" name="__start__" value="review_members:sequence">
351 <ul id="review_members" class="group_members">
306 <ul id="review_members" class="group_members">
352
307
353 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
308 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
354 <script>
309 <script>
355 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
310 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
356 var status = "${(status[0][1].status if status else 'not_reviewed')}";
311 var status = "${(status[0][1].status if status else 'not_reviewed')}";
357 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
312 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
358 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
313 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
359
314
360 var entry = renderTemplate('reviewMemberEntry', {
315 var entry = renderTemplate('reviewMemberEntry', {
361 'member': member,
316 'member': member,
362 'mandatory': member.mandatory,
317 'mandatory': member.mandatory,
363 'reasons': member.reasons,
318 'reasons': member.reasons,
364 'allowed_to_update': allowed_to_update,
319 'allowed_to_update': allowed_to_update,
365 'review_status': status,
320 'review_status': status,
366 'review_status_label': status_lbl,
321 'review_status_label': status_lbl,
367 'user_group': member.user_group,
322 'user_group': member.user_group,
368 'create': false
323 'create': false
369 });
324 });
370 $('#review_members').append(entry)
325 $('#review_members').append(entry)
371 </script>
326 </script>
372
327
373 % endfor
328 % endfor
374
329
375 </ul>
330 </ul>
376 <input type="hidden" name="__end__" value="review_members:sequence">
331 <input type="hidden" name="__end__" value="review_members:sequence">
377 ## end members redering block
332 ## end members redering block
378
333
379 %if not c.pull_request.is_closed():
334 %if not c.pull_request.is_closed():
380 <div id="add_reviewer" class="ac" style="display: none;">
335 <div id="add_reviewer" class="ac" style="display: none;">
381 %if c.allowed_to_update:
336 %if c.allowed_to_update:
382 % if not c.forbid_adding_reviewers:
337 % if not c.forbid_adding_reviewers:
383 <div id="add_reviewer_input" class="reviewer_ac">
338 <div id="add_reviewer_input" class="reviewer_ac">
384 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
339 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
385 <div id="reviewers_container"></div>
340 <div id="reviewers_container"></div>
386 </div>
341 </div>
387 % endif
342 % endif
388 <div class="pull-right">
343 <div class="pull-right">
389 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
344 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
390 </div>
345 </div>
391 %endif
346 %endif
392 </div>
347 </div>
393 %endif
348 %endif
394 </div>
349 </div>
395 </div>
350 </div>
396 </div>
351 </div>
397 <div class="box">
352 <div class="box">
398 ##DIFF
353 ##DIFF
399 <div class="table" >
354 <div class="table" >
400 <div id="changeset_compare_view_content">
355 <div id="changeset_compare_view_content">
401 ##CS
356 ##CS
402 % if c.missing_requirements:
357 % if c.missing_requirements:
403 <div class="box">
358 <div class="box">
404 <div class="alert alert-warning">
359 <div class="alert alert-warning">
405 <div>
360 <div>
406 <strong>${_('Missing requirements:')}</strong>
361 <strong>${_('Missing requirements:')}</strong>
407 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
362 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
408 </div>
363 </div>
409 </div>
364 </div>
410 </div>
365 </div>
411 % elif c.missing_commits:
366 % elif c.missing_commits:
412 <div class="box">
367 <div class="box">
413 <div class="alert alert-warning">
368 <div class="alert alert-warning">
414 <div>
369 <div>
415 <strong>${_('Missing commits')}:</strong>
370 <strong>${_('Missing commits')}:</strong>
416 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
371 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
417 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
372 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
418 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
373 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
419 </div>
374 </div>
420 </div>
375 </div>
421 </div>
376 </div>
422 % endif
377 % endif
423
378
424 <div class="compare_view_commits_title">
379 <div class="compare_view_commits_title">
425 % if not c.compare_mode:
380 % if not c.compare_mode:
426
381
427 % if c.at_version_pos:
382 % if c.at_version_pos:
428 <h4>
383 <h4>
429 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
384 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
430 </h4>
385 </h4>
431 % endif
386 % endif
432
387
433 <div class="pull-left">
388 <div class="pull-left">
434 <div class="btn-group">
389 <div class="btn-group">
435 <a
390 <a
436 class="btn"
391 class="btn"
437 href="#"
392 href="#"
438 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
393 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
439 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
394 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
440 </a>
395 </a>
441 <a
396 <a
442 class="btn"
397 class="btn"
443 href="#"
398 href="#"
444 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
399 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
445 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
400 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
446 </a>
401 </a>
447 </div>
402 </div>
448 </div>
403 </div>
449
404
450 <div class="pull-right">
405 <div class="pull-right">
451 % if c.allowed_to_update and not c.pull_request.is_closed():
406 % if c.allowed_to_update and not c.pull_request.is_closed():
452 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
407 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
453 % else:
408 % else:
454 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
409 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
455 % endif
410 % endif
456
411
457 </div>
412 </div>
458 % endif
413 % endif
459 </div>
414 </div>
460
415
461 % if not c.missing_commits:
416 % if not c.missing_commits:
462 % if c.compare_mode:
417 % if c.compare_mode:
463 % if c.at_version:
418 % if c.at_version:
464 <h4>
419 <h4>
465 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
420 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
466 </h4>
421 </h4>
467
422
468 <div class="subtitle-compare">
423 <div class="subtitle-compare">
469 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
424 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
470 </div>
425 </div>
471
426
472 <div class="container">
427 <div class="container">
473 <table class="rctable compare_view_commits">
428 <table class="rctable compare_view_commits">
474 <tr>
429 <tr>
475 <th></th>
430 <th></th>
476 <th>${_('Time')}</th>
431 <th>${_('Time')}</th>
477 <th>${_('Author')}</th>
432 <th>${_('Author')}</th>
478 <th>${_('Commit')}</th>
433 <th>${_('Commit')}</th>
479 <th></th>
434 <th></th>
480 <th>${_('Description')}</th>
435 <th>${_('Description')}</th>
481 </tr>
436 </tr>
482
437
483 % for c_type, commit in c.commit_changes:
438 % for c_type, commit in c.commit_changes:
484 % if c_type in ['a', 'r']:
439 % if c_type in ['a', 'r']:
485 <%
440 <%
486 if c_type == 'a':
441 if c_type == 'a':
487 cc_title = _('Commit added in displayed changes')
442 cc_title = _('Commit added in displayed changes')
488 elif c_type == 'r':
443 elif c_type == 'r':
489 cc_title = _('Commit removed in displayed changes')
444 cc_title = _('Commit removed in displayed changes')
490 else:
445 else:
491 cc_title = ''
446 cc_title = ''
492 %>
447 %>
493 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
448 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
494 <td>
449 <td>
495 <div class="commit-change-indicator color-${c_type}-border">
450 <div class="commit-change-indicator color-${c_type}-border">
496 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
451 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
497 ${c_type.upper()}
452 ${c_type.upper()}
498 </div>
453 </div>
499 </div>
454 </div>
500 </td>
455 </td>
501 <td class="td-time">
456 <td class="td-time">
502 ${h.age_component(commit.date)}
457 ${h.age_component(commit.date)}
503 </td>
458 </td>
504 <td class="td-user">
459 <td class="td-user">
505 ${base.gravatar_with_user(commit.author, 16)}
460 ${base.gravatar_with_user(commit.author, 16)}
506 </td>
461 </td>
507 <td class="td-hash">
462 <td class="td-hash">
508 <code>
463 <code>
509 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
464 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
510 r${commit.idx}:${h.short_id(commit.raw_id)}
465 r${commit.idx}:${h.short_id(commit.raw_id)}
511 </a>
466 </a>
512 ${h.hidden('revisions', commit.raw_id)}
467 ${h.hidden('revisions', commit.raw_id)}
513 </code>
468 </code>
514 </td>
469 </td>
515 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
470 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
516 <i class="icon-expand-linked"></i>
471 <i class="icon-expand-linked"></i>
517 </td>
472 </td>
518 <td class="mid td-description">
473 <td class="mid td-description">
519 <div class="log-container truncate-wrap">
474 <div class="log-container truncate-wrap">
520 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
475 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
521 </div>
476 </div>
522 </td>
477 </td>
523 </tr>
478 </tr>
524 % endif
479 % endif
525 % endfor
480 % endfor
526 </table>
481 </table>
527 </div>
482 </div>
528
483
529 % endif
484 % endif
530
485
531 % else:
486 % else:
532 <%include file="/compare/compare_commits.mako" />
487 <%include file="/compare/compare_commits.mako" />
533 % endif
488 % endif
534
489
535 <div class="cs_files">
490 <div class="cs_files">
536 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
491 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
492 % if c.at_version:
493 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
494 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
495 % else:
496 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
497 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
498 % endif
499
500 <%
501 pr_menu_data = {
502 'outdated_comm_count_ver': outdated_comm_count_ver
503 }
504 %>
537
505
538 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
506 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
539
507
540 % if c.range_diff_on:
508 % if c.range_diff_on:
541 % for commit in c.commit_ranges:
509 % for commit in c.commit_ranges:
542 ${cbdiffs.render_diffset(
510 ${cbdiffs.render_diffset(
543 c.changes[commit.raw_id],
511 c.changes[commit.raw_id],
544 commit=commit, use_comments=True,
512 commit=commit, use_comments=True,
545 collapse_when_files_over=5,
513 collapse_when_files_over=5,
546 disable_new_comments=True,
514 disable_new_comments=True,
547 deleted_files_comments=c.deleted_files_comments,
515 deleted_files_comments=c.deleted_files_comments,
548 inline_comments=c.inline_comments)}
516 inline_comments=c.inline_comments,
517 pull_request_menu=pr_menu_data)}
549 % endfor
518 % endfor
550 % else:
519 % else:
551 ${cbdiffs.render_diffset(
520 ${cbdiffs.render_diffset(
552 c.diffset, use_comments=True,
521 c.diffset, use_comments=True,
553 collapse_when_files_over=30,
522 collapse_when_files_over=30,
554 disable_new_comments=not c.allowed_to_comment,
523 disable_new_comments=not c.allowed_to_comment,
555 deleted_files_comments=c.deleted_files_comments,
524 deleted_files_comments=c.deleted_files_comments,
556 inline_comments=c.inline_comments)}
525 inline_comments=c.inline_comments,
526 pull_request_menu=pr_menu_data)}
557 % endif
527 % endif
558
528
559 </div>
529 </div>
560 % else:
530 % else:
561 ## skipping commits we need to clear the view for missing commits
531 ## skipping commits we need to clear the view for missing commits
562 <div style="clear:both;"></div>
532 <div style="clear:both;"></div>
563 % endif
533 % endif
564
534
565 </div>
535 </div>
566 </div>
536 </div>
567
537
568 ## template for inline comment form
538 ## template for inline comment form
569 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
539 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
570
540
541 ## comments heading with count
542 <div class="comments-heading">
543 <i class="icon-comment"></i>
544 ${_('Comments')} ${len(c.comments)}
545 </div>
546
571 ## render general comments
547 ## render general comments
572
573 <div id="comment-tr-show">
548 <div id="comment-tr-show">
574 <div class="comment">
549 % if general_outdated_comm_count_ver:
575 % if general_outdated_comm_count_ver:
550 <div class="info-box">
576 <div class="meta">
551 % if general_outdated_comm_count_ver == 1:
577 % if general_outdated_comm_count_ver == 1:
552 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
578 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
553 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
579 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
554 % else:
580 % else:
555 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
581 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
556 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
582 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
583 % endif
584 </div>
585 % endif
557 % endif
586 </div>
558 </div>
559 % endif
587 </div>
560 </div>
588
561
589 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
562 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
590
563
591 % if not c.pull_request.is_closed():
564 % if not c.pull_request.is_closed():
592 ## merge status, and merge action
565 ## merge status, and merge action
593 <div class="pull-request-merge">
566 <div class="pull-request-merge">
594 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
567 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
595 </div>
568 </div>
596
569
597 ## main comment form and it status
570 ## main comment form and it status
598 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
571 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
599 pull_request_id=c.pull_request.pull_request_id),
572 pull_request_id=c.pull_request.pull_request_id),
600 c.pull_request_review_status,
573 c.pull_request_review_status,
601 is_pull_request=True, change_status=c.allowed_to_change_status)}
574 is_pull_request=True, change_status=c.allowed_to_change_status)}
602 %endif
575 %endif
603
576
604 <script type="text/javascript">
577 <script type="text/javascript">
605 if (location.hash) {
578 if (location.hash) {
606 var result = splitDelimitedHash(location.hash);
579 var result = splitDelimitedHash(location.hash);
607 var line = $('html').find(result.loc);
580 var line = $('html').find(result.loc);
608 // show hidden comments if we use location.hash
581 // show hidden comments if we use location.hash
609 if (line.hasClass('comment-general')) {
582 if (line.hasClass('comment-general')) {
610 $(line).show();
583 $(line).show();
611 } else if (line.hasClass('comment-inline')) {
584 } else if (line.hasClass('comment-inline')) {
612 $(line).show();
585 $(line).show();
613 var $cb = $(line).closest('.cb');
586 var $cb = $(line).closest('.cb');
614 $cb.removeClass('cb-collapsed')
587 $cb.removeClass('cb-collapsed')
615 }
588 }
616 if (line.length > 0){
589 if (line.length > 0){
617 offsetScroll(line, 70);
590 offsetScroll(line, 70);
618 }
591 }
619 }
592 }
620
593
621 versionController = new VersionController();
594 versionController = new VersionController();
622 versionController.init();
595 versionController.init();
623
596
624 reviewersController = new ReviewersController();
597 reviewersController = new ReviewersController();
625 commitsController = new CommitsController();
598 commitsController = new CommitsController();
626
599
627 $(function(){
600 $(function(){
628
601
629 // custom code mirror
602 // custom code mirror
630 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
603 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
631
604
632 var PRDetails = {
605 var PRDetails = {
633 editButton: $('#open_edit_pullrequest'),
606 editButton: $('#open_edit_pullrequest'),
634 closeButton: $('#close_edit_pullrequest'),
607 closeButton: $('#close_edit_pullrequest'),
635 deleteButton: $('#delete_pullrequest'),
608 deleteButton: $('#delete_pullrequest'),
636 viewFields: $('#pr-desc, #pr-title'),
609 viewFields: $('#pr-desc, #pr-title'),
637 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
610 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
638
611
639 init: function() {
612 init: function() {
640 var that = this;
613 var that = this;
641 this.editButton.on('click', function(e) { that.edit(); });
614 this.editButton.on('click', function(e) { that.edit(); });
642 this.closeButton.on('click', function(e) { that.view(); });
615 this.closeButton.on('click', function(e) { that.view(); });
643 },
616 },
644
617
645 edit: function(event) {
618 edit: function(event) {
646 this.viewFields.hide();
619 this.viewFields.hide();
647 this.editButton.hide();
620 this.editButton.hide();
648 this.deleteButton.hide();
621 this.deleteButton.hide();
649 this.closeButton.show();
622 this.closeButton.show();
650 this.editFields.show();
623 this.editFields.show();
651 codeMirrorInstance.refresh();
624 codeMirrorInstance.refresh();
652 },
625 },
653
626
654 view: function(event) {
627 view: function(event) {
655 this.editButton.show();
628 this.editButton.show();
656 this.deleteButton.show();
629 this.deleteButton.show();
657 this.editFields.hide();
630 this.editFields.hide();
658 this.closeButton.hide();
631 this.closeButton.hide();
659 this.viewFields.show();
632 this.viewFields.show();
660 }
633 }
661 };
634 };
662
635
663 var ReviewersPanel = {
636 var ReviewersPanel = {
664 editButton: $('#open_edit_reviewers'),
637 editButton: $('#open_edit_reviewers'),
665 closeButton: $('#close_edit_reviewers'),
638 closeButton: $('#close_edit_reviewers'),
666 addButton: $('#add_reviewer'),
639 addButton: $('#add_reviewer'),
667 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
640 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
668
641
669 init: function() {
642 init: function() {
670 var self = this;
643 var self = this;
671 this.editButton.on('click', function(e) { self.edit(); });
644 this.editButton.on('click', function(e) { self.edit(); });
672 this.closeButton.on('click', function(e) { self.close(); });
645 this.closeButton.on('click', function(e) { self.close(); });
673 },
646 },
674
647
675 edit: function(event) {
648 edit: function(event) {
676 this.editButton.hide();
649 this.editButton.hide();
677 this.closeButton.show();
650 this.closeButton.show();
678 this.addButton.show();
651 this.addButton.show();
679 this.removeButtons.css('visibility', 'visible');
652 this.removeButtons.css('visibility', 'visible');
680 // review rules
653 // review rules
681 reviewersController.loadReviewRules(
654 reviewersController.loadReviewRules(
682 ${c.pull_request.reviewer_data_json | n});
655 ${c.pull_request.reviewer_data_json | n});
683 },
656 },
684
657
685 close: function(event) {
658 close: function(event) {
686 this.editButton.show();
659 this.editButton.show();
687 this.closeButton.hide();
660 this.closeButton.hide();
688 this.addButton.hide();
661 this.addButton.hide();
689 this.removeButtons.css('visibility', 'hidden');
662 this.removeButtons.css('visibility', 'hidden');
690 // hide review rules
663 // hide review rules
691 reviewersController.hideReviewRules()
664 reviewersController.hideReviewRules()
692 }
665 }
693 };
666 };
694
667
695 PRDetails.init();
668 PRDetails.init();
696 ReviewersPanel.init();
669 ReviewersPanel.init();
697
670
698 showOutdated = function(self){
671 showOutdated = function(self){
699 $('.comment-inline.comment-outdated').show();
672 $('.comment-inline.comment-outdated').show();
700 $('.filediff-outdated').show();
673 $('.filediff-outdated').show();
701 $('.showOutdatedComments').hide();
674 $('.showOutdatedComments').hide();
702 $('.hideOutdatedComments').show();
675 $('.hideOutdatedComments').show();
703 };
676 };
704
677
705 hideOutdated = function(self){
678 hideOutdated = function(self){
706 $('.comment-inline.comment-outdated').hide();
679 $('.comment-inline.comment-outdated').hide();
707 $('.filediff-outdated').hide();
680 $('.filediff-outdated').hide();
708 $('.hideOutdatedComments').hide();
681 $('.hideOutdatedComments').hide();
709 $('.showOutdatedComments').show();
682 $('.showOutdatedComments').show();
710 };
683 };
711
684
712 refreshMergeChecks = function(){
685 refreshMergeChecks = function(){
713 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
686 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
714 $('.pull-request-merge').css('opacity', 0.3);
687 $('.pull-request-merge').css('opacity', 0.3);
715 $('.action-buttons-extra').css('opacity', 0.3);
688 $('.action-buttons-extra').css('opacity', 0.3);
716
689
717 $('.pull-request-merge').load(
690 $('.pull-request-merge').load(
718 loadUrl, function() {
691 loadUrl, function() {
719 $('.pull-request-merge').css('opacity', 1);
692 $('.pull-request-merge').css('opacity', 1);
720
693
721 $('.action-buttons-extra').css('opacity', 1);
694 $('.action-buttons-extra').css('opacity', 1);
722 injectCloseAction();
695 injectCloseAction();
723 }
696 }
724 );
697 );
725 };
698 };
726
699
727 injectCloseAction = function() {
700 injectCloseAction = function() {
728 var closeAction = $('#close-pull-request-action').html();
701 var closeAction = $('#close-pull-request-action').html();
729 var $actionButtons = $('.action-buttons-extra');
702 var $actionButtons = $('.action-buttons-extra');
730 // clear the action before
703 // clear the action before
731 $actionButtons.html("");
704 $actionButtons.html("");
732 $actionButtons.html(closeAction);
705 $actionButtons.html(closeAction);
733 };
706 };
734
707
735 closePullRequest = function (status) {
708 closePullRequest = function (status) {
736 // inject closing flag
709 // inject closing flag
737 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
710 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
738 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
711 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
739 $(generalCommentForm.submitForm).submit();
712 $(generalCommentForm.submitForm).submit();
740 };
713 };
741
714
742 $('#show-outdated-comments').on('click', function(e){
715 $('#show-outdated-comments').on('click', function(e){
743 var button = $(this);
716 var button = $(this);
744 var outdated = $('.comment-outdated');
717 var outdated = $('.comment-outdated');
745
718
746 if (button.html() === "(Show)") {
719 if (button.html() === "(Show)") {
747 button.html("(Hide)");
720 button.html("(Hide)");
748 outdated.show();
721 outdated.show();
749 } else {
722 } else {
750 button.html("(Show)");
723 button.html("(Show)");
751 outdated.hide();
724 outdated.hide();
752 }
725 }
753 });
726 });
754
727
755 $('.show-inline-comments').on('change', function(e){
728 $('.show-inline-comments').on('change', function(e){
756 var show = 'none';
729 var show = 'none';
757 var target = e.currentTarget;
730 var target = e.currentTarget;
758 if(target.checked){
731 if(target.checked){
759 show = ''
732 show = ''
760 }
733 }
761 var boxid = $(target).attr('id_for');
734 var boxid = $(target).attr('id_for');
762 var comments = $('#{0} .inline-comments'.format(boxid));
735 var comments = $('#{0} .inline-comments'.format(boxid));
763 var fn_display = function(idx){
736 var fn_display = function(idx){
764 $(this).css('display', show);
737 $(this).css('display', show);
765 };
738 };
766 $(comments).each(fn_display);
739 $(comments).each(fn_display);
767 var btns = $('#{0} .inline-comments-button'.format(boxid));
740 var btns = $('#{0} .inline-comments-button'.format(boxid));
768 $(btns).each(fn_display);
741 $(btns).each(fn_display);
769 });
742 });
770
743
771 $('#merge_pull_request_form').submit(function() {
744 $('#merge_pull_request_form').submit(function() {
772 if (!$('#merge_pull_request').attr('disabled')) {
745 if (!$('#merge_pull_request').attr('disabled')) {
773 $('#merge_pull_request').attr('disabled', 'disabled');
746 $('#merge_pull_request').attr('disabled', 'disabled');
774 }
747 }
775 return true;
748 return true;
776 });
749 });
777
750
778 $('#edit_pull_request').on('click', function(e){
751 $('#edit_pull_request').on('click', function(e){
779 var title = $('#pr-title-input').val();
752 var title = $('#pr-title-input').val();
780 var description = codeMirrorInstance.getValue();
753 var description = codeMirrorInstance.getValue();
781 var renderer = $('#pr-renderer-input').val();
754 var renderer = $('#pr-renderer-input').val();
782 editPullRequest(
755 editPullRequest(
783 "${c.repo_name}", "${c.pull_request.pull_request_id}",
756 "${c.repo_name}", "${c.pull_request.pull_request_id}",
784 title, description, renderer);
757 title, description, renderer);
785 });
758 });
786
759
787 $('#update_pull_request').on('click', function(e){
760 $('#update_pull_request').on('click', function(e){
788 $(this).attr('disabled', 'disabled');
761 $(this).attr('disabled', 'disabled');
789 $(this).addClass('disabled');
762 $(this).addClass('disabled');
790 $(this).html(_gettext('Saving...'));
763 $(this).html(_gettext('Saving...'));
791 reviewersController.updateReviewers(
764 reviewersController.updateReviewers(
792 "${c.repo_name}", "${c.pull_request.pull_request_id}");
765 "${c.repo_name}", "${c.pull_request.pull_request_id}");
793 });
766 });
794
767
795 $('#update_commits').on('click', function(e){
768 $('#update_commits').on('click', function(e){
796 var isDisabled = !$(e.currentTarget).attr('disabled');
769 var isDisabled = !$(e.currentTarget).attr('disabled');
797 $(e.currentTarget).attr('disabled', 'disabled');
770 $(e.currentTarget).attr('disabled', 'disabled');
798 $(e.currentTarget).addClass('disabled');
771 $(e.currentTarget).addClass('disabled');
799 $(e.currentTarget).removeClass('btn-primary');
772 $(e.currentTarget).removeClass('btn-primary');
800 $(e.currentTarget).text(_gettext('Updating...'));
773 $(e.currentTarget).text(_gettext('Updating...'));
801 if(isDisabled){
774 if(isDisabled){
802 updateCommits(
775 updateCommits(
803 "${c.repo_name}", "${c.pull_request.pull_request_id}");
776 "${c.repo_name}", "${c.pull_request.pull_request_id}");
804 }
777 }
805 });
778 });
806 // fixing issue with caches on firefox
779 // fixing issue with caches on firefox
807 $('#update_commits').removeAttr("disabled");
780 $('#update_commits').removeAttr("disabled");
808
781
809 $('.show-inline-comments').on('click', function(e){
782 $('.show-inline-comments').on('click', function(e){
810 var boxid = $(this).attr('data-comment-id');
783 var boxid = $(this).attr('data-comment-id');
811 var button = $(this);
784 var button = $(this);
812
785
813 if(button.hasClass("comments-visible")) {
786 if(button.hasClass("comments-visible")) {
814 $('#{0} .inline-comments'.format(boxid)).each(function(index){
787 $('#{0} .inline-comments'.format(boxid)).each(function(index){
815 $(this).hide();
788 $(this).hide();
816 });
789 });
817 button.removeClass("comments-visible");
790 button.removeClass("comments-visible");
818 } else {
791 } else {
819 $('#{0} .inline-comments'.format(boxid)).each(function(index){
792 $('#{0} .inline-comments'.format(boxid)).each(function(index){
820 $(this).show();
793 $(this).show();
821 });
794 });
822 button.addClass("comments-visible");
795 button.addClass("comments-visible");
823 }
796 }
824 });
797 });
825
798
826 // register submit callback on commentForm form to track TODOs
799 // register submit callback on commentForm form to track TODOs
827 window.commentFormGlobalSubmitSuccessCallback = function(){
800 window.commentFormGlobalSubmitSuccessCallback = function(){
828 refreshMergeChecks();
801 refreshMergeChecks();
829 };
802 };
830 // initial injection
803 // initial injection
831 injectCloseAction();
804 injectCloseAction();
832
805
833 ReviewerAutoComplete('#user');
806 ReviewerAutoComplete('#user');
834
807
835 })
808 })
836 </script>
809 </script>
837
810
838 </div>
811 </div>
839 </div>
812 </div>
840
813
841 </%def>
814 </%def>
General Comments 0
You need to be logged in to leave comments. Login now