##// END OF EJS Templates
pull-request: verify resolve TODO comment needs to be bound to the same PR as we're calling
marcink -
r2441:9a41d4d3 default
parent child Browse files
Show More
@@ -1,653 +1,659 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 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, user, comment):
163 163 audit_logger.store(
164 164 action=action,
165 165 action_data=action_data,
166 166 user=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):
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 if not text:
194 194 log.warning('Missing text for comment, skipping...')
195 195 return
196 196 request = get_current_request()
197 197 _ = request.translate
198 198
199 199 if not renderer:
200 200 renderer = self._get_renderer(request=request)
201 201
202 202 repo = self._get_repo(repo)
203 203 user = self._get_user(user)
204 204
205 205 schema = comment_schema.CommentSchema()
206 206 validated_kwargs = schema.deserialize(dict(
207 207 comment_body=text,
208 208 comment_type=comment_type,
209 209 comment_file=f_path,
210 210 comment_line=line_no,
211 211 renderer_type=renderer,
212 212 status_change=status_change_type,
213 213 resolves_comment_id=resolves_comment_id,
214 214 repo=repo.repo_id,
215 215 user=user.user_id,
216 216 ))
217 217
218 218 comment = ChangesetComment()
219 219 comment.renderer = validated_kwargs['renderer_type']
220 220 comment.text = validated_kwargs['comment_body']
221 221 comment.f_path = validated_kwargs['comment_file']
222 222 comment.line_no = validated_kwargs['comment_line']
223 223 comment.comment_type = validated_kwargs['comment_type']
224 224
225 225 comment.repo = repo
226 226 comment.author = user
227 comment.resolved_comment = self.__get_commit_comment(
227 resolved_comment = self.__get_commit_comment(
228 228 validated_kwargs['resolves_comment_id'])
229 # check if the comment actually belongs to this PR
230 if resolved_comment and resolved_comment.pull_request and \
231 resolved_comment.pull_request != pull_request:
232 # comment not bound to this pull request, forbid
233 resolved_comment = None
234 comment.resolved_comment = resolved_comment
229 235
230 236 pull_request_id = pull_request
231 237
232 238 commit_obj = None
233 239 pull_request_obj = None
234 240
235 241 if commit_id:
236 242 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
237 243 # do a lookup, so we don't pass something bad here
238 244 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
239 245 comment.revision = commit_obj.raw_id
240 246
241 247 elif pull_request_id:
242 248 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
243 249 pull_request_obj = self.__get_pull_request(pull_request_id)
244 250 comment.pull_request = pull_request_obj
245 251 else:
246 252 raise Exception('Please specify commit or pull_request_id')
247 253
248 254 Session().add(comment)
249 255 Session().flush()
250 256 kwargs = {
251 257 'user': user,
252 258 'renderer_type': renderer,
253 259 'repo_name': repo.repo_name,
254 260 'status_change': status_change,
255 261 'status_change_type': status_change_type,
256 262 'comment_body': text,
257 263 'comment_file': f_path,
258 264 'comment_line': line_no,
259 265 'comment_type': comment_type or 'note'
260 266 }
261 267
262 268 if commit_obj:
263 269 recipients = ChangesetComment.get_users(
264 270 revision=commit_obj.raw_id)
265 271 # add commit author if it's in RhodeCode system
266 272 cs_author = User.get_from_cs_author(commit_obj.author)
267 273 if not cs_author:
268 274 # use repo owner if we cannot extract the author correctly
269 275 cs_author = repo.user
270 276 recipients += [cs_author]
271 277
272 278 commit_comment_url = self.get_url(comment, request=request)
273 279
274 280 target_repo_url = h.link_to(
275 281 repo.repo_name,
276 282 h.route_url('repo_summary', repo_name=repo.repo_name))
277 283
278 284 # commit specifics
279 285 kwargs.update({
280 286 'commit': commit_obj,
281 287 'commit_message': commit_obj.message,
282 288 'commit_target_repo': target_repo_url,
283 289 'commit_comment_url': commit_comment_url,
284 290 })
285 291
286 292 elif pull_request_obj:
287 293 # get the current participants of this pull request
288 294 recipients = ChangesetComment.get_users(
289 295 pull_request_id=pull_request_obj.pull_request_id)
290 296 # add pull request author
291 297 recipients += [pull_request_obj.author]
292 298
293 299 # add the reviewers to notification
294 300 recipients += [x.user for x in pull_request_obj.reviewers]
295 301
296 302 pr_target_repo = pull_request_obj.target_repo
297 303 pr_source_repo = pull_request_obj.source_repo
298 304
299 305 pr_comment_url = h.route_url(
300 306 'pullrequest_show',
301 307 repo_name=pr_target_repo.repo_name,
302 308 pull_request_id=pull_request_obj.pull_request_id,
303 309 _anchor='comment-%s' % comment.comment_id)
304 310
305 311 # set some variables for email notification
306 312 pr_target_repo_url = h.route_url(
307 313 'repo_summary', repo_name=pr_target_repo.repo_name)
308 314
309 315 pr_source_repo_url = h.route_url(
310 316 'repo_summary', repo_name=pr_source_repo.repo_name)
311 317
312 318 # pull request specifics
313 319 kwargs.update({
314 320 'pull_request': pull_request_obj,
315 321 'pr_id': pull_request_obj.pull_request_id,
316 322 'pr_target_repo': pr_target_repo,
317 323 'pr_target_repo_url': pr_target_repo_url,
318 324 'pr_source_repo': pr_source_repo,
319 325 'pr_source_repo_url': pr_source_repo_url,
320 326 'pr_comment_url': pr_comment_url,
321 327 'pr_closing': closing_pr,
322 328 })
323 329 if send_email:
324 330 # pre-generate the subject for notification itself
325 331 (subject,
326 332 _h, _e, # we don't care about those
327 333 body_plaintext) = EmailNotificationModel().render_email(
328 334 notification_type, **kwargs)
329 335
330 336 mention_recipients = set(
331 337 self._extract_mentions(text)).difference(recipients)
332 338
333 339 # create notification objects, and emails
334 340 NotificationModel().create(
335 341 created_by=user,
336 342 notification_subject=subject,
337 343 notification_body=body_plaintext,
338 344 notification_type=notification_type,
339 345 recipients=recipients,
340 346 mention_recipients=mention_recipients,
341 347 email_kwargs=kwargs,
342 348 )
343 349
344 350 Session().flush()
345 351 if comment.pull_request:
346 352 action = 'repo.pull_request.comment.create'
347 353 else:
348 354 action = 'repo.commit.comment.create'
349 355
350 356 comment_data = comment.get_api_data()
351 357 self._log_audit_action(
352 358 action, {'data': comment_data}, user, comment)
353 359
354 360 msg_url = ''
355 361 channel = None
356 362 if commit_obj:
357 363 msg_url = commit_comment_url
358 364 repo_name = repo.repo_name
359 365 channel = u'/repo${}$/commit/{}'.format(
360 366 repo_name,
361 367 commit_obj.raw_id
362 368 )
363 369 elif pull_request_obj:
364 370 msg_url = pr_comment_url
365 371 repo_name = pr_target_repo.repo_name
366 372 channel = u'/repo${}$/pr/{}'.format(
367 373 repo_name,
368 374 pull_request_id
369 375 )
370 376
371 377 message = '<strong>{}</strong> {} - ' \
372 378 '<a onclick="window.location=\'{}\';' \
373 379 'window.location.reload()">' \
374 380 '<strong>{}</strong></a>'
375 381 message = message.format(
376 382 user.username, _('made a comment'), msg_url,
377 383 _('Show it now'))
378 384
379 385 channelstream.post_message(
380 386 channel, message, user.username,
381 387 registry=get_current_registry())
382 388
383 389 return comment
384 390
385 391 def delete(self, comment, user):
386 392 """
387 393 Deletes given comment
388 394 """
389 395 comment = self.__get_commit_comment(comment)
390 396 old_data = comment.get_api_data()
391 397 Session().delete(comment)
392 398
393 399 if comment.pull_request:
394 400 action = 'repo.pull_request.comment.delete'
395 401 else:
396 402 action = 'repo.commit.comment.delete'
397 403
398 404 self._log_audit_action(
399 405 action, {'old_data': old_data}, user, comment)
400 406
401 407 return comment
402 408
403 409 def get_all_comments(self, repo_id, revision=None, pull_request=None):
404 410 q = ChangesetComment.query()\
405 411 .filter(ChangesetComment.repo_id == repo_id)
406 412 if revision:
407 413 q = q.filter(ChangesetComment.revision == revision)
408 414 elif pull_request:
409 415 pull_request = self.__get_pull_request(pull_request)
410 416 q = q.filter(ChangesetComment.pull_request == pull_request)
411 417 else:
412 418 raise Exception('Please specify commit or pull_request')
413 419 q = q.order_by(ChangesetComment.created_on)
414 420 return q.all()
415 421
416 422 def get_url(self, comment, request=None, permalink=False):
417 423 if not request:
418 424 request = get_current_request()
419 425
420 426 comment = self.__get_commit_comment(comment)
421 427 if comment.pull_request:
422 428 pull_request = comment.pull_request
423 429 if permalink:
424 430 return request.route_url(
425 431 'pull_requests_global',
426 432 pull_request_id=pull_request.pull_request_id,
427 433 _anchor='comment-%s' % comment.comment_id)
428 434 else:
429 435 return request.route_url('pullrequest_show',
430 436 repo_name=safe_str(pull_request.target_repo.repo_name),
431 437 pull_request_id=pull_request.pull_request_id,
432 438 _anchor='comment-%s' % comment.comment_id)
433 439
434 440 else:
435 441 repo = comment.repo
436 442 commit_id = comment.revision
437 443
438 444 if permalink:
439 445 return request.route_url(
440 446 'repo_commit', repo_name=safe_str(repo.repo_id),
441 447 commit_id=commit_id,
442 448 _anchor='comment-%s' % comment.comment_id)
443 449
444 450 else:
445 451 return request.route_url(
446 452 'repo_commit', repo_name=safe_str(repo.repo_name),
447 453 commit_id=commit_id,
448 454 _anchor='comment-%s' % comment.comment_id)
449 455
450 456 def get_comments(self, repo_id, revision=None, pull_request=None):
451 457 """
452 458 Gets main comments based on revision or pull_request_id
453 459
454 460 :param repo_id:
455 461 :param revision:
456 462 :param pull_request:
457 463 """
458 464
459 465 q = ChangesetComment.query()\
460 466 .filter(ChangesetComment.repo_id == repo_id)\
461 467 .filter(ChangesetComment.line_no == None)\
462 468 .filter(ChangesetComment.f_path == None)
463 469 if revision:
464 470 q = q.filter(ChangesetComment.revision == revision)
465 471 elif pull_request:
466 472 pull_request = self.__get_pull_request(pull_request)
467 473 q = q.filter(ChangesetComment.pull_request == pull_request)
468 474 else:
469 475 raise Exception('Please specify commit or pull_request')
470 476 q = q.order_by(ChangesetComment.created_on)
471 477 return q.all()
472 478
473 479 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
474 480 q = self._get_inline_comments_query(repo_id, revision, pull_request)
475 481 return self._group_comments_by_path_and_line_number(q)
476 482
477 483 def get_inline_comments_count(self, inline_comments, skip_outdated=True,
478 484 version=None):
479 485 inline_cnt = 0
480 486 for fname, per_line_comments in inline_comments.iteritems():
481 487 for lno, comments in per_line_comments.iteritems():
482 488 for comm in comments:
483 489 if not comm.outdated_at_version(version) and skip_outdated:
484 490 inline_cnt += 1
485 491
486 492 return inline_cnt
487 493
488 494 def get_outdated_comments(self, repo_id, pull_request):
489 495 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
490 496 # of a pull request.
491 497 q = self._all_inline_comments_of_pull_request(pull_request)
492 498 q = q.filter(
493 499 ChangesetComment.display_state ==
494 500 ChangesetComment.COMMENT_OUTDATED
495 501 ).order_by(ChangesetComment.comment_id.asc())
496 502
497 503 return self._group_comments_by_path_and_line_number(q)
498 504
499 505 def _get_inline_comments_query(self, repo_id, revision, pull_request):
500 506 # TODO: johbo: Split this into two methods: One for PR and one for
501 507 # commit.
502 508 if revision:
503 509 q = Session().query(ChangesetComment).filter(
504 510 ChangesetComment.repo_id == repo_id,
505 511 ChangesetComment.line_no != null(),
506 512 ChangesetComment.f_path != null(),
507 513 ChangesetComment.revision == revision)
508 514
509 515 elif pull_request:
510 516 pull_request = self.__get_pull_request(pull_request)
511 517 if not CommentsModel.use_outdated_comments(pull_request):
512 518 q = self._visible_inline_comments_of_pull_request(pull_request)
513 519 else:
514 520 q = self._all_inline_comments_of_pull_request(pull_request)
515 521
516 522 else:
517 523 raise Exception('Please specify commit or pull_request_id')
518 524 q = q.order_by(ChangesetComment.comment_id.asc())
519 525 return q
520 526
521 527 def _group_comments_by_path_and_line_number(self, q):
522 528 comments = q.all()
523 529 paths = collections.defaultdict(lambda: collections.defaultdict(list))
524 530 for co in comments:
525 531 paths[co.f_path][co.line_no].append(co)
526 532 return paths
527 533
528 534 @classmethod
529 535 def needed_extra_diff_context(cls):
530 536 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
531 537
532 538 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
533 539 if not CommentsModel.use_outdated_comments(pull_request):
534 540 return
535 541
536 542 comments = self._visible_inline_comments_of_pull_request(pull_request)
537 543 comments_to_outdate = comments.all()
538 544
539 545 for comment in comments_to_outdate:
540 546 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
541 547
542 548 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
543 549 diff_line = _parse_comment_line_number(comment.line_no)
544 550
545 551 try:
546 552 old_context = old_diff_proc.get_context_of_line(
547 553 path=comment.f_path, diff_line=diff_line)
548 554 new_context = new_diff_proc.get_context_of_line(
549 555 path=comment.f_path, diff_line=diff_line)
550 556 except (diffs.LineNotInDiffException,
551 557 diffs.FileNotInDiffException):
552 558 comment.display_state = ChangesetComment.COMMENT_OUTDATED
553 559 return
554 560
555 561 if old_context == new_context:
556 562 return
557 563
558 564 if self._should_relocate_diff_line(diff_line):
559 565 new_diff_lines = new_diff_proc.find_context(
560 566 path=comment.f_path, context=old_context,
561 567 offset=self.DIFF_CONTEXT_BEFORE)
562 568 if not new_diff_lines:
563 569 comment.display_state = ChangesetComment.COMMENT_OUTDATED
564 570 else:
565 571 new_diff_line = self._choose_closest_diff_line(
566 572 diff_line, new_diff_lines)
567 573 comment.line_no = _diff_to_comment_line_number(new_diff_line)
568 574 else:
569 575 comment.display_state = ChangesetComment.COMMENT_OUTDATED
570 576
571 577 def _should_relocate_diff_line(self, diff_line):
572 578 """
573 579 Checks if relocation shall be tried for the given `diff_line`.
574 580
575 581 If a comment points into the first lines, then we can have a situation
576 582 that after an update another line has been added on top. In this case
577 583 we would find the context still and move the comment around. This
578 584 would be wrong.
579 585 """
580 586 should_relocate = (
581 587 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
582 588 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
583 589 return should_relocate
584 590
585 591 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
586 592 candidate = new_diff_lines[0]
587 593 best_delta = _diff_line_delta(diff_line, candidate)
588 594 for new_diff_line in new_diff_lines[1:]:
589 595 delta = _diff_line_delta(diff_line, new_diff_line)
590 596 if delta < best_delta:
591 597 candidate = new_diff_line
592 598 best_delta = delta
593 599 return candidate
594 600
595 601 def _visible_inline_comments_of_pull_request(self, pull_request):
596 602 comments = self._all_inline_comments_of_pull_request(pull_request)
597 603 comments = comments.filter(
598 604 coalesce(ChangesetComment.display_state, '') !=
599 605 ChangesetComment.COMMENT_OUTDATED)
600 606 return comments
601 607
602 608 def _all_inline_comments_of_pull_request(self, pull_request):
603 609 comments = Session().query(ChangesetComment)\
604 610 .filter(ChangesetComment.line_no != None)\
605 611 .filter(ChangesetComment.f_path != None)\
606 612 .filter(ChangesetComment.pull_request == pull_request)
607 613 return comments
608 614
609 615 def _all_general_comments_of_pull_request(self, pull_request):
610 616 comments = Session().query(ChangesetComment)\
611 617 .filter(ChangesetComment.line_no == None)\
612 618 .filter(ChangesetComment.f_path == None)\
613 619 .filter(ChangesetComment.pull_request == pull_request)
614 620 return comments
615 621
616 622 @staticmethod
617 623 def use_outdated_comments(pull_request):
618 624 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
619 625 settings = settings_model.get_general_settings()
620 626 return settings.get('rhodecode_use_outdated_comments', False)
621 627
622 628
623 629 def _parse_comment_line_number(line_no):
624 630 """
625 631 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
626 632 """
627 633 old_line = None
628 634 new_line = None
629 635 if line_no.startswith('o'):
630 636 old_line = int(line_no[1:])
631 637 elif line_no.startswith('n'):
632 638 new_line = int(line_no[1:])
633 639 else:
634 640 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
635 641 return diffs.DiffLineNumber(old_line, new_line)
636 642
637 643
638 644 def _diff_to_comment_line_number(diff_line):
639 645 if diff_line.new is not None:
640 646 return u'n{}'.format(diff_line.new)
641 647 elif diff_line.old is not None:
642 648 return u'o{}'.format(diff_line.old)
643 649 return u''
644 650
645 651
646 652 def _diff_line_delta(a, b):
647 653 if None not in (a.new, b.new):
648 654 return abs(a.new - b.new)
649 655 elif None not in (a.old, b.old):
650 656 return abs(a.old - b.old)
651 657 else:
652 658 raise ValueError(
653 659 "Cannot compute delta between {} and {}".format(a, b))
General Comments 0
You need to be logged in to leave comments. Login now