azure-devops/azext_devops/dev/repos/pull_request.py (423 lines of code) (raw):

# -------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import webbrowser from knack.log import get_logger from knack.util import CLIError from azext_devops.devops_sdk.exceptions import AzureDevOpsClientRequestError from azext_devops.devops_sdk.v5_0.git.models import (GitPullRequest, GitPullRequestCompletionOptions, GitPullRequestSearchCriteria, IdentityRef, IdentityRefWithVote, ResourceRef, GitRefFavorite, WebApiTagDefinition) from azext_devops.devops_sdk.v5_0.work_item_tracking.models import JsonPatchOperation, WorkItemRelation from azext_devops.dev.common.arguments import should_detect from azext_devops.dev.common.git import get_current_branch_name, resolve_git_ref_heads, fetch_remote_and_checkout from azext_devops.dev.common.identities import ME, resolve_identity_as_id from azext_devops.dev.common.uri import uri_quote from azext_devops.dev.common.uuid import EMPTY_UUID from azext_devops.dev.common.services import (get_git_client, get_policy_client, get_work_item_tracking_client, resolve_instance, resolve_instance_project_and_repo, get_vsts_info_from_current_remote_url) logger = get_logger(__name__) def show_pull_request(id, open=False, organization=None, detect=None): # pylint: disable=redefined-builtin """Get the details of a pull request. :param id: ID of the pull request. :type id: int :param open: Open the pull request in your web browser. :type open: bool :rtype: :class:`GitPullRequest <v5_0.git.models.GitPullRequest>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) pr = client.get_pull_request_by_id(id) pr = client.get_pull_request(project=pr.repository.project.id, repository_id=pr.repository.id, pull_request_id=id, include_commits=False, include_work_item_refs=True) if open: _open_pull_request(pr, organization) return pr def list_pull_requests(repository=None, creator=None, include_links=False, reviewer=None, source_branch=None, status=None, target_branch=None, project=None, skip=None, top=None, organization=None, detect=None): """List pull requests. :param repository: Name or ID of the repository. :type repository: str :param creator: Limit results to pull requests created by this user. :type creator: str :param include_links: Include _links for each pull request. :type include_links: bool :param reviewer: Limit results to pull requests where this user is a reviewer. :type reviewer: str :param source_branch: Limit results to pull requests that originate from this source branch. :type source_branch: str :param status: Limit results to pull requests with this status. :type status: str :param target_branch: Limit results to pull requests that target this branch. :type target_branch: str :param skip: Number of pull requests to skip. :type skip: int :param top: Maximum number of pull requests to list. :type top: int :rtype: list of :class:`VssJsonCollectionWrapper <v5_0.git.models.VssJsonCollectionWrapper>` """ organization, project, repository = resolve_instance_project_and_repo( detect=detect, organization=organization, project=project, repo=repository) search_criteria = GitPullRequestSearchCriteria( creator_id=resolve_identity_as_id(creator, organization), include_links=include_links, reviewer_id=resolve_identity_as_id(reviewer, organization), source_ref_name=resolve_git_ref_heads(source_branch), status=status, target_ref_name=resolve_git_ref_heads(target_branch)) client = get_git_client(organization) if repository is None: pr_list = client.get_pull_requests_by_project(project=project, search_criteria=search_criteria, skip=skip, top=top) else: pr_list = client.get_pull_requests(project=project, repository_id=repository, search_criteria=search_criteria, skip=skip, top=top) return pr_list # pylint: disable=too-many-statements, too-many-locals, redefined-builtin def create_pull_request(project=None, repository=None, source_branch=None, target_branch=None, title=None, description=None, auto_complete=False, squash=False, delete_source_branch=False, bypass_policy=False, bypass_policy_reason=None, merge_commit_message=None, optional_reviewers=None, required_reviewers=None, work_items=None, draft=None, open=False, organization=None, detect=None, transition_work_items=False, labels=None): # pylint: disable=redefined-builtin """Create a pull request. :param project: Name or ID of the team project. :type project: str :param repository: Name or ID of the repository to create the pull request in. :type repository: str :param source_branch: Name of the source branch. Example: "dev". :type source_branch: str :param target_branch: Name of the target branch. If not specified, defaults to the default branch of the target repository. :type target_branch: str :param title: Title for the new pull request. :type title: str :param draft: Use this flag to create the pull request in draft/work in progress mode. :type draft: bool :param description: Description for the new pull request. Can include markdown. Each value sent to this arg will be a new line. For example: --description "First Line" "Second Line" :type description: list of str :param auto_complete: Set the pull request to complete automatically when all policies have passed and the source branch can be merged into the target branch. :type auto_complete: bool :param squash: Squash the commits in the source branch when merging into the target branch. :type squash: bool :param delete_source_branch: Delete the source branch after the pull request has been completed and merged into the target branch. :type delete_source_branch: bool :param bypass_policy: Bypass required policies (if any) and completes the pull request once it can be merged. :type bypass_policy: bool :param bypass_policy_reason: Reason for bypassing the required policies. :type bypass_policy_reason: str :param merge_commit_message: Message displayed when commits are merged. :type merge_commit_message: str :param optional_reviewers: Additional users or groups to include as optional reviewers on the new pull request. Space separated. :type optional_reviewers: list of str :param required_reviewers: Additional users or groups to include as required reviewers on the new pull request. Space separated. :type required_reviewers: list of str :param work_items: IDs of the work items to link to the new pull request. Space separated. :type work_items: list of str :param open: Open the pull request in your web browser. :type open: bool :param transition_work_items: Transition any work items linked to the pull request into the next logical state. (e.g. Active -> Resolved) :type transition_work_items: bool :param labels: The labels associated with the pull request. Space separated. :type labels: list of str :rtype: :class:`GitPullRequest <v5_0.git.models.GitPullRequest>` """ organization, project, repository = resolve_instance_project_and_repo( detect=detect, organization=organization, project=project, repo=repository) source_branch, target_branch = _get_branches_for_pull_request( organization, project, repository, source_branch, target_branch, detect) client = get_git_client(organization) multi_line_description = None if description is not None: multi_line_description = '\n'.join(description) if labels is not None: labels = [WebApiTagDefinition(name=label) for label in labels.split(' ')] pr = GitPullRequest(description=multi_line_description, source_ref_name=source_branch, target_ref_name=target_branch, labels=labels) if draft is not None: pr.is_draft = draft pr.title = 'Merge ' + source_branch + ' to ' + target_branch if title is not None: pr.title = title pr.source_ref_name = resolve_git_ref_heads(source_branch) pr.target_ref_name = resolve_git_ref_heads(target_branch) if pr.source_ref_name == pr.target_ref_name: raise CLIError('The source branch, "{}", can not be the same as the target branch.'.format (pr.source_ref_name)) if optional_reviewers is not None: optional_reviewers = list(set(x.lower() for x in optional_reviewers)) if required_reviewers is not None: required_reviewers = list(set(x.lower() for x in required_reviewers)) reviewers = _resolve_reviewers_as_refs(optional_reviewers, required_reviewers, organization) pr.reviewers = reviewers if work_items is not None and work_items: resolved_work_items = [] for work_item in work_items: resolved_work_items.append(ResourceRef(id=work_item)) pr.work_item_refs = resolved_work_items pr = client.create_pull_request(git_pull_request_to_create=pr, project=project, repository_id=repository) # if title or description are not provided and there is a single commit, we will # use the its comment to populate the not-provided field(s). # the first line of the commit comment is used for title and all lines of the # comment are used for description. title_from_commit = None description_from_commit = None if (title is None) or (description is None): commits = client.get_pull_request_commits(repository_id=repository, pull_request_id=pr.pull_request_id, project=project) if len(commits) == 1: commit_details = client.get_commit(commit_id=commits[0].commit_id, repository_id=repository, project=project, change_count=0) first_commit_comment = commit_details.comment if first_commit_comment: # when title is not specified, use the first line of first commit comment as PR title if title is None: title_from_commit = first_commit_comment.split("\n")[0] # when description is not specified, use the entire commit comment as PR description if description is None: description_from_commit = first_commit_comment set_completion_options = (bypass_policy or bypass_policy_reason is not None or squash or merge_commit_message is not None or delete_source_branch or transition_work_items) if auto_complete or set_completion_options or title_from_commit is not None or description_from_commit is not None: pr_for_update = GitPullRequest() if auto_complete: # auto-complete will not get set on create, so a subsequent update is required. pr_for_update.auto_complete_set_by = IdentityRef(id=resolve_identity_as_id(ME, organization)) if set_completion_options: completion_options = GitPullRequestCompletionOptions() completion_options.bypass_policy = bypass_policy completion_options.bypass_reason = bypass_policy_reason completion_options.delete_source_branch = delete_source_branch completion_options.squash_merge = squash completion_options.merge_commit_message = merge_commit_message completion_options.transition_work_items = transition_work_items pr_for_update.completion_options = completion_options if title_from_commit is not None: pr_for_update.title = title_from_commit if description_from_commit is not None: pr_for_update.description = description_from_commit pr = client.update_pull_request(git_pull_request_to_update=pr_for_update, project=pr.repository.project.id, repository_id=pr.repository.id, pull_request_id=pr.pull_request_id) if open: _open_pull_request(pr, organization) return pr def _get_branches_for_pull_request(organization, project, repository, source_branch, target_branch, detect): if should_detect(detect): if source_branch is None: source_branch = get_current_branch_name() if source_branch is None: raise ValueError('The source branch could not be detected,' 'please provide the --source-branch argument.') else: if source_branch is None: raise ValueError('--source-branch is a required argument.') if target_branch is None: if project is not None and repository is not None: target_branch = _get_default_branch(organization, project, repository) if target_branch is None: raise ValueError('The target branch could not be detected,' 'please provide the --target-branch argument.') return source_branch, target_branch def update_pull_request(id, title=None, description=None, auto_complete=None, # pylint: disable=redefined-builtin squash=None, delete_source_branch=None, bypass_policy=None, draft=None, bypass_policy_reason=None, merge_commit_message=None, organization=None, detect=None, transition_work_items=None, status=None): """Update a pull request. :param id: ID of the pull request. :type id: int :param title: New title for the pull request. :type title: str :param description: New description for the pull request. Can include markdown. Each value sent to this arg will be a new line. For example: --description "First Line" "Second Line" :type description: list of str :param auto_complete: Set the pull request to complete automatically when all policies have passed and the source branch can be merged into the target branch. :type auto_complete: bool :param squash: Squash the commits in the source branch when merging into the target branch. :type squash: bool :param delete_source_branch: Delete the source branch after the pull request has been completed and merged into the target branch. :type delete_source_branch: bool :param bypass_policy: Bypass required policies (if any) and completes the pull request once it can be merged. :type bypass_policy: bool :param bypass_policy_reason: Reason for bypassing the required policies. :type bypass_policy_reason: str :param draft: Publish the PR or convert to draft mode. :type draft: bool :param merge_commit_message: Message displayed when commits are merged. :type merge_commit_message: str :param transition_work_items: Transition any work items linked to the pull request into the next logical state. (e.g. Active -> Resolved) :type transition_work_items: bool :param status: Set the new state of pull request. :type status: str :rtype: :class:`GitPullRequest <v5_0.git.models.GitPullRequest>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) existing_pr = client.get_pull_request_by_id(id) if description is not None: multi_line_description = '\n'.join(description) else: multi_line_description = None pr = GitPullRequest(title=title, description=multi_line_description) if (bypass_policy is not None or # pylint: disable=too-many-boolean-expressions bypass_policy_reason is not None or squash is not None or merge_commit_message is not None or delete_source_branch is not None or transition_work_items is not None): completion_options = existing_pr.completion_options if completion_options is None: completion_options = GitPullRequestCompletionOptions() if bypass_policy is not None: completion_options.bypass_policy = bypass_policy if bypass_policy_reason is not None: completion_options.bypass_reason = bypass_policy_reason if delete_source_branch is not None: completion_options.delete_source_branch = delete_source_branch if squash is not None: completion_options.squash_merge = squash if merge_commit_message is not None: completion_options.merge_commit_message = merge_commit_message if transition_work_items is not None: completion_options.transition_work_items = transition_work_items pr.completion_options = completion_options if auto_complete is not None: if auto_complete: pr.auto_complete_set_by = IdentityRef(id=resolve_identity_as_id(ME, organization)) else: pr.auto_complete_set_by = IdentityRef(id=EMPTY_UUID) if draft is not None: pr.is_draft = draft if status is not None: pr.status = status if status == 'completed': pr.last_merge_source_commit = existing_pr.last_merge_source_commit if not pr.completion_options: pr.completion_options = existing_pr.completion_options pr = client.update_pull_request(git_pull_request_to_update=pr, project=existing_pr.repository.project.name, repository_id=existing_pr.repository.name, pull_request_id=id) return pr def create_pull_request_reviewers(id, reviewers, organization=None, detect=None, required=None): # pylint: disable=redefined-builtin """Add one or more reviewers to a pull request. :param id: ID of the pull request. :type id: int :param reviewers: Users or groups to include as reviewers on a pull request. Space separated. :type reviewers: list of str :param required: Make the reviewers required. :type reviewers: bool :rtype: list of :class:`IdentityRefWithVote <v5_0.git.models.IdentityRefWithVote>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) pr = client.get_pull_request_by_id(id) resolved_reviewers = _resolve_reviewers_as_refs(reviewers, None, organization) if required: for reviewer in resolved_reviewers: reviewer.is_required = True identities = client.create_pull_request_reviewers(reviewers=resolved_reviewers, project=pr.repository.project.id, repository_id=pr.repository.id, pull_request_id=id) return identities def delete_pull_request_reviewers(id, reviewers, organization=None, detect=None): # pylint: disable=redefined-builtin """Remove one or more reviewers from a pull request. :param id: ID of the pull request. :type id: int :param reviewers: Users or groups to remove as reviewers on a pull request. Space separated. :type reviewers: list of str :rtype: list of :class:`IdentityRefWithVote <v5_0.git.models.IdentityRefWithVote>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) pr = client.get_pull_request_by_id(id) resolved_reviewers = _resolve_reviewers_as_ids(reviewers, organization) for reviewer in resolved_reviewers: client.delete_pull_request_reviewer(project=pr.repository.project.id, repository_id=pr.repository.id, pull_request_id=id, reviewer_id=reviewer) return client.get_pull_request_reviewers(project=pr.repository.project.id, repository_id=pr.repository.id, pull_request_id=id) def list_pull_request_reviewers(id, organization=None, detect=None): # pylint: disable=redefined-builtin """List reviewers of a pull request. :param id: ID of the pull request. :type id: int :rtype: list of :class:`IdentityRefWithVote <v5_0.git.models.IdentityRefWithVote>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) pr = client.get_pull_request_by_id(id) return client.get_pull_request_reviewers(project=pr.repository.project.id, repository_id=pr.repository.id, pull_request_id=id) def add_pull_request_work_items(id, work_items, organization=None, detect=None): # pylint: disable=redefined-builtin """Link one or more work items to a pull request. :param id: ID of the pull request. :type id: int :param work_items: IDs of the work items to link. Space separated. :type work_items: list of int :rtype: list of :class:`AssociatedWorkItem <v5_0.git.models.AssociatedWorkItem>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) existing_pr = client.get_pull_request_by_id(id) if work_items is not None and work_items: work_items = list(set(work_items)) # make distinct wit_client = get_work_item_tracking_client(organization) pr_url = 'vstfs:///Git/PullRequestId/{project}%2F{repo}%2F{id}'.format( project=existing_pr.repository.project.id, repo=existing_pr.repository.id, id=id) for work_item_id in work_items: patch_document = [] patch_operation = JsonPatchOperation() patch_operation.op = 0 patch_operation.path = '/relations/-' patch_operation.value = WorkItemRelation() patch_operation.value.attributes = {'name': 'Pull Request'} patch_operation.value.rel = 'ArtifactLink' patch_operation.value.url = pr_url patch_document.append(patch_operation) try: wit_client.update_work_item(document=patch_document, id=work_item_id) except AzureDevOpsClientRequestError as ex: logger.debug(ex, exc_info=True) message = ex.args[0] if message != 'Relation already exists.': raise CLIError(ex) refs = client.get_pull_request_work_item_refs(project=existing_pr.repository.project.id, repository_id=existing_pr.repository.id, pull_request_id=id) ids = [] for ref in refs: ids.append(ref.id) return wit_client.get_work_items(ids=ids) def checkout(id, remote_name='origin'): # pylint: disable=redefined-builtin """Checkout the PR source branch locally, if no local changes are present :param id: ID of the pull request. :type id: int :param remote_name: Name of git remote against which PR is raised :type remote_name: str """ git_info = get_vsts_info_from_current_remote_url() organization = git_info.uri if not organization: raise CLIError('This command should be used from a valid Azure DevOps git repository only') client = get_git_client(organization) pr = client.get_pull_request_by_id(id) # favorite the ref refFavoriteRequest = GitRefFavorite(name=pr.source_ref_name, repository_id=pr.repository.id, type=2) try: client.create_favorite(favorite=refFavoriteRequest, project=pr.repository.project.id) except Exception as ex: # pylint: disable=broad-except if 'is already a favorite for user' not in str(ex): raise ex fetch_remote_and_checkout(pr.source_ref_name, remote_name) def remove_pull_request_work_items(id, work_items, organization=None, detect=None): # pylint: disable=redefined-builtin """Unlink one or more work items from a pull request. :param id: ID of the pull request. :type id: int :param work_items: IDs of the work items to unlink. Space separated. :type work_items: list of int :rtype: list of :class:`AssociatedWorkItem <v5_0.git.models.AssociatedWorkItem>` """ # pylint: disable=too-many-nested-blocks organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) existing_pr = client.get_pull_request_by_id(id) if work_items is not None and work_items: work_items = list(set(work_items)) # make distinct wit_client = get_work_item_tracking_client(organization) work_items_full = wit_client.get_work_items(ids=work_items, expand=1) if work_items_full: url = 'vstfs:///Git/PullRequestId/{project}%2F{repo}%2F{id}'.format( project=existing_pr.repository.project.id, repo=existing_pr.repository.id, id=id) for work_item in work_items_full: if work_item.relations is not None: index = 0 for relation in work_item.relations: if relation.url == url: patch_document = [] patch_test_operation = JsonPatchOperation() patch_test_operation.op = 'test' patch_test_operation.path = '/rev' patch_test_operation.value = work_item.rev patch_document.append(patch_test_operation) patch_operation = JsonPatchOperation() patch_operation.op = 1 patch_operation.path = '/relations/{index}'.format(index=index) patch_document.append(patch_operation) wit_client.update_work_item(document=patch_document, id=work_item.id) else: index += 1 refs = client.get_pull_request_work_item_refs(project=existing_pr.repository.project.id, repository_id=existing_pr.repository.id, pull_request_id=id) if refs: ids = [] for ref in refs: ids.append(ref.id) if ids: return wit_client.get_work_items(ids=ids) return None def list_pull_request_work_items(id, organization=None, detect=None): # pylint: disable=redefined-builtin """List linked work items for a pull request. :param id: ID of the pull request. :type id: int :rtype: list of :class:`AssociatedWorkItem <v5_0.git.models.AssociatedWorkItem>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) pr = client.get_pull_request_by_id(id) refs = client.get_pull_request_work_item_refs(project=pr.repository.project.id, repository_id=pr.repository.id, pull_request_id=id) if refs: ids = [] for ref in refs: ids.append(ref.id) wit_client = get_work_item_tracking_client(organization) return wit_client.get_work_items(ids=ids) return refs def vote_pull_request(id, vote, organization=None, detect=None): # pylint: disable=redefined-builtin """Vote on a pull request. :param id: ID of the pull request. :type id: int :param vote: New vote value for the pull request. :type vote: int :rtype: :class:`IdentityRefWithVote <v5_0.git.models.IdentityRefWithVote>` """ organization = resolve_instance(detect=detect, organization=organization) client = get_git_client(organization) pr = client.get_pull_request_by_id(id) resolved_reviewer = IdentityRefWithVote(id=resolve_identity_as_id(ME, organization)) resolved_reviewer.vote = _convert_vote_to_int(vote) created_reviewer = client.create_pull_request_reviewer(project=pr.repository.project.id, repository_id=pr.repository.id, pull_request_id=id, reviewer_id=resolved_reviewer.id, reviewer=resolved_reviewer) return created_reviewer def _convert_vote_to_int(vote): if vote.lower() == 'approve': return 10 if vote.lower() == 'approve-with-suggestions': return 5 if vote.lower() == 'reset': return 0 if vote.lower() == 'wait-for-author': return -5 if vote.lower() == 'reject': return -10 raise CLIError('"{vote}" is an invalid value for a pull request vote.'.format(vote=vote)) def list_pr_policies(id, organization=None, detect=None, top=None, skip=None): # pylint: disable=redefined-builtin """List policies of a pull request. :param id: ID of the pull request. :type id: int :param top: Maximum number of policies to list. :type top: int :param skip: Number of policies to skip. :type skip: int :rtype: list of :class:`PolicyEvaluationRecord <policy.v4_0.models.PolicyEvaluationRecord>` """ organization = resolve_instance(detect=detect, organization=organization) git_client = get_git_client(organization) pr = git_client.get_pull_request_by_id(id) policy_client = get_policy_client(organization) artifact_id = "vstfs:///CodeReview/CodeReviewId/{project_id}/{pull_request_id}".format( project_id=pr.repository.project.id, pull_request_id=id) return policy_client.get_policy_evaluations(project=pr.repository.project.id, artifact_id=artifact_id, top=top, skip=skip) def queue_pr_policy(id, evaluation_id, organization=None, detect=None): # pylint: disable=redefined-builtin """Queue an evaluation of a policy for a pull request. :param id: ID of the pull request. :type id: int :param evaluation_id: ID of the policy evaluation to queue. :type evaluation_id: str :rtype: :class:`PolicyEvaluationRecord <policy.v4_0.models.PolicyEvaluationRecord>` """ organization = resolve_instance(detect=detect, organization=organization) git_client = get_git_client(organization) pr = git_client.get_pull_request_by_id(id) policy_client = get_policy_client(organization) return policy_client.requeue_policy_evaluation(project=pr.repository.project.id, evaluation_id=evaluation_id) def _resolve_reviewers_as_refs(optional_reviewers, required_reviewers, organization): """Takes a list containing identity names, emails, and ids, and return a list of IdentityRefWithVote objects. Reviewers found twice will be made required :param optional_reviewers: the list of optional reviewers :param required_reviewers: the list of required reviewers :rtype: list of :class:`IdentityRefWithVote <v5_0.git.models.IdentityRefWithVote>` """ if optional_reviewers is None: optional_reviewers = [] if required_reviewers is None: required_reviewers = [] resolved_reviewers = [] for reviewer in optional_reviewers: resolved_reviewers.append(IdentityRefWithVote(id=resolve_identity_as_id(reviewer, organization))) for reviewer in required_reviewers: resolved_reviewer = IdentityRefWithVote(id=resolve_identity_as_id(reviewer, organization)) # is this id already in the list (make duplicate required) for optional_reviewer in resolved_reviewers: if optional_reviewer.id == resolved_reviewer.id: optional_reviewer.is_required = True continue resolved_reviewer.is_required = True resolved_reviewers.append(resolved_reviewer) return resolved_reviewers def _resolve_reviewers_as_ids(reviewers, organization): """Takes a list containing identity names, emails, and ids, and returns a list of IdentityRefWithVote objects. """ resolved_reviewers = None if reviewers is not None and reviewers: resolved_reviewers = [] for reviewer in reviewers: resolved_reviewers.append(resolve_identity_as_id(reviewer, organization)) return resolved_reviewers def _open_pull_request(pull_request, organization): """Opens the pull request in the default browser. :param pull_request: The pull request to open. :type pull_request: str """ url = organization.rstrip('/') + '/' + uri_quote(pull_request.repository.project.name)\ + '/_git/' + uri_quote(pull_request.repository.name) + '/pullrequest/'\ + str(pull_request.pull_request_id) webbrowser.open_new(url=url) def _get_default_branch(organization, project, repository): client = get_git_client(organization) repo = client.get_repository(project=project, repository_id=repository) return repo.default_branch