##// END OF EJS Templates
routing: removed more usage of pylons url() objects.
marcink -
r2103:c8a23404 default
parent child Browse files
Show More
@@ -1,58 +1,59 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import logging
23 23
24 24 from pyramid.view import view_config
25 25 from pyramid.renderers import render_to_response
26 26 from rhodecode.apps._base import BaseAppView
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 class DebugStyleView(BaseAppView):
32 32 def load_default_context(self):
33 33 c = self._get_local_tmpl_context()
34 34 self._register_global_c(c)
35 35 return c
36 36
37 37 @view_config(
38 38 route_name='debug_style_home', request_method='GET',
39 39 renderer=None)
40 40 def index(self):
41 41 c = self.load_default_context()
42 42 c.active = 'index'
43 43
44 44 return render_to_response(
45 45 'debug_style/index.html', self._get_template_context(c),
46 46 request=self.request)
47 47
48 48 @view_config(
49 49 route_name='debug_style_template', request_method='GET',
50 50 renderer=None)
51 51 def template(self):
52 52 t_path = self.request.matchdict['t_path']
53 53 c = self.load_default_context()
54 54 c.active = os.path.splitext(t_path)[0]
55 c.came_from = ''
55 56
56 57 return render_to_response(
57 58 'debug_style/' + t_path, self._get_template_context(c),
58 59 request=self.request) No newline at end of file
@@ -1,106 +1,106 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Single source for redirection links.
23 23
24 24 Goal of this module is to provide a single source of truth regarding external
25 25 links. The data inside this module is used to configure the routing
26 26 system of Enterprise and it is used also as a base to check if this data
27 27 and our server configuration are in sync.
28 28
29 29 .. py:data:: link_config
30 30
31 31 Contains the configuration for external links. Each item is supposed to be
32 32 a `dict` like this example::
33 33
34 34 {"name": "url_name",
35 35 "target": "https://rhodecode.com/r1/enterprise/keyword/",
36 36 "external_target": "https://example.com/some-page.html",
37 37 }
38 38
39 39 then you can retrieve the url by simply calling the URL function:
40 40
41 `h.url('url_name')`
41 `h.route_path('url_name')`
42 42
43 43 The redirection must be first implemented in our servers before
44 44 you can see it working.
45 45 """
46 46 # flake8: noqa
47 47 from __future__ import unicode_literals
48 48
49 49 link_config = [
50 50 {
51 51 "name": "enterprise_docs",
52 52 "target": "https://rhodecode.com/r1/enterprise/docs/",
53 53 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
54 54 },
55 55 {
56 56 "name": "enterprise_log_file_locations",
57 57 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
58 58 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
59 59 },
60 60 {
61 61 "name": "enterprise_issue_tracker_settings",
62 62 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
63 63 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
64 64 },
65 65 {
66 66 "name": "enterprise_svn_setup",
67 67 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
68 68 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
69 69 },
70 70 {
71 71 "name": "enterprise_license_convert_from_old",
72 72 "target": "https://rhodecode.com/r1/enterprise/convert-license/",
73 73 "external_target": "https://rhodecode.com/u/license-upgrade",
74 74 },
75 75 {
76 76 "name": "rst_help",
77 77 "target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
78 78 "external_target": "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 79 },
80 80 {
81 81 "name": "markdown_help",
82 82 "target": "https://daringfireball.net/projects/markdown/syntax",
83 83 "external_target": "https://daringfireball.net/projects/markdown/syntax",
84 84 },
85 85 {
86 86 "name": "rhodecode_official",
87 87 "target": "https://rhodecode.com",
88 88 "external_target": "https://rhodecode.com/",
89 89 },
90 90 {
91 91 "name": "rhodecode_support",
92 92 "target": "https://rhodecode.com/help/",
93 93 "external_target": "https://rhodecode.com/support",
94 94 },
95 95 {
96 96 "name": "rhodecode_translations",
97 97 "target": "https://rhodecode.com/translate/enterprise",
98 98 "external_target": "https://www.transifex.com/rhodecode/RhodeCode/",
99 99 },
100 100
101 101 ]
102 102
103 103
104 104 def connect_redirection_links(config):
105 105 for link in link_config:
106 106 config.add_route(link['name'], link['target'], static=True)
@@ -1,652 +1,652 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 comments model for RhodeCode
23 23 """
24 24
25 25 import logging
26 26 import traceback
27 27 import collections
28 28
29 29 from pylons.i18n.translation import _
30 30 from pyramid.threadlocal import get_current_registry, get_current_request
31 31 from sqlalchemy.sql.expression import null
32 32 from sqlalchemy.sql.functions import coalesce
33 33
34 34 from rhodecode.lib import helpers as h, diffs, channelstream
35 35 from rhodecode.lib import audit_logger
36 36 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str
37 37 from rhodecode.model import BaseModel
38 38 from rhodecode.model.db import (
39 39 ChangesetComment, User, Notification, PullRequest, AttributeDict)
40 40 from rhodecode.model.notification import NotificationModel
41 41 from rhodecode.model.meta import Session
42 42 from rhodecode.model.settings import VcsSettingsModel
43 43 from rhodecode.model.notification import EmailNotificationModel
44 44 from rhodecode.model.validation_schema.schemas import comment_schema
45 45
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class CommentsModel(BaseModel):
51 51
52 52 cls = ChangesetComment
53 53
54 54 DIFF_CONTEXT_BEFORE = 3
55 55 DIFF_CONTEXT_AFTER = 3
56 56
57 57 def __get_commit_comment(self, changeset_comment):
58 58 return self._get_instance(ChangesetComment, changeset_comment)
59 59
60 60 def __get_pull_request(self, pull_request):
61 61 return self._get_instance(PullRequest, pull_request)
62 62
63 63 def _extract_mentions(self, s):
64 64 user_objects = []
65 65 for username in extract_mentioned_users(s):
66 66 user_obj = User.get_by_username(username, case_insensitive=True)
67 67 if user_obj:
68 68 user_objects.append(user_obj)
69 69 return user_objects
70 70
71 71 def _get_renderer(self, global_renderer='rst'):
72 72 request = get_current_request()
73 73
74 74 try:
75 75 global_renderer = request.call_context.visual.default_renderer
76 76 except AttributeError:
77 77 log.debug("Renderer not set, falling back "
78 78 "to default renderer '%s'", global_renderer)
79 79 except Exception:
80 80 log.error(traceback.format_exc())
81 81 return global_renderer
82 82
83 83 def aggregate_comments(self, comments, versions, show_version, inline=False):
84 84 # group by versions, and count until, and display objects
85 85
86 86 comment_groups = collections.defaultdict(list)
87 87 [comment_groups[
88 88 _co.pull_request_version_id].append(_co) for _co in comments]
89 89
90 90 def yield_comments(pos):
91 91 for co in comment_groups[pos]:
92 92 yield co
93 93
94 94 comment_versions = collections.defaultdict(
95 95 lambda: collections.defaultdict(list))
96 96 prev_prvid = -1
97 97 # fake last entry with None, to aggregate on "latest" version which
98 98 # doesn't have an pull_request_version_id
99 99 for ver in versions + [AttributeDict({'pull_request_version_id': None})]:
100 100 prvid = ver.pull_request_version_id
101 101 if prev_prvid == -1:
102 102 prev_prvid = prvid
103 103
104 104 for co in yield_comments(prvid):
105 105 comment_versions[prvid]['at'].append(co)
106 106
107 107 # save until
108 108 current = comment_versions[prvid]['at']
109 109 prev_until = comment_versions[prev_prvid]['until']
110 110 cur_until = prev_until + current
111 111 comment_versions[prvid]['until'].extend(cur_until)
112 112
113 113 # save outdated
114 114 if inline:
115 115 outdated = [x for x in cur_until
116 116 if x.outdated_at_version(show_version)]
117 117 else:
118 118 outdated = [x for x in cur_until
119 119 if x.older_than_version(show_version)]
120 120 display = [x for x in cur_until if x not in outdated]
121 121
122 122 comment_versions[prvid]['outdated'] = outdated
123 123 comment_versions[prvid]['display'] = display
124 124
125 125 prev_prvid = prvid
126 126
127 127 return comment_versions
128 128
129 129 def get_unresolved_todos(self, pull_request, show_outdated=True):
130 130
131 131 todos = Session().query(ChangesetComment) \
132 132 .filter(ChangesetComment.pull_request == pull_request) \
133 133 .filter(ChangesetComment.resolved_by == None) \
134 134 .filter(ChangesetComment.comment_type
135 135 == ChangesetComment.COMMENT_TYPE_TODO)
136 136
137 137 if not show_outdated:
138 138 todos = todos.filter(
139 139 coalesce(ChangesetComment.display_state, '') !=
140 140 ChangesetComment.COMMENT_OUTDATED)
141 141
142 142 todos = todos.all()
143 143
144 144 return todos
145 145
146 146 def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
147 147
148 148 todos = Session().query(ChangesetComment) \
149 149 .filter(ChangesetComment.revision == commit_id) \
150 150 .filter(ChangesetComment.resolved_by == None) \
151 151 .filter(ChangesetComment.comment_type
152 152 == ChangesetComment.COMMENT_TYPE_TODO)
153 153
154 154 if not show_outdated:
155 155 todos = todos.filter(
156 156 coalesce(ChangesetComment.display_state, '') !=
157 157 ChangesetComment.COMMENT_OUTDATED)
158 158
159 159 todos = todos.all()
160 160
161 161 return todos
162 162
163 163 def _log_audit_action(self, action, action_data, user, comment):
164 164 audit_logger.store(
165 165 action=action,
166 166 action_data=action_data,
167 167 user=user,
168 168 repo=comment.repo)
169 169
170 170 def create(self, text, repo, user, commit_id=None, pull_request=None,
171 171 f_path=None, line_no=None, status_change=None,
172 172 status_change_type=None, comment_type=None,
173 173 resolves_comment_id=None, closing_pr=False, send_email=True,
174 174 renderer=None):
175 175 """
176 176 Creates new comment for commit or pull request.
177 177 IF status_change is not none this comment is associated with a
178 178 status change of commit or commit associated with pull request
179 179
180 180 :param text:
181 181 :param repo:
182 182 :param user:
183 183 :param commit_id:
184 184 :param pull_request:
185 185 :param f_path:
186 186 :param line_no:
187 187 :param status_change: Label for status change
188 188 :param comment_type: Type of comment
189 189 :param status_change_type: type of status change
190 190 :param closing_pr:
191 191 :param send_email:
192 192 :param renderer: pick renderer for this comment
193 193 """
194 194 if not text:
195 195 log.warning('Missing text for comment, skipping...')
196 196 return
197 197
198 198 if not renderer:
199 199 renderer = self._get_renderer()
200 200
201 201 repo = self._get_repo(repo)
202 202 user = self._get_user(user)
203 203
204 204 schema = comment_schema.CommentSchema()
205 205 validated_kwargs = schema.deserialize(dict(
206 206 comment_body=text,
207 207 comment_type=comment_type,
208 208 comment_file=f_path,
209 209 comment_line=line_no,
210 210 renderer_type=renderer,
211 211 status_change=status_change_type,
212 212 resolves_comment_id=resolves_comment_id,
213 213 repo=repo.repo_id,
214 214 user=user.user_id,
215 215 ))
216 216
217 217 comment = ChangesetComment()
218 218 comment.renderer = validated_kwargs['renderer_type']
219 219 comment.text = validated_kwargs['comment_body']
220 220 comment.f_path = validated_kwargs['comment_file']
221 221 comment.line_no = validated_kwargs['comment_line']
222 222 comment.comment_type = validated_kwargs['comment_type']
223 223
224 224 comment.repo = repo
225 225 comment.author = user
226 226 comment.resolved_comment = self.__get_commit_comment(
227 227 validated_kwargs['resolves_comment_id'])
228 228
229 229 pull_request_id = pull_request
230 230
231 231 commit_obj = None
232 232 pull_request_obj = None
233 233
234 234 if commit_id:
235 235 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
236 236 # do a lookup, so we don't pass something bad here
237 237 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
238 238 comment.revision = commit_obj.raw_id
239 239
240 240 elif pull_request_id:
241 241 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
242 242 pull_request_obj = self.__get_pull_request(pull_request_id)
243 243 comment.pull_request = pull_request_obj
244 244 else:
245 245 raise Exception('Please specify commit or pull_request_id')
246 246
247 247 Session().add(comment)
248 248 Session().flush()
249 249 kwargs = {
250 250 'user': user,
251 251 'renderer_type': renderer,
252 252 'repo_name': repo.repo_name,
253 253 'status_change': status_change,
254 254 'status_change_type': status_change_type,
255 255 'comment_body': text,
256 256 'comment_file': f_path,
257 257 'comment_line': line_no,
258 258 'comment_type': comment_type or 'note'
259 259 }
260 260
261 261 if commit_obj:
262 262 recipients = ChangesetComment.get_users(
263 263 revision=commit_obj.raw_id)
264 264 # add commit author if it's in RhodeCode system
265 265 cs_author = User.get_from_cs_author(commit_obj.author)
266 266 if not cs_author:
267 267 # use repo owner if we cannot extract the author correctly
268 268 cs_author = repo.user
269 269 recipients += [cs_author]
270 270
271 271 commit_comment_url = self.get_url(comment)
272 272
273 273 target_repo_url = h.link_to(
274 274 repo.repo_name,
275 275 h.route_url('repo_summary', repo_name=repo.repo_name))
276 276
277 277 # commit specifics
278 278 kwargs.update({
279 279 'commit': commit_obj,
280 280 'commit_message': commit_obj.message,
281 281 'commit_target_repo': target_repo_url,
282 282 'commit_comment_url': commit_comment_url,
283 283 })
284 284
285 285 elif pull_request_obj:
286 286 # get the current participants of this pull request
287 287 recipients = ChangesetComment.get_users(
288 288 pull_request_id=pull_request_obj.pull_request_id)
289 289 # add pull request author
290 290 recipients += [pull_request_obj.author]
291 291
292 292 # add the reviewers to notification
293 293 recipients += [x.user for x in pull_request_obj.reviewers]
294 294
295 295 pr_target_repo = pull_request_obj.target_repo
296 296 pr_source_repo = pull_request_obj.source_repo
297 297
298 298 pr_comment_url = h.route_url(
299 299 'pullrequest_show',
300 300 repo_name=pr_target_repo.repo_name,
301 301 pull_request_id=pull_request_obj.pull_request_id,
302 anchor='comment-%s' % comment.comment_id)
302 _anchor='comment-%s' % comment.comment_id)
303 303
304 304 # set some variables for email notification
305 305 pr_target_repo_url = h.route_url(
306 306 'repo_summary', repo_name=pr_target_repo.repo_name)
307 307
308 308 pr_source_repo_url = h.route_url(
309 309 'repo_summary', repo_name=pr_source_repo.repo_name)
310 310
311 311 # pull request specifics
312 312 kwargs.update({
313 313 'pull_request': pull_request_obj,
314 314 'pr_id': pull_request_obj.pull_request_id,
315 315 'pr_target_repo': pr_target_repo,
316 316 'pr_target_repo_url': pr_target_repo_url,
317 317 'pr_source_repo': pr_source_repo,
318 318 'pr_source_repo_url': pr_source_repo_url,
319 319 'pr_comment_url': pr_comment_url,
320 320 'pr_closing': closing_pr,
321 321 })
322 322 if send_email:
323 323 # pre-generate the subject for notification itself
324 324 (subject,
325 325 _h, _e, # we don't care about those
326 326 body_plaintext) = EmailNotificationModel().render_email(
327 327 notification_type, **kwargs)
328 328
329 329 mention_recipients = set(
330 330 self._extract_mentions(text)).difference(recipients)
331 331
332 332 # create notification objects, and emails
333 333 NotificationModel().create(
334 334 created_by=user,
335 335 notification_subject=subject,
336 336 notification_body=body_plaintext,
337 337 notification_type=notification_type,
338 338 recipients=recipients,
339 339 mention_recipients=mention_recipients,
340 340 email_kwargs=kwargs,
341 341 )
342 342
343 343 Session().flush()
344 344 if comment.pull_request:
345 345 action = 'repo.pull_request.comment.create'
346 346 else:
347 347 action = 'repo.commit.comment.create'
348 348
349 349 comment_data = comment.get_api_data()
350 350 self._log_audit_action(
351 351 action, {'data': comment_data}, user, comment)
352 352
353 353 msg_url = ''
354 354 channel = None
355 355 if commit_obj:
356 356 msg_url = commit_comment_url
357 357 repo_name = repo.repo_name
358 358 channel = u'/repo${}$/commit/{}'.format(
359 359 repo_name,
360 360 commit_obj.raw_id
361 361 )
362 362 elif pull_request_obj:
363 363 msg_url = pr_comment_url
364 364 repo_name = pr_target_repo.repo_name
365 365 channel = u'/repo${}$/pr/{}'.format(
366 366 repo_name,
367 367 pull_request_id
368 368 )
369 369
370 370 message = '<strong>{}</strong> {} - ' \
371 371 '<a onclick="window.location=\'{}\';' \
372 372 'window.location.reload()">' \
373 373 '<strong>{}</strong></a>'
374 374 message = message.format(
375 375 user.username, _('made a comment'), msg_url,
376 376 _('Show it now'))
377 377
378 378 channelstream.post_message(
379 379 channel, message, user.username,
380 380 registry=get_current_registry())
381 381
382 382 return comment
383 383
384 384 def delete(self, comment, user):
385 385 """
386 386 Deletes given comment
387 387 """
388 388 comment = self.__get_commit_comment(comment)
389 389 old_data = comment.get_api_data()
390 390 Session().delete(comment)
391 391
392 392 if comment.pull_request:
393 393 action = 'repo.pull_request.comment.delete'
394 394 else:
395 395 action = 'repo.commit.comment.delete'
396 396
397 397 self._log_audit_action(
398 398 action, {'old_data': old_data}, user, comment)
399 399
400 400 return comment
401 401
402 402 def get_all_comments(self, repo_id, revision=None, pull_request=None):
403 403 q = ChangesetComment.query()\
404 404 .filter(ChangesetComment.repo_id == repo_id)
405 405 if revision:
406 406 q = q.filter(ChangesetComment.revision == revision)
407 407 elif pull_request:
408 408 pull_request = self.__get_pull_request(pull_request)
409 409 q = q.filter(ChangesetComment.pull_request == pull_request)
410 410 else:
411 411 raise Exception('Please specify commit or pull_request')
412 412 q = q.order_by(ChangesetComment.created_on)
413 413 return q.all()
414 414
415 415 def get_url(self, comment, request=None, permalink=False):
416 416 if not request:
417 417 request = get_current_request()
418 418
419 419 comment = self.__get_commit_comment(comment)
420 420 if comment.pull_request:
421 421 pull_request = comment.pull_request
422 422 if permalink:
423 423 return request.route_url(
424 424 'pull_requests_global',
425 425 pull_request_id=pull_request.pull_request_id,
426 426 _anchor='comment-%s' % comment.comment_id)
427 427 else:
428 428 return request.route_url('pullrequest_show',
429 429 repo_name=safe_str(pull_request.target_repo.repo_name),
430 430 pull_request_id=pull_request.pull_request_id,
431 431 _anchor='comment-%s' % comment.comment_id)
432 432
433 433 else:
434 434 repo = comment.repo
435 435 commit_id = comment.revision
436 436
437 437 if permalink:
438 438 return request.route_url(
439 439 'repo_commit', repo_name=safe_str(repo.repo_id),
440 440 commit_id=commit_id,
441 441 _anchor='comment-%s' % comment.comment_id)
442 442
443 443 else:
444 444 return request.route_url(
445 445 'repo_commit', repo_name=safe_str(repo.repo_name),
446 446 commit_id=commit_id,
447 447 _anchor='comment-%s' % comment.comment_id)
448 448
449 449 def get_comments(self, repo_id, revision=None, pull_request=None):
450 450 """
451 451 Gets main comments based on revision or pull_request_id
452 452
453 453 :param repo_id:
454 454 :param revision:
455 455 :param pull_request:
456 456 """
457 457
458 458 q = ChangesetComment.query()\
459 459 .filter(ChangesetComment.repo_id == repo_id)\
460 460 .filter(ChangesetComment.line_no == None)\
461 461 .filter(ChangesetComment.f_path == None)
462 462 if revision:
463 463 q = q.filter(ChangesetComment.revision == revision)
464 464 elif pull_request:
465 465 pull_request = self.__get_pull_request(pull_request)
466 466 q = q.filter(ChangesetComment.pull_request == pull_request)
467 467 else:
468 468 raise Exception('Please specify commit or pull_request')
469 469 q = q.order_by(ChangesetComment.created_on)
470 470 return q.all()
471 471
472 472 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
473 473 q = self._get_inline_comments_query(repo_id, revision, pull_request)
474 474 return self._group_comments_by_path_and_line_number(q)
475 475
476 476 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
477 477 version=None):
478 478 inline_cnt = 0
479 479 for fname, per_line_comments in inline_comments.iteritems():
480 480 for lno, comments in per_line_comments.iteritems():
481 481 for comm in comments:
482 482 if not comm.outdated_at_version(version) and skip_outdated:
483 483 inline_cnt += 1
484 484
485 485 return inline_cnt
486 486
487 487 def get_outdated_comments(self, repo_id, pull_request):
488 488 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
489 489 # of a pull request.
490 490 q = self._all_inline_comments_of_pull_request(pull_request)
491 491 q = q.filter(
492 492 ChangesetComment.display_state ==
493 493 ChangesetComment.COMMENT_OUTDATED
494 494 ).order_by(ChangesetComment.comment_id.asc())
495 495
496 496 return self._group_comments_by_path_and_line_number(q)
497 497
498 498 def _get_inline_comments_query(self, repo_id, revision, pull_request):
499 499 # TODO: johbo: Split this into two methods: One for PR and one for
500 500 # commit.
501 501 if revision:
502 502 q = Session().query(ChangesetComment).filter(
503 503 ChangesetComment.repo_id == repo_id,
504 504 ChangesetComment.line_no != null(),
505 505 ChangesetComment.f_path != null(),
506 506 ChangesetComment.revision == revision)
507 507
508 508 elif pull_request:
509 509 pull_request = self.__get_pull_request(pull_request)
510 510 if not CommentsModel.use_outdated_comments(pull_request):
511 511 q = self._visible_inline_comments_of_pull_request(pull_request)
512 512 else:
513 513 q = self._all_inline_comments_of_pull_request(pull_request)
514 514
515 515 else:
516 516 raise Exception('Please specify commit or pull_request_id')
517 517 q = q.order_by(ChangesetComment.comment_id.asc())
518 518 return q
519 519
520 520 def _group_comments_by_path_and_line_number(self, q):
521 521 comments = q.all()
522 522 paths = collections.defaultdict(lambda: collections.defaultdict(list))
523 523 for co in comments:
524 524 paths[co.f_path][co.line_no].append(co)
525 525 return paths
526 526
527 527 @classmethod
528 528 def needed_extra_diff_context(cls):
529 529 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
530 530
531 531 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
532 532 if not CommentsModel.use_outdated_comments(pull_request):
533 533 return
534 534
535 535 comments = self._visible_inline_comments_of_pull_request(pull_request)
536 536 comments_to_outdate = comments.all()
537 537
538 538 for comment in comments_to_outdate:
539 539 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
540 540
541 541 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
542 542 diff_line = _parse_comment_line_number(comment.line_no)
543 543
544 544 try:
545 545 old_context = old_diff_proc.get_context_of_line(
546 546 path=comment.f_path, diff_line=diff_line)
547 547 new_context = new_diff_proc.get_context_of_line(
548 548 path=comment.f_path, diff_line=diff_line)
549 549 except (diffs.LineNotInDiffException,
550 550 diffs.FileNotInDiffException):
551 551 comment.display_state = ChangesetComment.COMMENT_OUTDATED
552 552 return
553 553
554 554 if old_context == new_context:
555 555 return
556 556
557 557 if self._should_relocate_diff_line(diff_line):
558 558 new_diff_lines = new_diff_proc.find_context(
559 559 path=comment.f_path, context=old_context,
560 560 offset=self.DIFF_CONTEXT_BEFORE)
561 561 if not new_diff_lines:
562 562 comment.display_state = ChangesetComment.COMMENT_OUTDATED
563 563 else:
564 564 new_diff_line = self._choose_closest_diff_line(
565 565 diff_line, new_diff_lines)
566 566 comment.line_no = _diff_to_comment_line_number(new_diff_line)
567 567 else:
568 568 comment.display_state = ChangesetComment.COMMENT_OUTDATED
569 569
570 570 def _should_relocate_diff_line(self, diff_line):
571 571 """
572 572 Checks if relocation shall be tried for the given `diff_line`.
573 573
574 574 If a comment points into the first lines, then we can have a situation
575 575 that after an update another line has been added on top. In this case
576 576 we would find the context still and move the comment around. This
577 577 would be wrong.
578 578 """
579 579 should_relocate = (
580 580 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
581 581 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
582 582 return should_relocate
583 583
584 584 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
585 585 candidate = new_diff_lines[0]
586 586 best_delta = _diff_line_delta(diff_line, candidate)
587 587 for new_diff_line in new_diff_lines[1:]:
588 588 delta = _diff_line_delta(diff_line, new_diff_line)
589 589 if delta < best_delta:
590 590 candidate = new_diff_line
591 591 best_delta = delta
592 592 return candidate
593 593
594 594 def _visible_inline_comments_of_pull_request(self, pull_request):
595 595 comments = self._all_inline_comments_of_pull_request(pull_request)
596 596 comments = comments.filter(
597 597 coalesce(ChangesetComment.display_state, '') !=
598 598 ChangesetComment.COMMENT_OUTDATED)
599 599 return comments
600 600
601 601 def _all_inline_comments_of_pull_request(self, pull_request):
602 602 comments = Session().query(ChangesetComment)\
603 603 .filter(ChangesetComment.line_no != None)\
604 604 .filter(ChangesetComment.f_path != None)\
605 605 .filter(ChangesetComment.pull_request == pull_request)
606 606 return comments
607 607
608 608 def _all_general_comments_of_pull_request(self, pull_request):
609 609 comments = Session().query(ChangesetComment)\
610 610 .filter(ChangesetComment.line_no == None)\
611 611 .filter(ChangesetComment.f_path == None)\
612 612 .filter(ChangesetComment.pull_request == pull_request)
613 613 return comments
614 614
615 615 @staticmethod
616 616 def use_outdated_comments(pull_request):
617 617 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
618 618 settings = settings_model.get_general_settings()
619 619 return settings.get('rhodecode_use_outdated_comments', False)
620 620
621 621
622 622 def _parse_comment_line_number(line_no):
623 623 """
624 624 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
625 625 """
626 626 old_line = None
627 627 new_line = None
628 628 if line_no.startswith('o'):
629 629 old_line = int(line_no[1:])
630 630 elif line_no.startswith('n'):
631 631 new_line = int(line_no[1:])
632 632 else:
633 633 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
634 634 return diffs.DiffLineNumber(old_line, new_line)
635 635
636 636
637 637 def _diff_to_comment_line_number(diff_line):
638 638 if diff_line.new is not None:
639 639 return u'n{}'.format(diff_line.new)
640 640 elif diff_line.old is not None:
641 641 return u'o{}'.format(diff_line.old)
642 642 return u''
643 643
644 644
645 645 def _diff_line_delta(a, b):
646 646 if None not in (a.new, b.new):
647 647 return abs(a.new - b.new)
648 648 elif None not in (a.old, b.old):
649 649 return abs(a.old - b.old)
650 650 else:
651 651 raise ValueError(
652 652 "Cannot compute delta between {} and {}".format(a, b))
@@ -1,271 +1,273 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 16 pyroutes.register('favicon', '/favicon.ico', []);
17 17 pyroutes.register('robots', '/robots.txt', []);
18 18 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
19 19 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
20 20 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
21 21 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
22 22 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
23 23 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
24 24 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
25 25 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
26 26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
27 27 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
28 28 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
29 29 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
30 30 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
31 31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
33 33 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
34 34 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
35 35 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
36 36 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
37 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
38 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
37 39 pyroutes.register('admin_home', '/_admin', []);
38 40 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
40 42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
41 43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
42 44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
43 45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
44 46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
45 47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
46 48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
47 49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
48 50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
49 51 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
50 52 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
51 53 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
52 54 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
53 55 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
54 56 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
55 57 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
56 58 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
57 59 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
58 60 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
59 61 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
60 62 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
61 63 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
62 64 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
63 65 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
64 66 pyroutes.register('users', '/_admin/users', []);
65 67 pyroutes.register('users_data', '/_admin/users_data', []);
66 68 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
67 69 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
68 70 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
69 71 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
70 72 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
71 73 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
72 74 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
73 75 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
74 76 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
75 77 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
76 78 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
77 79 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
78 80 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
79 81 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
80 82 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
81 83 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
82 84 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
83 85 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
84 86 pyroutes.register('user_groups', '/_admin/user_groups', []);
85 87 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
86 88 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
87 89 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
88 90 pyroutes.register('repos', '/_admin/repos', []);
89 91 pyroutes.register('repo_new', '/_admin/repos/new', []);
90 92 pyroutes.register('repo_create', '/_admin/repos/create', []);
91 93 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
92 94 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
93 95 pyroutes.register('channelstream_proxy', '/_channelstream', []);
94 96 pyroutes.register('login', '/_admin/login', []);
95 97 pyroutes.register('logout', '/_admin/logout', []);
96 98 pyroutes.register('register', '/_admin/register', []);
97 99 pyroutes.register('reset_password', '/_admin/password_reset', []);
98 100 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
99 101 pyroutes.register('home', '/', []);
100 102 pyroutes.register('user_autocomplete_data', '/_users', []);
101 103 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
102 104 pyroutes.register('repo_list_data', '/_repos', []);
103 105 pyroutes.register('goto_switcher_data', '/_goto_data', []);
104 106 pyroutes.register('journal', '/_admin/journal', []);
105 107 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
106 108 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
107 109 pyroutes.register('journal_public', '/_admin/public_journal', []);
108 110 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
109 111 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
110 112 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
111 113 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
112 114 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
113 115 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
114 116 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
115 117 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
116 118 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
117 119 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
118 120 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
119 121 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
120 122 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
121 123 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
122 124 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
123 125 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
124 126 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
125 127 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
126 128 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
127 129 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
128 130 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
129 131 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
130 132 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
131 133 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
132 134 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
133 135 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
134 136 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 137 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 138 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 139 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
138 140 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
139 141 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 142 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 143 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 144 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
143 145 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 146 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
145 147 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
146 148 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
147 149 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
148 150 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
149 151 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
150 152 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
151 153 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
152 154 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
153 155 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
154 156 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
155 157 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
156 158 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
157 159 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
158 160 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
159 161 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
160 162 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
161 163 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
162 164 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
163 165 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
164 166 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
165 167 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
166 168 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
167 169 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
168 170 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
169 171 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
170 172 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
171 173 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
172 174 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
173 175 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
174 176 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
175 177 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
176 178 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
177 179 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
178 180 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
179 181 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
180 182 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
181 183 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
182 184 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
183 185 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
184 186 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
185 187 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
186 188 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
187 189 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
188 190 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
189 191 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
190 192 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
191 193 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
192 194 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
193 195 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
194 196 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
195 197 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
196 198 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
197 199 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
198 200 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
199 201 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
200 202 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
201 203 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
202 204 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
203 205 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
204 206 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
205 207 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
206 208 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
207 209 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
208 210 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
209 211 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
210 212 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
211 213 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
212 214 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
213 215 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
214 216 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
215 217 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
216 218 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
217 219 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
218 220 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
219 221 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
220 222 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
221 223 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
222 224 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
223 225 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
224 226 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
225 227 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
226 228 pyroutes.register('search', '/_admin/search', []);
227 229 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
228 230 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
229 231 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
230 232 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
231 233 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
232 234 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
233 235 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
234 236 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
235 237 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
236 238 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
237 239 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
238 240 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
239 241 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
240 242 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
241 243 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
242 244 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
243 245 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
244 246 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
245 247 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
246 248 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
247 249 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
248 250 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
249 251 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
250 252 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
251 253 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
252 254 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
253 255 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
254 256 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
255 257 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
256 258 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
257 259 pyroutes.register('gists_show', '/_admin/gists', []);
258 260 pyroutes.register('gists_new', '/_admin/gists/new', []);
259 261 pyroutes.register('gists_create', '/_admin/gists/create', []);
260 262 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
261 263 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
262 264 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
263 265 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
264 266 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
265 267 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
266 268 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
267 269 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
268 270 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
269 271 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
270 272 pyroutes.register('apiv2', '/_admin/api', []);
271 273 }
@@ -1,16 +1,17 b''
1 1 <script>
2 2 var CHANNELSTREAM_URLS = ${config['url_gen'](request)|n};
3 3 %if request.registry.rhodecode_plugins['channelstream']['enabled'] and c.rhodecode_user.username != h.DEFAULT_USER:
4 4 var CHANNELSTREAM_SETTINGS = {
5 5 'enabled': true,
6 6 'ws_location': '${request.registry.settings.get('channelstream.ws_url')}',
7 'webapp_location': '${h.url('/', qualified=True)[:-1]}'
7 'webapp_location': '${h.route_url('home').rstrip('/')}'
8 8 };
9 9 %else:
10 10 var CHANNELSTREAM_SETTINGS = {
11 11 'enabled':false,
12 12 'ws_location': '',
13 'webapp_location': ''};
13 'webapp_location': '${h.route_url('home').rstrip('/')}'
14 };
14 15 %endif
15 16
16 17 </script>
@@ -1,671 +1,672 b''
1 1 <%namespace name="commentblock" file="/changeset/changeset_file_comment.mako"/>
2 2
3 3 <%def name="diff_line_anchor(filename, line, type)"><%
4 4 return '%s_%s_%i' % (h.safeid(filename), type, line)
5 5 %></%def>
6 6
7 7 <%def name="action_class(action)">
8 8 <%
9 9 return {
10 10 '-': 'cb-deletion',
11 11 '+': 'cb-addition',
12 12 ' ': 'cb-context',
13 13 }.get(action, 'cb-empty')
14 14 %>
15 15 </%def>
16 16
17 17 <%def name="op_class(op_id)">
18 18 <%
19 19 return {
20 20 DEL_FILENODE: 'deletion', # file deleted
21 21 BIN_FILENODE: 'warning' # binary diff hidden
22 22 }.get(op_id, 'addition')
23 23 %>
24 24 </%def>
25 25
26 26 <%def name="link_for(**kw)">
27 27 <%
28 28 new_args = request.GET.mixed()
29 29 new_args.update(kw)
30 return h.url('', **new_args)
30 return request.current_route_path(_query=new_args)
31 31 %>
32 32 </%def>
33 33
34 34 <%def name="render_diffset(diffset, commit=None,
35 35
36 36 # collapse all file diff entries when there are more than this amount of files in the diff
37 37 collapse_when_files_over=20,
38 38
39 39 # collapse lines in the diff when more than this amount of lines changed in the file diff
40 40 lines_changed_limit=500,
41 41
42 42 # add a ruler at to the output
43 43 ruler_at_chars=0,
44 44
45 45 # show inline comments
46 46 use_comments=False,
47 47
48 48 # disable new comments
49 49 disable_new_comments=False,
50 50
51 51 # special file-comments that were deleted in previous versions
52 52 # it's used for showing outdated comments for deleted files in a PR
53 53 deleted_files_comments=None
54 54
55 55 )">
56 56
57 57 %if use_comments:
58 58 <div id="cb-comments-inline-container-template" class="js-template">
59 59 ${inline_comments_container([])}
60 60 </div>
61 61 <div class="js-template" id="cb-comment-inline-form-template">
62 62 <div class="comment-inline-form ac">
63 63
64 64 %if c.rhodecode_user.username != h.DEFAULT_USER:
65 65 ## render template for inline comments
66 66 ${commentblock.comment_form(form_type='inline')}
67 67 %else:
68 68 ${h.form('', class_='inline-form comment-form-login', method='get')}
69 69 <div class="pull-left">
70 70 <div class="comment-help pull-right">
71 71 ${_('You need to be logged in to leave comments.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a>
72 72 </div>
73 73 </div>
74 74 <div class="comment-button pull-right">
75 75 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
76 76 ${_('Cancel')}
77 77 </button>
78 78 </div>
79 79 <div class="clearfix"></div>
80 80 ${h.end_form()}
81 81 %endif
82 82 </div>
83 83 </div>
84 84
85 85 %endif
86 86 <%
87 87 collapse_all = len(diffset.files) > collapse_when_files_over
88 88 %>
89 89
90 90 %if c.diffmode == 'sideside':
91 91 <style>
92 92 .wrapper {
93 93 max-width: 1600px !important;
94 94 }
95 95 </style>
96 96 %endif
97 97
98 98 %if ruler_at_chars:
99 99 <style>
100 100 .diff table.cb .cb-content:after {
101 101 content: "";
102 102 border-left: 1px solid blue;
103 103 position: absolute;
104 104 top: 0;
105 105 height: 18px;
106 106 opacity: .2;
107 107 z-index: 10;
108 108 //## +5 to account for diff action (+/-)
109 109 left: ${ruler_at_chars + 5}ch;
110 110 </style>
111 111 %endif
112 112
113 113 <div class="diffset ${disable_new_comments and 'diffset-comments-disabled'}">
114 114 <div class="diffset-heading ${diffset.limited_diff and 'diffset-heading-warning' or ''}">
115 115 %if commit:
116 116 <div class="pull-right">
117 117 <a class="btn tooltip" title="${h.tooltip(_('Browse Files at revision {}').format(commit.raw_id))}" href="${h.route_path('repo_files',repo_name=diffset.repo_name, commit_id=commit.raw_id, f_path='')}">
118 118 ${_('Browse Files')}
119 119 </a>
120 120 </div>
121 121 %endif
122 122 <h2 class="clearinner">
123 123 %if commit:
124 124 <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">${'r%s:%s' % (commit.revision,h.short_id(commit.raw_id))}</a> -
125 125 ${h.age_component(commit.date)} -
126 126 %endif
127 %if diffset.limited_diff:
128 ${_('The requested commit is too big and content was truncated.')}
127
128 %if diffset.limited_diff:
129 ${_('The requested commit is too big and content was truncated.')}
129 130
130 ${_ungettext('%(num)s file changed.', '%(num)s files changed.', diffset.changed_files) % {'num': diffset.changed_files}}
131 <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
132 %else:
133 ${_ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted',
134 '%(num)s files changed: %(linesadd)s inserted, %(linesdel)s deleted', diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}}
135 %endif
131 ${_ungettext('%(num)s file changed.', '%(num)s files changed.', diffset.changed_files) % {'num': diffset.changed_files}}
132 <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
133 %else:
134 ${_ungettext('%(num)s file changed: %(linesadd)s inserted, ''%(linesdel)s deleted',
135 '%(num)s files changed: %(linesadd)s inserted, %(linesdel)s deleted', diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}}
136 %endif
136 137
137 138 </h2>
138 139 </div>
139 140
140 141 %if not diffset.files:
141 142 <p class="empty_data">${_('No files')}</p>
142 143 %endif
143 144
144 145 <div class="filediffs">
145 146 ## initial value could be marked as False later on
146 147 <% over_lines_changed_limit = False %>
147 148 %for i, filediff in enumerate(diffset.files):
148 149
149 150 <%
150 151 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
151 152 over_lines_changed_limit = lines_changed > lines_changed_limit
152 153 %>
153 154 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filediff)}" type="checkbox">
154 155 <div
155 156 class="filediff"
156 157 data-f-path="${filediff.patch['filename']}"
157 158 id="a_${h.FID('', filediff.patch['filename'])}">
158 159 <label for="filediff-collapse-${id(filediff)}" class="filediff-heading">
159 160 <div class="filediff-collapse-indicator"></div>
160 161 ${diff_ops(filediff)}
161 162 </label>
162 163 ${diff_menu(filediff, use_comments=use_comments)}
163 164 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
164 165 %if not filediff.hunks:
165 166 %for op_id, op_text in filediff.patch['stats']['ops'].items():
166 167 <tr>
167 168 <td class="cb-text cb-${op_class(op_id)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
168 169 %if op_id == DEL_FILENODE:
169 170 ${_('File was deleted')}
170 171 %elif op_id == BIN_FILENODE:
171 172 ${_('Binary file hidden')}
172 173 %else:
173 174 ${op_text}
174 175 %endif
175 176 </td>
176 177 </tr>
177 178 %endfor
178 179 %endif
179 180 %if filediff.limited_diff:
180 181 <tr class="cb-warning cb-collapser">
181 182 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
182 183 ${_('The requested commit is too big and content was truncated.')} <a href="${link_for(fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
183 184 </td>
184 185 </tr>
185 186 %else:
186 187 %if over_lines_changed_limit:
187 188 <tr class="cb-warning cb-collapser">
188 189 <td class="cb-text" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=6'}>
189 190 ${_('This diff has been collapsed as it changes many lines, (%i lines changed)' % lines_changed)}
190 191 <a href="#" class="cb-expand"
191 192 onclick="$(this).closest('table').removeClass('cb-collapsed'); return false;">${_('Show them')}
192 193 </a>
193 194 <a href="#" class="cb-collapse"
194 195 onclick="$(this).closest('table').addClass('cb-collapsed'); return false;">${_('Hide them')}
195 196 </a>
196 197 </td>
197 198 </tr>
198 199 %endif
199 200 %endif
200 201
201 202 %for hunk in filediff.hunks:
202 203 <tr class="cb-hunk">
203 204 <td ${c.diffmode == 'unified' and 'colspan=3' or ''}>
204 205 ## TODO: dan: add ajax loading of more context here
205 206 ## <a href="#">
206 207 <i class="icon-more"></i>
207 208 ## </a>
208 209 </td>
209 210 <td ${c.diffmode == 'sideside' and 'colspan=5' or ''}>
210 211 @@
211 212 -${hunk.source_start},${hunk.source_length}
212 213 +${hunk.target_start},${hunk.target_length}
213 214 ${hunk.section_header}
214 215 </td>
215 216 </tr>
216 217 %if c.diffmode == 'unified':
217 218 ${render_hunk_lines_unified(hunk, use_comments=use_comments)}
218 219 %elif c.diffmode == 'sideside':
219 220 ${render_hunk_lines_sideside(hunk, use_comments=use_comments)}
220 221 %else:
221 222 <tr class="cb-line">
222 223 <td>unknown diff mode</td>
223 224 </tr>
224 225 %endif
225 226 %endfor
226 227
227 228 ## outdated comments that do not fit into currently displayed lines
228 229 % for lineno, comments in filediff.left_comments.items():
229 230
230 231 %if c.diffmode == 'unified':
231 232 <tr class="cb-line">
232 233 <td class="cb-data cb-context"></td>
233 234 <td class="cb-lineno cb-context"></td>
234 235 <td class="cb-lineno cb-context"></td>
235 236 <td class="cb-content cb-context">
236 237 ${inline_comments_container(comments)}
237 238 </td>
238 239 </tr>
239 240 %elif c.diffmode == 'sideside':
240 241 <tr class="cb-line">
241 242 <td class="cb-data cb-context"></td>
242 243 <td class="cb-lineno cb-context"></td>
243 244 <td class="cb-content cb-context"></td>
244 245
245 246 <td class="cb-data cb-context"></td>
246 247 <td class="cb-lineno cb-context"></td>
247 248 <td class="cb-content cb-context">
248 249 ${inline_comments_container(comments)}
249 250 </td>
250 251 </tr>
251 252 %endif
252 253
253 254 % endfor
254 255
255 256 </table>
256 257 </div>
257 258 %endfor
258 259
259 260 ## outdated comments that are made for a file that has been deleted
260 261 % for filename, comments_dict in (deleted_files_comments or {}).items():
261 262
262 263 <div class="filediffs filediff-outdated" style="display: none">
263 264 <input ${collapse_all and 'checked' or ''} class="filediff-collapse-state" id="filediff-collapse-${id(filename)}" type="checkbox">
264 265 <div class="filediff" data-f-path="${filename}" id="a_${h.FID('', filename)}">
265 266 <label for="filediff-collapse-${id(filename)}" class="filediff-heading">
266 267 <div class="filediff-collapse-indicator"></div>
267 268 <span class="pill">
268 269 ## file was deleted
269 270 <strong>${filename}</strong>
270 271 </span>
271 272 <span class="pill-group" style="float: left">
272 273 ## file op, doesn't need translation
273 274 <span class="pill" op="removed">removed in this version</span>
274 275 </span>
275 276 <a class="pill filediff-anchor" href="#a_${h.FID('', filename)}">ΒΆ</a>
276 277 <span class="pill-group" style="float: right">
277 278 <span class="pill" op="deleted">-${comments_dict['stats']}</span>
278 279 </span>
279 280 </label>
280 281
281 282 <table class="cb cb-diff-${c.diffmode} code-highlight ${over_lines_changed_limit and 'cb-collapsed' or ''}">
282 283 <tr>
283 284 % if c.diffmode == 'unified':
284 285 <td></td>
285 286 %endif
286 287
287 288 <td></td>
288 289 <td class="cb-text cb-${op_class(BIN_FILENODE)}" ${c.diffmode == 'unified' and 'colspan=4' or 'colspan=5'}>
289 290 ${_('File was deleted in this version, and outdated comments were made on it')}
290 291 </td>
291 292 </tr>
292 293 %if c.diffmode == 'unified':
293 294 <tr class="cb-line">
294 295 <td class="cb-data cb-context"></td>
295 296 <td class="cb-lineno cb-context"></td>
296 297 <td class="cb-lineno cb-context"></td>
297 298 <td class="cb-content cb-context">
298 299 ${inline_comments_container(comments_dict['comments'])}
299 300 </td>
300 301 </tr>
301 302 %elif c.diffmode == 'sideside':
302 303 <tr class="cb-line">
303 304 <td class="cb-data cb-context"></td>
304 305 <td class="cb-lineno cb-context"></td>
305 306 <td class="cb-content cb-context"></td>
306 307
307 308 <td class="cb-data cb-context"></td>
308 309 <td class="cb-lineno cb-context"></td>
309 310 <td class="cb-content cb-context">
310 311 ${inline_comments_container(comments_dict['comments'])}
311 312 </td>
312 313 </tr>
313 314 %endif
314 315 </table>
315 316 </div>
316 317 </div>
317 318 % endfor
318 319
319 320 </div>
320 321 </div>
321 322 </%def>
322 323
323 324 <%def name="diff_ops(filediff)">
324 325 <%
325 326 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
326 327 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
327 328 %>
328 329 <span class="pill">
329 330 %if filediff.source_file_path and filediff.target_file_path:
330 331 %if filediff.source_file_path != filediff.target_file_path:
331 332 ## file was renamed, or copied
332 333 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
333 334 <strong>${filediff.target_file_path}</strong> β¬… <del>${filediff.source_file_path}</del>
334 335 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
335 336 <strong>${filediff.target_file_path}</strong> β¬… ${filediff.source_file_path}
336 337 %endif
337 338 %else:
338 339 ## file was modified
339 340 <strong>${filediff.source_file_path}</strong>
340 341 %endif
341 342 %else:
342 343 %if filediff.source_file_path:
343 344 ## file was deleted
344 345 <strong>${filediff.source_file_path}</strong>
345 346 %else:
346 347 ## file was added
347 348 <strong>${filediff.target_file_path}</strong>
348 349 %endif
349 350 %endif
350 351 </span>
351 352 <span class="pill-group" style="float: left">
352 353 %if filediff.limited_diff:
353 354 <span class="pill tooltip" op="limited" title="The stats for this diff are not complete">limited diff</span>
354 355 %endif
355 356
356 357 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
357 358 <span class="pill" op="renamed">renamed</span>
358 359 %endif
359 360
360 361 %if COPIED_FILENODE in filediff.patch['stats']['ops']:
361 362 <span class="pill" op="copied">copied</span>
362 363 %endif
363 364
364 365 %if NEW_FILENODE in filediff.patch['stats']['ops']:
365 366 <span class="pill" op="created">created</span>
366 367 %if filediff['target_mode'].startswith('120'):
367 368 <span class="pill" op="symlink">symlink</span>
368 369 %else:
369 370 <span class="pill" op="mode">${nice_mode(filediff['target_mode'])}</span>
370 371 %endif
371 372 %endif
372 373
373 374 %if DEL_FILENODE in filediff.patch['stats']['ops']:
374 375 <span class="pill" op="removed">removed</span>
375 376 %endif
376 377
377 378 %if CHMOD_FILENODE in filediff.patch['stats']['ops']:
378 379 <span class="pill" op="mode">
379 380 ${nice_mode(filediff['source_mode'])} ➑ ${nice_mode(filediff['target_mode'])}
380 381 </span>
381 382 %endif
382 383 </span>
383 384
384 385 <a class="pill filediff-anchor" href="#a_${h.FID('', filediff.patch['filename'])}">ΒΆ</a>
385 386
386 387 <span class="pill-group" style="float: right">
387 388 %if BIN_FILENODE in filediff.patch['stats']['ops']:
388 389 <span class="pill" op="binary">binary</span>
389 390 %if MOD_FILENODE in filediff.patch['stats']['ops']:
390 391 <span class="pill" op="modified">modified</span>
391 392 %endif
392 393 %endif
393 394 %if filediff.patch['stats']['added']:
394 395 <span class="pill" op="added">+${filediff.patch['stats']['added']}</span>
395 396 %endif
396 397 %if filediff.patch['stats']['deleted']:
397 398 <span class="pill" op="deleted">-${filediff.patch['stats']['deleted']}</span>
398 399 %endif
399 400 </span>
400 401
401 402 </%def>
402 403
403 404 <%def name="nice_mode(filemode)">
404 405 ${filemode.startswith('100') and filemode[3:] or filemode}
405 406 </%def>
406 407
407 408 <%def name="diff_menu(filediff, use_comments=False)">
408 409 <div class="filediff-menu">
409 410 %if filediff.diffset.source_ref:
410 411 %if filediff.operation in ['D', 'M']:
411 412 <a
412 413 class="tooltip"
413 414 href="${h.route_path('repo_files',repo_name=filediff.diffset.repo_name,commit_id=filediff.diffset.source_ref,f_path=filediff.source_file_path)}"
414 415 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
415 416 >
416 417 ${_('Show file before')}
417 418 </a> |
418 419 %else:
419 420 <span
420 421 class="tooltip"
421 422 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.source_ref[:12]})}"
422 423 >
423 424 ${_('Show file before')}
424 425 </span> |
425 426 %endif
426 427 %if filediff.operation in ['A', 'M']:
427 428 <a
428 429 class="tooltip"
429 430 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)}"
430 431 title="${h.tooltip(_('Show file at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
431 432 >
432 433 ${_('Show file after')}
433 434 </a> |
434 435 %else:
435 436 <span
436 437 class="tooltip"
437 438 title="${h.tooltip(_('File no longer present at commit: %(commit_id)s') % {'commit_id': filediff.diffset.target_ref[:12]})}"
438 439 >
439 440 ${_('Show file after')}
440 441 </span> |
441 442 %endif
442 443 <a
443 444 class="tooltip"
444 445 title="${h.tooltip(_('Raw diff'))}"
445 446 href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='raw'))}"
446 447 >
447 448 ${_('Raw diff')}
448 449 </a> |
449 450 <a
450 451 class="tooltip"
451 452 title="${h.tooltip(_('Download diff'))}"
452 453 href="${h.route_path('repo_files_diff',repo_name=filediff.diffset.repo_name,f_path=filediff.target_file_path, _query=dict(diff2=filediff.diffset.target_ref,diff1=filediff.diffset.source_ref,diff='download'))}"
453 454 >
454 455 ${_('Download diff')}
455 456 </a>
456 457 % if use_comments:
457 458 |
458 459 % endif
459 460
460 461 ## TODO: dan: refactor ignorews_url and context_url into the diff renderer same as diffmode=unified/sideside. Also use ajax to load more context (by clicking hunks)
461 462 %if hasattr(c, 'ignorews_url'):
462 463 ${c.ignorews_url(request, h.FID('', filediff.patch['filename']))}
463 464 %endif
464 465 %if hasattr(c, 'context_url'):
465 466 ${c.context_url(request, h.FID('', filediff.patch['filename']))}
466 467 %endif
467 468
468 469 %if use_comments:
469 470 <a href="#" onclick="return Rhodecode.comments.toggleComments(this);">
470 471 <span class="show-comment-button">${_('Show comments')}</span><span class="hide-comment-button">${_('Hide comments')}</span>
471 472 </a>
472 473 %endif
473 474 %endif
474 475 </div>
475 476 </%def>
476 477
477 478
478 479 <%def name="inline_comments_container(comments)">
479 480 <div class="inline-comments">
480 481 %for comment in comments:
481 482 ${commentblock.comment_block(comment, inline=True)}
482 483 %endfor
483 484
484 485 % if comments and comments[-1].outdated:
485 486 <span class="btn btn-secondary cb-comment-add-button comment-outdated}"
486 487 style="display: none;}">
487 488 ${_('Add another comment')}
488 489 </span>
489 490 % else:
490 491 <span onclick="return Rhodecode.comments.createComment(this)"
491 492 class="btn btn-secondary cb-comment-add-button">
492 493 ${_('Add another comment')}
493 494 </span>
494 495 % endif
495 496
496 497 </div>
497 498 </%def>
498 499
499 500
500 501 <%def name="render_hunk_lines_sideside(hunk, use_comments=False)">
501 502 %for i, line in enumerate(hunk.sideside):
502 503 <%
503 504 old_line_anchor, new_line_anchor = None, None
504 505 if line.original.lineno:
505 506 old_line_anchor = diff_line_anchor(hunk.source_file_path, line.original.lineno, 'o')
506 507 if line.modified.lineno:
507 508 new_line_anchor = diff_line_anchor(hunk.target_file_path, line.modified.lineno, 'n')
508 509 %>
509 510
510 511 <tr class="cb-line">
511 512 <td class="cb-data ${action_class(line.original.action)}"
512 513 data-line-number="${line.original.lineno}"
513 514 >
514 515 <div>
515 516 %if line.original.comments:
516 517 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
517 518 %endif
518 519 </div>
519 520 </td>
520 521 <td class="cb-lineno ${action_class(line.original.action)}"
521 522 data-line-number="${line.original.lineno}"
522 523 %if old_line_anchor:
523 524 id="${old_line_anchor}"
524 525 %endif
525 526 >
526 527 %if line.original.lineno:
527 528 <a name="${old_line_anchor}" href="#${old_line_anchor}">${line.original.lineno}</a>
528 529 %endif
529 530 </td>
530 531 <td class="cb-content ${action_class(line.original.action)}"
531 532 data-line-number="o${line.original.lineno}"
532 533 >
533 534 %if use_comments and line.original.lineno:
534 535 ${render_add_comment_button()}
535 536 %endif
536 537 <span class="cb-code">${line.original.action} ${line.original.content or '' | n}</span>
537 538 %if use_comments and line.original.lineno and line.original.comments:
538 539 ${inline_comments_container(line.original.comments)}
539 540 %endif
540 541 </td>
541 542 <td class="cb-data ${action_class(line.modified.action)}"
542 543 data-line-number="${line.modified.lineno}"
543 544 >
544 545 <div>
545 546 %if line.modified.comments:
546 547 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
547 548 %endif
548 549 </div>
549 550 </td>
550 551 <td class="cb-lineno ${action_class(line.modified.action)}"
551 552 data-line-number="${line.modified.lineno}"
552 553 %if new_line_anchor:
553 554 id="${new_line_anchor}"
554 555 %endif
555 556 >
556 557 %if line.modified.lineno:
557 558 <a name="${new_line_anchor}" href="#${new_line_anchor}">${line.modified.lineno}</a>
558 559 %endif
559 560 </td>
560 561 <td class="cb-content ${action_class(line.modified.action)}"
561 562 data-line-number="n${line.modified.lineno}"
562 563 >
563 564 %if use_comments and line.modified.lineno:
564 565 ${render_add_comment_button()}
565 566 %endif
566 567 <span class="cb-code">${line.modified.action} ${line.modified.content or '' | n}</span>
567 568 %if use_comments and line.modified.lineno and line.modified.comments:
568 569 ${inline_comments_container(line.modified.comments)}
569 570 %endif
570 571 </td>
571 572 </tr>
572 573 %endfor
573 574 </%def>
574 575
575 576
576 577 <%def name="render_hunk_lines_unified(hunk, use_comments=False)">
577 578 %for old_line_no, new_line_no, action, content, comments in hunk.unified:
578 579 <%
579 580 old_line_anchor, new_line_anchor = None, None
580 581 if old_line_no:
581 582 old_line_anchor = diff_line_anchor(hunk.source_file_path, old_line_no, 'o')
582 583 if new_line_no:
583 584 new_line_anchor = diff_line_anchor(hunk.target_file_path, new_line_no, 'n')
584 585 %>
585 586 <tr class="cb-line">
586 587 <td class="cb-data ${action_class(action)}">
587 588 <div>
588 589 %if comments:
589 590 <i class="icon-comment" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
590 591 %endif
591 592 </div>
592 593 </td>
593 594 <td class="cb-lineno ${action_class(action)}"
594 595 data-line-number="${old_line_no}"
595 596 %if old_line_anchor:
596 597 id="${old_line_anchor}"
597 598 %endif
598 599 >
599 600 %if old_line_anchor:
600 601 <a name="${old_line_anchor}" href="#${old_line_anchor}">${old_line_no}</a>
601 602 %endif
602 603 </td>
603 604 <td class="cb-lineno ${action_class(action)}"
604 605 data-line-number="${new_line_no}"
605 606 %if new_line_anchor:
606 607 id="${new_line_anchor}"
607 608 %endif
608 609 >
609 610 %if new_line_anchor:
610 611 <a name="${new_line_anchor}" href="#${new_line_anchor}">${new_line_no}</a>
611 612 %endif
612 613 </td>
613 614 <td class="cb-content ${action_class(action)}"
614 615 data-line-number="${new_line_no and 'n' or 'o'}${new_line_no or old_line_no}"
615 616 >
616 617 %if use_comments:
617 618 ${render_add_comment_button()}
618 619 %endif
619 620 <span class="cb-code">${action} ${content or '' | n}</span>
620 621 %if use_comments and comments:
621 622 ${inline_comments_container(comments)}
622 623 %endif
623 624 </td>
624 625 </tr>
625 626 %endfor
626 627 </%def>
627 628
628 629 <%def name="render_add_comment_button()">
629 630 <button class="btn btn-small btn-primary cb-comment-box-opener" onclick="return Rhodecode.comments.createComment(this)">
630 631 <span><i class="icon-comment"></i></span>
631 632 </button>
632 633 </%def>
633 634
634 635 <%def name="render_diffset_menu()">
635 636
636 637 <div class="diffset-menu clearinner">
637 638 <div class="pull-right">
638 639 <div class="btn-group">
639 640
640 641 <a
641 642 class="btn ${c.diffmode == 'sideside' and 'btn-primary'} tooltip"
642 643 title="${h.tooltip(_('View side by side'))}"
643 644 href="${h.url_replace(diffmode='sideside')}">
644 645 <span>${_('Side by Side')}</span>
645 646 </a>
646 647 <a
647 648 class="btn ${c.diffmode == 'unified' and 'btn-primary'} tooltip"
648 649 title="${h.tooltip(_('View unified'))}" href="${h.url_replace(diffmode='unified')}">
649 650 <span>${_('Unified')}</span>
650 651 </a>
651 652 </div>
652 653 </div>
653 654
654 655 <div class="pull-left">
655 656 <div class="btn-group">
656 657 <a
657 658 class="btn"
658 659 href="#"
659 660 onclick="$('input[class=filediff-collapse-state]').prop('checked', false); return false">${_('Expand All Files')}</a>
660 661 <a
661 662 class="btn"
662 663 href="#"
663 664 onclick="$('input[class=filediff-collapse-state]').prop('checked', true); return false">${_('Collapse All Files')}</a>
664 665 <a
665 666 class="btn"
666 667 href="#"
667 668 onclick="return Rhodecode.comments.toggleWideMode(this)">${_('Wide Mode Diff')}</a>
668 669 </div>
669 670 </div>
670 671 </div>
671 672 </%def>
@@ -1,74 +1,74 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/debug_style/index.html"/>
3 3
4 4
5 5 <%def name="breadcrumbs_links()">
6 6 ${h.link_to(_('Style'), h.route_path('debug_style_home'))}
7 7 &raquo;
8 8 ${c.active}
9 9 </%def>
10 10
11 11
12 12 <%def name="real_main()">
13 13 <div class="box">
14 14 <div class="title">
15 15 ${self.breadcrumbs()}
16 16 </div>
17 17
18 18 ##main
19 19 <div class='sidebar-col-wrapper'>
20 20 ${self.sidebar()}
21 21
22 22 <div class="main-content">
23 23
24 24
25 25 <div class="bs-example pull-left">
26 26
27 27 <div id="quick_login">
28 28 <h4>${_('Sign in to your account')}</h4>
29 29
30 ${h.form(h.url('login_home',came_from=h.url.current()), needs_csrf_token=False)}
30 ${h.form(h.route_path('login'), needs_csrf_token=False)}
31 31 <div class="form form-vertical">
32 32 <div class="fields">
33 33
34 34 <div class="field">
35 35 <div class="label">
36 36 <label for="username">${_('Username')}:</label>
37 37 </div>
38 38 <div class="input">
39 39 ${h.text('username',class_='focus',tabindex=1)}
40 40 </div>
41 41 </div>
42 42
43 43 <div class="field">
44 44 <div class="label">
45 45 <label for="password">${_('Password')}:</label>
46 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.url('reset_password'))}</span>
46 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
47 47 </div>
48 48 <div class="input">
49 49 ${h.password('password',class_='focus',tabindex=2)}
50 50 </div>
51 51 </div>
52 52
53 53 <div class="buttons">
54 54 <div class="register">
55 55 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
56 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
56 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
57 57 %endif
58 58 </div>
59 59 <div class="submit">
60 60 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
61 61 </div>
62 62 </div>
63 63
64 64 </div>
65 65 </div>
66 66 ${h.end_form()}
67 67 </div>
68 68
69 69 </div>
70 70 </div>
71 71 </div>
72 72 </div>
73 73
74 74 </%def>
General Comments 0
You need to be logged in to leave comments. Login now