示例#1
0
    def from_github(repo: GithubRepository,
                    pull_id: int) -> 'PullRequestDetails':
        """Retrieves a single pull request.

        References:
            https://developer.github.com/v3/pulls/#get-a-single-pull-request

        Args:
            repo: The github repo to get the pull request from.
            pull_id: The id of the pull request.

        Raises:
            RuntimeError: If the request does not return status 200 (success).
        """
        url = "https://api.github.com/repos/{}/{}/pulls/{}".format(
            repo.organization, repo.name, pull_id)

        response = repo.get(url)

        if response.status_code != 200:
            raise RuntimeError(
                'Pull check failed. Code: {}. Content: {!r}.'.format(
                    response.status_code, response.content))

        payload = json.JSONDecoder().decode(response.content.decode())
        return PullRequestDetails(payload, repo)
示例#2
0
def main():
    access_token = os.getenv(ACCESS_TOKEN_ENV_VARIABLE)
    if not access_token:
        project_id = 'cirq-infra'
        print('{} not set. Trying secret manager.'.format(
            ACCESS_TOKEN_ENV_VARIABLE),
              file=sys.stderr)
        client = secretmanager_v1beta1.SecretManagerServiceClient()
        secret_name = (f'projects/{project_id}/'
                       f'secrets/cirq-bot-api-key/versions/1')
        response = client.access_secret_version(name=secret_name)
        access_token = response.payload.data.decode('UTF-8')

    repo = GithubRepository(
        organization=GITHUB_REPO_ORGANIZATION,
        name=GITHUB_REPO_NAME,
        access_token=access_token)

    log('Watching for automergeable PRs.')
    problem_seen_times = {}  # type: Dict[int, datetime.datetime]
    while True:
        try:
            duty_cycle(repo, problem_seen_times)
        except Exception:  # Anything but a keyboard interrupt / system exit.
            traceback.print_exc()
        wait_for_polling_period()
示例#3
0
def check_collaborator_has_write(
        repo: GithubRepository,
        username: str) -> Optional[CannotAutomergeError]:
    """Checks whether the given user is a collaborator (admin and write access).

    References:
        https://developer.github.com/v3/issues/events/#list-events-for-an-issue

    Args:
        repo: The github repo to check.
        username: The github username to check whether the user is a collaborator.

    Returns:
        CannotAutomergeError if the user does not have admin and write permissions and so
            cannot use automerge, None otherwise.

    Raises:
        RuntimeError: If the request does not return status 200 (success).
    """
    url = "https://api.github.com/repos/{}/{}/collaborators/{}/permission" "".format(
        repo.organization, repo.name, username)

    response = repo.get(url)

    if response.status_code != 200:
        raise RuntimeError(
            'Collaborator check failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))

    payload = json.JSONDecoder().decode(response.content.decode())
    if payload['permission'] not in ['admin', 'write']:
        return CannotAutomergeError(
            'Only collaborators with write permission can use automerge.')

    return None
示例#4
0
 def remote_repo(self) -> GithubRepository:
     """Return the GithubRepository corresponding to this pull request."""
     return GithubRepository(
         organization=self.payload['head']['repo']['owner']['login'],
         name=self.payload['head']['repo']['name'],
         access_token=self.repo.access_token,
     )
示例#5
0
文件: pr_monitor.py 项目: verult/Cirq
def edit_comment(repo: GithubRepository, text: str, comment_id: int) -> None:
    """Edits an existing github comment.

    References:
        https://developer.github.com/v3/issues/comments/#edit-a-comment

    Args:
        repo: The github repo that contains the comment.
        text: The new comment text.
        comment_id: The id of the comment to edit.

    Raises:
            RuntimeError: If the request does not return status 200 (success).
    """
    url = "https://api.github.com/repos/{}/{}/issues/comments/{}".format(
        repo.organization, repo.name, comment_id
    )
    data = {'body': text}
    response = repo.patch(url, json=data)

    if response.status_code != 200:
        raise RuntimeError(
            'Edit comment failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content
            )
        )
示例#6
0
def list_open_pull_requests(repo: GithubRepository,
                            base_branch: Optional[str] = None,
                            per_page: int = 100) -> List[PullRequestDetails]:
    url = (
        f"https://api.github.com/repos/{repo.organization}/{repo.name}/pulls"
        f"?per_page={per_page}")
    data = {
        'state': 'open',
    }
    if base_branch is not None:
        data['base'] = base_branch
    response = repo.get(url, json=data)

    if response.status_code != 200:
        raise RuntimeError(
            'List pulls failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))

    pulls = json.JSONDecoder().decode(response.content.decode())
    results = [PullRequestDetails(pull, repo) for pull in pulls]

    # Filtering via the API doesn't seem to work, so we do it ourselves.
    if base_branch is not None:
        results = [
            result for result in results
            if result.base_branch_name == base_branch
        ]
    return results
示例#7
0
文件: pr_monitor.py 项目: verult/Cirq
def add_labels_to_pr(repo: GithubRepository, pull_id: int, *labels: str) -> None:
    """Add lables to a pull request.

    References:
        https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue

    Args:
        repo: The github repo where the pull request lives.
        pull_id: The id of the pull request.
        *labels: The labels to add to the pull request.

    Raises:
        RuntimeError: If the request to add labels returned anything other than success.
    """
    url = "https://api.github.com/repos/{}/{}/issues/{}/labels".format(
        repo.organization, repo.name, pull_id
    )
    response = repo.post(url, json=list(labels))

    if response.status_code != 200:
        raise RuntimeError(
            'Add labels failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content
            )
        )
示例#8
0
文件: pr_monitor.py 项目: verult/Cirq
def get_repo_ref(repo: GithubRepository, ref: str) -> Dict[str, Any]:
    """Get a given github reference.

    References:
        https://developer.github.com/v3/git/refs/#get-a-reference

    Args:
        repo: The github repo to get the reference from.
        ref: The id of the reference.

    Returns:
        The raw response of the request for the reference..

    Raises:
        RuntimeError: If the request does not return status 200 (success).
    """

    url = f"https://api.github.com/repos/{repo.organization}/{repo.name}/git/refs/{ref}"
    response = repo.get(url)
    if response.status_code != 200:
        raise RuntimeError(
            'Refs get failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content
            )
        )
    payload = json.JSONDecoder().decode(response.content.decode())
    return payload
示例#9
0
def get_branch_details(repo: GithubRepository, branch: str) -> Any:
    """Get details about a github branch.

    References:
        https://developer.github.com/v3/repos/branches/#get-branch

    Args:
        repo: The github repo that has the branch.
        branch: The name of the branch.

    Returns:
        The raw response to the query to get details.

    Raises:
        RuntimeError: If the request does not return status 200 (success).
    """
    url = "https://api.github.com/repos/{}/{}/branches/{}".format(
        repo.organization, repo.name, branch)
    response = repo.get(url)

    if response.status_code != 200:
        raise RuntimeError(
            'Failed to get branch details. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))

    return json.JSONDecoder().decode(response.content.decode())
示例#10
0
def get_all(repo: GithubRepository, url_func: Callable[[int],
                                                       str]) -> List[Any]:
    """Get all results, accounting for pagination.

    Args:
        repo: The github repo to call GET on.
        url_func: A function from an integer page number to the url to get the result for that page.

    Returns:
        A list of the results by page.

    Raises:
        RuntimeError: If the request does not return status 200 (success).
    """
    results: List[Any] = []
    page = 0
    has_next = True
    while has_next:
        url = url_func(page)
        response = repo.get(url)

        if response.status_code != 200:
            raise RuntimeError(
                f'Request failed to {url}. Code: {response.status_code}.'
                f' Content: {response.content!r}.')

        payload = json.JSONDecoder().decode(response.content.decode())
        results += payload
        has_next = 'link' in response.headers and 'rel="next"' in response.headers[
            'link']
        page += 1
    return results
示例#11
0
def remove_label_from_pr(repo: GithubRepository, pull_id: int,
                         label: str) -> bool:
    """Removes a label from a pull request.

    References:
        https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue

    Args:
        repo: The github repo for the pull request.
        pull_id: The id for the pull request.
        label: The label to remove.

    Raises:
        RuntimeError: If the request does not return status 200 (success).

    Returns:
        True if the label existed and was deleted. False if the label did not exist.
    """
    url = "https://api.github.com/repos/{}/{}/issues/{}/labels/{}".format(
        repo.organization, repo.name, pull_id, label)
    response = repo.delete(url)

    if response.status_code == 404:
        payload = json.JSONDecoder().decode(response.content.decode())
        if payload['message'] == 'Label does not exist':
            return False

    if response.status_code == 200:
        # Removed the label.
        return True

    raise RuntimeError('Label remove failed. Code: {}. Content: {!r}.'.format(
        response.status_code, response.content))
示例#12
0
def list_pr_comments(repo: GithubRepository,
                     pull_id: int) -> List[Dict[str, Any]]:
    """List comments for a given pull request.

    References:
        https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue

    Args:
        repo: The github repo for the pull request.
        pull_id: The id of the pull request.

    Returns:
        A list of the raw responses for the pull requests.

    Raises:
        RuntimeError: If the request does not return status 200 (success).
    """
    url = "https://api.github.com/repos/{}/{}/issues/{}/comments".format(
        repo.organization, repo.name, pull_id)
    response = repo.get(url)
    if response.status_code != 200:
        raise RuntimeError(
            'Comments get failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))
    payload = json.JSONDecoder().decode(response.content.decode())
    return payload
示例#13
0
文件: pr_monitor.py 项目: verult/Cirq
def add_comment(repo: GithubRepository, pull_id: int, text: str) -> None:
    """Add a comment to a pull request.

    References:
        https://developer.github.com/v3/issues/comments/#create-a-comment

    Arg:
        rep: The github repo whose pull request should have a comment added to.
        pull_id: The id of the pull request to comment on.
        text: The text of the comment.

    Raises:
        RuntimeError: If the request does not return status 201 (created).
    """
    url = "https://api.github.com/repos/{}/{}/issues/{}/comments".format(
        repo.organization, repo.name, pull_id
    )
    data = {'body': text}
    response = repo.post(url, json=data)

    if response.status_code != 201:
        raise RuntimeError(
            'Add comment failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content
            )
        )
def fetch_github_pull_request(
    destination_directory: str,
    repository: github_repository.GithubRepository,
    pull_request_number: int,
    verbose: bool,
) -> prepared_env.PreparedEnv:
    """Uses content from github to create a dir for testing and comparisons.

    Args:
        destination_directory: The location to fetch the contents into.
        repository: The github repository that the commit lives under.
        pull_request_number: The id of the pull request to clone. If None, then
            the master branch is cloned instead.
        verbose: When set, more progress output is produced.

    Returns:
        Commit ids corresponding to content to test/compare.
    """

    branch = 'pull/{}/head'.format(pull_request_number)
    os.chdir(destination_directory)
    print('chdir', destination_directory, file=sys.stderr)

    shell_tools.run_cmd('git',
                        'init',
                        None if verbose else '--quiet',
                        out=sys.stderr)
    result = _git_fetch_for_comparison(
        remote=repository.as_remote(),
        actual_branch=branch,
        compare_branch='master',
        verbose=verbose,
    )
    shell_tools.run_cmd(
        'git',
        'branch',
        None if verbose else '--quiet',
        'compare_commit',
        result.compare_commit_id,
        log_run_to_stderr=verbose,
    )
    shell_tools.run_cmd(
        'git',
        'checkout',
        None if verbose else '--quiet',
        '-b',
        'actual_commit',
        result.actual_commit_id,
        log_run_to_stderr=verbose,
    )
    return prepared_env.PreparedEnv(
        github_repo=repository,
        actual_commit_id=result.actual_commit_id,
        compare_commit_id=result.compare_commit_id,
        destination_directory=destination_directory,
        virtual_env_path=None,
    )
示例#15
0
def delete_comment(repo: GithubRepository, comment_id: int) -> None:
    """
    References:
        https://developer.github.com/v3/issues/comments/#delete-a-comment
    """
    url = "https://api.github.com/repos/{}/{}/issues/comments/{}".format(
        repo.organization, repo.name, comment_id)
    response = repo.delete(url)
    if response.status_code != 204:
        raise RuntimeError(
            'Comment delete failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))
示例#16
0
def fetch_github_pull_request(destination_directory: str,
                              repository: github_repository.GithubRepository,
                              pull_request_number: int,
                              verbose: bool
                              ) -> prepared_env.PreparedEnv:
    """Uses content from github to create a dir for testing and comparisons.

    Args:
        destination_directory: The location to fetch the contents into.
        repository: The github repository that the commit lives under.
        pull_request_number: The id of the pull request to clone. If None, then
            the master branch is cloned instead.
        verbose: When set, more progress output is produced.

    Returns:
        Commit ids corresponding to content to test/compare.
    """

    branch = 'pull/{}/head'.format(pull_request_number)
    os.chdir(destination_directory)
    print('chdir', destination_directory, file=sys.stderr)

    shell_tools.run_cmd(
        'git',
        'init',
        None if verbose else '--quiet',
        out=sys.stderr)
    result = _git_fetch_for_comparison(remote=repository.as_remote(),
                                       actual_branch=branch,
                                       compare_branch='master',
                                       verbose=verbose)
    shell_tools.run_cmd(
        'git',
        'branch',
        None if verbose else '--quiet',
        'compare_commit',
        result.compare_commit_id,
        log_run_to_stderr=verbose)
    shell_tools.run_cmd(
        'git',
        'checkout',
        None if verbose else '--quiet',
        '-b',
        'actual_commit',
        result.actual_commit_id,
        log_run_to_stderr=verbose)
    return prepared_env.PreparedEnv(
        github_repo=repository,
        actual_commit_id=result.actual_commit_id,
        compare_commit_id=result.compare_commit_id,
        destination_directory=destination_directory,
        virtual_env_path=None)
示例#17
0
def get_repo_ref(repo: GithubRepository, ref: str) -> Dict[str, Any]:
    """
    References:
        https://developer.github.com/v3/git/refs/#get-a-reference
    """

    url = f"https://api.github.com/repos/{repo.organization}/{repo.name}/git/refs/{ref}"
    response = repo.get(url)
    if response.status_code != 200:
        raise RuntimeError('Refs get failed. Code: {}. Content: {!r}.'.format(
            response.status_code, response.content))
    payload = json.JSONDecoder().decode(response.content.decode())
    return payload
示例#18
0
def add_labels_to_pr(repo: GithubRepository, pull_id: int,
                     *labels: str) -> None:
    """
    References:
        https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue
    """
    url = "https://api.github.com/repos/{}/{}/issues/{}/labels".format(
        repo.organization, repo.name, pull_id)
    response = repo.post(url, json=list(labels))

    if response.status_code != 200:
        raise RuntimeError(
            'Add labels failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))
示例#19
0
def add_comment(repo: GithubRepository, pull_id: int, text: str) -> None:
    """
    References:
        https://developer.github.com/v3/issues/comments/#create-a-comment
    """
    url = "https://api.github.com/repos/{}/{}/issues/{}/comments".format(
        repo.organization, repo.name, pull_id)
    data = {'body': text}
    response = repo.post(url, json=data)

    if response.status_code != 201:
        raise RuntimeError(
            'Add comment failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))
示例#20
0
def edit_comment(repo: GithubRepository, text: str, comment_id: int) -> None:
    """
    References:
        https://developer.github.com/v3/issues/comments/#edit-a-comment
    """
    url = "https://api.github.com/repos/{}/{}/issues/comments/{}".format(
        repo.organization, repo.name, comment_id)
    data = {'body': text}
    response = repo.patch(url, json=data)

    if response.status_code != 200:
        raise RuntimeError(
            'Edit comment failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))
示例#21
0
def get_branch_details(repo: GithubRepository, branch: str) -> Any:
    """
    References:
        https://developer.github.com/v3/repos/branches/#get-branch
    """
    url = "https://api.github.com/repos/{}/{}/branches/{}".format(
        repo.organization, repo.name, branch)
    response = repo.get(url)

    if response.status_code != 200:
        raise RuntimeError(
            'Failed to get branch details. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))

    return json.JSONDecoder().decode(response.content.decode())
示例#22
0
def list_pr_comments(repo: GithubRepository,
                     pull_id: int) -> List[Dict[str, Any]]:
    """
    References:
        https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
    """
    url = "https://api.github.com/repos/{}/{}/issues/{}/comments".format(
        repo.organization, repo.name, pull_id)
    response = repo.get(url)
    if response.status_code != 200:
        raise RuntimeError(
            'Comments get failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))
    payload = json.JSONDecoder().decode(response.content.decode())
    return payload
示例#23
0
def main():
    access_token = os.getenv('CIRQ_BOT_GITHUB_ACCESS_TOKEN')
    if not access_token:
        print('CIRQ_BOT_GITHUB_ACCESS_TOKEN not set.', file=sys.stderr)
        sys.exit(1)

    repo = GithubRepository(
        organization='quantumlib',
        name='cirq',
        access_token=access_token)

    log('Watching for automergeable PRs.')
    while True:
        duty_cycle(repo)
        wait_a_tick()
示例#24
0
def main():
    access_token = os.getenv('CIRQ_BOT_GITHUB_ACCESS_TOKEN')
    if not access_token:
        print('CIRQ_BOT_GITHUB_ACCESS_TOKEN not set.', file=sys.stderr)
        sys.exit(1)

    repo = GithubRepository(
        organization='quantumlib',
        name='cirq',
        access_token=access_token)

    log('Watching for automergeable PRs.')
    problem_seen_times = {}  # type: Dict[int, datetime.datetime]
    while True:
        duty_cycle(repo, problem_seen_times)
        wait_for_polling_period()
示例#25
0
def main():
    access_token = os.getenv(ACCESS_TOKEN_ENV_VARIABLE)
    if not access_token:
        print('{} not set.'.format(ACCESS_TOKEN_ENV_VARIABLE), file=sys.stderr)
        sys.exit(1)

    repo = GithubRepository(organization=GITHUB_REPO_ORGANIZATION,
                            name=GITHUB_REPO_NAME,
                            access_token=access_token)

    log('Watching for automergeable PRs.')
    problem_seen_times = {}  # type: Dict[int, datetime.datetime]
    while True:
        try:
            duty_cycle(repo, problem_seen_times)
        except Exception:  # Anything but a keyboard interrupt / system exit.
            traceback.print_exc()
        wait_for_polling_period()
示例#26
0
    def from_github(repo: GithubRepository,
                    pull_id: int) -> 'PullRequestDetails':
        """
        References:
            https://developer.github.com/v3/pulls/#get-a-single-pull-request
        """
        url = "https://api.github.com/repos/{}/{}/pulls/{}".format(
            repo.organization, repo.name, pull_id)

        response = repo.get(url)

        if response.status_code != 200:
            raise RuntimeError(
                'Pull check failed. Code: {}. Content: {!r}.'.format(
                    response.status_code, response.content))

        payload = json.JSONDecoder().decode(response.content.decode())
        return PullRequestDetails(payload, repo)
示例#27
0
def get_all(repo: GithubRepository, url_func: Callable[[int],
                                                       str]) -> List[Any]:
    results: List[Any] = []
    page = 0
    has_next = True
    while has_next:
        url = url_func(page)
        response = repo.get(url)

        if response.status_code != 200:
            raise RuntimeError(
                f'Request failed to {url}. Code: {response.status_code}.'
                f' Content: {response.content!r}.')

        payload = json.JSONDecoder().decode(response.content.decode())
        results += payload
        has_next = 'link' in response.headers and 'rel="next"' in response.headers[
            'link']
        page += 1
    return results
示例#28
0
def delete_comment(repo: GithubRepository, comment_id: int) -> None:
    """Delete a comment.

    References:
        https://developer.github.com/v3/issues/comments/#delete-a-comment

    Args:
        repo: The github repo where the comment lives.
        comment_id: The id of the comment to delete.

    Raises:
        RuntimeError: If the request does not return status 204 (no content).
    """
    url = "https://api.github.com/repos/{}/{}/issues/comments/{}".format(
        repo.organization, repo.name, comment_id)
    response = repo.delete(url)
    if response.status_code != 204:
        raise RuntimeError(
            'Comment delete failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content))
示例#29
0
def main():
    global cla_access_token
    pull_ids = [int(e) for e in sys.argv[1:]]
    access_token = os.getenv('CIRQ_BOT_GITHUB_ACCESS_TOKEN')
    cla_access_token = os.getenv('CIRQ_BOT_ALT_CLA_GITHUB_ACCESS_TOKEN')
    if not access_token:
        print('CIRQ_BOT_GITHUB_ACCESS_TOKEN not set.', file=sys.stderr)
        sys.exit(1)
    if not cla_access_token:
        print('CIRQ_BOT_ALT_CLA_GITHUB_ACCESS_TOKEN not set.', file=sys.stderr)
        sys.exit(1)

    repo = GithubRepository(organization='quantumlib',
                            name='cirq',
                            access_token=access_token)

    if pull_ids:
        auto_merge_multiple_pull_requests(repo, pull_ids)
    else:
        watch_for_auto_mergeable_pull_requests(repo)
示例#30
0
def remove_label_from_pr(repo: GithubRepository, pull_id: int,
                         label: str) -> bool:
    """
    References:
        https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue
    """
    url = "https://api.github.com/repos/{}/{}/issues/{}/labels/{}".format(
        repo.organization, repo.name, pull_id, label)
    response = repo.delete(url)

    if response.status_code == 404:
        payload = json.JSONDecoder().decode(response.content.decode())
        if payload['message'] == 'Label does not exist':
            return False

    if response.status_code == 200:
        # Removed the label.
        return True

    raise RuntimeError('Label remove failed. Code: {}. Content: {!r}.'.format(
        response.status_code, response.content))
示例#31
0
文件: pr_monitor.py 项目: verult/Cirq
def list_open_pull_requests(
    repo: GithubRepository, base_branch: Optional[str] = None, per_page: int = 100
) -> List[PullRequestDetails]:
    """List open pull requests.

    Args:
        repo: The github repo for the pull requests.
        base_branch: The branch for which to request pull requests.
        per_page: The number of results to obtain per page.

    Returns:
        A list of the pull requests.

    Raises:
        RuntimeError: If the request does not return status 200 (success).
    """
    url = (
        f"https://api.github.com/repos/{repo.organization}/{repo.name}/pulls"
        f"?per_page={per_page}"
    )
    data = {'state': 'open'}
    if base_branch is not None:
        data['base'] = base_branch
    response = repo.get(url, json=data)

    if response.status_code != 200:
        raise RuntimeError(
            'List pulls failed. Code: {}. Content: {!r}.'.format(
                response.status_code, response.content
            )
        )

    pulls = json.JSONDecoder().decode(response.content.decode())
    results = [PullRequestDetails(pull, repo) for pull in pulls]

    # Filtering via the API doesn't seem to work, so we do it ourselves.
    if base_branch is not None:
        results = [result for result in results if result.base_branch_name == base_branch]
    return results