##// END OF EJS Templates
comments: fix extracing auth_user from the passed in objects. Before if auth_user is empty we could relly on INT or STR passed in
marcink -
r3026:71b96937 stable
parent child Browse files
Show More
@@ -1,662 +1,662 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2018 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 pyramid.threadlocal import get_current_registry, get_current_request
30 30 from sqlalchemy.sql.expression import null
31 31 from sqlalchemy.sql.functions import coalesce
32 32
33 33 from rhodecode.lib import helpers as h, diffs, channelstream
34 34 from rhodecode.lib import audit_logger
35 35 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str
36 36 from rhodecode.model import BaseModel
37 37 from rhodecode.model.db import (
38 38 ChangesetComment, User, Notification, PullRequest, AttributeDict)
39 39 from rhodecode.model.notification import NotificationModel
40 40 from rhodecode.model.meta import Session
41 41 from rhodecode.model.settings import VcsSettingsModel
42 42 from rhodecode.model.notification import EmailNotificationModel
43 43 from rhodecode.model.validation_schema.schemas import comment_schema
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class CommentsModel(BaseModel):
50 50
51 51 cls = ChangesetComment
52 52
53 53 DIFF_CONTEXT_BEFORE = 3
54 54 DIFF_CONTEXT_AFTER = 3
55 55
56 56 def __get_commit_comment(self, changeset_comment):
57 57 return self._get_instance(ChangesetComment, changeset_comment)
58 58
59 59 def __get_pull_request(self, pull_request):
60 60 return self._get_instance(PullRequest, pull_request)
61 61
62 62 def _extract_mentions(self, s):
63 63 user_objects = []
64 64 for username in extract_mentioned_users(s):
65 65 user_obj = User.get_by_username(username, case_insensitive=True)
66 66 if user_obj:
67 67 user_objects.append(user_obj)
68 68 return user_objects
69 69
70 70 def _get_renderer(self, global_renderer='rst', request=None):
71 71 request = request or get_current_request()
72 72
73 73 try:
74 74 global_renderer = request.call_context.visual.default_renderer
75 75 except AttributeError:
76 76 log.debug("Renderer not set, falling back "
77 77 "to default renderer '%s'", global_renderer)
78 78 except Exception:
79 79 log.error(traceback.format_exc())
80 80 return global_renderer
81 81
82 82 def aggregate_comments(self, comments, versions, show_version, inline=False):
83 83 # group by versions, and count until, and display objects
84 84
85 85 comment_groups = collections.defaultdict(list)
86 86 [comment_groups[
87 87 _co.pull_request_version_id].append(_co) for _co in comments]
88 88
89 89 def yield_comments(pos):
90 90 for co in comment_groups[pos]:
91 91 yield co
92 92
93 93 comment_versions = collections.defaultdict(
94 94 lambda: collections.defaultdict(list))
95 95 prev_prvid = -1
96 96 # fake last entry with None, to aggregate on "latest" version which
97 97 # doesn't have an pull_request_version_id
98 98 for ver in versions + [AttributeDict({'pull_request_version_id': None})]:
99 99 prvid = ver.pull_request_version_id
100 100 if prev_prvid == -1:
101 101 prev_prvid = prvid
102 102
103 103 for co in yield_comments(prvid):
104 104 comment_versions[prvid]['at'].append(co)
105 105
106 106 # save until
107 107 current = comment_versions[prvid]['at']
108 108 prev_until = comment_versions[prev_prvid]['until']
109 109 cur_until = prev_until + current
110 110 comment_versions[prvid]['until'].extend(cur_until)
111 111
112 112 # save outdated
113 113 if inline:
114 114 outdated = [x for x in cur_until
115 115 if x.outdated_at_version(show_version)]
116 116 else:
117 117 outdated = [x for x in cur_until
118 118 if x.older_than_version(show_version)]
119 119 display = [x for x in cur_until if x not in outdated]
120 120
121 121 comment_versions[prvid]['outdated'] = outdated
122 122 comment_versions[prvid]['display'] = display
123 123
124 124 prev_prvid = prvid
125 125
126 126 return comment_versions
127 127
128 128 def get_unresolved_todos(self, pull_request, show_outdated=True):
129 129
130 130 todos = Session().query(ChangesetComment) \
131 131 .filter(ChangesetComment.pull_request == pull_request) \
132 132 .filter(ChangesetComment.resolved_by == None) \
133 133 .filter(ChangesetComment.comment_type
134 134 == ChangesetComment.COMMENT_TYPE_TODO)
135 135
136 136 if not show_outdated:
137 137 todos = todos.filter(
138 138 coalesce(ChangesetComment.display_state, '') !=
139 139 ChangesetComment.COMMENT_OUTDATED)
140 140
141 141 todos = todos.all()
142 142
143 143 return todos
144 144
145 145 def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
146 146
147 147 todos = Session().query(ChangesetComment) \
148 148 .filter(ChangesetComment.revision == commit_id) \
149 149 .filter(ChangesetComment.resolved_by == None) \
150 150 .filter(ChangesetComment.comment_type
151 151 == ChangesetComment.COMMENT_TYPE_TODO)
152 152
153 153 if not show_outdated:
154 154 todos = todos.filter(
155 155 coalesce(ChangesetComment.display_state, '') !=
156 156 ChangesetComment.COMMENT_OUTDATED)
157 157
158 158 todos = todos.all()
159 159
160 160 return todos
161 161
162 162 def _log_audit_action(self, action, action_data, auth_user, comment):
163 163 audit_logger.store(
164 164 action=action,
165 165 action_data=action_data,
166 166 user=auth_user,
167 167 repo=comment.repo)
168 168
169 169 def create(self, text, repo, user, commit_id=None, pull_request=None,
170 170 f_path=None, line_no=None, status_change=None,
171 171 status_change_type=None, comment_type=None,
172 172 resolves_comment_id=None, closing_pr=False, send_email=True,
173 173 renderer=None, auth_user=None):
174 174 """
175 175 Creates new comment for commit or pull request.
176 176 IF status_change is not none this comment is associated with a
177 177 status change of commit or commit associated with pull request
178 178
179 179 :param text:
180 180 :param repo:
181 181 :param user:
182 182 :param commit_id:
183 183 :param pull_request:
184 184 :param f_path:
185 185 :param line_no:
186 186 :param status_change: Label for status change
187 187 :param comment_type: Type of comment
188 188 :param status_change_type: type of status change
189 189 :param closing_pr:
190 190 :param send_email:
191 191 :param renderer: pick renderer for this comment
192 192 """
193 193
194 auth_user = auth_user or user
195 194 if not text:
196 195 log.warning('Missing text for comment, skipping...')
197 196 return
198 197 request = get_current_request()
199 198 _ = request.translate
200 199
201 200 if not renderer:
202 201 renderer = self._get_renderer(request=request)
203 202
204 203 repo = self._get_repo(repo)
205 204 user = self._get_user(user)
205 auth_user = auth_user or user
206 206
207 207 schema = comment_schema.CommentSchema()
208 208 validated_kwargs = schema.deserialize(dict(
209 209 comment_body=text,
210 210 comment_type=comment_type,
211 211 comment_file=f_path,
212 212 comment_line=line_no,
213 213 renderer_type=renderer,
214 214 status_change=status_change_type,
215 215 resolves_comment_id=resolves_comment_id,
216 216 repo=repo.repo_id,
217 217 user=user.user_id,
218 218 ))
219 219
220 220 comment = ChangesetComment()
221 221 comment.renderer = validated_kwargs['renderer_type']
222 222 comment.text = validated_kwargs['comment_body']
223 223 comment.f_path = validated_kwargs['comment_file']
224 224 comment.line_no = validated_kwargs['comment_line']
225 225 comment.comment_type = validated_kwargs['comment_type']
226 226
227 227 comment.repo = repo
228 228 comment.author = user
229 229 resolved_comment = self.__get_commit_comment(
230 230 validated_kwargs['resolves_comment_id'])
231 231 # check if the comment actually belongs to this PR
232 232 if resolved_comment and resolved_comment.pull_request and \
233 233 resolved_comment.pull_request != pull_request:
234 234 # comment not bound to this pull request, forbid
235 235 resolved_comment = None
236 236 comment.resolved_comment = resolved_comment
237 237
238 238 pull_request_id = pull_request
239 239
240 240 commit_obj = None
241 241 pull_request_obj = None
242 242
243 243 if commit_id:
244 244 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
245 245 # do a lookup, so we don't pass something bad here
246 246 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
247 247 comment.revision = commit_obj.raw_id
248 248
249 249 elif pull_request_id:
250 250 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
251 251 pull_request_obj = self.__get_pull_request(pull_request_id)
252 252 comment.pull_request = pull_request_obj
253 253 else:
254 254 raise Exception('Please specify commit or pull_request_id')
255 255
256 256 Session().add(comment)
257 257 Session().flush()
258 258 kwargs = {
259 259 'user': user,
260 260 'renderer_type': renderer,
261 261 'repo_name': repo.repo_name,
262 262 'status_change': status_change,
263 263 'status_change_type': status_change_type,
264 264 'comment_body': text,
265 265 'comment_file': f_path,
266 266 'comment_line': line_no,
267 267 'comment_type': comment_type or 'note'
268 268 }
269 269
270 270 if commit_obj:
271 271 recipients = ChangesetComment.get_users(
272 272 revision=commit_obj.raw_id)
273 273 # add commit author if it's in RhodeCode system
274 274 cs_author = User.get_from_cs_author(commit_obj.author)
275 275 if not cs_author:
276 276 # use repo owner if we cannot extract the author correctly
277 277 cs_author = repo.user
278 278 recipients += [cs_author]
279 279
280 280 commit_comment_url = self.get_url(comment, request=request)
281 281
282 282 target_repo_url = h.link_to(
283 283 repo.repo_name,
284 284 h.route_url('repo_summary', repo_name=repo.repo_name))
285 285
286 286 # commit specifics
287 287 kwargs.update({
288 288 'commit': commit_obj,
289 289 'commit_message': commit_obj.message,
290 290 'commit_target_repo': target_repo_url,
291 291 'commit_comment_url': commit_comment_url,
292 292 })
293 293
294 294 elif pull_request_obj:
295 295 # get the current participants of this pull request
296 296 recipients = ChangesetComment.get_users(
297 297 pull_request_id=pull_request_obj.pull_request_id)
298 298 # add pull request author
299 299 recipients += [pull_request_obj.author]
300 300
301 301 # add the reviewers to notification
302 302 recipients += [x.user for x in pull_request_obj.reviewers]
303 303
304 304 pr_target_repo = pull_request_obj.target_repo
305 305 pr_source_repo = pull_request_obj.source_repo
306 306
307 307 pr_comment_url = h.route_url(
308 308 'pullrequest_show',
309 309 repo_name=pr_target_repo.repo_name,
310 310 pull_request_id=pull_request_obj.pull_request_id,
311 311 _anchor='comment-%s' % comment.comment_id)
312 312
313 313 # set some variables for email notification
314 314 pr_target_repo_url = h.route_url(
315 315 'repo_summary', repo_name=pr_target_repo.repo_name)
316 316
317 317 pr_source_repo_url = h.route_url(
318 318 'repo_summary', repo_name=pr_source_repo.repo_name)
319 319
320 320 # pull request specifics
321 321 kwargs.update({
322 322 'pull_request': pull_request_obj,
323 323 'pr_id': pull_request_obj.pull_request_id,
324 324 'pr_target_repo': pr_target_repo,
325 325 'pr_target_repo_url': pr_target_repo_url,
326 326 'pr_source_repo': pr_source_repo,
327 327 'pr_source_repo_url': pr_source_repo_url,
328 328 'pr_comment_url': pr_comment_url,
329 329 'pr_closing': closing_pr,
330 330 })
331 331 if send_email:
332 332 # pre-generate the subject for notification itself
333 333 (subject,
334 334 _h, _e, # we don't care about those
335 335 body_plaintext) = EmailNotificationModel().render_email(
336 336 notification_type, **kwargs)
337 337
338 338 mention_recipients = set(
339 339 self._extract_mentions(text)).difference(recipients)
340 340
341 341 # create notification objects, and emails
342 342 NotificationModel().create(
343 343 created_by=user,
344 344 notification_subject=subject,
345 345 notification_body=body_plaintext,
346 346 notification_type=notification_type,
347 347 recipients=recipients,
348 348 mention_recipients=mention_recipients,
349 349 email_kwargs=kwargs,
350 350 )
351 351
352 352 Session().flush()
353 353 if comment.pull_request:
354 354 action = 'repo.pull_request.comment.create'
355 355 else:
356 356 action = 'repo.commit.comment.create'
357 357
358 358 comment_data = comment.get_api_data()
359 359 self._log_audit_action(
360 360 action, {'data': comment_data}, auth_user, comment)
361 361
362 362 msg_url = ''
363 363 channel = None
364 364 if commit_obj:
365 365 msg_url = commit_comment_url
366 366 repo_name = repo.repo_name
367 367 channel = u'/repo${}$/commit/{}'.format(
368 368 repo_name,
369 369 commit_obj.raw_id
370 370 )
371 371 elif pull_request_obj:
372 372 msg_url = pr_comment_url
373 373 repo_name = pr_target_repo.repo_name
374 374 channel = u'/repo${}$/pr/{}'.format(
375 375 repo_name,
376 376 pull_request_id
377 377 )
378 378
379 379 message = '<strong>{}</strong> {} - ' \
380 380 '<a onclick="window.location=\'{}\';' \
381 381 'window.location.reload()">' \
382 382 '<strong>{}</strong></a>'
383 383 message = message.format(
384 384 user.username, _('made a comment'), msg_url,
385 385 _('Show it now'))
386 386
387 387 channelstream.post_message(
388 388 channel, message, user.username,
389 389 registry=get_current_registry())
390 390
391 391 return comment
392 392
393 393 def delete(self, comment, auth_user):
394 394 """
395 395 Deletes given comment
396 396 """
397 397 comment = self.__get_commit_comment(comment)
398 398 old_data = comment.get_api_data()
399 399 Session().delete(comment)
400 400
401 401 if comment.pull_request:
402 402 action = 'repo.pull_request.comment.delete'
403 403 else:
404 404 action = 'repo.commit.comment.delete'
405 405
406 406 self._log_audit_action(
407 407 action, {'old_data': old_data}, auth_user, comment)
408 408
409 409 return comment
410 410
411 411 def get_all_comments(self, repo_id, revision=None, pull_request=None):
412 412 q = ChangesetComment.query()\
413 413 .filter(ChangesetComment.repo_id == repo_id)
414 414 if revision:
415 415 q = q.filter(ChangesetComment.revision == revision)
416 416 elif pull_request:
417 417 pull_request = self.__get_pull_request(pull_request)
418 418 q = q.filter(ChangesetComment.pull_request == pull_request)
419 419 else:
420 420 raise Exception('Please specify commit or pull_request')
421 421 q = q.order_by(ChangesetComment.created_on)
422 422 return q.all()
423 423
424 424 def get_url(self, comment, request=None, permalink=False):
425 425 if not request:
426 426 request = get_current_request()
427 427
428 428 comment = self.__get_commit_comment(comment)
429 429 if comment.pull_request:
430 430 pull_request = comment.pull_request
431 431 if permalink:
432 432 return request.route_url(
433 433 'pull_requests_global',
434 434 pull_request_id=pull_request.pull_request_id,
435 435 _anchor='comment-%s' % comment.comment_id)
436 436 else:
437 437 return request.route_url(
438 438 'pullrequest_show',
439 439 repo_name=safe_str(pull_request.target_repo.repo_name),
440 440 pull_request_id=pull_request.pull_request_id,
441 441 _anchor='comment-%s' % comment.comment_id)
442 442
443 443 else:
444 444 repo = comment.repo
445 445 commit_id = comment.revision
446 446
447 447 if permalink:
448 448 return request.route_url(
449 449 'repo_commit', repo_name=safe_str(repo.repo_id),
450 450 commit_id=commit_id,
451 451 _anchor='comment-%s' % comment.comment_id)
452 452
453 453 else:
454 454 return request.route_url(
455 455 'repo_commit', repo_name=safe_str(repo.repo_name),
456 456 commit_id=commit_id,
457 457 _anchor='comment-%s' % comment.comment_id)
458 458
459 459 def get_comments(self, repo_id, revision=None, pull_request=None):
460 460 """
461 461 Gets main comments based on revision or pull_request_id
462 462
463 463 :param repo_id:
464 464 :param revision:
465 465 :param pull_request:
466 466 """
467 467
468 468 q = ChangesetComment.query()\
469 469 .filter(ChangesetComment.repo_id == repo_id)\
470 470 .filter(ChangesetComment.line_no == None)\
471 471 .filter(ChangesetComment.f_path == None)
472 472 if revision:
473 473 q = q.filter(ChangesetComment.revision == revision)
474 474 elif pull_request:
475 475 pull_request = self.__get_pull_request(pull_request)
476 476 q = q.filter(ChangesetComment.pull_request == pull_request)
477 477 else:
478 478 raise Exception('Please specify commit or pull_request')
479 479 q = q.order_by(ChangesetComment.created_on)
480 480 return q.all()
481 481
482 482 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
483 483 q = self._get_inline_comments_query(repo_id, revision, pull_request)
484 484 return self._group_comments_by_path_and_line_number(q)
485 485
486 486 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
487 487 version=None):
488 488 inline_cnt = 0
489 489 for fname, per_line_comments in inline_comments.iteritems():
490 490 for lno, comments in per_line_comments.iteritems():
491 491 for comm in comments:
492 492 if not comm.outdated_at_version(version) and skip_outdated:
493 493 inline_cnt += 1
494 494
495 495 return inline_cnt
496 496
497 497 def get_outdated_comments(self, repo_id, pull_request):
498 498 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
499 499 # of a pull request.
500 500 q = self._all_inline_comments_of_pull_request(pull_request)
501 501 q = q.filter(
502 502 ChangesetComment.display_state ==
503 503 ChangesetComment.COMMENT_OUTDATED
504 504 ).order_by(ChangesetComment.comment_id.asc())
505 505
506 506 return self._group_comments_by_path_and_line_number(q)
507 507
508 508 def _get_inline_comments_query(self, repo_id, revision, pull_request):
509 509 # TODO: johbo: Split this into two methods: One for PR and one for
510 510 # commit.
511 511 if revision:
512 512 q = Session().query(ChangesetComment).filter(
513 513 ChangesetComment.repo_id == repo_id,
514 514 ChangesetComment.line_no != null(),
515 515 ChangesetComment.f_path != null(),
516 516 ChangesetComment.revision == revision)
517 517
518 518 elif pull_request:
519 519 pull_request = self.__get_pull_request(pull_request)
520 520 if not CommentsModel.use_outdated_comments(pull_request):
521 521 q = self._visible_inline_comments_of_pull_request(pull_request)
522 522 else:
523 523 q = self._all_inline_comments_of_pull_request(pull_request)
524 524
525 525 else:
526 526 raise Exception('Please specify commit or pull_request_id')
527 527 q = q.order_by(ChangesetComment.comment_id.asc())
528 528 return q
529 529
530 530 def _group_comments_by_path_and_line_number(self, q):
531 531 comments = q.all()
532 532 paths = collections.defaultdict(lambda: collections.defaultdict(list))
533 533 for co in comments:
534 534 paths[co.f_path][co.line_no].append(co)
535 535 return paths
536 536
537 537 @classmethod
538 538 def needed_extra_diff_context(cls):
539 539 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
540 540
541 541 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
542 542 if not CommentsModel.use_outdated_comments(pull_request):
543 543 return
544 544
545 545 comments = self._visible_inline_comments_of_pull_request(pull_request)
546 546 comments_to_outdate = comments.all()
547 547
548 548 for comment in comments_to_outdate:
549 549 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
550 550
551 551 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
552 552 diff_line = _parse_comment_line_number(comment.line_no)
553 553
554 554 try:
555 555 old_context = old_diff_proc.get_context_of_line(
556 556 path=comment.f_path, diff_line=diff_line)
557 557 new_context = new_diff_proc.get_context_of_line(
558 558 path=comment.f_path, diff_line=diff_line)
559 559 except (diffs.LineNotInDiffException,
560 560 diffs.FileNotInDiffException):
561 561 comment.display_state = ChangesetComment.COMMENT_OUTDATED
562 562 return
563 563
564 564 if old_context == new_context:
565 565 return
566 566
567 567 if self._should_relocate_diff_line(diff_line):
568 568 new_diff_lines = new_diff_proc.find_context(
569 569 path=comment.f_path, context=old_context,
570 570 offset=self.DIFF_CONTEXT_BEFORE)
571 571 if not new_diff_lines:
572 572 comment.display_state = ChangesetComment.COMMENT_OUTDATED
573 573 else:
574 574 new_diff_line = self._choose_closest_diff_line(
575 575 diff_line, new_diff_lines)
576 576 comment.line_no = _diff_to_comment_line_number(new_diff_line)
577 577 else:
578 578 comment.display_state = ChangesetComment.COMMENT_OUTDATED
579 579
580 580 def _should_relocate_diff_line(self, diff_line):
581 581 """
582 582 Checks if relocation shall be tried for the given `diff_line`.
583 583
584 584 If a comment points into the first lines, then we can have a situation
585 585 that after an update another line has been added on top. In this case
586 586 we would find the context still and move the comment around. This
587 587 would be wrong.
588 588 """
589 589 should_relocate = (
590 590 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
591 591 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
592 592 return should_relocate
593 593
594 594 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
595 595 candidate = new_diff_lines[0]
596 596 best_delta = _diff_line_delta(diff_line, candidate)
597 597 for new_diff_line in new_diff_lines[1:]:
598 598 delta = _diff_line_delta(diff_line, new_diff_line)
599 599 if delta < best_delta:
600 600 candidate = new_diff_line
601 601 best_delta = delta
602 602 return candidate
603 603
604 604 def _visible_inline_comments_of_pull_request(self, pull_request):
605 605 comments = self._all_inline_comments_of_pull_request(pull_request)
606 606 comments = comments.filter(
607 607 coalesce(ChangesetComment.display_state, '') !=
608 608 ChangesetComment.COMMENT_OUTDATED)
609 609 return comments
610 610
611 611 def _all_inline_comments_of_pull_request(self, pull_request):
612 612 comments = Session().query(ChangesetComment)\
613 613 .filter(ChangesetComment.line_no != None)\
614 614 .filter(ChangesetComment.f_path != None)\
615 615 .filter(ChangesetComment.pull_request == pull_request)
616 616 return comments
617 617
618 618 def _all_general_comments_of_pull_request(self, pull_request):
619 619 comments = Session().query(ChangesetComment)\
620 620 .filter(ChangesetComment.line_no == None)\
621 621 .filter(ChangesetComment.f_path == None)\
622 622 .filter(ChangesetComment.pull_request == pull_request)
623 623 return comments
624 624
625 625 @staticmethod
626 626 def use_outdated_comments(pull_request):
627 627 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
628 628 settings = settings_model.get_general_settings()
629 629 return settings.get('rhodecode_use_outdated_comments', False)
630 630
631 631
632 632 def _parse_comment_line_number(line_no):
633 633 """
634 634 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
635 635 """
636 636 old_line = None
637 637 new_line = None
638 638 if line_no.startswith('o'):
639 639 old_line = int(line_no[1:])
640 640 elif line_no.startswith('n'):
641 641 new_line = int(line_no[1:])
642 642 else:
643 643 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
644 644 return diffs.DiffLineNumber(old_line, new_line)
645 645
646 646
647 647 def _diff_to_comment_line_number(diff_line):
648 648 if diff_line.new is not None:
649 649 return u'n{}'.format(diff_line.new)
650 650 elif diff_line.old is not None:
651 651 return u'o{}'.format(diff_line.old)
652 652 return u''
653 653
654 654
655 655 def _diff_line_delta(a, b):
656 656 if None not in (a.new, b.new):
657 657 return abs(a.new - b.new)
658 658 elif None not in (a.old, b.old):
659 659 return abs(a.old - b.old)
660 660 else:
661 661 raise ValueError(
662 662 "Cannot compute delta between {} and {}".format(a, b))
General Comments 0
You need to be logged in to leave comments. Login now