##// END OF EJS Templates
core: avoid using rhodecode.test packages inside main packages as tests are removed during build which can cause some problems in some edge case calls
core: avoid using rhodecode.test packages inside main packages as tests are removed during build which can cause some problems in some edge case calls

File last commit:

r5608:6d33e504 default
r5618:bdbdb63f default
Show More
pull_request.py
2387 lines | 95.9 KiB | text/x-python | PythonLexer
core: updated copyright to 2024
r5608 # Copyright (C) 2012-2024 RhodeCode GmbH
project: added all source files and assets
r1 #
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
"""
pull request model for RhodeCode
"""
import logging
pull-requests: small code cleanup to define other type of merge username...
r4191 import os
project: added all source files and assets
r1 import datetime
models: major update for python3,...
r5070 import urllib.request
import urllib.parse
import urllib.error
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 import collections
project: added all source files and assets
r1
models: major update for python3,...
r5070 import dataclasses as dataclasses
events: expose permalink urls for different set of object....
r1788 from pyramid.threadlocal import get_current_request
project: added all source files and assets
r1
pull-requests: fix way how pull-request calculates common ancestors....
r4346 from rhodecode.lib.vcs.nodes import FileNode
dan
pull-requests: ensure merge response provide more details...
r3339 from rhodecode.translation import lazy_ugettext
project: added all source files and assets
r1 from rhodecode.lib import helpers as h, hooks_utils, diffs
audit-logs: implemented pull request and comment events.
r1807 from rhodecode.lib import audit_logger
python3: removed compat modules
r4928 from collections import OrderedDict
tests: fixed test suite for celery adoption
r5607 from rhodecode.lib.hook_daemon.utils import prepare_callback_daemon
models: major update for python3,...
r5070 from rhodecode.lib.ext_json import sjson as json
project: added all source files and assets
r1 from rhodecode.lib.markup_renderer import (
DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
models: major update for python3,...
r5070 from rhodecode.lib.hash_utils import md5_safe
from rhodecode.lib.str_utils import safe_str
from rhodecode.lib.utils2 import AttributeDict, get_current_rhodecode_user
project: added all source files and assets
r1 from rhodecode.lib.vcs.backends.base import (
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 Reference, MergeResponse, MergeFailureReason, UpdateFailureReason,
TargetRefMissing, SourceRefMissing)
Martin Bornhold
config: Use hooks protocol and direc calls seting from vcs settings module.
r590 from rhodecode.lib.vcs.conf import settings as vcs_settings
project: added all source files and assets
r1 from rhodecode.lib.vcs.exceptions import (
CommitDoesNotExistError, EmptyRepositoryError)
from rhodecode.model import BaseModel
from rhodecode.model.changeset_status import ChangesetStatusModel
comments: renamed ChangesetCommentsModel to CommentsModel to reflect what it actually does....
r1323 from rhodecode.model.comment import CommentsModel
project: added all source files and assets
r1 from rhodecode.model.db import (
models: major update for python3,...
r5070 aliased, null, lazyload, and_, or_, select, func, String, cast, PullRequest, PullRequestReviewers, ChangesetStatus,
pull-requests: allow filter by author
r4320 PullRequestVersion, ChangesetComment, Repository, RepoReviewRule, User)
project: added all source files and assets
r1 from rhodecode.model.meta import Session
from rhodecode.model.notification import NotificationModel, \
EmailNotificationModel
from rhodecode.model.scm import ScmModel
from rhodecode.model.settings import VcsSettingsModel
log = logging.getLogger(__name__)
Martin Bornhold
pr: Add UpdateResponse data structure to hold response data when updating commits.
r1073 # Data structure to hold the response data when updating commits during a pull
# request update.
pull-requests: added update pull-requests email+notifications...
r4120 class UpdateResponse(object):
def __init__(self, executed, reason, new, old, common_ancestor_id,
commit_changes, source_changed, target_changed):
self.executed = executed
self.reason = reason
self.new = new
self.old = old
self.common_ancestor_id = common_ancestor_id
self.changes = commit_changes
self.source_changed = source_changed
self.target_changed = target_changed
Martin Bornhold
pr: Add UpdateResponse data structure to hold response data when updating commits.
r1073
pull-requests: fix way how pull-request calculates common ancestors....
r4346 def get_diff_info(
source_repo, source_ref, target_repo, target_ref, get_authors=False,
get_commit_authors=True):
"""
Calculates detailed diff information for usage in preview of creation of a pull-request.
This is also used for default reviewers logic
"""
source_scm = source_repo.scm_instance()
target_scm = target_repo.scm_instance()
ancestor_id = target_scm.get_common_ancestor(target_ref, source_ref, source_scm)
if not ancestor_id:
raise ValueError(
'cannot calculate diff info without a common ancestor. '
'Make sure both repositories are related, and have a common forking commit.')
# case here is that want a simple diff without incoming commits,
# previewing what will be merged based only on commits in the source.
log.debug('Using ancestor %s as source_ref instead of %s',
ancestor_id, source_ref)
# source of changes now is the common ancestor
source_commit = source_scm.get_commit(commit_id=ancestor_id)
# target commit becomes the source ref as it is the last commit
# for diff generation this logic gives proper diff
target_commit = source_scm.get_commit(commit_id=source_ref)
vcs_diff = \
source_scm.get_diff(commit1=source_commit, commit2=target_commit,
ignore_whitespace=False, context=3)
models: major update for python3,...
r5070 diff_processor = diffs.DiffProcessor(vcs_diff, diff_format='newdiff',
diff_limit=0, file_limit=0, show_full_diff=True)
pull-requests: fix way how pull-request calculates common ancestors....
r4346
_parsed = diff_processor.prepare()
all_files = []
all_files_changes = []
changed_lines = {}
stats = [0, 0]
for f in _parsed:
all_files.append(f['filename'])
all_files_changes.append({
'filename': f['filename'],
'stats': f['stats']
})
stats[0] += f['stats']['added']
stats[1] += f['stats']['deleted']
changed_lines[f['filename']] = []
if len(f['chunks']) < 2:
continue
# first line is "context" information
for chunks in f['chunks'][1:]:
for chunk in chunks['lines']:
if chunk['action'] not in ('del', 'mod'):
continue
changed_lines[f['filename']].append(chunk['old_lineno'])
commit_authors = []
user_counts = {}
email_counts = {}
author_counts = {}
_commit_cache = {}
commits = []
if get_commit_authors:
default-reviewers: diff data should load more things lazy for better performance.
r4508 log.debug('Obtaining commit authors from set of commits')
reviewers: optimize diff data, and creation of PR with advanced default reviewers
r4510 _compare_data = target_scm.compare(
pull-requests: fix way how pull-request calculates common ancestors....
r4346 target_ref, source_ref, source_scm, merge=True,
reviewers: optimize diff data, and creation of PR with advanced default reviewers
r4510 pre_load=["author", "date", "message"]
)
pull-requests: fix way how pull-request calculates common ancestors....
r4346
reviewers: optimize diff data, and creation of PR with advanced default reviewers
r4510 for commit in _compare_data:
# NOTE(marcink): we serialize here, so we don't produce more vcsserver calls on data returned
# at this function which is later called via JSON serialization
serialized_commit = dict(
author=commit.author,
date=commit.date,
message=commit.message,
pull-requests: fixed creation of pr after new serialized commits data.
r4517 commit_id=commit.raw_id,
raw_id=commit.raw_id
reviewers: optimize diff data, and creation of PR with advanced default reviewers
r4510 )
commits.append(serialized_commit)
user = User.get_from_cs_author(serialized_commit['author'])
pull-requests: fix way how pull-request calculates common ancestors....
r4346 if user and user not in commit_authors:
commit_authors.append(user)
# lines
if get_authors:
default-reviewers: diff data should load more things lazy for better performance.
r4508 log.debug('Calculating authors of changed files')
pull-requests: fix way how pull-request calculates common ancestors....
r4346 target_commit = source_repo.get_commit(ancestor_id)
for fname, lines in changed_lines.items():
reviewers: optimize diff data, and creation of PR with advanced default reviewers
r4510
pull-requests: fix way how pull-request calculates common ancestors....
r4346 try:
reviewers: optimize diff data, and creation of PR with advanced default reviewers
r4510 node = target_commit.get_node(fname, pre_load=["is_binary"])
pull-requests: fix way how pull-request calculates common ancestors....
r4346 except Exception:
reviewers: optimize diff data, and creation of PR with advanced default reviewers
r4510 log.exception("Failed to load node with path %s", fname)
pull-requests: fix way how pull-request calculates common ancestors....
r4346 continue
if not isinstance(node, FileNode):
continue
reviewers: optimize diff data, and creation of PR with advanced default reviewers
r4510 # NOTE(marcink): for binary node we don't do annotation, just use last author
if node.is_binary:
author = node.last_commit.author
email = node.last_commit.author_email
user = User.get_from_cs_author(author)
if user:
user_counts[user.user_id] = user_counts.get(user.user_id, 0) + 1
author_counts[author] = author_counts.get(author, 0) + 1
email_counts[email] = email_counts.get(email, 0) + 1
continue
pull-requests: fix way how pull-request calculates common ancestors....
r4346 for annotation in node.annotate:
line_no, commit_id, get_commit_func, line_text = annotation
if line_no in lines:
if commit_id not in _commit_cache:
_commit_cache[commit_id] = get_commit_func()
commit = _commit_cache[commit_id]
author = commit.author
email = commit.author_email
user = User.get_from_cs_author(author)
if user:
default-reviewers: fixed problems with new diff format for more advanced default reviewer rules.
r4385 user_counts[user.user_id] = user_counts.get(user.user_id, 0) + 1
pull-requests: fix way how pull-request calculates common ancestors....
r4346 author_counts[author] = author_counts.get(author, 0) + 1
email_counts[email] = email_counts.get(email, 0) + 1
default-reviewers: diff data should load more things lazy for better performance.
r4508 log.debug('Default reviewers processing finished')
pull-requests: fix way how pull-request calculates common ancestors....
r4346 return {
'commits': commits,
'files': all_files_changes,
'stats': stats,
'ancestor': ancestor_id,
# original authors of modified files
'original_authors': {
'users': user_counts,
'authors': author_counts,
'emails': email_counts,
},
'commit_authors': commit_authors
}
project: added all source files and assets
r1 class PullRequestModel(BaseModel):
cls = PullRequest
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 DIFF_CONTEXT = diffs.DEFAULT_CONTEXT
project: added all source files and assets
r1
Martin Bornhold
pr: Add update status messages dict.
r1072 UPDATE_STATUS_MESSAGES = {
UpdateFailureReason.NONE: lazy_ugettext(
'Pull request update successful.'),
UpdateFailureReason.UNKNOWN: lazy_ugettext(
'Pull request update failed because of an unknown error.'),
UpdateFailureReason.NO_CHANGE: lazy_ugettext(
pull-requests: change the update commits logic to handle target changes better....
r1601 'No update needed because the source and target have not changed.'),
pull-request: fixed typo in wrong ref type error, and added which...
r1687 UpdateFailureReason.WRONG_REF_TYPE: lazy_ugettext(
Martin Bornhold
pr: Add update status messages dict.
r1072 'Pull request cannot be updated because the reference type is '
pull-request: fixed typo in wrong ref type error, and added which...
r1687 'not supported for an update. Only Branch, Tag or Bookmark is allowed.'),
Martin Bornhold
pr: Add update status messages dict.
r1072 UpdateFailureReason.MISSING_TARGET_REF: lazy_ugettext(
'This pull request cannot be updated because the target '
'reference is missing.'),
UpdateFailureReason.MISSING_SOURCE_REF: lazy_ugettext(
'This pull request cannot be updated because the source '
'reference is missing.'),
}
pull-requests: validate ref types for pull request so users cannot provide wrongs ones.
r3302 REF_TYPES = ['bookmark', 'book', 'tag', 'branch']
UPDATABLE_REF_TYPES = ['bookmark', 'book', 'branch']
Martin Bornhold
pr: Add update status messages dict.
r1072
project: added all source files and assets
r1 def __get_pull_request(self, pull_request):
pull-requests: when creating a new version set the created_date to now instead of...
r1191 return self._get_instance((
PullRequest, PullRequestVersion), pull_request)
project: added all source files and assets
r1
def _check_perms(self, perms, pull_request, user, api=False):
if not api:
return h.HasRepoPermissionAny(*perms)(
user=user, repo_name=pull_request.target_repo.repo_name)
else:
return h.HasRepoPermissionAnyApi(*perms)(
user=user, repo_name=pull_request.target_repo.repo_name)
def check_user_read(self, pull_request, user, api=False):
_perms = ('repository.admin', 'repository.write', 'repository.read',)
return self._check_perms(_perms, pull_request, user, api)
def check_user_merge(self, pull_request, user, api=False):
_perms = ('repository.admin', 'repository.write', 'hg.admin',)
return self._check_perms(_perms, pull_request, user, api)
def check_user_update(self, pull_request, user, api=False):
owner = user.user_id == pull_request.user_id
return self.check_user_merge(pull_request, user, api) or owner
pull-requests: moved the delete logic into the show view....
r1085 def check_user_delete(self, pull_request, user):
owner = user.user_id == pull_request.user_id
perms: fixed call to correctly check permissions for admin.
r1375 _perms = ('repository.admin',)
pull-requests: moved the delete logic into the show view....
r1085 return self._check_perms(_perms, pull_request, user) or owner
pull-requests: only allow actual reviewers to leave status/votes....
r4513 def is_user_reviewer(self, pull_request, user):
return user.user_id in [
x.user_id for x in
pull_request.get_pull_request_reviewers(PullRequestReviewers.ROLE_REVIEWER)
if x.user
]
project: added all source files and assets
r1 def check_user_change_status(self, pull_request, user, api=False):
pull-requests: only allow actual reviewers to leave status/votes....
r4513 return self.check_user_update(pull_request, user, api) \
or self.is_user_reviewer(pull_request, user)
project: added all source files and assets
r1
pull-requests: security, prevent from injecting comments to other pull requests users...
r2181 def check_user_comment(self, pull_request, user):
owner = user.user_id == pull_request.user_id
return self.check_user_read(pull_request, user) or owner
project: added all source files and assets
r1 def get(self, pull_request):
return self.__get_pull_request(pull_request)
pull-requests: added quick filter to grid view.
r4055 def _prepare_get_all_query(self, repo_name, search_q=None, source=False,
statuses=None, opened_by=None, order_by=None,
dan
pull-requests: add indication of state change in list of pull-requests and actually show them in the list.
r3816 order_dir='desc', only_created=False):
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 repo = None
if repo_name:
repo = self._get_repo(repo_name)
project: added all source files and assets
r1 q = PullRequest.query()
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084
pull-requests: added quick filter to grid view.
r4055 if search_q:
models: major update for python3,...
r5070 like_expression = u'%{}%'.format(safe_str(search_q))
pull-requests: added awaiting my review filter for users pull-requests....
r4690 q = q.join(User, User.user_id == PullRequest.user_id)
pull-requests: added quick filter to grid view.
r4055 q = q.filter(or_(
cast(PullRequest.pull_request_id, String).ilike(like_expression),
pull-requests: allow filter by author
r4320 User.username.ilike(like_expression),
pull-requests: added quick filter to grid view.
r4055 PullRequest.title.ilike(like_expression),
PullRequest.description.ilike(like_expression),
))
project: added all source files and assets
r1 # source or target
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 if repo and source:
project: added all source files and assets
r1 q = q.filter(PullRequest.source_repo == repo)
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 elif repo:
project: added all source files and assets
r1 q = q.filter(PullRequest.target_repo == repo)
# closed,opened
if statuses:
q = q.filter(PullRequest.status.in_(statuses))
# opened by filter
if opened_by:
q = q.filter(PullRequest.user_id.in_(opened_by))
pull-requests: introduce operation state for pull requests to prevent from...
r3371 # only get those that are in "created" state
if only_created:
q = q.filter(PullRequest.pull_request_state == PullRequest.STATE_CREATED)
pull-requests: fix potential crash on providing a wrong order-by type column.
r4716 order_map = {
'name_raw': PullRequest.pull_request_id,
'id': PullRequest.pull_request_id,
'title': PullRequest.title,
'updated_on_raw': PullRequest.updated_on,
'target_repo': PullRequest.target_repo_id
}
if order_by and order_by in order_map:
project: added all source files and assets
r1 if order_dir == 'asc':
q = q.order_by(order_map[order_by].asc())
else:
q = q.order_by(order_map[order_by].desc())
return q
pull-requests: added quick filter to grid view.
r4055 def count_all(self, repo_name, search_q=None, source=False, statuses=None,
project: added all source files and assets
r1 opened_by=None):
"""
Count the number of pull requests for a specific repository.
:param repo_name: target or source repo
pull-requests: added quick filter to grid view.
r4055 :param search_q: filter by text
project: added all source files and assets
r1 :param source: boolean flag to specify if repo_name refers to source
:param statuses: list of pull request statuses
:param opened_by: author user of the pull request
:returns: int number of pull requests
"""
q = self._prepare_get_all_query(
pull-requests: added quick filter to grid view.
r4055 repo_name, search_q=search_q, source=source, statuses=statuses,
opened_by=opened_by)
project: added all source files and assets
r1
return q.count()
pull-requests: added quick filter to grid view.
r4055 def get_all(self, repo_name, search_q=None, source=False, statuses=None,
opened_by=None, offset=0, length=None, order_by=None, order_dir='desc'):
project: added all source files and assets
r1 """
Get all pull requests for a specific repository.
:param repo_name: target or source repo
pull-requests: added quick filter to grid view.
r4055 :param search_q: filter by text
project: added all source files and assets
r1 :param source: boolean flag to specify if repo_name refers to source
:param statuses: list of pull request statuses
:param opened_by: author user of the pull request
:param offset: pagination offset
:param length: length of returned list
:param order_by: order of the returned list
:param order_dir: 'asc' or 'desc' ordering direction
:returns: list of pull requests
"""
q = self._prepare_get_all_query(
pull-requests: added quick filter to grid view.
r4055 repo_name, search_q=search_q, source=source, statuses=statuses,
opened_by=opened_by, order_by=order_by, order_dir=order_dir)
project: added all source files and assets
r1
if length:
pull_requests = q.limit(length).offset(offset).all()
else:
pull_requests = q.all()
return pull_requests
pull-requests: added awaiting my review filter for users pull-requests....
r4690 def count_awaiting_review(self, repo_name, search_q=None, statuses=None):
project: added all source files and assets
r1 """
Count the number of pull requests for a specific repository that are
awaiting review.
:param repo_name: target or source repo
pull-requests: added quick filter to grid view.
r4055 :param search_q: filter by text
project: added all source files and assets
r1 :param statuses: list of pull request statuses
:returns: int number of pull requests
"""
pull_requests = self.get_awaiting_review(
pull-requests: added awaiting my review filter for users pull-requests....
r4690 repo_name, search_q=search_q, statuses=statuses)
project: added all source files and assets
r1
return len(pull_requests)
pull-requests: added awaiting my review filter for users pull-requests....
r4690 def get_awaiting_review(self, repo_name, search_q=None, statuses=None,
offset=0, length=None, order_by=None, order_dir='desc'):
project: added all source files and assets
r1 """
Get all pull requests for a specific repository that are awaiting
review.
:param repo_name: target or source repo
pull-requests: added quick filter to grid view.
r4055 :param search_q: filter by text
project: added all source files and assets
r1 :param statuses: list of pull request statuses
:param offset: pagination offset
:param length: length of returned list
:param order_by: order of the returned list
:param order_dir: 'asc' or 'desc' ordering direction
:returns: list of pull requests
"""
pull_requests = self.get_all(
pull-requests: added awaiting my review filter for users pull-requests....
r4690 repo_name, search_q=search_q, statuses=statuses,
order_by=order_by, order_dir=order_dir)
project: added all source files and assets
r1
_filtered_pull_requests = []
for pr in pull_requests:
status = pr.calculated_review_status()
if status in [ChangesetStatus.STATUS_NOT_REVIEWED,
ChangesetStatus.STATUS_UNDER_REVIEW]:
_filtered_pull_requests.append(pr)
if length:
return _filtered_pull_requests[offset:offset+length]
else:
return _filtered_pull_requests
pull-requests: added awaiting my review filter for users pull-requests....
r4690 def _prepare_awaiting_my_review_review_query(
self, repo_name, user_id, search_q=None, statuses=None,
order_by=None, order_dir='desc'):
for_review_statuses = [
ChangesetStatus.STATUS_UNDER_REVIEW, ChangesetStatus.STATUS_NOT_REVIEWED
]
pull_request_alias = aliased(PullRequest)
status_alias = aliased(ChangesetStatus)
reviewers_alias = aliased(PullRequestReviewers)
repo_alias = aliased(Repository)
last_ver_subq = Session()\
.query(func.min(ChangesetStatus.version)) \
.filter(ChangesetStatus.pull_request_id == reviewers_alias.pull_request_id)\
.filter(ChangesetStatus.user_id == reviewers_alias.user_id) \
.subquery()
q = Session().query(pull_request_alias) \
.options(lazyload(pull_request_alias.author)) \
.join(reviewers_alias,
reviewers_alias.pull_request_id == pull_request_alias.pull_request_id) \
.join(repo_alias,
repo_alias.repo_id == pull_request_alias.target_repo_id) \
.outerjoin(status_alias,
and_(status_alias.user_id == reviewers_alias.user_id,
status_alias.pull_request_id == reviewers_alias.pull_request_id)) \
.filter(or_(status_alias.version == null(),
status_alias.version == last_ver_subq)) \
.filter(reviewers_alias.user_id == user_id) \
.filter(repo_alias.repo_name == repo_name) \
.filter(or_(status_alias.status == null(), status_alias.status.in_(for_review_statuses))) \
.group_by(pull_request_alias)
# closed,opened
if statuses:
q = q.filter(pull_request_alias.status.in_(statuses))
if search_q:
models: major update for python3,...
r5070 like_expression = u'%{}%'.format(safe_str(search_q))
pull-requests: added awaiting my review filter for users pull-requests....
r4690 q = q.join(User, User.user_id == pull_request_alias.user_id)
q = q.filter(or_(
cast(pull_request_alias.pull_request_id, String).ilike(like_expression),
User.username.ilike(like_expression),
pull_request_alias.title.ilike(like_expression),
pull_request_alias.description.ilike(like_expression),
))
pull-requests: fix potential crash on providing a wrong order-by type column.
r4716 order_map = {
'name_raw': pull_request_alias.pull_request_id,
'title': pull_request_alias.title,
'updated_on_raw': pull_request_alias.updated_on,
'target_repo': pull_request_alias.target_repo_id
}
if order_by and order_by in order_map:
pull-requests: added awaiting my review filter for users pull-requests....
r4690 if order_dir == 'asc':
q = q.order_by(order_map[order_by].asc())
else:
q = q.order_by(order_map[order_by].desc())
return q
def count_awaiting_my_review(self, repo_name, user_id, search_q=None, statuses=None):
project: added all source files and assets
r1 """
Count the number of pull requests for a specific repository that are
awaiting review from a specific user.
:param repo_name: target or source repo
pull-requests: added awaiting my review filter for users pull-requests....
r4690 :param user_id: reviewer user of the pull request
pull-requests: added quick filter to grid view.
r4055 :param search_q: filter by text
project: added all source files and assets
r1 :param statuses: list of pull request statuses
:returns: int number of pull requests
"""
pull-requests: added awaiting my review filter for users pull-requests....
r4690 q = self._prepare_awaiting_my_review_review_query(
repo_name, user_id, search_q=search_q, statuses=statuses)
return q.count()
project: added all source files and assets
r1
pull-requests: added awaiting my review filter for users pull-requests....
r4690 def get_awaiting_my_review(self, repo_name, user_id, search_q=None, statuses=None,
offset=0, length=None, order_by=None, order_dir='desc'):
project: added all source files and assets
r1 """
Get all pull requests for a specific repository that are awaiting
review from a specific user.
:param repo_name: target or source repo
pull-requests: added awaiting my review filter for users pull-requests....
r4690 :param user_id: reviewer user of the pull request
pull-requests: added quick filter to grid view.
r4055 :param search_q: filter by text
project: added all source files and assets
r1 :param statuses: list of pull request statuses
:param offset: pagination offset
:param length: length of returned list
:param order_by: order of the returned list
:param order_dir: 'asc' or 'desc' ordering direction
:returns: list of pull requests
"""
pull-requests: added awaiting my review filter for users pull-requests....
r4690 q = self._prepare_awaiting_my_review_review_query(
repo_name, user_id, search_q=search_q, statuses=statuses,
order_by=order_by, order_dir=order_dir)
project: added all source files and assets
r1 if length:
pull-requests: added awaiting my review filter for users pull-requests....
r4690 pull_requests = q.limit(length).offset(offset).all()
project: added all source files and assets
r1 else:
pull-requests: added awaiting my review filter for users pull-requests....
r4690 pull_requests = q.all()
return pull_requests
project: added all source files and assets
r1
pull-requests: added awaiting my review filter for users pull-requests....
r4690 def _prepare_im_participating_query(self, user_id=None, statuses=None, query='',
order_by=None, order_dir='desc'):
"""
return a query of pull-requests user is an creator, or he's added as a reviewer
"""
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 q = PullRequest.query()
if user_id:
models: major update for python3,...
r5070
base_query = select(PullRequestReviewers)\
.where(PullRequestReviewers.user_id == user_id)\
.with_only_columns(PullRequestReviewers.pull_request_id)
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 user_filter = or_(
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 PullRequest.user_id == user_id,
models: major update for python3,...
r5070 PullRequest.pull_request_id.in_(base_query)
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 )
q = PullRequest.query().filter(user_filter)
# closed,opened
if statuses:
q = q.filter(PullRequest.status.in_(statuses))
pull-requests: added filters to my account pull requests page.
r4318 if query:
models: major update for python3,...
r5070 like_expression = u'%{}%'.format(safe_str(query))
pull-requests: added awaiting my review filter for users pull-requests....
r4690 q = q.join(User, User.user_id == PullRequest.user_id)
pull-requests: added filters to my account pull requests page.
r4318 q = q.filter(or_(
cast(PullRequest.pull_request_id, String).ilike(like_expression),
pull-requests: allow filter by author
r4320 User.username.ilike(like_expression),
pull-requests: added filters to my account pull requests page.
r4318 PullRequest.title.ilike(like_expression),
PullRequest.description.ilike(like_expression),
))
pull-requests: fix potential crash on providing a wrong order-by type column.
r4716
order_map = {
'name_raw': PullRequest.pull_request_id,
'title': PullRequest.title,
'updated_on_raw': PullRequest.updated_on,
'target_repo': PullRequest.target_repo_id
}
if order_by and order_by in order_map:
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 if order_dir == 'asc':
q = q.order_by(order_map[order_by].asc())
else:
q = q.order_by(order_map[order_by].desc())
return q
pull-requests: added filters to my account pull requests page.
r4318 def count_im_participating_in(self, user_id=None, statuses=None, query=''):
pull-requests: added awaiting my review filter for users pull-requests....
r4690 q = self._prepare_im_participating_query(user_id, statuses=statuses, query=query)
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 return q.count()
def get_im_participating_in(
pull-requests: added filters to my account pull requests page.
r4318 self, user_id=None, statuses=None, query='', offset=0,
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 length=None, order_by=None, order_dir='desc'):
"""
pull-requests: added awaiting my review filter for users pull-requests....
r4690 Get all Pull requests that i'm participating in as a reviewer, or i have opened
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 """
pull-requests: added awaiting my review filter for users pull-requests....
r4690 q = self._prepare_im_participating_query(
user_id, statuses=statuses, query=query, order_by=order_by,
order_dir=order_dir)
if length:
pull_requests = q.limit(length).offset(offset).all()
else:
pull_requests = q.all()
return pull_requests
def _prepare_participating_in_for_review_query(
self, user_id, statuses=None, query='', order_by=None, order_dir='desc'):
for_review_statuses = [
ChangesetStatus.STATUS_UNDER_REVIEW, ChangesetStatus.STATUS_NOT_REVIEWED
]
pull_request_alias = aliased(PullRequest)
status_alias = aliased(ChangesetStatus)
reviewers_alias = aliased(PullRequestReviewers)
last_ver_subq = Session()\
.query(func.min(ChangesetStatus.version)) \
.filter(ChangesetStatus.pull_request_id == reviewers_alias.pull_request_id)\
.filter(ChangesetStatus.user_id == reviewers_alias.user_id) \
.subquery()
q = Session().query(pull_request_alias) \
.options(lazyload(pull_request_alias.author)) \
.join(reviewers_alias,
reviewers_alias.pull_request_id == pull_request_alias.pull_request_id) \
.outerjoin(status_alias,
and_(status_alias.user_id == reviewers_alias.user_id,
status_alias.pull_request_id == reviewers_alias.pull_request_id)) \
.filter(or_(status_alias.version == null(),
status_alias.version == last_ver_subq)) \
.filter(reviewers_alias.user_id == user_id) \
.filter(or_(status_alias.status == null(), status_alias.status.in_(for_review_statuses))) \
.group_by(pull_request_alias)
# closed,opened
if statuses:
q = q.filter(pull_request_alias.status.in_(statuses))
if query:
models: major update for python3,...
r5070 like_expression = u'%{}%'.format(safe_str(query))
pull-requests: added awaiting my review filter for users pull-requests....
r4690 q = q.join(User, User.user_id == pull_request_alias.user_id)
q = q.filter(or_(
cast(pull_request_alias.pull_request_id, String).ilike(like_expression),
User.username.ilike(like_expression),
pull_request_alias.title.ilike(like_expression),
pull_request_alias.description.ilike(like_expression),
))
pull-requests: fix potential crash on providing a wrong order-by type column.
r4716 order_map = {
'name_raw': pull_request_alias.pull_request_id,
'title': pull_request_alias.title,
'updated_on_raw': pull_request_alias.updated_on,
'target_repo': pull_request_alias.target_repo_id
}
if order_by and order_by in order_map:
pull-requests: added awaiting my review filter for users pull-requests....
r4690 if order_dir == 'asc':
q = q.order_by(order_map[order_by].asc())
else:
q = q.order_by(order_map[order_by].desc())
return q
def count_im_participating_in_for_review(self, user_id, statuses=None, query=''):
q = self._prepare_participating_in_for_review_query(user_id, statuses=statuses, query=query)
return q.count()
def get_im_participating_in_for_review(
self, user_id, statuses=None, query='', offset=0,
length=None, order_by=None, order_dir='desc'):
"""
Get all Pull requests that needs user approval or rejection
"""
q = self._prepare_participating_in_for_review_query(
pull-requests: added filters to my account pull requests page.
r4318 user_id, statuses=statuses, query=query, order_by=order_by,
pull-requests: redo my account pull request page with datagrid. Fixes #4297...
r1084 order_dir=order_dir)
if length:
pull_requests = q.limit(length).offset(offset).all()
else:
pull_requests = q.all()
return pull_requests
project: added all source files and assets
r1 def get_versions(self, pull_request):
"""
returns version of pull request sorted by ID descending
"""
return PullRequestVersion.query()\
.filter(PullRequestVersion.pull_request == pull_request)\
.order_by(PullRequestVersion.pull_request_version_id.asc())\
.all()
pull-requests: moved get_pr_version into PullRequestModel.
r2393 def get_pr_version(self, pull_request_id, version=None):
at_version = None
if version and version == 'latest':
pull_request_ver = PullRequest.get(pull_request_id)
pull_request_obj = pull_request_ver
_org_pull_request_obj = pull_request_obj
at_version = 'latest'
elif version:
pull_request_ver = PullRequestVersion.get_or_404(version)
pull_request_obj = pull_request_ver
_org_pull_request_obj = pull_request_ver.pull_request
at_version = pull_request_ver.pull_request_version_id
else:
_org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
pull_request_id)
pull_request_display_obj = PullRequest.get_pr_display_object(
pull_request_obj, _org_pull_request_obj)
return _org_pull_request_obj, pull_request_obj, \
pull_request_display_obj, at_version
pull-requests: expose commit versions in the pull-request commit list. Fixes #5642
r4615 def pr_commits_versions(self, versions):
"""
Maps the pull-request commits into all known PR versions. This way we can obtain
each pr version the commit was introduced in.
"""
commit_versions = collections.defaultdict(list)
num_versions = [x.pull_request_version_id for x in versions]
for ver in versions:
for commit_id in ver.revisions:
ver_idx = ChangesetComment.get_index_from_version(
ver.pull_request_version_id, num_versions=num_versions)
commit_versions[commit_id].append(ver_idx)
return commit_versions
project: added all source files and assets
r1 def create(self, created_by, source_repo, source_ref, target_repo,
reviewers: added observers as another way to define reviewers....
r4500 target_ref, revisions, reviewers, observers, title, description=None,
pull-requests: fix way how pull-request calculates common ancestors....
r4346 common_ancestor_id=None,
pull-requests: make the renderer stored and saved for each pull requests....
r2903 description_renderer=None,
audit-logs: use auth_user in few places to track action IP.
r2788 reviewer_data=None, translator=None, auth_user=None):
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 translator = translator or get_current_request().translate
pull-request: extended default reviewers functionality....
r1769
project: added all source files and assets
r1 created_by_user = self._get_user(created_by)
pull-requests: add merge validation to prevent merges to protected branches.
r2981 auth_user = auth_user or created_by_user.AuthUser()
project: added all source files and assets
r1 source_repo = self._get_repo(source_repo)
target_repo = self._get_repo(target_repo)
pull_request = PullRequest()
pull_request.source_repo = source_repo
pull_request.source_ref = source_ref
pull_request.target_repo = target_repo
pull_request.target_ref = target_ref
pull_request.revisions = revisions
pull_request.title = title
pull_request.description = description
pull-requests: make the renderer stored and saved for each pull requests....
r2903 pull_request.description_renderer = description_renderer
project: added all source files and assets
r1 pull_request.author = created_by_user
pull-request: extended default reviewers functionality....
r1769 pull_request.reviewer_data = reviewer_data
pull-requests: introduce operation state for pull requests to prevent from...
r3371 pull_request.pull_request_state = pull_request.STATE_CREATING
pull-requests: fix way how pull-request calculates common ancestors....
r4346 pull_request.common_ancestor_id = common_ancestor_id
project: added all source files and assets
r1 Session().add(pull_request)
Session().flush()
dan
reviewers: store reviewer reasons to database, fixes #4238
r873 reviewer_ids = set()
project: added all source files and assets
r1 # members / reviewers
dan
reviewers: store reviewer reasons to database, fixes #4238
r873 for reviewer_object in reviewers:
reviewers: added observers as another way to define reviewers....
r4500 user_id, reasons, mandatory, role, rules = reviewer_object
dan
pull-requests: make sure to skip duplicates of reviewers via PR create method.
r1793 user = self._get_user(user_id)
dan
reviewers: store reviewer reasons to database, fixes #4238
r873
dan
pull-requests: make sure to skip duplicates of reviewers via PR create method.
r1793 # skip duplicates
if user.user_id in reviewer_ids:
continue
dan
reviewers: store reviewer reasons to database, fixes #4238
r873 reviewer_ids.add(user.user_id)
pull-request: extended default reviewers functionality....
r1769 reviewer = PullRequestReviewers()
reviewer.user = user
reviewer.pull_request = pull_request
reviewer.reasons = reasons
reviewer.mandatory = mandatory
reviewers: added observers as another way to define reviewers....
r4500 reviewer.role = role
default-reviewers: introduce new voting rule logic that allows...
r2484
# NOTE(marcink): pick only first rule for now
api: fix creation of PR with default reviewer rules.
r2855 rule_id = list(rules)[0] if rules else None
default-reviewers: introduce new voting rule logic that allows...
r2484 rule = RepoReviewRule.get(rule_id) if rule_id else None
if rule:
default-reviewers: fixed voting rule calculation on user-group. The previous...
r2960 review_group = rule.user_group_vote_rule(user_id)
# we check if this particular reviewer is member of a voting group
default-reviewers: introduce new voting rule logic that allows...
r2484 if review_group:
# NOTE(marcink):
default-reviewers: fixed voting rule calculation on user-group. The previous...
r2960 # can be that user is member of more but we pick the first same,
# same as default reviewers algo
default-reviewers: introduce new voting rule logic that allows...
r2484 review_group = review_group[0]
rule_data = {
'rule_name':
rule.review_rule_name,
'rule_user_group_entry_id':
review_group.repo_review_rule_users_group_id,
'rule_user_group_name':
review_group.users_group.users_group_name,
'rule_user_group_members':
[x.user.username for x in review_group.users_group.members],
default-reviewers: fixed voting rule calculation on user-group. The previous...
r2960 'rule_user_group_members_id':
[x.user.user_id for x in review_group.users_group.members],
default-reviewers: introduce new voting rule logic that allows...
r2484 }
# e.g {'vote_rule': -1, 'mandatory': True}
rule_data.update(review_group.rule_data())
reviewer.rule_data = rule_data
project: added all source files and assets
r1 Session().add(reviewer)
pull-requests: fix problem with long DB transaction and row-locking....
r2792 Session().flush()
project: added all source files and assets
r1
reviewers: added observers as another way to define reviewers....
r4500 for observer_object in observers:
user_id, reasons, mandatory, role, rules = observer_object
user = self._get_user(user_id)
# skip duplicates from reviewers
if user.user_id in reviewer_ids:
continue
#reviewer_ids.add(user.user_id)
observer = PullRequestReviewers()
observer.user = user
observer.pull_request = pull_request
observer.reasons = reasons
observer.mandatory = mandatory
observer.role = role
# NOTE(marcink): pick only first rule for now
rule_id = list(rules)[0] if rules else None
rule = RepoReviewRule.get(rule_id) if rule_id else None
if rule:
# TODO(marcink): do we need this for observers ??
pass
Session().add(observer)
Session().flush()
project: added all source files and assets
r1 # Set approval status to "Under Review" for all commits which are
# part of this pull request.
ChangesetStatusModel().set_status(
repo=target_repo,
status=ChangesetStatus.STATUS_UNDER_REVIEW,
user=created_by_user,
pull_request=pull_request
)
pull-requests: fix problem with long DB transaction and row-locking....
r2792 # we commit early at this point. This has to do with a fact
# that before queries do some row-locking. And because of that
pull-requests: increase stability of concurrent pull requests creation by flushing prematurly the statuses of commits....
r3408 # we need to commit and finish transaction before below validate call
pull-requests: fix problem with long DB transaction and row-locking....
r2792 # that for large repos could be long resulting in long row locks
Session().commit()
project: added all source files and assets
r1
pull-requests: introduce operation state for pull requests to prevent from...
r3371 # prepare workspace, and run initial merge simulation. Set state during that
# operation
pull_request = PullRequest.get(pull_request.pull_request_id)
pull-requests: handle exceptions in state change and improve logging.
r3828 # set as merging, for merge simulation, and if finished to created so we mark
pull-requests: introduce operation state for pull requests to prevent from...
r3371 # simulation is working fine
with pull_request.set_state(PullRequest.STATE_MERGING,
pull-requests: handle exceptions in state change and improve logging.
r3828 final_state=PullRequest.STATE_CREATED) as state_obj:
pull-requests: introduce operation state for pull requests to prevent from...
r3371 MergeCheck.validate(
pull_request, auth_user=auth_user, translator=translator)
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168
pull-requests: limit the ammount of data saved in default reviewers data for better memory usage...
r4509 self.notify_reviewers(pull_request, reviewer_ids, created_by_user)
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 self.trigger_pull_request_hook(pull_request, created_by_user, 'create')
project: added all source files and assets
r1
audit-logs: implemented pull request and comment events.
r1807 creation_data = pull_request.get_api_data(with_merge_state=False)
self._log_audit_action(
'repo.pull_request.create', {'data': creation_data},
audit-logs: use auth_user in few places to track action IP.
r2788 auth_user, pull_request)
audit-logs: implemented pull request and comment events.
r1807
project: added all source files and assets
r1 return pull_request
events: trigger 'review_status_change' when reviewers are updated....
r3415 def trigger_pull_request_hook(self, pull_request, user, action, data=None):
project: added all source files and assets
r1 pull_request = self.__get_pull_request(pull_request)
target_scm = pull_request.target_repo.scm_instance()
if action == 'create':
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 trigger_hook = hooks_utils.trigger_create_pull_request_hook
project: added all source files and assets
r1 elif action == 'merge':
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 trigger_hook = hooks_utils.trigger_merge_pull_request_hook
project: added all source files and assets
r1 elif action == 'close':
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 trigger_hook = hooks_utils.trigger_close_pull_request_hook
project: added all source files and assets
r1 elif action == 'review_status_change':
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 trigger_hook = hooks_utils.trigger_review_pull_request_hook
project: added all source files and assets
r1 elif action == 'update':
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 trigger_hook = hooks_utils.trigger_update_pull_request_hook
events: trigger 'review_status_change' when reviewers are updated....
r3415 elif action == 'comment':
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 trigger_hook = hooks_utils.trigger_comment_pull_request_hook
comments: added new events for comment editing to handle them in integrations.
r4444 elif action == 'comment_edit':
trigger_hook = hooks_utils.trigger_comment_pull_request_edit_hook
project: added all source files and assets
r1 else:
return
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 log.debug('Handling pull_request %s trigger_pull_request_hook with action %s and hook: %s',
pull_request, action, trigger_hook)
project: added all source files and assets
r1 trigger_hook(
username=user.username,
repo_name=pull_request.target_repo.repo_name,
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 repo_type=target_scm.alias,
events: trigger 'review_status_change' when reviewers are updated....
r3415 pull_request=pull_request,
data=data)
project: added all source files and assets
r1
def _get_commit_ids(self, pull_request):
"""
Return the commit ids of the merged pull request.
This method is not dealing correctly yet with the lack of autoupdates
nor with the implicit target updates.
For example: if a commit in the source repo is already in the target it
will be reported anyways.
"""
merge_rev = pull_request.merge_rev
if merge_rev is None:
raise ValueError('This pull request was not merged yet')
commit_ids = list(pull_request.revisions)
if merge_rev not in commit_ids:
commit_ids.append(merge_rev)
return commit_ids
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 def merge_repo(self, pull_request, user, extras):
statsd/audit-logs: cleanup push/pull user agent code....
r4858 repo_type = pull_request.source_repo.repo_type
models: major update for python3,...
r5070 log.debug("Merging pull request %s", pull_request)
statsd/audit-logs: cleanup push/pull user agent code....
r4858 extras['user_agent'] = '{}/internal-merge'.format(repo_type)
project: added all source files and assets
r1 merge_state = self._merge_pull_request(pull_request, user, extras)
if merge_state.executed:
dan
pull-requests: ensure merge response provide more details...
r3339 log.debug("Merge was successful, updating the pull request comments.")
project: added all source files and assets
r1 self._comment_and_close_pr(pull_request, user, merge_state)
audit-logs: implemented pull request and comment events.
r1807
self._log_audit_action(
'repo.pull_request.merge',
{'merge_state': merge_state.__dict__},
user, pull_request)
pull_request: Increase debug logging around merge.
r149 else:
models: major update for python3,...
r5070 log.warning("Merge failed, not updating the pull request.")
project: added all source files and assets
r1 return merge_state
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 def _merge_pull_request(self, pull_request, user, extras, merge_msg=None):
project: added all source files and assets
r1 target_vcs = pull_request.target_repo.scm_instance()
source_vcs = pull_request.source_repo.scm_instance()
models: major update for python3,...
r5070 message = safe_str(merge_msg or vcs_settings.MERGE_MESSAGE_TMPL).format(
vcs: use a real two part name for merge operation....
r3040 pr_id=pull_request.pull_request_id,
pr_title=pull_request.title,
pull-requests: Add possibility to include pull request description into merge commit message
r4821 pr_desc=pull_request.description,
vcs: use a real two part name for merge operation....
r3040 source_repo=source_vcs.name,
source_ref_name=pull_request.source_ref_parts.name,
target_repo=target_vcs.name,
target_ref_name=pull_request.target_ref_parts.name,
)
project: added all source files and assets
r1
workspace_id = self._workspace_id(pull_request)
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 repo_id = pull_request.target_repo.repo_id
Martin Bornhold
settings: Read 'rebase-merge' config option and pass it to the VCS insntacen on merge.
r361 use_rebase = self._use_rebase_for_merging(pull_request)
Mathieu Cantin
mercurial: Add option to close a branch before merging
r2055 close_branch = self._close_branch_before_merging(pull_request)
pull-requests: small code cleanup to define other type of merge username...
r4191 user_name = self._user_name_for_merging(pull_request, user)
project: added all source files and assets
r1
vcs: use a real two part name for merge operation....
r3040 target_ref = self._refresh_reference(
pull_request.target_ref_parts, target_vcs)
tests: fixed test suite for celery adoption
r5607 callback_daemon, extras = prepare_callback_daemon(extras, protocol=vcs_settings.HOOKS_PROTOCOL)
project: added all source files and assets
r1
with callback_daemon:
# TODO: johbo: Implement a clean way to run a config_override
# for a single call.
target_vcs.config.set(
'rhodecode', 'RC_SCM_DATA', json.dumps(extras))
vcs: use a real two part name for merge operation....
r3040
project: added all source files and assets
r1 merge_state = target_vcs.merge(
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 repo_id, workspace_id, target_ref, source_vcs,
pull_request.source_ref_parts,
vcs: use a real two part name for merge operation....
r3040 user_name=user_name, user_email=user.email,
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 message=message, use_rebase=use_rebase,
Mathieu Cantin
mercurial: Add option to close a branch before merging
r2055 close_branch=close_branch)
models: major update for python3,...
r5070
project: added all source files and assets
r1 return merge_state
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 def _comment_and_close_pr(self, pull_request, user, merge_state, close_msg=None):
Martin Bornhold
pr-shadow: Adapt to new merge response object.
r1052 pull_request.merge_rev = merge_state.merge_ref.commit_id
project: added all source files and assets
r1 pull_request.updated_on = datetime.datetime.now()
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 close_msg = close_msg or 'Pull request merged and closed'
project: added all source files and assets
r1
comments: renamed ChangesetCommentsModel to CommentsModel to reflect what it actually does....
r1323 CommentsModel().create(
models: major update for python3,...
r5070 text=safe_str(close_msg),
project: added all source files and assets
r1 repo=pull_request.target_repo.repo_id,
user=user.user_id,
pull_request=pull_request.pull_request_id,
f_path=None,
line_no=None,
closing_pr=True
)
Session().add(pull_request)
Session().flush()
# TODO: paris: replace invalidation with less radical solution
ScmModel().mark_for_invalidation(
pull_request.target_repo.repo_name)
events: trigger 'review_status_change' when reviewers are updated....
r3415 self.trigger_pull_request_hook(pull_request, user, 'merge')
project: added all source files and assets
r1
def has_valid_update_type(self, pull_request):
source_ref_type = pull_request.source_ref_parts.type
pull-requests: validate ref types for pull request so users cannot provide wrongs ones.
r3302 return source_ref_type in self.REF_TYPES
project: added all source files and assets
r1
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 def get_flow_commits(self, pull_request):
# source repo
source_ref_name = pull_request.source_ref_parts.name
source_ref_type = pull_request.source_ref_parts.type
source_ref_id = pull_request.source_ref_parts.commit_id
source_repo = pull_request.source_repo.scm_instance()
try:
if source_ref_type in self.REF_TYPES:
backends: use reference explicitly to properly translate GIT references to commits such as numeric branches
r4653 source_commit = source_repo.get_commit(
source_ref_name, reference_obj=pull_request.source_ref_parts)
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 else:
source_commit = source_repo.get_commit(source_ref_id)
except CommitDoesNotExistError:
raise SourceRefMissing()
# target repo
target_ref_name = pull_request.target_ref_parts.name
target_ref_type = pull_request.target_ref_parts.type
target_ref_id = pull_request.target_ref_parts.commit_id
target_repo = pull_request.target_repo.scm_instance()
try:
if target_ref_type in self.REF_TYPES:
backends: use reference explicitly to properly translate GIT references to commits such as numeric branches
r4653 target_commit = target_repo.get_commit(
target_ref_name, reference_obj=pull_request.target_ref_parts)
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 else:
target_commit = target_repo.get_commit(target_ref_id)
except CommitDoesNotExistError:
raise TargetRefMissing()
return source_commit, target_commit
pull-requests: added update pull-requests email+notifications...
r4120 def update_commits(self, pull_request, updating_user):
project: added all source files and assets
r1 """
Get the updated list of commits for the pull request
and return the new pull request version and the list
of commits processed by this update action
pull-requests: added update pull-requests email+notifications...
r4120
updating_user is the user_object who triggered the update
project: added all source files and assets
r1 """
pull_request = self.__get_pull_request(pull_request)
source_ref_type = pull_request.source_ref_parts.type
source_ref_name = pull_request.source_ref_parts.name
source_ref_id = pull_request.source_ref_parts.commit_id
pull-request: force update pull-request in case of the target repo reference changes....
r1595 target_ref_type = pull_request.target_ref_parts.type
target_ref_name = pull_request.target_ref_parts.name
target_ref_id = pull_request.target_ref_parts.commit_id
project: added all source files and assets
r1 if not self.has_valid_update_type(pull_request):
pull-requests: introduce operation state for pull requests to prevent from...
r3371 log.debug("Skipping update of pull request %s due to ref type: %s",
pull_request, source_ref_type)
Martin Bornhold
pr: Return update response objects instead of tuples.
r1074 return UpdateResponse(
Martin Bornhold
pr: Rename update response flag `success` -> `executed`...
r1083 executed=False,
pull-request: fixed typo in wrong ref type error, and added which...
r1687 reason=UpdateFailureReason.WRONG_REF_TYPE,
pull-requests: added update pull-requests email+notifications...
r4120 old=pull_request, new=None, common_ancestor_id=None, commit_changes=None,
pull-requests: change the update commits logic to handle target changes better....
r1601 source_changed=False, target_changed=False)
project: added all source files and assets
r1
Martin Bornhold
pr: Catch errors if target or source reference are missing during commit update. #3950
r1075 try:
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 source_commit, target_commit = self.get_flow_commits(pull_request)
except SourceRefMissing:
Martin Bornhold
pr: Catch errors if target or source reference are missing during commit update. #3950
r1075 return UpdateResponse(
Martin Bornhold
pr: Rename update response flag `success` -> `executed`...
r1083 executed=False,
Martin Bornhold
pr: Catch errors if target or source reference are missing during commit update. #3950
r1075 reason=UpdateFailureReason.MISSING_SOURCE_REF,
pull-requests: added update pull-requests email+notifications...
r4120 old=pull_request, new=None, common_ancestor_id=None, commit_changes=None,
pull-requests: change the update commits logic to handle target changes better....
r1601 source_changed=False, target_changed=False)
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 except TargetRefMissing:
pull-request: force update pull-request in case of the target repo reference changes....
r1595 return UpdateResponse(
executed=False,
reason=UpdateFailureReason.MISSING_TARGET_REF,
pull-requests: added update pull-requests email+notifications...
r4120 old=pull_request, new=None, common_ancestor_id=None, commit_changes=None,
pull-requests: change the update commits logic to handle target changes better....
r1601 source_changed=False, target_changed=False)
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317
source_changed = source_ref_id != source_commit.raw_id
pull-request: force update pull-request in case of the target repo reference changes....
r1595 target_changed = target_ref_id != target_commit.raw_id
if not (source_changed or target_changed):
project: added all source files and assets
r1 log.debug("Nothing changed in pull request %s", pull_request)
Martin Bornhold
pr: Return update response objects instead of tuples.
r1074 return UpdateResponse(
Martin Bornhold
pr: Rename update response flag `success` -> `executed`...
r1083 executed=False,
Martin Bornhold
pr: Return update response objects instead of tuples.
r1074 reason=UpdateFailureReason.NO_CHANGE,
pull-requests: added update pull-requests email+notifications...
r4120 old=pull_request, new=None, common_ancestor_id=None, commit_changes=None,
pull-requests: change the update commits logic to handle target changes better....
r1601 source_changed=target_changed, target_changed=source_changed)
project: added all source files and assets
r1
pull-request: force update pull-request in case of the target repo reference changes....
r1595 change_in_found = 'target repo' if target_changed else 'source repo'
log.debug('Updating pull request because of change in %s detected',
change_in_found)
project: added all source files and assets
r1
pull-request: force update pull-request in case of the target repo reference changes....
r1595 # Finally there is a need for an update, in case of source change
# we create a new version, else just an update
if source_changed:
pull_request_version = self._create_version_from_snapshot(pull_request)
self._link_comments_to_version(pull_request_version)
else:
pull-requests: updates on pull requests that don't have versions shouldn't...
r1596 try:
ver = pull_request.versions[-1]
except IndexError:
ver = None
pull-request: force update pull-request in case of the target repo reference changes....
r1595 pull_request.pull_request_version_id = \
ver.pull_request_version_id if ver else None
pull_request_version = pull_request
project: added all source files and assets
r1
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 source_repo = pull_request.source_repo.scm_instance()
target_repo = pull_request.target_repo.scm_instance()
project: added all source files and assets
r1
# re-compute commit ids
pr-model: don't use set to calculate commit ranges as they generate random order.
r1372 old_commit_ids = pull_request.revisions
vcs: optimized pre-load attributes for better caching.
r3850 pre_load = ["author", "date", "message", "branch"]
project: added all source files and assets
r1 commit_ranges = target_repo.compare(
target_commit.raw_id, source_commit.raw_id, source_repo, merge=True,
pre_load=pre_load)
pull-requests: fix way how pull-request calculates common ancestors....
r4346 target_ref = target_commit.raw_id
source_ref = source_commit.raw_id
ancestor_commit_id = target_repo.get_common_ancestor(
target_ref, source_ref, source_repo)
if not ancestor_commit_id:
raise ValueError(
'cannot calculate diff info without a common ancestor. '
'Make sure both repositories are related, and have a common forking commit.')
pull_request.common_ancestor_id = ancestor_commit_id
project: added all source files and assets
r1
chore(code-cleanups): small fixes for readability
r5197 pull_request.source_ref = f'{source_ref_type}:{source_ref_name}:{source_commit.raw_id}'
pull_request.target_ref = f'{target_ref_type}:{target_ref_name}:{ancestor_commit_id}'
pull-request: force update pull-request in case of the target repo reference changes....
r1595
project: added all source files and assets
r1 pull_request.revisions = [
commit.raw_id for commit in reversed(commit_ranges)]
pull_request.updated_on = datetime.datetime.now()
Session().add(pull_request)
pr-model: don't use set to calculate commit ranges as they generate random order.
r1372 new_commit_ids = pull_request.revisions
project: added all source files and assets
r1
old_diff_data, new_diff_data = self._generate_update_diffs(
pull_request, pull_request_version)
pull-requests: change the update commits logic to handle target changes better....
r1601 # calculate commit and file changes
pull-requests: added update pull-requests email+notifications...
r4120 commit_changes = self._calculate_commit_id_changes(
pull-requests: change the update commits logic to handle target changes better....
r1601 old_commit_ids, new_commit_ids)
file_changes = self._calculate_file_changes(
old_diff_data, new_diff_data)
# set comments as outdated if DIFFS changed
comments: renamed ChangesetCommentsModel to CommentsModel to reflect what it actually does....
r1323 CommentsModel().outdate_comments(
project: added all source files and assets
r1 pull_request, old_diff_data=old_diff_data,
new_diff_data=new_diff_data)
pull-requests: added update pull-requests email+notifications...
r4120 valid_commit_changes = (commit_changes.added or commit_changes.removed)
pull-requests: change the update commits logic to handle target changes better....
r1601 file_node_changes = (
file_changes.added or file_changes.modified or file_changes.removed)
pull-requests: added update pull-requests email+notifications...
r4120 pr_has_changes = valid_commit_changes or file_node_changes
project: added all source files and assets
r1
pull-requests: change the update commits logic to handle target changes better....
r1601 # Add an automatic comment to the pull request, in case
# anything has changed
if pr_has_changes:
update_comment = CommentsModel().create(
pull-requests: added update pull-requests email+notifications...
r4120 text=self._render_update_message(ancestor_commit_id, commit_changes, file_changes),
pull-requests: change the update commits logic to handle target changes better....
r1601 repo=pull_request.target_repo,
project: added all source files and assets
r1 user=pull_request.author,
pull_request=pull_request,
pull-requests: change the update commits logic to handle target changes better....
r1601 send_email=False, renderer=DEFAULT_COMMENTS_RENDERER)
# Update status to "Under Review" for added commits
pull-requests: added update pull-requests email+notifications...
r4120 for commit_id in commit_changes.added:
pull-requests: change the update commits logic to handle target changes better....
r1601 ChangesetStatusModel().set_status(
repo=pull_request.source_repo,
status=ChangesetStatus.STATUS_UNDER_REVIEW,
comment=update_comment,
user=pull_request.author,
pull_request=pull_request,
revision=commit_id)
project: added all source files and assets
r1
pull-requests: optimize db transaction logic....
r4712 # initial commit
Session().commit()
if pr_has_changes:
pull-requests: added update pull-requests email+notifications...
r4120 # send update email to users
try:
self.notify_users(pull_request=pull_request, updating_user=updating_user,
ancestor_commit_id=ancestor_commit_id,
commit_changes=commit_changes,
file_changes=file_changes)
pull-requests: optimize db transaction logic....
r4712 Session().commit()
pull-requests: added update pull-requests email+notifications...
r4120 except Exception:
log.exception('Failed to send email notification to users')
pull-requests: optimize db transaction logic....
r4712 Session().rollback()
pull-requests: added update pull-requests email+notifications...
r4120
project: added all source files and assets
r1 log.debug(
'Updated pull request %s, added_ids: %s, common_ids: %s, '
'removed_ids: %s', pull_request.pull_request_id,
pull-requests: added update pull-requests email+notifications...
r4120 commit_changes.added, commit_changes.common, commit_changes.removed)
pull-requests: change the update commits logic to handle target changes better....
r1601 log.debug(
'Updated pull request with the following file changes: %s',
file_changes)
project: added all source files and assets
r1
log.info(
"Updated pull request %s from commit %s to commit %s, "
"stored new version %s of this pull request.",
pull_request.pull_request_id, source_ref_id,
pull_request.source_ref_parts.commit_id,
pull_request_version.pull_request_version_id)
pull-requests: optimize db transaction logic....
r4712
events: trigger 'review_status_change' when reviewers are updated....
r3415 self.trigger_pull_request_hook(pull_request, pull_request.author, 'update')
dan
reviewers: store reviewer reasons to database, fixes #4238
r873
Martin Bornhold
pr: Return update response objects instead of tuples.
r1074 return UpdateResponse(
Martin Bornhold
pr: Rename update response flag `success` -> `executed`...
r1083 executed=True, reason=UpdateFailureReason.NONE,
pull-requests: added update pull-requests email+notifications...
r4120 old=pull_request, new=pull_request_version,
common_ancestor_id=ancestor_commit_id, commit_changes=commit_changes,
pull-requests: change the update commits logic to handle target changes better....
r1601 source_changed=source_changed, target_changed=target_changed)
project: added all source files and assets
r1
def _create_version_from_snapshot(self, pull_request):
version = PullRequestVersion()
version.title = pull_request.title
version.description = pull_request.description
version.status = pull_request.status
pull-requests: introduce operation state for pull requests to prevent from...
r3371 version.pull_request_state = pull_request.pull_request_state
pull-requests: when creating a new version set the created_date to now instead of...
r1207 version.created_on = datetime.datetime.now()
project: added all source files and assets
r1 version.updated_on = pull_request.updated_on
version.user_id = pull_request.user_id
version.source_repo = pull_request.source_repo
version.source_ref = pull_request.source_ref
version.target_repo = pull_request.target_repo
version.target_ref = pull_request.target_ref
version._last_merge_source_rev = pull_request._last_merge_source_rev
version._last_merge_target_rev = pull_request._last_merge_target_rev
db: use a wrapper on pull requests _last_merge_status to ensure this is always INT....
r1968 version.last_merge_status = pull_request.last_merge_status
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 version.last_merge_metadata = pull_request.last_merge_metadata
Martin Bornhold
pr-shadow: Adapt to new merge response object.
r1052 version.shadow_merge_ref = pull_request.shadow_merge_ref
project: added all source files and assets
r1 version.merge_rev = pull_request.merge_rev
pull-request: extended default reviewers functionality....
r1769 version.reviewer_data = pull_request.reviewer_data
project: added all source files and assets
r1
version.revisions = pull_request.revisions
pull-requests: fix way how pull-request calculates common ancestors....
r4346 version.common_ancestor_id = pull_request.common_ancestor_id
project: added all source files and assets
r1 version.pull_request = pull_request
Session().add(version)
Session().flush()
return version
def _generate_update_diffs(self, pull_request, pull_request_version):
pr-versioning: implemented versioning for pull requests....
r1368
project: added all source files and assets
r1 diff_context = (
self.DIFF_CONTEXT +
comments: renamed ChangesetCommentsModel to CommentsModel to reflect what it actually does....
r1323 CommentsModel.needed_extra_diff_context())
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 hide_whitespace_changes = False
pr-versioning: implemented versioning for pull requests....
r1368 source_repo = pull_request_version.source_repo
source_ref_id = pull_request_version.source_ref_parts.commit_id
target_ref_id = pull_request_version.target_ref_parts.commit_id
project: added all source files and assets
r1 old_diff = self._get_diff_from_pr_or_version(
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 source_repo, source_ref_id, target_ref_id,
hide_whitespace_changes=hide_whitespace_changes, diff_context=diff_context)
pr-versioning: implemented versioning for pull requests....
r1368
source_repo = pull_request.source_repo
source_ref_id = pull_request.source_ref_parts.commit_id
target_ref_id = pull_request.target_ref_parts.commit_id
project: added all source files and assets
r1 new_diff = self._get_diff_from_pr_or_version(
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 source_repo, source_ref_id, target_ref_id,
hide_whitespace_changes=hide_whitespace_changes, diff_context=diff_context)
project: added all source files and assets
r1
models: major update for python3,...
r5070 # NOTE: this was using diff_format='gitdiff'
old_diff_data = diffs.DiffProcessor(old_diff, diff_format='newdiff')
project: added all source files and assets
r1 old_diff_data.prepare()
models: major update for python3,...
r5070 new_diff_data = diffs.DiffProcessor(new_diff, diff_format='newdiff')
project: added all source files and assets
r1 new_diff_data.prepare()
return old_diff_data, new_diff_data
def _link_comments_to_version(self, pull_request_version):
"""
Link all unlinked comments of this pull request to the given version.
:param pull_request_version: The `PullRequestVersion` to which
the comments shall be linked.
"""
pull_request = pull_request_version.pull_request
pull-requests: make sure we process comments in the order of IDS when...
r1705 comments = ChangesetComment.query()\
.filter(
# TODO: johbo: Should we query for the repo at all here?
# Pending decision on how comments of PRs are to be related
# to either the source repo, the target repo or no repo at all.
ChangesetComment.repo_id == pull_request.target_repo.repo_id,
ChangesetComment.pull_request == pull_request,
lint: use null() to compare to == None for linters to be happy
r5180 ChangesetComment.pull_request_version == null())\
pull-requests: make sure we process comments in the order of IDS when...
r1705 .order_by(ChangesetComment.comment_id.asc())
project: added all source files and assets
r1
# TODO: johbo: Find out why this breaks if it is done in a bulk
# operation.
for comment in comments:
comment.pull_request_version_id = (
pull_request_version.pull_request_version_id)
Session().add(comment)
def _calculate_commit_id_changes(self, old_ids, new_ids):
pr-versioning: implemented versioning for pull requests....
r1368 added = [x for x in new_ids if x not in old_ids]
common = [x for x in new_ids if x in old_ids]
removed = [x for x in old_ids if x not in new_ids]
total = new_ids
return ChangeTuple(added, common, removed, total)
project: added all source files and assets
r1
def _calculate_file_changes(self, old_diff_data, new_diff_data):
old_files = OrderedDict()
for diff_data in old_diff_data.parsed_diff:
old_files[diff_data['filename']] = md5_safe(diff_data['raw_diff'])
added_files = []
modified_files = []
removed_files = []
for diff_data in new_diff_data.parsed_diff:
new_filename = diff_data['filename']
new_hash = md5_safe(diff_data['raw_diff'])
old_hash = old_files.get(new_filename)
if not old_hash:
pull-requests: handle case when removing existing files from a repository in versioning diff.
r4129 # file is not present in old diff, we have to figure out from parsed diff
# operation ADD/REMOVE
operations_dict = diff_data['stats']['ops']
if diffs.DEL_FILENODE in operations_dict:
removed_files.append(new_filename)
else:
added_files.append(new_filename)
project: added all source files and assets
r1 else:
if new_hash != old_hash:
modified_files.append(new_filename)
# now remove a file from old, since we have seen it already
del old_files[new_filename]
# removed files is when there are present in old, but not in NEW,
# since we remove old files that are present in new diff, left-overs
# if any should be the removed files
removed_files.extend(old_files.keys())
return FileChangeTuple(added_files, modified_files, removed_files)
pull-requests: added update pull-requests email+notifications...
r4120 def _render_update_message(self, ancestor_commit_id, changes, file_changes):
project: added all source files and assets
r1 """
render the message using DEFAULT_COMMENTS_RENDERER (RST renderer),
so it's always looking the same disregarding on which default
renderer system is using.
pull-requests: added update pull-requests email+notifications...
r4120 :param ancestor_commit_id: ancestor raw_id
project: added all source files and assets
r1 :param changes: changes named tuple
:param file_changes: file changes named tuple
"""
new_status = ChangesetStatus.get_status_lbl(
ChangesetStatus.STATUS_UNDER_REVIEW)
changed_files = (
file_changes.added + file_changes.modified + file_changes.removed)
params = {
'under_review_label': new_status,
'added_commits': changes.added,
'removed_commits': changes.removed,
'changed_files': changed_files,
'added_files': file_changes.added,
'modified_files': file_changes.modified,
'removed_files': file_changes.removed,
pull-requests: added update pull-requests email+notifications...
r4120 'ancestor_commit_id': ancestor_commit_id
project: added all source files and assets
r1 }
renderer = RstTemplateRenderer()
return renderer.render('pull_request_update.mako', **params)
pull-requests: make the renderer stored and saved for each pull requests....
r2903 def edit(self, pull_request, title, description, description_renderer, user):
project: added all source files and assets
r1 pull_request = self.__get_pull_request(pull_request)
audit-logs: implemented pull request and comment events.
r1807 old_data = pull_request.get_api_data(with_merge_state=False)
project: added all source files and assets
r1 if pull_request.is_closed():
raise ValueError('This pull request is closed')
if title:
pull_request.title = title
pull_request.description = description
pull_request.updated_on = datetime.datetime.now()
pull-requests: make the renderer stored and saved for each pull requests....
r2903 pull_request.description_renderer = description_renderer
project: added all source files and assets
r1 Session().add(pull_request)
audit-logs: implemented pull request and comment events.
r1807 self._log_audit_action(
'repo.pull_request.edit', {'old_data': old_data},
user, pull_request)
project: added all source files and assets
r1
audit-logs: implemented pull request and comment events.
r1807 def update_reviewers(self, pull_request, reviewer_data, user):
dan
reviewers: store reviewer reasons to database, fixes #4238
r873 """
Update the reviewers in the pull request
:param pull_request: the pr to update
pull-request: extended default reviewers functionality....
r1769 :param reviewer_data: list of tuples
reviewers: added observers as another way to define reviewers....
r4500 [(user, ['reason1', 'reason2'], mandatory_flag, role, [rules])]
:param user: current use who triggers this action
dan
reviewers: store reviewer reasons to database, fixes #4238
r873 """
reviewers: added observers as another way to define reviewers....
r4500
pull-requests: forbid doing any changes on closed pull-requests....
r2383 pull_request = self.__get_pull_request(pull_request)
if pull_request.is_closed():
raise ValueError('This pull request is closed')
dan
reviewers: store reviewer reasons to database, fixes #4238
r873
pull-request: extended default reviewers functionality....
r1769 reviewers = {}
reviewers: added observers as another way to define reviewers....
r4500 for user_id, reasons, mandatory, role, rules in reviewer_data:
py3: remove use of pyramid.compat
r4908 if isinstance(user_id, (int, str)):
dan
reviewers: store reviewer reasons to database, fixes #4238
r873 user_id = self._get_user(user_id).user_id
pull-request: extended default reviewers functionality....
r1769 reviewers[user_id] = {
reviewers: added observers as another way to define reviewers....
r4500 'reasons': reasons, 'mandatory': mandatory, 'role': role}
dan
reviewers: store reviewer reasons to database, fixes #4238
r873
pull-request: extended default reviewers functionality....
r1769 reviewers_ids = set(reviewers.keys())
reviewers: added observers as another way to define reviewers....
r4500 current_reviewers = PullRequestReviewers.get_pull_request_reviewers(
pull_request.pull_request_id, role=PullRequestReviewers.ROLE_REVIEWER)
project: added all source files and assets
r1 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
ids_to_add = reviewers_ids.difference(current_reviewers_ids)
ids_to_remove = current_reviewers_ids.difference(reviewers_ids)
log.debug("Adding %s reviewers", ids_to_add)
log.debug("Removing %s reviewers", ids_to_remove)
changed = False
pull-requests: fixed a bug in removal of multiple reviewers
r3575 added_audit_reviewers = []
removed_audit_reviewers = []
project: added all source files and assets
r1 for uid in ids_to_add:
changed = True
_usr = self._get_user(uid)
pull-request: extended default reviewers functionality....
r1769 reviewer = PullRequestReviewers()
reviewer.user = _usr
reviewer.pull_request = pull_request
reviewer.reasons = reviewers[uid]['reasons']
# NOTE(marcink): mandatory shouldn't be changed now
audit-logs: implemented pull request and comment events.
r1807 # reviewer.mandatory = reviewers[uid]['reasons']
reviewers: added observers as another way to define reviewers....
r4500 # NOTE(marcink): role should be hardcoded, so we won't edit it.
reviewer.role = PullRequestReviewers.ROLE_REVIEWER
project: added all source files and assets
r1 Session().add(reviewer)
pull-requests: fixed a bug in removal of multiple reviewers
r3575 added_audit_reviewers.append(reviewer.get_dict())
project: added all source files and assets
r1
for uid in ids_to_remove:
changed = True
reviewers: added observers as another way to define reviewers....
r4500 # NOTE(marcink): we fetch "ALL" reviewers objects using .all().
# This is an edge case that handles previous state of having the same reviewer twice.
pull-requests: fixed a bug in removal of multiple reviewers
r3575 # this CAN happen due to the lack of DB checks
pull-request: lock button when updating reviewers to forbid multi-submit....
r1578 reviewers = PullRequestReviewers.query()\
project: added all source files and assets
r1 .filter(PullRequestReviewers.user_id == uid,
reviewers: added observers as another way to define reviewers....
r4500 PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER,
project: added all source files and assets
r1 PullRequestReviewers.pull_request == pull_request)\
pull-request: lock button when updating reviewers to forbid multi-submit....
r1578 .all()
pull-requests: fixed a bug in removal of multiple reviewers
r3575
pull-request: lock button when updating reviewers to forbid multi-submit....
r1578 for obj in reviewers:
pull-requests: fixed a bug in removal of multiple reviewers
r3575 added_audit_reviewers.append(obj.get_dict())
pull-request: lock button when updating reviewers to forbid multi-submit....
r1578 Session().delete(obj)
project: added all source files and assets
r1 if changed:
pull-requests: fixed a bug in removal of multiple reviewers
r3575 Session().expire_all()
project: added all source files and assets
r1 pull_request.updated_on = datetime.datetime.now()
Session().add(pull_request)
pull-requests: fixed a bug in removal of multiple reviewers
r3575 # finally store audit logs
for user_data in added_audit_reviewers:
self._log_audit_action(
'repo.pull_request.reviewer.add', {'data': user_data},
user, pull_request)
for user_data in removed_audit_reviewers:
self._log_audit_action(
'repo.pull_request.reviewer.delete', {'old_data': user_data},
user, pull_request)
observers: code cleanups and fixed tests.
r4519 self.notify_reviewers(pull_request, ids_to_add, user)
reviewers: added observers as another way to define reviewers....
r4500 return ids_to_add, ids_to_remove
def update_observers(self, pull_request, observer_data, user):
"""
Update the observers in the pull request
:param pull_request: the pr to update
:param observer_data: list of tuples
[(user, ['reason1', 'reason2'], mandatory_flag, role, [rules])]
:param user: current use who triggers this action
"""
pull_request = self.__get_pull_request(pull_request)
if pull_request.is_closed():
raise ValueError('This pull request is closed')
observers = {}
for user_id, reasons, mandatory, role, rules in observer_data:
py3: remove use of pyramid.compat
r4908 if isinstance(user_id, (int, str)):
reviewers: added observers as another way to define reviewers....
r4500 user_id = self._get_user(user_id).user_id
observers[user_id] = {
'reasons': reasons, 'observers': mandatory, 'role': role}
observers_ids = set(observers.keys())
current_observers = PullRequestReviewers.get_pull_request_reviewers(
pull_request.pull_request_id, role=PullRequestReviewers.ROLE_OBSERVER)
current_observers_ids = set([x.user.user_id for x in current_observers])
ids_to_add = observers_ids.difference(current_observers_ids)
ids_to_remove = current_observers_ids.difference(observers_ids)
log.debug("Adding %s observer", ids_to_add)
log.debug("Removing %s observer", ids_to_remove)
changed = False
added_audit_observers = []
removed_audit_observers = []
for uid in ids_to_add:
changed = True
_usr = self._get_user(uid)
observer = PullRequestReviewers()
observer.user = _usr
observer.pull_request = pull_request
observer.reasons = observers[uid]['reasons']
# NOTE(marcink): mandatory shouldn't be changed now
# observer.mandatory = observer[uid]['reasons']
# NOTE(marcink): role should be hardcoded, so we won't edit it.
observer.role = PullRequestReviewers.ROLE_OBSERVER
Session().add(observer)
added_audit_observers.append(observer.get_dict())
for uid in ids_to_remove:
changed = True
# NOTE(marcink): we fetch "ALL" reviewers objects using .all().
# This is an edge case that handles previous state of having the same reviewer twice.
# this CAN happen due to the lack of DB checks
observers = PullRequestReviewers.query()\
.filter(PullRequestReviewers.user_id == uid,
PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER,
PullRequestReviewers.pull_request == pull_request)\
.all()
for obj in observers:
added_audit_observers.append(obj.get_dict())
Session().delete(obj)
if changed:
Session().expire_all()
pull_request.updated_on = datetime.datetime.now()
Session().add(pull_request)
# finally store audit logs
for user_data in added_audit_observers:
self._log_audit_action(
'repo.pull_request.observer.add', {'data': user_data},
user, pull_request)
for user_data in removed_audit_observers:
self._log_audit_action(
'repo.pull_request.observer.delete', {'old_data': user_data},
user, pull_request)
observers: code cleanups and fixed tests.
r4519 self.notify_observers(pull_request, ids_to_add, user)
project: added all source files and assets
r1 return ids_to_add, ids_to_remove
events: expose permalink urls for different set of object....
r1788 def get_url(self, pull_request, request=None, permalink=False):
if not request:
request = get_current_request()
if permalink:
return request.route_url(
'pull_requests_global',
pull_request_id=pull_request.pull_request_id,)
else:
pull-requests: prepare the migration of pull request to pyramid....
r1813 return request.route_url('pullrequest_show',
events: expose permalink urls for different set of object....
r1788 repo_name=safe_str(pull_request.target_repo.repo_name),
pull_request_id=pull_request.pull_request_id,)
dan
events: add serialization .to_dict() to events based on marshmallow
r379
events: expose shadow repo build url.
r2582 def get_shadow_clone_url(self, pull_request, request=None):
Martin Bornhold
pr: Unify clone url generation of shadow repository.
r897 """
Returns qualified url pointing to the shadow repository. If this pull
request is closed there is no shadow repository and ``None`` will be
returned.
"""
if pull_request.is_closed():
return None
else:
python3: fix urllib usage
r4914 pr_url = urllib.parse.unquote(self.get_url(pull_request, request=request))
models: major update for python3,...
r5070 return safe_str('{pr_url}/repository'.format(pr_url=pr_url))
Martin Bornhold
pr: Display link to shadow repository on pull request page.
r896
reviewers: added observers as another way to define reviewers....
r4500 def _notify_reviewers(self, pull_request, user_ids, role, user):
# notification to reviewers/observers
if not user_ids:
project: added all source files and assets
r1 return
reviewers: added observers as another way to define reviewers....
r4500 log.debug('Notify following %s users about pull-request %s', role, user_ids)
pull-requests: handle exceptions in state change and improve logging.
r3828
project: added all source files and assets
r1 pull_request_obj = pull_request
# get the current participants of this pull request
reviewers: added observers as another way to define reviewers....
r4500 recipients = user_ids
project: added all source files and assets
r1 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST
pr_source_repo = pull_request_obj.source_repo
pr_target_repo = pull_request_obj.target_repo
pull-requests: prepare the migration of pull request to pyramid....
r1813 pr_url = h.route_url('pullrequest_show',
dan
emails: updated emails design and data structure they provide....
r4038 repo_name=pr_target_repo.repo_name,
pull_request_id=pull_request_obj.pull_request_id,)
project: added all source files and assets
r1
# set some variables for email notification
repo-summary: re-implemented summary view as pyramid....
r1785 pr_target_repo_url = h.route_url(
'repo_summary', repo_name=pr_target_repo.repo_name)
project: added all source files and assets
r1
repo-summary: re-implemented summary view as pyramid....
r1785 pr_source_repo_url = h.route_url(
'repo_summary', repo_name=pr_source_repo.repo_name)
project: added all source files and assets
r1
# pull request specifics
pull_request_commits = [
(x.raw_id, x.message)
for x in map(pr_source_repo.get_commit, pull_request.revisions)]
reviewers: added observers as another way to define reviewers....
r4500 current_rhodecode_user = user
project: added all source files and assets
r1 kwargs = {
reviewers: added observers as another way to define reviewers....
r4500 'user': current_rhodecode_user,
'pull_request_author': pull_request.author,
project: added all source files and assets
r1 'pull_request': pull_request_obj,
'pull_request_commits': pull_request_commits,
'pull_request_target_repo': pr_target_repo,
'pull_request_target_repo_url': pr_target_repo_url,
'pull_request_source_repo': pr_source_repo,
'pull_request_source_repo_url': pr_source_repo_url,
'pull_request_url': pr_url,
emails: set References header for threading in mail user agents even with different subjects...
r4447 'thread_ids': [pr_url],
reviewers: added observers as another way to define reviewers....
r4500 'user_role': role
project: added all source files and assets
r1 }
# create notification objects, and emails
NotificationModel().create(
reviewers: added observers as another way to define reviewers....
r4500 created_by=current_rhodecode_user,
notifications: skip double rendering just to generate email title/desc
r4560 notification_subject='', # Filled in based on the notification_type
notification_body='', # Filled in based on the notification_type
project: added all source files and assets
r1 notification_type=notification_type,
recipients=recipients,
email_kwargs=kwargs,
)
reviewers: added observers as another way to define reviewers....
r4500 def notify_reviewers(self, pull_request, reviewers_ids, user):
return self._notify_reviewers(pull_request, reviewers_ids,
PullRequestReviewers.ROLE_REVIEWER, user)
def notify_observers(self, pull_request, observers_ids, user):
return self._notify_reviewers(pull_request, observers_ids,
PullRequestReviewers.ROLE_OBSERVER, user)
pull-requests: added update pull-requests email+notifications...
r4120 def notify_users(self, pull_request, updating_user, ancestor_commit_id,
commit_changes, file_changes):
updating_user_id = updating_user.user_id
reviewers: use common function to obtain reviewers with filter of role.
r4514 reviewers = set([x.user.user_id for x in pull_request.get_pull_request_reviewers()])
pull-requests: added update pull-requests email+notifications...
r4120 # NOTE(marcink): send notification to all other users except to
# person who updated the PR
recipients = reviewers.difference(set([updating_user_id]))
log.debug('Notify following recipients about pull-request update %s', recipients)
pull_request_obj = pull_request
# send email about the update
changed_files = (
file_changes.added + file_changes.modified + file_changes.removed)
pr_source_repo = pull_request_obj.source_repo
pr_target_repo = pull_request_obj.target_repo
pr_url = h.route_url('pullrequest_show',
repo_name=pr_target_repo.repo_name,
pull_request_id=pull_request_obj.pull_request_id,)
# set some variables for email notification
pr_target_repo_url = h.route_url(
'repo_summary', repo_name=pr_target_repo.repo_name)
pr_source_repo_url = h.route_url(
'repo_summary', repo_name=pr_source_repo.repo_name)
email_kwargs = {
'date': datetime.datetime.now(),
'updating_user': updating_user,
'pull_request': pull_request_obj,
'pull_request_target_repo': pr_target_repo,
'pull_request_target_repo_url': pr_target_repo_url,
'pull_request_source_repo': pr_source_repo,
'pull_request_source_repo_url': pr_source_repo_url,
'pull_request_url': pr_url,
'ancestor_commit_id': ancestor_commit_id,
'added_commits': commit_changes.added,
'removed_commits': commit_changes.removed,
'changed_files': changed_files,
'added_files': file_changes.added,
'modified_files': file_changes.modified,
'removed_files': file_changes.removed,
emails: set References header for threading in mail user agents even with different subjects...
r4447 'thread_ids': [pr_url],
pull-requests: added update pull-requests email+notifications...
r4120 }
# create notification objects, and emails
NotificationModel().create(
created_by=updating_user,
notifications: skip double rendering just to generate email title/desc
r4560 notification_subject='', # Filled in based on the notification_type
notification_body='', # Filled in based on the notification_type
pull-requests: added update pull-requests email+notifications...
r4120 notification_type=EmailNotificationModel.TYPE_PULL_REQUEST_UPDATE,
recipients=recipients,
email_kwargs=email_kwargs,
)
dan
users: added option to detach pull requests for users which we delete....
r4351 def delete(self, pull_request, user=None):
if not user:
user = getattr(get_current_rhodecode_user(), 'username', None)
project: added all source files and assets
r1 pull_request = self.__get_pull_request(pull_request)
audit-logs: implemented pull request and comment events.
r1807 old_data = pull_request.get_api_data(with_merge_state=False)
project: added all source files and assets
r1 self._cleanup_merge_workspace(pull_request)
audit-logs: implemented pull request and comment events.
r1807 self._log_audit_action(
'repo.pull_request.delete', {'old_data': old_data},
user, pull_request)
project: added all source files and assets
r1 Session().delete(pull_request)
def close_pull_request(self, pull_request, user):
pull_request = self.__get_pull_request(pull_request)
self._cleanup_merge_workspace(pull_request)
pull_request.status = PullRequest.STATUS_CLOSED
pull_request.updated_on = datetime.datetime.now()
Session().add(pull_request)
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 self.trigger_pull_request_hook(pull_request, pull_request.author, 'close')
pull-request-events: add audit data for pull_request.close action
r2082
pr_data = pull_request.get_api_data(with_merge_state=False)
audit-logs: implemented pull request and comment events.
r1807 self._log_audit_action(
pull-request-events: add audit data for pull_request.close action
r2082 'repo.pull_request.close', {'data': pr_data}, user, pull_request)
project: added all source files and assets
r1
pull-request-api: updated logic of closing a PR via API call....
r1792 def close_pull_request_with_comment(
comments: use proper auth user for close PR action.
r3027 self, pull_request, user, repo, message=None, auth_user=None):
pull-request-api: updated logic of closing a PR via API call....
r1792
pull_request_review_status = pull_request.calculated_review_status()
project: added all source files and assets
r1
pull-request-api: updated logic of closing a PR via API call....
r1792 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
# approved only if we have voting consent
status = ChangesetStatus.STATUS_APPROVED
else:
status = ChangesetStatus.STATUS_REJECTED
status_lbl = ChangesetStatus.get_status_lbl(status)
project: added all source files and assets
r1
pull-request-api: updated logic of closing a PR via API call....
r1792 default_message = (
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 'Closing with status change {transition_icon} {status}.'
pull-request-api: updated logic of closing a PR via API call....
r1792 ).format(transition_icon='>', status=status_lbl)
text = message or default_message
project: added all source files and assets
r1
pull-request-api: updated logic of closing a PR via API call....
r1792 # create a comment, and link it to new status
comment = CommentsModel().create(
text=text,
project: added all source files and assets
r1 repo=repo.repo_id,
user=user.user_id,
pull_request=pull_request.pull_request_id,
pull-request-api: updated logic of closing a PR via API call....
r1792 status_change=status_lbl,
emails: added new tags to status sent...
r548 status_change_type=status,
comments: use proper auth user for close PR action.
r3027 closing_pr=True,
auth_user=auth_user,
project: added all source files and assets
r1 )
pull-request-api: updated logic of closing a PR via API call....
r1792 # calculate old status before we change it
old_calculated_status = pull_request.calculated_review_status()
project: added all source files and assets
r1 ChangesetStatusModel().set_status(
repo.repo_id,
status,
user.user_id,
pull-request-api: updated logic of closing a PR via API call....
r1792 comment=comment,
project: added all source files and assets
r1 pull_request=pull_request.pull_request_id
)
pull-request-api: updated logic of closing a PR via API call....
r1792
project: added all source files and assets
r1 Session().flush()
dan
hooks: added new hooks for comments on pull requests and commits....
r4305
self.trigger_pull_request_hook(pull_request, user, 'comment',
data={'comment': comment})
pull-request-api: updated logic of closing a PR via API call....
r1792 # we now calculate the status of pull request again, and based on that
# calculation trigger status change. This might happen in cases
# that non-reviewer admin closes a pr, which means his vote doesn't
# change the status, while if he's a reviewer this might change it.
calculated_status = pull_request.calculated_review_status()
if old_calculated_status != calculated_status:
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 self.trigger_pull_request_hook(pull_request, user, 'review_status_change',
data={'status': calculated_status})
project: added all source files and assets
r1
pull-request-api: updated logic of closing a PR via API call....
r1792 # finally close the PR
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 PullRequestModel().close_pull_request(pull_request.pull_request_id, user)
project: added all source files and assets
r1
pull-request-api: updated logic of closing a PR via API call....
r1792 return comment, status
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 def merge_status(self, pull_request, translator=None, force_shadow_repo_refresh=False):
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 _ = translator or get_current_request().translate
project: added all source files and assets
r1 if not self._is_merge_enabled(pull_request):
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 return None, False, _('Server-side pull request merging is disabled.')
project: added all source files and assets
r1 if pull_request.is_closed():
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 return None, False, _('This pull request is closed.')
project: added all source files and assets
r1 merge_possible, msg = self._check_repo_requirements(
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 target=pull_request.target_repo, source=pull_request.source_repo,
translator=_)
project: added all source files and assets
r1 if not merge_possible:
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 return None, merge_possible, msg
project: added all source files and assets
r1
try:
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 merge_response = self._try_merge(
pull_request, force_shadow_repo_refresh=force_shadow_repo_refresh)
log.debug("Merge response: %s", merge_response)
return merge_response, merge_response.possible, merge_response.merge_status_message
project: added all source files and assets
r1 except NotImplementedError:
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 return None, False, _('Pull request merging is not supported.')
project: added all source files and assets
r1
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 def _check_repo_requirements(self, target, source, translator):
project: added all source files and assets
r1 """
Check if `target` and `source` have compatible requirements.
Currently this is just checking for largefiles.
"""
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 _ = translator
project: added all source files and assets
r1 target_has_largefiles = self._has_largefiles(target)
source_has_largefiles = self._has_largefiles(source)
merge_possible = True
message = u''
if target_has_largefiles != source_has_largefiles:
merge_possible = False
if source_has_largefiles:
message = _(
'Target repository large files support is disabled.')
else:
message = _(
'Source repository large files support is disabled.')
return merge_possible, message
def _has_largefiles(self, repo):
largefiles_ui = VcsSettingsModel(repo=repo).get_ui_settings(
'extensions', 'largefiles')
return largefiles_ui and largefiles_ui[0].active
pull-reqeusts: added option to force-refresh merge workspace in case of problems.
r2780 def _try_merge(self, pull_request, force_shadow_repo_refresh=False):
project: added all source files and assets
r1 """
Try to merge the pull request and return the merge status.
"""
pull_request: Add debug logging around merge status calculation...
r141 log.debug(
pull-reqeusts: added option to force-refresh merge workspace in case of problems.
r2780 "Trying out if the pull request %s can be merged. Force_refresh=%s",
pull_request.pull_request_id, force_shadow_repo_refresh)
project: added all source files and assets
r1 target_vcs = pull_request.target_repo.scm_instance()
Martin Bornhold
pr: Return proper merge response if target reference is missing.
r1071 # Refresh the target reference.
try:
target_ref = self._refresh_reference(
pull_request.target_ref_parts, target_vcs)
except CommitDoesNotExistError:
merge_state = MergeResponse(
dan
pull-requests: ensure merge response provide more details...
r3339 False, False, None, MergeFailureReason.MISSING_TARGET_REF,
metadata={'target_ref': pull_request.target_ref_parts})
Martin Bornhold
pr: Return proper merge response if target reference is missing.
r1071 return merge_state
project: added all source files and assets
r1
target_locked = pull_request.target_repo.locked
if target_locked and target_locked[0]:
dan
pull-requests: ensure merge response provide more details...
r3339 locked_by = 'user:{}'.format(target_locked[0])
log.debug("The target repository is locked by %s.", locked_by)
project: added all source files and assets
r1 merge_state = MergeResponse(
dan
pull-requests: ensure merge response provide more details...
r3339 False, False, None, MergeFailureReason.TARGET_IS_LOCKED,
metadata={'locked_by': locked_by})
pull-reqeusts: added option to force-refresh merge workspace in case of problems.
r2780 elif force_shadow_repo_refresh or self._needs_merge_state_refresh(
pull_request, target_ref):
pull_request: Add debug logging around merge status calculation...
r141 log.debug("Refreshing the merge status of the repository.")
project: added all source files and assets
r1 merge_state = self._refresh_merge_state(
pull_request, target_vcs, target_ref)
else:
pull-requests: expose force refresh of merge workspace and expose all metadata for merge message formatting.
r3558 possible = pull_request.last_merge_status == MergeFailureReason.NONE
metadata = {
pull-requests: expose unresolved files in merge response.
r4080 'unresolved_files': '',
pull-requests: expose force refresh of merge workspace and expose all metadata for merge message formatting.
r3558 'target_ref': pull_request.target_ref_parts,
pull-requests: updated metadata information for failed merges with multiple heads.
r3627 'source_ref': pull_request.source_ref_parts,
pull-requests: expose force refresh of merge workspace and expose all metadata for merge message formatting.
r3558 }
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 if pull_request.last_merge_metadata:
pull-requests: properly save merge failure metadata. Before this change...
r4471 metadata.update(pull_request.last_merge_metadata_parsed)
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299
pull-requests: updated metadata information for failed merges with multiple heads.
r3627 if not possible and target_ref.type == 'branch':
# NOTE(marcink): case for mercurial multiple heads on branch
heads = target_vcs._heads(target_ref.name)
if len(heads) != 1:
heads = '\n,'.join(target_vcs._heads(target_ref.name))
metadata.update({
'heads': heads
})
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299
project: added all source files and assets
r1 merge_state = MergeResponse(
pull-requests: expose force refresh of merge workspace and expose all metadata for merge message formatting.
r3558 possible, False, None, pull_request.last_merge_status, metadata=metadata)
Martin Bornhold
pr: Move log statement to allow early return and still log merge response.
r1070
project: added all source files and assets
r1 return merge_state
def _refresh_reference(self, reference, vcs_repository):
pull-requests: validate ref types for pull request so users cannot provide wrongs ones.
r3302 if reference.type in self.UPDATABLE_REF_TYPES:
project: added all source files and assets
r1 name_or_id = reference.name
else:
name_or_id = reference.commit_id
tests: fix cache problems after empty repo check change.
r3738
project: added all source files and assets
r1 refreshed_commit = vcs_repository.get_commit(name_or_id)
refreshed_reference = Reference(
reference.type, reference.name, refreshed_commit.raw_id)
return refreshed_reference
def _needs_merge_state_refresh(self, pull_request, target_reference):
return not(
pull_request.revisions and
pull_request.revisions[0] == pull_request._last_merge_source_rev and
target_reference.commit_id == pull_request._last_merge_target_rev)
def _refresh_merge_state(self, pull_request, target_vcs, target_reference):
workspace_id = self._workspace_id(pull_request)
source_vcs = pull_request.source_repo.scm_instance()
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 repo_id = pull_request.target_repo.repo_id
Martin Bornhold
settings: Read 'rebase-merge' config option and pass it to the VCS insntacen on merge.
r361 use_rebase = self._use_rebase_for_merging(pull_request)
Mathieu Cantin
mercurial: Add option to close a branch before merging
r2055 close_branch = self._close_branch_before_merging(pull_request)
project: added all source files and assets
r1 merge_state = target_vcs.merge(
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 repo_id, workspace_id,
project: added all source files and assets
r1 target_reference, source_vcs, pull_request.source_ref_parts,
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 dry_run=True, use_rebase=use_rebase,
Mathieu Cantin
mercurial: Add option to close a branch before merging
r2055 close_branch=close_branch)
project: added all source files and assets
r1
# Do not store the response if there was an unknown error.
if merge_state.failure_reason != MergeFailureReason.UNKNOWN:
Martin Bornhold
pr-shadow: Set last merge revision on merge status update.
r1043 pull_request._last_merge_source_rev = \
pull_request.source_ref_parts.commit_id
project: added all source files and assets
r1 pull_request._last_merge_target_rev = target_reference.commit_id
db: use a wrapper on pull requests _last_merge_status to ensure this is always INT....
r1968 pull_request.last_merge_status = merge_state.failure_reason
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 pull_request.last_merge_metadata = merge_state.metadata
Martin Bornhold
pr-shadow: Adapt to new merge response object.
r1052 pull_request.shadow_merge_ref = merge_state.merge_ref
project: added all source files and assets
r1 Session().add(pull_request)
Martin Bornhold
pr-model: Commit changes to DB on merge status update.
r1044 Session().commit()
project: added all source files and assets
r1
return merge_state
def _workspace_id(self, pull_request):
workspace_id = 'pr-%s' % pull_request.pull_request_id
return workspace_id
def generate_repo_data(self, repo, commit_id=None, branch=None,
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 bookmark=None, translator=None):
pull-requests: add link to switch base based on the target.
r2469 from rhodecode.model.repo import RepoModel
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168
project: added all source files and assets
r1 all_refs, selected_ref = \
self._get_repo_pullrequest_sources(
repo.scm_instance(), commit_id=commit_id,
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 branch=branch, bookmark=bookmark, translator=translator)
project: added all source files and assets
r1
refs_select2 = []
for element in all_refs:
children = [{'id': x[0], 'text': x[1]} for x in element[0]]
refs_select2.append({'text': element[1], 'children': children})
return {
'user': {
'user_id': repo.user.user_id,
'username': repo.user.username,
security: use new safe escaped user attributes across the application....
r1815 'firstname': repo.user.first_name,
'lastname': repo.user.last_name,
project: added all source files and assets
r1 'gravatar_link': h.gravatar_url(repo.user.email, 14),
},
pull-requests: add link to switch base based on the target.
r2469 'name': repo.repo_name,
'link': RepoModel().get_url(repo),
security: use safe escaped version of description for repo and repo group to potentially...
r1830 'description': h.chop_at_smart(repo.description_safe, '\n'),
project: added all source files and assets
r1 'refs': {
'all_refs': all_refs,
'selected_ref': selected_ref,
'select2_refs': refs_select2
}
}
def generate_pullrequest_title(self, source, source_ref, target):
Martin Bornhold
pr: Use unicode object when generating the pull request title
r842 return u'{source}#{at_ref} to {target}'.format(
project: added all source files and assets
r1 source=source,
at_ref=source_ref,
target=target,
)
def _cleanup_merge_workspace(self, pull_request):
# Merging related cleanup
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 repo_id = pull_request.target_repo.repo_id
project: added all source files and assets
r1 target_scm = pull_request.target_repo.scm_instance()
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 workspace_id = self._workspace_id(pull_request)
project: added all source files and assets
r1
try:
shadow-repos: use numeric repo id for creation of shadow repos....
r2810 target_scm.cleanup_merge_workspace(repo_id, workspace_id)
project: added all source files and assets
r1 except NotImplementedError:
pass
def _get_repo_pullrequest_sources(
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 self, repo, commit_id=None, branch=None, bookmark=None,
translator=None):
project: added all source files and assets
r1 """
Return a structure with repo's interesting commits, suitable for
the selectors in pullrequest controller
:param commit_id: a commit that must be in the list somehow
and selected by default
:param branch: a branch that must be in the list and selected
by default - even if closed
:param bookmark: a bookmark that must be in the list and selected
"""
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 _ = translator or get_current_request().translate
project: added all source files and assets
r1
commit_id = safe_str(commit_id) if commit_id else None
models: major update for python3,...
r5070 branch = safe_str(branch) if branch else None
bookmark = safe_str(bookmark) if bookmark else None
project: added all source files and assets
r1
selected = None
# order matters: first source that has commit_id in it will be selected
sources = []
sources.append(('book', repo.bookmarks.items(), _('Bookmarks'), bookmark))
sources.append(('branch', repo.branches.items(), _('Branches'), branch))
if commit_id:
ref_commit = (h.short_id(commit_id), commit_id)
sources.append(('rev', [ref_commit], _('Commit IDs'), commit_id))
sources.append(
('branch', repo.branches_closed.items(), _('Closed Branches'), branch),
)
groups = []
pull-requests: handle non-ascii branches from short branch selector via URL
r3504
project: added all source files and assets
r1 for group_key, ref_list, group_name, match in sources:
group_refs = []
for ref_name, ref_id in ref_list:
pull-requests: handle non-ascii branches from short branch selector via URL
r3504 ref_key = u'{}:{}:{}'.format(group_key, ref_name, ref_id)
project: added all source files and assets
r1 group_refs.append((ref_key, ref_name))
dan
pullrequests: select a ref if one exists matching the commit id
r6 if not selected:
if set([commit_id, match]) & set([ref_id, ref_name]):
selected = ref_key
project: added all source files and assets
r1 if group_refs:
groups.append((group_refs, group_name))
if not selected:
ref = commit_id or branch or bookmark
if ref:
raise CommitDoesNotExistError(
pull-requests: handle non-ascii branches from short branch selector via URL
r3504 u'No commit refs could be found matching: {}'.format(ref))
project: added all source files and assets
r1 elif repo.DEFAULT_BRANCH_NAME in repo.branches:
pull-requests: handle non-ascii branches from short branch selector via URL
r3504 selected = u'branch:{}:{}'.format(
models: major update for python3,...
r5070 safe_str(repo.DEFAULT_BRANCH_NAME),
safe_str(repo.branches[repo.DEFAULT_BRANCH_NAME])
project: added all source files and assets
r1 )
elif repo.commit_ids:
pull-requests: don't select first commit in case we don't get default branch. Loading...
r2474 # make the user select in this case
selected = None
project: added all source files and assets
r1 else:
raise EmptyRepositoryError()
return groups, selected
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 def get_diff(self, source_repo, source_ref_id, target_ref_id,
hide_whitespace_changes, diff_context):
pr-versioning: implemented versioning for pull requests....
r1368 return self._get_diff_from_pr_or_version(
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 source_repo, source_ref_id, target_ref_id,
hide_whitespace_changes=hide_whitespace_changes, diff_context=diff_context)
project: added all source files and assets
r1
pr-versioning: implemented versioning for pull requests....
r1368 def _get_diff_from_pr_or_version(
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 self, source_repo, source_ref_id, target_ref_id,
hide_whitespace_changes, diff_context):
project: added all source files and assets
r1 target_commit = source_repo.get_commit(
commit_id=safe_str(target_ref_id))
pr-versioning: implemented versioning for pull requests....
r1368 source_commit = source_repo.get_commit(
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
pr-versioning: implemented versioning for pull requests....
r1368 if isinstance(source_repo, Repository):
vcs_repo = source_repo.scm_instance()
else:
vcs_repo = source_repo
project: added all source files and assets
r1
# TODO: johbo: In the context of an update, we cannot reach
# the old commit anymore with our normal mechanisms. It needs
# some sort of special support in the vcs layer to avoid this
# workaround.
if (source_commit.raw_id == vcs_repo.EMPTY_COMMIT_ID and
vcs_repo.alias == 'git'):
source_commit.raw_id = safe_str(source_ref_id)
log.debug('calculating diff between '
'source_ref:%s and target_ref:%s for repo `%s`',
target_ref_id, source_ref_id,
models: major update for python3,...
r5070 safe_str(vcs_repo.path))
project: added all source files and assets
r1
vcs_diff = vcs_repo.get_diff(
dan
diffs: introducing diff menu for whitespace toggle and context changes
r3134 commit1=target_commit, commit2=source_commit,
ignore_whitespace=hide_whitespace_changes, context=diff_context)
project: added all source files and assets
r1 return vcs_diff
def _is_merge_enabled(self, pull_request):
Mathieu Cantin
mercurial: Add option to close a branch before merging
r2055 return self._get_general_setting(
pull_request, 'rhodecode_pr_merge_enabled')
def _use_rebase_for_merging(self, pull_request):
pull-requests: use close action with proper --close-commit solution....
r2056 repo_type = pull_request.target_repo.repo_type
if repo_type == 'hg':
return self._get_general_setting(
pull_request, 'rhodecode_hg_use_rebase_for_merging')
elif repo_type == 'git':
return self._get_general_setting(
pull_request, 'rhodecode_git_use_rebase_for_merging')
return False
Mathieu Cantin
mercurial: Add option to close a branch before merging
r2055
pull-requests: small code cleanup to define other type of merge username...
r4191 def _user_name_for_merging(self, pull_request, user):
env_user_name_attr = os.environ.get('RC_MERGE_USER_NAME_ATTR', '')
if env_user_name_attr and hasattr(user, env_user_name_attr):
user_name_attr = env_user_name_attr
else:
user_name_attr = 'short_contact'
user_name = getattr(user, user_name_attr)
return user_name
Mathieu Cantin
mercurial: Add option to close a branch before merging
r2055 def _close_branch_before_merging(self, pull_request):
pull-requests: use close action with proper --close-commit solution....
r2056 repo_type = pull_request.target_repo.repo_type
if repo_type == 'hg':
return self._get_general_setting(
pull_request, 'rhodecode_hg_close_branch_before_merging')
elif repo_type == 'git':
return self._get_general_setting(
pull_request, 'rhodecode_git_close_branch_before_merging')
return False
Mathieu Cantin
mercurial: Add option to close a branch before merging
r2055
def _get_general_setting(self, pull_request, settings_key, default=False):
project: added all source files and assets
r1 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
settings = settings_model.get_general_settings()
Mathieu Cantin
mercurial: Add option to close a branch before merging
r2055 return settings.get(settings_key, default)
Martin Bornhold
settings: Read 'rebase-merge' config option and pass it to the VCS insntacen on merge.
r361
audit-logs: implemented pull request and comment events.
r1807 def _log_audit_action(self, action, action_data, user, pull_request):
audit_logger.store(
action=action,
action_data=action_data,
user=user,
repo=pull_request.target_repo)
project: added all source files and assets
r1
pull-request: extended default reviewers functionality....
r1769 def get_reviewer_functions(self):
"""
Fetches functions for validation and fetching default reviewers.
If available we use the EE package, else we fallback to CE
package functions
"""
try:
from rc_reviewers.utils import get_default_reviewers_data
from rc_reviewers.utils import validate_default_reviewers
reviewers: added observers as another way to define reviewers....
r4500 from rc_reviewers.utils import validate_observers
pull-request: extended default reviewers functionality....
r1769 except ImportError:
code: code cleanups, use is None instead of == None.
r3231 from rhodecode.apps.repository.utils import get_default_reviewers_data
from rhodecode.apps.repository.utils import validate_default_reviewers
reviewers: added observers as another way to define reviewers....
r4500 from rhodecode.apps.repository.utils import validate_observers
pull-request: extended default reviewers functionality....
r1769
reviewers: added observers as another way to define reviewers....
r4500 return get_default_reviewers_data, validate_default_reviewers, validate_observers
pull-request: extended default reviewers functionality....
r1769
project: added all source files and assets
r1
pull-requests: unified merge checks....
r1335 class MergeCheck(object):
"""
Perform Merge Checks and returns a check object which stores information
about merge errors, and merge conditions
"""
merge-checks: added more detailed information about why merge is not possible....
r1341 TODO_CHECK = 'todo'
PERM_CHECK = 'perm'
REVIEW_CHECK = 'review'
MERGE_CHECK = 'merge'
pull-requests: add merge check that detects WIP marker in title. This will prevent merges in such case....
r4099 WIP_CHECK = 'wip'
pull-requests: unified merge checks....
r1335
def __init__(self):
pull-requests: add explicit CLOSE pr action instead of closed status from selector....
r1445 self.review_status = None
pull-requests: unified merge checks....
r1335 self.merge_possible = None
self.merge_msg = ''
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 self.merge_response = None
pull-requests: unified merge checks....
r1335 self.failed = None
self.errors = []
merge-checks: added more detailed information about why merge is not possible....
r1341 self.error_details = OrderedDict()
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 self.source_commit = AttributeDict()
self.target_commit = AttributeDict()
reviewers: only require a review when we have reviewers defined....
r4561 self.reviewers_count = 0
self.observers_count = 0
pull-requests: unified merge checks....
r1335
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 def __repr__(self):
return '<MergeCheck(possible:{}, failed:{}, errors:{})>'.format(
self.merge_possible, self.failed, self.errors)
merge-checks: added more detailed information about why merge is not possible....
r1341 def push_error(self, error_type, message, error_key, details):
pull-requests: unified merge checks....
r1335 self.failed = True
self.errors.append([error_type, message])
merge-checks: added more detailed information about why merge is not possible....
r1341 self.error_details[error_key] = dict(
details=details,
error_type=error_type,
message=message
)
pull-requests: unified merge checks....
r1335
@classmethod
pull-requests: add merge validation to prevent merges to protected branches.
r2981 def validate(cls, pull_request, auth_user, translator, fail_early=False,
pull-reqeusts: added option to force-refresh merge workspace in case of problems.
r2780 force_shadow_repo_refresh=False):
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 _ = translator
pull-requests: unified merge checks....
r1335 merge_check = cls()
pull-requests: add merge check that detects WIP marker in title. This will prevent merges in such case....
r4099 # title has WIP:
if pull_request.work_in_progress:
log.debug("MergeCheck: cannot merge, title has wip: marker.")
msg = _('WIP marker in title prevents from accidental merge.')
merge_check.push_error('error', msg, cls.WIP_CHECK, pull_request.title)
if fail_early:
return merge_check
pull-requests: add explicit CLOSE pr action instead of closed status from selector....
r1445 # permissions to merge
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 user_allowed_to_merge = PullRequestModel().check_user_merge(pull_request, auth_user)
pull-requests: unified merge checks....
r1335 if not user_allowed_to_merge:
log.debug("MergeCheck: cannot merge, approval is pending.")
pull-requests: add merge validation to prevent merges to protected branches.
r2981 msg = _('User `{}` not allowed to perform merge.').format(auth_user.username)
merge_check.push_error('error', msg, cls.PERM_CHECK, auth_user.username)
if fail_early:
return merge_check
# permission to merge into the target branch
target_commit_id = pull_request.target_ref_parts.commit_id
if pull_request.target_ref_parts.type == 'branch':
branch_name = pull_request.target_ref_parts.name
else:
# for mercurial we can always figure out the branch from the commit
# in case of bookmark
target_commit = pull_request.target_repo.get_commit(target_commit_id)
branch_name = target_commit.branch
rule, branch_perm = auth_user.get_rule_and_branch_permission(
pull_request.target_repo.repo_name, branch_name)
if branch_perm and branch_perm == 'branch.none':
msg = _('Target branch `{}` changes rejected by rule {}.').format(
branch_name, rule)
merge_check.push_error('error', msg, cls.PERM_CHECK, auth_user.username)
pull-requests: unified merge checks....
r1335 if fail_early:
return merge_check
pull-requests: add explicit CLOSE pr action instead of closed status from selector....
r1445 # review status, must be always present
pull-requests: unified merge checks....
r1335 review_status = pull_request.calculated_review_status()
pull-requests: add explicit CLOSE pr action instead of closed status from selector....
r1445 merge_check.review_status = review_status
reviewers: only require a review when we have reviewers defined....
r4561 merge_check.reviewers_count = pull_request.reviewers_count
merge_check.observers_count = pull_request.observers_count
pull-requests: add explicit CLOSE pr action instead of closed status from selector....
r1445
pull-requests: unified merge checks....
r1335 status_approved = review_status == ChangesetStatus.STATUS_APPROVED
reviewers: only require a review when we have reviewers defined....
r4561 if not status_approved and merge_check.reviewers_count:
pull-requests: unified merge checks....
r1335 log.debug("MergeCheck: cannot merge, approval is pending.")
msg = _('Pull request reviewer approval is pending.')
pull-requests: introduce operation state for pull requests to prevent from...
r3371 merge_check.push_error('warning', msg, cls.REVIEW_CHECK, review_status)
pull-requests: unified merge checks....
r1335
if fail_early:
return merge_check
# left over TODOs
comments: expose a function to fetch unresolved TODOs for repository
r3433 todos = CommentsModel().get_pull_request_unresolved_todos(pull_request)
pull-requests: unified merge checks....
r1335 if todos:
log.debug("MergeCheck: cannot merge, {} "
pull-requests: introduce operation state for pull requests to prevent from...
r3371 "unresolved TODOs left.".format(len(todos)))
pull-requests: unified merge checks....
r1335
if len(todos) == 1:
msg = _('Cannot merge, {} TODO still not resolved.').format(
len(todos))
else:
msg = _('Cannot merge, {} TODOs still not resolved.').format(
len(todos))
merge-checks: added more detailed information about why merge is not possible....
r1341 merge_check.push_error('warning', msg, cls.TODO_CHECK, todos)
pull-requests: unified merge checks....
r1335
if fail_early:
return merge_check
pull-requests: fix problem with long DB transaction and row-locking....
r2792 # merge possible, here is the filesystem simulation + shadow repo
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 merge_response, merge_status, msg = PullRequestModel().merge_status(
pull-reqeusts: added option to force-refresh merge workspace in case of problems.
r2780 pull_request, translator=translator,
force_shadow_repo_refresh=force_shadow_repo_refresh)
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299
pull-requests: unified merge checks....
r1335 merge_check.merge_possible = merge_status
merge_check.merge_msg = msg
pull-requests: fixed case for GIT repositories when a merge check failed due to merge conflicts the pull request wrongly reported missing commits....
r4299 merge_check.merge_response = merge_response
dan
pull-requests: add information about changes in source repositories in pull-request show page....
r4317 source_ref_id = pull_request.source_ref_parts.commit_id
target_ref_id = pull_request.target_ref_parts.commit_id
try:
source_commit, target_commit = PullRequestModel().get_flow_commits(pull_request)
merge_check.source_commit.changed = source_ref_id != source_commit.raw_id
merge_check.source_commit.ref_spec = pull_request.source_ref_parts
merge_check.source_commit.current_raw_id = source_commit.raw_id
merge_check.source_commit.previous_raw_id = source_ref_id
merge_check.target_commit.changed = target_ref_id != target_commit.raw_id
merge_check.target_commit.ref_spec = pull_request.target_ref_parts
merge_check.target_commit.current_raw_id = target_commit.raw_id
merge_check.target_commit.previous_raw_id = target_ref_id
except (SourceRefMissing, TargetRefMissing):
pass
pull-requests: unified merge checks....
r1335 if not merge_status:
pull-requests: introduce operation state for pull requests to prevent from...
r3371 log.debug("MergeCheck: cannot merge, pull request merge not possible.")
merge-checks: added more detailed information about why merge is not possible....
r1341 merge_check.push_error('warning', msg, cls.MERGE_CHECK, None)
pull-requests: unified merge checks....
r1335
if fail_early:
return merge_check
pull-requests: migrated code from pylons to pyramid
r1974 log.debug('MergeCheck: is failed: %s', merge_check.failed)
pull-requests: unified merge checks....
r1335 return merge_check
pull-requests: use merge info to show how Pull requests will be merged....
r2053 @classmethod
pull-requests: trigger merge simulation during PR creation. Fixes #5396
r2168 def get_merge_conditions(cls, pull_request, translator):
_ = translator
pull-requests: use merge info to show how Pull requests will be merged....
r2053 merge_details = {}
model = PullRequestModel()
use_rebase = model._use_rebase_for_merging(pull_request)
if use_rebase:
merge_details['merge_strategy'] = dict(
details={},
message=_('Merge strategy: rebase')
)
else:
merge_details['merge_strategy'] = dict(
details={},
message=_('Merge strategy: explicit merge commit')
)
close_branch = model._close_branch_before_merging(pull_request)
if close_branch:
repo_type = pull_request.target_repo.repo_type
pull-requests: introduce operation state for pull requests to prevent from...
r3371 close_msg = ''
pull-requests: use merge info to show how Pull requests will be merged....
r2053 if repo_type == 'hg':
pull-requests: changed the order of close-branch after merge, so we don't leave open heads....
r4436 close_msg = _('Source branch will be closed before the merge.')
pull-requests: use merge info to show how Pull requests will be merged....
r2053 elif repo_type == 'git':
pull-requests: changed the order of close-branch after merge, so we don't leave open heads....
r4436 close_msg = _('Source branch will be deleted after the merge.')
pull-requests: use merge info to show how Pull requests will be merged....
r2053
merge_details['close_branch'] = dict(
details={},
message=close_msg
)
return merge_details
pull-requests: unified merge checks....
r1335
pull-requests: introduce operation state for pull requests to prevent from...
r3371
models: major update for python3,...
r5070 @dataclasses.dataclass
class ChangeTuple:
added: list
common: list
removed: list
total: list
project: added all source files and assets
r1
models: major update for python3,...
r5070
@dataclasses.dataclass
class FileChangeTuple:
added: list
modified: list
removed: list