diff --git a/.github/workflows/gen-java-docs.yml b/.github/workflows/gen-java-docs.yml new file mode 100644 index 00000000..f15e03cc --- /dev/null +++ b/.github/workflows/gen-java-docs.yml @@ -0,0 +1,35 @@ +name: Generate Java Docs + +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: corretto + + - name: Build with Maven + run: mvn clean install + + - name: Generate Javadocs + run: mvn javadoc:javadoc + + # Publishes Javadocs to GitHub Pages by pushing to `gh-pages` branch + - name: Deploy Javadocs to GitHub Pages + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + mvn scm-publish:publish-scm + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/maven-version-determiner.py b/.github/workflows/maven-version-determiner.py new file mode 100755 index 00000000..8e46562c --- /dev/null +++ b/.github/workflows/maven-version-determiner.py @@ -0,0 +1,116 @@ +#!/usr/bin/python3 +import re +import subprocess +import sys + + +def get_arg(arg_idx) -> str: + return sys.argv[arg_idx] + + +def get_current_version() -> str: + current_version_cmd = subprocess.run( + "mvn help:evaluate -Dexpression=project.version -q -DforceStdout", + shell=True, capture_output=True, text=True) + return current_version_cmd.stdout + + +def get_release_version(release_type: str, current_version: str) -> str: + major, minor, patch = determine_new_version(current_version, release_type) + return str(major) + "." + str(minor) + "." + str(patch) + + +def get_snapshot_version(release_type: str, current_version: str) -> str: + major, minor, patch = determine_new_version(current_version, release_type) + patch += 1 + return str(major) + "." + str(minor) + "." + str(patch) + "-SNAPSHOT" + + +def get_version_tag(release_type: str, current_version: str) -> str: + major, minor, patch = determine_new_version(current_version, release_type) + return "v" + str(major) + "." + str(minor) + "." + str(patch) + + +def determine_new_version(current_version, release_type): + major, minor, patch, is_prerelease = dissect_version(current_version) + + match release_type: + case "MAJOR": + major, minor, patch = get_major_release_version(major) + case "MINOR": + major, minor, patch = get_minor_release_version(major, minor) + case "PATCH": + major, minor, patch = get_patch_release_version(major, minor, patch, + is_prerelease) + case _: + print("Second arg has to be `MAJOR`, `MINOR` or `PATCH`") + sys.exit() + + return major, minor, patch + + +def dissect_version(current_version) -> (int, int, int): + version_regex = get_regex() + regex_match = version_regex.search(current_version) + major: int = int(regex_match.groupdict().get("major")) + minor: int = int(regex_match.groupdict().get("minor")) + patch: int = int(regex_match.groupdict().get("patch")) + is_prerelease: bool = True if regex_match.groupdict().get( + "prerelease") else False + return major, minor, patch, is_prerelease + + +def get_regex(): + # Following REGEX is suggested on semver.org + # https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + return re.compile( + r'^' + r'(?P0|[1-9]\d*)' + r'\.' + r'(?P0|[1-9]\d*)' + r'\.' + r'(?P0|[1-9]\d*)' + r'(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?' + r'(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$') + + +def get_major_release_version(major): + major += 1 + minor = 0 + patch = 0 + return major, minor, patch + + +def get_minor_release_version(major, minor): + minor += 1 + patch = 0 + return major, minor, patch + + +def get_patch_release_version(major, minor, patch, is_prerelease): + if is_prerelease: + # Leave values as is because current version without `prerelease` is new patch version + return major, minor, patch + return major, minor, patch + 1 + + +if __name__ == "__main__": + + version_type = get_arg(1) + release_type = get_arg(2) + + current_version = get_current_version() + + match version_type: + case "release-version": + new_version = get_release_version(release_type, current_version) + case "version-tag": + new_version = get_version_tag(release_type, current_version) + case "snapshot-version": + new_version = get_snapshot_version(release_type, current_version) + case _: + print( + "First arg has to be `release-version`, `version-tag` or `snapshot-version`.") + sys.exit() + + print(new_version) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 00000000..285de369 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,56 @@ +name: prepare-release + +on: + workflow_dispatch: + inputs: + release: + description: Type of release + required: true + type: choice + options: + - PATCH + - MINOR + - MAJOR + default: PATCH + +jobs: + prepare: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v4 + + # SSH connection with keys is necessary to allow `git push` + - name: Ensure correct SSH connection + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - uses: actions/setup-java@v3 + with: + java-version: 11 + distribution: corretto + cache: maven + + - name: Determine new versions + run: | + echo "release_version=$(./.github/workflows/maven-version-determiner.py release-version $release_type)" >> "$GITHUB_ENV" + echo "snapshot_version=$(./.github/workflows/maven-version-determiner.py snapshot-version $release_type)" >> "$GITHUB_ENV" + echo "version_tag=$(./.github/workflows/maven-version-determiner.py version-tag $release_type)" >> "$GITHUB_ENV" + env: + release_type: ${{ inputs.release }} + + - name: Configure Git user + run: | + git config user.email "actions@users.noreply.github.com" + git config user.name "GitHub Actions" + + - name: Prepare with Maven release plugin + run: > + mvn + --batch-mode + -Dresume=false + -Drelease-version=$release_version + -Dtag=$version_tag + -DdevelopmentVersion=$snapshot_version + release:prepare diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index e0586986..6c346251 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -1,29 +1,47 @@ -# This workflow will build a package using Maven and then publish it to GitHub packages when a release is created -# For more information see: https://github.com/actions/setup-java#apache-maven-with-a-settings-path - name: maven-build on: - pull_request: - branches: - - master + pull_request: + branches: + - master +# Do not execute only for specific paths if workflow is required. +# https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks jobs: build: - runs-on: ubuntu-latest + + strategy: + matrix: + os: + - ubuntu-latest + java-version: + - 11 + - 17 + - 21 + + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.11 - uses: actions/setup-java@v1 - with: - java-version: 1.11 - server-id: github # Value of the distributionManagement/repository/id field of the pom.xml - settings-path: ${{ github.workspace }} # location for the settings.xml file - - - name: Build - run: mvn -B package --file pom.xml -Pcoverage -Dsytle.colors=always --errors - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} + + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java-version }} + distribution: corretto + cache: maven + + - name: Build with Maven + run: > + mvn + --batch-mode + --file pom.xml + -Pcoverage + -Dsytle.colors=always + --errors + package + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release-on-github.yml b/.github/workflows/release-on-github.yml new file mode 100644 index 00000000..46d3d72e --- /dev/null +++ b/.github/workflows/release-on-github.yml @@ -0,0 +1,29 @@ +name: github-release + +on: + workflow_dispatch: + inputs: + tag: + description: Create GitHub release of following tag + required: true + type: string + workflow_call: + inputs: + tag: + required: true + type: string + +jobs: + create-release: + runs-on: ubuntu-latest + steps: + + - name: Create GitHub release + run: | + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/spotify/github-java-client/releases \ + -d '{"tag_name":"${{ inputs.tag }}","target_commitish":"master","draft":false,"prerelease":false,"generate_release_notes":true}' \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b5d81ec..1439ea00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,40 +1,74 @@ -# This workflow will build a package using Maven and then publish it to GitHub packages when a release is created -# For more information see: https://github.com/actions/setup-java#apache-maven-with-a-settings-path - name: maven-release on: push: branches: - master + paths-ignore: + - '*.md' + - '.gitignore' jobs: build: - runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: corretto + cache: maven + server-id: central # Value of distributionManagement.repository.id field of pom.xml + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + settings-path: ${{ github.workspace }} # Location for settings.xml file + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY_ABHI }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Publish with Maven deploy + run: | + if [ "${{ env.ACTIONS_STEP_DEBUG }}" == "true" ]; then + mvn --batch-mode --activate-profiles deploy --settings $GITHUB_WORKSPACE/settings.xml -Pcoverage clean deploy -X + else + mvn --batch-mode --activate-profiles deploy --settings $GITHUB_WORKSPACE/settings.xml -Pcoverage clean deploy + fi + env: + MAVEN_USERNAME: ${{ secrets.NEXUS_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE_ABHI }} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + get-tag-of-current-sha: + needs: build + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.tag-retriever.outputs.tag }} + # Var is empty if command to retrieve tag fails (e.g. if current SHA has no tag associated) steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.11 - uses: actions/setup-java@v1 - with: - java-version: 1.11 - server-id: github # Value of the distributionManagement/repository/id field of the pom.xml - settings-path: ${{ github.workspace }} # location for the settings.xml file - - - name: Publish - uses: samuelmeuli/action-maven-publish@v1 - with: - gpg_private_key: ${{ secrets.gpg_private_key }} - gpg_passphrase: ${{ secrets.gpg_passphrase }} - nexus_username: ${{ secrets.nexus_username }} - nexus_password: ${{ secrets.nexus_password }} - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} -# - name: Release -# uses: softprops/action-gh-release@v1 -# if: startsWith(github.ref, 'refs/tags/') -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + + - name: Clone repo with complete history and tags + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Store tag of SHA if present + id: tag-retriever + run: | + echo "tag=$(git describe --exact-match ${{ github.sha }})" >> "$GITHUB_OUTPUT" + + + trigger-github-release: + needs: get-tag-of-current-sha + name: Trigger GitHub release workflow + if: needs.get-tag-of-current-sha.outputs.tag + # Runs job only if tag is present. + uses: ./.github/workflows/release-on-github.yml + with: + tag: ${{ needs.get-tag-of-current-sha.outputs.tag }} diff --git a/.gitignore b/.gitignore index b199a8ee..dd496563 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ target .idea *.iml +## VS Code +.vscode + ## Logs *.log @@ -23,3 +26,10 @@ dependency-reduced-pom.xml .classpath .project .settings/ + +# mvn release +pom.xml.releaseBackup +release.properties + +# macOS +.DS_Store diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 00000000..85081bc9 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,4 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=11.0.21-amzn +maven=3.8.6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..af4ce57a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing to the github-java-client + +Thanks for your interest in the github-java-client. Our goal is to bring a reliable Java-based +alternative to the GitHub API. + +## Getting Started + +The Github Java Clients's [open issues are here](https://github.com/spotify/github-java-client/issues). +In time, we'll tag issues that would make a good first pull request for new contributors. An easy +way to get started helping the project is to *file an issue*. Issues can include bugs to fix, +features to add, or documentation that looks outdated. + +This library is maintained by @spotify/gjc-maintainers. If you have any questions, issues or need a +review, please tag this team in the relevant PR/issue. + +## Contributions + +This project welcomes contributions from everyone. + +Contributions to github-java-client should be made in the form of GitHub pull requests. Each pull +request will be reviewed by a maintainer of the library and either merged and released or given +feedback for changes that would be required. + +## Pull Request Checklist + +- Branch from the master branch and, if needed, rebase to the current master branch before + submitting your pull request. If it doesn't merge cleanly with master you may be asked to rebase + your changes. +- Commits should be as small as possible while ensuring that each commit is valid independently + (i.e. each commit should compile and the tests should pass). +- Add tests relevant to the fixed bug or new feature. We love to increase our test coverage so any + contributions made to improving that will be very welcomed. + +## Coding Standards + +- This library is modelled after the [GitHub API](https://docs.github.com/en/rest?apiVersion=2022-11-28) and it has been structured to mimic that. + For example, to access the Teams endpoints, you need to instantiate an `OrganisationClient` + and then a `TeamsClient` as [seen here](./src/main/java/com/spotify/github/v3/clients/OrganisationClient.java). This mirrors the nested structure of the API endpoints such as the + [/orgs/{org}/teams/{team_slug}](https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#list-teams) endpoint +- We operate a monkey see, monkey do approach to this library. We understand that there are some inconsistencies in the library + in terms of how the tests and/or endpoints are written but we, with your help, are working on creating a more consistent codebase. +- All bug fixes and new features need to be fully tested. diff --git a/README.md b/README.md index 42ac3eb3..a87acd16 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,57 @@ -![maven](https://github.com/spotify/github-client/workflows/maven/badge.svg) +# WE ARE NO LONGER ACCEPTING CHANGES TO THIS REPOSITORY AND WILL BE DISCONTINUING SUPPORT FOR THIS PROJECT END OF JAN 2026 +Please see alternatives such as a https://github.com/hub4j/github-api + +![release pipeline](https://github.com/spotify/github-java-client/actions/workflows/release.yml/badge.svg) [![codecov](https://codecov.io/gh/spotify/github-java-client/branch/master/graph/badge.svg?token=ADHNCIESSL)](https://codecov.io/gh/spotify/github-java-client)[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![lifecycle: beta](https://img.shields.io/badge/lifecycle-beta-509bf5.svg) -![Maven Central](https://img.shields.io/maven-central/v/com.spotify/github-client) - +[![Maven Central](https://img.shields.io/maven-central/v/com.spotify/github-client)](https://mvnrepository.com/artifact/com.spotify/github-client) -# github-client +# github-java-client -A small Java library for talking to Github/Github Enterprise and interacting with projects. +A small Java library for talking to GitHub/GitHub Enterprise and interacting with projects. -It supports authentication via simple access tokens, JWT endpoints and Github Apps (via private key). +It supports authentication via simple access tokens, JWT endpoints and GitHub Apps (via private key). It is also very light on GitHub, doing as few requests as necessary. +This library is maintained by @spotify/gjc-maintainers. If you have any questions, issues or need a +review, please tag this team in the relevant PR/issue. + ## Getting Started +You can find this library in [maven central repository](https://mvnrepository.com/artifact/com.spotify/github-client). + Include the latest version of github-client into your project: In Maven: + ```xml + - com.spotify - github-client - version + com.spotify + github-client + version ``` -Start talking to Github API. - -```java -final GitHubClient github = GitHubClient.create(URI.create("https://github.com/api/v3/")); -final IssueApi issueClient = github.createRepositoryClient("my-org", "my-repo").createIssueClient(); -issueClient.listComments(ISSUE_ID).get().forEach(comment -> log.info(comment.body())); -``` - ## Authenticating ### Simple access token ```java -final GitHubClient github = GitHubClient.create(URI.create("https://github.com/api/v3/"), "my-access-token"); -// Do the requests -github.createRepositoryClient("my-org", "my-repo").getCommit("sha"); +final GitHubClient githubClient = GitHubClient.create(URI.create("https://api.github.com/"), "my-access-token"); ``` ### Private key -To authenticate as a Github App, you must provide a private key and the App ID, together with the API URL. +To authenticate as a GitHub App, you must provide a private key and the App ID, together with the API URL. ```java -final GitHubClient github = - GitHubClient.create( - URI.create("https://github.com/api/v3/"), - new File("/path-to-the/private-key.pem"), - APP_ID); +final GitHubClient githubClient = + GitHubClient.create( + URI.create("https://api.github.com/"), + new File("/path-to-the/private-key.pem"), + APP_ID); ``` Then, you can scope the client for a specific Installation ID, to do the operations at the installation level. @@ -60,40 +59,93 @@ The client will manage the generation of JWT tokens, as well as requesting and c from GitHub. ```java -final GitHubClient scoped = GitHubClient.scopeForInstallationId(github, INSTALLATION_ID); -// Do the requests now using the scoped client. -scoped.createRepositoryClient("my-org", "my-repo").getCommit("sha"); +final GitHubClient scopedClient = GitHubClient.scopeForInstallationId(githubClient, INSTALLATION_ID); ``` It is also possible to provide the installation to the root client. -Refer to [Github App Authentication Guide](https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/) for more information. +Refer +to [GitHub App Authentication Guide](https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/) +for more information. ## Usage -This library attempts to mirror the structure of GitHub API endpoints. As an example, to get details of a Commit, there is -the `GET /repos/:owner/:repo/commits` API call, under the `repos` API. Therefore, the `getCommit` method lives in the RepositoryClient. +This library attempts to mirror the structure of GitHub API endpoints. As an example, to get details of a Commit, there +is +the `GET /repos/:owner/:repo/commits` API call, under the `repos` API. Therefore, the `getCommit` method lives in the +RepositoryClient. ```java -final GitHubClient github = GitHubClient.create(URI.create("https://github.com/api/v3/"), "my-access-token"); -github.createRepositoryClient("my-org", "my-repo").getCommit("sha"); +final RepositoryClient repositoryClient = githubClient.createRepositoryClient("my-org", "my-repo"); +log.info(repositoryClient.getCommit("sha").get().htmlUrl()); ``` -Some APIs, such as Checks API are nested in the Repository API. Endpoints such as `POST /repos/:owner/:repo/check-runs` live in the ChecksClient: +Another example of the mirrored structure is that some of the APIs are nested under a parent API. +For example, endpoints related to check runs or issues are nested under the Repository client: ```java -final GitHubClient github = - GitHubClient.create( - URI.create("https://github.com/api/v3/"), - new File("/path-to-the/private-key.der"), - APP_ID); -// Checks API need to be used by Github Apps -GitHubClient.scopeForInstallationId(github, INSTALLATION_ID) - .createRepositoryClient("my-org", "my-repo") - .createChecksApiClient() - .createCheckRun(CHECK_RUN_REQUEST); +final ChecksClient checksClient = repositoryClient.createChecksApiClient(); +checksClient.createCheckRun(CHECK_RUN_REQUEST); + +final IssueClient issueClient = repositoryClient.createIssueClient(); +issueClient + .createComment(ISSUE_ID, "comment body") + .thenAccept(comment ->log.info("created comment "+comment.htmlUrl())); + ``` +And endpoints related to teams and memberships are nested under the Organisation client: + +```java +final TeamClient teamClient = organisationClient.createTeamClient(); +teamClient.getMembership("username"); +``` + +## Tracing + +The GitHub client supports tracing via both OpenCensus and OpenTelemetry. Since OpenCensus is deprecated, we recommend +using OpenTelemetry. Using OpenTelemetry also enables context propagation when using this library. +To enable tracing, you need to provide a tracer when initializing the client. + +### OpenTelemetry + +```java +import com.spotify.github.tracing.opentelemetry.OpenTelemetryTracer; + +final GitHubClient githubClient = + GitHubClient.create(baseUri, accessToken) + // Uses GlobalOpenTelemetry.get() to fetch the default tracer + .withTracer(new OpenTelemetryTracer()); +``` + +You can also provide a custom `OpenTelemetry` object if you want to use a specific one. + +```java +import com.spotify.github.tracing.opentelemetry.OpenTelemetryTracer; + +final GitHubClient githubClient = + GitHubClient.create(baseUri, accessToken) + // Uses custom openTelemetry object to fetch the tracer + .withTracer(new OpenTelemetryTracer(openTelemetry)); +``` + +### OpenCensus + +```java +import com.spotify.github.tracing.opencensus.OpenCensusTracer; + +final GitHubClient githubClient = + GitHubClient.create(baseUri, accessToken) + // Uses Tracing.getTracer() to fetch the default tracer + .withTracer(new OpenCensusTracer()); +``` + +## Supported Java versions + +This library is written and published with Java version 11. In our CI workflows, we execute +automated tests with the Java LTS versions 11, 17 and 21. Due to Java's backward compatibility, +this library can definitely be used in all the tested versions. + ## Contributing This project uses Maven. To run the tests locally, just run: @@ -102,15 +154,72 @@ This project uses Maven. To run the tests locally, just run: mvn clean verify ``` +### Maintainers + +#### Publishing a new version + +If you are a maintainer, you can release a new version by just triggering the workflow +[prepare-release](./.github/workflows/prepare-release.yml) through the +[web UI](https://github.com/spotify/github-java-client/actions/workflows/prepare-release.yml). + +- Select whether the new release should be a `major`, `minor` or `patch` release +- Trigger the release preparation on the `master` branch +- Pushes of this workflow will trigger runs of the + [maven-release](https://github.com/spotify/github-java-client/actions/workflows/release.yml) + workflow, which in turn will trigger the + [github-release](https://github.com/spotify/github-java-client/actions/workflows/release-on-github.yml) + workflow with the automatically created tag + +The `prepare-release` workflow will also update the snapshot version in the `pom.xml` file to the next version. The +version which will be published to Maven Central will be the one specified in the `pom.xml` file (without the +`-SNAPSHOT` suffix). + +#### Updating the GPG signing key + +If you need to update the GPG signing key used for signing the releases when the existing key expires, you can do so by +following these steps: + +1. Generate a new GPG key pair or use an existing one. + If you don't have a GPG key pair, you can generate one using the following command: + ```bash + gpg --full-generate-key + ``` + Follow the prompts to create your key pair. Make sure to remember the passphrase you set. +2. List your GPG keys to find the key ID: + ```bash + gpg --list-keys + ``` + Look for the `pub` line, which will show the key ID in the format `XXXXXXXX`. +3. Export the public key to a file: + ```bash + gpg --armor --export > publickey.asc + ``` +4. export the private key to a file: + ```bash + gpg --armor --export-secret-key > privatekey.asc + ``` +5. Upload the private key to the GitHub repository secrets in `GPG_PRIVATE_KEY` and paste the contents of + `privatekey.asc`. +6. Update the passphrase in the `GPG_PASSPHRASE` secret with the passphrase you set when generating the key. +7. Upload the public key to the OpenGpg key server at https://keys.openpgp.org/ +8. Make sure to verify the public key with your email address on OpenGPG and that it is available on the key server. +9. Make sure that the release workflow is configured to use the `GPG_PRIVATE_KEY` and `GPG_PASSPHRASE` secrets. +10. Run the release workflow to publish a new version of the library. + ## Notes about maturity This module was created after existing libraries were evaluated and dismissed, and we found that we were writing similar -code in multiple projects. As such, it at least initially only contains enough functionality for our internal requirements -which reflect that we were working on build system integration with the Github pull requests. It has been widely used for 4+ -years. It's important to notice that it does not cover all Github v3 API. Adding missing endpoints should be very straightforward. +code in multiple projects. As such, it at least initially only contains enough functionality for our internal +requirements +which reflects that we were working on build system integration with the GitHub pull requests. It has been widely used +for 4+ +years. It's important to notice that it does not cover all GitHub v3 API. Adding missing endpoints should be very +straightforward. Pull Requests are welcome. ## Code of conduct -This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. + +This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this +code. [code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md diff --git a/catalog-info.yaml b/catalog-info.yaml new file mode 100644 index 00000000..5b84848c --- /dev/null +++ b/catalog-info.yaml @@ -0,0 +1,7 @@ +apiVersion: backstage.io/v1alpha1 +kind: Resource +metadata: + name: github-java-client +spec: + type: resource + owner: hotsauce diff --git a/pom.xml b/pom.xml index d3adbb3a..40dc2c05 100644 --- a/pom.xml +++ b/pom.xml @@ -1,14 +1,14 @@ 4.0.0 - + github-java-client github-client - 0.0.26-SNAPSHOT + 0.5.9-SNAPSHOT com.spotify foss-root - 10 + 15 @@ -23,42 +23,25 @@ scm:git:https://github.com/spotify/github-java-client.git scm:git:git@github.com:spotify/github-java-client.git scm:https://github.com/spotify/github-java-client/ - HEAD + v0.3.7 Spotify AB - http://www.spotify.com + https://www.spotify.com - ossrh - https://oss.sonatype.org/content/repositories/snapshots + central + https://central.sonatype.com/repository/maven-snapshots/ - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + central + https://central.sonatype.com/repository/maven-releases/ - - - henriquetruta - Henrique Truta - henriquet@spotify.com - Spotify AB - http://www.spotify.com - - - hewhomustnotbenamed - Abhimanyu Shegokar - abhimanyus@spotify.com - Spotify AB - http://www.spotify.com - - - apache.snapshots @@ -84,24 +67,25 @@ UTF-8 UTF-8 + 1772201868 spotbugsexclude.xml error checkstyle.xml - 28.0-jre + 32.1.3-jre 3.0.2 1.1.1 - 4.13.1 - 1.7.12 - 3.2.4 + 5.10.0 + 1.7.36 + 5.6.0 2.6 - 2.8.9 - 2.8.3 - 2.0.2 - 3.0.1 - - ${project.groupId}.githubclient.shade + 2.20.1 + 2.9.3 + 3.3 + 0.31.1 + 4.11.0 + 1.51.0 @@ -111,6 +95,20 @@ objenesis ${objenesis.version} + + com.fasterxml.jackson + jackson-bom + ${jackson.version} + import + pom + + + io.opentelemetry + opentelemetry-bom + ${opentelemetry.version} + pom + import + @@ -128,7 +126,7 @@ com.squareup.okhttp3 okhttp - 3.14.7 + ${okhttp.version} org.slf4j @@ -140,46 +138,75 @@ jsr311-api ${jsr311-api.version} + + org.apache.httpcomponents + httpclient + 4.5.14 + com.fasterxml.jackson.core jackson-annotations - 2.11.0 com.fasterxml.jackson.core jackson-databind - 2.11.0 com.fasterxml.jackson.module jackson-module-parameter-names - ${jackson.version} com.fasterxml.jackson.datatype jackson-datatype-jdk8 - ${jackson.version} com.fasterxml.jackson.datatype jackson-datatype-jsr310 - ${jackson.version} io.jsonwebtoken jjwt-api - 0.10.5 + 0.12.3 + + + io.opencensus + opencensus-api + ${opencensus.version} + + + + io.opentelemetry + opentelemetry-api + + + io.opentelemetry + opentelemetry-sdk + + + io.opentelemetry + opentelemetry-sdk-testing + + + io.opentelemetry.instrumentation + opentelemetry-okhttp-3.0 + 2.8.0-alpha + + + commons-io + commons-io + 2.14.0 + compile io.jsonwebtoken jjwt-impl - 0.10.5 + 0.12.3 runtime io.jsonwebtoken jjwt-jackson - 0.10.5 + 0.12.3 runtime @@ -191,7 +218,7 @@ uk.co.datumedge hamcrest-json - 0.2 + 0.3 test @@ -201,17 +228,30 @@ test - junit - junit - ${junit.version} + com.github.npathai + hamcrest-optional + 2.0.0 test org.junit.jupiter junit-jupiter-engine - 5.5.0 + ${junit.version} test + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + org.mockito mockito-core @@ -219,99 +259,87 @@ test - com.squareup.okhttp3 - mockwebserver - 3.14.7 + io.opencensus + opencensus-testing + ${opencensus.version} test - org.powermock - powermock-module-junit4 - ${powermock.version} + io.opencensus + opencensus-impl + ${opencensus.version} test - - - org.mockito - mockito-core - - - org.powermock - powermock-api-mockito2 - ${powermock.version} + com.squareup.okhttp3 + mockwebserver + ${okhttp.version} test - - - org.mockito - mockito-core - - commons-io commons-io - 2.6 + 2.14.0 compile - coverage - - - - org.jacoco - jacoco-maven-plugin - 0.8.5 - - - - - **/*Builder* - **/*Immutable* - **/*_Factory* - **/*_*Factory*.* - **/generated-sources*.* - - - - BUNDLE - - - INSTRUCTION - COVEREDRATIO - 0.60 - - - - - - - - pre-test - - prepare-agent - - - - default-check - - check - - - - post-unit-test - test - - report - - - - - - + coverage + + + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + + + **/*Builder* + **/*Immutable* + **/*_Factory* + **/*_*Factory*.* + **/generated-sources*.* + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + 0.60 + + + + + + + + pre-test + + prepare-agent + + + + default-check + + check + + + + post-unit-test + test + + report + + + + + + @@ -322,7 +350,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.1.0 sign-artifacts @@ -331,9 +359,11 @@ sign + --pinentry-mode loopback + --no-tty @@ -353,51 +383,35 @@ - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - - attach-javadocs - - jar - - - - - ${project.build.sourceDirectory}:${project.build.directory}/generated-sources/annotations - - - javax.annotation - javax.annotation-api - 1.3.2 - - - - none - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 true - ossrh - https://oss.sonatype.org/ - true + central + github-java-client + true + published - + maven-compiler-plugin + + + default-compile + generate-sources + + compile + + + 11 11 @@ -414,11 +428,11 @@ org.apache.maven.plugins maven-surefire-plugin - 2.19.1 + 3.2.1 maven-jar-plugin - 3.0.2 + 3.2.2 @@ -443,7 +457,7 @@ maven-enforcer-plugin - 1.4.1 + 3.6.2 enforce @@ -481,36 +495,43 @@ org.apache.maven.plugins - maven-shade-plugin - 3.2.3 + maven-javadoc-plugin + 3.3.1 - package + attach-javadocs - shade + jar - - - - ${project.groupId}:${project.artifactId} - com.squareup.okhttp3 - com.squareup.okio - - - - - okhttp3 - ${shade.id}.okhttp3 - - - okio - ${shade.id}.okio - - - + + 11 + none + + + + org.apache.maven.plugins + maven-scm-publish-plugin + 1.0 + + target/site/apidocs + scm:git:git@github.com:spotify/github-java-client.git + gh-pages + + + + + org.apache.maven.plugins + maven-release-plugin + 3.0.0-M6 + + deploy + + + + diff --git a/src/main/java/com/spotify/github/async/Async.java b/src/main/java/com/spotify/github/async/Async.java new file mode 100644 index 00000000..cb49608b --- /dev/null +++ b/src/main/java/com/spotify/github/async/Async.java @@ -0,0 +1,54 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2024 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.async; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.util.stream.StreamSupport.stream; + +/** Async class to facilitate async operations. */ +public class Async { + private Async() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static Stream streamFromPaginatingIterable(final Iterable> iterable) { + return stream(iterable.spliterator(), false) + .flatMap(page -> stream(page.spliterator(), false)); + } + + public static CompletableFuture exceptionallyCompose( + final CompletableFuture future, final Function> handler) { + + return future + .handle( + (result, throwable) -> { + if (throwable != null) { + return handler.apply(throwable); + } else { + return CompletableFuture.completedFuture(result); + } + }) + .thenCompose(Function.identity()); + } +} diff --git a/src/main/java/com/spotify/github/http/BaseHttpResponse.java b/src/main/java/com/spotify/github/http/BaseHttpResponse.java new file mode 100644 index 00000000..0731e041 --- /dev/null +++ b/src/main/java/com/spotify/github/http/BaseHttpResponse.java @@ -0,0 +1,129 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.http; + +import java.util.List; +import java.util.Map; + +/** BaseHttpResponse is the base implementation of HttpResponse. */ +public abstract class BaseHttpResponse implements HttpResponse { + private static final int HTTP_OK = 200; + private static final int HTTP_BAD_REQUEST = 400; + + protected final HttpRequest request; + protected final int statusCode; + protected final String statusMessage; + protected final Map> headers; + + public BaseHttpResponse( + final HttpRequest request, + final int statusCode, + final String statusMessage, + final Map> headers) { + this.request = request; + this.statusCode = statusCode; + this.statusMessage = statusMessage; + this.headers = headers; + } + + /** + * Returns the request that generated this response. + * + * @return HttpRequest the request that generated this response + */ + @Override + public HttpRequest request() { + return request; + } + + /** + * Returns the HTTP status code of the response. + * + * @return the status code of the response + */ + @Override + public int statusCode() { + return statusCode; + } + + /** + * Returns the HTTP status message of the response. + * + * @return the status message of the response + */ + @Override + public String statusMessage() { + return statusMessage; + } + + /** + * Returns the headers of the response. + * + * @return the headers of the response as a Map of strings + */ + @Override + public Map> headers() { + return this.headers; + } + + /** + * Returns the values of the header with the given name. If the header is not present, this method + * returns null. + * + * @param headerName the name of the header + * @return the values of the header with the given name as a List of strings, or null if the + * header is not present + */ + @Override + public List headers(final String headerName) { + return this.headers.get(headerName); + } + + /** + * Returns the first value of the header with the given name. If the header is not present, this + * method returns null. + * + * @param headerName the name of the header + * @return the first value of the header with the given name, or null if the header is not present + */ + @Override + public String header(final String headerName) { + List headerValues = this.headers(headerName); + if (headerValues == null || headerValues.isEmpty()) { + return null; + } + if (headerValues.size() == 1) { + return headerValues.get(0); + } else { + return String.join(",", headerValues); + } + } + + /** + * Was the request successful? + * + * @return true if the status code is in the range [200, 400) + */ + @Override + public boolean isSuccessful() { + return this.statusCode() >= HTTP_OK && this.statusCode() < HTTP_BAD_REQUEST; + } +} diff --git a/src/main/java/com/spotify/github/http/HttpClient.java b/src/main/java/com/spotify/github/http/HttpClient.java new file mode 100644 index 00000000..bc29e158 --- /dev/null +++ b/src/main/java/com/spotify/github/http/HttpClient.java @@ -0,0 +1,29 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.http; + +import com.spotify.github.tracing.Tracer; +import java.util.concurrent.CompletableFuture; + +public interface HttpClient { + CompletableFuture send(HttpRequest request); + void setTracer(Tracer tracer); +} diff --git a/src/main/java/com/spotify/github/http/HttpRequest.java b/src/main/java/com/spotify/github/http/HttpRequest.java new file mode 100644 index 00000000..018797ca --- /dev/null +++ b/src/main/java/com/spotify/github/http/HttpRequest.java @@ -0,0 +1,67 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.http; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import java.util.List; +import java.util.Map; +import org.immutables.value.Value; + +import javax.annotation.Nullable; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableHttpRequest.class) +@JsonDeserialize(as = ImmutableHttpRequest.class) +public interface HttpRequest { + @Value.Default + default String method() { + return "GET"; + } + + String url(); + + @Nullable + String body(); + + @Value.Default + default Map> headers() { + return Map.of(); + } + + default List headers(String headerName) { + return headers().get(headerName); + } + + default String header(String headerName) { + List headerValues = this.headers(headerName); + if (headerValues == null || headerValues.isEmpty()) { + return null; + } + if (headerValues.size() == 1) { + return headerValues.get(0); + } else { + return String.join(",", headerValues); + } + } +} diff --git a/src/main/java/com/spotify/github/http/HttpResponse.java b/src/main/java/com/spotify/github/http/HttpResponse.java new file mode 100644 index 00000000..34b9488c --- /dev/null +++ b/src/main/java/com/spotify/github/http/HttpResponse.java @@ -0,0 +1,48 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.http; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +public interface HttpResponse { + // Returns the request that was sent to the server + HttpRequest request(); + // Returns the HTTP status code + int statusCode(); + // Returns the HTTP status message + String statusMessage(); + // Returns the response body as an InputStream + InputStream body(); + // Returns the response body as a String + String bodyString(); + // Returns the response headers as a Map + Map> headers(); + // Returns the response headers for a specific header name as a list of Strings + List headers(String headerName); + // Returns the response headers for a specific header name as a single String + String header(String headerName); + // Returns true if the response was successful + boolean isSuccessful(); + // Closes the response + void close(); +} diff --git a/src/main/java/com/spotify/github/http/okhttp/OkHttpHttpClient.java b/src/main/java/com/spotify/github/http/okhttp/OkHttpHttpClient.java new file mode 100644 index 00000000..c5ef9bc4 --- /dev/null +++ b/src/main/java/com/spotify/github/http/okhttp/OkHttpHttpClient.java @@ -0,0 +1,242 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.http.okhttp; + +import static okhttp3.MediaType.parse; + +import com.google.common.annotations.VisibleForTesting; +import com.spotify.github.http.HttpClient; +import com.spotify.github.http.HttpRequest; +import com.spotify.github.http.HttpResponse; +import com.spotify.github.http.ImmutableHttpRequest; +import com.spotify.github.tracing.NoopTracer; +import com.spotify.github.tracing.Span; +import com.spotify.github.tracing.TraceHelper; +import com.spotify.github.tracing.Tracer; +import com.spotify.github.tracing.opencensus.OpenCensusTracer; +import com.spotify.github.tracing.opentelemetry.OpenTelemetryTracer; +import io.opentelemetry.instrumentation.okhttp.v3_0.OkHttpTelemetry; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + +/** + * OkHttpHttpClient is the implementation of HttpClient using OkHttp. This also serves as an example + * of how to create a custom HttpClient. This HttpClient is also capable of tracing the requests + * using OpenCensus or OpenTelemetry. + */ +public class OkHttpHttpClient implements HttpClient { + private final OkHttpClient client; + private Tracer tracer; + private Call.Factory callFactory; + + public OkHttpHttpClient(final OkHttpClient client) { + this.client = client; + this.tracer = NoopTracer.INSTANCE; + this.callFactory = createTracedClient(); + } + + public OkHttpHttpClient(final OkHttpClient client, final Tracer tracer) { + this.client = client; + this.tracer = tracer; + this.callFactory = createTracedClient(); + } + + /** + * Send a request and return a future with the response. + * + * @param httpRequest the request to send + * @return a future with the response + */ + @Override + public CompletableFuture send(final HttpRequest httpRequest) { + Request request = buildOkHttpRequest(httpRequest); + CompletableFuture future = new CompletableFuture<>(); + try (Span span = tracer.span(httpRequest)) { + if (this.callFactory == null) { + this.callFactory = createTracedClient(); + } + tracer.attachSpanToFuture(span, future); + try { + this.callFactory + .newCall(request) + .enqueue( + new Callback() { + + @Override + public void onResponse(@NotNull final Call call, @NotNull final Response response) + throws IOException { + future.complete(new OkHttpHttpResponse(httpRequest, response)); + } + + @Override + public void onFailure(@NotNull final Call call, @NotNull final IOException e) { + future.completeExceptionally(e); + } + }); + } catch (Exception e) { + future.completeExceptionally(e); + } + } + return future; + } + + @Override + public void setTracer(final Tracer tracer) { + this.tracer = tracer; + this.callFactory = createTracedClient(); + } + + /** + * Build an OkHttp Request from an HttpRequest. + * + * @param request the HttpRequest + * @return the OkHttp Request + */ + private Request buildOkHttpRequest(final HttpRequest request) { + Request.Builder requestBuilder = new Request.Builder().url(request.url()); + request + .headers() + .forEach( + (key, values) -> { + values.forEach(value -> requestBuilder.addHeader(key, value)); + }); + if (request.method().equals("GET")) { + requestBuilder.get(); + } else { + requestBuilder.method( + request.method(), + RequestBody.create(parse(javax.ws.rs.core.MediaType.APPLICATION_JSON), request.body())); + } + return requestBuilder.build(); + } + + /** + * Build an HttpRequest from an OkHttp Request. + * + * @param request the OkHttp Request + * @return the HttpRequest + */ + private HttpRequest buildHttpRequest(final Request request) { + return ImmutableHttpRequest.builder() + .url(request.url().toString()) + .method(request.method()) + .headers(request.headers().toMultimap()) + .body(Optional.ofNullable(request.body()).map(RequestBody::toString).orElse("")) + .build(); + } + + /** + * Create a traced client based on the tracer. + * + * @return the traced client + */ + private Call.Factory createTracedClient() { + if (this.tracer == null || this.tracer instanceof NoopTracer) { + return createTracedClientNoopTracer(); + } + if (this.tracer instanceof OpenCensusTracer) { + return createTracedClientOpenCensus(); + } + if (this.tracer instanceof OpenTelemetryTracer) { + return createTracedClientOpenTelemetry(); + } + return createTracedClientNoopTracer(); + } + + /** + * Create a traced client with a NoopTracer. + * + * @return the traced client + */ + protected Call.Factory createTracedClientNoopTracer() { + return new Call.Factory() { + @NotNull + @Override + public Call newCall(@NotNull final Request request) { + return client.newCall(request); + } + }; + } + + /** + * Create a traced client with OpenTelemetry. + * + * @return the traced client + */ + @VisibleForTesting + protected Call.Factory createTracedClientOpenTelemetry() { + // OkHttpTelemetry is a helper class that provides a Call.Factory that can be used to trace + return OkHttpTelemetry.builder(((OpenTelemetryTracer) this.tracer).getOpenTelemetry()) + .build() + .newCallFactory(client); + } + + /** + * Create a traced client with OpenCensus. + * + * @return the traced client + */ + protected Call.Factory createTracedClientOpenCensus() { + return new Call.Factory() { + @NotNull + @Override + public Call newCall(@NotNull final Request request) { + CompletableFuture future = new CompletableFuture<>(); + Span span = + OkHttpHttpClient.this + .tracer + .span(buildHttpRequest(request)) + .addTag(TraceHelper.TraceTags.HTTP_URL, request.url().toString()); + OkHttpClient.Builder okBuilder = client.newBuilder(); + // Add a network interceptor to trace the request + okBuilder + .networkInterceptors() + .add( + 0, + new Interceptor() { + @NotNull + @Override + public Response intercept(@NotNull final Chain chain) throws IOException { + try { + Response response = chain.proceed(chain.request()); + span.addTag(TraceHelper.TraceTags.HTTP_STATUS_CODE, response.code()) + .addTag(TraceHelper.TraceTags.HTTP_STATUS_MESSAGE, response.message()) + .success(); + future.complete(response); + return response; + } catch (Exception ex) { + span.failure(ex); + future.completeExceptionally(ex); + throw ex; + } finally { + span.close(); + } + } + }); + + return okBuilder.build().newCall(request); + } + }; + } +} diff --git a/src/main/java/com/spotify/github/http/okhttp/OkHttpHttpResponse.java b/src/main/java/com/spotify/github/http/okhttp/OkHttpHttpResponse.java new file mode 100644 index 00000000..b668ab31 --- /dev/null +++ b/src/main/java/com/spotify/github/http/okhttp/OkHttpHttpResponse.java @@ -0,0 +1,96 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.http.okhttp; + +import com.spotify.github.http.BaseHttpResponse; +import com.spotify.github.http.HttpRequest; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.invoke.MethodHandles; +import java.util.Optional; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** OkHttpHttpResponse is the implementation of HttpResponse using OkHttp. */ +public class OkHttpHttpResponse extends BaseHttpResponse { + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final Response response; + private InputStream body; + private String bodyString; + + public OkHttpHttpResponse(final HttpRequest request, final Response response) { + super(request, response.code(), response.message(), response.headers().toMultimap()); + this.response = response; + } + + @Override + public InputStream body() { + if (body == null) { + body = Optional.ofNullable(response.body()).map(ResponseBody::byteStream).orElse(null); + } + return body; + } + + @Override + public String bodyString() { + if (bodyString == null) { + if (response != null) { + bodyString = responseBodyUnchecked(response); + } + } + return bodyString; + } + + @Override + public void close() { + try { + if (response != null) { + if (response.body() != null) { + response.body().close(); + } + response.close(); + } + } catch (IllegalStateException e) { + log.debug("Failed closing response: {}", e.getMessage()); + } + } + + /** + * Get the response body as a string. + * + * @param response the response + * @return the response body as a string + */ + private static String responseBodyUnchecked(final Response response) { + if (response.body() == null) { + return null; + } + try (ResponseBody body = response.body()) { + return body.string(); + } catch (IOException e) { + throw new UncheckedIOException("Failed getting response body for: " + response, e); + } + } +} diff --git a/src/main/java/com/spotify/github/jackson/CommentReactionContentDeserializer.java b/src/main/java/com/spotify/github/jackson/CommentReactionContentDeserializer.java new file mode 100644 index 00000000..abbaeb73 --- /dev/null +++ b/src/main/java/com/spotify/github/jackson/CommentReactionContentDeserializer.java @@ -0,0 +1,43 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.spotify.github.v3.comment.CommentReactionContent; + +import java.io.IOException; +/** + * Custom deserializer for {@link CommentReactionContent}. + */ +public class CommentReactionContentDeserializer extends JsonDeserializer { + @Override + public CommentReactionContent deserialize(final JsonParser p, final DeserializationContext ctxt) + throws IOException { + String value = p.getText(); + for (CommentReactionContent content : CommentReactionContent.values()) { + if (content.toString().equals(value)) { + return content; + } + } + return null; + } +} diff --git a/src/main/java/com/spotify/github/jackson/CommentReactionContentSerializer.java b/src/main/java/com/spotify/github/jackson/CommentReactionContentSerializer.java new file mode 100644 index 00000000..c476dc49 --- /dev/null +++ b/src/main/java/com/spotify/github/jackson/CommentReactionContentSerializer.java @@ -0,0 +1,35 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.spotify.github.v3.comment.CommentReactionContent; +import java.io.IOException; +/** + * Custom serializer for {@link CommentReactionContent}. + */ +public class CommentReactionContentSerializer extends JsonSerializer { + @Override + public void serialize(final CommentReactionContent value, final JsonGenerator gen, final SerializerProvider serializers) throws IOException { + gen.writeString(value.toString()); + } +} diff --git a/src/main/java/com/spotify/github/jackson/Json.java b/src/main/java/com/spotify/github/jackson/Json.java index c77c06f3..31435b7c 100644 --- a/src/main/java/com/spotify/github/jackson/Json.java +++ b/src/main/java/com/spotify/github/jackson/Json.java @@ -20,7 +20,7 @@ package com.spotify.github.jackson; -import static com.fasterxml.jackson.databind.PropertyNamingStrategy.SNAKE_CASE; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import static java.util.Objects.isNull; import com.fasterxml.jackson.annotation.JsonInclude; @@ -248,6 +248,6 @@ private static class DefaultMapper { .enable(SerializationFeature.WRITE_DATES_WITH_ZONE_ID) .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .setPropertyNamingStrategy(SNAKE_CASE); + .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); } } diff --git a/src/main/java/com/spotify/github/opencensus/OpenCensusSpan.java b/src/main/java/com/spotify/github/opencensus/OpenCensusSpan.java new file mode 100644 index 00000000..e513a58e --- /dev/null +++ b/src/main/java/com/spotify/github/opencensus/OpenCensusSpan.java @@ -0,0 +1,38 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.opencensus; + +import io.opencensus.trace.Span; + +/** + * OpenCensusSpan is a wrapper around OpenCensus Span. This class is kept for backward + * compatibility. + * + * @deprecated This class has been moved to the package com.spotify.github.tracing.opencensus. + * Please use com.spotify.github.tracing.opencensus.OpenCensusSpan instead. + */ +@Deprecated +public class OpenCensusSpan extends com.spotify.github.tracing.opencensus.OpenCensusSpan { + public OpenCensusSpan(final Span span) { + super(span); + } + // This class is kept for backward compatibility +} diff --git a/src/main/java/com/spotify/github/opencensus/OpenCensusTracer.java b/src/main/java/com/spotify/github/opencensus/OpenCensusTracer.java new file mode 100644 index 00000000..6f1b00c5 --- /dev/null +++ b/src/main/java/com/spotify/github/opencensus/OpenCensusTracer.java @@ -0,0 +1,33 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.opencensus; + +/** + * OpenCensusTracer is a wrapper around OpenCensus Tracer. This class is kept for backward + * compatibility. + * + * @deprecated This class has been moved to the package com.spotify.github.tracing.opencensus. + * Please use com.spotify.github.tracing.opencensus.OpenCensusTracer instead. + */ +@Deprecated +public class OpenCensusTracer extends com.spotify.github.tracing.opencensus.OpenCensusTracer { + // This class is kept for backward compatibility +} diff --git a/src/main/java/com/spotify/github/tracing/BaseTracer.java b/src/main/java/com/spotify/github/tracing/BaseTracer.java new file mode 100644 index 00000000..f0ed42a0 --- /dev/null +++ b/src/main/java/com/spotify/github/tracing/BaseTracer.java @@ -0,0 +1,73 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +import static java.util.Objects.requireNonNull; + +import com.spotify.github.http.HttpRequest; +import java.util.concurrent.CompletionStage; + +public abstract class BaseTracer implements Tracer { + @Override + public Span span(final String name, final String method, final CompletionStage future) { + return internalSpan(name, method, future); + } + + @Override + public Span span(final String path, final String method) { + return internalSpan(path, method, null); + } + + @Override + public Span span(final HttpRequest request) { + requireNonNull(request); + return internalSpan(request, null); + } + + @Override + public Span span(final HttpRequest request, final CompletionStage future) { + return internalSpan(request, future); + } + + protected abstract Span internalSpan(String path, String method, CompletionStage future); + + protected abstract Span internalSpan(HttpRequest request, CompletionStage future); + + @Override + public void attachSpanToFuture(final Span span, final CompletionStage future) { + future + .whenComplete( + (result, t) -> { + if (t == null) { + span.success(); + } else { + span.failure(t); + } + span.close(); + }) + .exceptionally( + t -> { + span.failure(t); + span.close(); + return null; + }); + } +} diff --git a/src/main/java/com/spotify/github/tracing/NoopTracer.java b/src/main/java/com/spotify/github/tracing/NoopTracer.java new file mode 100644 index 00000000..b921f897 --- /dev/null +++ b/src/main/java/com/spotify/github/tracing/NoopTracer.java @@ -0,0 +1,82 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + + +import com.spotify.github.http.HttpRequest; +import java.util.concurrent.CompletionStage; + +public class NoopTracer extends BaseTracer { + + public static final NoopTracer INSTANCE = new NoopTracer(); + private static final Span SPAN = + new Span() { + @Override + public Span success() { + return this; + } + + @Override + public Span failure(final Throwable t) { + return this; + } + + @Override + public void close() {} + + @Override + public Span addTag(final String key, final String value) { + return this; + } + + @Override + public Span addTag(final String key, final boolean value) { + return this; + } + + @Override + public Span addTag(final String key, final long value) { + return this; + } + + @Override + public Span addTag(final String key, final double value) { + return this; + } + + @Override + public Span addEvent(final String description) { + return this; + } + }; + + private NoopTracer() {} + + @Override + protected Span internalSpan(final String path, final String method, final CompletionStage future) { + return SPAN; + } + + @Override + protected Span internalSpan(final HttpRequest request, final CompletionStage future) { + return SPAN; + } +} diff --git a/src/main/java/com/spotify/github/tracing/Span.java b/src/main/java/com/spotify/github/tracing/Span.java new file mode 100644 index 00000000..f5c1abdb --- /dev/null +++ b/src/main/java/com/spotify/github/tracing/Span.java @@ -0,0 +1,42 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +public interface Span extends AutoCloseable { + + Span success(); + + Span failure(Throwable t); + + /** Close span. Must be called for any opened span. */ + @Override + void close(); + + Span addTag(String key, String value); + + Span addTag(String key, boolean value); + + Span addTag(String key, long value); + + Span addTag(String key, double value); + + Span addEvent(String description); +} diff --git a/src/main/java/com/spotify/github/tracing/TraceHelper.java b/src/main/java/com/spotify/github/tracing/TraceHelper.java new file mode 100644 index 00000000..6c783067 --- /dev/null +++ b/src/main/java/com/spotify/github/tracing/TraceHelper.java @@ -0,0 +1,66 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +import com.spotify.github.v3.exceptions.RequestNotOkException; + +public class TraceHelper { + // Tracing Headers + public static final String HEADER_CLOUD_TRACE_CONTEXT = "X-Cloud-Trace-Context"; + public static final String HEADER_TRACE_PARENT = "traceparent"; + public static final String HEADER_TRACE_STATE = "tracestate"; + + public static final int NOT_FOUND = 404; + public static final int INTERNAL_SERVER_ERROR = 500; + + // Private constructor to prevent instantiation + private TraceHelper() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + public static Span failSpan(final Span span, final Throwable t) { + if (t instanceof RequestNotOkException) { + RequestNotOkException ex = (RequestNotOkException) t; + span.addTag(TraceHelper.TraceTags.HTTP_STATUS_CODE, ex.statusCode()) + .addTag(TraceHelper.TraceTags.ERROR_MESSAGE, ex.getRawMessage()); + if (ex.statusCode() - INTERNAL_SERVER_ERROR >= 0) { + span.addTag(TraceHelper.TraceTags.ERROR, true); + } + } else { + if (t != null) { + span.addTag(TraceHelper.TraceTags.ERROR_MESSAGE, t.getMessage()); + } + span.addTag(TraceHelper.TraceTags.ERROR, true); + } + return span; + } + + public static class TraceTags { + public static final String COMPONENT = "component"; + public static final String PEER_SERVICE = "peer.service"; + public static final String HTTP_URL = "http.url"; + public static final String HTTP_METHOD = "method"; + public static final String HTTP_STATUS_CODE = "http.status_code"; + public static final String HTTP_STATUS_MESSAGE = "http.status_message"; + public static final String ERROR_MESSAGE = "message"; + public static final String ERROR = "error"; + } +} diff --git a/src/main/java/com/spotify/github/tracing/Tracer.java b/src/main/java/com/spotify/github/tracing/Tracer.java new file mode 100644 index 00000000..afa3499c --- /dev/null +++ b/src/main/java/com/spotify/github/tracing/Tracer.java @@ -0,0 +1,38 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +import com.spotify.github.http.HttpRequest; +import java.util.concurrent.CompletionStage; + +public interface Tracer { + + /** Create scoped span. Span will be closed when future completes. */ + Span span(String path, String method, CompletionStage future); + + Span span(String path, String method); + + Span span(HttpRequest request); + + Span span(HttpRequest request, CompletionStage future); + + void attachSpanToFuture(Span span, CompletionStage future); +} diff --git a/src/main/java/com/spotify/github/tracing/opencensus/OpenCensusSpan.java b/src/main/java/com/spotify/github/tracing/opencensus/OpenCensusSpan.java new file mode 100644 index 00000000..755d0a0f --- /dev/null +++ b/src/main/java/com/spotify/github/tracing/opencensus/OpenCensusSpan.java @@ -0,0 +1,84 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing.opencensus; + +import static com.spotify.github.tracing.TraceHelper.failSpan; +import static java.util.Objects.requireNonNull; + +import com.spotify.github.tracing.Span; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.Status; + +public class OpenCensusSpan implements Span { + private final io.opencensus.trace.Span span; + + public OpenCensusSpan(final io.opencensus.trace.Span span) { + this.span = requireNonNull(span); + } + + @Override + public Span success() { + span.setStatus(Status.OK); + return this; + } + + @Override + public Span failure(final Throwable t) { + failSpan(this, t); + span.setStatus(Status.UNKNOWN); + return this; + } + + @Override + public void close() { + span.end(); + } + + @Override + public Span addTag(final String key, final String value) { + this.span.putAttribute(key, AttributeValue.stringAttributeValue(value)); + return this; + } + + @Override + public Span addTag(final String key, final boolean value) { + this.span.putAttribute(key, AttributeValue.booleanAttributeValue(value)); + return this; + } + + @Override + public Span addTag(final String key, final long value) { + this.span.putAttribute(key, AttributeValue.longAttributeValue(value)); + return this; + } + + @Override + public Span addTag(final String key, final double value) { + this.span.putAttribute(key, AttributeValue.doubleAttributeValue(value)); + return this; + } + + @Override + public Span addEvent(final String description) { + this.span.addAnnotation(description); + return this; + } +} diff --git a/src/main/java/com/spotify/github/tracing/opencensus/OpenCensusTracer.java b/src/main/java/com/spotify/github/tracing/opencensus/OpenCensusTracer.java new file mode 100644 index 00000000..6e7bb2c3 --- /dev/null +++ b/src/main/java/com/spotify/github/tracing/opencensus/OpenCensusTracer.java @@ -0,0 +1,66 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing.opencensus; + +import static io.opencensus.trace.Span.Kind.CLIENT; +import static java.util.Objects.requireNonNull; + +import com.spotify.github.http.HttpRequest; +import com.spotify.github.tracing.BaseTracer; +import com.spotify.github.tracing.Span; +import com.spotify.github.tracing.TraceHelper; +import io.opencensus.trace.Tracing; +import java.util.concurrent.CompletionStage; +import okhttp3.*; + +/** Tracer implementation using OpenCensus. */ +public class OpenCensusTracer extends BaseTracer { + + private static final io.opencensus.trace.Tracer TRACER = Tracing.getTracer(); + + @SuppressWarnings("MustBeClosedChecker") + protected Span internalSpan( + final String path, final String method, final CompletionStage future) { + requireNonNull(path); + + final io.opencensus.trace.Span ocSpan = + TRACER.spanBuilder("GitHub Request").setSpanKind(CLIENT).startSpan(); + + final Span span = + new OpenCensusSpan(ocSpan) + .addTag(TraceHelper.TraceTags.COMPONENT, "github-api-client") + .addTag(TraceHelper.TraceTags.PEER_SERVICE, "github") + .addTag(TraceHelper.TraceTags.HTTP_URL, path) + .addTag(TraceHelper.TraceTags.HTTP_METHOD, method); + + if (future != null) { + attachSpanToFuture(span, future); + } + + return span; + } + + @Override + protected Span internalSpan(final HttpRequest request, final CompletionStage future) { + requireNonNull(request); + return internalSpan(request.url(), request.method(), future); + } +} diff --git a/src/main/java/com/spotify/github/tracing/opentelemetry/OpenTelemetrySpan.java b/src/main/java/com/spotify/github/tracing/opentelemetry/OpenTelemetrySpan.java new file mode 100644 index 00000000..66b1c493 --- /dev/null +++ b/src/main/java/com/spotify/github/tracing/opentelemetry/OpenTelemetrySpan.java @@ -0,0 +1,86 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing.opentelemetry; + +import static com.spotify.github.tracing.TraceHelper.failSpan; +import static java.util.Objects.requireNonNull; + +import com.spotify.github.tracing.Span; +import io.opentelemetry.api.trace.StatusCode; + +public class OpenTelemetrySpan implements Span { + public static final int NOT_FOUND = 404; + public static final int INTERNAL_SERVER_ERROR = 500; + + private final io.opentelemetry.api.trace.Span span; + + public OpenTelemetrySpan(final io.opentelemetry.api.trace.Span span) { + this.span = requireNonNull(span); + } + + @Override + public Span success() { + span.setStatus(StatusCode.OK); + return this; + } + + @Override + public Span failure(final Throwable t) { + failSpan(this, t); + span.setStatus(StatusCode.ERROR); + return this; + } + + @Override + public void close() { + span.end(); + } + + @Override + public Span addTag(final String key, final String value) { + this.span.setAttribute(key, value); + return this; + } + + @Override + public Span addTag(final String key, final boolean value) { + this.span.setAttribute(key, value); + return this; + } + + @Override + public Span addTag(final String key, final long value) { + this.span.setAttribute(key, value); + return this; + } + + @Override + public Span addTag(final String key, final double value) { + this.span.setAttribute(key, value); + return this; + } + + @Override + public Span addEvent(final String description) { + this.span.addEvent(description); + return this; + } +} diff --git a/src/main/java/com/spotify/github/tracing/opentelemetry/OpenTelemetryTracer.java b/src/main/java/com/spotify/github/tracing/opentelemetry/OpenTelemetryTracer.java new file mode 100644 index 00000000..764ec5c2 --- /dev/null +++ b/src/main/java/com/spotify/github/tracing/opentelemetry/OpenTelemetryTracer.java @@ -0,0 +1,127 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing.opentelemetry; + +import static java.util.Objects.requireNonNull; + +import com.spotify.github.http.HttpRequest; +import com.spotify.github.tracing.BaseTracer; +import com.spotify.github.tracing.Span; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.concurrent.CompletionStage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Tracer implementation using OpenTelemetry. */ +public class OpenTelemetryTracer extends BaseTracer { + private final io.opentelemetry.api.trace.Tracer tracer; + private final OpenTelemetry openTelemetry; + + public OpenTelemetryTracer(final OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + this.tracer = openTelemetry.getTracer("github-java-client"); + } + + public OpenTelemetryTracer() { + this(GlobalOpenTelemetry.get()); + } + + public OpenTelemetry getOpenTelemetry() { + return openTelemetry; + } + + /** + * Create a new span for the given path and method. + * + * @param path The path of the request. + * @param method The method of the request. + * @param future The future to attach the span to. + * @return The created span. + */ + @SuppressWarnings("MustBeClosedChecker") + protected Span internalSpan( + final String path, final String method, final CompletionStage future) { + requireNonNull(path); + + Context context = Context.current(); + + final io.opentelemetry.api.trace.Span otSpan = + tracer + .spanBuilder("GitHub Request") + .setParent(context) + .setSpanKind(SpanKind.CLIENT) + .startSpan(); + + otSpan.setAttribute("component", "github-api-client"); + otSpan.setAttribute("peer.service", "github"); + otSpan.setAttribute("http.url", path); + otSpan.setAttribute("method", method); + final Span span = new OpenTelemetrySpan(otSpan); + + if (future == null) { + return span; + } else { + attachSpanToFuture(span, future); + } + return span; + } + + /** + * Create a new span for the given request. + * + * @param request The request to create a span for. + * @param future The future to attach the span to. + * @return The created span. + */ + @Override + protected Span internalSpan(final HttpRequest request, final CompletionStage future) { + requireNonNull(request); + // Extract the context from the request headers. + Context context = + W3CTraceContextPropagator.getInstance() + .extract( + Context.current(), + request, + new TextMapGetter<>() { + @Override + public Iterable keys(@NotNull final HttpRequest carrier) { + return carrier.headers().keySet(); + } + + @Nullable + @Override + public String get( + @Nullable final HttpRequest carrier, @NotNull final String key) { + if (carrier == null) { + return null; + } + return carrier.header(key); + } + }); + context.makeCurrent(); + return internalSpan(request.url(), request.method(), future); + } +} diff --git a/src/main/java/com/spotify/github/v3/activity/events/CheckRunEvent.java b/src/main/java/com/spotify/github/v3/activity/events/CheckRunEvent.java new file mode 100644 index 00000000..6fc25819 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/activity/events/CheckRunEvent.java @@ -0,0 +1,43 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.activity.events; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import com.spotify.github.UpdateTracking; +import com.spotify.github.v3.checks.CheckRunResponse; +import org.immutables.value.Value; + +import javax.annotation.Nullable; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableCheckRunEvent.class) +@JsonDeserialize(as = ImmutableCheckRunEvent.class) +public interface CheckRunEvent extends BaseEvent, UpdateTracking { + + @Nullable + String action(); + + @Nullable + CheckRunResponse checkRun(); +} diff --git a/src/main/java/com/spotify/github/v3/activity/events/CreateEvent.java b/src/main/java/com/spotify/github/v3/activity/events/CreateEvent.java index 384784ad..f3ef02dd 100644 --- a/src/main/java/com/spotify/github/v3/activity/events/CreateEvent.java +++ b/src/main/java/com/spotify/github/v3/activity/events/CreateEvent.java @@ -47,7 +47,6 @@ public interface CreateEvent extends BaseEvent { String masterBranch(); /** The repository's current description. */ - @Nullable Optional description(); /** No doc found on github - Usually is "user". */ diff --git a/src/main/java/com/spotify/github/v3/activity/events/EventType.java b/src/main/java/com/spotify/github/v3/activity/events/EventType.java new file mode 100644 index 00000000..e956cbc4 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/activity/events/EventType.java @@ -0,0 +1,95 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.activity.events; + +public enum EventType { + BRANCH_PROTECTION_RULE, + CHECK_RUN, + CHECK_SUITE, + CODE_SCANNING_ALERT, + COMMIT_COMMENT, + CONTENT_REFERENCE, + CREATE, + DELETE, + DEPLOY_KEY, + DEPLOYMENT, + DEPLOYMENT_STATUS, + DISCUSSION, + DISCUSSION_COMMENT, + DOWNLOAD, + FOLLOW, + FORK, + FORK_APPLY, + GITHUB_APP_AUTHORIZATION, + GIST, + GOLLUM, + INSTALLATION, + INSTALLATION_REPOSITORIES, + INTEGRATION_INSTALLATION_REPOSITORIES, + ISSUE_COMMENT, + ISSUES, + LABEL, + MARKETPLACE_PURCHASE, + MEMBER, + MEMBERSHIP, + MERGE_QUEUE_ENTRY, + MERGE_GROUP, + META, + MILESTONE, + ORGANIZATION, + ORG_BLOCK, + PACKAGE, + PAGE_BUILD, + PROJECT_CARD, + PROJECT_COLUMN, + PROJECT, + PING, + PUBLIC, + PULL_REQUEST, + PULL_REQUEST_REVIEW, + PULL_REQUEST_REVIEW_COMMENT, + PULL_REQUEST_REVIEW_THREAD, + PUSH, + REGISTRY_PACKAGE, + RELEASE, + REPOSITORY_DISPATCH, + REPOSITORY, + REPOSITORY_IMPORT, + REPOSITORY_RULESET, + REPOSITORY_VULNERABILITY_ALERT, + SCHEDULE, + SECURITY_ADVISORY, + STAR, + STATUS, + TEAM, + TEAM_ADD, + WATCH, + WORKFLOW_JOB, + WORKFLOW_DISPATCH, + WORKFLOW_RUN, + UNKNOWN, + ALL; + + @Override + public String toString() { + return this.name().toLowerCase(); + } +} diff --git a/src/main/java/com/spotify/github/v3/activity/events/MergeGroup.java b/src/main/java/com/spotify/github/v3/activity/events/MergeGroup.java new file mode 100644 index 00000000..0666e0be --- /dev/null +++ b/src/main/java/com/spotify/github/v3/activity/events/MergeGroup.java @@ -0,0 +1,54 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.activity.events; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +/** + * Represents a merge group in GitHub's merge queue. + * A merge group is created when pull requests are added to a merge queue. + */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableMergeGroup.class) +@JsonDeserialize(as = ImmutableMergeGroup.class) +public interface MergeGroup { + + /** The SHA of the merge group. */ + @Nullable + String headSha(); + + /** The full ref of the merge group. */ + @Nullable + String headRef(); + + /** The SHA of the merge group's parent commit. */ + @Nullable + String baseSha(); + + /** The full ref of the branch the merge group is merging into. */ + @Nullable + String baseRef(); +} diff --git a/src/main/java/com/spotify/github/v3/activity/events/MergeGroupEvent.java b/src/main/java/com/spotify/github/v3/activity/events/MergeGroupEvent.java new file mode 100644 index 00000000..d49251ad --- /dev/null +++ b/src/main/java/com/spotify/github/v3/activity/events/MergeGroupEvent.java @@ -0,0 +1,49 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.activity.events; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +/** + * Triggered when a pull request is added to a merge queue. + * The merge queue feature allows pull requests to be queued and merged automatically. + */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableMergeGroupEvent.class) +@JsonDeserialize(as = ImmutableMergeGroupEvent.class) +public interface MergeGroupEvent extends BaseEvent { + + /** + * The action that was performed. Can be "checks_requested" when a merge group is created + * and checks are requested, or "destroyed" when the merge group is removed from the queue. + */ + @Nullable + String action(); + + /** The merge group. */ + @Nullable + MergeGroup mergeGroup(); +} diff --git a/src/main/java/com/spotify/github/v3/activity/events/PushEvent.java b/src/main/java/com/spotify/github/v3/activity/events/PushEvent.java index b6f2690a..06d33cb8 100644 --- a/src/main/java/com/spotify/github/v3/activity/events/PushEvent.java +++ b/src/main/java/com/spotify/github/v3/activity/events/PushEvent.java @@ -93,7 +93,6 @@ public interface PushEvent { List commits(); /** The push commit object of the most recent commit on ref after the push. */ - @Nullable Optional headCommit(); /** Pusher */ diff --git a/src/main/java/com/spotify/github/v3/activity/events/StatusEvent.java b/src/main/java/com/spotify/github/v3/activity/events/StatusEvent.java index a43123d1..35d0a1fd 100644 --- a/src/main/java/com/spotify/github/v3/activity/events/StatusEvent.java +++ b/src/main/java/com/spotify/github/v3/activity/events/StatusEvent.java @@ -66,7 +66,6 @@ public interface StatusEvent extends BaseEvent, UpdateTracking { String context(); /** The optional human-readable description added to the status. */ - @Nullable Optional description(); /** The new state. Can be pending, success, failure, or error. */ diff --git a/src/main/java/com/spotify/github/v3/apps/requests/AccessTokenRequest.java b/src/main/java/com/spotify/github/v3/apps/requests/AccessTokenRequest.java new file mode 100644 index 00000000..ede485da --- /dev/null +++ b/src/main/java/com/spotify/github/v3/apps/requests/AccessTokenRequest.java @@ -0,0 +1,53 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.v3.apps.requests; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import java.util.List; +import java.util.Optional; +import org.immutables.value.Value; + +/** Request to create an installation access token with repository scoping. */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableAccessTokenRequest.class) +@JsonDeserialize(as = ImmutableAccessTokenRequest.class) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public interface AccessTokenRequest { + + /** + * List of repository names that the token should be scoped to. + * + * @return list of repository names + */ + Optional> repositories(); + + /** + * List of repository IDs that the token should be scoped to. + * + * @return list of repository IDs + */ + @JsonProperty("repository_ids") + Optional> repositoryIds(); +} diff --git a/src/main/java/com/spotify/github/v3/checks/Annotation.java b/src/main/java/com/spotify/github/v3/checks/Annotation.java index 6f8465fe..7e62f9e1 100644 --- a/src/main/java/com/spotify/github/v3/checks/Annotation.java +++ b/src/main/java/com/spotify/github/v3/checks/Annotation.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.Preconditions; import com.spotify.github.GithubStyle; import java.util.Optional; import org.immutables.value.Value; @@ -42,7 +43,7 @@ @Value.Immutable @GithubStyle @JsonDeserialize(as = ImmutableAnnotation.class) -@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonInclude(JsonInclude.Include.NON_ABSENT) public interface Annotation { /** @@ -114,4 +115,34 @@ public interface Annotation { * @return the optional */ Optional endColumn(); + + /** + * Automatically validates the maximum length of properties. + * + * GitHub does not validate these properly on their side (at least in GHE 3.2) + * and returns 5xx HTTP responses instead. To avoid that, let's validate the data + * in this client library. + */ + @Value.Check + @SuppressWarnings("checkstyle:magicnumber") + default Annotation check() { + // max values from https://docs.github.com/en/rest/checks/runs + Preconditions.checkState(title().map(String::length).orElse(0) <= 255, + "'title' exceeded max length of 255"); + Preconditions.checkState(message().length() <= 64 * 1024, + "'message' exceeded max length of 64kB"); + Preconditions.checkState(rawDetails().map(String::length).orElse(0) <= 64 * 1024, + "'rawDetails' exceeded max length of 64kB"); + + // Omit this (start_column, end_column) parameter if start_line and end_line have different values + // from https://docs.github.com/en/rest/checks/runs + if (startLine() != endLine() && (startColumn().isPresent() || endColumn().isPresent())) { + return ImmutableAnnotation.builder() + .from(this) + .startColumn(Optional.empty()) + .endColumn(Optional.empty()) + .build(); + } + return this; + } } diff --git a/src/main/java/com/spotify/github/v3/checks/App.java b/src/main/java/com/spotify/github/v3/checks/App.java index 8affe3a0..e130b754 100644 --- a/src/main/java/com/spotify/github/v3/checks/App.java +++ b/src/main/java/com/spotify/github/v3/checks/App.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.spotify.github.GithubStyle; +import com.spotify.github.v3.User; import java.time.ZonedDateTime; import java.util.List; import java.util.Map; @@ -112,4 +113,53 @@ public interface App { * @return the optional count */ Optional installationsCount(); + + /** + * The client ID of the GitHub App. + * + * @return the optional client ID + */ + Optional clientId(); + + /** + * The name of the single file the GitHub App can access (if applicable). + * + * @return the optional single file name + */ + Optional singleFileName(); + + /** + * Whether the GitHub App has access to multiple single files. + * + * @return the optional boolean + */ + Optional hasMultipleSingleFiles(); + + /** + * The list of single file paths the GitHub App can access. + * + * @return the optional list of file paths + */ + Optional> singleFilePaths(); + + /** + * The slug name of the GitHub App. + * + * @return the optional app slug + */ + Optional appSlug(); + + /** + * The date the App was suspended. + * + * @return the optional suspended date + */ + Optional suspendedAt(); + + /** + * The user who suspended the App. + * + * @return the optional user + */ + Optional suspendedBy(); } diff --git a/src/main/java/com/spotify/github/v3/checks/CheckRunAction.java b/src/main/java/com/spotify/github/v3/checks/CheckRunAction.java index feb81b3e..f3c93c9b 100644 --- a/src/main/java/com/spotify/github/v3/checks/CheckRunAction.java +++ b/src/main/java/com/spotify/github/v3/checks/CheckRunAction.java @@ -21,6 +21,7 @@ package com.spotify.github.v3.checks; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.Preconditions; import com.spotify.github.GithubStyle; import org.immutables.value.Value; @@ -57,4 +58,23 @@ public interface CheckRunAction { * @return the string */ String description(); + + /** + * Automatically validates the maximum length of properties. + * + * GitHub does not validate these properly on their side (at least in GHE 3.2) + * and returns 5xx HTTP responses instead. To avoid that, let's validate the data + * in this client library. + */ + @Value.Check + @SuppressWarnings("checkstyle:magicnumber") + default void check() { + // max values from https://docs.github.com/en/rest/checks/runs + Preconditions.checkState(label().length() <= 20, + "'label' exceeded max length of 20"); + Preconditions.checkState(identifier().length() <= 20, + "'identifier' exceeded max length of 20"); + Preconditions.checkState(description().length() <= 40, + "'description' exceeded max length of 40"); + } } diff --git a/src/main/java/com/spotify/github/v3/checks/CheckRunConclusion.java b/src/main/java/com/spotify/github/v3/checks/CheckRunConclusion.java index 9d804a67..0b427925 100644 --- a/src/main/java/com/spotify/github/v3/checks/CheckRunConclusion.java +++ b/src/main/java/com/spotify/github/v3/checks/CheckRunConclusion.java @@ -32,5 +32,6 @@ public enum CheckRunConclusion { failure, neutral, success, + skipped, stale } diff --git a/src/main/java/com/spotify/github/v3/checks/CheckRunOutput.java b/src/main/java/com/spotify/github/v3/checks/CheckRunOutput.java index 95a644b4..76014b40 100644 --- a/src/main/java/com/spotify/github/v3/checks/CheckRunOutput.java +++ b/src/main/java/com/spotify/github/v3/checks/CheckRunOutput.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.common.base.Preconditions; import com.spotify.github.GithubStyle; import java.util.List; import java.util.Optional; @@ -87,4 +88,20 @@ public interface CheckRunOutput { * @return the optional */ Optional annotationsUrl(); + + /** + * Automatically validates the maximum length of properties. + *

+ * GitHub does not validate these properly on their side (at least in GHE 3.2) and returns 422 + * HTTP responses instead. To avoid that, let's validate the data in this client library. + */ + @Value.Check + @SuppressWarnings("checkstyle:magicnumber") + default void check() { + // max values from https://docs.github.com/en/enterprise-server@3.5/rest/checks/runs#create-a-check-run + Preconditions.checkState(summary().map(String::length).orElse(0) <= 65535, + "'summary' exceeded max length of 65535 characters"); + Preconditions.checkState(text().map(String::length).orElse(0) <= 65535, + "'text' exceeded max length of 65535 characters"); + } } diff --git a/src/main/java/com/spotify/github/v3/checks/CheckRunResponse.java b/src/main/java/com/spotify/github/v3/checks/CheckRunResponse.java index e4add01b..3acd1379 100644 --- a/src/main/java/com/spotify/github/v3/checks/CheckRunResponse.java +++ b/src/main/java/com/spotify/github/v3/checks/CheckRunResponse.java @@ -22,7 +22,11 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.spotify.github.GithubStyle; + +import java.util.List; import java.util.Optional; + +import com.spotify.github.v3.prs.PartialPullRequestItem; import org.immutables.value.Value; /** The CheckRun response resource. */ @@ -36,7 +40,7 @@ public interface CheckRunResponse extends CheckRunBase { * * @return the int */ - int id(); + long id(); /** * Url string. @@ -73,4 +77,10 @@ public interface CheckRunResponse extends CheckRunBase { * @return the optional */ Optional app(); + + /** + * Pull Requests where this check is applied. + * @return the optional of list of pull requests + */ + Optional> pullRequests(); } diff --git a/src/main/java/com/spotify/github/v3/checks/CheckRunStatus.java b/src/main/java/com/spotify/github/v3/checks/CheckRunStatus.java index e80a749c..d782660d 100644 --- a/src/main/java/com/spotify/github/v3/checks/CheckRunStatus.java +++ b/src/main/java/com/spotify/github/v3/checks/CheckRunStatus.java @@ -24,5 +24,6 @@ public enum CheckRunStatus { queued, in_progress, - completed + completed, + pending } diff --git a/src/main/java/com/spotify/github/v3/checks/CheckSuite.java b/src/main/java/com/spotify/github/v3/checks/CheckSuite.java index 6d364029..ea13d811 100644 --- a/src/main/java/com/spotify/github/v3/checks/CheckSuite.java +++ b/src/main/java/com/spotify/github/v3/checks/CheckSuite.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,18 +22,34 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.spotify.github.GithubStyle; + +import java.util.List; +import java.util.Optional; + +import com.spotify.github.v3.prs.PartialPullRequestItem; import org.immutables.value.Value; -/** Github CheckSuite */ +/** GitHub CheckSuite */ @Value.Immutable @GithubStyle @JsonDeserialize(as = ImmutableCheckSuite.class) public interface CheckSuite { /** - * The Check Suite Id. + * The Check Suite id. + * + * @return the long id + */ + Long id(); + + Optional app(); + + Optional headBranch(); + + /** + * Pull Requests where this check suite is applied. * - * @return the integer + * @return the list of pull requests */ - Integer id(); + List pullRequests(); } diff --git a/src/main/java/com/spotify/github/v3/clients/ActionsClient.java b/src/main/java/com/spotify/github/v3/clients/ActionsClient.java new file mode 100644 index 00000000..8dc25c86 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/clients/ActionsClient.java @@ -0,0 +1,46 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.clients; + +public class ActionsClient { + private final String owner; + private final String repo; + private final GitHubClient github; + + ActionsClient(final GitHubClient github, final String owner, final String repo) { + this.github = github; + this.owner = owner; + this.repo = repo; + } + + static ActionsClient create(final GitHubClient github, final String owner, final String repo) { + return new ActionsClient(github, owner, repo); + } + + /** + * Workflows API client + * + * @return Workflows API client + */ + public WorkflowsClient createWorkflowsClient() { + return WorkflowsClient.create(github, owner, repo); + } +} diff --git a/src/main/java/com/spotify/github/v3/clients/ChecksClient.java b/src/main/java/com/spotify/github/v3/clients/ChecksClient.java index 70439199..3dc78c2b 100644 --- a/src/main/java/com/spotify/github/v3/clients/ChecksClient.java +++ b/src/main/java/com/spotify/github/v3/clients/ChecksClient.java @@ -60,6 +60,10 @@ public class ChecksClient { this.repo = repo; } + static ChecksClient create(final GitHubClient github, final String owner, final String repo) { + return new ChecksClient(github, owner, repo); + } + /** * Create a checkRun. * @@ -80,7 +84,7 @@ public CompletableFuture createCheckRun(final CheckRunRequest * @return the completable future */ public CompletableFuture updateCheckRun( - final int id, final CheckRunRequest checkRun) { + final long id, final CheckRunRequest checkRun) { final String path = String.format(GET_CHECK_RUN_URI, owner, repo, id); return github.patch( path, github.json().toJsonUnchecked(checkRun), CheckRunResponse.class, extraHeaders); @@ -92,7 +96,7 @@ public CompletableFuture updateCheckRun( * @param id the checkRun id * @return a CheckRunResponse */ - public CompletableFuture getCheckRun(final int id) { + public CompletableFuture getCheckRun(final long id) { final String path = String.format(GET_CHECK_RUN_URI, owner, repo, id); return github.request(path, CheckRunResponse.class, extraHeaders); } diff --git a/src/main/java/com/spotify/github/v3/clients/GitDataClient.java b/src/main/java/com/spotify/github/v3/clients/GitDataClient.java index 40d33b6b..6f7fda03 100644 --- a/src/main/java/com/spotify/github/v3/clients/GitDataClient.java +++ b/src/main/java/com/spotify/github/v3/clients/GitDataClient.java @@ -27,7 +27,11 @@ import com.google.common.collect.ImmutableMap; import com.spotify.github.v3.git.Reference; +import com.spotify.github.v3.git.ShaLink; import com.spotify.github.v3.git.Tag; +import com.spotify.github.v3.git.Tree; +import com.spotify.github.v3.git.TreeItem; +import com.spotify.github.v3.repos.Commit; import java.time.Instant; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -42,6 +46,14 @@ public class GitDataClient { private static final String CREATE_REFERENCE_URI = "/repos/%s/%s/git/refs"; private static final String CREATE_REFERENCE_TAG = "/repos/%s/%s/git/tags"; private static final String LIST_MATCHING_REFERENCES_URI = "/repos/%s/%s/git/matching-refs/%s"; + + private static final String CREATE_COMMIT_URI_TEMPLATE = "/repos/%s/%s/git/commits"; + + private static final String TREE_SHA_URI_TEMPLATE = "/repos/%s/%s/git/trees/%s"; + private static final String TREE_URI_TEMPLATE = "/repos/%s/%s/git/trees"; + + private static final String BLOB_URI_TEMPLATE = "/repos/%s/%s/git/blobs"; + private final GitHubClient github; private final String owner; private final String repo; @@ -62,8 +74,7 @@ static GitDataClient create(final GitHubClient github, final String owner, final * @param ref search parameters */ public CompletableFuture deleteReference(final String ref) { - final String path = - format(REFERENCE_URI, owner, repo, ref.replaceAll("refs/", "")); + final String path = format(REFERENCE_URI, owner, repo, ref.replaceAll("refs/", "")); return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); } @@ -136,16 +147,12 @@ public CompletableFuture> listReferences(final String ref) { return github.request(path, LIST_REFERENCES); } - /** - * List references. (Replaced by listMatchingReferences for github enterprise version > 2.18) - * - */ + /** List references. (Replaced by listMatchingReferences for github enterprise version > 2.18) */ @Deprecated public CompletableFuture> listReferences() { return listReferences(""); } - /** * Create a git reference. * @@ -154,10 +161,10 @@ public CompletableFuture> listReferences() { */ public CompletableFuture createReference(final String ref, final String sha) { final String path = format(CREATE_REFERENCE_URI, owner, repo); - final ImmutableMap body = of( - "ref", ref, - "sha", sha - ); + final ImmutableMap body = + of( + "ref", ref, + "sha", sha); return github.post(path, github.json().toJsonUnchecked(body), Reference.class); } @@ -181,6 +188,23 @@ public CompletableFuture createTagReference(final String tag, final S return createReference(format("refs/tags/%s", tag), sha); } + /** + * Update a git reference. + * + * @param ref reference name + * @param sha The SHA1 value to set the reference to + * @param force Indicates whether to force the update or to make sure the update is a fast-forward update. Setting + * this to false will make sure you're not overwriting work. + */ + public CompletableFuture updateReference(final String ref, final String sha, final boolean force) { + final String path = format(REFERENCE_URI, owner, repo, ref); + final ImmutableMap body = + of( + "sha", sha, + "force", Boolean.toString(force)); + return github.patch(path, github.json().toJsonUnchecked(body), Reference.class); + } + /** * Create an annotated tag. First it would create a tag reference and then create annotated tag * @@ -208,8 +232,79 @@ public CompletableFuture createAnnotatedTag( "name", taggerName, "email", taggerEmail, "date", Instant.now().toString())); - return createTagReference(tag, sha) - .thenCompose( - reference -> github.post(tagPath, github.json().toJsonUnchecked(body), Tag.class)); + return github.post(tagPath, github.json().toJsonUnchecked(body), Tag.class) + .thenCompose(tagCreated -> + createTagReference(tag, tagCreated.sha()) + .thenApply(reference -> tagCreated)); + } + + /** + * Create a commit which references a tree + * + * @param message commit message + * @param parents list of parent sha values, usually just one sha + * @param treeSha sha value of the tree + */ + public CompletableFuture createCommit( + final String message, final List parents, final String treeSha) { + final String path = String.format(CREATE_COMMIT_URI_TEMPLATE, owner, repo); + final String requestBody = + github + .json() + .toJsonUnchecked( + ImmutableMap.of("message", message, "parents", parents, "tree", treeSha)); + return github.post(path, requestBody, Commit.class); } + + /** + * Get a repository tree. + * + * @param sha commit sha + * @return tree + */ + public CompletableFuture getTree(final String sha) { + final String path = String.format(TREE_SHA_URI_TEMPLATE, owner, repo, sha); + return github.request(path, Tree.class); + } + + /** + * Get a repository tree recursively. + * + * @param sha commit sha + * @return tree + */ + public CompletableFuture getRecursiveTree(final String sha) { + final String path = String.format(TREE_SHA_URI_TEMPLATE, owner, repo, sha); + return github.request(path + "?recursive=true", Tree.class); + } + + /** + * Set a repository tree. + * + * @param tree list of tree items + * @param baseTreeSha sha of existing tree used as base for new tree + * @return tree + */ + public CompletableFuture createTree(final List tree, final String baseTreeSha) { + final String path = String.format(TREE_URI_TEMPLATE, owner, repo); + final String requestBody = github.json() + .toJsonUnchecked(ImmutableMap.of("base_tree", baseTreeSha, "tree", tree)); + return github.post(path, requestBody, Tree.class); + } + + + /** + * Post new content to the server. + * + * @param content the content to be posted + */ + public CompletableFuture createBlob(final String content) { + final String path = String.format(BLOB_URI_TEMPLATE, owner, repo); + final String encoding = "utf-8|base64"; + final String requestBody = github.json() + .toJsonUnchecked(ImmutableMap.of("content", content, "encoding", encoding)); + return github.post(path, requestBody, ShaLink.class); + } + + } diff --git a/src/main/java/com/spotify/github/v3/clients/GitHubClient.java b/src/main/java/com/spotify/github/v3/clients/GitHubClient.java index 2d4d9e92..b7809a47 100644 --- a/src/main/java/com/spotify/github/v3/clients/GitHubClient.java +++ b/src/main/java/com/spotify/github/v3/clients/GitHubClient.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,71 +21,98 @@ package com.spotify.github.v3.clients; import static java.util.concurrent.CompletableFuture.completedFuture; -import static okhttp3.MediaType.parse; import com.fasterxml.jackson.core.type.TypeReference; +import com.spotify.github.async.Async; +import com.spotify.github.http.HttpClient; +import com.spotify.github.http.HttpRequest; +import com.spotify.github.http.HttpResponse; +import com.spotify.github.http.ImmutableHttpRequest; +import com.spotify.github.http.okhttp.OkHttpHttpClient; import com.spotify.github.jackson.Json; +import com.spotify.github.tracing.NoopTracer; +import com.spotify.github.tracing.Tracer; +import com.spotify.github.v3.Team; +import com.spotify.github.v3.User; import com.spotify.github.v3.checks.AccessToken; +import com.spotify.github.v3.checks.Installation; import com.spotify.github.v3.comment.Comment; +import com.spotify.github.v3.comment.CommentReaction; import com.spotify.github.v3.exceptions.ReadOnlyRepositoryException; import com.spotify.github.v3.exceptions.RequestNotOkException; +import com.spotify.github.v3.git.FileItem; import com.spotify.github.v3.git.Reference; +import com.spotify.github.v3.orgs.TeamInvitation; import com.spotify.github.v3.prs.PullRequestItem; import com.spotify.github.v3.prs.Review; import com.spotify.github.v3.prs.ReviewRequests; -import com.spotify.github.v3.repos.Branch; -import com.spotify.github.v3.repos.CommitItem; -import com.spotify.github.v3.repos.FolderContent; -import com.spotify.github.v3.repos.Repository; -import com.spotify.github.v3.repos.Status; +import com.spotify.github.v3.repos.*; import java.io.File; import java.io.IOException; -import java.io.UncheckedIOException; import java.lang.invoke.MethodHandles; import java.net.URI; import java.time.ZonedDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; +import javax.annotation.Nullable; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; - import okhttp3.*; +import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Github client is a main communication entry point. Provides lower level communication + * GitHub client is a main communication entry point. Provides lower level communication * functionality as well as acts as a factory for the higher level API clients. */ public class GitHubClient { - static final Consumer IGNORE_RESPONSE_CONSUMER = (ignore) -> {}; - static final TypeReference> LIST_COMMENT_TYPE_REFERENCE = - new TypeReference<>() {}; - static final TypeReference> LIST_REPOSITORY = + private static final int EXPIRY_MARGIN_IN_MINUTES = 5; + private static final int HTTP_NOT_FOUND = 404; + + private Tracer tracer = NoopTracer.INSTANCE; + + static final Consumer IGNORE_RESPONSE_CONSUMER = + (response) -> { + if (response != null) { + response.close(); + } + }; + static final TypeReference> LIST_COMMENT_TYPE_REFERENCE = new TypeReference<>() {}; + static final TypeReference> LIST_COMMENT_REACTION_TYPE_REFERENCE = new TypeReference<>() {}; + static final TypeReference> LIST_REPOSITORY = new TypeReference<>() {}; static final TypeReference> LIST_COMMIT_TYPE_REFERENCE = new TypeReference<>() {}; static final TypeReference> LIST_REVIEW_TYPE_REFERENCE = new TypeReference<>() {}; static final TypeReference LIST_REVIEW_REQUEST_TYPE_REFERENCE = new TypeReference<>() {}; - static final TypeReference> LIST_STATUS_TYPE_REFERENCE = - new TypeReference<>() {}; + static final TypeReference> LIST_STATUS_TYPE_REFERENCE = new TypeReference<>() {}; static final TypeReference> LIST_FOLDERCONTENT_TYPE_REFERENCE = new TypeReference<>() {}; static final TypeReference> LIST_PR_TYPE_REFERENCE = new TypeReference<>() {}; - static final TypeReference> LIST_BRANCHES = + static final TypeReference> + LIST_PR_COMMENT_TYPE_REFERENCE = new TypeReference<>() {}; + static final TypeReference> LIST_BRANCHES = new TypeReference<>() {}; + static final TypeReference> LIST_REFERENCES = new TypeReference<>() {}; + static final TypeReference> LIST_REPOSITORY_INVITATION = new TypeReference<>() {}; - static final TypeReference> LIST_REFERENCES = + + static final TypeReference> LIST_TEAMS = new TypeReference<>() {}; + + static final TypeReference> LIST_TEAM_MEMBERS = new TypeReference<>() {}; + + static final TypeReference> LIST_PENDING_TEAM_INVITATIONS = new TypeReference<>() {}; + static final TypeReference> LIST_FILE_ITEMS = new TypeReference<>() {}; + private static final String GET_ACCESS_TOKEN_URL = "app/installations/%s/access_tokens"; private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -95,30 +122,53 @@ public class GitHubClient { private static final int FORBIDDEN = 403; private final URI baseUrl; + + private final Optional graphqlUrl; private final Json json = Json.create(); - private final OkHttpClient client; + private final HttpClient client; + private Call.Factory callFactory; private final String token; - private final File privateKey; + private final byte[] privateKey; private final Integer appId; private final Integer installationId; private final Map installationTokens; private GitHubClient( - final OkHttpClient client, + final HttpClient client, final URI baseUrl, + final URI graphqlUrl, final String accessToken, - final File privateKey, + final byte[] privateKey, final Integer appId, final Integer installationId) { this.baseUrl = baseUrl; + this.graphqlUrl = Optional.ofNullable(graphqlUrl); this.token = accessToken; this.client = client; this.privateKey = privateKey; this.appId = appId; this.installationId = installationId; - this.installationTokens = new HashMap<>(); + this.installationTokens = new ConcurrentHashMap<>(); + } + + private GitHubClient( + final OkHttpClient client, + final URI baseUrl, + final URI graphqlUrl, + final String accessToken, + final byte[] privateKey, + final Integer appId, + final Integer installationId) { + this.baseUrl = baseUrl; + this.graphqlUrl = Optional.ofNullable(graphqlUrl); + this.token = accessToken; + this.client = new OkHttpHttpClient(client); + this.privateKey = privateKey; + this.appId = appId; + this.installationId = installationId; + this.installationTokens = new ConcurrentHashMap<>(); } /** @@ -129,7 +179,11 @@ private GitHubClient( * @return github api client */ public static GitHubClient create(final URI baseUrl, final String token) { - return new GitHubClient(new OkHttpClient(), baseUrl, token, null, null, null); + return new GitHubClient(new OkHttpClient(), baseUrl, null, token, null, null, null); + } + + public static GitHubClient create(final URI baseUrl, final URI graphqlUri, final String token) { + return new GitHubClient(new OkHttpClient(), baseUrl, graphqlUri, token, null, null, null); } /** @@ -141,7 +195,20 @@ public static GitHubClient create(final URI baseUrl, final String token) { * @return github api client */ public static GitHubClient create(final URI baseUrl, final File privateKey, final Integer appId) { - return new GitHubClient(new OkHttpClient(), baseUrl, null, privateKey, appId, null); + return createOrThrow(new OkHttpClient(), baseUrl, null, privateKey, appId, null); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param baseUrl base URL + * @param privateKey the private key as byte array + * @param appId the github app ID + * @return github api client + */ + public static GitHubClient create( + final URI baseUrl, final byte[] privateKey, final Integer appId) { + return new GitHubClient(new OkHttpClient(), baseUrl, null, null, privateKey, appId, null); } /** @@ -155,7 +222,25 @@ public static GitHubClient create(final URI baseUrl, final File privateKey, fina */ public static GitHubClient create( final URI baseUrl, final File privateKey, final Integer appId, final Integer installationId) { - return new GitHubClient(new OkHttpClient(), baseUrl, null, privateKey, appId, installationId); + return createOrThrow(new OkHttpClient(), baseUrl, null, privateKey, appId, installationId); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param baseUrl base URL + * @param privateKey the private key as byte array + * @param appId the github app ID + * @param installationId the installationID to be authenticated as + * @return github api client + */ + public static GitHubClient create( + final URI baseUrl, + final byte[] privateKey, + final Integer appId, + final Integer installationId) { + return new GitHubClient( + new OkHttpClient(), baseUrl, null, null, privateKey, appId, installationId); } /** @@ -172,7 +257,7 @@ public static GitHubClient create( final URI baseUrl, final File privateKey, final Integer appId) { - return new GitHubClient(httpClient, baseUrl, null, privateKey, appId, null); + return createOrThrow(httpClient, baseUrl, null, privateKey, appId, null); } /** @@ -187,10 +272,63 @@ public static GitHubClient create( public static GitHubClient create( final OkHttpClient httpClient, final URI baseUrl, + final URI graphqlUrl, final File privateKey, + final Integer appId) { + return createOrThrow(httpClient, baseUrl, graphqlUrl, privateKey, appId, null); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param httpClient an instance of OkHttpClient + * @param baseUrl base URL + * @param privateKey the private key as byte array + * @param appId the github app ID + * @return github api client + */ + public static GitHubClient create( + final OkHttpClient httpClient, + final URI baseUrl, + final byte[] privateKey, + final Integer appId) { + return new GitHubClient(httpClient, baseUrl, null, null, privateKey, appId, null); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param httpClient an instance of OkHttpClient + * @param baseUrl base URL + * @param privateKey the private key PEM file + * @param appId the github app ID + * @return github api client + */ + public static GitHubClient create( + final OkHttpClient httpClient, + final URI baseUrl, + final File privateKey, + final Integer appId, + final Integer installationId) { + return createOrThrow(httpClient, baseUrl, null, privateKey, appId, installationId); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param httpClient an instance of OkHttpClient + * @param baseUrl base URL + * @param privateKey the private key as byte array + * @param appId the github app ID + * @return github api client + */ + public static GitHubClient create( + final OkHttpClient httpClient, + final URI baseUrl, + final byte[] privateKey, final Integer appId, final Integer installationId) { - return new GitHubClient(httpClient, baseUrl, null, privateKey, appId, installationId); + return new GitHubClient(httpClient, baseUrl, null, null, privateKey, appId, installationId); } /** @@ -203,7 +341,115 @@ public static GitHubClient create( */ public static GitHubClient create( final OkHttpClient httpClient, final URI baseUrl, final String token) { - return new GitHubClient(httpClient, baseUrl, token, null, null, null); + return new GitHubClient(httpClient, baseUrl, null, token, null, null, null); + } + + public static GitHubClient create( + final OkHttpClient httpClient, final URI baseUrl, final URI graphqlUrl, final String token) { + return new GitHubClient(httpClient, baseUrl, graphqlUrl, token, null, null, null); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param httpClient an instance of OkHttpClient + * @param baseUrl base URL + * @param privateKey the private key PEM file + * @param appId the github app ID + * @return github api client + */ + public static GitHubClient create( + final HttpClient httpClient, final URI baseUrl, final File privateKey, final Integer appId) { + return createOrThrow(httpClient, baseUrl, null, privateKey, appId, null); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param httpClient an instance of OkHttpClient + * @param baseUrl base URL + * @param privateKey the private key PEM file + * @param appId the github app ID + * @return github api client + */ + public static GitHubClient create( + final HttpClient httpClient, + final URI baseUrl, + final URI graphqlUrl, + final File privateKey, + final Integer appId) { + return createOrThrow(httpClient, baseUrl, graphqlUrl, privateKey, appId, null); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param httpClient an instance of OkHttpClient + * @param baseUrl base URL + * @param privateKey the private key as byte array + * @param appId the github app ID + * @return github api client + */ + public static GitHubClient create( + final HttpClient httpClient, + final URI baseUrl, + final byte[] privateKey, + final Integer appId) { + return new GitHubClient(httpClient, baseUrl, null, null, privateKey, appId, null); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param httpClient an instance of OkHttpClient + * @param baseUrl base URL + * @param privateKey the private key PEM file + * @param appId the github app ID + * @return github api client + */ + public static GitHubClient create( + final HttpClient httpClient, + final URI baseUrl, + final File privateKey, + final Integer appId, + final Integer installationId) { + return createOrThrow(httpClient, baseUrl, null, privateKey, appId, installationId); + } + + /** + * Create a github api client with a given base URL and a path to a key. + * + * @param httpClient an instance of OkHttpClient + * @param baseUrl base URL + * @param privateKey the private key as byte array + * @param appId the github app ID + * @return github api client + */ + public static GitHubClient create( + final HttpClient httpClient, + final URI baseUrl, + final byte[] privateKey, + final Integer appId, + final Integer installationId) { + return new GitHubClient(httpClient, baseUrl, null, null, privateKey, appId, installationId); + } + + /** + * Create a github api client with a given base URL and authorization token. + * + * @param httpClient an instance of OkHttpClient + * @param baseUrl base URL + * @param token authorization token + * @return github api client + */ + public static GitHubClient create( + final HttpClient httpClient, final URI baseUrl, final String token) { + return new GitHubClient(httpClient, baseUrl, null, token, null, null, null); + } + + public static GitHubClient create( + final HttpClient httpClient, final URI baseUrl, final URI graphqlUrl, final String token) { + return new GitHubClient(httpClient, baseUrl, graphqlUrl, token, null, null, null); } /** @@ -215,27 +461,66 @@ public static GitHubClient create( */ public static GitHubClient scopeForInstallationId( final GitHubClient client, final int installationId) { - if (!client.getPrivateKey().isPresent()) { + if (client.getPrivateKey().isEmpty()) { throw new RuntimeException("Installation ID scoped client needs a private key"); } return new GitHubClient( client.client, client.baseUrl, null, + null, client.getPrivateKey().get(), client.appId, installationId); } - static String responseBodyUnchecked(final Response response) { - try (ResponseBody body = response.body()) { - return body.string(); - } catch (IOException e) { - throw new UncheckedIOException("Failed getting response body for: " + response, e); + public GitHubClient withScopeForInstallationId(final int installationId) { + if (Optional.ofNullable(privateKey).isEmpty()) { + throw new RuntimeException("Installation ID scoped client needs a private key"); } + return new GitHubClient( + client, baseUrl, graphqlUrl.orElse(null), null, privateKey, appId, installationId); + } + + /** + * This is for clients authenticated as a GitHub App: when performing operations, the + * "installation" of the App must be specified. This returns a {@code GitHubClient} that has been + * scoped to the user's/organization's installation of the app, if any. + */ + public CompletionStage> asAppScopedClient(final String owner) { + return Async.exceptionallyCompose( + this.createOrganisationClient(owner) + .createGithubAppClient() + .getInstallation() + .thenApply(Installation::id), + e -> { + if (e.getCause() instanceof RequestNotOkException + && ((RequestNotOkException) e.getCause()).statusCode() == HTTP_NOT_FOUND) { + return this.createUserClient(owner) + .createGithubAppClient() + .getUserInstallation() + .thenApply(Installation::id); + } + return CompletableFuture.failedFuture(e); + }) + .thenApply(id -> Optional.of(this.withScopeForInstallationId(id))) + .exceptionally( + e -> { + if (e.getCause() instanceof RequestNotOkException + && ((RequestNotOkException) e.getCause()).statusCode() == HTTP_NOT_FOUND) { + return Optional.empty(); + } + throw new RuntimeException(e); + }); + } + + public GitHubClient withTracer(final Tracer tracer) { + this.tracer = tracer; + this.client.setTracer(tracer); + return this; } - public Optional getPrivateKey() { + public Optional getPrivateKey() { return Optional.ofNullable(privateKey); } @@ -274,56 +559,98 @@ public SearchClient createSearchClient() { return SearchClient.create(this); } + /** + * Create a checks API client + * + * @param owner repository owner + * @param repo repository name + * @return checks API client + */ + public ChecksClient createChecksClient(final String owner, final String repo) { + return ChecksClient.create(this, owner, repo); + } + + /** + * Create organisation API client + * + * @return organisation API client + */ + public OrganisationClient createOrganisationClient(final String org) { + return OrganisationClient.create(this, org); + } + + /** + * Create user API client + * + * @return user API client + */ + public UserClient createUserClient(final String owner) { + return UserClient.create(this, owner); + } + + /** + * Create GitHub App API client + * + * @return GitHub App API client + */ + public GithubAppClient createGithubAppClient() { + return new GithubAppClient(this); + } + Json json() { return json; } /** - * Make an http GET request for the given path on the server + * Make a http GET request for the given path on the server * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @return response body as a String */ - CompletableFuture request(final String path) { - final Request request = requestBuilder(path).build(); - log.debug("Making request to {}", request.url().toString()); - return call(request); + CompletableFuture request(final String path) { + return call("GET", path); } /** - * Make an http GET request for the given path on the server + * Make a http GET request for the given path on the server * - * @param path relative to the Github base url + * @param path relative to the GitHub base url + * @param extraHeaders extra github headers to be added to the call + * @return a reader of response body + */ + CompletableFuture request( + final String path, final Map extraHeaders) { + return call("GET", path, extraHeaders); + } + + /** + * Make a http GET request for the given path on the server + * + * @param path relative to the GitHub base url * @return body deserialized as provided type */ CompletableFuture request(final String path, final Class clazz) { - final Request request = requestBuilder(path).build(); - log.debug("Making request to {}", request.url().toString()); - return call(request) - .thenApply(body -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(body), clazz)); + return call(path) + .thenApply(response -> json().fromJsonUncheckedNotNull(response.bodyString(), clazz)); } /** - * Make an http GET request for the given path on the server + * Make a http GET request for the given path on the server * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @param extraHeaders extra github headers to be added to the call * @return body deserialized as provided type */ CompletableFuture request( final String path, final Class clazz, final Map extraHeaders) { - final Request.Builder builder = requestBuilder(path); - extraHeaders.forEach(builder::addHeader); - final Request request = builder.build(); - log.debug("Making request to {}", request.url().toString()); - return call(request) - .thenApply(body -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(body), clazz)); + return call("GET", path, null, extraHeaders) + .thenApply(response -> json().fromJsonUncheckedNotNull(response.bodyString(), clazz)); } /** - * Make an http request for the given path on the Github server. + * Make a http request for the given path on the GitHub server. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @param extraHeaders extra github headers to be added to the call * @return body deserialized as provided type */ @@ -331,70 +658,51 @@ CompletableFuture request( final String path, final TypeReference typeReference, final Map extraHeaders) { - final Request.Builder builder = requestBuilder(path); - extraHeaders.forEach(builder::addHeader); - final Request request = builder.build(); - log.debug("Making request to {}", request.url().toString()); - return call(request) + return call("GET", path, null, extraHeaders) .thenApply( - response -> - json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), typeReference)); + response -> json().fromJsonUncheckedNotNull(response.bodyString(), typeReference)); } /** - * Make an http request for the given path on the Github server. + * Make a http request for the given path on the GitHub server. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @return body deserialized as provided type */ CompletableFuture request(final String path, final TypeReference typeReference) { - final Request request = requestBuilder(path).build(); - log.debug("Making request to {}", request.url().toString()); - return call(request) + return call(path) .thenApply( - response -> - json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), typeReference)); + response -> json().fromJsonUncheckedNotNull(response.bodyString(), typeReference)); } /** - * Make an http POST request for the given path with provided JSON body. + * Make a http POST request for the given path with provided JSON body. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @param data request body as stringified JSON * @return response body as String */ - CompletableFuture post(final String path, final String data) { - final Request request = - requestBuilder(path) - .method("POST", RequestBody.create(parse(MediaType.APPLICATION_JSON), data)) - .build(); - log.debug("Making POST request to {}", request.url().toString()); - return call(request); + CompletableFuture post(final String path, final String data) { + return call("POST", path, data); } /** - * Make an http POST request for the given path with provided JSON body. + * Make a http POST request for the given path with provided JSON body. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @param data request body as stringified JSON * @param extraHeaders * @return response body as String */ - CompletableFuture post( + CompletableFuture post( final String path, final String data, final Map extraHeaders) { - final Request.Builder builder = - requestBuilder(path) - .method("POST", RequestBody.create(parse(MediaType.APPLICATION_JSON), data)); - extraHeaders.forEach(builder::addHeader); - final Request request = builder.build(); - log.debug("Making POST request to {}", request.url().toString()); - return call(request); + return call("POST", path, data, extraHeaders); } /** - * Make an http POST request for the given path with provided JSON body. + * Make a http POST request for the given path with provided JSON body. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @param data request body as stringified JSON * @param clazz class to cast response as * @param extraHeaders @@ -406,110 +714,205 @@ CompletableFuture post( final Class clazz, final Map extraHeaders) { return post(path, data, extraHeaders) - .thenApply( - response -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), clazz)); + .thenApply(response -> json().fromJsonUncheckedNotNull(response.bodyString(), clazz)); } /** - * Make an http POST request for the given path with provided JSON body. + * Make a http POST request for the given path with provided JSON body. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @param data request body as stringified JSON * @param clazz class to cast response as * @return response body deserialized as provided class */ CompletableFuture post(final String path, final String data, final Class clazz) { return post(path, data) - .thenApply( - response -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), clazz)); + .thenApply(response -> json().fromJsonUncheckedNotNull(response.bodyString(), clazz)); } /** - * Make an http PUT request for the given path with provided JSON body. + * Make a POST request to the graphql endpoint of GitHub * - * @param path relative to the Github base url * @param data request body as stringified JSON - * @return response body as String + * @return response + * @see + * "https://docs.github.com/en/enterprise-server@3.9/graphql/guides/forming-calls-with-graphql#communicating-with-graphql" */ - CompletableFuture put(final String path, final String data) { - final Request request = - requestBuilder(path) - .method("PUT", RequestBody.create(parse(MediaType.APPLICATION_JSON), data)) - .build(); - log.debug("Making POST request to {}", request.url().toString()); - return call(request); + public CompletableFuture postGraphql(final String data) { + return graphqlRequestBuilder() + .thenCompose( + requestBuilder -> { + final HttpRequest request = requestBuilder.method("POST").body(data).build(); + log.info("Making POST request to {}", request.url()); + return call(request); + }); } /** - * Make an http PATCH request for the given path with provided JSON body. + * Make a http PUT request for the given path with provided JSON body. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @param data request body as stringified JSON * @return response body as String */ - CompletableFuture patch(final String path, final String data) { - final Request request = - requestBuilder(path) - .method("PATCH", RequestBody.create(parse(MediaType.APPLICATION_JSON), data)) - .build(); - log.debug("Making PATCH request to {}", request.url().toString()); - return call(request); + CompletableFuture put(final String path, final String data) { + return call("PUT", path, data); } /** - * Make an http PATCH request for the given path with provided JSON body + * Make a HTTP PUT request for the given path with provided JSON body. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @param data request body as stringified JSON * @param clazz class to cast response as + * @return response body deserialized as provided class + */ + CompletableFuture put(final String path, final String data, final Class clazz) { + return put(path, data) + .thenApply(response -> json().fromJsonUncheckedNotNull(response.bodyString(), clazz)); + } + + /** + * Make a http PATCH request for the given path with provided JSON body. + * + * @param path relative to the GitHub base url + * @param data request body as stringified JSON * @return response body as String */ + CompletableFuture patch(final String path, final String data) { + return call("PATCH", path, data); + } + + /** + * Make a http PATCH request for the given path with provided JSON body. + * + * @param path relative to the GitHub base url + * @param data request body as stringified JSON + * @param clazz class to cast response as + * @return response body deserialized as provided class + */ + CompletableFuture patch(final String path, final String data, final Class clazz) { + return patch(path, data) + .thenApply(response -> json().fromJsonUncheckedNotNull(response.bodyString(), clazz)); + } + + /** + * Make a http PATCH request for the given path with provided JSON body + * + * @param path relative to the GitHub base url + * @param data request body as stringified JSON + * @param clazz class to cast response as + * @return response body deserialized as provided class + */ CompletableFuture patch( final String path, final String data, final Class clazz, final Map extraHeaders) { - final Request.Builder builder = - requestBuilder(path) - .method("PATCH", RequestBody.create(parse(MediaType.APPLICATION_JSON), data)); - extraHeaders.forEach(builder::addHeader); - final Request request = builder.build(); - log.debug("Making PATCH request to {}", request.url().toString()); - return call(request) - .thenApply( - response -> json().fromJsonUncheckedNotNull(responseBodyUnchecked(response), clazz)); + return call("PATCH", path, data, extraHeaders) + .thenApply(response -> json().fromJsonUncheckedNotNull(response.bodyString(), clazz)); } /** - * Make an http DELETE request for the given path. + * Make a http DELETE request for the given path. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @return response body as String */ - CompletableFuture delete(final String path) { - final Request request = requestBuilder(path).delete().build(); - log.debug("Making DELETE request to {}", request.url().toString()); - return call(request); + CompletableFuture delete(final String path) { + return call("DELETE", path); } /** - * Make an http DELETE request for the given path. + * Make a http DELETE request for the given path. * - * @param path relative to the Github base url + * @param path relative to the GitHub base url * @param data request body as stringified JSON * @return response body as String */ - CompletableFuture delete(final String path, final String data) { - final Request request = - requestBuilder(path) - .method("DELETE", RequestBody.create(parse(MediaType.APPLICATION_JSON), data)) - .build(); - log.debug("Making DELETE request to {}", request.url().toString()); - return call(request); + CompletableFuture delete(final String path, final String data) { + return call("DELETE", path, data); + } + + /** + * Make a http DELETE request for the given path. + * + * @param path relative to the GitHub base url + * @return response body as String + */ + private CompletableFuture call(final String path) { + return call("GET", path, null, null); + } + + /** + * Make a http request for the given path on the GitHub server. + * + * @param method HTTP method + * @param path relative to the GitHub base url + * @return response body as String + */ + private CompletableFuture call(final String method, final String path) { + return call(method, path, null, null); } /** - * Create a URL for a given path to this Github server. + * Make a http request for the given path on the GitHub server. + * + * @param method HTTP method + * @param path relative to the GitHub base url + * @param extraHeaders extra github headers to be added to the call + * @return response body as String + */ + private CompletableFuture call( + final String method, final String path, final Map extraHeaders) { + return call(method, path, null, extraHeaders); + } + + /* + * Make a http request for the given path on the GitHub server. + * + * @param method HTTP method + * @param path relative to the GitHub base url + * @param data request body as stringified JSON + * @return response body as String + */ + private CompletableFuture call( + final String method, final String path, final String data) { + return call(method, path, data, null); + } + + /** + * Make a http request for the given path on the GitHub server. + * + * @param method HTTP method + * @param path relative to the GitHub base url + * @param data request body as stringified JSON + * @param extraHeaders extra github headers to be added to the call + * @return response body as String + */ + private CompletableFuture call( + final String method, + final String path, + @Nullable final String data, + @Nullable final Map extraHeaders) { + return requestBuilder(path) + .thenCompose( + requestBuilder -> { + final ImmutableHttpRequest.Builder builder = requestBuilder.method(method); + if (data != null) { + builder.body(data); + } + final HttpRequest request = + extraHeaders == null || extraHeaders.isEmpty() + ? builder.build() + : toHttpRequestHeaders(builder, extraHeaders).build(); + log.debug("Making {} request to {}", method, request.url().toString()); + return call(request); + }); + } + + /** + * Create a URL for a given path to this GitHub server. * * @param path relative URI * @return URL to path on this server @@ -518,46 +921,103 @@ String urlFor(final String path) { return baseUrl.toString().replaceAll("/+$", "") + "/" + path.replaceAll("^/+", ""); } - private Request.Builder requestBuilder(final String path) { - final Request.Builder builder = - new Request.Builder() - .url(urlFor(path)) - .addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) - .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); - builder.addHeader(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(path)); - + /** + * Adds extra headers to the Request Builder + * + * @param builder the request builder + * @param extraHeaders the extra headers to be added + * @return the request builder with the extra headers + */ + private ImmutableHttpRequest.Builder toHttpRequestHeaders( + final ImmutableHttpRequest.Builder builder, final Map extraHeaders) { + HttpRequest request = builder.build(); + + extraHeaders.forEach( + (headerKey, headerValue) -> { + if (request.headers().containsKey(headerKey)) { + List headers = new ArrayList<>(request.headers().get(headerKey)); + headers.add(headerValue); + builder.putHeaders(headerKey, headers); + } else { + builder.putHeaders(headerKey, List.of(headerValue)); + } + }); return builder; } + /* + * Create a Request Builder for this GitHub GraphQL server. + * + * @return GraphQL Request Builder + */ + private CompletableFuture graphqlRequestBuilder() { + URI url = graphqlUrl.orElseThrow(() -> new IllegalStateException("No graphql url set")); + return requestBuilder("/graphql") + .thenApply(requestBuilder -> requestBuilder.url(url.toString())); + } + + /* + * Create a Request Builder for this GitHub server. + * + * @param path relative URI + * @return Request Builder + */ + private CompletableFuture requestBuilder(final String path) { + return getAuthorizationHeader(path) + .thenApply( + authHeader -> + ImmutableHttpRequest.builder() + .url(urlFor(path)) + .method("GET") + .body("") + .putHeaders(HttpHeaders.ACCEPT, List.of(MediaType.APPLICATION_JSON)) + .putHeaders(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_JSON)) + .putHeaders(HttpHeaders.AUTHORIZATION, List.of(authHeader))); + } + + /* + * Check if the GraphQL API is enabled for this client. + * + * @return true if the GraphQL API is enabled, false otherwise + */ + public boolean isGraphqlEnabled() { + return graphqlUrl.isPresent(); + } + /* Generates the Authentication header, given the API endpoint and the credentials provided. -

Github Requests can be authenticated in 3 different ways. +

GitHub Requests can be authenticated in 3 different ways. (1) Regular, static access token; - (2) JWT Token, generated from a private key. Used in Github Apps; - (3) Installation Token, generated from the JWT token. Also used in Github Apps. + (2) JWT Token, generated from a private key. Used in GitHub Apps; + (3) Installation Token, generated from the JWT token. Also used in GitHub Apps. */ - private String getAuthorizationHeader(final String path) { + private CompletableFuture getAuthorizationHeader(final String path) { if (isJwtRequest(path) && getPrivateKey().isEmpty()) { throw new IllegalStateException("This endpoint needs a client with a private key for an App"); } if (getAccessToken().isPresent()) { - return String.format("token %s", token); + return completedFuture(String.format("token %s", token)); } else if (getPrivateKey().isPresent()) { final String jwtToken; try { - jwtToken = JwtTokenIssuer.fromFile(privateKey).getToken(appId); + jwtToken = JwtTokenIssuer.fromPrivateKey(privateKey).getToken(appId); } catch (Exception e) { throw new RuntimeException("There was an error generating JWT token", e); } if (isJwtRequest(path)) { - return String.format("Bearer %s", jwtToken); + return completedFuture(String.format("Bearer %s", jwtToken)); } if (installationId == null) { throw new RuntimeException("This endpoint needs a client with an installation ID"); } try { - return String.format("token %s", getInstallationToken(jwtToken, installationId)); + return getInstallationToken(jwtToken, installationId) + .thenApply(token -> String.format("token %s", token)) + .exceptionally( + ex -> { + throw new RuntimeException("Could not generate access token for github app", ex); + }); } catch (Exception e) { throw new RuntimeException("Could not generate access token for github app", e); } @@ -566,131 +1026,239 @@ private String getAuthorizationHeader(final String path) { } private boolean isJwtRequest(final String path) { - return path.startsWith("/app/installation") || path.endsWith("installation"); + return path.equals("/app") + || path.startsWith("/app/installation") + || path.endsWith("installation"); } - private String getInstallationToken(final String jwtToken, final int installationId) - throws Exception { + /** + * Fetches installation token from the cache or from the server if it is expired. + * + * @param jwtToken the JWT token + * @param installationId the installation ID + * @return a CompletableFuture with the installation token + */ + private CompletableFuture getInstallationToken( + final String jwtToken, final int installationId) { AccessToken installationToken = installationTokens.get(installationId); if (installationToken == null || isExpired(installationToken)) { log.info( - "Github token for installation {} is either expired or null. Trying to get a new one.", + "GitHub token for installation {} is either expired or null. Trying to get a new one.", installationId); - installationToken = generateInstallationToken(jwtToken, installationId); - installationTokens.put(installationId, installationToken); + return generateInstallationToken(jwtToken, installationId) + .thenApply( + accessToken -> { + installationTokens.put(installationId, accessToken); + return accessToken.token(); + }); } - return installationToken.token(); + return completedFuture(installationToken.token()); } + /** + * Check if the token is expired. + * + * @param token the access token + * @return true if the token is expired, false otherwise + */ private boolean isExpired(final AccessToken token) { - return token.expiresAt().isBefore(ZonedDateTime.now().plusMinutes(-1)); + // Adds a few minutes to avoid making calls with an expired token due to clock differences + return token.expiresAt().isBefore(ZonedDateTime.now().plusMinutes(EXPIRY_MARGIN_IN_MINUTES)); } - private AccessToken generateInstallationToken(final String jwtToken, final int installationId) - throws Exception { - log.info("Got JWT Token. Now getting Github access_token for installation {}", installationId); + /** + * Generates the installation token for a given installation ID. + * + * @param jwtToken the JWT token + * @param installationId the installation ID + * @return a CompletableFuture with the access token + */ + private CompletableFuture generateInstallationToken( + final String jwtToken, final int installationId) { + log.info("Got JWT Token. Now getting GitHub access_token for installation {}", installationId); final String url = String.format(urlFor(GET_ACCESS_TOKEN_URL), installationId); - final Request request = - new Request.Builder() - .addHeader("Accept", "application/vnd.github.machine-man-preview+json") - .addHeader("Authorization", "Bearer " + jwtToken) + final HttpRequest request = + ImmutableHttpRequest.builder() .url(url) - .method("POST", RequestBody.create(parse(MediaType.APPLICATION_JSON), "")) + .putHeaders("Accept", List.of("application/vnd.github.machine-man-preview+json")) + .putHeaders("Authorization", List.of("Bearer " + jwtToken)) + .method("POST") + .body("") .build(); - final Response response = client.newCall(request).execute(); - - if (!response.isSuccessful()) { - throw new Exception( - String.format( - "Got non-2xx status %s when getting an access token from GitHub: %s", - response.code(), response.message())); - } - - if (response.body() == null) { - throw new Exception( - String.format( - "Got empty response body when getting an access token from GitHub, HTTP status was: %s", - response.message())); - } - final String text = response.body().string(); - response.body().close(); - return Json.create().fromJson(text, AccessToken.class); + return this.client + .send(request) + .thenApply( + response -> { + if (!response.isSuccessful()) { + throw new RuntimeException( + String.format( + "Got non-2xx status %s when getting an access token from GitHub: %s", + response.statusCode(), response.statusMessage())); + } + + if (response.bodyString() == null) { + throw new RuntimeException( + String.format( + "Got empty response body when getting an access token from GitHub, HTTP" + + " status was: %s", + response.statusMessage())); + } + final String text = response.bodyString(); + try { + return Json.create().fromJson(text, AccessToken.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .toCompletableFuture(); } - private CompletableFuture call(final Request request) { - final Call call = client.newCall(request); - - final CompletableFuture future = new CompletableFuture<>(); + private CompletableFuture call(final HttpRequest httpRequest) { + return this.client + .send(httpRequest) + .thenCompose(httpResponse -> handleResponse(httpRequest, httpResponse)); + } + /** + * Handle the response from the server. If the response is a redirect, redo the request with the + * new URL. + * + * @param httpRequest the original request + * @param httpResponse the response from the server + * @return a CompletableFuture with the processed response + */ + private CompletableFuture handleResponse( + final HttpRequest httpRequest, final HttpResponse httpResponse) { + final CompletableFuture future = new CompletableFuture<>(); // avoid multiple redirects final AtomicBoolean redirected = new AtomicBoolean(false); - - call.enqueue( - new Callback() { - @Override - public void onFailure(final Call call, final IOException e) { - future.completeExceptionally(e); - } - - @Override - public void onResponse(final Call call, final Response response) { - processPossibleRedirects(response, redirected) - .handle( - (res, ex) -> { - if (Objects.nonNull(ex)) { - future.completeExceptionally(ex); - } else if (!res.isSuccessful()) { - try { - future.completeExceptionally(mapException(res, request)); - } catch (final Throwable e) { - future.completeExceptionally(e); - } finally { - if (res.body() != null) { - res.body().close(); - } - } - } else { - future.complete(res); - } - return res; - }); - } - }); - + processPossibleRedirects(httpResponse, redirected) + .handle( + (res, ex) -> { + if (Objects.nonNull(ex)) { + future.completeExceptionally(ex); + } else if (!res.isSuccessful()) { + try { + future.completeExceptionally(mapException(httpRequest, res)); + } catch (final Throwable e) { + future.completeExceptionally(e); + } + } else { + future.complete(res); + } + return res; + }) + .join(); return future; } - private RequestNotOkException mapException(final Response res, final Request request) - throws IOException { - String bodyString = res.body() != null ? res.body().string() : ""; - if (res.code() == FORBIDDEN) { + /** + * Map the exception to a specific type based on the response status code. + * + * @param httpRequest the original request + * @param httpResponse the response from the server + * @return a RequestNotOkException with the appropriate type + */ + private RequestNotOkException mapException( + final HttpRequest httpRequest, final HttpResponse httpResponse) throws IOException { + String bodyString = Optional.ofNullable(httpResponse.bodyString()).orElse(""); + Map> headersMap = httpResponse.headers(); + + if (httpResponse.statusCode() == FORBIDDEN) { if (bodyString.contains("Repository was archived so is read-only")) { - return new ReadOnlyRepositoryException(request.url().encodedPath(), res.code(), bodyString); + return new ReadOnlyRepositoryException( + httpRequest.method(), + URI.create(httpRequest.url()).getPath(), + httpResponse.statusCode(), + bodyString, + headersMap); } } - return new RequestNotOkException(request.url().encodedPath(), res.code(), bodyString); + + return new RequestNotOkException( + httpRequest.method(), + URI.create(httpRequest.url()).getPath(), + httpResponse.statusCode(), + bodyString, + headersMap); } - CompletableFuture processPossibleRedirects( - final Response response, final AtomicBoolean redirected) { - if (response.code() >= PERMANENT_REDIRECT - && response.code() <= TEMPORARY_REDIRECT + /** + * Process possible redirects. If the response is a redirect, redo the request with the new URL. + * + * @param response the response to process + * @param redirected a flag to indicate if a redirect has already occurred + * @return a CompletableFuture with the processed response + */ + CompletableFuture processPossibleRedirects( + final HttpResponse response, final AtomicBoolean redirected) { + if (response.statusCode() >= PERMANENT_REDIRECT + && response.statusCode() <= TEMPORARY_REDIRECT && !redirected.get()) { redirected.set(true); // redo the same request with a new URL - final String newLocation = response.header("Location"); - final Request request = - requestBuilder(newLocation) - .url(newLocation) - .method("POST", response.request().body()) - .build(); - // Do the new call and complete the original future when the new call completes - return call(request); + final String newLocation = response.headers().get("Location").get(0); + return requestBuilder(newLocation) + .thenCompose( + requestBuilder -> { + HttpRequest request = + requestBuilder + .url(newLocation) + .method(response.request().method()) + .body(response.request().body()) + .build(); + // Do the new call and complete the original future when the new call completes + return call(request); + }); } return completedFuture(response); } + + /** Wrapper to Constructors that expose File object for the privateKey argument */ + private static GitHubClient createOrThrow( + final OkHttpClient httpClient, + final URI baseUrl, + final URI graphqlUrl, + final File privateKey, + final Integer appId, + final Integer installationId) { + try { + return new GitHubClient( + httpClient, + baseUrl, + graphqlUrl, + null, + FileUtils.readFileToByteArray(privateKey), + appId, + installationId); + } catch (IOException e) { + throw new RuntimeException("There was an error generating JWT token", e); + } + } + + /** Wrapper to Constructors that expose File object for the privateKey argument */ + private static GitHubClient createOrThrow( + final HttpClient httpClient, + final URI baseUrl, + final URI graphqlUrl, + final File privateKey, + final Integer appId, + final Integer installationId) { + try { + return new GitHubClient( + httpClient, + baseUrl, + graphqlUrl, + null, + FileUtils.readFileToByteArray(privateKey), + appId, + installationId); + } catch (IOException e) { + throw new RuntimeException("There was an error generating JWT token", e); + } + } } diff --git a/src/main/java/com/spotify/github/v3/clients/GithubAppClient.java b/src/main/java/com/spotify/github/v3/clients/GithubAppClient.java index bcc075a5..aeb96094 100644 --- a/src/main/java/com/spotify/github/v3/clients/GithubAppClient.java +++ b/src/main/java/com/spotify/github/v3/clients/GithubAppClient.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,24 +23,36 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.ImmutableMap; import com.spotify.github.v3.apps.InstallationRepositoriesResponse; +import com.spotify.github.v3.apps.requests.AccessTokenRequest; import com.spotify.github.v3.checks.AccessToken; +import com.spotify.github.v3.checks.App; import com.spotify.github.v3.checks.Installation; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import javax.ws.rs.core.HttpHeaders; /** Apps API client */ public class GithubAppClient { + private static final String GET_AUTHENTICATED_APP_URL = "/app"; + private static final String GET_INSTALLATION_BY_ID_URL = "/app/installations/%s"; private static final String GET_ACCESS_TOKEN_URL = "/app/installations/%s/access_tokens"; private static final String GET_INSTALLATIONS_URL = "/app/installations?per_page=100"; private static final String GET_INSTALLATION_REPO_URL = "/repos/%s/%s/installation"; private static final String LIST_ACCESSIBLE_REPOS_URL = "/installation/repositories"; + /* + Owner and org are interchangeable and therefore "owner" is used to + refer to the organisation in the installation endpoint + */ + private static final String GET_INSTALLATION_ORG_URL = "/orgs/%s/installation"; + private static final String GET_INSTALLATION_USER_URL = "/users/%s/installation"; + private final GitHubClient github; - private final String owner; - private final String repo; + private final Optional maybeOwner; + private final Optional maybeRepo; private final Map extraHeaders = ImmutableMap.of(HttpHeaders.ACCEPT, "application/vnd.github.machine-man-preview+json"); @@ -50,8 +62,36 @@ public class GithubAppClient { GithubAppClient(final GitHubClient github, final String owner, final String repo) { this.github = github; - this.owner = owner; - this.repo = repo; + this.maybeOwner = Optional.of(owner); + this.maybeRepo = Optional.of(repo); + } + + GithubAppClient(final GitHubClient github, final String owner) { + this.github = github; + this.maybeOwner = Optional.of(owner); + this.maybeRepo = Optional.empty(); + } + + GithubAppClient(final GitHubClient github) { + this.github = github; + this.maybeOwner = Optional.empty(); + this.maybeRepo = Optional.empty(); + } + + /** + * Gets the owner, throwing a descriptive exception if not present. + * + * @return the owner string + * @throws IllegalStateException if owner is not present + */ + private String requireOwner() { + return maybeOwner.orElseThrow( + () -> + new IllegalStateException( + "This operation requires an owner context. " + + "Use GitHubClient.createOrganisationClient(owner).createGithubAppClient() " + + "or GitHubClient.createRepositoryClient(owner, repo).createGithubAppClient() " + + "instead of GitHubClient.createGithubAppClient()")); } /** @@ -64,25 +104,80 @@ public CompletableFuture> getInstallations() { } /** - * Get Installation of a repo + * Get Installation of repo or org * - * @return a list of Installation + * @return an Installation */ public CompletableFuture getInstallation() { + return maybeRepo.map(this::getRepoInstallation).orElseGet(this::getOrgInstallation); + } + + /** + * Get Installation identified by its installation id + * + * @return an Installation + */ + public CompletableFuture getInstallation(final Integer installationId) { + return github.request( + String.format(GET_INSTALLATION_BY_ID_URL, installationId), Installation.class); + } + + /** + * Get an installation of a repo + * + * @return an Installation + */ + private CompletableFuture getRepoInstallation(final String repo) { + return github.request( + String.format(GET_INSTALLATION_REPO_URL, requireOwner(), repo), Installation.class); + } + + /** + * Get an installation of an org + * + * @return an Installation + */ + private CompletableFuture getOrgInstallation() { return github.request( - String.format(GET_INSTALLATION_REPO_URL, owner, repo), Installation.class, extraHeaders); + String.format(GET_INSTALLATION_ORG_URL, requireOwner()), Installation.class); + } + + /** + * Get an installation of a user + * + * @return an Installation + */ + public CompletableFuture getUserInstallation() { + return github.request( + String.format(GET_INSTALLATION_USER_URL, requireOwner()), Installation.class); } /** * Authenticates as an installation * * @return an Installation Token + * @see #getAccessToken(Integer, AccessTokenRequest) for repository-scoped tokens */ public CompletableFuture getAccessToken(final Integer installationId) { final String path = String.format(GET_ACCESS_TOKEN_URL, installationId); return github.post(path, "", AccessToken.class, extraHeaders); } + /** + * Authenticates as an installation with repository scoping. + * + * @param installationId the installation ID + * @param request the access token request with optional repository scoping + * @return an Installation Token + * @see "https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app" + */ + public CompletableFuture getAccessToken( + final Integer installationId, + final AccessTokenRequest request) { + final String path = String.format(GET_ACCESS_TOKEN_URL, installationId); + return github.post(path, github.json().toJsonUnchecked(request), AccessToken.class, extraHeaders); + } + /** * Lists the repositories that an app installation can access. * @@ -95,4 +190,17 @@ public CompletableFuture listAccessibleReposit return GitHubClient.scopeForInstallationId(github, installationId) .request(LIST_ACCESSIBLE_REPOS_URL, InstallationRepositoriesResponse.class, extraHeaders); } + + /** + * Get the authenticated GitHub App. + * + *

Returns the authenticated app. You must use a JWT to access this endpoint. + * + *

see https://docs.github.com/en/rest/apps/apps#get-the-authenticated-app + * + * @return the authenticated App + */ + public CompletableFuture getAuthenticatedApp() { + return github.request(GET_AUTHENTICATED_APP_URL, App.class); + } } diff --git a/src/main/java/com/spotify/github/v3/clients/GithubPage.java b/src/main/java/com/spotify/github/v3/clients/GithubPage.java index 6bbd4ad0..3e9e6c3a 100644 --- a/src/main/java/com/spotify/github/v3/clients/GithubPage.java +++ b/src/main/java/com/spotify/github/v3/clients/GithubPage.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,7 +20,6 @@ package com.spotify.github.v3.clients; -import static com.spotify.github.v3.clients.GitHubClient.responseBodyUnchecked; import static java.util.Arrays.stream; import static java.util.Objects.nonNull; import static java.util.function.Function.identity; @@ -37,8 +36,9 @@ import java.util.NoSuchElementException; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; -import okhttp3.ResponseBody; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.http.client.utils.URIBuilder; /** * Async page implementation for github resources @@ -47,12 +47,27 @@ */ public class GithubPage implements AsyncPage { + static final int ITEM_PER_PAGE_DEFAULT = 30; private final GitHubClient github; private final String path; private final TypeReference> typeReference; + private final int itemsPerPage; + + protected static String formatPath(final String path, final int itemsPerPage) { + try { + URIBuilder uriBuilder = new URIBuilder(path); + if (uriBuilder.getQueryParams().stream().anyMatch(p -> p.getName().equals("per_page"))) { + return path; + } + uriBuilder.addParameter("per_page", Integer.toString(itemsPerPage)); + return uriBuilder.toString(); + } catch (Exception e) { + return path; + } + } /** - * C'tor. + * Constructor. * * @param github github client * @param path resource page path @@ -60,8 +75,27 @@ public class GithubPage implements AsyncPage { */ GithubPage( final GitHubClient github, final String path, final TypeReference> typeReference) { + this.itemsPerPage = ITEM_PER_PAGE_DEFAULT; + this.github = github; + this.path = formatPath(path, ITEM_PER_PAGE_DEFAULT); + this.typeReference = typeReference; + } + + /** + * Constructor. + * + * @param github github client + * @param path resource page path + * @param typeReference type reference for deserialization + */ + GithubPage( + final GitHubClient github, + final String path, + final TypeReference> typeReference, + final int itemsPerPage) { + this.itemsPerPage = itemsPerPage; this.github = github; - this.path = path; + this.path = formatPath(path, itemsPerPage); this.typeReference = typeReference; } @@ -80,7 +114,7 @@ public CompletableFuture pagination() { .map( prevLink -> pageNumberFromUri(prevLink.url().toString()) - .orElseThrow( + .orElseThrow( () -> new RuntimeException( "Could not parse page number from Link header with rel=\"next\""))); @@ -94,7 +128,7 @@ public CompletableFuture pagination() { .map( lastLink -> pageNumberFromUri(lastLink.url().toString()) - .orElseThrow( + .orElseThrow( () -> new RuntimeException( "Could not parse page number from Link " @@ -124,7 +158,7 @@ public CompletableFuture> nextPage() { Optional.ofNullable(linkMap.get("next")) .map(nextLink -> nextLink.url().toString().replaceAll(github.urlFor(""), "")) .orElseThrow(() -> new NoSuchElementException("Page iteration exhausted")); - return new GithubPage<>(github, nextPath, typeReference); + return new GithubPage<>(github, nextPath, typeReference, itemsPerPage); }); } @@ -137,7 +171,7 @@ public CompletableFuture hasNextPage() { /** {@inheritDoc} */ @Override public AsyncPage clone() { - return new GithubPage<>(github, path, typeReference); + return new GithubPage<>(github, path, typeReference, itemsPerPage); } /** {@inheritDoc} */ @@ -147,9 +181,7 @@ public Iterator iterator() { .request(path) .thenApply( response -> - github - .json() - .fromJsonUncheckedNotNull(responseBodyUnchecked(response), typeReference)) + github.json().fromJsonUncheckedNotNull(response.bodyString(), typeReference)) .join() .iterator(); } @@ -158,20 +190,22 @@ private CompletableFuture> linkMapAsync() { return github .request(path) .thenApply( - response -> { - Optional.ofNullable(response.body()).ifPresent(ResponseBody::close); - return Optional.ofNullable(response.headers().get("Link")) - .map(linkHeader -> stream(linkHeader.split(","))) - .orElseGet(Stream::empty) + response -> + Optional.ofNullable(response.header("Link")).stream() + .flatMap(linkHeader -> stream(linkHeader.split(","))) .map(linkString -> Link.from(linkString.split(";"))) .filter(link -> link.rel().isPresent()) - .collect(toMap(link -> link.rel().get(), identity())); - }); + .collect(toMap(link -> link.rel().get(), identity()))); } - private Optional pageNumberFromUri(final String uri) { - return Optional.ofNullable(uri.replaceAll(".*\\?page=", "").replaceAll("&.*", "")) - .filter(string -> string.matches("\\d+")) - .map(Integer::parseInt); + protected static Optional pageNumberFromUri(final String uri) { + Pattern pageInQueryPattern = Pattern.compile("(^|\\?|&)page=(?\\d+)", Pattern.CASE_INSENSITIVE); + try { + String query = new URIBuilder(uri).build().getQuery(); + Matcher matcher = pageInQueryPattern.matcher(query); + return matcher.find() ? Optional.of(Integer.parseInt(matcher.group("page"))) : Optional.empty(); + } catch (Exception e) { + return Optional.empty(); + } } } diff --git a/src/main/java/com/spotify/github/v3/clients/IssueClient.java b/src/main/java/com/spotify/github/v3/clients/IssueClient.java index 1d965d27..7db3a41b 100644 --- a/src/main/java/com/spotify/github/v3/clients/IssueClient.java +++ b/src/main/java/com/spotify/github/v3/clients/IssueClient.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,12 +20,15 @@ package com.spotify.github.v3.clients; -import static com.spotify.github.v3.clients.GitHubClient.IGNORE_RESPONSE_CONSUMER; -import static com.spotify.github.v3.clients.GitHubClient.LIST_COMMENT_TYPE_REFERENCE; +import static com.spotify.github.v3.clients.GitHubClient.*; import com.google.common.collect.ImmutableMap; import com.spotify.github.async.AsyncPage; +import com.spotify.github.http.HttpResponse; import com.spotify.github.v3.comment.Comment; +import com.spotify.github.v3.comment.CommentReaction; +import com.spotify.github.v3.comment.CommentReactionContent; +import com.spotify.github.v3.issues.Issue; import java.lang.invoke.MethodHandles; import java.util.Iterator; import java.util.concurrent.CompletableFuture; @@ -35,94 +38,282 @@ /** Issue API client */ public class IssueClient { + // URI templates for various API endpoints static final String COMMENTS_URI_NUMBER_TEMPLATE = "/repos/%s/%s/issues/%s/comments"; static final String COMMENTS_URI_TEMPLATE = "/repos/%s/%s/issues/comments"; static final String COMMENTS_URI_ID_TEMPLATE = "/repos/%s/%s/issues/comments/%s"; + static final String COMMENTS_REACTION_TEMPLATE = "/repos/%s/%s/issues/comments/%s/reactions"; + static final String COMMENTS_REACTION_ID_TEMPLATE = "/repos/%s/%s/issues/comments/%s/reactions/%s"; + static final String ISSUES_REACTION_TEMPLATE = "/repos/%s/%s/issues/%s/reactions"; + static final String ISSUES_REACTION_ID_TEMPLATE = "/repos/%s/%s/issues/%s/reactions/%s"; + static final String ISSUES_URI_ID_TEMPLATE = "/repos/%s/%s/issues/%s"; private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private final GitHubClient github; private final String owner; private final String repo; + /** + * Constructs an IssueClient. + * @param github the GitHub client + * @param owner the repository owner + * @param repo the repository name + */ IssueClient(final GitHubClient github, final String owner, final String repo) { this.github = github; this.owner = owner; this.repo = repo; } + /** + * Creates an IssueClient. + * + * @param github the GitHub client + * @param owner the repository owner + * @param repo the repository name + * @return a new IssueClient instance + */ static IssueClient create(final GitHubClient github, final String owner, final String repo) { return new IssueClient(github, owner, repo); } /** - * List repository comments. + * Lists repository comments. * - * @return comments + * @return an iterator of asynchronous pages of comments */ public Iterator> listComments() { return listComments(String.format(COMMENTS_URI_TEMPLATE, owner, repo)); } /** - * List given issue number comments. + * Lists comments for a given issue number. * - * @param number issue number - * @return comments + * @param issueNumber the issue number + * @return an iterator of asynchronous pages of comments */ - public Iterator> listComments(final int number) { - return listComments(String.format(COMMENTS_URI_NUMBER_TEMPLATE, owner, repo, number)); + public Iterator> listComments(final long issueNumber) { + return listComments(String.format(COMMENTS_URI_NUMBER_TEMPLATE, owner, repo, issueNumber)); } /** - * Get a specific comment. + * Lists comments for a given issue number. * - * @param id comment id - * @return a comment + * @deprecated Use {@link #listComments(long)} instead + * @param issueNumber the issue number + * @return an iterator of asynchronous pages of comments */ - public CompletableFuture getComment(final int id) { - final String path = String.format(COMMENTS_URI_ID_TEMPLATE, owner, repo, id); + @Deprecated + public Iterator> listComments(final int issueNumber) { + return listComments((long) issueNumber); + } + + /** + * Gets a specific comment. + * + * @param commentId the comment id + * @return a CompletableFuture containing the comment + */ + public CompletableFuture getComment(final long commentId) { + final String path = String.format(COMMENTS_URI_ID_TEMPLATE, owner, repo, commentId); log.info("Fetching issue comments from " + path); return github.request(path, Comment.class); } /** - * Create a comment for a given issue number. + * Gets a specific comment. + * + * @deprecated Use {@link #getComment(long)} instead + * @param commentId the comment id + * @return a CompletableFuture containing the comment + */ + @Deprecated + public CompletableFuture getComment(final int commentId) { + return getComment((long) commentId); + } + + /** + * Creates a comment for a given issue number. * - * @param number issue number - * @param body comment content - * @return the Comment that was just created + * @param issueNumber the issue number + * @param body the comment content + * @return a CompletableFuture containing the created comment */ - public CompletableFuture createComment(final int number, final String body) { - final String path = String.format(COMMENTS_URI_NUMBER_TEMPLATE, owner, repo, number); + public CompletableFuture createComment(final long issueNumber, final String body) { + final String path = String.format(COMMENTS_URI_NUMBER_TEMPLATE, owner, repo, issueNumber); final String requestBody = github.json().toJsonUnchecked(ImmutableMap.of("body", body)); return github.post(path, requestBody, Comment.class); } /** - * Edit a specific comment. + * Creates a comment for a given issue number. + * + * @deprecated Use {@link #createComment(long, String)} instead + * @param issueNumber the issue number + * @param body the comment content + * @return a CompletableFuture containing the created comment + */ + @Deprecated + public CompletableFuture createComment(final int issueNumber, final String body) { + return createComment((long) issueNumber, body); + } + + /** + * Edits a specific comment. * - * @param id comment id - * @param body new comment content + * @param commentId the comment id + * @param body the new comment content + * @return a CompletableFuture representing the completion of the operation */ - public CompletableFuture editComment(final int id, final String body) { - final String path = String.format(COMMENTS_URI_ID_TEMPLATE, owner, repo, id); + public CompletableFuture editComment(final long commentId, final String body) { + final String path = String.format(COMMENTS_URI_ID_TEMPLATE, owner, repo, commentId); return github .patch(path, github.json().toJsonUnchecked(ImmutableMap.of("body", body))) .thenAccept(IGNORE_RESPONSE_CONSUMER); } /** - * Delete a comment. + * Edits a specific comment. * - * @param id comment id + * @deprecated Use {@link #editComment(long, String)} instead + * @param commentId the comment id + * @param body the new comment content + * @return a CompletableFuture representing the completion of the operation */ - public CompletableFuture deleteComment(final int id) { + @Deprecated + public CompletableFuture editComment(final int commentId, final String body) { + return editComment((long) commentId, body); + } + + /** + * Deletes a comment. + * + * @param commentId the comment id + * @return a CompletableFuture representing the completion of the operation + */ + public CompletableFuture deleteComment(final long commentId) { return github - .delete(String.format(COMMENTS_URI_ID_TEMPLATE, owner, repo, id)) + .delete(String.format(COMMENTS_URI_ID_TEMPLATE, owner, repo, commentId)) .thenAccept(IGNORE_RESPONSE_CONSUMER); } + /** + * Deletes a comment. + * + * @deprecated Use {@link #deleteComment(long)} instead + * @param commentId the comment id + * @return a CompletableFuture representing the completion of the operation + */ + @Deprecated + public CompletableFuture deleteComment(final int commentId) { + return deleteComment((long) commentId); + } + + /** + * Lists comments for a given path. + * + * @param path the API endpoint path + * @return an iterator of asynchronous pages of comments + */ private Iterator> listComments(final String path) { return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_COMMENT_TYPE_REFERENCE)); } + + /** + * Gets an issue by id. + * + * @param issueId the issue id + * @return a CompletableFuture containing the issue + */ + public CompletableFuture getIssue(final long issueId) { + return github.request(String.format(ISSUES_URI_ID_TEMPLATE, owner, repo, issueId), Issue.class); + } + + /** + * Gets an issue by id. + * + * @deprecated Use {@link #getIssue(long)} instead + * @param issueId the issue id + * @return a CompletableFuture containing the issue + */ + @Deprecated + public CompletableFuture getIssue(final int issueId) { + return getIssue((long) issueId); + } + + /** + * Creates a reaction on a comment. + * + * @param commentId the comment id + * @param reaction the reaction content + * @return a CompletableFuture containing the created reaction + */ + public CompletableFuture createCommentReaction( + final long commentId, final CommentReactionContent reaction) { + final String path = String.format(COMMENTS_REACTION_TEMPLATE, owner, repo, commentId); + final String requestBody = + github.json().toJsonUnchecked(ImmutableMap.of("content", reaction.toString())); + return github.post(path, requestBody, CommentReaction.class); + } + + /** + * Deletes a reaction on a comment. See List + * reactions for an issue comment + * + * @param commentId the comment id + * @param reactionId the reaction id + * @return a CompletableFuture containing the HTTP response + */ + public CompletableFuture deleteCommentReaction( + final long commentId, final long reactionId) { + final String path = + String.format(COMMENTS_REACTION_ID_TEMPLATE, owner, repo, commentId, reactionId); + return github.delete(path); + } + + /** + * Lists reactions on a comment. See List + * reactions for an issue comment + * + * @param commentId the comment id + * @return an iterator of asynchronous pages of comment reactions + */ + public GithubPageIterator listCommentReaction(final long commentId) { + final String path = String.format(COMMENTS_REACTION_TEMPLATE, owner, repo, commentId); + return new GithubPageIterator<>( + new GithubPage<>(github, path, LIST_COMMENT_REACTION_TYPE_REFERENCE)); + } + + /** + * Creates a reaction on an issue. + * + * @param issueNumber the issue number + * @param reaction the reaction content + * @return a CompletableFuture containing the created reaction + */ + public CompletableFuture createIssueReaction( + final long issueNumber, final CommentReactionContent reaction) { + final String path = String.format(ISSUES_REACTION_TEMPLATE, owner, repo, issueNumber); + final String requestBody = + github.json().toJsonUnchecked(ImmutableMap.of("content", reaction.toString())); + return github.post(path, requestBody, CommentReaction.class); + } + + /** + * Deletes a reaction on an issue. See Delete + * an issue reaction + * + * @param issueNumber the issue number + * @param reactionId the reaction id + * @return a CompletableFuture containing the HTTP response + */ + public CompletableFuture deleteIssueReaction( + final long issueNumber, final long reactionId) { + final String path = + String.format(ISSUES_REACTION_ID_TEMPLATE, owner, repo, issueNumber, reactionId); + return github.delete(path); + } } diff --git a/src/main/java/com/spotify/github/v3/clients/JwtTokenIssuer.java b/src/main/java/com/spotify/github/v3/clients/JwtTokenIssuer.java index 90cfc765..8746eee2 100644 --- a/src/main/java/com/spotify/github/v3/clients/JwtTokenIssuer.java +++ b/src/main/java/com/spotify/github/v3/clients/JwtTokenIssuer.java @@ -22,8 +22,7 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import java.io.File; -import java.io.IOException; + import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -31,7 +30,6 @@ import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Date; -import org.apache.commons.io.FileUtils; /** The helper Jwt token issuer. */ public class JwtTokenIssuer { @@ -45,20 +43,6 @@ private JwtTokenIssuer(final PrivateKey signingKey) { this.signingKey = signingKey; } - /** - * Instantiates a new Jwt token issuer. - * - * @param privateKeyFile the private key file - * @throws NoSuchAlgorithmException the no such algorithm exception - * @throws InvalidKeySpecException the invalid key spec exception - * @throws IOException the io exception - */ - public static JwtTokenIssuer fromFile(final File privateKeyFile) - throws NoSuchAlgorithmException, InvalidKeySpecException, IOException { - byte[] apiKeySecretBytes = FileUtils.readFileToByteArray(privateKeyFile); - return fromPrivateKey(apiKeySecretBytes); - } - /** * Instantiates a new Jwt token issuer. * diff --git a/src/main/java/com/spotify/github/v3/clients/OrganisationClient.java b/src/main/java/com/spotify/github/v3/clients/OrganisationClient.java new file mode 100644 index 00000000..a1270b4e --- /dev/null +++ b/src/main/java/com/spotify/github/v3/clients/OrganisationClient.java @@ -0,0 +1,90 @@ +///*- +// * -\-\- +// * github-api +// * -- +// * Copyright (C) 2016 - 2020 Spotify AB +// * -- +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// * -/-/- +// */ +// +package com.spotify.github.v3.clients; + +import com.spotify.github.v3.orgs.OrgMembership; +import com.spotify.github.v3.orgs.requests.OrgMembershipCreate; +import java.lang.invoke.MethodHandles; +import java.util.concurrent.CompletableFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OrganisationClient { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final String MEMBERSHIP_TEMPLATE = "/orgs/%s/memberships/%s"; + + private final GitHubClient github; + + private final String org; + + OrganisationClient(final GitHubClient github, final String org) { + this.github = github; + this.org = org; + } + + static OrganisationClient create(final GitHubClient github, final String org) { + return new OrganisationClient(github, org); + } + + /** + * Create a Teams API client. + * + * @return Teams API client + */ + public TeamClient createTeamClient() { + return TeamClient.create(github, org); + } + + /** + * Create GitHub App API client + * + * @return GitHub App API client + */ + public GithubAppClient createGithubAppClient() { + return new GithubAppClient(github, org); + } + + /** + * Get an org membership of a user. + * + * @param username username of the org member + * @return membership + */ + public CompletableFuture getOrgMembership(final String username) { + final String path = String.format(MEMBERSHIP_TEMPLATE, org, username); + log.debug("Fetching org membership for: " + path); + return github.request(path, OrgMembership.class); + } + + /** + * Add or update an org membership for a user. + * + * @param request update org membership request + * @return membership + */ + public CompletableFuture updateOrgMembership(final OrgMembershipCreate request, final String username) { + final String path = String.format(MEMBERSHIP_TEMPLATE, org, username); + log.debug("Updating membership in org: " + path); + return github.put(path, github.json().toJsonUnchecked(request), OrgMembership.class); + } +} diff --git a/src/main/java/com/spotify/github/v3/clients/PullRequestClient.java b/src/main/java/com/spotify/github/v3/clients/PullRequestClient.java index 00ed9974..36d2783e 100644 --- a/src/main/java/com/spotify/github/v3/clients/PullRequestClient.java +++ b/src/main/java/com/spotify/github/v3/clients/PullRequestClient.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,21 +22,39 @@ import static com.spotify.github.v3.clients.GitHubClient.IGNORE_RESPONSE_CONSUMER; import static com.spotify.github.v3.clients.GitHubClient.LIST_COMMIT_TYPE_REFERENCE; +import static com.spotify.github.v3.clients.GitHubClient.LIST_FILE_ITEMS; +import static com.spotify.github.v3.clients.GitHubClient.LIST_PR_COMMENT_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_PR_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_REVIEW_REQUEST_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_REVIEW_TYPE_REFERENCE; +import static java.lang.Math.toIntExact; +import static java.util.Objects.isNull; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; import com.spotify.github.async.AsyncPage; -import com.spotify.github.v3.prs.*; +import com.spotify.github.jackson.Json; +import com.spotify.github.v3.git.FileItem; +import com.spotify.github.v3.prs.Comment; +import com.spotify.github.v3.prs.MergeParameters; +import com.spotify.github.v3.prs.PullRequest; +import com.spotify.github.v3.prs.PullRequestItem; +import com.spotify.github.v3.prs.RequestReviewParameters; +import com.spotify.github.v3.prs.Review; +import com.spotify.github.v3.prs.ReviewParameters; +import com.spotify.github.v3.prs.ReviewRequests; import com.spotify.github.v3.prs.requests.PullRequestCreate; import com.spotify.github.v3.prs.requests.PullRequestParameters; import com.spotify.github.v3.prs.requests.PullRequestUpdate; import com.spotify.github.v3.repos.CommitItem; +import java.io.InputStreamReader; +import java.io.Reader; import java.lang.invoke.MethodHandles; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; +import javax.ws.rs.core.HttpHeaders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,7 +66,14 @@ public class PullRequestClient { private static final String PR_NUMBER_TEMPLATE = "/repos/%s/%s/pulls/%s"; private static final String PR_COMMITS_TEMPLATE = "/repos/%s/%s/pulls/%s/commits"; private static final String PR_REVIEWS_TEMPLATE = "/repos/%s/%s/pulls/%s/reviews"; - private static final String PR_REVIEW_REQUESTS_TEMPLATE = "/repos/%s/%s/pulls/%s/requested_reviewers"; + private static final String PR_REVIEW_COMMENTS_TEMPLATE = + "/repos/%s/%s/pulls/%s/reviews/%s/comments"; + private static final String PR_COMMENTS_TEMPLATE = "/repos/%s/%s/pulls/%s/comments"; + private static final String PR_CHANGED_FILES_TEMPLATE = "/repos/%s/%s/pulls/%s/files"; + private static final String PR_REVIEW_REQUESTS_TEMPLATE = + "/repos/%s/%s/pulls/%s/requested_reviewers"; + private static final String PR_COMMENT_REPLIES_TEMPLATE = + "/repos/%s/%s/pulls/%s/comments/%s/replies"; private final GitHubClient github; private final String owner; @@ -89,11 +114,23 @@ public CompletableFuture> list(final PullRequestParameters /** * Get a specific pull request. * - * @param number pull request number + * @deprecated Use {@link #get(long)} instead + * @param prNumber pull request number * @return pull request */ - public CompletableFuture get(final int number) { - final String path = String.format(PR_NUMBER_TEMPLATE, owner, repo, number); + @Deprecated + public CompletableFuture get(final int prNumber) { + return get((long) prNumber); + } + + /** + * Get a specific pull request. + * + * @param prNumber pull request number + * @return pull request + */ + public CompletableFuture get(final long prNumber) { + final String path = String.format(PR_NUMBER_TEMPLATE, owner, repo, prNumber); log.debug("Fetching pull request from " + path); return github.request(path, PullRequest.class); } @@ -102,74 +139,151 @@ public CompletableFuture get(final int number) { * Create a pull request. * * @param request create request + * @return pull request */ - public CompletableFuture create(final PullRequestCreate request) { + public CompletableFuture create(final PullRequestCreate request) { final String path = String.format(PR_TEMPLATE, owner, repo); - return github - .post(path, github.json().toJsonUnchecked(request)) - .thenAccept(IGNORE_RESPONSE_CONSUMER); + return github.post(path, github.json().toJsonUnchecked(request), PullRequest.class); } /** * Update given pull request. * - * @param number pull request number + * @deprecated Use {@link #update(long, PullRequestUpdate)} instead + * @param prNumber pull request number * @param request update request + * @return pull request */ - public CompletableFuture update(final int number, final PullRequestUpdate request) { - final String path = String.format(PR_NUMBER_TEMPLATE, owner, repo, number); - return github - .patch(path, github.json().toJsonUnchecked(request)) - .thenAccept(IGNORE_RESPONSE_CONSUMER); + @Deprecated + public CompletableFuture update( + final int prNumber, final PullRequestUpdate request) { + return update((long) prNumber, request); + } + + /** + * Update given pull request. + * + * @param prNumber pull request number + * @param request update request + * @return pull request + */ + public CompletableFuture update( + final long prNumber, final PullRequestUpdate request) { + final String path = String.format(PR_NUMBER_TEMPLATE, owner, repo, prNumber); + return github.patch(path, github.json().toJsonUnchecked(request), PullRequest.class); + } + + /** + * List pull request commits. + * + * @deprecated Use {@link #listCommits(long)} instead + * @param prNumber pull request number + * @return commits + */ + @Deprecated + public CompletableFuture> listCommits(final int prNumber) { + return listCommits((long) prNumber); } /** * List pull request commits. * - * @param number pull request number + * @param prNumber pull request number * @return commits */ - public CompletableFuture> listCommits(final int number) { - final String path = String.format(PR_COMMITS_TEMPLATE, owner, repo, number); + public CompletableFuture> listCommits(final long prNumber) { + final String path = String.format(PR_COMMITS_TEMPLATE, owner, repo, prNumber); log.debug("Fetching pull request commits from " + path); - return github.request(path, LIST_COMMIT_TYPE_REFERENCE); + return github + .request(path) + .thenApply( + response -> + Json.create() + .fromJsonUncheckedNotNull(response.bodyString(), LIST_COMMIT_TYPE_REFERENCE)); + } + + public Iterator> listCommits(final long prNumber, final int itemsPerPage) { + final String path = String.format(PR_COMMITS_TEMPLATE, owner, repo, prNumber); + + return new GithubPageIterator<>( + new GithubPage<>(github, path, LIST_COMMIT_TYPE_REFERENCE, itemsPerPage)); } /** * List pull request reviews. Reviews are returned in chronological order. * - * @param number pull request number + * @deprecated Use {@link #listReviews(long)} instead + * @param prNumber pull request number * @return list of reviews */ - public CompletableFuture> listReviews(final int number) { - final String path = String.format(PR_REVIEWS_TEMPLATE, owner, repo, number); - log.debug("Fetching pull request reviews from " + path); - return github.request(path, LIST_REVIEW_TYPE_REFERENCE); + @Deprecated + public CompletableFuture> listReviews(final int prNumber) { + return listReviews((long) prNumber); + } + + /** + * List pull request reviews. Reviews are returned in chronological order. + * + * @param prNumber pull request number + * @return list of reviews + */ + public CompletableFuture> listReviews(final long prNumber) { + final String path = String.format(PR_REVIEWS_TEMPLATE, owner, repo, prNumber); + log.debug("Fetching pull request reviews from " + path); + return github.request(path, LIST_REVIEW_TYPE_REFERENCE); + } + + /** + * List pull request reviews paginated. Reviews are returned in chronological order. + * + * @deprecated Use {@link #listReviews(long,long)} instead + * @param prNumber pull request number + * @param itemsPerPage prNumber of items per page + * @return iterator of reviews + */ + @Deprecated + public Iterator> listReviews(final int prNumber, final int itemsPerPage) { + return listReviews((long) prNumber, itemsPerPage); } /** * List pull request reviews paginated. Reviews are returned in chronological order. * - * @param number pull request number - * @param itemsPerPage number of items per page + * @param prNumber pull request number + * @param itemsPerPage prNumber of items per page * @return iterator of reviews */ - public Iterator> listReviews(final int number, final int itemsPerPage) { - // FIXME Use itemsPerPage property - final String path = String.format(PR_REVIEWS_TEMPLATE, owner, repo, number); + public Iterator> listReviews(final long prNumber, final long itemsPerPage) { + final String path = String.format(PR_REVIEWS_TEMPLATE, owner, repo, prNumber); log.debug("Fetching pull request reviews from " + path); - return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_REVIEW_TYPE_REFERENCE)); + return new GithubPageIterator<>( + new GithubPage<>(github, path, LIST_REVIEW_TYPE_REFERENCE, toIntExact(itemsPerPage))); } /** * Creates a review for a pull request. * - * @param number pull request number + * @deprecated Use {@link #createReview(long,ReviewParameters)} instead + * @param prNumber pull request number * @param properties properties for reviewing the PR, such as sha, body and event * @see "https://developer.github.com/v3/pulls/reviews/#create-a-review-for-a-pull-request" */ - public CompletableFuture createReview(final int number, final ReviewParameters properties) { - final String path = String.format(PR_REVIEWS_TEMPLATE, owner, repo, number); + @Deprecated + public CompletableFuture createReview( + final int prNumber, final ReviewParameters properties) { + return createReview((long) prNumber, properties); + } + + /** + * Creates a review for a pull request. + * + * @param prNumber pull request number + * @param properties properties for reviewing the PR, such as sha, body and event + * @see "https://developer.github.com/v3/pulls/reviews/#create-a-review-for-a-pull-request" + */ + public CompletableFuture createReview( + final long prNumber, final ReviewParameters properties) { + final String path = String.format(PR_REVIEWS_TEMPLATE, owner, repo, prNumber); final String jsonPayload = github.json().toJsonUnchecked(properties); log.debug("Creating review for PR: " + path); return github.post(path, jsonPayload, Review.class); @@ -178,11 +292,23 @@ public CompletableFuture createReview(final int number, final ReviewPara /** * List pull request requested reviews. * - * @param number pull request number + * @deprecated Use {@link #listReviewRequests(long)} instead + * @param prNumber pull request number * @return list of reviews */ - public CompletableFuture listReviewRequests(final int number) { - final String path = String.format(PR_REVIEW_REQUESTS_TEMPLATE, owner, repo, number); + @Deprecated + public CompletableFuture listReviewRequests(final int prNumber) { + return listReviewRequests((long) prNumber); + } + + /** + * List pull request requested reviews. + * + * @param prNumber pull request number + * @return list of reviews + */ + public CompletableFuture listReviewRequests(final long prNumber) { + final String path = String.format(PR_REVIEW_REQUESTS_TEMPLATE, owner, repo, prNumber); log.debug("Fetching pull request requested reviews from " + path); return github.request(path, LIST_REVIEW_REQUEST_TYPE_REFERENCE); } @@ -190,12 +316,27 @@ public CompletableFuture listReviewRequests(final int number) { /** * Requests a review for a pull request. * - * @param number pull request number + * @deprecated Use {@link #requestReview(long,RequestReviewParameters)} instead + * @param prNumber pull request number + * @param properties properties for reviewing the PR, such as reviewers and team_reviewers. + * @see "https://docs.github.com/en/rest/reference/pulls#request-reviewers-for-a-pull-request" + */ + @Deprecated + public CompletableFuture requestReview( + final int prNumber, final RequestReviewParameters properties) { + return requestReview((long) prNumber, properties); + } + + /** + * Requests a review for a pull request. + * + * @param prNumber pull request number * @param properties properties for reviewing the PR, such as reviewers and team_reviewers. * @see "https://docs.github.com/en/rest/reference/pulls#request-reviewers-for-a-pull-request" */ - public CompletableFuture requestReview(final int number, final RequestReviewParameters properties) { - final String path = String.format(PR_REVIEW_REQUESTS_TEMPLATE, owner, repo, number); + public CompletableFuture requestReview( + final long prNumber, final RequestReviewParameters properties) { + final String path = String.format(PR_REVIEW_REQUESTS_TEMPLATE, owner, repo, prNumber); final String jsonPayload = github.json().toJsonUnchecked(properties); log.debug("Requesting reviews for PR: " + path); return github.post(path, jsonPayload, PullRequest.class); @@ -204,12 +345,27 @@ public CompletableFuture requestReview(final int number, final Requ /** * Remove a request for review for a pull request. * - * @param number pull request number + * @deprecated Use {@link #removeRequestedReview(long,RequestReviewParameters)} instead + * @param prNumber pull request number + * @param properties properties for reviewing the PR, such as reviewers and team_reviewers. + * @see "https://docs.github.com/en/rest/reference/pulls#request-reviewers-for-a-pull-request" + */ + @Deprecated + public CompletableFuture removeRequestedReview( + final int prNumber, final RequestReviewParameters properties) { + return removeRequestedReview((long) prNumber, properties); + } + + /** + * Remove a request for review for a pull request. + * + * @param prNumber pull request number * @param properties properties for reviewing the PR, such as reviewers and team_reviewers. * @see "https://docs.github.com/en/rest/reference/pulls#request-reviewers-for-a-pull-request" */ - public CompletableFuture removeRequestedReview(final int number, final RequestReviewParameters properties) { - final String path = String.format(PR_REVIEW_REQUESTS_TEMPLATE, owner, repo, number); + public CompletableFuture removeRequestedReview( + final long prNumber, final RequestReviewParameters properties) { + final String path = String.format(PR_REVIEW_REQUESTS_TEMPLATE, owner, repo, prNumber); final String jsonPayload = github.json().toJsonUnchecked(properties); log.debug("Removing requested reviews for PR: " + path); return github.delete(path, jsonPayload).thenAccept(IGNORE_RESPONSE_CONSUMER); @@ -218,21 +374,157 @@ public CompletableFuture removeRequestedReview(final int number, final Req /** * Merges a pull request. * - * @param number pull request number + * @deprecated Use {@link #merge(long,MergeParameters)} instead + * @param prNumber pull request number + * @param properties the properties on merging the PR, such as title, message and sha + * @see "https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button" + */ + @Deprecated + public CompletableFuture merge(final int prNumber, final MergeParameters properties) { + return merge((long) prNumber, properties); + } + + /** + * Merges a pull request. + * + * @param prNumber pull request number * @param properties the properties on merging the PR, such as title, message and sha * @see "https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button" */ - public CompletableFuture merge(final int number, final MergeParameters properties) { - final String path = String.format(PR_NUMBER_TEMPLATE + "/merge", owner, repo, number); + public CompletableFuture merge(final long prNumber, final MergeParameters properties) { + final String path = String.format(PR_NUMBER_TEMPLATE + "/merge", owner, repo, prNumber); final String jsonPayload = github.json().toJsonUnchecked(properties); log.debug("Merging pr, running: {}", path); return github.put(path, jsonPayload).thenAccept(IGNORE_RESPONSE_CONSUMER); } + /** + * Fetches a pull request patch. + * + * @deprecated Use {@link #patch(long)} instead + * @param prNumber pull request number + * @return reader for the patch + */ + @Deprecated + public CompletableFuture patch(final int prNumber) { + return patch((long) prNumber); + } + + /** + * Fetches a pull request patch. + * + * @param prNumber pull request number + * @return reader for the patch + */ + public CompletableFuture patch(final long prNumber) { + final String path = String.format(PR_NUMBER_TEMPLATE, owner, repo, prNumber); + final Map extraHeaders = + ImmutableMap.of(HttpHeaders.ACCEPT, "application/vnd.github.patch"); + log.debug("Fetching pull request patch from " + path); + return github + .request(path, extraHeaders) + .thenApply( + response -> { + final var body = response.body(); + if (isNull(body)) { + return Reader.nullReader(); + } + return new InputStreamReader(body); + }); + } + + /** + * Fetches a pull request diff. + * + * @deprecated Use {@link #diff(long)} instead + * @param prNumber pull request number + * @return reader for the diff + */ + @Deprecated + public CompletableFuture diff(final int prNumber) { + return diff((long) prNumber); + } + + /** + * Fetches a pull request diff. + * + * @param prNumber pull request number + * @return reader for the diff + */ + public CompletableFuture diff(final long prNumber) { + final String path = String.format(PR_NUMBER_TEMPLATE, owner, repo, prNumber); + final Map extraHeaders = + ImmutableMap.of(HttpHeaders.ACCEPT, "application/vnd.github.diff"); + log.debug("Fetching pull diff from " + path); + return github + .request(path, extraHeaders) + .thenApply( + response -> { + final var body = response.body(); + if (isNull(body)) { + return Reader.nullReader(); + } + return new InputStreamReader(body); + }); + } + + public Iterator> changedFiles(final long prNumber) { + final String path = String.format(PR_CHANGED_FILES_TEMPLATE, owner, repo, prNumber); + return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_FILE_ITEMS)); + } + + /** + * List pull requests using given parameters. + * + * @param parameterPath request parameters + * @return pull requests + */ private CompletableFuture> list(final String parameterPath) { final String path = String.format(PR_TEMPLATE + parameterPath, owner, repo); log.debug("Fetching pull requests from " + path); return github.request(path, LIST_PR_TYPE_REFERENCE); } + /** + * List pull request review comments. + * + * @param prNumber pull request number + * @return iterator of comments + */ + public Iterator> listComments(final long prNumber) { + final String path = String.format(PR_COMMENTS_TEMPLATE, owner, repo, prNumber); + return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_PR_COMMENT_TYPE_REFERENCE)); + } + + /** + * Creates a reply to a pull request review comment. + * + * @param prNumber pull request number + * @param commentId the ID of the comment to reply to + * @param body the reply message + * @return the created comment + * @see "https://docs.github.com/en/rest/pulls/comments#create-a-reply-for-a-review-comment" + */ + public CompletableFuture createCommentReply( + final long prNumber, final long commentId, final String body) { + final String path = + String.format(PR_COMMENT_REPLIES_TEMPLATE, owner, repo, prNumber, commentId); + final Map payload = ImmutableMap.of("body", body); + final String jsonPayload = github.json().toJsonUnchecked(payload); + log.debug("Creating reply to PR comment: " + path); + return github.post(path, jsonPayload, Comment.class); + } + + /** + * List pull request review comments for a specific review with pagination. + * + * @param prNumber pull request number + * @param reviewId the ID of the review + * @return iterator of comments for the review + * @see "https://docs.github.com/en/rest/pulls/comments#list-comments-for-a-pull-request-review" + */ + public Iterator> listReviewComments(final long prNumber, final long reviewId) { + final String path = String.format(PR_REVIEW_COMMENTS_TEMPLATE, owner, repo, prNumber, reviewId); + return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_PR_COMMENT_TYPE_REFERENCE)); + } } diff --git a/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java b/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java index 5039efc9..a80e46e1 100644 --- a/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java +++ b/src/main/java/com/spotify/github/v3/clients/RepositoryClient.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,11 +21,13 @@ package com.spotify.github.v3.clients; import static com.spotify.github.v3.clients.GitHubClient.IGNORE_RESPONSE_CONSUMER; +import static com.spotify.github.v3.clients.GitHubClient.LIST_BRANCHES; import static com.spotify.github.v3.clients.GitHubClient.LIST_COMMIT_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_FOLDERCONTENT_TYPE_REFERENCE; -import static com.spotify.github.v3.clients.GitHubClient.LIST_STATUS_TYPE_REFERENCE; -import static com.spotify.github.v3.clients.GitHubClient.LIST_BRANCHES; +import static com.spotify.github.v3.clients.GitHubClient.LIST_PR_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_REPOSITORY; +import static com.spotify.github.v3.clients.GitHubClient.LIST_STATUS_TYPE_REFERENCE; +import static com.spotify.github.v3.clients.GitHubClient.LIST_REPOSITORY_INVITATION; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; @@ -34,24 +36,30 @@ import com.spotify.github.v3.exceptions.RequestNotOkException; import com.spotify.github.v3.git.Tree; import com.spotify.github.v3.hooks.requests.WebhookCreate; +import com.spotify.github.v3.prs.PullRequestItem; import com.spotify.github.v3.repos.Branch; import com.spotify.github.v3.repos.Commit; import com.spotify.github.v3.repos.CommitComparison; import com.spotify.github.v3.repos.CommitItem; import com.spotify.github.v3.repos.CommitStatus; +import com.spotify.github.v3.repos.CommitWithFolderContent; import com.spotify.github.v3.repos.Content; +import com.spotify.github.v3.repos.requests.*; import com.spotify.github.v3.repos.FolderContent; import com.spotify.github.v3.repos.Languages; import com.spotify.github.v3.repos.Repository; +import com.spotify.github.v3.repos.RepositoryInvitation; import com.spotify.github.v3.repos.Status; -import com.spotify.github.v3.repos.requests.RepositoryCreateStatus; -import com.spotify.github.v3.repos.requests.AuthenticatedUserRepositoriesFilter; + +import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import javax.ws.rs.core.HttpHeaders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,19 +77,26 @@ public class RepositoryClient { public static final String STATUS_URI_TEMPLATE = "/repos/%s/%s/statuses/%s"; private static final String COMMITS_URI_TEMPLATE = "/repos/%s/%s/commits"; private static final String COMMIT_SHA_URI_TEMPLATE = "/repos/%s/%s/commits/%s"; + private static final String COMMIT_PULL_REQUESTS_SHA_URI_TEMPLATE = + "/repos/%s/%s/commits/%s/pulls"; private static final String COMMIT_STATUS_URI_TEMPLATE = "/repos/%s/%s/commits/%s/status"; private static final String TREE_SHA_URI_TEMPLATE = "/repos/%s/%s/git/trees/%s"; private static final String COMPARE_COMMIT_TEMPLATE = "/repos/%s/%s/compare/%s...%s"; private static final String BRANCH_TEMPLATE = "/repos/%s/%s/branches/%s"; private static final String LIST_BRANCHES_TEMPLATE = "/repos/%s/%s/branches"; private static final String CREATE_COMMENT_TEMPLATE = "/repos/%s/%s/commits/%s/comments"; + private static final String CREATE_REPOSITORY_DISPATCH_EVENT_TEMPLATE = "/repos/%s/%s/dispatches"; private static final String COMMENT_TEMPLATE = "/repos/%s/%s/comments/%s"; private static final String LANGUAGES_TEMPLATE = "/repos/%s/%s/languages"; private static final String MERGE_TEMPLATE = "/repos/%s/%s/merges"; private static final String FORK_TEMPLATE = "/repos/%s/%s/forks"; private static final String LIST_REPOSITORY_TEMPLATE = "/orgs/%s/repos"; private static final String LIST_REPOSITORIES_FOR_AUTHENTICATED_USER = "/user/repos"; - private static final String IS_USER_COLLABORATOR_OF_REPO = "/repos/%s/%s/collaborators/%s"; + private static final String REPOSITORY_COLLABORATOR = "/repos/%s/%s/collaborators/%s"; + private static final String REPOSITORY_INVITATION = "/repos/%s/%s/invitations/%s"; + private static final String REPOSITORY_INVITATIONS = "/repos/%s/%s/invitations"; + private static final String REPOSITORY_DOWNLOAD_TARBALL = "/repos/%s/%s/tarball/%s"; + private static final String REPOSITORY_DOWNLOAD_ZIPBALL = "/repos/%s/%s/zipball/%s"; private final String owner; private final String repo; private final GitHubClient github; @@ -135,6 +150,15 @@ public ChecksClient createChecksApiClient() { return new ChecksClient(github, owner, repo); } + /** + * Actions API client + * + * @return Actions API client + */ + public ActionsClient createActionsClient() { + return ActionsClient.create(github, owner, repo); + } + /** * Get information about this repository. * @@ -145,6 +169,18 @@ public CompletableFuture getRepository() { return github.request(path, Repository.class); } + /** + * Update Repository properties + * https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#update-a-repository + * + * @return repository information + */ + public CompletableFuture updateRepository(final RepositoryUpdate repoUpdate) { + final String path = String.format(REPOSITORY_URI_TEMPLATE, owner, repo); + final String data = github.json().toJsonUnchecked(repoUpdate); + return github.patch(path, data, Repository.class); + } + /** * List all repositories in this organization. * @@ -161,9 +197,12 @@ public CompletableFuture> listOrganizationRepositories() { * @param filter filter parameters * @return list of repositories for the authenticated user */ - public Iterator> listAuthenticatedUserRepositories(final AuthenticatedUserRepositoriesFilter filter) { + public Iterator> listAuthenticatedUserRepositories( + final AuthenticatedUserRepositoriesFilter filter) { final String serial = filter.serialize(); - final String path = LIST_REPOSITORIES_FOR_AUTHENTICATED_USER + (Strings.isNullOrEmpty(serial) ? "" : "?" + serial); + final String path = + LIST_REPOSITORIES_FOR_AUTHENTICATED_USER + + (Strings.isNullOrEmpty(serial) ? "" : "?" + serial); return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_REPOSITORY)); } @@ -174,8 +213,102 @@ public Iterator> listAuthenticatedUserRepositories(final A * @return boolean indicating if user is collaborator */ public CompletableFuture isCollaborator(final String user) { - final String path = String.format(IS_USER_COLLABORATOR_OF_REPO, owner, repo, user); - return github.request(path).thenApply(response -> response.code() == NO_CONTENT); + final String path = String.format(REPOSITORY_COLLABORATOR, owner, repo, user); + return github.request(path).thenApply(response -> response.statusCode() == NO_CONTENT); + } + + /** + * Add a collaborator to the repo. + * + * @param user the GitHub username to add + * @param permission the permission level for the user; one of RepositoryPermission, or a custom + * role + * @return + */ + public CompletableFuture> addCollaborator( + final String user, final String permission) { + final String path = String.format(REPOSITORY_COLLABORATOR, owner, repo, user); + final String data = github.json().toJsonUnchecked(Map.of("permission", permission)); + return github + .put(path, data) + .thenApply( + response -> { + // Non-successful statuses result in an RequestNotOkException exception and this code + // not called. + if (response.statusCode() == NO_CONTENT) { + /* + GitHub returns a 204 when: + - an existing collaborator is added as a collaborator + - an organization member is added as an individual collaborator + - an existing team member (whose team is also a repository collaborator) is + added as a collaborator + */ + return Optional.empty(); + } + final RepositoryInvitation invitation = + github + .json() + .fromJsonUnchecked(response.bodyString(), RepositoryInvitation.class); + return Optional.of(invitation); + }); + } + + public CompletableFuture removeCollaborator(final String user) { + final String path = String.format(REPOSITORY_COLLABORATOR, owner, repo, user); + return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); + } + + public CompletableFuture removeInvite(final String invitationId) { + final String path = String.format(REPOSITORY_INVITATION, owner, repo, invitationId); + return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); + } + + public CompletableFuture> listInvitations() { + final String path = String.format(REPOSITORY_INVITATIONS, owner, repo); + return github.request(path, LIST_REPOSITORY_INVITATION); + } + + /** + * Downloads a tar archive of the repository’s default branch (usually main). + * + * @return a CompletableFuture that resolves to an Optional InputStream + */ + public CompletableFuture> downloadTarball() { + return downloadRepository(REPOSITORY_DOWNLOAD_TARBALL, Optional.empty()); + } + + /** + * Downloads a tar archive of the repository. Use :ref to specify a branch or tag to download. + * + * @return a CompletableFuture that resolves to an Optional InputStream + */ + public CompletableFuture> downloadTarball(final String ref) { + return downloadRepository(REPOSITORY_DOWNLOAD_TARBALL, Optional.of(ref)); + } + + /** + * Downloads a zip archive of the repository’s default branch (usually main). + * + * @return a CompletableFuture that resolves to an Optional InputStream + */ + public CompletableFuture> downloadZipball() { + return downloadRepository(REPOSITORY_DOWNLOAD_ZIPBALL, Optional.empty()); + } + + /** + * Downloads a zip archive of the repository. Use :ref to specify a branch or tag to download. + * + * @return a CompletableFuture that resolves to an Optional InputStream + */ + public CompletableFuture> downloadZipball(final String ref) { + return downloadRepository(REPOSITORY_DOWNLOAD_ZIPBALL, Optional.of(ref)); + } + + private CompletableFuture> downloadRepository( + final String path, final Optional maybeRef) { + final var repoRef = maybeRef.orElse(""); + final var repoPath = String.format(path, owner, repo, repoRef); + return github.request(repoPath).thenApply(response -> Optional.ofNullable(response.body())); } /** @@ -202,8 +335,7 @@ public CompletableFuture createWebhook( return null; } - throw new RequestNotOkException( - e1.path(), e1.statusCode(), "Failed creating a webhook: " + request, e); + throw e1; } throw new CompletionException(e); @@ -235,8 +367,8 @@ public CompletableFuture getCommitStatus(final String ref) { } /** - * List statuses for a specific ref. Statuses are returned in reverse chronological order. - * The first status in the list will be the latest one. + * List statuses for a specific ref. Statuses are returned in reverse chronological order. The + * first status in the list will be the latest one. * * @param sha the commit sha to list the statuses for */ @@ -270,6 +402,22 @@ public CompletableFuture> listCommits() { return github.request(path, LIST_COMMIT_TYPE_REFERENCE); } + /** + * List pull requests that contain the given commit. + * + * @param sha commit sha + * @return pull requests + */ + public CompletableFuture> listPullRequestsForCommit(final String sha) { + final String path = String.format(COMMIT_PULL_REQUESTS_SHA_URI_TEMPLATE, owner, repo, sha); + + // As of GHE 3.2, this feature is still in preview, so we need to add the extra header. + // https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/ + final Map extraHeaders = + ImmutableMap.of(HttpHeaders.ACCEPT, "application/vnd.github.groot-preview+json"); + return github.request(path, LIST_PR_TYPE_REFERENCE, extraHeaders); + } + /** * Get a repository commit. * @@ -284,9 +432,11 @@ public CompletableFuture getCommit(final String sha) { /** * Get a repository tree. * + * @deprecated Use {@link com.spotify.github.v3.clients.GitDataClient#getTree(String)} instead * @param sha commit sha * @return tree */ + @Deprecated public CompletableFuture getTree(final String sha) { final String path = String.format(TREE_SHA_URI_TEMPLATE, owner, repo, sha); return github.request(path, Tree.class); @@ -313,6 +463,34 @@ public CompletableFuture getFileContent(final String path, final String return github.request(getContentPath(path, "?ref=" + ref), Content.class); } + /** + * Create a file + * + * @param path path to a file + * @param request file creation request + * @return commit with content + */ + public CompletableFuture createFileContent( + final String path, final FileCreate request) { + final String contentPath = getContentPath(path, ""); + final String requestBody = github.json().toJsonUnchecked(request); + return github.put(contentPath, requestBody, CommitWithFolderContent.class); + } + + /** + * Update file contents + * + * @param path path to a file + * @param request file update request + * @return commit with content + */ + public CompletableFuture updateFileContent( + final String path, final FileUpdate request) { + final String contentPath = getContentPath(path, ""); + final String requestBody = github.json().toJsonUnchecked(request); + return github.put(contentPath, requestBody, CommitWithFolderContent.class); + } + /** * Get repository contents of a folder. * @@ -383,15 +561,26 @@ public CompletableFuture getBranch(final String branch) { } /** - * Get a specific branch. + * List some branches in repository. Doesn't return more than 30 branches. Use {@link + * RepositoryClient#listAllBranches} instead to get all branches. * - * @return list of all branches in repository + * @return list of 30 branches in repository */ public CompletableFuture> listBranches() { final String path = String.format(LIST_BRANCHES_TEMPLATE, owner, repo); return github.request(path, LIST_BRANCHES); } + /** + * List all branches in repository + * + * @return list of all branches in repository + */ + public Iterator> listAllBranches() { + final String path = String.format(LIST_BRANCHES_TEMPLATE, owner, repo); + return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_BRANCHES)); + } + /** * Delete a comment for a given id. * @@ -429,7 +618,6 @@ public CompletableFuture getLanguages() { * Perform a merge. * * @see "https://developer.github.com/enterprise/2.18/v3/repos/merging/" - * * @param base branch name or sha * @param head branch name or sha * @return resulting merge commit, or empty if base already contains the head (nothing to merge) @@ -442,7 +630,6 @@ public CompletableFuture> merge(final String base, final St * Perform a merge. * * @see "https://developer.github.com/enterprise/2.18/v3/repos/merging/" - * * @param base branch name that the head will be merged into * @param head branch name or sha to merge * @param commitMessage commit message to use for the merge commit @@ -464,33 +651,27 @@ public CompletableFuture> merge( // Non-successful statuses result in an RequestNotOkException exception and this code // not being called. - if (response.code() == NO_CONTENT) { + if (response.statusCode() == NO_CONTENT) { // Base already contains the head, nothing to merge return Optional.empty(); } final CommitItem commitItem = - github - .json() - .fromJsonUnchecked( - GitHubClient.responseBodyUnchecked(response), CommitItem.class); + github.json().fromJsonUnchecked(response.bodyString(), CommitItem.class); return Optional.of(commitItem); }); } - /** + /** * Create a fork. * * @see "https://developer.github.com/v3/repos/forks/#create-a-fork" - * * @param organization the organization where the fork will be created * @return resulting repository */ public CompletableFuture createFork(final String organization) { final String path = String.format(FORK_TEMPLATE, owner, repo); final ImmutableMap params = - (organization == null) - ? ImmutableMap.of() - : ImmutableMap.of("organization", organization); + (organization == null) ? ImmutableMap.of() : ImmutableMap.of("organization", organization); final String body = github.json().toJsonUnchecked(params); return github @@ -498,10 +679,7 @@ public CompletableFuture createFork(final String organization) { .thenApply( response -> { final Repository repositoryItem = - github - .json() - .fromJsonUnchecked( - GitHubClient.responseBodyUnchecked(response), Repository.class); + github.json().fromJsonUnchecked(response.bodyString(), Repository.class); return repositoryItem; }); } @@ -512,4 +690,17 @@ private String getContentPath(final String path, final String query) { } return String.format(CONTENTS_URI_TEMPLATE, owner, repo, path, query); } + + /** + * Create a repository_dispatch event. + * + * @param request The repository dispatch request. + */ + public CompletableFuture createRepositoryDispatchEvent( + final RepositoryDispatch request) { + final String path = String.format(CREATE_REPOSITORY_DISPATCH_EVENT_TEMPLATE, owner, repo); + return github + .post(path, github.json().toJsonUnchecked(request)) + .thenApply(response -> response.statusCode() == NO_CONTENT); // should always return a 204 + } } diff --git a/src/main/java/com/spotify/github/v3/clients/TeamClient.java b/src/main/java/com/spotify/github/v3/clients/TeamClient.java new file mode 100644 index 00000000..409d5911 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/clients/TeamClient.java @@ -0,0 +1,231 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.clients; + +import static com.spotify.github.v3.clients.GitHubClient.*; + +import com.spotify.github.async.AsyncPage; +import com.spotify.github.v3.Team; +import com.spotify.github.v3.User; +import com.spotify.github.v3.orgs.Membership; +import com.spotify.github.v3.orgs.TeamInvitation; +import com.spotify.github.v3.orgs.requests.ImmutableTeamRepoPermissionUpdate; +import com.spotify.github.v3.orgs.requests.MembershipCreate; +import com.spotify.github.v3.orgs.requests.TeamCreate; +import com.spotify.github.v3.orgs.requests.TeamRepoPermissionUpdate; +import com.spotify.github.v3.orgs.requests.TeamUpdate; +import java.lang.invoke.MethodHandles; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TeamClient { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final String TEAM_TEMPLATE = "/orgs/%s/teams"; + + private static final String TEAM_SLUG_TEMPLATE = "/orgs/%s/teams/%s"; + + private static final String MEMBERS_TEMPLATE = "/orgs/%s/teams/%s/members"; + + private static final String PAGED_MEMBERS_TEMPLATE = "/orgs/%s/teams/%s/members"; + + private static final String MEMBERSHIP_TEMPLATE = "/orgs/%s/teams/%s/memberships/%s"; + + private static final String INVITATIONS_TEMPLATE = "/orgs/%s/teams/%s/invitations"; + + private static final String REPO_TEMPLATE = "/orgs/%s/teams/%s/repos/%s/%s"; + + private final GitHubClient github; + + private final String org; + + TeamClient(final GitHubClient github, final String org) { + this.github = github; + this.org = org; + } + + static TeamClient create(final GitHubClient github, final String org) { + return new TeamClient(github, org); + } + + /** + * Create a team in an organisation. + * + * @param request create team request + * @return team + */ + public CompletableFuture createTeam(final TeamCreate request) { + final String path = String.format(TEAM_TEMPLATE, org); + log.debug("Creating team in: {}", path); + return github.post(path, github.json().toJsonUnchecked(request), Team.class); + } + + /** + * Get a specific team in an organisation. + * + * @param slug slug of the team name + * @return team + */ + public CompletableFuture getTeam(final String slug) { + final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); + log.debug("Fetching team from {}", path); + return github.request(path, Team.class); + } + + /** + * List teams within an organisation. + * + * @return list of all teams in an organisation + */ + public CompletableFuture> listTeams() { + final String path = String.format(TEAM_TEMPLATE, org); + log.debug("Fetching teams from {}", path); + return github.request(path, LIST_TEAMS); + } + + /** + * Update a team in an organisation. + * + * @param request update team request + * @param slug slug of the team name + * @return team + */ + public CompletableFuture updateTeam(final TeamUpdate request, final String slug) { + final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); + log.debug("Updating team in: {}", path); + return github.patch(path, github.json().toJsonUnchecked(request), Team.class); + } + + /** + * Delete a specific team in an organisation. + * + * @param slug slug of the team name + * @return team + */ + public CompletableFuture deleteTeam(final String slug) { + final String path = String.format(TEAM_SLUG_TEMPLATE, org, slug); + log.debug("Deleting team from: {}", path); + return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); + } + + /** + * Add or update a team membership for a user. + * + * @param request update membership request + * @return membership + */ + public CompletableFuture updateMembership( + final MembershipCreate request, final String slug, final String username) { + final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username); + log.debug("Updating membership in: {}", path); + return github.put(path, github.json().toJsonUnchecked(request), Membership.class); + } + + /** + * Get a team membership of a user. + * + * @param slug the team slug + * @param username username of the team member + * @return membership + */ + public CompletableFuture getMembership(final String slug, final String username) { + final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username); + log.debug("Fetching membership for: {}", path); + return github.request(path, Membership.class); + } + + /** + * List members of a specific team. + * + * @param slug the team slug + * @return list of all users in a team + */ + public CompletableFuture> listTeamMembers(final String slug) { + final String path = String.format(MEMBERS_TEMPLATE, org, slug); + log.debug("Fetching members for: {}", path); + return github.request(path, LIST_TEAM_MEMBERS); + } + + /** + * List members of a specific team. + * + * @param slug the team slug + * @param pageSize the number of users to fetch per page + * @return list of all users in a team + */ + public Iterator> listTeamMembers(final String slug, final int pageSize) { + final String path = String.format(PAGED_MEMBERS_TEMPLATE, org, slug); + log.debug("Fetching members for: {}", path); + return new GithubPageIterator<>(new GithubPage<>(github, path, LIST_TEAM_MEMBERS, pageSize)); + } + + /** + * Delete a membership for a user. + * + * @param slug slug of the team name + * @return membership + */ + public CompletableFuture deleteMembership(final String slug, final String username) { + final String path = String.format(MEMBERSHIP_TEMPLATE, org, slug, username); + log.debug("Deleting membership from: {}", path); + return github.delete(path).thenAccept(IGNORE_RESPONSE_CONSUMER); + } + + /** + * List pending invitations for a team. + * + * @param slug the team slug + * @return list of pending invitations for a team + */ + public CompletableFuture> listPendingTeamInvitations(final String slug) { + final String path = String.format(INVITATIONS_TEMPLATE, org, slug); + log.debug("Fetching pending invitations for: {}", path); + return github.request(path, LIST_PENDING_TEAM_INVITATIONS); + } + + /** + * Update permissions for a team on a specific repository. + * + * @param slug the team slug + * @param repo the repository name + * @param permission the permission level (pull, push, maintain, triage, admin, or a custom repo defined role name) + * @return void status code 204 if successful + */ + public CompletableFuture updateTeamPermissions( + final String slug, final String repo, final String permission) { + final String path = String.format(REPO_TEMPLATE, org, slug, org, repo); + final TeamRepoPermissionUpdate request = + ImmutableTeamRepoPermissionUpdate.builder() + .org(org) + .repo(repo) + .teamSlug(slug) + .permission(permission) + .build(); + log.debug("Updating team permissions for: {}", path); + return github + .put(path, github.json().toJsonUnchecked(request)) + .thenAccept(IGNORE_RESPONSE_CONSUMER); + } +} diff --git a/src/main/java/com/spotify/github/v3/clients/UserClient.java b/src/main/java/com/spotify/github/v3/clients/UserClient.java new file mode 100644 index 00000000..6fec500d --- /dev/null +++ b/src/main/java/com/spotify/github/v3/clients/UserClient.java @@ -0,0 +1,74 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2024 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.clients; + +import com.spotify.github.v3.user.requests.SuspensionReason; +import java.util.concurrent.CompletableFuture; + +public class UserClient { + + public static final int NO_CONTENT = 204; + private final GitHubClient github; + private final String owner; + + private static final String SUSPEND_USER_TEMPLATE = "/users/%s/suspended"; + + UserClient(final GitHubClient github, final String owner) { + this.github = github; + this.owner = owner; + } + + static UserClient create(final GitHubClient github, final String owner) { + return new UserClient(github, owner); + } + + public GithubAppClient createGithubAppClient() { + return new GithubAppClient(this.github, this.owner); + } + + /** + * Suspend a user. + * + * @param username username of the user to suspend + * @return a CompletableFuture that indicates success or failure + */ + public CompletableFuture suspendUser( + final String username, final SuspensionReason reason) { + final String path = String.format(SUSPEND_USER_TEMPLATE, username); + return github + .put(path, github.json().toJsonUnchecked(reason)) + .thenApply(resp -> resp.statusCode() == NO_CONTENT); + } + + /** + * Unsuspend a user. + * + * @param username username of the user to unsuspend + * @return a CompletableFuture that indicates success or failure + */ + public CompletableFuture unSuspendUser( + final String username, final SuspensionReason reason) { + final String path = String.format(SUSPEND_USER_TEMPLATE, username); + return github + .delete(path, github.json().toJsonUnchecked(reason)) + .thenApply(resp -> resp.statusCode() == NO_CONTENT); + } +} diff --git a/src/main/java/com/spotify/github/v3/clients/WorkflowsClient.java b/src/main/java/com/spotify/github/v3/clients/WorkflowsClient.java new file mode 100644 index 00000000..8aeb6fa5 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/clients/WorkflowsClient.java @@ -0,0 +1,73 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.clients; + +import com.google.common.collect.ImmutableMap; +import com.spotify.github.v3.workflows.WorkflowsRepositoryResponseList; +import com.spotify.github.v3.workflows.WorkflowsResponse; + +import javax.ws.rs.core.HttpHeaders; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** Workflows API client */ +public class WorkflowsClient { + private static final String LIST_REPOSITORY_WORKFLOWS_URI = "/repos/%s/%s/actions/workflows"; + private static final String GET_WORKFLOW_URI = "/repos/%s/%s/actions/workflows/%s"; + + private final GitHubClient github; + private final String owner; + private final String repo; + + private final Map extraHeaders = + ImmutableMap.of(HttpHeaders.ACCEPT, "application/vnd.github+json"); + + public WorkflowsClient(final GitHubClient github, final String owner, final String repo) { + this.github = github; + this.owner = owner; + this.repo = repo; + } + + static WorkflowsClient create(final GitHubClient github, final String owner, final String repo) { + return new WorkflowsClient(github, owner, repo); + } + + /** + * List workflows for a repository. + * + * @return a list of workflows for the repository + */ + public CompletableFuture listWorkflows() { + final String path = String.format(LIST_REPOSITORY_WORKFLOWS_URI, owner, repo); + return github.request(path, WorkflowsRepositoryResponseList.class, extraHeaders); + } + + /** + * Gets a workflow by id. + * + * @param id the workflow id + * @return a WorkflowsResponse + */ + public CompletableFuture getWorkflow(final int id) { + final String path = String.format(GET_WORKFLOW_URI, owner, repo, id); + return github.request(path, WorkflowsResponse.class, extraHeaders); + } +} diff --git a/src/main/java/com/spotify/github/v3/comment/Comment.java b/src/main/java/com/spotify/github/v3/comment/Comment.java index 175fdc19..9621255f 100644 --- a/src/main/java/com/spotify/github/v3/comment/Comment.java +++ b/src/main/java/com/spotify/github/v3/comment/Comment.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -46,7 +46,7 @@ public interface Comment extends UpdateTracking { URI htmlUrl(); /** Comment ID. */ - int id(); + Long id(); /** The {@link User} that made the comment. */ @Nullable @@ -60,6 +60,7 @@ public interface Comment extends UpdateTracking { * * @deprecated Use {@link #position()} instead */ + @Deprecated Optional line(); /** Relative path of the file to comment on. */ @@ -74,4 +75,8 @@ public interface Comment extends UpdateTracking { /** The issueURL which the comment belongs to. */ Optional issueUrl(); + + /** Node ID */ + @Nullable + String nodeId(); } diff --git a/src/main/java/com/spotify/github/v3/comment/CommentReaction.java b/src/main/java/com/spotify/github/v3/comment/CommentReaction.java new file mode 100644 index 00000000..705d0dd2 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/comment/CommentReaction.java @@ -0,0 +1,50 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.v3.comment; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import com.spotify.github.UpdateTracking; +import com.spotify.github.v3.User; +import org.immutables.value.Value; + +/** + * Comment reaction object. + * + *

See About + * GitHub Issue Comment reactions + */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableCommentReaction.class) +@JsonDeserialize(as = ImmutableCommentReaction.class) +public interface CommentReaction extends UpdateTracking { + + /** Reaction ID. */ + long id(); + + /** Reaction user. */ + User user(); + + /** Reaction content. */ + CommentReactionContent content(); +} diff --git a/src/main/java/com/spotify/github/v3/comment/CommentReactionContent.java b/src/main/java/com/spotify/github/v3/comment/CommentReactionContent.java new file mode 100644 index 00000000..cc4f8727 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/comment/CommentReactionContent.java @@ -0,0 +1,56 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.v3.comment; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.jackson.CommentReactionContentDeserializer; +import com.spotify.github.jackson.CommentReactionContentSerializer; + +/** + * Comment reaction content. + * + *

See About + * GitHub Issue Comment reactions + */ +@JsonDeserialize(using = CommentReactionContentDeserializer.class) +@JsonSerialize(using = CommentReactionContentSerializer.class) +public enum CommentReactionContent { + THUMBS_UP("+1"), // 👍 + THUMBS_DOWN("-1"), // 👎 + LAUGH("laugh"), // 😄 + HOORAY("hooray"), // 🎉 + CONFUSED("confused"), // 😕 + HEART("heart"), // ❤️ + ROCKET("rocket"), // 🚀 + EYES("eyes"); // 👀 + + private final String reaction; + + CommentReactionContent(final String reaction) { + this.reaction = reaction; + } + + @Override + public String toString() { + return reaction; + } +} diff --git a/src/main/java/com/spotify/github/v3/exceptions/GithubException.java b/src/main/java/com/spotify/github/v3/exceptions/GithubException.java index 7599c719..b88f809f 100644 --- a/src/main/java/com/spotify/github/v3/exceptions/GithubException.java +++ b/src/main/java/com/spotify/github/v3/exceptions/GithubException.java @@ -22,6 +22,8 @@ /** Common github exception */ public class GithubException extends RuntimeException { + private static final long serialVersionUID = 1L; + /** * C'tor for setting a message diff --git a/src/main/java/com/spotify/github/v3/exceptions/ReadOnlyRepositoryException.java b/src/main/java/com/spotify/github/v3/exceptions/ReadOnlyRepositoryException.java index d36c465f..08dc681b 100644 --- a/src/main/java/com/spotify/github/v3/exceptions/ReadOnlyRepositoryException.java +++ b/src/main/java/com/spotify/github/v3/exceptions/ReadOnlyRepositoryException.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,29 +20,23 @@ package com.spotify.github.v3.exceptions; +import java.util.List; +import java.util.Map; + /** The Read only repository exception. */ public class ReadOnlyRepositoryException extends RequestNotOkException { - /** - * Instantiates a new Read only repository exception. - * - * @param path the path - * @param statusCode the status code - * @param msg the msg - */ - public ReadOnlyRepositoryException(final String path, final int statusCode, final String msg) { - super(path, statusCode, msg); - } + private static final long serialVersionUID = 1L; /** * Instantiates a new Read only repository exception. * + * @param method HTTP method * @param path the path * @param statusCode the status code * @param msg the msg - * @param cause the cause */ public ReadOnlyRepositoryException( - final String path, final int statusCode, final String msg, final Throwable cause) { - super(path, statusCode, msg, cause); + final String method, final String path, final int statusCode, final String msg, final Map> headers) { + super(method, path, statusCode, msg, headers); } } diff --git a/src/main/java/com/spotify/github/v3/exceptions/RequestNotOkException.java b/src/main/java/com/spotify/github/v3/exceptions/RequestNotOkException.java index 315ea63d..342c00ea 100644 --- a/src/main/java/com/spotify/github/v3/exceptions/RequestNotOkException.java +++ b/src/main/java/com/spotify/github/v3/exceptions/RequestNotOkException.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -34,38 +34,50 @@ */ package com.spotify.github.v3.exceptions; +import java.util.List; +import java.util.Map; + /** HTTP response with non-200 StatusCode. */ public class RequestNotOkException extends GithubException { + private static final long serialVersionUID = 1L; + private final int statusCode; + private final String method; private final String path; + private final String msg; + private final Map> headers; + + private static String decoratedMessage( + final String method, final String path, final int statusCode, final String msg) { + return String.format("%s %s %d: %s", method, path, statusCode, msg); + } /** * Response to request came back with non-2xx status code * + * @param method HTTP method * @param path URI path * @param statusCode status of repsonse * @param msg response body */ - public RequestNotOkException(final String path, final int statusCode, final String msg) { - super(msg); + public RequestNotOkException( + final String method, final String path, final int statusCode, final String msg, final Map> headers) { + super(decoratedMessage(method, path, statusCode, msg)); this.statusCode = statusCode; + this.method = method; this.path = path; + this.msg = msg; + this.headers = headers; } /** - * Response to request came back with non-2xx status code + * Get the raw message from github * - * @param path URI path - * @param statusCode status of repsonse - * @param msg response body - * @param cause exception cause + * @return msg */ - public RequestNotOkException( - final String path, final int statusCode, final String msg, final Throwable cause) { - super(msg, cause); - this.statusCode = statusCode; - this.path = path; + public String getRawMessage() { + return msg; } /** @@ -77,6 +89,15 @@ public int statusCode() { return statusCode; } + /** + * Get request HTTP method + * + * @return method + */ + public String method() { + return method; + } + /** * Get request URI path * @@ -85,4 +106,13 @@ public int statusCode() { public String path() { return path; } + + /** + * Get response headers + * + * @return headers + */ + public Map> headers() { + return headers; + } } diff --git a/src/main/java/com/spotify/github/v3/git/FileItem.java b/src/main/java/com/spotify/github/v3/git/FileItem.java new file mode 100644 index 00000000..8356ca85 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/git/FileItem.java @@ -0,0 +1,74 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.git; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; +import javax.annotation.Nullable; +import java.net.URI; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableFileItem.class) +@JsonDeserialize(as = ImmutableFileItem.class) + +public interface FileItem { + + /** Commit sha value. */ + String sha(); + + /** Commit node_id. */ + String filename(); + + /** Commit API URL. */ + @Nullable + String status(); + + @Nullable + Integer additions(); + + /** Author commit user. */ + @Nullable + Integer deletions(); + + @Nullable + Integer changes(); + + @Nullable + @JsonProperty("blob_url") + URI blobUrl(); + + @Nullable + @JsonProperty("raw_url") + URI rawUrl(); + + @Nullable + @JsonProperty("contents_url") + URI contentsUrl(); + + @Nullable + String patch(); + +} + diff --git a/src/main/java/com/spotify/github/v3/git/ParentItem.java b/src/main/java/com/spotify/github/v3/git/ParentItem.java new file mode 100644 index 00000000..a19a09cc --- /dev/null +++ b/src/main/java/com/spotify/github/v3/git/ParentItem.java @@ -0,0 +1,49 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.git; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; + +import javax.annotation.Nullable; +import java.net.URI; + + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableParentItem.class) +@JsonDeserialize(as = ImmutableParentItem.class) + +public interface ParentItem { + + @Nullable + String sha(); + + @Nullable + URI url(); + + @Nullable + @JsonProperty("html_url") + URI htmlUrl(); +} diff --git a/src/main/java/com/spotify/github/v3/git/StatItem.java b/src/main/java/com/spotify/github/v3/git/StatItem.java new file mode 100644 index 00000000..4d301cfe --- /dev/null +++ b/src/main/java/com/spotify/github/v3/git/StatItem.java @@ -0,0 +1,44 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.git; + +import javax.annotation.Nullable; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableStatItem.class) +@JsonDeserialize(as = ImmutableStatItem.class) + +public interface StatItem { + + @Nullable + Integer total(); + + @Nullable + Integer additions(); + + @Nullable + Integer deletions(); +} diff --git a/src/main/java/com/spotify/github/v3/git/Verification.java b/src/main/java/com/spotify/github/v3/git/Verification.java new file mode 100644 index 00000000..6187e690 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/git/Verification.java @@ -0,0 +1,51 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.git; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; + +import javax.annotation.Nullable; + + + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableVerification.class) +@JsonDeserialize(as = ImmutableVerification.class) +public interface Verification { + + @Nullable + Boolean verified(); + + @Nullable + String reason(); + + @Nullable + String signature(); + + @Nullable + String payload(); + +} + diff --git a/src/main/java/com/spotify/github/v3/issues/Issue.java b/src/main/java/com/spotify/github/v3/issues/Issue.java index c1d77819..2d5c7988 100644 --- a/src/main/java/com/spotify/github/v3/issues/Issue.java +++ b/src/main/java/com/spotify/github/v3/issues/Issue.java @@ -41,18 +41,16 @@ public interface Issue extends CloseTracking { /** ID. */ @Nullable - Integer id(); + Long id(); /** URL. */ @Nullable URI url(); /** Events URL. */ - @Nullable Optional eventsUrl(); /** Repository URL. */ - @Nullable Optional repositoryUrl(); /** Labels URL template. */ @@ -69,7 +67,7 @@ public interface Issue extends CloseTracking { /** Number. */ @Nullable - Integer number(); + Long number(); /** Indicates the state of the issues to return. Can be either open, closed, or all. */ @Nullable @@ -80,7 +78,6 @@ public interface Issue extends CloseTracking { String title(); /** The contents of the issue. */ - @Nullable Optional body(); /** User. */ diff --git a/src/main/java/com/spotify/github/v3/issues/Label.java b/src/main/java/com/spotify/github/v3/issues/Label.java index bf78bad0..da46cc6f 100644 --- a/src/main/java/com/spotify/github/v3/issues/Label.java +++ b/src/main/java/com/spotify/github/v3/issues/Label.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,29 +20,57 @@ package com.spotify.github.v3.issues; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.spotify.github.GithubStyle; + import java.net.URI; import javax.annotation.Nullable; + import org.immutables.value.Value; -/** Issue label resource */ +/** + * Issue label resource + */ @Value.Immutable @GithubStyle @JsonSerialize(as = ImmutableLabel.class) @JsonDeserialize(as = ImmutableLabel.class) public interface Label { - /** URL */ - @Nullable - URI url(); + /** + * Id + */ + Long id(); + + @Nullable + String nodeId(); + + /** + * URL + */ + @Nullable + URI url(); + + /** + * Name + */ + @Nullable + String name(); + + /** + * Color + */ + @Nullable + String color(); - /** Name */ - @Nullable - String name(); + @Nullable + String description(); - /** Color */ - @Nullable - String color(); + /** + * Default + */ + @JsonProperty("default") + boolean isDefault(); } diff --git a/src/main/java/com/spotify/github/v3/orgs/Membership.java b/src/main/java/com/spotify/github/v3/orgs/Membership.java new file mode 100644 index 00000000..17c190c1 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/Membership.java @@ -0,0 +1,50 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.orgs; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import java.net.URI; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +/** + * Membership resource represents data returned by a single Membership get operation. + */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableMembership.class) +@JsonDeserialize(as = ImmutableMembership.class) +public interface Membership { + + /** URL */ + @Nullable + URI url(); + + /** ROLE */ + @Nullable + String role(); + + /** STATE */ + @Nullable + String state(); +} diff --git a/src/main/java/com/spotify/github/v3/orgs/OrgMembership.java b/src/main/java/com/spotify/github/v3/orgs/OrgMembership.java new file mode 100644 index 00000000..1094c113 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/OrgMembership.java @@ -0,0 +1,59 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.orgs; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import com.spotify.github.v3.User; +import com.spotify.github.v3.repos.Organization; +import java.net.URI; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +/** + * Org Membership resource represents data returned by a single Membership get operation. + */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableOrgMembership.class) +@JsonDeserialize(as = ImmutableOrgMembership.class) +public interface OrgMembership { + + /** URL */ + @Nullable + URI url(); + + /** ROLE */ + @Nullable + String role(); + + /** STATE */ + @Nullable + String state(); + + @Nullable + Organization organization(); + + /** USER */ + @Nullable + User user(); +} diff --git a/src/main/java/com/spotify/github/v3/orgs/TeamInvitation.java b/src/main/java/com/spotify/github/v3/orgs/TeamInvitation.java new file mode 100644 index 00000000..a58c12b6 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/TeamInvitation.java @@ -0,0 +1,76 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.orgs; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import com.spotify.github.v3.User; +import java.net.URI; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableTeamInvitation.class) +@JsonDeserialize(as = ImmutableTeamInvitation.class) +public interface TeamInvitation { + /** ID */ + @Nullable + Integer id(); + + /** login username */ + @Nullable + String login(); + + /** Node ID */ + @Nullable + String nodeId(); + + /** Email address */ + @Nullable + String email(); + + /** Role */ + @Nullable + String role(); + + /** Failed reason */ + @Nullable + String failedReason(); + + /** Inviter */ + + @Nullable + User inviter(); + + /** Team Count */ + @Nullable + Integer teamCount(); + + /** Invitation Teams URL */ + @Nullable + URI invitationTeamsUrl(); + + /** Invitation Source */ + @Nullable + String invitationSource(); +} diff --git a/src/main/java/com/spotify/github/v3/orgs/requests/MembershipCreate.java b/src/main/java/com/spotify/github/v3/orgs/requests/MembershipCreate.java new file mode 100644 index 00000000..cc6fc100 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/requests/MembershipCreate.java @@ -0,0 +1,42 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.orgs.requests; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +/** Request to create a team within a given organisation */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableMembershipCreate.class) +@JsonDeserialize(as = ImmutableMembershipCreate.class) +public interface MembershipCreate { + + /** + * The role that this user should have in the team. + * Defaults to 'member' + */ + @Nullable + String role(); +} diff --git a/src/main/java/com/spotify/github/v3/orgs/requests/OrgMembershipCreate.java b/src/main/java/com/spotify/github/v3/orgs/requests/OrgMembershipCreate.java new file mode 100644 index 00000000..284b31d8 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/requests/OrgMembershipCreate.java @@ -0,0 +1,42 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.orgs.requests; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +/** Request to create a member within a given org */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableOrgMembershipCreate.class) +@JsonDeserialize(as = ImmutableOrgMembershipCreate.class) +public interface OrgMembershipCreate { + + /** + * The role that this user should have in the org. + * Defaults to 'member' + */ + @Nullable + String role(); +} diff --git a/src/main/java/com/spotify/github/v3/orgs/requests/TeamCreate.java b/src/main/java/com/spotify/github/v3/orgs/requests/TeamCreate.java new file mode 100644 index 00000000..e5fe07b7 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/requests/TeamCreate.java @@ -0,0 +1,79 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.v3.orgs.requests; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import java.util.List; +import java.util.Optional; +import org.immutables.value.Value; + +/** Request to create a team within a given organisation */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableTeamCreate.class) +@JsonDeserialize(as = ImmutableTeamCreate.class) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public interface TeamCreate { + + /** The name of the team. */ + String name(); + + /** The description of the team. */ + Optional description(); + + /** + * The level of privacy this team should have. For a non-nested team: secret - only visible to + * organization owners and members of this team. closed - visible to all members of this + * organization. Default: secret For a parent or child team: closed - visible to all members of + * this organization. Default for child team: closed Can be one of: secret, closed + */ + Optional privacy(); + + /** + * The notification setting the team has chosen. The options are: + * + *

notifications_enabled - team members receive notifications when the team is @mentioned. + * + *

notifications_disabled - no one receives notifications. + * + *

Default: notifications_enabled + * + *

Can be one of: notifications_enabled, notifications_disabled + */ + @JsonProperty("notification_setting") + Optional notificationSetting(); + + /** List GitHub IDs for organization members who will become team maintainers. */ + Optional> maintainers(); + + /** + * The full name (e.g., "organization-name/repository-name") of repositories to add the team to. + */ + @JsonProperty("repo_names") + Optional> repoNames(); + + /** The ID of a team to set as the parent team. */ + @JsonProperty("parent_team_id") + Optional parentTeamId(); +} diff --git a/src/main/java/com/spotify/github/v3/orgs/requests/TeamRepoPermissionUpdate.java b/src/main/java/com/spotify/github/v3/orgs/requests/TeamRepoPermissionUpdate.java new file mode 100644 index 00000000..5263ff5f --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/requests/TeamRepoPermissionUpdate.java @@ -0,0 +1,38 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.orgs.requests; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; + +/** Request to update permissions of a team for a specific repo */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableTeamRepoPermissionUpdate.class) +@JsonDeserialize(as = ImmutableTeamRepoPermissionUpdate.class) +public interface TeamRepoPermissionUpdate { + String org(); + String repo(); + String teamSlug(); + String permission(); +} diff --git a/src/main/java/com/spotify/github/v3/orgs/requests/TeamUpdate.java b/src/main/java/com/spotify/github/v3/orgs/requests/TeamUpdate.java new file mode 100644 index 00000000..3652cf85 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/orgs/requests/TeamUpdate.java @@ -0,0 +1,73 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.v3.orgs.requests; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import java.util.Optional; +import javax.annotation.Nullable; +import org.immutables.value.Value; + +/** Request to create a team within a given organisation */ +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableTeamUpdate.class) +@JsonDeserialize(as = ImmutableTeamUpdate.class) +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public interface TeamUpdate { + + /** The name of the team. */ + @Nullable + String name(); + + /** The description of the team. */ + Optional description(); + + /** + * The level of privacy this team should have. + * For a non-nested team: + * secret - only visible to organization owners and members of this team. + * closed - visible to all members of this organization. + * Default: secret + * For a parent or child team: + * closed - visible to all members of this organization. + * Default for child team: closed + * Can be one of: secret, closed + */ + Optional privacy(); + + /** + * The notification setting the team has chosen. The options are: + * notifications_enabled - team members receive notifications when the team is @mentioned. + * notifications_disabled - no one receives notifications. + * Default: notifications_enabled + * Can be one of: notifications_enabled, notifications_disabled + */ + @JsonProperty("notification_setting") + Optional notificationSetting(); + + + /** The ID of a team to set as the parent team. */ + @JsonProperty("parent_team_id") + Optional parentTeamId(); +} diff --git a/src/main/java/com/spotify/github/v3/prs/AutoMerge.java b/src/main/java/com/spotify/github/v3/prs/AutoMerge.java new file mode 100644 index 00000000..6cbad38e --- /dev/null +++ b/src/main/java/com/spotify/github/v3/prs/AutoMerge.java @@ -0,0 +1,49 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.prs; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import com.spotify.github.v3.User; +import org.immutables.value.Value; + +import javax.annotation.Nullable; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableAutoMerge.class) +@JsonDeserialize(as = ImmutableAutoMerge.class) +public interface AutoMerge { + // Who enabled the auto merge + User enabledBy(); + + // Merge Method chosen for the auto merge + String mergeMethod(); + + // The commit title to use when merging the pull request + @Nullable + String commitTitle(); + + // The commit message to use when merging the pull request + @Nullable + String commitMessage(); +} diff --git a/src/main/java/com/spotify/github/v3/prs/Comment.java b/src/main/java/com/spotify/github/v3/prs/Comment.java index 0c890408..c15d3dff 100644 --- a/src/main/java/com/spotify/github/v3/prs/Comment.java +++ b/src/main/java/com/spotify/github/v3/prs/Comment.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -73,7 +73,6 @@ public interface Comment extends UpdateTracking { /** Base commit sha. */ @Nullable String originalCommitId(); - /** Comment author. */ @Nullable User user(); @@ -82,6 +81,38 @@ public interface Comment extends UpdateTracking { @Nullable String body(); + /** The ID of the comment to reply to. */ + @Nullable + Long inReplyToId(); + + /** The author association of the comment. */ + @Nullable + String authorAssociation(); + + /** The starting line number in the diff. */ + @Nullable + Integer startLine(); + + /** The starting line number in the original file. */ + @Nullable + Integer originalStartLine(); + + /** The side of the diff where the starting line is from. */ + @Nullable + String startSide(); + + /** The line number in the diff. */ + @Nullable + Integer line(); + + /** The line number in the original file. */ + @Nullable + Integer originalLine(); + + /** The side of the diff where the line is from. */ + @Nullable + String side(); + /** Comment URL. */ @Nullable URI htmlUrl(); @@ -94,4 +125,12 @@ public interface Comment extends UpdateTracking { @Nullable @JsonProperty("_links") CommentLinks links(); + + /** Node ID */ + @Nullable + String nodeId(); + + /** Pull request review ID. */ + @Nullable + Long pullRequestReviewId(); } diff --git a/src/main/java/com/spotify/github/v3/prs/PartialPullRequestItem.java b/src/main/java/com/spotify/github/v3/prs/PartialPullRequestItem.java new file mode 100644 index 00000000..9e8a1177 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/prs/PartialPullRequestItem.java @@ -0,0 +1,55 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.v3.prs; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.CloseTracking; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; + +import javax.annotation.Nullable; +import java.net.URI; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutablePartialPullRequestItem.class) +@JsonDeserialize(as = ImmutablePartialPullRequestItem.class) +public interface PartialPullRequestItem extends CloseTracking { + /** ID. */ + @Nullable + Long id(); + + /** URL. */ + @Nullable + URI url(); + + /** Number. */ + @Nullable + Long number(); + + /** Head reference. */ + @Nullable + PullRequestRef head(); + + /** Base reference. */ + @Nullable + PullRequestRef base(); +} diff --git a/src/main/java/com/spotify/github/v3/prs/PullRequest.java b/src/main/java/com/spotify/github/v3/prs/PullRequest.java index 6925e3d8..875ce19d 100644 --- a/src/main/java/com/spotify/github/v3/prs/PullRequest.java +++ b/src/main/java/com/spotify/github/v3/prs/PullRequest.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -24,8 +24,10 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.spotify.github.GithubStyle; import com.spotify.github.v3.User; + import java.util.Optional; import javax.annotation.Nullable; + import org.immutables.value.Value; /** @@ -38,6 +40,8 @@ @JsonDeserialize(as = ImmutablePullRequest.class) public interface PullRequest extends PullRequestItem { + String nodeId(); + /** Is it merged. */ @Nullable Boolean merged(); @@ -75,7 +79,4 @@ public interface PullRequest extends PullRequestItem { /** The mergeable state of this PR. */ @Nullable String mergeableState(); - - /** Is it a draft PR? */ - Optional draft(); } diff --git a/src/main/java/com/spotify/github/v3/prs/PullRequestItem.java b/src/main/java/com/spotify/github/v3/prs/PullRequestItem.java index 452cf3b2..96830e77 100644 --- a/src/main/java/com/spotify/github/v3/prs/PullRequestItem.java +++ b/src/main/java/com/spotify/github/v3/prs/PullRequestItem.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,15 +23,17 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.spotify.github.CloseTracking; import com.spotify.github.GitHubInstant; import com.spotify.github.GithubStyle; import com.spotify.github.v3.Milestone; import com.spotify.github.v3.User; +import com.spotify.github.v3.issues.Label; + +import javax.annotation.Nullable; import java.net.URI; import java.util.List; import java.util.Optional; -import javax.annotation.Nullable; + import org.immutables.value.Value; /** Pull request item resource represents data returned during pull request list operation */ @@ -39,15 +41,7 @@ @GithubStyle @JsonSerialize(as = ImmutablePullRequestItem.class) @JsonDeserialize(as = ImmutablePullRequestItem.class) -public interface PullRequestItem extends CloseTracking { - - /** ID. */ - @Nullable - Integer id(); - - /** URL. */ - @Nullable - URI url(); +public interface PullRequestItem extends PartialPullRequestItem { /** HTML URL. */ @Nullable @@ -69,10 +63,6 @@ public interface PullRequestItem extends CloseTracking { @Nullable URI commitsUrl(); - /** Number. */ - @Nullable - Integer number(); - /** Either open, closed, or all to filter by state. Default: open. */ @Nullable String state(); @@ -100,14 +90,6 @@ public interface PullRequestItem extends CloseTracking { /** Merged date. */ Optional mergedAt(); - /** Head reference. */ - @Nullable - PullRequestRef head(); - - /** Base reference. */ - @Nullable - PullRequestRef base(); - /** User. */ @Nullable User user(); @@ -143,6 +125,23 @@ public interface PullRequestItem extends CloseTracking { @JsonProperty("requested_teams") List requestedTeams(); - /** @Deprecated the merge commit sha. */ + /** + * @Deprecated the merge commit sha. + */ Optional mergeCommitSha(); + + /** Node ID. */ + @Nullable + String nodeId(); + + /** Is Automerge Enabled */ + @Nullable + AutoMerge autoMerge(); + + /** Is it a draft PR? */ + Optional draft(); + + /** List of PR labels */ + @Nullable + List

Default: false + */ + Optional allowForking(); + + /** Allow squash merges */ + Optional allowSquashMerge(); + + /** Allow merge commits */ + Optional allowMergeCommit(); + + /** Allow rebase merges */ + Optional allowRebaseMerge(); + + /** + * Either true to always allow a pull request head branch that is behind its base branch to be + * updated even if it is not required to be up to date before merging, or false otherwise. + * + *

Default: false + */ + Optional allowUpdateBranch(); + + /** Updates the default branch for this repository. */ + Optional defaultBranch(); + + /** + * Either true to allow automatically deleting head branches when pull requests are merged, or + * false to prevent automatic deletion. + * + *

Default: false + */ + Optional deleteBranchOnMerge(); + + /** Homepage URL */ + Optional homepage(); + + /** Does it have downloads */ + Optional hasDownloads(); + + /** Does it have issues */ + Optional hasIssues(); + + /** Does it have wiki */ + Optional hasWiki(); + + /** Does it have pages */ + Optional hasPages(); + + /** Does it have projects */ + Optional hasProjects(); + + /** + * Whether to archive this repository. false will unarchive a previously archived repository. + * + *

Default: false + */ + @JsonProperty("archived") + Optional isArchived(); + + /** Is it private */ + @JsonProperty("private") + Optional isPrivate(); + + /** + * Either true to make this repo available as a template repository or false to prevent it. + * Default: false + */ + Optional isTemplate(); + + /** + * The default value for a squash merge commit message: + * + *

PR_BODY - default to the pull request's body. COMMIT_MESSAGES - default to the branch's + * commit messages. BLANK - default to a blank commit message. Can be one of: PR_BODY, + * COMMIT_MESSAGES, BLANK + */ + Optional squashMergeCommitMessage(); + + /** + * squash_merge_commit_title string The default value for a squash merge commit title: + * + *

PR_TITLE - default to the pull request's title. COMMIT_OR_PR_TITLE - default to the commit's + * title (if only one commit) or the pull request's title (when more than one commit). Can be one + * of: PR_TITLE, COMMIT_OR_PR_TITLE + */ + Optional squashMergeCommitTitle(); + + /** + * The default value for a merge commit message. + * + *

PR_TITLE - default to the pull request's title. PR_BODY - default to the pull request's + * body. BLANK - default to a blank commit message. + */ + Optional mergeCommitMessage(); + + /** + * The default value for a merge commit title. + * + *

PR_TITLE - default to the pull request's title. MERGE_MESSAGE - default to the classic title + * for a merge message (e.g., Merge pull request #123 from branch-name). Can be one of: PR_TITLE, + * MERGE_MESSAGE + */ + Optional mergeCommitTitle(); + + /** + * The id of the team that will be granted access to this repository. This is only valid when + * creating a repository in an organization. Default: false + */ + Optional teamId(); + + /** The visibility of the repo. Can be one of `public`, `private`, `internal` */ + Optional visibility(); + + /** + * Either true to require contributors to sign off on web-based commits, or false to not require + * contributors to sign off on web-based commits. + * + *

Default: false + */ + Optional webCommitSignoffRequired(); +} diff --git a/src/main/java/com/spotify/github/v3/search/requests/SearchParameters.java b/src/main/java/com/spotify/github/v3/search/requests/SearchParameters.java index ec0b1d7f..c88d7110 100644 --- a/src/main/java/com/spotify/github/v3/search/requests/SearchParameters.java +++ b/src/main/java/com/spotify/github/v3/search/requests/SearchParameters.java @@ -20,13 +20,16 @@ package com.spotify.github.v3.search.requests; +import java.util.Optional; + +import javax.annotation.Nullable; + +import org.immutables.value.Value; + import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.spotify.github.GithubStyle; import com.spotify.github.Parameters; -import java.util.Optional; -import javax.annotation.Nullable; -import org.immutables.value.Value; /** * Search parameters resource defines required and optional parameters. To be serialized as @@ -47,4 +50,15 @@ public interface SearchParameters extends Parameters { /** The sort order if sort parameter is provided. One of asc or desc. Default: desc */ Optional order(); + + /** + * The number of results per page (max 100). Default: 30 + */ + @SuppressWarnings("checkstyle:methodname") + Optional per_page(); + + /** + * Page number of the results to fetch. Default: 1 + */ + Optional page(); } diff --git a/src/main/java/com/spotify/github/v3/user/requests/SuspensionReason.java b/src/main/java/com/spotify/github/v3/user/requests/SuspensionReason.java new file mode 100644 index 00000000..22bb1f32 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/user/requests/SuspensionReason.java @@ -0,0 +1,34 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2024 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.v3.user.requests; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; + +@Value.Immutable +@GithubStyle +@JsonSerialize(as = ImmutableSuspensionReason.class) +@JsonDeserialize(as = ImmutableSuspensionReason.class) +public interface SuspensionReason { + + String reason(); +} diff --git a/src/main/java/com/spotify/github/v3/workflows/WorkflowsRepositoryResponseList.java b/src/main/java/com/spotify/github/v3/workflows/WorkflowsRepositoryResponseList.java new file mode 100644 index 00000000..1995d242 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/workflows/WorkflowsRepositoryResponseList.java @@ -0,0 +1,51 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.workflows; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; + +import java.util.List; + +/** + * The WorkflowsResponse list resource + * + * @see "https://docs.github.com/en/rest/actions/workflows#list-repository-workflows" + */ +@Value.Immutable +@GithubStyle +@JsonDeserialize(as = ImmutableWorkflowsRepositoryResponseList.class) +public interface WorkflowsRepositoryResponseList { + /** + * The count of workflows in the response + * + * @return the int + */ + int totalCount(); + + /** + * Workflows list. + * + * @return the list of workflows + */ + List workflows(); +} diff --git a/src/main/java/com/spotify/github/v3/workflows/WorkflowsResponse.java b/src/main/java/com/spotify/github/v3/workflows/WorkflowsResponse.java new file mode 100644 index 00000000..bf5da87e --- /dev/null +++ b/src/main/java/com/spotify/github/v3/workflows/WorkflowsResponse.java @@ -0,0 +1,94 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.workflows; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.spotify.github.GithubStyle; +import org.immutables.value.Value; + +import javax.annotation.Nullable; +import java.time.ZonedDateTime; + +@Value.Immutable +@GithubStyle +@JsonDeserialize(as = ImmutableWorkflowsResponse.class) +public interface WorkflowsResponse { + /** + * The Workflow ID. + * + * @return the int + */ + int id(); + + /** Node ID */ + String nodeId(); + + /** Name. */ + String name(); + + /** The workflow path. */ + String path(); + + /** Indicates the state of the workflow. */ + WorkflowsState state(); + + /** + * Created At + * + * @return The time when the workflow was created + */ + ZonedDateTime createdAt(); + + /** + * Updated At + * + * @return The time when the workflow was updated + */ + ZonedDateTime updatedAt(); + + /** + * Deleted At + * + * @return The time when the workflow was deleted + */ + @Nullable ZonedDateTime deletedAt(); + + /** + * Url string. + * + * @return the string + */ + String url(); + + /** + * Html url string. + * + * @return the string + */ + String htmlUrl(); + + /** + * Badge Url string. + * + * @return the string + */ + String badgeUrl(); +} diff --git a/src/main/java/com/spotify/github/v3/workflows/WorkflowsState.java b/src/main/java/com/spotify/github/v3/workflows/WorkflowsState.java new file mode 100644 index 00000000..fea5de18 --- /dev/null +++ b/src/main/java/com/spotify/github/v3/workflows/WorkflowsState.java @@ -0,0 +1,30 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.workflows; + +/** The Workflow State. */ +public enum WorkflowsState { + active, + deleted, + disabled_fork, + disabled_inactivity, + disabled_manually +} diff --git a/src/test/java/com/spotify/github/GitHubInstantTest.java b/src/test/java/com/spotify/github/GitHubInstantTest.java index 32fc51ff..8d8e8428 100644 --- a/src/test/java/com/spotify/github/GitHubInstantTest.java +++ b/src/test/java/com/spotify/github/GitHubInstantTest.java @@ -25,7 +25,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import java.time.Instant; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class GitHubInstantTest { diff --git a/src/test/java/com/spotify/github/MockHelper.java b/src/test/java/com/spotify/github/MockHelper.java new file mode 100644 index 00000000..40a7ea16 --- /dev/null +++ b/src/test/java/com/spotify/github/MockHelper.java @@ -0,0 +1,70 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github; + +import com.spotify.github.http.BaseHttpResponse; +import com.spotify.github.http.HttpRequest; +import com.spotify.github.http.HttpResponse; +import com.spotify.github.http.ImmutableHttpRequest; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class MockHelper { + private static final int HTTP_OK = 200; + private static final int HTTP_BAD_REQUEST = 400; + + public static HttpResponse createMockResponse( + final String headerLinksFixture, final String bodyFixture) throws IOException { + int statusCode = 200; + return createMockHttpResponse( + "", statusCode, bodyFixture, Map.of("Link", List.of(headerLinksFixture))); + } + + public static HttpResponse createMockHttpResponse( + final String url, + final int statusCode, + final String body, + final Map> headers) { + HttpRequest httpRequest = ImmutableHttpRequest.builder().url(url).build(); + return new BaseHttpResponse(httpRequest, statusCode, "", headers) { + @Override + public InputStream body() { + if (body != null) { + return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); + } + return null; + } + + @Override + public String bodyString() { + return Optional.ofNullable(body).orElse(""); + } + + @Override + public void close() {} + }; + } +} diff --git a/src/test/java/com/spotify/github/hooks/PullRequestEventTest.java b/src/test/java/com/spotify/github/hooks/PullRequestEventTest.java index 29e4e422..c6b8163d 100644 --- a/src/test/java/com/spotify/github/hooks/PullRequestEventTest.java +++ b/src/test/java/com/spotify/github/hooks/PullRequestEventTest.java @@ -27,7 +27,7 @@ import com.spotify.github.jackson.Json; import com.spotify.github.v3.activity.events.PullRequestEvent; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class PullRequestEventTest { diff --git a/src/test/java/com/spotify/github/http/HttpRequestTest.java b/src/test/java/com/spotify/github/http/HttpRequestTest.java new file mode 100644 index 00000000..c0faed77 --- /dev/null +++ b/src/test/java/com/spotify/github/http/HttpRequestTest.java @@ -0,0 +1,75 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.http; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.jupiter.api.Assertions.*; + +public class HttpRequestTest { + @Test + void createBareRequest() { + // When + HttpRequest httpRequest = ImmutableHttpRequest.builder().url("https://example.com").build(); + // Then + assertNotNull(httpRequest); + assertEquals("GET", httpRequest.method()); + assertEquals("https://example.com", httpRequest.url()); + assertNull(httpRequest.body()); + assertEquals(0, httpRequest.headers().size()); + assertNull(httpRequest.headers("Accept-Encoding")); + assertNull(httpRequest.header("Accept-Encoding")); + } + + @Test + void createRequest() { + // When + HttpRequest httpRequest = + ImmutableHttpRequest.builder() + .url("https://example.com") + .method("POST") + .body("{\"foo\":\"bar\"}") + .headers( + Map.of( + "Content-Type", + List.of("application/json", "charset=utf-8"), + "Accept", + List.of("application/json"))) + .build(); + // Then + assertNotNull(httpRequest); + assertEquals("POST", httpRequest.method()); + assertEquals("https://example.com", httpRequest.url()); + assertEquals("{\"foo\":\"bar\"}", httpRequest.body()); + assertEquals(2, httpRequest.headers().size()); + assertThat( + httpRequest.headers("Content-Type"), + containsInAnyOrder("application/json", "charset=utf-8")); + assertEquals("application/json,charset=utf-8", httpRequest.header("Content-Type")); + assertThat(httpRequest.headers("Accept"), containsInAnyOrder("application/json")); + assertEquals("application/json", httpRequest.header("Accept")); + } +} diff --git a/src/test/java/com/spotify/github/http/HttpResponseTest.java b/src/test/java/com/spotify/github/http/HttpResponseTest.java new file mode 100644 index 00000000..ea0bc63c --- /dev/null +++ b/src/test/java/com/spotify/github/http/HttpResponseTest.java @@ -0,0 +1,88 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.http; + +import static com.spotify.github.MockHelper.createMockHttpResponse; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class HttpResponseTest { + @Test + void createBareResponse() { + // When + HttpResponse httpResponse = createMockHttpResponse("https://example.com", 200, "{}", Map.of()); + // Then + assertNotNull(httpResponse); + assertEquals("GET", httpResponse.request().method()); + assertEquals("https://example.com", httpResponse.request().url()); + assertTrue(httpResponse.isSuccessful()); + assertEquals(0, httpResponse.headers().size()); + assertNull(httpResponse.headers("Accept-Encoding")); + assertNull(httpResponse.header("Accept-Encoding")); + } + + @Test + void createResponse() throws IOException { + // When + HttpResponse httpResponse = + createMockHttpResponse( + "https://example.com", + 200, + "{\"foo\":\"bar\"}", + Map.of( + "Content-Type", + List.of("application/json", "charset=utf-8"), + "Accept", + List.of("application/json"), + "Cache-Control", + List.of("no-cache"), + "Set-Cookie", + List.of("sessionId=abc123", "userId=xyz789"))); + String responseBody = null; + try (InputStream is = httpResponse.body()) { + responseBody = new String(is.readAllBytes()); + } + // Then + assertNotNull(httpResponse); + assertEquals("{\"foo\":\"bar\"}", httpResponse.bodyString()); + assertEquals("{\"foo\":\"bar\"}", responseBody); + assertEquals(4, httpResponse.headers().size()); + assertThat( + httpResponse.headers("Content-Type"), + containsInAnyOrder("application/json", "charset=utf-8")); + assertEquals("application/json,charset=utf-8", httpResponse.header("Content-Type")); + assertThat(httpResponse.headers("Accept"), containsInAnyOrder("application/json")); + assertEquals("application/json", httpResponse.header("Accept")); + assertThat(httpResponse.headers("Cache-Control"), containsInAnyOrder("no-cache")); + assertEquals("no-cache", httpResponse.header("Cache-Control")); + assertThat( + httpResponse.headers("Set-Cookie"), + containsInAnyOrder("sessionId=abc123", "userId=xyz789")); + assertEquals("sessionId=abc123,userId=xyz789", httpResponse.header("Set-Cookie")); + } +} diff --git a/src/test/java/com/spotify/github/http/LinkTest.java b/src/test/java/com/spotify/github/http/LinkTest.java index 70d42edc..975934e6 100644 --- a/src/test/java/com/spotify/github/http/LinkTest.java +++ b/src/test/java/com/spotify/github/http/LinkTest.java @@ -26,7 +26,7 @@ import java.util.Arrays; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class LinkTest { diff --git a/src/test/java/com/spotify/github/http/okhttp/OkHttpHttpClientTest.java b/src/test/java/com/spotify/github/http/okhttp/OkHttpHttpClientTest.java new file mode 100644 index 00000000..90b602c9 --- /dev/null +++ b/src/test/java/com/spotify/github/http/okhttp/OkHttpHttpClientTest.java @@ -0,0 +1,208 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.http.okhttp; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import com.spotify.github.http.HttpRequest; +import com.spotify.github.http.HttpResponse; +import com.spotify.github.http.ImmutableHttpRequest; +import com.spotify.github.tracing.NoopTracer; +import com.spotify.github.tracing.Span; +import com.spotify.github.tracing.TraceHelper; +import com.spotify.github.tracing.Tracer; +import com.spotify.github.tracing.opencensus.OpenCensusTracer; +import com.spotify.github.tracing.opentelemetry.OpenTelemetryTracer; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.stream.Stream; +import okhttp3.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; + +class OkHttpHttpClientTest { + private static final OkHttpClient okHttpClient = mock(OkHttpClient.class); + private static final OkHttpClient.Builder mockOkHttpClientBuilder = + mock(OkHttpClient.Builder.class); + private static final Tracer noopTracer = mock(NoopTracer.class); + private static final Tracer ocTracer = mock(OpenCensusTracer.class); + private static final Tracer otTracer = mock(OpenTelemetryTracer.class); + private static final Span mockSpan = mock(Span.class); + private static final Call.Factory mockCallFactory = mock(Call.Factory.class); + + private static OkHttpHttpClient httpClient; + + static Stream tracers() { + return Stream.of(noopTracer, ocTracer, otTracer); + } + + @BeforeAll + static void setUp() { + httpClient = + new OkHttpHttpClient(okHttpClient, noopTracer) { + @Override + protected Call.Factory createTracedClientOpenTelemetry() { + return mockCallFactory; + } + }; + } + + @BeforeEach + void setUpEach() { + List interceptors = new ArrayList<>(); + when(okHttpClient.newBuilder()).thenReturn(mockOkHttpClientBuilder); + when(mockOkHttpClientBuilder.networkInterceptors()).thenReturn(interceptors); + when(mockOkHttpClientBuilder.build()).thenReturn(okHttpClient); + } + + @AfterEach + void tearDown() { + reset(okHttpClient, noopTracer, ocTracer, otTracer, mockSpan); + } + + @ParameterizedTest + @MethodSource("tracers") + void sendSuccessfully(Tracer tracer) throws IOException { + // Given + final Call call = mock(Call.class); + final ArgumentCaptor capture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(capture.capture()); + final Response response = + new okhttp3.Response.Builder() + .code(200) + .body(ResponseBody.create(MediaType.get("application/json"), "{\"foo\":\"bar\"}")) + .message("foo") + .protocol(Protocol.HTTP_1_1) + .request(new Request.Builder().url("https://example.com").build()) + .build(); + + HttpRequest httpRequest = ImmutableHttpRequest.builder().url("https://example.com").build(); + when(okHttpClient.newCall(any())).thenReturn(call); + when(mockCallFactory.newCall(any())).thenReturn(call); + + when(tracer.span(any())).thenReturn(mockSpan); + + // When + httpClient.setTracer(tracer); + CompletableFuture futureResponse = httpClient.send(httpRequest); + capture.getValue().onResponse(call, response); + HttpResponse httpResponse = futureResponse.join(); + + // Then + assertNotNull(httpResponse); + assertEquals("{\"foo\":\"bar\"}", httpResponse.bodyString()); + assertEquals(200, httpResponse.statusCode()); + assertEquals("foo", httpResponse.statusMessage()); + assertTrue(httpResponse.isSuccessful()); + if (tracer instanceof NoopTracer || tracer instanceof OpenTelemetryTracer) { + verify(tracer, times(1)).span(any(HttpRequest.class)); + + } else if (tracer instanceof OpenCensusTracer) { + verify(tracer, times(2)).span(any(HttpRequest.class)); + verify(mockSpan).addTag(TraceHelper.TraceTags.HTTP_URL, "https://example.com/"); + } + verify(mockSpan, times(1)).close(); + } + + @ParameterizedTest + @MethodSource("tracers") + void sendWithException(Tracer tracer) { + // Given + final Call call = mock(Call.class); + final ArgumentCaptor capture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(capture.capture()); + final IOException exception = new IOException("Network error"); + + HttpRequest httpRequest = ImmutableHttpRequest.builder().url("https://example.com").build(); + when(okHttpClient.newCall(any())).thenReturn(call); + when(mockCallFactory.newCall(any())).thenReturn(call); + when(tracer.span(any())).thenReturn(mockSpan); + + // When + httpClient.setTracer(tracer); + CompletableFuture futureResponse = httpClient.send(httpRequest); + capture.getValue().onFailure(call, exception); + + // Then + assertThrows(CompletionException.class, futureResponse::join); + if (tracer instanceof NoopTracer || tracer instanceof OpenTelemetryTracer) { + verify(tracer, times(1)).span(any(HttpRequest.class)); + + } else if (tracer instanceof OpenCensusTracer) { + verify(tracer, times(2)).span(any(HttpRequest.class)); + verify(mockSpan).addTag(TraceHelper.TraceTags.HTTP_URL, "https://example.com/"); + } + verify(mockSpan, times(1)).close(); + } + + @ParameterizedTest + @MethodSource("tracers") + void sendWithClientError(Tracer tracer) throws IOException { + // Given + final Call call = mock(Call.class); + final ArgumentCaptor capture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(capture.capture()); + final Response response = + new okhttp3.Response.Builder() + .code(404) + .body( + ResponseBody.create(MediaType.get("application/json"), "{\"error\":\"Not Found\"}")) + .message("Not Found") + .protocol(Protocol.HTTP_1_1) + .request(new Request.Builder().url("https://example.com").build()) + .build(); + + HttpRequest httpRequest = ImmutableHttpRequest.builder().url("https://example.com").build(); + when(okHttpClient.newCall(any())).thenReturn(call); + when(mockCallFactory.newCall(any())).thenReturn(call); + when(tracer.span(any())).thenReturn(mockSpan); + + // When + httpClient.setTracer(tracer); + CompletableFuture futureResponse = httpClient.send(httpRequest); + capture.getValue().onResponse(call, response); + HttpResponse httpResponse = futureResponse.join(); + + // Then + assertNotNull(httpResponse); + assertEquals("{\"error\":\"Not Found\"}", httpResponse.bodyString()); + assertEquals(404, httpResponse.statusCode()); + assertEquals("Not Found", httpResponse.statusMessage()); + assertFalse(httpResponse.isSuccessful()); + if (tracer instanceof NoopTracer || tracer instanceof OpenTelemetryTracer) { + verify(tracer, times(1)).span(any(HttpRequest.class)); + + } else if (tracer instanceof OpenCensusTracer) { + verify(tracer, times(2)).span(any(HttpRequest.class)); + verify(mockSpan).addTag(TraceHelper.TraceTags.HTTP_URL, "https://example.com/"); + } + verify(mockSpan, times(1)).close(); + } +} diff --git a/src/test/java/com/spotify/github/jackson/GitHubInstantModuleTest.java b/src/test/java/com/spotify/github/jackson/GitHubInstantModuleTest.java index ddf60b36..7432720e 100644 --- a/src/test/java/com/spotify/github/jackson/GitHubInstantModuleTest.java +++ b/src/test/java/com/spotify/github/jackson/GitHubInstantModuleTest.java @@ -26,14 +26,14 @@ import com.spotify.github.GitHubInstant; import java.io.IOException; import java.time.Instant; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class GitHubInstantModuleTest { private Json mapper; - @Before + @BeforeEach public void setUp() throws Exception { mapper = Json.create(); } diff --git a/src/test/java/com/spotify/github/tracing/OcTestExportHandler.java b/src/test/java/com/spotify/github/tracing/OcTestExportHandler.java new file mode 100644 index 00000000..b5919c99 --- /dev/null +++ b/src/test/java/com/spotify/github/tracing/OcTestExportHandler.java @@ -0,0 +1,82 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +import io.opencensus.trace.export.SpanData; +import io.opencensus.trace.export.SpanExporter; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A dummy SpanExporter.Handler which keeps any exported Spans in memory, so we can query against + * them in tests. + * + *

The opencensus-testing library has a TestHandler that can be used in tests like this, but the + * only method it exposes to gain access to the received spans is waitForExport(int) which blocks + * forever until the given number of spans is exported, which could be never. So instead we define + * our own very simple implementation. + */ +class OcTestExportHandler extends SpanExporter.Handler { + private static final Logger LOG = LoggerFactory.getLogger(OcTestExportHandler.class); + + private final List receivedSpans = new ArrayList<>(); + private final Object lock = new Object(); + + @Override + public void export(final Collection spanDataList) { + synchronized (lock) { + receivedSpans.addAll(spanDataList); + LOG.info("received {} spans, {} total", spanDataList.size(), receivedSpans.size()); + } + } + + List receivedSpans() { + synchronized (lock) { + return new ArrayList<>(receivedSpans); + } + } + + /** Wait up to waitTime for at least `count` spans to be exported */ + List waitForSpansToBeExported(final int count) throws InterruptedException { + // opencensus is hardcoded to export batches every 5 seconds (see + // io.opencensus.implcore.trace.export.ExportComponentImpl), so wait slightly longer than that + Duration waitTime = Duration.ofSeconds(7); + Instant deadline = Instant.now().plus(waitTime); + + List spanData = receivedSpans(); + while (spanData.size() < count) { + //noinspection BusyWait + Thread.sleep(100); + spanData = receivedSpans(); + + if (!Instant.now().isBefore(deadline)) { + LOG.warn("ending busy wait for spans because deadline passed"); + break; + } + } + return spanData; + } +} diff --git a/src/test/java/com/spotify/github/tracing/OpenCensusSpanTest.java b/src/test/java/com/spotify/github/tracing/OpenCensusSpanTest.java new file mode 100644 index 00000000..f53d4505 --- /dev/null +++ b/src/test/java/com/spotify/github/tracing/OpenCensusSpanTest.java @@ -0,0 +1,160 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +import com.spotify.github.tracing.opencensus.OpenCensusSpan; +import com.spotify.github.v3.exceptions.RequestNotOkException; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.Status; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class OpenCensusSpanTest { + private final io.opencensus.trace.Span wrapped = mock(io.opencensus.trace.Span.class); + + @Test + public void succeed() { + final Span span = new OpenCensusSpan(wrapped); + span.success(); + span.close(); + + verify(wrapped).setStatus(Status.OK); + verify(wrapped).end(); + } + + @Test + public void fail() { + final Span span = new OpenCensusSpan(wrapped); + span.failure( + new RequestNotOkException("method", "path", 404, "Not found", Collections.emptyMap())); + span.close(); + + verify(wrapped).setStatus(Status.UNKNOWN); + verify(wrapped).putAttribute("http.status_code", AttributeValue.longAttributeValue(404)); + verify(wrapped).end(); + } + + @Test + public void failOnServerError() { + final Span span = new OpenCensusSpan(wrapped); + span.failure( + new RequestNotOkException( + "method", "path", 500, "Internal Server Error", Collections.emptyMap())); + span.close(); + + verify(wrapped).setStatus(Status.UNKNOWN); + verify(wrapped).putAttribute("http.status_code", AttributeValue.longAttributeValue(500)); + verify(wrapped).putAttribute("error", AttributeValue.booleanAttributeValue(true)); + verify(wrapped).end(); + } + + @Test + public void addTags() { + final Span span = new OpenCensusSpan(wrapped); + span.addTag("key", "value"); + span.addTag("key", true); + span.addTag("key", 42L); + span.close(); + + verify(wrapped).putAttribute("key", AttributeValue.stringAttributeValue("value")); + verify(wrapped).putAttribute("key", AttributeValue.booleanAttributeValue(true)); + verify(wrapped).putAttribute("key", AttributeValue.longAttributeValue(42L)); + verify(wrapped).end(); + } + + @Test + public void addEvent() { + final Span span = new OpenCensusSpan(wrapped); + span.addEvent("description"); + span.close(); + + verify(wrapped).addAnnotation("description"); + verify(wrapped).end(); + } + + @Test + @SuppressWarnings("deprecation") + public void succeedDeprecated() { + final Span span = new com.spotify.github.opencensus.OpenCensusSpan(wrapped); + span.success(); + span.close(); + + verify(wrapped).setStatus(Status.OK); + verify(wrapped).end(); + } + + @Test + @SuppressWarnings("deprecation") + public void failDeprecated() { + final Span span = new com.spotify.github.opencensus.OpenCensusSpan(wrapped); + span.failure( + new RequestNotOkException("method", "path", 404, "Not found", Collections.emptyMap())); + span.close(); + + verify(wrapped).setStatus(Status.UNKNOWN); + verify(wrapped).putAttribute("http.status_code", AttributeValue.longAttributeValue(404)); + verify(wrapped).end(); + } + + @Test + @SuppressWarnings("deprecation") + public void failOnServerErrorDeprecated() { + final Span span = new com.spotify.github.opencensus.OpenCensusSpan(wrapped); + span.failure( + new RequestNotOkException( + "method", "path", 500, "Internal Server Error", Collections.emptyMap())); + span.close(); + + verify(wrapped).setStatus(Status.UNKNOWN); + verify(wrapped).putAttribute("http.status_code", AttributeValue.longAttributeValue(500)); + verify(wrapped).putAttribute("error", AttributeValue.booleanAttributeValue(true)); + verify(wrapped).end(); + } + + @Test + @SuppressWarnings("deprecation") + public void addTagsDeprecated() { + final Span span = new com.spotify.github.opencensus.OpenCensusSpan(wrapped); + span.addTag("key", "value"); + span.addTag("key", true); + span.addTag("key", 42L); + span.close(); + + verify(wrapped).putAttribute("key", AttributeValue.stringAttributeValue("value")); + verify(wrapped).putAttribute("key", AttributeValue.booleanAttributeValue(true)); + verify(wrapped).putAttribute("key", AttributeValue.longAttributeValue(42L)); + verify(wrapped).end(); + } + + @Test + @SuppressWarnings("deprecation") + public void addEventDeprecated() { + final Span span = new com.spotify.github.opencensus.OpenCensusSpan(wrapped); + span.addEvent("description"); + span.close(); + + verify(wrapped).addAnnotation("description"); + verify(wrapped).end(); + } +} diff --git a/src/test/java/com/spotify/github/tracing/OpenCensusTracerTest.java b/src/test/java/com/spotify/github/tracing/OpenCensusTracerTest.java new file mode 100644 index 00000000..bd7f2bda --- /dev/null +++ b/src/test/java/com/spotify/github/tracing/OpenCensusTracerTest.java @@ -0,0 +1,177 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +import com.spotify.github.http.HttpRequest; +import com.spotify.github.tracing.opencensus.OpenCensusTracer; +import io.grpc.Context; +import io.opencensus.trace.Span; +import io.opencensus.trace.*; +import io.opencensus.trace.config.TraceConfig; +import io.opencensus.trace.config.TraceParams; +import io.opencensus.trace.export.SpanData; +import io.opencensus.trace.samplers.Samplers; +import io.opencensus.trace.unsafe.ContextUtils; +import okhttp3.Call; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static io.opencensus.trace.AttributeValue.stringAttributeValue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class OpenCensusTracerTest { + + private final String rootSpanName = "root span"; + private OcTestExportHandler spanExporterHandler; + + /** + * Test that trace() a) returns a future that completes when the input future completes and b) + * sets up the Spans appropriately so that the Span for the operation is exported with the + * rootSpan set as the parent. + */ + @ParameterizedTest + @ValueSource(strings = {"GET", "POST", "PUT", "DELETE"}) + public void traceCompletionStageSimple(final String requestMethod) throws Exception { + io.opencensus.trace.Span rootSpan = startRootSpan(); + final CompletableFuture future = new CompletableFuture<>(); + OpenCensusTracer tracer = new OpenCensusTracer(); + + tracer.span("path", requestMethod, future); + future.complete("all done"); + rootSpan.end(); + + List exportedSpans = spanExporterHandler.waitForSpansToBeExported(2); + assertEquals(2, exportedSpans.size()); + + SpanData root = findSpan(exportedSpans, rootSpanName); + SpanData inner = findSpan(exportedSpans, "GitHub Request"); + + assertEquals(root.getContext().getTraceId(), inner.getContext().getTraceId()); + assertEquals(root.getContext().getSpanId(), inner.getParentSpanId()); + final Map attributes = inner.getAttributes().getAttributeMap(); + assertEquals(stringAttributeValue("github-api-client"), attributes.get("component")); + assertEquals(stringAttributeValue("github"), attributes.get("peer.service")); + assertEquals(stringAttributeValue("path"), attributes.get("http.url")); + assertEquals(stringAttributeValue(requestMethod), attributes.get("method")); + assertEquals(Status.OK, inner.getStatus()); + } + + @ParameterizedTest + @ValueSource(strings = {"GET", "POST", "PUT", "DELETE"}) + public void traceCompletionStageFails(final String requestMethod) throws Exception { + io.opencensus.trace.Span rootSpan = startRootSpan(); + final CompletableFuture future = new CompletableFuture<>(); + OpenCensusTracer tracer = new OpenCensusTracer(); + + tracer.span("path", requestMethod, future); + future.completeExceptionally(new Exception("GitHub failed!")); + rootSpan.end(); + + List exportedSpans = spanExporterHandler.waitForSpansToBeExported(2); + assertEquals(2, exportedSpans.size()); + + SpanData root = findSpan(exportedSpans, rootSpanName); + SpanData inner = findSpan(exportedSpans, "GitHub Request"); + + assertEquals(root.getContext().getTraceId(), inner.getContext().getTraceId()); + assertEquals(root.getContext().getSpanId(), inner.getParentSpanId()); + final Map attributes = inner.getAttributes().getAttributeMap(); + assertEquals(stringAttributeValue("github-api-client"), attributes.get("component")); + assertEquals(stringAttributeValue("github"), attributes.get("peer.service")); + assertEquals(stringAttributeValue("path"), attributes.get("http.url")); + assertEquals(stringAttributeValue(requestMethod), attributes.get("method")); + assertEquals(Status.UNKNOWN, inner.getStatus()); + } + + @ParameterizedTest + @ValueSource(strings = {"GET", "POST", "PUT", "DELETE"}) + public void traceCompletionStageWithRequest(final String requestMethod) throws Exception { + io.opencensus.trace.Span rootSpan = startRootSpan(); + OpenCensusTracer tracer = new OpenCensusTracer(); + final CompletableFuture future = new CompletableFuture<>(); + HttpRequest mockRequest = mock(HttpRequest.class); + when(mockRequest.url()).thenReturn("https://api.github.com/repos/spotify/github-java-client"); + when(mockRequest.method()).thenReturn(requestMethod); + + try (com.spotify.github.tracing.Span span = tracer.span(mockRequest)) { + tracer.attachSpanToFuture(span, future); + future.complete("all done"); + } + rootSpan.end(); + + List exportedSpans = spanExporterHandler.waitForSpansToBeExported(2); + assertEquals(2, exportedSpans.size()); + + SpanData root = findSpan(exportedSpans, rootSpanName); + SpanData inner = findSpan(exportedSpans, "GitHub Request"); + + assertEquals(root.getContext().getTraceId(), inner.getContext().getTraceId()); + assertEquals(root.getContext().getSpanId(), inner.getParentSpanId()); + final Map attributes = inner.getAttributes().getAttributeMap(); + assertEquals(stringAttributeValue("github-api-client"), attributes.get("component")); + assertEquals(stringAttributeValue("github"), attributes.get("peer.service")); + assertEquals( + stringAttributeValue("https://api.github.com/repos/spotify/github-java-client"), + attributes.get("http.url")); + assertEquals(stringAttributeValue(requestMethod), attributes.get("method")); + assertEquals(Status.OK, inner.getStatus()); + } + + @SuppressWarnings("deprecation") + private io.opencensus.trace.Span startRootSpan() { + Span rootSpan = Tracing.getTracer().spanBuilder(rootSpanName).startSpan(); + Context context = ContextUtils.withValue(Context.current(), rootSpan); + context.attach(); + return rootSpan; + } + + private SpanData findSpan(final List spans, final String name) { + return spans.stream().filter(s -> s.getName().equals(name)).findFirst().get(); + } + + @BeforeEach + public void setUpExporter() { + spanExporterHandler = new OcTestExportHandler(); + Tracing.getExportComponent().getSpanExporter().registerHandler("test", spanExporterHandler); + } + + @BeforeAll + public static void setupTracing() { + final TraceConfig traceConfig = Tracing.getTraceConfig(); + final Sampler sampler = Samplers.alwaysSample(); + final TraceParams newParams = + traceConfig.getActiveTraceParams().toBuilder().setSampler(sampler).build(); + traceConfig.updateActiveTraceParams(newParams); + } +} diff --git a/src/test/java/com/spotify/github/tracing/OpenTelemetrySpanTest.java b/src/test/java/com/spotify/github/tracing/OpenTelemetrySpanTest.java new file mode 100644 index 00000000..41223174 --- /dev/null +++ b/src/test/java/com/spotify/github/tracing/OpenTelemetrySpanTest.java @@ -0,0 +1,117 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +import com.spotify.github.tracing.opentelemetry.OpenTelemetrySpan; +import com.spotify.github.v3.exceptions.RequestNotOkException; +import io.opentelemetry.api.trace.StatusCode; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +import static org.mockito.Mockito.*; + +class OpenTelemetrySpanTest { + private final io.opentelemetry.api.trace.Span wrapped = + mock(io.opentelemetry.api.trace.Span.class); + + @Test + public void succeed() { + final Span span = new OpenTelemetrySpan(wrapped); + span.success(); + span.close(); + + verify(wrapped).setStatus(StatusCode.OK); + verify(wrapped).end(); + } + + @Test + public void fail() { + final Span span = new OpenTelemetrySpan(wrapped); + span.failure( + new RequestNotOkException("method", "path", 404, "Not found", Collections.emptyMap())); + span.close(); + + verify(wrapped).setStatus(StatusCode.ERROR); + verify(wrapped).setAttribute("http.status_code", 404); + verify(wrapped).end(); + } + + @Test + public void failOnServerError() { + final Span span = new OpenTelemetrySpan(wrapped); + span.failure( + new RequestNotOkException( + "method", "path", 500, "Internal Server Error", Collections.emptyMap())); + span.close(); + + verify(wrapped).setStatus(StatusCode.ERROR); + verify(wrapped).setAttribute("http.status_code", 500); + verify(wrapped).setAttribute("error", true); + verify(wrapped).end(); + } + + @Test + public void failWithNullThrowable() { + final Span span = new OpenTelemetrySpan(wrapped); + span.failure(null); + span.close(); + + verify(wrapped).setStatus(StatusCode.ERROR); + verify(wrapped, never()).setAttribute(anyString(), any()); + verify(wrapped).end(); + } + + @Test + public void failWithNonRequestNotOkException() { + final Span span = new OpenTelemetrySpan(wrapped); + span.failure(new RuntimeException("Unexpected error")); + span.close(); + + verify(wrapped).setStatus(StatusCode.ERROR); + verify(wrapped, never()).setAttribute("http.status_code", 404); + verify(wrapped).setAttribute("error", true); + verify(wrapped).end(); + } + + @Test + public void addTags() { + final Span span = new OpenTelemetrySpan(wrapped); + span.addTag("key", "value"); + span.addTag("key", true); + span.addTag("key", 42L); + span.close(); + + verify(wrapped).setAttribute("key", "value"); + verify(wrapped).setAttribute("key", true); + verify(wrapped).setAttribute("key", 42L); + } + + @Test + public void addEvent() { + final Span span = new OpenTelemetrySpan(wrapped); + span.addEvent("description"); + span.close(); + + verify(wrapped).addEvent("description"); + verify(wrapped).end(); + } +} diff --git a/src/test/java/com/spotify/github/tracing/OpenTelemetryTracerTest.java b/src/test/java/com/spotify/github/tracing/OpenTelemetryTracerTest.java new file mode 100644 index 00000000..7db54f9c --- /dev/null +++ b/src/test/java/com/spotify/github/tracing/OpenTelemetryTracerTest.java @@ -0,0 +1,187 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +import com.spotify.github.http.HttpRequest; +import com.spotify.github.http.ImmutableHttpRequest; +import com.spotify.github.tracing.opentelemetry.OpenTelemetryTracer; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import okhttp3.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class OpenTelemetryTracerTest { + + private final String rootSpanName = "root span"; + private static OtTestExportHandler spanExporterHandler; + private final OpenTelemetry openTelemetry = GlobalOpenTelemetry.get(); + private final Tracer tracer = openTelemetry.getTracer("github-java-client-test"); + + /** + * Test that trace() a) returns a future that completes when the input future completes and b) + * sets up the Spans appropriately so that the Span for the operation is exported with the + * rootSpan set as the parent. + */ + @ParameterizedTest + @ValueSource(strings = {"GET", "POST", "PUT", "DELETE"}) + public void traceCompletionStageSimple(final String requestMethod) throws Exception { + Span rootSpan = startRootSpan(); + final CompletableFuture future = new CompletableFuture<>(); + OpenTelemetryTracer tracer = new OpenTelemetryTracer(); + + tracer.span("path", requestMethod, future); + future.complete("all done"); + rootSpan.end(); + + List exportedSpans = spanExporterHandler.waitForSpansToBeExported(2); + assertEquals(2, exportedSpans.size()); + + SpanData root = findSpan(exportedSpans, rootSpanName); + SpanData inner = findSpan(exportedSpans, "GitHub Request"); + + assertEquals(root.getSpanContext().getTraceId(), inner.getSpanContext().getTraceId()); + assertEquals(root.getSpanContext().getSpanId(), inner.getParentSpanId()); + final Attributes attributes = inner.getAttributes(); + assertEquals("github-api-client", attributes.get(AttributeKey.stringKey("component"))); + assertEquals("github", attributes.get(AttributeKey.stringKey("peer.service"))); + assertEquals("path", attributes.get(AttributeKey.stringKey("http.url"))); + assertEquals(requestMethod, attributes.get(AttributeKey.stringKey("method"))); + assertEquals(StatusCode.OK, inner.getStatus().getStatusCode()); + } + + @ParameterizedTest + @ValueSource(strings = {"GET", "POST", "PUT", "DELETE"}) + public void traceCompletionStageFails(final String requestMethod) throws Exception { + Span rootSpan = startRootSpan(); + final CompletableFuture future = new CompletableFuture<>(); + OpenTelemetryTracer tracer = new OpenTelemetryTracer(); + + tracer.span("path", requestMethod, future); + future.completeExceptionally(new Exception("GitHub failed!")); + rootSpan.end(); + + List exportedSpans = spanExporterHandler.waitForSpansToBeExported(2); + assertEquals(2, exportedSpans.size()); + + SpanData root = findSpan(exportedSpans, rootSpanName); + SpanData inner = findSpan(exportedSpans, "GitHub Request"); + + assertEquals(root.getSpanContext().getTraceId(), inner.getSpanContext().getTraceId()); + assertEquals(root.getSpanContext().getSpanId(), inner.getParentSpanId()); + final Attributes attributes = inner.getAttributes(); + assertEquals("github-api-client", attributes.get(AttributeKey.stringKey("component"))); + assertEquals("github", attributes.get(AttributeKey.stringKey("peer.service"))); + assertEquals("path", attributes.get(AttributeKey.stringKey("http.url"))); + assertEquals(requestMethod, attributes.get(AttributeKey.stringKey("method"))); + assertEquals(StatusCode.ERROR, inner.getStatus().getStatusCode()); + } + + @ParameterizedTest + @ValueSource(strings = {"GET", "POST", "PUT", "DELETE"}) + public void traceCompletionStageWithRequest(final String requestMethod) throws Exception { + Span rootSpan = startRootSpan(); + final CompletableFuture future = new CompletableFuture<>(); + OpenTelemetryTracer tracer = new OpenTelemetryTracer(); + HttpRequest mockRequest = + ImmutableHttpRequest.builder() + .url("https://api.github.com/repos/spotify/github-java-client") + .method(requestMethod) + .body("") + .headers(Map.of()) + .build(); + + try (com.spotify.github.tracing.Span span = tracer.span(mockRequest)) { + tracer.attachSpanToFuture(span, future); + future.complete("all done"); + } + rootSpan.end(); + + List exportedSpans = spanExporterHandler.waitForSpansToBeExported(2); + assertEquals(2, exportedSpans.size()); + + SpanData root = findSpan(exportedSpans, rootSpanName); + SpanData inner = findSpan(exportedSpans, "GitHub Request"); + + assertEquals(root.getSpanContext().getTraceId(), inner.getSpanContext().getTraceId()); + assertEquals(root.getSpanContext().getSpanId(), inner.getParentSpanId()); + final Attributes attributes = inner.getAttributes(); + assertEquals("github-api-client", attributes.get(AttributeKey.stringKey("component"))); + assertEquals("github", attributes.get(AttributeKey.stringKey("peer.service"))); + assertEquals( + "https://api.github.com/repos/spotify/github-java-client", + attributes.get(AttributeKey.stringKey("http.url"))); + assertEquals(requestMethod, attributes.get(AttributeKey.stringKey("method"))); + assertEquals(StatusCode.OK, inner.getStatus().getStatusCode()); + } + + private Span startRootSpan() { + Span rootSpan = tracer.spanBuilder(rootSpanName).startSpan(); + Context context = Context.current().with(rootSpan); + context.makeCurrent(); + return rootSpan; + } + + private SpanData findSpan(final List spans, final String name) { + return spans.stream().filter(s -> s.getName().equals(name)).findFirst().get(); + } + + @AfterEach + public void flushSpans() { + spanExporterHandler.flush(); + } + + @BeforeAll + public static void setupTracing() { + spanExporterHandler = new OtTestExportHandler(); + SdkTracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(spanExporterHandler)) + .setSampler(Sampler.alwaysOn()) + .build(); + OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).buildAndRegisterGlobal(); + } +} diff --git a/src/test/java/com/spotify/github/tracing/OtTestExportHandler.java b/src/test/java/com/spotify/github/tracing/OtTestExportHandler.java new file mode 100644 index 00000000..5e68285f --- /dev/null +++ b/src/test/java/com/spotify/github/tracing/OtTestExportHandler.java @@ -0,0 +1,93 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.tracing; + +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A dummy SpanExporter.Handler which keeps any exported Spans in memory, so we can query against + * them in tests. + * + *

The opencensus-testing library has a TestHandler that can be used in tests like this, but the + * only method it exposes to gain access to the received spans is waitForExport(int) which blocks + * forever until the given number of spans is exported, which could be never. So instead we define + * our own very simple implementation. + */ +class OtTestExportHandler implements SpanExporter { + private static final Logger LOG = LoggerFactory.getLogger(OtTestExportHandler.class); + + private final List receivedSpans = new ArrayList<>(); + private final Object lock = new Object(); + @Override + public CompletableResultCode export(Collection spanDataList) { + synchronized (lock) { + receivedSpans.addAll(spanDataList); + LOG.info("received {} spans, {} total", spanDataList.size(), receivedSpans.size()); + } + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode flush() { + this.receivedSpans.clear(); + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + return CompletableResultCode.ofSuccess(); + } + + List receivedSpans() { + synchronized (lock) { + return new ArrayList<>(receivedSpans); + } + } + + /** Wait up to waitTime for at least `count` spans to be exported */ + List waitForSpansToBeExported(final int count) throws InterruptedException { + Duration waitTime = Duration.ofSeconds(7); + Instant deadline = Instant.now().plus(waitTime); + + List spanData = receivedSpans(); + while (spanData.size() < count) { + //noinspection BusyWait + Thread.sleep(100); + spanData = receivedSpans(); + + if (!Instant.now().isBefore(deadline)) { + LOG.warn("ending busy wait for spans because deadline passed"); + break; + } + } + return spanData; + } +} diff --git a/src/test/java/com/spotify/github/v3/TeamTest.java b/src/test/java/com/spotify/github/v3/TeamTest.java index 95cbd94a..d6c48f0c 100644 --- a/src/test/java/com/spotify/github/v3/TeamTest.java +++ b/src/test/java/com/spotify/github/v3/TeamTest.java @@ -29,9 +29,8 @@ import com.spotify.github.jackson.Json; import java.io.IOException; import java.net.URI; -import java.util.Optional; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class TeamTest { @@ -50,7 +49,7 @@ public static final void assertTeam(final Team team) { assertThat(team.repositoriesUrl(), is(URI.create(team.url() + "/repos"))); } - @Before + @BeforeEach public void setUp() throws Exception { fixture = Resources.toString(getResource(this.getClass(), "team.json"), defaultCharset()); } diff --git a/src/test/java/com/spotify/github/v3/TreeItemTest.java b/src/test/java/com/spotify/github/v3/TreeItemTest.java new file mode 100644 index 00000000..4d9c71c4 --- /dev/null +++ b/src/test/java/com/spotify/github/v3/TreeItemTest.java @@ -0,0 +1,57 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2021 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3; + +import static com.google.common.io.Resources.getResource; +import static java.nio.charset.Charset.defaultCharset; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +import com.google.common.io.Resources; +import com.spotify.github.jackson.Json; +import com.spotify.github.v3.git.TreeItem; +import java.io.IOException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class TreeItemTest { + + private String fixture; + public static final void assertTreeItem(final TreeItem treeItem) { + assertThat(treeItem.path(), is("README.md")); + assertThat(treeItem.mode(), is("100644")); + assertThat(treeItem.type(), is("blob")); + assertThat(treeItem.size(), is(12L)); + } + + @BeforeEach + public void setUp() throws Exception { + fixture = Resources.toString(getResource(this.getClass(), "treeItem.json"), defaultCharset()); + } + + @Test + public void testDeserialization() throws IOException { + final TreeItem treeItem = Json.create().fromJson(fixture, TreeItem.class); + assertTreeItem(treeItem); + } + + +} diff --git a/src/test/java/com/spotify/github/v3/UserTest.java b/src/test/java/com/spotify/github/v3/UserTest.java index 3d268cb1..4e9578d2 100644 --- a/src/test/java/com/spotify/github/v3/UserTest.java +++ b/src/test/java/com/spotify/github/v3/UserTest.java @@ -30,8 +30,8 @@ import java.io.IOException; import java.net.URI; import java.util.Optional; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class UserTest { @@ -50,7 +50,7 @@ public static final void assertUser(final User user) { assertThat(user.siteAdmin().get(), is(false)); } - @Before + @BeforeEach public void setUp() throws Exception { fixture = Resources.toString(getResource(this.getClass(), "user.json"), defaultCharset()); } diff --git a/src/test/java/com/spotify/github/v3/activity/events/CheckRunEventTest.java b/src/test/java/com/spotify/github/v3/activity/events/CheckRunEventTest.java new file mode 100644 index 00000000..3ba9e507 --- /dev/null +++ b/src/test/java/com/spotify/github/v3/activity/events/CheckRunEventTest.java @@ -0,0 +1,49 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.activity.events; + +import static com.google.common.io.Resources.getResource; +import static java.nio.charset.Charset.defaultCharset; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +import com.google.common.io.Resources; +import com.spotify.github.jackson.Json; + +import java.io.IOException; +import org.junit.jupiter.api.Test; + +public class CheckRunEventTest { + + @Test + public void testDeserialization() throws IOException { + // sample payload from https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads + String fixture = + Resources.toString( + getResource(this.getClass(), "fixtures/check_run_event.json"), defaultCharset()); + final CheckRunEvent checkRunEvent = Json.create().fromJson(fixture, CheckRunEvent.class); + assertThat(checkRunEvent.action(), is("created")); + assertThat(checkRunEvent.checkRun().name(), is("Octocoders-linter")); + assertThat(checkRunEvent.repository().name(), is("Hello-World")); + assertThat(checkRunEvent.checkRun().checkSuite().get().headBranch().get(), is("changes")); + } + +} \ No newline at end of file diff --git a/src/test/java/com/spotify/github/v3/activity/events/IssueCommentEventTest.java b/src/test/java/com/spotify/github/v3/activity/events/IssueCommentEventTest.java new file mode 100644 index 00000000..15d1c78d --- /dev/null +++ b/src/test/java/com/spotify/github/v3/activity/events/IssueCommentEventTest.java @@ -0,0 +1,49 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.activity.events; + +import java.io.IOException; +import static java.nio.charset.Charset.defaultCharset; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import org.junit.jupiter.api.Test; + +import com.google.common.io.Resources; +import static com.google.common.io.Resources.getResource; +import com.spotify.github.jackson.Json; + +public class IssueCommentEventTest { + + @Test + public void testDeserialization() throws IOException { + String fixture = + Resources.toString( + getResource(this.getClass(), "fixtures/issue_comment_event.json"), defaultCharset()); + final IssueCommentEvent event = Json.create().fromJson(fixture, IssueCommentEvent.class); + assertThat(event.action(), is("created")); + assertThat(event.issue().number(), is(2L)); + assertThat(event.comment().id(), is(99262140L)); + assertThat(event.comment().nodeId(), is("asd123")); + assertThat( + event.comment().body(), is("You are totally right! I'll get this fixed right away.")); + } +} diff --git a/src/test/java/com/spotify/github/v3/activity/events/MergeGroupEventTest.java b/src/test/java/com/spotify/github/v3/activity/events/MergeGroupEventTest.java new file mode 100644 index 00000000..c10c79e9 --- /dev/null +++ b/src/test/java/com/spotify/github/v3/activity/events/MergeGroupEventTest.java @@ -0,0 +1,53 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.activity.events; + +import static com.google.common.io.Resources.getResource; +import static java.nio.charset.Charset.defaultCharset; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; + +import com.google.common.io.Resources; +import com.spotify.github.jackson.Json; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +public class MergeGroupEventTest { + @Test + public void testDeserialization() throws IOException { + String fixture = + Resources.toString( + getResource(this.getClass(), "fixtures/merge_group_event.json"), + defaultCharset()); + final MergeGroupEvent mergeGroupEvent = + Json.create().fromJson(fixture, MergeGroupEvent.class); + assertThat(mergeGroupEvent.action(), is("checks_requested")); + assertThat(mergeGroupEvent.mergeGroup(), notNullValue()); + assertThat(mergeGroupEvent.mergeGroup().headSha(), is("cd84187b3e9a3e8f5b5f5b5f5b5f5b5f5b5f5b5f")); + assertThat(mergeGroupEvent.mergeGroup().headRef(), is("refs/heads/gh-readonly-queue/main/pr-123-cd84187b3e9a3e8f5b5f5b5f5b5f5b5f5b5f5b5f")); + assertThat(mergeGroupEvent.mergeGroup().baseSha(), is("9049f1265b7d61be4a8904a9a27120d2064dab3b")); + assertThat(mergeGroupEvent.mergeGroup().baseRef(), is("refs/heads/main")); + assertThat(mergeGroupEvent.repository(), notNullValue()); + assertThat(mergeGroupEvent.repository().name(), is("public-repo")); + assertThat(mergeGroupEvent.repository().owner().login(), is("baxterthehacker")); + } +} \ No newline at end of file diff --git a/src/test/java/com/spotify/github/v3/activity/events/PullRequestEventTest.java b/src/test/java/com/spotify/github/v3/activity/events/PullRequestEventTest.java index b558428d..f07bc2ca 100644 --- a/src/test/java/com/spotify/github/v3/activity/events/PullRequestEventTest.java +++ b/src/test/java/com/spotify/github/v3/activity/events/PullRequestEventTest.java @@ -29,7 +29,7 @@ import com.spotify.github.jackson.Json; import com.spotify.github.v3.prs.PullRequestActionState; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class PullRequestEventTest { diff --git a/src/test/java/com/spotify/github/v3/activity/events/PullRequestReviewCommentEventTest.java b/src/test/java/com/spotify/github/v3/activity/events/PullRequestReviewCommentEventTest.java new file mode 100644 index 00000000..96bfde99 --- /dev/null +++ b/src/test/java/com/spotify/github/v3/activity/events/PullRequestReviewCommentEventTest.java @@ -0,0 +1,60 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.activity.events; + +import java.io.IOException; +import static java.nio.charset.Charset.defaultCharset; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import org.junit.jupiter.api.Test; + +import com.google.common.io.Resources; +import static com.google.common.io.Resources.getResource; +import com.spotify.github.jackson.Json; + +public class PullRequestReviewCommentEventTest { + @Test + public void testDeserialization() throws IOException { + String fixture = + Resources.toString( + getResource(this.getClass(), "fixtures/pull_request_review_comment_event.json"), + defaultCharset()); + final PullRequestReviewCommentEvent event = + Json.create().fromJson(fixture, PullRequestReviewCommentEvent.class); + assertThat(event.action(), is("created")); + assertThat(event.comment().id(), is(29724692L)); + assertThat(event.comment().nodeId(), is("abc234")); + assertThat(event.pullRequest().nodeId(), is("abc123")); + assertThat(event.comment().body(), is("Maybe you should use more emojji on this line.")); + assertThat(event.comment().originalCommitId(), is("0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c")); + assertThat(event.comment().originalLine(), is(1)); + assertThat(event.comment().originalPosition(), is(1)); + assertThat(event.comment().originalStartLine(), is(1)); + assertThat(event.comment().line(), is(1)); + assertThat(event.comment().side(), is("RIGHT")); + assertThat(event.comment().startLine(), is(1)); + assertThat(event.comment().startSide(), is("RIGHT")); + assertThat(event.comment().authorAssociation(), is("NONE")); + assertThat(event.comment().pullRequestReviewId(), is(42L)); + assertThat(event.comment().inReplyToId(), is(426899381L)); + } +} diff --git a/src/test/java/com/spotify/github/v3/activity/events/PullRequestReviewEventTest.java b/src/test/java/com/spotify/github/v3/activity/events/PullRequestReviewEventTest.java index ec4640ae..4afb8fbf 100644 --- a/src/test/java/com/spotify/github/v3/activity/events/PullRequestReviewEventTest.java +++ b/src/test/java/com/spotify/github/v3/activity/events/PullRequestReviewEventTest.java @@ -28,7 +28,7 @@ import com.google.common.io.Resources; import com.spotify.github.jackson.Json; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class PullRequestReviewEventTest { @Test @@ -40,7 +40,7 @@ public void testDeserialization() throws IOException { final PullRequestReviewEvent statusEvent = Json.create().fromJson(fixture, PullRequestReviewEvent.class); assertThat(statusEvent.action(), is("submitted")); - assertThat(statusEvent.pullRequest().number(), is(8)); + assertThat(statusEvent.pullRequest().number(), is(8L)); assertThat(statusEvent.review().state(), is(ReviewState.APPROVED)); } } diff --git a/src/test/java/com/spotify/github/v3/activity/events/StatusEventTest.java b/src/test/java/com/spotify/github/v3/activity/events/StatusEventTest.java index 0a686533..2a195d41 100644 --- a/src/test/java/com/spotify/github/v3/activity/events/StatusEventTest.java +++ b/src/test/java/com/spotify/github/v3/activity/events/StatusEventTest.java @@ -28,7 +28,7 @@ import com.google.common.io.Resources; import com.spotify.github.jackson.Json; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class StatusEventTest { @Test diff --git a/src/test/java/com/spotify/github/v3/checks/AccessTokenTest.java b/src/test/java/com/spotify/github/v3/checks/AccessTokenTest.java index 0d62c6ff..0e404040 100644 --- a/src/test/java/com/spotify/github/v3/checks/AccessTokenTest.java +++ b/src/test/java/com/spotify/github/v3/checks/AccessTokenTest.java @@ -27,7 +27,7 @@ import com.spotify.github.jackson.Json; import java.io.IOException; import java.time.ZonedDateTime; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class AccessTokenTest { private final Json json = Json.create(); diff --git a/src/test/java/com/spotify/github/v3/checks/AnnotationTest.java b/src/test/java/com/spotify/github/v3/checks/AnnotationTest.java new file mode 100644 index 00000000..46730a28 --- /dev/null +++ b/src/test/java/com/spotify/github/v3/checks/AnnotationTest.java @@ -0,0 +1,106 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2022 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.checks; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.spotify.github.jackson.Json; +import com.spotify.github.v3.checks.ImmutableAnnotation.Builder; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class AnnotationTest { + private Builder builder() { + return ImmutableAnnotation.builder() + .title("title") + .message("message") + .rawDetails("rawDetails") + .path("path") + .startLine(1) + .endLine(2) + .startColumn(1) + .endColumn(9) + .annotationLevel(AnnotationLevel.notice); + } + + @Test + public void allowsCreationWithinLimits(){ + builder().build(); + + builder() + .title("a".repeat(255)) + .message("a".repeat(64000)) + .rawDetails("a".repeat(64000)) + .build(); + } + + @Test + public void failsCreationWhenMaxLengthExceeded(){ + assertThrows(IllegalStateException.class, () -> + builder().title("a".repeat(256)).build() + ); + assertThrows(IllegalStateException.class, () -> + builder().message("a".repeat(66000)).build() + ); + assertThrows(IllegalStateException.class, () -> + builder().rawDetails("a".repeat(66000)).build() + ); + } + + @Test + public void serializesWithEmptyFields() { + Annotation annotationWithEmptyStringFields = ImmutableAnnotation.builder() + .message("") + .path("") + .title("") + .startLine(1) + .endLine(2) + .annotationLevel(AnnotationLevel.notice) + .build(); + + String serializedAnnotation = Json.create().toJsonUnchecked(annotationWithEmptyStringFields); + String expected = "{\"path\":\"\",\"annotation_level\":\"notice\",\"message\":\"\",\"title\":\"\",\"start_line\":1,\"end_line\":2}"; + assertThat(serializedAnnotation, is(expected)); + } + + @Test + public void clearsColumnFieldsForMultiLineAnnotation() { + Annotation multiLineAnnotation = builder().startLine(1).endLine(2).build(); + Assertions.assertTrue(multiLineAnnotation.startColumn().isEmpty()); + Assertions.assertTrue(multiLineAnnotation.endColumn().isEmpty()); + + Annotation anotherMultiLineAnnotation = builder().startLine(1).endLine(2).startColumn(Optional.empty()).endColumn(1).build(); + Assertions.assertTrue(anotherMultiLineAnnotation.startColumn().isEmpty()); + Assertions.assertTrue(anotherMultiLineAnnotation.endColumn().isEmpty()); + + Annotation yetAnotherMultiLineAnnotation = builder().startLine(1).endLine(2).startColumn(1).endColumn(Optional.empty()).build(); + Assertions.assertTrue(yetAnotherMultiLineAnnotation.startColumn().isEmpty()); + Assertions.assertTrue(yetAnotherMultiLineAnnotation.endColumn().isEmpty()); + + Annotation singleLineAnnotation = builder().startLine(1).endLine(1).build(); + assertEquals(1, singleLineAnnotation.startColumn().orElse(0)); + assertEquals(9, singleLineAnnotation.endColumn().orElse(0)); + } +} diff --git a/src/test/java/com/spotify/github/v3/checks/CheckRunActionTest.java b/src/test/java/com/spotify/github/v3/checks/CheckRunActionTest.java new file mode 100644 index 00000000..49d487b9 --- /dev/null +++ b/src/test/java/com/spotify/github/v3/checks/CheckRunActionTest.java @@ -0,0 +1,59 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2022 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.checks; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.spotify.github.v3.checks.ImmutableCheckRunAction.Builder; +import org.junit.jupiter.api.Test; + +public class CheckRunActionTest { + private Builder builder() { + return ImmutableCheckRunAction.builder() + .label("label") + .identifier("identifier") + .description("description"); + } + + @Test + public void allowsCreationWithinLimits(){ + builder().build(); + + builder() + .label("a".repeat(20)) + .identifier("a".repeat(20)) + .description("a".repeat(40)) + .build(); + } + + @Test + public void failsCreationWhenMaxLengthExceeded(){ + assertThrows(IllegalStateException.class, () -> + builder().label("a".repeat(21)).build() + ); + assertThrows(IllegalStateException.class, () -> + builder().identifier("a".repeat(21)).build() + ); + assertThrows(IllegalStateException.class, () -> + builder().description("a".repeat(41)).build() + ); + } +} diff --git a/src/test/java/com/spotify/github/v3/checks/CheckRunOutputTest.java b/src/test/java/com/spotify/github/v3/checks/CheckRunOutputTest.java new file mode 100644 index 00000000..1bf0cfaa --- /dev/null +++ b/src/test/java/com/spotify/github/v3/checks/CheckRunOutputTest.java @@ -0,0 +1,49 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2022 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.checks; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.spotify.github.v3.checks.ImmutableCheckRunOutput.Builder; +import org.junit.jupiter.api.Test; + +public class CheckRunOutputTest { + + private Builder builder() { + return ImmutableCheckRunOutput.builder(); + } + + @Test + public void allowsCreationWithinLimits() { + builder().build(); + builder() + .text("t".repeat(65535)).summary("s".repeat(65535)).build(); + } + + @Test + public void failsCreationWhenMaxLengthExceeded() { + assertThrows(IllegalStateException.class, + () -> builder().text("t".repeat(65536)).build()); + assertThrows(IllegalStateException.class, + () -> builder().summary("s".repeat(65536)).build()); + } +} + diff --git a/src/test/java/com/spotify/github/v3/checks/CheckSuiteTest.java b/src/test/java/com/spotify/github/v3/checks/CheckSuiteTest.java new file mode 100644 index 00000000..8da6ab50 --- /dev/null +++ b/src/test/java/com/spotify/github/v3/checks/CheckSuiteTest.java @@ -0,0 +1,58 @@ +/*- + * -\-\- + * github-client + * -- + * Copyright (C) 2016 - 2022 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.checks; + +import static com.google.common.io.Resources.getResource; +import static java.nio.charset.Charset.defaultCharset; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.common.io.Resources; +import com.spotify.github.jackson.Json; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +public class CheckSuiteTest { + + @Test + public void testDeserialization() throws IOException { + // sample payload from https://docs.github.com/en/rest/checks/suites#list-check-suites-for-a-git-reference + String fixture = + Resources.toString( + getResource(this.getClass(), "check-suites-response.json"), defaultCharset()); + final CheckSuiteResponseList checkSuiteResponseList = Json.create().fromJson(fixture, CheckSuiteResponseList.class); + assertThat(checkSuiteResponseList.checkSuites().get(0).id(), is(5L)); + assertThat(checkSuiteResponseList.checkSuites().get(0).app().get().slug().get(), is("octoapp")); + } + + + @Test + public void testDeserializationWithLongId() throws IOException { + // sample payload from https://docs.github.com/en/rest/checks/suites#list-check-suites-for-a-git-reference + String fixture = + Resources.toString( + getResource(this.getClass(), "check-suites-response-long-id.json"), defaultCharset()); + final CheckSuiteResponseList checkSuiteResponseList = Json.create().fromJson(fixture, CheckSuiteResponseList.class); + assertThat(checkSuiteResponseList.checkSuites().get(0).id(), is(14707641936L)); + assertThat(checkSuiteResponseList.checkSuites().get(0).app().get().slug().get(), is("octoapp")); + } + +} \ No newline at end of file diff --git a/src/test/java/com/spotify/github/v3/clients/ChecksClientTest.java b/src/test/java/com/spotify/github/v3/clients/ChecksClientTest.java index 46cb2517..8c137faa 100644 --- a/src/test/java/com/spotify/github/v3/clients/ChecksClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/ChecksClientTest.java @@ -43,8 +43,8 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.util.concurrent.CompletableFuture; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class ChecksClientTest { @@ -61,7 +61,7 @@ public static String loadResource(final String path) { } } - @Before + @BeforeEach public void setUp() { github = mock(GitHubClient.class); checksClient = new ChecksClient(github, "someowner", "somerepo"); @@ -128,11 +128,26 @@ public void getCompletedCheckRun() throws Exception { final CompletableFuture actualResponse = checksClient.getCheckRun(4); assertThat(actualResponse.get().status(), is(completed)); - assertThat(actualResponse.get().id(), is(4)); + assertThat(actualResponse.get().id(), is(4L)); assertThat(actualResponse.get().headSha(), is("ce587453ced02b1526dfb4cb910479d431683101")); assertThat(actualResponse.get().output().annotationsCount().get(), is(2)); } + @Test + public void getCompletedCheckRunWithLongId() throws Exception { + final CheckRunResponse checkRunResponse = + json.fromJson( + loadResource(FIXTURES_PATH + "checks-run-completed-long-id-response.json"), + CheckRunResponse.class); + + final CompletableFuture fixtureResponse = completedFuture(checkRunResponse); + when(github.request(any(), eq(CheckRunResponse.class), any())).thenReturn(fixtureResponse); + + final CompletableFuture actualResponse = checksClient.getCheckRun(6971753714L); + + assertThat(actualResponse.get().id(), is(6971753714L)); + } + @Test public void getCheckRunsList() throws Exception { final CheckRunResponseList checkRunResponse = diff --git a/src/test/java/com/spotify/github/v3/clients/GitDataClientTest.java b/src/test/java/com/spotify/github/v3/clients/GitDataClientTest.java index 1afef55b..1ef3dfdd 100644 --- a/src/test/java/com/spotify/github/v3/clients/GitDataClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/GitDataClientTest.java @@ -22,8 +22,10 @@ import static com.google.common.collect.ImmutableMap.of; import static com.google.common.io.Resources.getResource; +import static com.spotify.github.v3.UserTest.assertUser; import static com.spotify.github.v3.clients.GitHubClient.LIST_REFERENCES; import static java.nio.charset.Charset.defaultCharset; +import static java.util.Collections.emptyList; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; @@ -36,14 +38,20 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; import com.spotify.github.jackson.Json; +import com.spotify.github.v3.git.ImmutableTree; +import com.spotify.github.v3.git.ImmutableTreeItem; import com.spotify.github.v3.git.Reference; +import com.spotify.github.v3.git.ShaLink; import com.spotify.github.v3.git.Tag; +import com.spotify.github.v3.git.Tree; +import com.spotify.github.v3.git.TreeItem; +import com.spotify.github.v3.repos.Commit; import java.io.IOException; import java.time.Instant; import java.util.List; import java.util.concurrent.CompletableFuture; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class GitDataClientTest { @@ -55,7 +63,7 @@ private static String getFixture(String resource) throws IOException { return Resources.toString(getResource(GitDataClientTest.class, resource), defaultCharset()); } - @Before + @BeforeEach public void setUp() { github = mock(GitHubClient.class); gitDataClient = new GitDataClient(github, "someowner", "somerepo"); @@ -91,13 +99,13 @@ public void listMatchingReferences() throws Exception { final CompletableFuture> fixture = completedFuture(json.fromJson(getFixture("reference_list.json"), LIST_REFERENCES)); when(github.request( - "/repos/someowner/somerepo/git/matching-refs/heads/feature", - LIST_REFERENCES)) + "/repos/someowner/somerepo/git/matching-refs/heads/feature", LIST_REFERENCES)) .thenReturn(fixture); - final List matchingReferences = gitDataClient.listMatchingReferences("heads/feature").get(); + final List matchingReferences = + gitDataClient.listMatchingReferences("heads/feature").get(); assertThat(matchingReferences.size(), is(2)); for (Reference ref : matchingReferences) { - assertThat(ref.ref(), containsString("heads/feature")); + assertThat(ref.ref(), containsString("heads/feature")); } } @@ -106,28 +114,26 @@ public void listMatchingReferences() throws Exception { public void listReferences() throws Exception { final CompletableFuture> fixture = completedFuture(json.fromJson(getFixture("tags_list.json"), LIST_REFERENCES)); - when(github.request( - "/repos/someowner/somerepo/git/refs/tags", - LIST_REFERENCES)) + when(github.request("/repos/someowner/somerepo/git/refs/tags", LIST_REFERENCES)) .thenReturn(fixture); final List matchingReferences = gitDataClient.listReferences("refs/tags").get(); assertThat(matchingReferences.size(), is(1)); for (Reference ref : matchingReferences) { - assertThat(ref.ref(), containsString("refs/tags")); + assertThat(ref.ref(), containsString("refs/tags")); } } - public void createReference() throws Exception { final CompletableFuture fixture = completedFuture(json.fromJson(getFixture("reference.json"), Reference.class)); - final ImmutableMap body = of( - "ref", "featureA", - "sha", "aa218f56b14c9653891f9e74264a383fa43fefbd" - ); - when(github.post("/repos/someowner/somerepo/git/refs", - github.json().toJsonUnchecked(body), - Reference.class)) + final ImmutableMap body = + of( + "ref", "featureA", + "sha", "aa218f56b14c9653891f9e74264a383fa43fefbd"); + when(github.post( + "/repos/someowner/somerepo/git/refs", + github.json().toJsonUnchecked(body), + Reference.class)) .thenReturn(fixture); final Reference reference = gitDataClient.createReference("featureA", "aa218f56b14c9653891f9e74264a383fa43fefbd").get(); @@ -139,16 +145,19 @@ public void createReference() throws Exception { public void createBranchReference() throws Exception { final CompletableFuture fixture = completedFuture(json.fromJson(getFixture("branch.json"), Reference.class)); - final ImmutableMap body = of( - "ref", "refs/heads/featureA", - "sha", "aa218f56b14c9653891f9e74264a383fa43fefbd" - ); - when(github.post("/repos/someowner/somerepo/git/refs", - github.json().toJsonUnchecked(body), - Reference.class)) + final ImmutableMap body = + of( + "ref", "refs/heads/featureA", + "sha", "aa218f56b14c9653891f9e74264a383fa43fefbd"); + when(github.post( + "/repos/someowner/somerepo/git/refs", + github.json().toJsonUnchecked(body), + Reference.class)) .thenReturn(fixture); final Reference reference = - gitDataClient.createBranchReference("featureA", "aa218f56b14c9653891f9e74264a383fa43fefbd").get(); + gitDataClient + .createBranchReference("featureA", "aa218f56b14c9653891f9e74264a383fa43fefbd") + .get(); assertThat(reference.ref(), is("refs/heads/featureA")); assertThat(reference.object().sha(), is("aa218f56b14c9653891f9e74264a383fa43fefbd")); } @@ -172,20 +181,26 @@ public void createTagReference() throws Exception { } @Test - public void createAnnotateTag() throws Exception { - final CompletableFuture reference = - completedFuture(json.fromJson(getFixture("tag.json"), Reference.class)); - when(github.post( - "/repos/someowner/somerepo/git/refs", - github - .json() - .toJsonUnchecked( - of( - "ref", "refs/tags/0.0.1", - "sha", "5926dd300de5fee31d445c57be223f00e128a634")), - Reference.class)) - .thenReturn(reference); + public void updateReference() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("branch.json"), Reference.class)); + final ImmutableMap body = + of( + "sha", "aa218f56b14c9653891f9e74264a383fa43fefbd", + "force", "false"); + when(github.patch( + "/repos/someowner/somerepo/git/refs/featureA", + github.json().toJsonUnchecked(body), + Reference.class)) + .thenReturn(fixture); + final Reference reference = + gitDataClient.updateReference("featureA", "aa218f56b14c9653891f9e74264a383fa43fefbd", false).get(); + assertThat(reference.ref(), is("refs/heads/featureA")); + assertThat(reference.object().sha(), is("aa218f56b14c9653891f9e74264a383fa43fefbd")); + } + @Test + public void createAnnotateTag() throws Exception { final String now = Instant.now().toString(); final ImmutableMap body = of( @@ -202,6 +217,21 @@ public void createAnnotateTag() throws Exception { completedFuture(json.fromJson(getFixture("release-tag.json"), Tag.class)); when(github.post(eq("/repos/someowner/somerepo/git/tags"), any(), eq(Tag.class))) .thenReturn(fixture); + + // Ref to the annotate tag should reference the SHA of the tag, not the SHA of the commit. + final CompletableFuture reference = + completedFuture(json.fromJson(getFixture("tag.json"), Reference.class)); + when(github.post( + "/repos/someowner/somerepo/git/refs", + github + .json() + .toJsonUnchecked( + of( + "ref", "refs/tags/0.0.1", + "sha", "827210625b551200e7d3dc608935b1454523eaa8")), + Reference.class)) + .thenReturn(reference); + Tag tag = gitDataClient .createAnnotatedTag( @@ -213,4 +243,99 @@ public void createAnnotateTag() throws Exception { .join(); assertThat(tag.object().sha(), is("5926dd300de5fee31d445c57be223f00e128a634")); } + + @Test + public void testCreateCommit() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("commit.json"), Commit.class)); + + final String expectedRequestBody = + json.toJsonUnchecked( + ImmutableMap.of("message", "message", "parents", emptyList(), "tree", "thesha")); + + when(github.post("/repos/someowner/somerepo/git/commits", expectedRequestBody, Commit.class)) + .thenReturn(fixture); + final Commit commit = gitDataClient.createCommit("message", emptyList(), "thesha").get(); + assertUser(commit.author().get()); + assertThat(commit.commit().message(), is("Fix all the bugs")); + assertThat(commit.files().size(), is(1)); + assertThat(commit.files().get(0).filename(), is("file1.txt")); + } + + @Test + public void testGetTree() throws IOException { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("tree.json"), Tree.class)); + + when(github.request("/repos/someowner/somerepo/git/trees/thesha", Tree.class)) + .thenReturn(fixture); + + final Tree tree = + gitDataClient + .getTree("thesha") + .join(); + assertThat(tree.sha(), is("9c27bd92524e2b57b569d4c86695b3993d9b8f9f")); + } + + @Test + public void testGetRecursiveTree() throws IOException { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("recursive-tree.json"), Tree.class)); + + when(github.request("/repos/someowner/somerepo/git/trees/thesha", Tree.class)) + .thenReturn(fixture); + + final Tree tree = + gitDataClient + .getTree("thesha") + .join(); + assertThat(tree.sha(), is("9c27bd92524e2b57b569d4c86695b3993d9b8f9f")); + assertThat(tree.tree().size(), is(7)); + } + + @Test + public void testCreateTree() throws IOException { + final TreeItem treeItem = + ImmutableTreeItem.builder() + .path("somefolder/somefolder/somefile") + .mode("100644") + .type("commit") + .sha("9c27bd92524e2b57b569d4c86695b3993d9b8f9f") + .build(); + final Tree treeObject = ImmutableTree.builder().addTree(treeItem).build(); + + final String expectedRequestBody = + json.toJsonUnchecked( + ImmutableMap.of( + "base_tree", + "9c27bd92524e2b57b569d4c86695b3993d9b8f9f", + "tree", + List.of(treeItem))); + + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("tree.json"), Tree.class)); + + when(github.post("/repos/someowner/somerepo/git/trees", expectedRequestBody, Tree.class)) + .thenReturn(fixture); + + final Tree tree = + gitDataClient + .createTree(treeObject.tree(), "9c27bd92524e2b57b569d4c86695b3993d9b8f9f") + .join(); + assertThat(tree.sha(), is("9c27bd92524e2b57b569d4c86695b3993d9b8f9f")); + } + + @Test + public void testCreateBlob() throws IOException { + final String expectedRequestBody = + json.toJsonUnchecked(ImmutableMap.of("content", "content", "encoding", "utf-8|base64")); + + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("shalink.json"), ShaLink.class)); + when(github.post("/repos/someowner/somerepo/git/blobs", expectedRequestBody, ShaLink.class)) + .thenReturn(fixture); + final ShaLink shalink = gitDataClient.createBlob("content").join(); + + assertThat(shalink.sha(), is("8fc4e0fe57752b892a921806a1352e4cc72dff37")); + } } diff --git a/src/test/java/com/spotify/github/v3/clients/GitHubAuthTest.java b/src/test/java/com/spotify/github/v3/clients/GitHubAuthTest.java index 2996e8a6..5f9ce379 100644 --- a/src/test/java/com/spotify/github/v3/clients/GitHubAuthTest.java +++ b/src/test/java/com/spotify/github/v3/clients/GitHubAuthTest.java @@ -22,12 +22,11 @@ import static com.spotify.github.v3.clients.ChecksClientTest.loadResource; import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.fasterxml.jackson.core.JsonProcessingException; import com.spotify.github.jackson.Json; @@ -44,9 +43,9 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class GitHubAuthTest { @@ -70,12 +69,19 @@ public class GitHubAuthTest { .toJson( ImmutableAccessToken.copyOf(getTestInstallationToken()) .withExpiresAt(ZonedDateTime.now().minusHours(2)))); + private final MockResponse expiredTokenWithinExpiryMarginResponse = + new MockResponse() + .setBody( + Json.create() + .toJson( + ImmutableAccessToken.copyOf(getTestInstallationToken()) + .withExpiresAt(ZonedDateTime.now().minusMinutes(3)))); private final MockResponse checkRunResponse = new MockResponse().setBody(loadResource("com/spotify/github/v3/checks/checks-run-completed-response.json")); public GitHubAuthTest() throws JsonProcessingException {} - @Before + @BeforeEach public void setUp() throws IOException { client = new OkHttpClient.Builder() @@ -91,7 +97,7 @@ public void setUp() throws IOException { .createChecksApiClient(); } - @After + @AfterEach public void tearDown() throws IOException { mockServer.shutdown(); } @@ -149,16 +155,35 @@ public void fetchesANewInstallationTokenIfExpired() throws Exception { assertThat(mockServer.takeRequest().getPath(), is("/repos/foo/bar/check-runs/123")); } + @Test + public void fetchesANewInstallationTokenIfExpirationIsWithinExpiryMargin() throws Exception { + mockServer.enqueue(expiredTokenWithinExpiryMarginResponse); + mockServer.enqueue(checkRunResponse); + mockServer.enqueue(validTokenResponse); + mockServer.enqueue(checkRunResponse); + + checksClient.getCheckRun(123).join(); + checksClient.getCheckRun(123).join(); + + // 2 to get the token, 2 checks + assertThat(mockServer.getRequestCount(), is(4)); + + assertThat(mockServer.takeRequest().getPath(), is("/app/installations/1/access_tokens")); + assertThat(mockServer.takeRequest().getPath(), is("/repos/foo/bar/check-runs/123")); + assertThat(mockServer.takeRequest().getPath(), is("/app/installations/1/access_tokens")); + assertThat(mockServer.takeRequest().getPath(), is("/repos/foo/bar/check-runs/123")); + } + @Test public void throwsIfFetchingInstallationTokenRequestIsUnsuccessful() throws Exception { mockServer.enqueue(new MockResponse().setResponseCode(500)); RuntimeException ex = assertThrows(RuntimeException.class, () -> checksClient.getCheckRun(123).join()); - assertThat(ex.getMessage(), is("Could not generate access token for github app")); + assertThat(ex.getCause().getMessage(), is("Could not generate access token for github app")); - assertThat(ex.getCause(), is(notNullValue())); - assertThat(ex.getCause().getMessage(), startsWith("Got non-2xx status 500 when getting an access token from GitHub")); + assertThat(ex.getCause().getCause(), is(notNullValue())); + assertThat(ex.getCause().getCause().getCause().getMessage(), startsWith("Got non-2xx status 500 when getting an access token from GitHub")); RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS); // make sure it was the expected request that threw @@ -186,7 +211,7 @@ public void assertChecksApiContainsCorrectHeader() throws Exception { mockServer.enqueue(validTokenResponse); mockServer.enqueue(checkRunResponse); - checksClient.updateCheckRun(12, null).join(); + checksClient.updateCheckRun(12L, null).join(); assertThat(mockServer.getRequestCount(), is(2)); assertThat(mockServer.takeRequest().getPath(), is("/app/installations/1/access_tokens")); @@ -235,10 +260,12 @@ public void assertNoPrivateKeyProvidedUsesAccessToken() throws Exception { assertThat(request.getMethod(), is("GET")); } - @Test(expected = IllegalArgumentException.class) + @Test public void assertNoTokenThrowsException() { final GitHubClient apiWithNoKey = GitHubClient.create(URI.create("someurl"), "a-token"); - apiWithNoKey.createRepositoryClient("foo", "bar").createChecksApiClient(); + assertThrows( + IllegalArgumentException.class, + () -> apiWithNoKey.createRepositoryClient("foo", "bar").createChecksApiClient()); } private AccessToken getTestInstallationToken() { diff --git a/src/test/java/com/spotify/github/v3/clients/GitHubClientTest.java b/src/test/java/com/spotify/github/v3/clients/GitHubClientTest.java index 182a7a96..8725958d 100644 --- a/src/test/java/com/spotify/github/v3/clients/GitHubClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/GitHubClientTest.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,46 +20,89 @@ package com.spotify.github.v3.clients; +import static com.google.common.io.Resources.getResource; +import static java.nio.charset.Charset.defaultCharset; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsMapContaining.hasEntry; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import com.google.common.io.Resources; +import com.spotify.github.http.HttpRequest; +import com.spotify.github.tracing.Span; +import com.spotify.github.tracing.Tracer; +import com.spotify.github.v3.checks.CheckSuiteResponseList; +import com.spotify.github.v3.checks.Installation; import com.spotify.github.v3.exceptions.ReadOnlyRepositoryException; import com.spotify.github.v3.exceptions.RequestNotOkException; import com.spotify.github.v3.repos.CommitItem; +import com.spotify.github.v3.repos.RepositoryInvitation; + +import java.io.IOException; import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; + +import com.spotify.github.v3.workflows.WorkflowsResponse; +import com.spotify.github.v3.workflows.WorkflowsState; import okhttp3.Call; import okhttp3.Callback; +import okhttp3.Headers; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Protocol; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; -import org.junit.Before; -import org.junit.Test; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; public class GitHubClientTest { private GitHubClient github; private OkHttpClient client; + private final Tracer tracer = mock(Tracer.class); + private final Span mockSpan = mock(Span.class); + + private static String getFixture(String resource) throws IOException { + return Resources.toString(getResource(GitHubClientTest.class, resource), defaultCharset()); + } - @Before + @BeforeEach public void setUp() { client = mock(OkHttpClient.class); github = GitHubClient.create(client, URI.create("http://bogus"), "token"); + when(tracer.span(any())).thenReturn(mockSpan); + } + + @Test + public void withScopedInstallationIdShouldFailWhenMissingPrivateKey() { + assertThrows(RuntimeException.class, () -> github.withScopeForInstallationId(1)); + } + + @Test + public void testWithScopedInstallationId() throws URISyntaxException { + GitHubClient org = + GitHubClient.create( + new URI("http://apa.bepa.cepa"), "some_key_content".getBytes(), null, null); + GitHubClient scoped = org.withScopeForInstallationId(1); + Assertions.assertTrue(scoped.getPrivateKey().isPresent()); + Assertions.assertEquals(org.getPrivateKey().get(), scoped.getPrivateKey().get()); } - @Test(expected = ReadOnlyRepositoryException.class) + @Test public void testSearchIssue() throws Throwable { final Call call = mock(Call.class); @@ -88,15 +131,14 @@ public void testSearchIssue() throws Throwable { when(client.newCall(any())).thenReturn(call); IssueClient issueClient = - github.createRepositoryClient("testorg", "testrepo").createIssueClient(); + github.withTracer(tracer).createRepositoryClient("testorg", "testrepo").createIssueClient(); CompletableFuture maybeSucceeded = issueClient.editComment(1, "some comment"); capture.getValue().onResponse(call, response); - try { - maybeSucceeded.get(); - } catch (Exception e) { - throw e.getCause(); - } + verify(tracer, times(1)).span(any(HttpRequest.class)); + + Exception exception = assertThrows(ExecutionException.class, maybeSucceeded::get); + Assertions.assertEquals(ReadOnlyRepositoryException.class, exception.getCause().getClass()); } @Test @@ -105,17 +147,17 @@ public void testRequestNotOkException() throws Throwable { final ArgumentCaptor capture = ArgumentCaptor.forClass(Callback.class); doNothing().when(call).enqueue(capture.capture()); - final Response response = new okhttp3.Response.Builder() - .code(409) // Conflict - .body( - ResponseBody.create( - MediaType.get("application/json"), - "{\n \"message\": \"Merge Conflict\"\n}" - )) - .message("") - .protocol(Protocol.HTTP_1_1) - .request(new Request.Builder().url("http://localhost/").build()) - .build(); + final Response response = + new okhttp3.Response.Builder() + .code(409) // Conflict + .headers(Headers.of("x-ratelimit-remaining", "0")) + .body( + ResponseBody.create( + MediaType.get("application/json"), "{\n \"message\": \"Merge Conflict\"\n}")) + .message("") + .protocol(Protocol.HTTP_1_1) + .request(new Request.Builder().url("http://localhost/").build()) + .build(); when(client.newCall(any())).thenReturn(call); RepositoryClient repoApi = github.createRepositoryClient("testorg", "testrepo"); @@ -124,12 +166,170 @@ public void testRequestNotOkException() throws Throwable { capture.getValue().onResponse(call, response); try { future.get(); - fail("Did not throw"); + Assertions.fail("Did not throw"); } catch (ExecutionException e) { assertThat(e.getCause() instanceof RequestNotOkException, is(true)); RequestNotOkException e1 = (RequestNotOkException) e.getCause(); assertThat(e1.statusCode(), is(409)); + assertThat(e1.method(), is("POST")); + assertThat(e1.path(), is("/repos/testorg/testrepo/merges")); + assertThat(e1.headers(), hasEntry("x-ratelimit-remaining", List.of("0"))); + assertThat(e1.getMessage(), containsString("POST")); + assertThat(e1.getMessage(), containsString("/repos/testorg/testrepo/merges")); assertThat(e1.getMessage(), containsString("Merge Conflict")); + assertThat(e1.getRawMessage(), containsString("Merge Conflict")); } } + + @Test + public void testPutConvertsToClass() throws Throwable { + final Call call = mock(Call.class); + final ArgumentCaptor callbackCapture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(callbackCapture.capture()); + + final ArgumentCaptor requestCapture = ArgumentCaptor.forClass(Request.class); + when(client.newCall(requestCapture.capture())).thenReturn(call); + + final Response response = + new okhttp3.Response.Builder() + .code(200) + .body( + ResponseBody.create( + MediaType.get("application/json"), getFixture("repository_invitation.json"))) + .message("") + .protocol(Protocol.HTTP_1_1) + .request(new Request.Builder().url("http://localhost/").build()) + .build(); + + CompletableFuture future = + github.put("collaborators/", "", RepositoryInvitation.class); + callbackCapture.getValue().onResponse(call, response); + + RepositoryInvitation invitation = future.get(); + assertThat(requestCapture.getValue().method(), is("PUT")); + assertThat(requestCapture.getValue().url().toString(), is("http://bogus/collaborators/")); + assertThat(invitation.id(), is(1)); + } + + @Test + public void testGetCheckSuites() throws Throwable { + + final Call call = mock(Call.class); + final ArgumentCaptor callbackCapture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(callbackCapture.capture()); + + final Response response = + new okhttp3.Response.Builder() + .code(200) + .body( + ResponseBody.create( + MediaType.get("application/json"), + getFixture("../checks/check-suites-response.json"))) + .message("") + .protocol(Protocol.HTTP_1_1) + .request(new Request.Builder().url("http://localhost/").build()) + .build(); + + when(client.newCall(any())).thenReturn(call); + ChecksClient client = github.createChecksClient("testorg", "testrepo"); + + CompletableFuture future = client.getCheckSuites("sha"); + callbackCapture.getValue().onResponse(call, response); + var result = future.get(); + + assertThat(result.totalCount(), is(1)); + assertThat(result.checkSuites().get(0).app().get().slug().get(), is("octoapp")); + } + + @Test + public void testGetWorkflow() throws Throwable { + final Call call = mock(Call.class); + final ArgumentCaptor callbackCapture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(callbackCapture.capture()); + + final Response response = + new okhttp3.Response.Builder() + .code(200) + .body( + ResponseBody.create( + MediaType.get("application/json"), + getFixture("../workflows/workflows-get-workflow-response.json"))) + .message("") + .protocol(Protocol.HTTP_1_1) + .request(new Request.Builder().url("http://localhost/").build()) + .build(); + + when(client.newCall(any())).thenReturn(call); + WorkflowsClient client = + github + .withTracer(tracer) + .createRepositoryClient("testorg", "testrepo") + .createActionsClient() + .createWorkflowsClient(); + + CompletableFuture future = client.getWorkflow(161335); + callbackCapture.getValue().onResponse(call, response); + var result = future.get(); + + assertThat(result.id(), is(161335)); + assertThat(result.state(), is(WorkflowsState.active)); + } + + @Test + void asAppScopedClientGetsUserClientIfOrgClientNotFound() { + var appGithub = GitHubClient.create(client, URI.create("http://bogus"), new byte[] {}, 1); + var githubSpy = spy(appGithub); + + var orgClientMock = mock(OrganisationClient.class); + when(githubSpy.createOrganisationClient("owner")).thenReturn(orgClientMock); + + var appClientMock = mock(GithubAppClient.class); + when(orgClientMock.createGithubAppClient()).thenReturn(appClientMock); + when(appClientMock.getInstallation()) + .thenReturn(failedFuture(new RequestNotOkException("", "", 404, "", new HashMap<>()))); + + var userClientMock = mock(UserClient.class); + when(githubSpy.createUserClient("owner")).thenReturn(userClientMock); + + var appClientMock2 = mock(GithubAppClient.class); + when(userClientMock.createGithubAppClient()).thenReturn(appClientMock2); + + var installationMock = mock(Installation.class); + when(appClientMock2.getUserInstallation()).thenReturn(completedFuture(installationMock)); + when(installationMock.id()).thenReturn(1); + + var maybeScopedClient = githubSpy.asAppScopedClient("owner").toCompletableFuture().join(); + + Assertions.assertTrue(maybeScopedClient.isPresent()); + verify(githubSpy, times(1)).createOrganisationClient("owner"); + verify(githubSpy, times(1)).createUserClient("owner"); + } + + @Test + void asAppScopedClientReturnsEmptyIfNoInstallation() { + var appGithub = GitHubClient.create(client, URI.create("http://bogus"), new byte[] {}, 1); + var githubSpy = spy(appGithub); + + var orgClientMock = mock(OrganisationClient.class); + when(githubSpy.createOrganisationClient("owner")).thenReturn(orgClientMock); + + var appClientMock = mock(GithubAppClient.class); + when(orgClientMock.createGithubAppClient()).thenReturn(appClientMock); + when(appClientMock.getInstallation()) + .thenReturn(failedFuture(new RequestNotOkException("", "", 404, "", new HashMap<>()))); + + var userClientMock = mock(UserClient.class); + when(githubSpy.createUserClient("owner")).thenReturn(userClientMock); + + var appClientMock2 = mock(GithubAppClient.class); + when(userClientMock.createGithubAppClient()).thenReturn(appClientMock2); + + var installationMock = mock(Installation.class); + when(appClientMock2.getUserInstallation()) + .thenReturn(failedFuture(new RequestNotOkException("", "", 404, "", new HashMap<>()))); + when(installationMock.id()).thenReturn(1); + + var maybeScopedClient = githubSpy.asAppScopedClient("owner").toCompletableFuture().join(); + Assertions.assertTrue(maybeScopedClient.isEmpty()); + } } diff --git a/src/test/java/com/spotify/github/v3/clients/GitHubPageTest.java b/src/test/java/com/spotify/github/v3/clients/GitHubPageTest.java new file mode 100644 index 00000000..295a4d76 --- /dev/null +++ b/src/test/java/com/spotify/github/v3/clients/GitHubPageTest.java @@ -0,0 +1,53 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2025 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.clients; + +import static org.hamcrest.core.Is.is; + +import java.net.URI; +import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAndIs; +import static com.github.npathai.hamcrestopt.OptionalMatchers.isEmpty; + +public class GitHubPageTest { + private static final String MOCK_GITHUB_HOST = "bogus.host"; + private static final URI MOCK_GITHUB_URI = URI.create(String.format("http://%s/api/v3/", MOCK_GITHUB_HOST)); + + @Test + public void testFormatPathWithPerPage() { + assertThat(GithubPage.formatPath("/commits?page=2", 3), is("/commits?page=2&per_page=3")); + assertThat(GithubPage.formatPath("NOT_A_CORRECT PATH ", 3), is("NOT_A_CORRECT PATH ")); + assertThat(GithubPage.formatPath("/commits", 3), is("/commits?per_page=3")); + assertThat(GithubPage.formatPath("/commits?page=2&per_page=7", 3), is("/commits?page=2&per_page=7")); + } + + @Test + public void testPageNumberFromURI() { + assertThat(GithubPage.pageNumberFromUri(MOCK_GITHUB_URI.resolve("/commits?page=5").toString()), isPresentAndIs(5)); + assertThat(GithubPage.pageNumberFromUri(MOCK_GITHUB_URI.resolve("/commits").toString()), isEmpty()); + assertThat(GithubPage.pageNumberFromUri(MOCK_GITHUB_URI.resolve("commits").toString()), isEmpty()); + assertThat(GithubPage.pageNumberFromUri("/commits?page=2"), isPresentAndIs(2)); + assertThat(GithubPage.pageNumberFromUri("/commits?per_page=4&page=2"), isPresentAndIs(2)); + assertThat(GithubPage.pageNumberFromUri("NOT_A_CORRECT PATH "), isEmpty()); + assertThat(GithubPage.pageNumberFromUri("/commits"), isEmpty()); + } +} diff --git a/src/test/java/com/spotify/github/v3/clients/GithubAppClientTest.java b/src/test/java/com/spotify/github/v3/clients/GithubAppClientTest.java index f0644c29..c47d3a4d 100644 --- a/src/test/java/com/spotify/github/v3/clients/GithubAppClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/GithubAppClientTest.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,15 +20,24 @@ package com.spotify.github.v3.clients; +import static com.google.common.io.Resources.getResource; +import static java.nio.charset.Charset.defaultCharset; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.core.Is.is; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.io.Resources; import com.spotify.github.FixtureHelper; import com.spotify.github.v3.apps.InstallationRepositoriesResponse; +import com.spotify.github.v3.apps.requests.AccessTokenRequest; +import com.spotify.github.v3.apps.requests.ImmutableAccessTokenRequest; +import com.spotify.github.v3.checks.AccessToken; +import com.spotify.github.v3.checks.App; import com.spotify.github.v3.checks.Installation; import java.io.File; +import java.io.IOException; import java.net.URI; import java.time.ZonedDateTime; import java.util.List; @@ -36,26 +45,28 @@ import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class GithubAppClientTest { - @Rule public final MockWebServer mockServer = new MockWebServer(); private final ObjectMapper objectMapper = new ObjectMapper(); private final int appId = 42; private GithubAppClient client; - @Before + private static String getFixture(String resource) throws IOException { + return Resources.toString(getResource(GithubAppClientTest.class, resource), defaultCharset()); + } + + @BeforeEach public void setUp() throws Exception { URI uri = mockServer.url("").uri(); File key = FixtureHelper.loadFile("githubapp/key.pem"); GitHubClient rootclient = GitHubClient.create(uri, key, appId); - client = rootclient.createRepositoryClient("", "").createGithubAppClient(); + client = rootclient.createRepositoryClient("owner", "repo").createGithubAppClient(); } @Test @@ -104,8 +115,7 @@ public void listAccessibleRepositories() throws Exception { .setResponseCode(200) .setBody(FixtureHelper.loadFixture("githubapp/accessible-repositories.json"))); - InstallationRepositoriesResponse response = - client.listAccessibleRepositories(1234).join(); + InstallationRepositoriesResponse response = client.listAccessibleRepositories(1234).join(); assertThat(response.totalCount(), is(2)); assertThat(response.repositories().size(), is(2)); @@ -122,4 +132,122 @@ public void listAccessibleRepositories() throws Exception { assertThat(listReposRequest.getMethod(), is("GET")); assertThat(listReposRequest.getRequestUrl().encodedPath(), is("/installation/repositories")); } + + @Test + public void getInstallation() throws Exception { + mockServer.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody(FixtureHelper.loadFixture("githubapp/installation.json"))); + + Installation installation = client.getInstallation().join(); + + assertThat(installation.id(), is(1)); + assertThat(installation.account().login(), is("github")); + + RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS); + assertThat(recordedRequest.getRequestUrl().encodedPath(), is("/repos/owner/repo/installation")); + } + + @Test + public void getInstallationByInstallationId() throws Exception { + mockServer.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody(FixtureHelper.loadFixture("githubapp/installation.json"))); + + Installation installation = client.getInstallation(1234).join(); + + assertThat(installation.id(), is(1)); + assertThat(installation.account().login(), is("github")); + + RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS); + assertThat(recordedRequest.getRequestUrl().encodedPath(), is("/app/installations/1234")); + } + + @Test + public void getAuthenticatedApp() throws Exception { + mockServer.enqueue( + new MockResponse() + .setResponseCode(200) + .setBody(FixtureHelper.loadFixture("githubapp/authenticated-app.json"))); + + App app = client.getAuthenticatedApp().join(); + + assertThat(app.id(), is(1)); + assertThat(app.slug().get(), is("octoapp")); + assertThat(app.name(), is("Octocat App")); + assertThat(app.clientId().get(), is("Iv1.8a61f9b3a7aba766")); + assertThat(app.installationsCount().get(), is(5)); + + RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS); + assertThat(recordedRequest.getRequestUrl().encodedPath(), is("/app")); + assertThat( + recordedRequest.getHeaders().values("Authorization").get(0).startsWith("Bearer "), + is(true)); + } + + @Test + public void getAccessTokenWithoutScoping() throws Exception { + mockServer.enqueue( + new MockResponse() + .setResponseCode(201) + .setBody(FixtureHelper.loadFixture("githubapp/access-token.json"))); + + AccessToken token = client.getAccessToken(1234).join(); + + assertThat(token.token(), is("ghs_16C7e42F292c6912E7710c838347Ae178B4a")); + + RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS); + assertThat(recordedRequest.getMethod(), is("POST")); + assertThat( + recordedRequest.getRequestUrl().encodedPath(), + is("/app/installations/1234/access_tokens")); + assertThat(recordedRequest.getBody().readUtf8(), is("")); + } + + @Test + public void getAccessTokenWithRepositoryScoping() throws Exception { + mockServer.enqueue( + new MockResponse() + .setResponseCode(201) + .setBody(FixtureHelper.loadFixture("githubapp/access-token.json"))); + + AccessTokenRequest request = ImmutableAccessTokenRequest.builder() + .repositories(List.of("Hello-World")) + .repositoryIds(List.of(1)) + .build(); + + AccessToken token = client.getAccessToken(1234, request).join(); + + assertThat(token.token(), is("ghs_16C7e42F292c6912E7710c838347Ae178B4a")); + + RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS); + assertThat(recordedRequest.getMethod(), is("POST")); + assertThat( + recordedRequest.getRequestUrl().encodedPath(), + is("/app/installations/1234/access_tokens")); + + String requestBody = recordedRequest.getBody().readUtf8(); + assertThat(requestBody, containsString("\"repositories\":[\"Hello-World\"]")); + assertThat(requestBody, containsString("\"repository_ids\":[1]")); + } + + @Test + public void getAccessTokenWithEmptyRequest() throws Exception { + mockServer.enqueue( + new MockResponse() + .setResponseCode(201) + .setBody(FixtureHelper.loadFixture("githubapp/access-token.json"))); + + AccessTokenRequest emptyRequest = ImmutableAccessTokenRequest.builder().build(); + + AccessToken token = client.getAccessToken(1234, emptyRequest).join(); + + assertThat(token.token(), is("ghs_16C7e42F292c6912E7710c838347Ae178B4a")); + + RecordedRequest recordedRequest = mockServer.takeRequest(1, TimeUnit.MILLISECONDS); + String requestBody = recordedRequest.getBody().readUtf8(); + assertThat(requestBody, is("{}")); + } } diff --git a/src/test/java/com/spotify/github/v3/clients/IssueClientTest.java b/src/test/java/com/spotify/github/v3/clients/IssueClientTest.java index 3d3cc719..65ac3054 100644 --- a/src/test/java/com/spotify/github/v3/clients/IssueClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/IssueClientTest.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -22,47 +22,58 @@ import static com.google.common.io.Resources.getResource; import static com.spotify.github.FixtureHelper.loadFixture; -import static com.spotify.github.v3.clients.IssueClient.COMMENTS_URI_NUMBER_TEMPLATE; -import static com.spotify.github.v3.clients.MockHelper.createMockResponse; +import static com.spotify.github.v3.clients.IssueClient.*; +import static com.spotify.github.MockHelper.createMockResponse; import static java.lang.String.format; import static java.nio.charset.Charset.defaultCharset; import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; import static java.util.stream.Collectors.toList; -import static java.util.stream.StreamSupport.stream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; -import static org.powermock.api.mockito.PowerMockito.mock; +import static org.mockito.Mockito.*; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.io.Resources; +import com.spotify.github.async.Async; import com.spotify.github.async.AsyncPage; +import com.spotify.github.http.HttpResponse; import com.spotify.github.jackson.Json; +import com.spotify.github.v3.ImmutableUser; import com.spotify.github.v3.comment.Comment; +import com.spotify.github.v3.comment.CommentReaction; +import com.spotify.github.v3.comment.CommentReactionContent; +import com.spotify.github.v3.comment.ImmutableCommentReaction; +import com.spotify.github.v3.exceptions.RequestNotOkException; +import com.spotify.github.v3.issues.Issue; import java.io.IOException; +import java.util.HashMap; import java.util.List; -import okhttp3.Headers; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -@RunWith(PowerMockRunner.class) -@PrepareForTest({Headers.class, ResponseBody.class, Response.class}) +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + public class IssueClientTest { private GitHubClient github; private IssueClient issueClient; + private Json json; - @Before + @BeforeEach public void setUp() { + json = Json.create(); github = mock(GitHubClient.class); - when(github.json()).thenReturn(Json.create()); + when(github.json()).thenReturn(json); when(github.urlFor("")).thenReturn("https://github.com/api/v3"); issueClient = new IssueClient(github, "someowner", "somerepo"); } @@ -70,32 +81,35 @@ public void setUp() { @Test public void testCommentPaginationSpliterator() throws IOException { final String firstPageLink = - "; rel=\"next\", ; rel=\"last\""; + "; rel=\"next\", ; rel=\"last\""; final String firstPageBody = Resources.toString(getResource(this.getClass(), "comments_page1.json"), defaultCharset()); - final Response firstPageResponse = createMockResponse(firstPageLink, firstPageBody); + final HttpResponse firstPageResponse = createMockResponse(firstPageLink, firstPageBody); final String lastPageLink = - "; rel=\"first\", ; rel=\"prev\""; + "; rel=\"first\", ; rel=\"prev\""; final String lastPageBody = Resources.toString(getResource(this.getClass(), "comments_page2.json"), defaultCharset()); - final Response lastPageResponse = createMockResponse(lastPageLink, lastPageBody); + final HttpResponse lastPageResponse = createMockResponse(lastPageLink, lastPageBody); - when(github.request(format(COMMENTS_URI_NUMBER_TEMPLATE, "someowner", "somerepo", "123"))) + when(github.request( + format(COMMENTS_URI_NUMBER_TEMPLATE + "?per_page=30", "someowner", "somerepo", "123"))) .thenReturn(completedFuture(firstPageResponse)); when(github.request( - format(COMMENTS_URI_NUMBER_TEMPLATE + "?page=2", "someowner", "somerepo", "123"))) + format( + COMMENTS_URI_NUMBER_TEMPLATE + "?page=2&per_page=30", + "someowner", + "somerepo", + "123"))) .thenReturn(completedFuture(lastPageResponse)); final Iterable> pageIterator = () -> issueClient.listComments(123); final List listComments = - stream(pageIterator.spliterator(), false) - .flatMap(page -> stream(page.spliterator(), false)) - .collect(toList()); + Async.streamFromPaginatingIterable(pageIterator).collect(toList()); assertThat(listComments.size(), is(30)); - assertThat(listComments.get(0).id(), is(1345268)); - assertThat(listComments.get(listComments.size() - 1).id(), is(1356168)); + assertThat(listComments.get(0).id(), is(1345268L)); + assertThat(listComments.get(listComments.size() - 1).id(), is(1356168L)); } @Test @@ -104,18 +118,23 @@ public void testCommentPaginationForeach() throws IOException { "; rel=\"next\", ; rel=\"last\""; final String firstPageBody = Resources.toString(getResource(this.getClass(), "comments_page1.json"), defaultCharset()); - final Response firstPageResponse = createMockResponse(firstPageLink, firstPageBody); + final HttpResponse firstPageResponse = createMockResponse(firstPageLink, firstPageBody); final String lastPageLink = "; rel=\"first\", ; rel=\"prev\""; final String lastPageBody = Resources.toString(getResource(this.getClass(), "comments_page2.json"), defaultCharset()); - final Response lastPageResponse = createMockResponse(lastPageLink, lastPageBody); + final HttpResponse lastPageResponse = createMockResponse(lastPageLink, lastPageBody); - when(github.request(format(COMMENTS_URI_NUMBER_TEMPLATE, "someowner", "somerepo", "123"))) + when(github.request( + format(COMMENTS_URI_NUMBER_TEMPLATE + "?per_page=30", "someowner", "somerepo", "123"))) .thenReturn(completedFuture(firstPageResponse)); when(github.request( - format(COMMENTS_URI_NUMBER_TEMPLATE + "?page=2", "someowner", "somerepo", "123"))) + format( + COMMENTS_URI_NUMBER_TEMPLATE + "?page=2&per_page=30", + "someowner", + "somerepo", + "123"))) .thenReturn(completedFuture(lastPageResponse)); final List listComments = Lists.newArrayList(); @@ -127,19 +146,180 @@ public void testCommentPaginationForeach() throws IOException { }); assertThat(listComments.size(), is(30)); - assertThat(listComments.get(0).id(), is(1345268)); - assertThat(listComments.get(listComments.size() - 1).id(), is(1356168)); + assertThat(listComments.get(0).id(), is(1345268L)); + assertThat(listComments.get(listComments.size() - 1).id(), is(1356168L)); } @Test public void testCommentCreated() throws IOException { final String fixture = loadFixture("clients/comment_created.json"); - final Response response = createMockResponse("", fixture); + final HttpResponse response = createMockResponse("", fixture); + final String path = format(COMMENTS_URI_NUMBER_TEMPLATE, "someowner", "somerepo", 10); + when(github.post(anyString(), anyString(), eq(Comment.class))).thenCallRealMethod(); + when(github.post(eq(path), anyString())).thenReturn(completedFuture(response)); + final Comment comment = issueClient.createComment(10, "Me too").join(); + + assertThat(comment.id(), is(114L)); + } + + @Test + public void testCommentCreatedWithLargeId() throws IOException { + final String fixture = loadFixture("clients/comment_created_long_id.json"); + final HttpResponse response = createMockResponse("", fixture); final String path = format(COMMENTS_URI_NUMBER_TEMPLATE, "someowner", "somerepo", 10); when(github.post(anyString(), anyString(), eq(Comment.class))).thenCallRealMethod(); when(github.post(eq(path), anyString())).thenReturn(completedFuture(response)); final Comment comment = issueClient.createComment(10, "Me too").join(); - assertThat(comment.id(), is(114)); + assertThat(comment.id(), is(2459198527L)); + } + + @Test + public void testGetIssue() throws IOException { + final String fixture = loadFixture("issues/issue.json"); + final CompletableFuture response = completedFuture(json.fromJson(fixture, Issue.class)); + final String path = format(ISSUES_URI_ID_TEMPLATE, "someowner", "somerepo", 2); + when(github.request(eq(path), eq(Issue.class))).thenReturn(response); + + final var issue = issueClient.getIssue(2).join(); + + assertThat(issue.id(), is(2L)); + assertNotNull(issue.labels()); + assertFalse(issue.labels().isEmpty()); + assertThat(issue.labels().get(0).name(), is("bug")); + } + + @ParameterizedTest + @EnumSource(CommentReactionContent.class) + public void testCreateIssueCommentReaction(CommentReactionContent reaction) { + long commentId = 22369886; + final CompletableFuture reactionResponse = + completedFuture( + ImmutableCommentReaction.builder() + .id(42L) + .content(reaction) + .user(ImmutableUser.builder().login("octocat").build()) + .build()); + final String path = format(COMMENTS_REACTION_TEMPLATE, "someowner", "somerepo", commentId); + final String requestBody = + github.json().toJsonUnchecked(ImmutableMap.of("content", reaction.toString())); + when(github.post(eq(path), eq(requestBody), eq(CommentReaction.class))) + .thenReturn(reactionResponse); + + final var commentReaction = issueClient.createCommentReaction(commentId, reaction).join(); + + assertThat(commentReaction.id(), is(42L)); + assertNotNull(commentReaction.user()); + assertThat(commentReaction.user().login(), is("octocat")); + assertThat(commentReaction.content().toString(), is(reaction.toString())); + verify(github, times(1)).post(eq(path), eq(requestBody), eq(CommentReaction.class)); + } + + @Test + public void testDeleteIssueCommentReaction() { + long commentId = 42; + long reactionId = 385825; + final String path = + format(COMMENTS_REACTION_ID_TEMPLATE, "someowner", "somerepo", commentId, reactionId); + HttpResponse mockResponse = mock(HttpResponse.class); + when(mockResponse.statusCode()).thenReturn(204); + when(github.delete(eq(path))).thenReturn(completedFuture(mockResponse)); + + final var response = issueClient.deleteCommentReaction(commentId, reactionId).join(); + + assertThat(response.statusCode(), is(204)); + assertThat(response, is(mockResponse)); + verify(github, times(1)).delete(eq(path)); + } + + @Test + public void testListIssueCommentReaction() throws IOException { + long commentId = 22369886; + final CompletableFuture> listResponse = + completedFuture( + List.of( + (ImmutableCommentReaction.builder() + .id(42L) + .content(CommentReactionContent.HEART) + .user(ImmutableUser.builder().login("octocat").build()) + .build()))); + final String path = + format(COMMENTS_REACTION_TEMPLATE + "?per_page=30", "someowner", "somerepo", commentId); + + final String firstPageLink = + format( + "; rel=\"last\"", + commentId); + final String firstPageBody = github.json().toJsonUnchecked(listResponse.join().toArray()); + final HttpResponse firstPageResponse = createMockResponse(firstPageLink, firstPageBody); + + when(github.request(eq(path))).thenReturn(completedFuture(firstPageResponse)); + final List listCommentReactions = Lists.newArrayList(); + issueClient + .listCommentReaction(commentId) + .forEachRemaining( + page -> { + page.iterator().forEachRemaining(listCommentReactions::add); + }); + + assertThat(listCommentReactions.size(), is(1)); + assertNotNull(listCommentReactions.get(0)); + assertThat(listCommentReactions.get(0).user().login(), is("octocat")); + assertThat( + listCommentReactions.get(0).content().toString(), + is(CommentReactionContent.HEART.toString())); + verify(github, atLeastOnce()).request(eq(path)); + } + + @ParameterizedTest + @EnumSource(CommentReactionContent.class) + public void testCreateIssueReaction(CommentReactionContent reaction) { + long issueNumber = 42; + final CompletableFuture reactionResponse = + completedFuture( + ImmutableCommentReaction.builder() + .id(123L) + .content(reaction) + .user(ImmutableUser.builder().login("octocat").build()) + .build()); + final String path = format(ISSUES_REACTION_TEMPLATE, "someowner", "somerepo", issueNumber); + final String requestBody = + github.json().toJsonUnchecked(ImmutableMap.of("content", reaction.toString())); + when(github.post(eq(path), eq(requestBody), eq(CommentReaction.class))) + .thenReturn(reactionResponse); + + final var issueReaction = issueClient.createIssueReaction(issueNumber, reaction).join(); + + assertThat(issueReaction.id(), is(123L)); + assertNotNull(issueReaction.user()); + assertThat(issueReaction.user().login(), is("octocat")); + assertThat(issueReaction.content().toString(), is(reaction.toString())); + verify(github, times(1)).post(eq(path), eq(requestBody), eq(CommentReaction.class)); + } + + @Test + public void testDeleteIssueReaction() { + long issueNumber = 42; + long reactionId = 385825; + final String path = + format(ISSUES_REACTION_ID_TEMPLATE, "someowner", "somerepo", issueNumber, reactionId); + HttpResponse mockResponse = mock(HttpResponse.class); + when(mockResponse.statusCode()).thenReturn(204); + when(github.delete(eq(path))).thenReturn(completedFuture(mockResponse)); + + final var response = issueClient.deleteIssueReaction(issueNumber, reactionId).join(); + + assertThat(response.statusCode(), is(204)); + assertThat(response, is(mockResponse)); + verify(github, times(1)).delete(eq(path)); + } + + @Test + public void testGetIssueNoIssue() { + final String path = format(ISSUES_URI_ID_TEMPLATE, "someowner", "somerepo", 2); + when(github.request(eq(path), eq(Issue.class))) + .thenReturn(failedFuture(new RequestNotOkException("", "", 404, "", new HashMap<>()))); + + assertThrows(CompletionException.class, () -> issueClient.getIssue(2).join()); } } diff --git a/src/test/java/com/spotify/github/v3/clients/JwtTokenIssuerTest.java b/src/test/java/com/spotify/github/v3/clients/JwtTokenIssuerTest.java index 437bc4cc..4bb1622d 100644 --- a/src/test/java/com/spotify/github/v3/clients/JwtTokenIssuerTest.java +++ b/src/test/java/com/spotify/github/v3/clients/JwtTokenIssuerTest.java @@ -26,7 +26,7 @@ import com.google.common.io.Resources; import java.net.URL; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class JwtTokenIssuerTest { diff --git a/src/test/java/com/spotify/github/v3/clients/MockHelper.java b/src/test/java/com/spotify/github/v3/clients/MockHelper.java deleted file mode 100644 index f6537e8f..00000000 --- a/src/test/java/com/spotify/github/v3/clients/MockHelper.java +++ /dev/null @@ -1,45 +0,0 @@ -/*- - * -\-\- - * github-client - * -- - * Copyright (C) 2016 - 2020 Spotify AB - * -- - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * -/-/- - */ - -package com.spotify.github.v3.clients; - -import static org.mockito.Mockito.when; -import static org.powermock.api.mockito.PowerMockito.mock; - -import java.io.IOException; -import okhttp3.Headers; -import okhttp3.Response; -import okhttp3.ResponseBody; - -public class MockHelper { - public static Response createMockResponse(final String headerLinksFixture, final String bodyFixture) - throws IOException { - final ResponseBody body = mock(ResponseBody.class); - when(body.string()).thenReturn(bodyFixture); - - final Headers headers = mock(Headers.class); - when(headers.get("Link")).thenReturn(headerLinksFixture); - - final Response response = mock(Response.class); - when(response.headers()).thenReturn(headers); - when(response.body()).thenReturn(body); - return response; - } -} \ No newline at end of file diff --git a/src/test/java/com/spotify/github/v3/clients/OrganisationClientTest.java b/src/test/java/com/spotify/github/v3/clients/OrganisationClientTest.java new file mode 100644 index 00000000..1ac3c1ed --- /dev/null +++ b/src/test/java/com/spotify/github/v3/clients/OrganisationClientTest.java @@ -0,0 +1,115 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.clients; + +import static com.google.common.io.Resources.getResource; +import static java.nio.charset.Charset.defaultCharset; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.io.Resources; +import com.spotify.github.jackson.Json; +import com.spotify.github.v3.Team; +import com.spotify.github.v3.checks.Installation; +import com.spotify.github.v3.orgs.OrgMembership; +import com.spotify.github.v3.orgs.requests.OrgMembershipCreate; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class OrganisationClientTest { + + private GitHubClient github; + + private OrganisationClient organisationClient; + + private Json json; + + private static String getFixture(String resource) throws IOException { + return Resources.toString(getResource(TeamClientTest.class, resource), defaultCharset()); + } + + @BeforeEach + public void setUp() { + github = mock(GitHubClient.class); + organisationClient = new OrganisationClient(github, "github"); + json = Json.create(); + when(github.json()).thenReturn(json); + } + + @Test + public void testTeamClient() throws Exception { + final TeamClient teamClient = organisationClient.createTeamClient(); + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("team_get.json"), Team.class)); + when(github.request("/orgs/github/teams/justice-league", Team.class)).thenReturn(fixture); + final Team team = teamClient.getTeam("justice-league").get(); + assertThat(team.id(), is(1)); + assertThat(team.name(), is("Justice League")); + } + + @Test + public void testAppClient() throws Exception { + final GithubAppClient githubAppClient = organisationClient.createGithubAppClient(); + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("../githubapp/installation.json"), Installation.class)); + when(github.request("/orgs/github/installation", Installation.class)).thenReturn(fixture); + final Installation installation = githubAppClient.getInstallation().get(); + assertThat(installation.id(), is(1)); + assertThat(installation.account().login(), is("github")); + } + + @Test + public void getOrgMembership() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("org_membership.json"), OrgMembership.class)); + when(github.request("/orgs/github/memberships/octocat", OrgMembership.class)) + .thenReturn(fixture); + final OrgMembership orgMembership = organisationClient.getOrgMembership("octocat").get(); + assertThat( + orgMembership.url().toString(), is("https://api.github.com/orgs/github/memberships/octocat")); + assertThat(orgMembership.role(), is("member")); + assertThat(orgMembership.state(), is("active")); + } + + @Test + public void updateMembership() throws Exception { + final OrgMembershipCreate orgMembershipCreateRequest = + json.fromJson(getFixture("membership_update.json"), OrgMembershipCreate.class); + + final CompletableFuture fixtureResponse = + completedFuture( + json.fromJson(getFixture("org_membership.json"), OrgMembership.class)); + when(github.put(any(), any(), eq(OrgMembership.class))).thenReturn(fixtureResponse); + final CompletableFuture actualResponse = + organisationClient.updateOrgMembership(orgMembershipCreateRequest, "octocat"); + + assertThat(actualResponse.get().role(), is("member")); + assertThat(actualResponse.get().organization().login(), is("github")); + assertThat(actualResponse.get().user().login(), is("octocat")); + } +} diff --git a/src/test/java/com/spotify/github/v3/clients/PullRequestClientTest.java b/src/test/java/com/spotify/github/v3/clients/PullRequestClientTest.java index 1d79e765..a203a602 100644 --- a/src/test/java/com/spotify/github/v3/clients/PullRequestClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/PullRequestClientTest.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -21,8 +21,15 @@ package com.spotify.github.v3.clients; import static com.google.common.io.Resources.getResource; +import static com.spotify.github.MockHelper.createMockResponse; +import static java.lang.String.format; import static java.nio.charset.Charset.defaultCharset; -import static org.junit.Assert.assertEquals; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; @@ -30,11 +37,26 @@ import com.google.common.collect.ImmutableList; import com.google.common.io.Resources; +import com.spotify.github.async.Async; +import com.spotify.github.async.AsyncPage; +import com.spotify.github.http.HttpResponse; +import com.spotify.github.jackson.Json; import com.spotify.github.v3.exceptions.RequestNotOkException; +import com.spotify.github.v3.git.FileItem; +import com.spotify.github.v3.git.ImmutableFileItem; +import com.spotify.github.v3.prs.Comment; import com.spotify.github.v3.prs.ImmutableRequestReviewParameters; +import com.spotify.github.v3.prs.PullRequest; import com.spotify.github.v3.prs.ReviewRequests; +import com.spotify.github.v3.prs.requests.ImmutablePullRequestCreate; +import com.spotify.github.v3.prs.requests.ImmutablePullRequestUpdate; +import com.spotify.github.v3.prs.requests.PullRequestCreate; +import com.spotify.github.v3.prs.requests.PullRequestUpdate; +import com.spotify.github.v3.repos.CommitItem; import java.io.IOException; +import java.io.Reader; import java.net.URI; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import okhttp3.Call; @@ -45,23 +67,109 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; -import org.junit.Before; -import org.junit.Test; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; public class PullRequestClientTest { + private static final String MOCK_GITHUB_HOST = "bogus.host"; + private static final URI MOCK_GITHUB_URI = + URI.create(String.format("http://%s/api/v3", MOCK_GITHUB_HOST)); + private static final URI MOCK_GITHUB_URI_GQL = MOCK_GITHUB_URI.resolve("/graphql"); + private static final String PR_CHANGED_FILES_TEMPLATE = "/repos/%s/%s/pulls/%s/files"; private GitHubClient github; + private GitHubClient mockGithub; private OkHttpClient client; private static String getFixture(String resource) throws IOException { return Resources.toString(getResource(PullRequestClientTest.class, resource), defaultCharset()); } - @Before + @BeforeEach public void setUp() { client = mock(OkHttpClient.class); - github = GitHubClient.create(client, URI.create("http://bogus"), "token"); + github = GitHubClient.create(client, MOCK_GITHUB_URI, MOCK_GITHUB_URI_GQL, "token"); + mockGithub = mock(GitHubClient.class); + } + + @Test + public void createPullRequest() throws Exception { + final String title = "Amazing new feature"; + final String body = "Please pull these awesome changes in!"; + final String head = "octocat:new-topic"; + final String base = "master"; + + final Call call = mock(Call.class); + final ArgumentCaptor capture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(capture.capture()); + + final Response response = + new Response.Builder() + .code(201) + .protocol(Protocol.HTTP_1_1) + .message("Created") + .body( + ResponseBody.create( + MediaType.get("application/json"), getFixture("pull_request.json"))) + .request(new Request.Builder().url("http://localhost/").build()) + .build(); + + when(client.newCall(any())).thenReturn(call); + + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); + + final PullRequestCreate request = + ImmutablePullRequestCreate.builder().title(title).body(body).head(head).base(base).build(); + + final CompletableFuture result = pullRequestClient.create(request); + + capture.getValue().onResponse(call, response); + + PullRequest pullRequest = result.get(); + + assertThat(pullRequest.title(), is(title)); + assertThat(pullRequest.body().get(), is(body)); + assertThat(pullRequest.head().label().get(), is(head)); + assertThat(pullRequest.base().ref(), is(base)); + } + + @Test + public void updatePullRequest() throws Exception { + final String title = "Amazing new feature"; + final String body = "Please pull these awesome changes in!"; + + final Call call = mock(Call.class); + final ArgumentCaptor capture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(capture.capture()); + + final Response response = + new Response.Builder() + .code(200) + .protocol(Protocol.HTTP_1_1) + .message("OK") + .body( + ResponseBody.create( + MediaType.get("application/json"), getFixture("pull_request.json"))) + .request(new Request.Builder().url("http://localhost/").build()) + .build(); + + when(client.newCall(any())).thenReturn(call); + + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); + + final PullRequestUpdate request = + ImmutablePullRequestUpdate.builder().title(title).body(body).build(); + + final CompletableFuture result = pullRequestClient.update(1L, request); + + capture.getValue().onResponse(call, response); + + PullRequest pullRequest = result.get(); + + assertThat(pullRequest.title(), is(title)); + assertThat(pullRequest.body().get(), is(body)); } @Test @@ -77,18 +185,15 @@ public void testListReviewRequests() throws Throwable { .message("OK") .body( ResponseBody.create( - MediaType.get("application/json"), - getFixture("requestedReviews.json"))) + MediaType.get("application/json"), getFixture("requestedReviews.json"))) .request(new Request.Builder().url("http://localhost/").build()) .build(); when(client.newCall(any())).thenReturn(call); - final PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); - final CompletableFuture result = - pullRequestClient.listReviewRequests(1); + final CompletableFuture result = pullRequestClient.listReviewRequests(1L); capture.getValue().onResponse(call, response); @@ -117,13 +222,14 @@ public void testRemoveRequestedReview() throws Throwable { when(client.newCall(any())).thenReturn(call); - PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); CompletableFuture result = - pullRequestClient.removeRequestedReview(1, ImmutableRequestReviewParameters.builder() - .reviewers(ImmutableList.of("user1", "user2")) - .build()); + pullRequestClient.removeRequestedReview( + 1L, + ImmutableRequestReviewParameters.builder() + .reviewers(ImmutableList.of("user1", "user2")) + .build()); capture.getValue().onResponse(call, response); @@ -131,7 +237,7 @@ public void testRemoveRequestedReview() throws Throwable { // Passes without throwing } - @Test(expected = RequestNotOkException.class) + @Test public void testRemoveRequestedReview_failure() throws Throwable { final Call call = mock(Call.class); @@ -148,21 +254,341 @@ public void testRemoveRequestedReview_failure() throws Throwable { when(client.newCall(any())).thenReturn(call); - PullRequestClient pullRequestClient = - PullRequestClient.create(github, "owner", "repo"); + PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); CompletableFuture result = - pullRequestClient.removeRequestedReview(1, ImmutableRequestReviewParameters.builder() - .reviewers(ImmutableList.of("user1", "user2")) - .build()); + pullRequestClient.removeRequestedReview( + 1L, + ImmutableRequestReviewParameters.builder() + .reviewers(ImmutableList.of("user1", "user2")) + .build()); + + capture.getValue().onResponse(call, response); + + Exception exception = assertThrows(ExecutionException.class, result::get); + assertEquals(RequestNotOkException.class, exception.getCause().getClass()); + } + + @Test + public void testGetPatch() throws Throwable { + final Call call = mock(Call.class); + final ArgumentCaptor capture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(capture.capture()); + + final Response response = + new Response.Builder() + .code(200) + .protocol(Protocol.HTTP_1_1) + .message("OK") + .body( + ResponseBody.create( + MediaType.get("application/vnd.github.patch"), getFixture("patch.txt"))) + .request(new Request.Builder().url("http://localhost/").build()) + .build(); + + when(client.newCall(any())).thenReturn(call); + + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); + + final CompletableFuture result = pullRequestClient.patch(1L); + + capture.getValue().onResponse(call, response); + + Reader patchReader = result.get(); + + assertEquals(getFixture("patch.txt"), IOUtils.toString(patchReader)); + } + + @Test + public void testGetDiff() throws Throwable { + final Call call = mock(Call.class); + final ArgumentCaptor capture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(capture.capture()); + + final Response response = + new Response.Builder() + .code(200) + .protocol(Protocol.HTTP_1_1) + .message("OK") + .body( + ResponseBody.create( + MediaType.get("application/vnd.github.diff"), getFixture("diff.txt"))) + .request(new Request.Builder().url("http://localhost/").build()) + .build(); + + when(client.newCall(any())).thenReturn(call); + + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); + + final CompletableFuture result = pullRequestClient.diff(1L); capture.getValue().onResponse(call, response); - try { - result.get(); - } catch (ExecutionException e) { - throw e.getCause(); - // expecting RequestNotOkException - } + Reader diffReader = result.get(); + + assertEquals(getFixture("diff.txt"), IOUtils.toString(diffReader)); + } + + @Test + public void testCreateCommentReply() throws Throwable { + final Call call = mock(Call.class); + final ArgumentCaptor capture = ArgumentCaptor.forClass(Callback.class); + doNothing().when(call).enqueue(capture.capture()); + + final Response response = + new Response.Builder() + .code(201) + .protocol(Protocol.HTTP_1_1) + .message("Created") + .body( + ResponseBody.create( + MediaType.get("application/json"), + getFixture("pull_request_review_comment_reply.json"))) + .request(new Request.Builder().url("http://localhost/").build()) + .build(); + + when(client.newCall(any())).thenReturn(call); + + final PullRequestClient pullRequestClient = PullRequestClient.create(github, "owner", "repo"); + + final String replyBody = "Thanks for the feedback!"; + final CompletableFuture result = + pullRequestClient.createCommentReply(1L, 123L, replyBody); + + capture.getValue().onResponse(call, response); + + Comment comment = result.get(); + + assertThat(comment.body(), is("Great stuff!")); + assertThat(comment.id(), is(10L)); + assertThat( + comment.diffHunk(), is("@@ -16,33 +16,40 @@ public class Connection : IConnection...")); + assertThat(comment.path(), is("file1.txt")); + assertThat(comment.position(), is(1)); + assertThat(comment.originalPosition(), is(4)); + assertThat(comment.commitId(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e")); + assertThat(comment.originalCommitId(), is("9c48853fa3dc5c1c3d6f1f1cd1f2743e72652840")); + assertThat(comment.inReplyToId(), is(426899381L)); + assertThat(comment.authorAssociation(), is("NONE")); + assertThat(comment.user().login(), is("octocat")); + assertThat(comment.startLine(), is(1)); + assertThat(comment.originalStartLine(), is(1)); + assertThat(comment.startSide(), is("RIGHT")); + assertThat(comment.line(), is(2)); + assertThat(comment.originalLine(), is(2)); + assertThat(comment.side(), is("RIGHT")); + assertThat(comment.pullRequestReviewId(), is(42L)); + } + + @Test + void testChangedFiles() throws IOException { + FileItem file1 = + ImmutableFileItem.builder() + .filename("file1.txt") + .status("added") + .additions(103) + .deletions(21) + .changes(124) + .rawUrl( + URI.create( + "https://github.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt")) + .blobUrl( + URI.create( + "https://github.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/file1.txt")) + .patch("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test") + .contentsUrl( + URI.create( + "https://api.github.com/repos/octocat/Hello-World/contents/file1.txt?ref=6dcb09b5b57875f334f61aebed695e2e4193db5e")) + .sha("bbcd538c8e72b8c175046e27cc8f907076331401") + .build(); + FileItem file2 = + ImmutableFileItem.builder() + .filename("file2.txt") + .status("modified") + .additions(103) + .deletions(21) + .changes(124) + .rawUrl( + URI.create( + "https://github.com/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/file2.txt")) + .blobUrl( + URI.create( + "https://github.com/octocat/Hello-World/blob/6dcb09b5b57875f334f61aebed695e2e4193db5e/file2.txt")) + .patch("@@ -132,7 +132,7 @@ module Test @@ -1000,7 +1000,7 @@ module Test") + .contentsUrl( + URI.create( + "https://api.github.com/repos/octocat/Hello-World/contents/file2.txt?ref=6dcb09b5b57875f334f61aebed695e2e4193db5e")) + .sha("bbcd538c8e72b8c175046e27cc8f907076331401") + .build(); + List expectedFiles = List.of(file1, file2); + final String expectedBody = github.json().toJsonUnchecked(expectedFiles); + + final String pageLink = + "; rel=\"first\""; + + final HttpResponse firstPageResponse = createMockResponse(pageLink, expectedBody); + + when(mockGithub.request( + format(PR_CHANGED_FILES_TEMPLATE + "?per_page=30", "owner", "repo", "1"))) + .thenReturn(completedFuture(firstPageResponse)); + + when(mockGithub.json()).thenReturn(github.json()); + + final PullRequestClient pullRequestClient = + PullRequestClient.create(mockGithub, "owner", "repo"); + + final Iterable> pageIterator = () -> pullRequestClient.changedFiles(1L); + List actualFiles = Async.streamFromPaginatingIterable(pageIterator).collect(toList()); + assertEquals(actualFiles.size(), expectedFiles.size()); + assertEquals(actualFiles.get(0).filename(), expectedFiles.get(0).filename()); + assertEquals(actualFiles.get(1).filename(), expectedFiles.get(1).filename()); + } + + @Test + public void testListComments() throws Throwable { + final String expectedBody = "[" + getFixture("pull_request_review_comment_reply.json") + "]"; + + final String pageLink = + ";" + + " rel=\"first\""; + + final HttpResponse firstPageResponse = createMockResponse(pageLink, expectedBody); + + when(mockGithub.request("/repos/owner/repo/pulls/1/comments?per_page=30")) + .thenReturn(completedFuture(firstPageResponse)); + + when(mockGithub.json()).thenReturn(github.json()); + + final PullRequestClient pullRequestClient = + PullRequestClient.create(mockGithub, "owner", "repo"); + + final Iterable> pageIterator = () -> pullRequestClient.listComments(1L); + List comments = Async.streamFromPaginatingIterable(pageIterator).collect(toList()); + + assertEquals(1, comments.size()); + assertThat(comments.get(0).body(), is("Great stuff!")); + assertThat(comments.get(0).id(), is(10L)); + assertThat(comments.get(0).user().login(), is("octocat")); + } + + @Test + public void testListReviewComments() throws Throwable { + final String expectedBody = "[" + getFixture("pull_request_review_comment_reply.json") + "]"; + + final String pageLink = + ";" + + " rel=\"first\""; + + final HttpResponse firstPageResponse = createMockResponse(pageLink, expectedBody); + + when(mockGithub.request("/repos/owner/repo/pulls/1/reviews/123/comments?per_page=30")) + .thenReturn(completedFuture(firstPageResponse)); + + when(mockGithub.json()).thenReturn(github.json()); + + final PullRequestClient pullRequestClient = + PullRequestClient.create(mockGithub, "owner", "repo"); + + final Iterable> pageIterator = + () -> pullRequestClient.listReviewComments(1L, 123L); + List comments = Async.streamFromPaginatingIterable(pageIterator).collect(toList()); + + assertEquals(1, comments.size()); + assertThat(comments.get(0).body(), is("Great stuff!")); + assertThat(comments.get(0).id(), is(10L)); + assertThat(comments.get(0).user().login(), is("octocat")); + } + + @Test + public void listCommitsWithoutSpecifyingPages() throws Exception { + // Given + final int COMMIT_PER_PAGE = 30; + + final String firstPageLink = + "; rel=\"next\"," + + " ; rel=\"last\""; + final String firstPageBody = + Resources.toString( + getResource(this.getClass(), "pull_request_commits_page1.json"), defaultCharset()); + final HttpResponse firstPageResponse = createMockResponse(firstPageLink, firstPageBody); + + when(mockGithub.request("/repos/owner/repo/pulls/1/commits")) + .thenReturn(completedFuture(firstPageResponse)); + + final PullRequestClient pullRequestClient = + PullRequestClient.create(mockGithub, "owner", "repo"); + + // When + final List commits = pullRequestClient.listCommits(1L).get(); + + // Then + assertThat(commits.size(), is(COMMIT_PER_PAGE)); + assertThat( + commits.get(COMMIT_PER_PAGE - 1).commit().tree().sha(), + is("219cb4c1ffada21259876d390df1a85767481617")); + } + + @Test + public void listCommitsSpecifyingPages() throws Exception { + // Given + final int COMMIT_PER_PAGE = 30; + + final String firstPageLink = + String.format( + "<%s/repos/owner/repo/pulls/1/commits?page=2&per_page=30>; rel=\"next\"," + + " <%s/repos/owner/repo/pulls/1/commits?page=3&per_page=30>; rel=\"last\"", + MOCK_GITHUB_URI, MOCK_GITHUB_URI); + final String firstPageBody = + Resources.toString( + getResource(this.getClass(), "pull_request_commits_page1.json"), defaultCharset()); + final HttpResponse firstPageResponse = createMockResponse(firstPageLink, firstPageBody); + + final String secondPageLink = + String.format( + "<%s/repos/owner/repo/pulls/1/commits?page=1&per_page=30>; rel=\"prev\"," + + " <%s/repos/owner/repo/pulls/1/commits?page=3&per_page=30>; rel=\"next\"," + + " <%s/repos/owner/repo/pulls/1/commits?page=3&per_page=30>; rel=\"last\"," + + " <%s/repos/owner/repo/pulls/1/commits?page=1&per_page=30>; rel=\"first\"", + MOCK_GITHUB_URI, MOCK_GITHUB_URI, MOCK_GITHUB_URI, MOCK_GITHUB_URI); + final String secondPageBody = + Resources.toString( + getResource(this.getClass(), "pull_request_commits_page2.json"), defaultCharset()); + final HttpResponse secondPageResponse = createMockResponse(secondPageLink, secondPageBody); + + final String thirdPageLink = + String.format( + "<%s/repos/owner/repo/pulls/1/commits?page=2&per_page=30>; rel=\"prev\"," + + " <%s/repos/owner/repo/pulls/1/commits?page=1&per_page=30>; rel=\"first\"", + MOCK_GITHUB_URI, MOCK_GITHUB_URI); + final String thirdPageBody = + Resources.toString( + getResource(this.getClass(), "pull_request_commits_page3.json"), defaultCharset()); + final HttpResponse thirdPageResponse = createMockResponse(thirdPageLink, thirdPageBody); + + when(mockGithub.urlFor("")).thenReturn(MOCK_GITHUB_URI.toString()); + when(mockGithub.json()).thenReturn(Json.create()); + when(mockGithub.request("/repos/owner/repo/pulls/1/commits?per_page=30")) + .thenReturn(completedFuture(firstPageResponse)); + when(mockGithub.request("/repos/owner/repo/pulls/1/commits?page=1&per_page=30")) + .thenReturn(completedFuture(firstPageResponse)); + when(mockGithub.request("/repos/owner/repo/pulls/1/commits?page=2&per_page=30")) + .thenReturn(completedFuture(secondPageResponse)); + when(mockGithub.request("/repos/owner/repo/pulls/1/commits?page=3&per_page=30")) + .thenReturn(completedFuture(thirdPageResponse)); + + final PullRequestClient pullRequestClient = + PullRequestClient.create(mockGithub, "owner", "repo"); + + // When + final Iterable> pageIterator = + () -> pullRequestClient.listCommits(1L, 30); + final List commits = + Async.streamFromPaginatingIterable(pageIterator).collect(toList()); + + // Then + assertThat(commits.size(), is(COMMIT_PER_PAGE * 3)); + assertThat( + commits.get(COMMIT_PER_PAGE - 1).commit().tree().sha(), + is("219cb4c1ffada21259876d390df1a85767481617")); } } diff --git a/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java b/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java index 5f214551..fab85818 100644 --- a/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/RepositoryClientTest.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,60 +23,66 @@ import static com.google.common.io.Resources.getResource; import static com.spotify.github.FixtureHelper.loadFixture; import static com.spotify.github.v3.UserTest.assertUser; -import static com.spotify.github.v3.clients.GitHubClient.LIST_COMMIT_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_BRANCHES; +import static com.spotify.github.v3.clients.GitHubClient.LIST_COMMIT_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_FOLDERCONTENT_TYPE_REFERENCE; +import static com.spotify.github.v3.clients.GitHubClient.LIST_PR_TYPE_REFERENCE; import static com.spotify.github.v3.clients.GitHubClient.LIST_REPOSITORY; -import static com.spotify.github.v3.clients.MockHelper.createMockResponse; +import static com.spotify.github.v3.clients.GitHubClient.LIST_REPOSITORY_INVITATION; +import static com.spotify.github.MockHelper.createMockHttpResponse; +import static com.spotify.github.MockHelper.createMockResponse; import static com.spotify.github.v3.clients.RepositoryClient.STATUS_URI_TEMPLATE; import static java.lang.String.format; import static java.nio.charset.Charset.defaultCharset; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static java.util.stream.StreamSupport.stream; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.io.Resources; +import com.spotify.github.async.Async; import com.spotify.github.async.AsyncPage; +import com.spotify.github.http.HttpResponse; import com.spotify.github.jackson.Json; import com.spotify.github.v3.comment.Comment; +import com.spotify.github.v3.prs.PullRequestItem; import com.spotify.github.v3.repos.Branch; import com.spotify.github.v3.repos.Commit; import com.spotify.github.v3.repos.CommitComparison; import com.spotify.github.v3.repos.CommitItem; import com.spotify.github.v3.repos.CommitStatus; +import com.spotify.github.v3.repos.CommitWithFolderContent; import com.spotify.github.v3.repos.Content; import com.spotify.github.v3.repos.FolderContent; import com.spotify.github.v3.repos.Repository; +import com.spotify.github.v3.repos.RepositoryInvitation; +import com.spotify.github.v3.repos.RepositoryPermission; import com.spotify.github.v3.repos.RepositoryTest; import com.spotify.github.v3.repos.Status; -import com.spotify.github.v3.repos.requests.ImmutableAuthenticatedUserRepositoriesFilter; +import com.spotify.github.v3.repos.requests.*; + import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -import okhttp3.Headers; -import okhttp3.MediaType; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -@RunWith(PowerMockRunner.class) -@PrepareForTest({ Headers.class, ResponseBody.class, Response.class}) + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import uk.co.datumedge.hamcrest.json.SameJSONAs; + public class RepositoryClientTest { private GitHubClient github; @@ -87,7 +93,7 @@ private static String getFixture(String resource) throws IOException { return Resources.toString(getResource(RepositoryTest.class, resource), defaultCharset()); } - @Before + @BeforeEach public void setUp() { github = mock(GitHubClient.class); repoClient = new RepositoryClient(github, "someowner", "somerepo"); @@ -106,6 +112,28 @@ public void getRepository() throws Exception { assertThat(repository.name(), is("Hello-World")); assertThat(repository.fullName(), is(repository.owner().login() + "/Hello-World")); assertThat(repository.isPrivate(), is(false)); + assertThat(repository.isArchived(), is(false)); + assertThat(repository.fork(), is(false)); + } + + @Test + public void updateRepository() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("repository_get.json"), Repository.class)); + when(github.patch( + eq("/repos/someowner/somerepo"), + eq("{\"allow_auto_merge\":true}"), + eq(Repository.class))) + .thenReturn(fixture); + RepositoryUpdate request = + ImmutableRepositoryUpdate.builder().allowAutoMerge(Optional.of(true)).build(); + final Repository repository = repoClient.updateRepository(request).get(); + assertThat(repository.id(), is(1296269)); + assertUser(repository.owner()); + assertThat(repository.name(), is("Hello-World")); + assertThat(repository.fullName(), is(repository.owner().login() + "/Hello-World")); + assertThat(repository.isPrivate(), is(false)); + assertThat(repository.isArchived(), is(false)); assertThat(repository.fork(), is(false)); } @@ -123,15 +151,16 @@ public void listOrganizationRepositories() throws Exception { public void listAuthenticatedUserRepositories() throws Exception { final String pageLink = "; rel=\"first\""; final String pageBody = getFixture("list_of_repos_for_authenticated_user.json"); - final Response pageResponse = createMockResponse(pageLink, pageBody); + final HttpResponse pageResponse = createMockResponse(pageLink, pageBody); - when(github.request("/user/repos")).thenReturn(completedFuture(pageResponse)); + when(github.request("/user/repos?per_page=30")).thenReturn(completedFuture(pageResponse)); - final Iterable> pageIterator = () -> repoClient.listAuthenticatedUserRepositories(ImmutableAuthenticatedUserRepositoriesFilter.builder().build()); + final Iterable> pageIterator = + () -> + repoClient.listAuthenticatedUserRepositories( + ImmutableAuthenticatedUserRepositoriesFilter.builder().build()); final List repositories = - stream(pageIterator.spliterator(), false) - .flatMap(page -> stream(page.spliterator(), false)) - .collect(Collectors.toList()); + Async.streamFromPaginatingIterable(pageIterator).collect(Collectors.toList()); assertThat(repositories.get(0).id(), is(1296269)); assertThat(repositories.size(), is(1)); @@ -139,22 +168,96 @@ public void listAuthenticatedUserRepositories() throws Exception { @Test public void isCollaborator() throws Exception { - final Response response = mock(Response.class); - when(response.code()).thenReturn(204); - when(github.request("/repos/someowner/somerepo/collaborators/user")).thenReturn(completedFuture(response)); + final HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(204); + when(github.request("/repos/someowner/somerepo/collaborators/user")) + .thenReturn(completedFuture(response)); boolean isCollaborator = repoClient.isCollaborator("user").get(); assertTrue(isCollaborator); } @Test public void isNotCollaborator() throws Exception { - final Response response = mock(Response.class); - when(response.code()).thenReturn(404); - when(github.request("/repos/someowner/somerepo/collaborators/user")).thenReturn(completedFuture(response)); + final HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(404); + when(github.request("/repos/someowner/somerepo/collaborators/user")) + .thenReturn(completedFuture(response)); boolean isCollaborator = repoClient.isCollaborator("user").get(); assertFalse(isCollaborator); } + @Test + public void addCollaborator() throws Exception { + final HttpResponse response = createMockResponse("", getFixture("repository_invitation.json")); + when(github.put("/repos/someowner/somerepo/collaborators/user", "{\"permission\":\"pull\"}")) + .thenReturn(completedFuture(response)); + + final Optional maybeInvite = + repoClient.addCollaborator("user", RepositoryPermission.PULL).get(); + + assertTrue(maybeInvite.isPresent()); + final RepositoryInvitation repoInvite = maybeInvite.get(); + assertThat(repoInvite.id(), is(1)); + assertThat(repoInvite.nodeId(), is("MDEwOlJlcG9zaXRvcnkxMjk2MjY5")); + assertThat(repoInvite.repository().id(), is(1296269)); + assertUser(repoInvite.repository().owner()); + assertUser(repoInvite.invitee()); + assertUser(repoInvite.inviter()); + assertThat(repoInvite.permissions(), is("write")); + } + + @Test + public void addCollaboratorUserExists() throws Exception { + final HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(204); + when(github.put("/repos/someowner/somerepo/collaborators/user", "{\"permission\":\"pull\"}")) + .thenReturn(completedFuture(response)); + + final Optional maybeInvite = + repoClient.addCollaborator("user", RepositoryPermission.PULL).get(); + + assertTrue(maybeInvite.isEmpty()); + } + + @Test + public void removeCollaborator() throws Exception { + CompletableFuture response = completedFuture(mock(HttpResponse.class)); + final ArgumentCaptor capture = ArgumentCaptor.forClass(String.class); + when(github.delete(capture.capture())).thenReturn(response); + + CompletableFuture deleteResponse = repoClient.removeCollaborator("user"); + deleteResponse.get(); + + assertThat(capture.getValue(), is("/repos/someowner/somerepo/collaborators/user")); + } + + @Test + public void removeInvite() throws Exception { + CompletableFuture response = completedFuture(mock(HttpResponse.class)); + final ArgumentCaptor capture = ArgumentCaptor.forClass(String.class); + when(github.delete(capture.capture())).thenReturn(response); + + CompletableFuture deleteResponse = repoClient.removeInvite("invitation1"); + deleteResponse.get(); + + assertThat(capture.getValue(), is("/repos/someowner/somerepo/invitations/invitation1")); + } + + @Test + public void listInvites() throws Exception { + final CompletableFuture> fixture = + completedFuture( + json.fromJson( + "[" + getFixture("repository_invitation.json") + "]", LIST_REPOSITORY_INVITATION)); + when(github.request("/repos/someowner/somerepo/invitations", LIST_REPOSITORY_INVITATION)) + .thenReturn(fixture); + + final List invitations = repoClient.listInvitations().get(); + assertThat(invitations.size(), is(1)); + assertThat(invitations.get(0).repository().name(), is("Hello-World")); + assertThat(invitations.get(0).inviter().login(), is("octocat")); + } + @Test public void listCommits() throws Exception { final CompletableFuture> fixture = @@ -170,6 +273,22 @@ public void listCommits() throws Exception { commits.get(0).commit().tree().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e")); } + @Test + public void listPullRequestsForCommit() throws Exception { + final CompletableFuture> fixture = + completedFuture( + json.fromJson( + "[" + getFixture("../prs/pull_request_item.json") + "]", LIST_PR_TYPE_REFERENCE)); + when(github.request( + eq("/repos/someowner/somerepo/commits/thesha/pulls"), + eq(LIST_PR_TYPE_REFERENCE), + any())) + .thenReturn(fixture); + final List prs = repoClient.listPullRequestsForCommit("thesha").get(); + assertThat(prs.size(), is(1)); + assertThat(prs.get(0).number(), is(1347L)); + } + @Test public void getCommit() throws Exception { final CompletableFuture fixture = @@ -206,6 +325,62 @@ public void getFileContent() throws Exception { assertThat(fileContent.content(), is("encoded content ...")); } + @Test + public void createFileContent() throws Exception { + String rawFileCreateRequest = getFixture("create-content-request.json"); + final CompletableFuture fixture = + completedFuture( + json.fromJson( + getFixture("create-content-repsonse.json"), CommitWithFolderContent.class)); + when(github.put( + eq("/repos/someowner/somerepo/contents/test/README.md"), + argThat(body -> SameJSONAs.sameJSONAs(rawFileCreateRequest).matches(body)), + eq(CommitWithFolderContent.class))) + .thenReturn(fixture); + + FileCreate fileCreateRequest = + ImmutableFileCreate.builder() + .message("my commit message") + .content("encoded content ...") + .build(); + + final CommitWithFolderContent commitWithFolderContent = + repoClient.createFileContent("test/README.md", fileCreateRequest).get(); + assertThat(commitWithFolderContent.commit().message(), is("my commit message")); + assertThat(commitWithFolderContent.content().type(), is("file")); + assertThat(commitWithFolderContent.content().name(), is("README.md")); + assertThat(commitWithFolderContent.content().path(), is("test/README.md")); + } + + @Test + public void updateFileContent() throws Exception { + String rawFileUpdateRequest = getFixture("update-content-request.json"); + final CompletableFuture fixture = + completedFuture( + json.fromJson( + getFixture("create-content-repsonse.json"), CommitWithFolderContent.class)); + when(github.put( + eq("/repos/someowner/somerepo/contents/test/README.md"), + argThat(body -> SameJSONAs.sameJSONAs(rawFileUpdateRequest).matches(body)), + eq(CommitWithFolderContent.class))) + .thenReturn(fixture); + + FileUpdate fileUpdateRequest = + ImmutableFileUpdate.builder() + .message("my commit message") + .content("encoded content ...") + .branch("test-branch") + .sha("12345") + .build(); + + final CommitWithFolderContent commitWithFolderContent = + repoClient.updateFileContent("test/README.md", fileUpdateRequest).get(); + assertThat(commitWithFolderContent.commit().message(), is("my commit message")); + assertThat(commitWithFolderContent.content().type(), is("file")); + assertThat(commitWithFolderContent.content().name(), is("README.md")); + assertThat(commitWithFolderContent.content().path(), is("test/README.md")); + } + @Test public void getFolderContent() throws Exception { final CompletableFuture> fixture = @@ -249,20 +424,108 @@ public void getBranch() throws Exception { when(github.request("/repos/someowner/somerepo/branches/somebranch", Branch.class)) .thenReturn(fixture); final Branch branch = repoClient.getBranch("somebranch").get(); + assertThat(branch.isProtected().orElse(false), is(true)); + assertThat( + branch.protectionUrl().get().toString(), + is("https://api.github.com/repos/octocat/Hello-World/branches/master/protection")); + assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e")); + assertThat( + branch.commit().url().toString(), + is( + "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc")); + assertTrue(branch.protection().isPresent()); + assertTrue(branch.protection().get().enabled()); + assertThat( + branch.protection().get().requiredStatusChecks().enforcementLevel(), is("non_admins")); + assertTrue(branch.protection().get().requiredStatusChecks().contexts().contains("Context 1")); + assertTrue(branch.protection().get().requiredStatusChecks().contexts().contains("Context 2")); + } + + @Test + public void getBranchWithNoProtection() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("branch-not-protected.json"), Branch.class)); + when(github.request("/repos/someowner/somerepo/branches/somebranch", Branch.class)) + .thenReturn(fixture); + final Branch branch = repoClient.getBranch("somebranch").get(); + assertThat(branch.isProtected().orElse(false), is(false)); + assertTrue(branch.protectionUrl().isEmpty()); assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e")); } + @Test + public void getBranchWithoutProtectionFields() throws Exception { + final CompletableFuture fixture = + completedFuture( + json.fromJson(getFixture("branch-no-protection-fields.json"), Branch.class)); + when(github.request("/repos/someowner/somerepo/branches/somebranch", Branch.class)) + .thenReturn(fixture); + final Branch branch = repoClient.getBranch("somebranch").get(); + assertThat(branch.isProtected().orElse(false), is(false)); + assertTrue(branch.protectionUrl().isEmpty()); + assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e")); + assertThat( + branch.commit().url().toString(), + is( + "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc")); + } + + @Test + public void getBranchWithCharactersIncorrectlyUnescapedByTheGithubApi() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("branch-escape-chars.json"), Branch.class)); + when(github.request( + "/repos/someowner/somerepo/branches/unescaped-percent-sign-%", Branch.class)) + .thenReturn(fixture); + final Branch branch = repoClient.getBranch("unescaped-percent-sign-%").get(); + assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e")); + assertThat( + branch.protectionUrl().get().toString(), + is( + "https://api.github.com/repos/octocat/Hello-World/branches/unescaped-percent-sign-%25/protection")); + } + + @Test + public void getBranchWithCharactersIncorrectlyUnescapedByTheGithubApi_uriVariationTwo() + throws Exception { + final CompletableFuture fixture = + completedFuture( + json.fromJson(getFixture("branch-escape-chars-url-variation-two.json"), Branch.class)); + when(github.request( + "/repos/someowner/somerepo/branches/unescaped-percent-sign-%", Branch.class)) + .thenReturn(fixture); + final Branch branch = repoClient.getBranch("unescaped-percent-sign-%").get(); + assertThat(branch.commit().sha(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e")); + assertThat( + branch.protectionUrl().get().toString(), + is( + "https://api.github.com/api/v3/repos/octocat/Hello-World/branches/branch-name-with-slashes/unescaped-percent-sign-%25/protection")); + } + @Test public void listBranches() throws Exception { final CompletableFuture> fixture = completedFuture(json.fromJson(getFixture("list_branches.json"), LIST_BRANCHES)); - when(github.request("/repos/someowner/somerepo/branches", LIST_BRANCHES)) - .thenReturn(fixture); + when(github.request("/repos/someowner/somerepo/branches", LIST_BRANCHES)).thenReturn(fixture); final List branches = repoClient.listBranches().get(); assertThat(branches.get(0).commit().sha(), is("c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc")); assertThat(branches.size(), is(1)); } + @Test + void listAllBranches() throws Exception { + final String link = + "; rel=\"last\""; + final HttpResponse response = createMockResponse(link, getFixture("list_branches.json")); + + when(github.request("/repos/someowner/somerepo/branches?per_page=30")) + .thenReturn(completedFuture(response)); + final List branches = + Async.streamFromPaginatingIterable(repoClient::listAllBranches) + .collect(Collectors.toList()); + assertThat(branches.get(0).commit().sha(), is("c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc")); + assertThat(branches.size(), is(1)); + } @Test public void testCommentCreated() throws IOException { @@ -277,7 +540,7 @@ public void testCommentCreated() throws IOException { .thenReturn(fixture); final Comment comment = repoClient.createComment("someweirdsha", "Me too").join(); - assertThat(comment.id(), is(123)); + assertThat(comment.id(), is(123L)); assertThat(comment.commitId().get(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e")); } @@ -289,7 +552,7 @@ public void getComment() throws IOException { .thenReturn(fixture); final Comment comment = repoClient.getComment(123).join(); - assertThat(comment.id(), is(123)); + assertThat(comment.id(), is(123L)); assertThat(comment.commitId().get(), is("6dcb09b5b57875f334f61aebed695e2e4193db5e")); } @@ -299,26 +562,34 @@ public void testStatusesPaginationForeach() throws Exception { "; rel=\"next\", ; rel=\"last\""; final String firstPageBody = loadFixture("clients/statuses_page1.json"); - final Response firstPageResponse = createMockResponse(firstPageLink, firstPageBody); + final HttpResponse firstPageResponse = createMockResponse(firstPageLink, firstPageBody); final String lastPageLink = "; rel=\"first\", ; rel=\"prev\""; final String lastPageBody = loadFixture("clients/statuses_page2.json"); - final Response lastPageResponse = createMockResponse(lastPageLink, lastPageBody); + final HttpResponse lastPageResponse = createMockResponse(lastPageLink, lastPageBody); when(github.urlFor("")).thenReturn("https://github.com/api/v3"); when(github.request( - format(STATUS_URI_TEMPLATE, "someowner", "somerepo", "553c2077f0edc3d5dc5d17262f6aa498e69d6f8e"))) + format( + STATUS_URI_TEMPLATE + "?per_page=30", + "someowner", + "somerepo", + "553c2077f0edc3d5dc5d17262f6aa498e69d6f8e"))) .thenReturn(completedFuture(firstPageResponse)); when(github.request( - format(STATUS_URI_TEMPLATE + "?page=2", "someowner", "somerepo", "553c2077f0edc3d5dc5d17262f6aa498e69d6f8e"))) + format( + STATUS_URI_TEMPLATE + "?page=2&per_page=30", + "someowner", + "somerepo", + "553c2077f0edc3d5dc5d17262f6aa498e69d6f8e"))) .thenReturn(completedFuture(lastPageResponse)); final List listStatuses = Lists.newArrayList(); - repoClient.listCommitStatuses("553c2077f0edc3d5dc5d17262f6aa498e69d6f8e", 10) - .forEachRemaining(page -> page.iterator() - .forEachRemaining(listStatuses::add)); + repoClient + .listCommitStatuses("553c2077f0edc3d5dc5d17262f6aa498e69d6f8e", 10) + .forEachRemaining(page -> page.iterator().forEachRemaining(listStatuses::add)); assertThat(listStatuses.size(), is(12)); assertThat(listStatuses.get(0).id(), is(61764535L)); @@ -327,23 +598,19 @@ public void testStatusesPaginationForeach() throws Exception { @Test public void merge() throws IOException { - CompletableFuture okResponse = completedFuture( - new Response.Builder() - .request(new Request.Builder().url("http://example.com/whatever").build()) - .protocol(Protocol.HTTP_1_1) - .message("") - .code(201) - .body( - ResponseBody.create( - MediaType.get("application/json"), - getFixture("merge_commit_item.json") - )) - .build()); - final String expectedRequestBody = json.toJsonUnchecked(ImmutableMap.of( - "base", "basebranch", - "head", "headbranch")); - when(github - .post("/repos/someowner/somerepo/merges", expectedRequestBody)) + CompletableFuture okResponse = + completedFuture( + createMockHttpResponse( + "http://example.com/whatever", + 201, + getFixture("merge_commit_item.json"), + Map.of())); + final String expectedRequestBody = + json.toJsonUnchecked( + ImmutableMap.of( + "base", "basebranch", + "head", "headbranch")); + when(github.post("/repos/someowner/somerepo/merges", expectedRequestBody)) .thenReturn(okResponse); final CommitItem commit = repoClient.merge("basebranch", "headbranch").join().get(); @@ -354,21 +621,12 @@ public void merge() throws IOException { @Test public void createFork() throws IOException { - CompletableFuture okResponse = completedFuture( - new Response.Builder() - .request(new Request.Builder().url("http://example.com/whatever").build()) - .protocol(Protocol.HTTP_1_1) - .message("") - .code(202) - .body( - ResponseBody.create( - MediaType.get("application/json"), - getFixture("fork_create_item.json") - )) - .build()); + CompletableFuture okResponse = + completedFuture( + createMockHttpResponse( + "http://example.com/whatever", 202, getFixture("fork_create_item.json"), Map.of())); final String expectedRequestBody = json.toJsonUnchecked(ImmutableMap.of()); - when(github - .post("/repos/someowner/somerepo/forks", expectedRequestBody)) + when(github.post("/repos/someowner/somerepo/forks", expectedRequestBody)) .thenReturn(okResponse); final Repository repo = repoClient.createFork(null).join(); @@ -377,15 +635,80 @@ public void createFork() throws IOException { @Test public void mergeNoop() { - CompletableFuture okResponse = completedFuture( - new Response.Builder() - .request(new Request.Builder().url("http://example.com/whatever").build()) - .protocol(Protocol.HTTP_1_1) - .message("") - .code(204) // No Content - .build()); + CompletableFuture okResponse = + completedFuture(createMockHttpResponse("http://example.com/whatever", 204, null, Map.of())); when(github.post(any(), any())).thenReturn(okResponse); final Optional maybeCommit = repoClient.merge("basebranch", "headbranch").join(); assertThat(maybeCommit, is(Optional.empty())); } + + @Test + public void shouldDownloadTarball() throws Exception { + CompletableFuture fixture = + completedFuture( + createMockHttpResponse( + "http://example.com/whatever", + 200, + "some bytes", + Map.of())); + when(github.request("/repos/someowner/somerepo/tarball/")).thenReturn(fixture); + + try (InputStream response = repoClient.downloadTarball().get().orElseThrow()) { + String result = new String(response.readAllBytes(), StandardCharsets.UTF_8); + assertThat(result, is("some bytes")); + } + } + + @Test + public void shouldDownloadZipball() throws Exception { + CompletableFuture fixture = + completedFuture( + createMockHttpResponse( + "http://example.com/whatever", + 200, + "some bytes", + Map.of())); + when(github.request("/repos/someowner/somerepo/zipball/")).thenReturn(fixture); + + try (InputStream response = repoClient.downloadZipball().get().orElseThrow()) { + String result = new String(response.readAllBytes(), StandardCharsets.UTF_8); + assertThat(result, is("some bytes")); + } + } + + @Test + public void shouldReturnEmptyOptionalWhenResponseBodyNotPresent() throws Exception { + CompletableFuture fixture = + completedFuture(createMockHttpResponse("http://example.com/whatever", 204, null, Map.of())); + when(github.request("/repos/someowner/somerepo/zipball/master")).thenReturn(fixture); + + Optional response = repoClient.downloadZipball("master").get(); + assertThat(response, is(Optional.empty())); + } + + @Test + public void shouldReturnEmptyResponseWhenRepositoryDispatchEndpointTriggered() throws Exception { + final HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(204); + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode clientPayload = mapper.createObjectNode(); + clientPayload.put("my-custom-true-property", "true"); + clientPayload.put("my-custom-false-property", "false"); + + RepositoryDispatch repositoryDispatchRequest = + ImmutableRepositoryDispatch.builder() + .eventType("my-custom-event") + .clientPayload(clientPayload) + .build(); + + when(github.post( + "/repos/someowner/somerepo/dispatches", + json.toJsonUnchecked(repositoryDispatchRequest))) + .thenReturn(completedFuture(response)); + + boolean repoDispatchResult = + repoClient.createRepositoryDispatchEvent(repositoryDispatchRequest).get(); + assertTrue(repoDispatchResult); + } } diff --git a/src/test/java/com/spotify/github/v3/clients/SearchClientTest.java b/src/test/java/com/spotify/github/v3/clients/SearchClientTest.java index 53d74d10..40e6c571 100644 --- a/src/test/java/com/spotify/github/v3/clients/SearchClientTest.java +++ b/src/test/java/com/spotify/github/v3/clients/SearchClientTest.java @@ -35,8 +35,8 @@ import com.spotify.github.v3.search.requests.ImmutableSearchParameters; import java.io.IOException; import java.util.concurrent.CompletableFuture; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class SearchClientTest { @@ -48,7 +48,7 @@ private static String getFixture(String resource) throws IOException { return Resources.toString(getResource(SearchTest.class, resource), defaultCharset()); } - @Before + @BeforeEach public void setUp() { github = mock(GitHubClient.class); searchClient = SearchClient.create(github); diff --git a/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java b/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java new file mode 100644 index 00000000..457c16ed --- /dev/null +++ b/src/test/java/com/spotify/github/v3/clients/TeamClientTest.java @@ -0,0 +1,254 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.clients; + +import static com.google.common.io.Resources.getResource; +import static com.spotify.github.MockHelper.createMockHttpResponse; +import static com.spotify.github.v3.clients.GitHubClient.LIST_PENDING_TEAM_INVITATIONS; +import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAMS; +import static com.spotify.github.v3.clients.GitHubClient.LIST_TEAM_MEMBERS; +import static com.spotify.github.MockHelper.createMockResponse; +import static java.nio.charset.Charset.defaultCharset; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.stream.Collectors.toList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +import com.google.common.io.Resources; +import com.spotify.github.async.Async; +import com.spotify.github.async.AsyncPage; +import com.spotify.github.http.HttpResponse; +import com.spotify.github.jackson.Json; +import com.spotify.github.v3.Team; +import com.spotify.github.v3.User; +import com.spotify.github.v3.orgs.Membership; +import com.spotify.github.v3.orgs.TeamInvitation; +import com.spotify.github.v3.orgs.requests.MembershipCreate; +import com.spotify.github.v3.orgs.requests.TeamCreate; +import com.spotify.github.v3.orgs.requests.TeamUpdate; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +public class TeamClientTest { + + private GitHubClient github; + + private TeamClient teamClient; + + private Json json; + + private static String getFixture(String resource) throws IOException { + return Resources.toString(getResource(TeamClientTest.class, resource), defaultCharset()); + } + + @BeforeEach + public void setUp() { + github = mock(GitHubClient.class); + teamClient = new TeamClient(github, "github"); + json = Json.create(); + when(github.json()).thenReturn(json); + when(github.urlFor("")).thenReturn("https://github.com/api/v3"); + } + + @Test + public void getTeam() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("team_get.json"), Team.class)); + when(github.request("/orgs/github/teams/justice-league", Team.class)).thenReturn(fixture); + final Team team = teamClient.getTeam("justice-league").get(); + assertThat(team.id(), is(1)); + assertThat(team.name(), is("Justice League")); + } + + @Test + public void listTeams() throws Exception { + final CompletableFuture> fixture = + completedFuture(json.fromJson(getFixture("teams_list.json"), LIST_TEAMS)); + when(github.request("/orgs/github/teams", LIST_TEAMS)).thenReturn(fixture); + final List teams = teamClient.listTeams().get(); + assertThat(teams.get(0).slug(), is("justice-league")); + assertThat(teams.get(1).slug(), is("x-men")); + assertThat(teams.size(), is(2)); + } + + @Test + public void deleteTeam() throws Exception { + final CompletableFuture response = completedFuture(mock(HttpResponse.class)); + final ArgumentCaptor capture = ArgumentCaptor.forClass(String.class); + when(github.delete(capture.capture())).thenReturn(response); + + CompletableFuture deleteResponse = teamClient.deleteTeam("justice-league"); + deleteResponse.get(); + assertThat(capture.getValue(), is("/orgs/github/teams/justice-league")); + } + + @Test + public void createTeam() throws Exception { + final TeamCreate teamCreateRequest = + json.fromJson(getFixture("teams_request.json"), TeamCreate.class); + + final CompletableFuture fixtureResponse = + completedFuture(json.fromJson(getFixture("team_get.json"), Team.class)); + when(github.post(any(), any(), eq(Team.class))).thenReturn(fixtureResponse); + final CompletableFuture actualResponse = teamClient.createTeam(teamCreateRequest); + + assertThat(actualResponse.get().name(), is("Justice League")); + verify(github, times(1)) + .post(eq("/orgs/github/teams"), eq("{\"name\":\"Justice League\"}"), eq(Team.class)); + } + + @Test + public void updateTeam() throws Exception { + final TeamUpdate teamUpdateRequest = + json.fromJson(getFixture("teams_patch.json"), TeamUpdate.class); + + final CompletableFuture fixtureResponse = + completedFuture(json.fromJson(getFixture("teams_patch_response.json"), Team.class)); + when(github.patch(any(), any(), eq(Team.class))).thenReturn(fixtureResponse); + final CompletableFuture actualResponse = + teamClient.updateTeam(teamUpdateRequest, "justice-league"); + + assertThat(actualResponse.get().name(), is("Justice League2")); + verify(github, times(1)) + .patch( + eq("/orgs/github/teams/justice-league"), + eq("{\"name\":\"Justice League2\"}"), + eq(Team.class)); + } + + @Test + public void getMembership() throws Exception { + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("membership.json"), Membership.class)); + when(github.request("/orgs/github/teams/1/memberships/octocat", Membership.class)) + .thenReturn(fixture); + final Membership membership = teamClient.getMembership("1", "octocat").get(); + assertThat( + membership.url().toString(), is("https://api.github.com/teams/1/memberships/octocat")); + assertThat(membership.role(), is("maintainer")); + assertThat(membership.state(), is("active")); + } + + @Test + public void listTeamMembers() throws Exception { + final CompletableFuture> fixture = + completedFuture(json.fromJson(getFixture("list_members.json"), LIST_TEAM_MEMBERS)); + when(github.request("/orgs/github/teams/1/members", LIST_TEAM_MEMBERS)).thenReturn(fixture); + final List teamMembers = teamClient.listTeamMembers("1").get(); + assertThat(teamMembers.get(0).login(), is("octocat")); + assertThat(teamMembers.get(1).id(), is(2)); + assertThat(teamMembers.size(), is(2)); + } + + @Test + public void listTeamMembersPaged() throws Exception { + final String firstPageLink = + "; rel=\"next\", ; rel=\"last\""; + final String firstPageBody = + Resources.toString( + getResource(this.getClass(), "list_members_page1.json"), defaultCharset()); + final HttpResponse firstPageResponse = createMockResponse(firstPageLink, firstPageBody); + + final String lastPageLink = + "; rel=\"first\", ; rel=\"prev\""; + final String lastPageBody = + Resources.toString( + getResource(this.getClass(), "list_members_page2.json"), defaultCharset()); + + final HttpResponse lastPageResponse = createMockResponse(lastPageLink, lastPageBody); + + when(github.request(endsWith("/orgs/github/teams/1/members?per_page=1"))) + .thenReturn(completedFuture(firstPageResponse)); + when(github.request(endsWith("/orgs/github/teams/1/members?page=2&per_page=1"))) + .thenReturn(completedFuture(lastPageResponse)); + + final Iterable> pageIterator = () -> teamClient.listTeamMembers("1", 1); + final List users = Async.streamFromPaginatingIterable(pageIterator).collect(toList()); + + assertThat(users.size(), is(2)); + assertThat(users.get(0).login(), is("octocat")); + assertThat(users.get(1).id(), is(2)); + } + + @Test + public void updateMembership() throws Exception { + final MembershipCreate membershipCreateRequest = + json.fromJson(getFixture("membership_update.json"), MembershipCreate.class); + + final CompletableFuture fixtureResponse = + completedFuture( + json.fromJson(getFixture("membership_update_response.json"), Membership.class)); + when(github.put(any(), any(), eq(Membership.class))).thenReturn(fixtureResponse); + final CompletableFuture actualResponse = + teamClient.updateMembership(membershipCreateRequest, "1", "octocat"); + + assertThat(actualResponse.get().role(), is("member")); + } + + @Test + public void deleteMembership() throws Exception { + final CompletableFuture response = completedFuture(mock(HttpResponse.class)); + final ArgumentCaptor capture = ArgumentCaptor.forClass(String.class); + when(github.delete(capture.capture())).thenReturn(response); + + CompletableFuture deleteResponse = teamClient.deleteMembership("1", "octocat"); + deleteResponse.get(); + assertThat(capture.getValue(), is("/orgs/github/teams/1/memberships/octocat")); + } + + @Test + public void listPendingTeamInvitations() throws Exception { + final CompletableFuture> fixture = + completedFuture( + json.fromJson(getFixture("list_team_invitations.json"), LIST_PENDING_TEAM_INVITATIONS)); + when(github.request("/orgs/github/teams/1/invitations", LIST_PENDING_TEAM_INVITATIONS)) + .thenReturn(fixture); + final List pendingInvitations = + teamClient.listPendingTeamInvitations("1").get(); + assertThat(pendingInvitations.get(0).login(), is("octocat")); + assertThat(pendingInvitations.get(1).id(), is(2)); + assertThat(pendingInvitations.size(), is(2)); + } + + @Test + void updateTeamPermissions() { + String apiUrl = "/orgs/github/teams/cat-squad/repos/github/octocat"; + HttpResponse response = createMockHttpResponse(apiUrl, 204, "", Map.of()); + when(github.put(eq(apiUrl), any())).thenReturn(completedFuture(response)); + + teamClient.updateTeamPermissions("cat-squad", "octocat", "pull").join(); + + verify(github, times(1)) + .put( + eq(apiUrl), + eq( + "{\"org\":\"github\",\"repo\":\"octocat\",\"team_slug\":\"cat-squad\",\"permission\":\"pull\"}")); + } +} diff --git a/src/test/java/com/spotify/github/v3/clients/UserClientTest.java b/src/test/java/com/spotify/github/v3/clients/UserClientTest.java new file mode 100644 index 00000000..50fcd3b9 --- /dev/null +++ b/src/test/java/com/spotify/github/v3/clients/UserClientTest.java @@ -0,0 +1,112 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2024 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.v3.clients; + +import static com.google.common.io.Resources.getResource; +import static java.nio.charset.Charset.defaultCharset; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.io.Resources; +import com.spotify.github.http.HttpResponse; +import com.spotify.github.jackson.Json; +import com.spotify.github.v3.checks.Installation; +import com.spotify.github.v3.user.requests.ImmutableSuspensionReason; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +import okhttp3.Response; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class UserClientTest { + + private GitHubClient github; + private UserClient userClient; + private String owner = "github"; + private Json json; + private static String getFixture(String resource) throws IOException { + return Resources.toString(getResource(TeamClientTest.class, resource), defaultCharset()); + } + + @BeforeEach + public void setUp() { + github = mock(GitHubClient.class); + userClient = new UserClient(github, owner); + json = Json.create(); + when(github.json()).thenReturn(json); + } + + @Test + public void testSuspendUserSuccess() throws Exception { + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(204); + when(github.put(eq("/users/username/suspended"), any())).thenReturn(completedFuture(response)); + final CompletableFuture result = userClient.suspendUser("username", ImmutableSuspensionReason.builder().reason("That's why").build()); + assertTrue(result.get()); + } + + @Test + public void testSuspendUserFailure() throws Exception { + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(403); + when(github.put(eq("/users/username/suspended"), any())).thenReturn(completedFuture(response)); + final CompletableFuture result = userClient.suspendUser("username", ImmutableSuspensionReason.builder().reason("That's why").build()); + assertFalse(result.get()); + } + + @Test + public void testUnSuspendUserSuccess() throws Exception { + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(204); + when(github.delete(eq("/users/username/suspended"), any())).thenReturn(completedFuture(response)); + final CompletableFuture result = userClient.unSuspendUser("username", ImmutableSuspensionReason.builder().reason("That's why").build()); + assertTrue(result.get()); + } + + @Test + public void testUnSuspendUserFailure() throws Exception { + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(403); + when(github.delete(eq("/users/username/suspended"), any())).thenReturn(completedFuture(response)); + final CompletableFuture result = userClient.unSuspendUser("username", ImmutableSuspensionReason.builder().reason("That's why").build()); + assertFalse(result.get()); + } + + @Test + public void testAppClient() throws Exception { + final GithubAppClient githubAppClient = userClient.createGithubAppClient(); + final CompletableFuture fixture = + completedFuture(json.fromJson(getFixture("../githubapp/installation.json"), Installation.class)); + when(github.request("/users/github/installation", Installation.class)).thenReturn(fixture); + + final Installation installation = githubAppClient.getUserInstallation().get(); + + assertThat(installation.id(), is(1)); + assertThat(installation.account().login(), is("github")); + } +} diff --git a/src/test/java/com/spotify/github/v3/clients/WorkflowsClientTest.java b/src/test/java/com/spotify/github/v3/clients/WorkflowsClientTest.java new file mode 100644 index 00000000..3a51722a --- /dev/null +++ b/src/test/java/com/spotify/github/v3/clients/WorkflowsClientTest.java @@ -0,0 +1,97 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ + +package com.spotify.github.v3.clients; + +import com.google.common.io.Resources; +import com.spotify.github.jackson.Json; +import com.spotify.github.v3.workflows.WorkflowsRepositoryResponseList; +import com.spotify.github.v3.workflows.WorkflowsResponse; +import com.spotify.github.v3.workflows.WorkflowsState; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.concurrent.CompletableFuture; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class WorkflowsClientTest { + + private static final String FIXTURES_PATH = "com/spotify/github/v3/workflows/"; + private GitHubClient github; + private WorkflowsClient workflowsClient; + private Json json; + + public static String loadResource(final String path) { + try { + return Resources.toString(Resources.getResource(path), UTF_8); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @BeforeEach + public void setUp() { + github = mock(GitHubClient.class); + workflowsClient = new WorkflowsClient(github, "someowner", "somerepo"); + json = Json.create(); + when(github.json()).thenReturn(json); + } + + @Test + public void getWorkflow() throws Exception { + final WorkflowsResponse workflowsResponse = + json.fromJson( + loadResource(FIXTURES_PATH + "workflows-get-workflow-response.json"), WorkflowsResponse.class); + final CompletableFuture fixtureResponse = completedFuture(workflowsResponse); + when(github.request(any(), eq(WorkflowsResponse.class), any())).thenReturn(fixtureResponse); + + final CompletableFuture actualResponse = + workflowsClient.getWorkflow(161335); + + assertThat(actualResponse.get().id(), is(161335)); + assertThat(actualResponse.get().state(), is(WorkflowsState.active)); + } + + @Test + public void listWorkflows() throws Exception { + final WorkflowsRepositoryResponseList workflowsListResponse = + json.fromJson( + loadResource(FIXTURES_PATH + "workflows-list-workflows-response.json"), WorkflowsRepositoryResponseList.class); + final CompletableFuture fixtureResponse = completedFuture(workflowsListResponse); + when(github.request(any(), eq(WorkflowsRepositoryResponseList.class), any())).thenReturn(fixtureResponse); + + final CompletableFuture actualResponse = + workflowsClient.listWorkflows(); + + assertThat(actualResponse.get().totalCount(), is(2)); + assertThat(actualResponse.get().workflows().get(0).name(), is("CI")); + assertThat(actualResponse.get().workflows().get(1).name(), is("Linter")); + } +} \ No newline at end of file diff --git a/src/test/java/com/spotify/github/v3/comment/CommentReactionContentTest.java b/src/test/java/com/spotify/github/v3/comment/CommentReactionContentTest.java new file mode 100644 index 00000000..cade32ba --- /dev/null +++ b/src/test/java/com/spotify/github/v3/comment/CommentReactionContentTest.java @@ -0,0 +1,40 @@ +/*- + * -\-\- + * github-api + * -- + * Copyright (C) 2016 - 2020 Spotify AB + * -- + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * -/-/- + */ +package com.spotify.github.v3.comment; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +class CommentReactionContentTest { + @ParameterizedTest + @EnumSource(CommentReactionContent.class) + public void testDeserialize(CommentReactionContent reaction) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + String json = "\"" + reaction.toString() + "\""; + + CommentReactionContent content = mapper.readValue(json, CommentReactionContent.class); + + assertEquals(reaction, content); + } +} diff --git a/src/test/java/com/spotify/github/v3/prs/PullRequestTest.java b/src/test/java/com/spotify/github/v3/prs/PullRequestTest.java index 2549fc37..5cedad6b 100644 --- a/src/test/java/com/spotify/github/v3/prs/PullRequestTest.java +++ b/src/test/java/com/spotify/github/v3/prs/PullRequestTest.java @@ -24,12 +24,14 @@ import static java.nio.charset.Charset.defaultCharset; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import com.google.common.io.Resources; import com.spotify.github.jackson.Json; import java.io.IOException; import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class PullRequestTest { @@ -38,6 +40,9 @@ public void testDeserializationPr() throws IOException { String fixture = Resources.toString(getResource(this.getClass(), "pull_request.json"), defaultCharset()); final PullRequest pr = Json.create().fromJson(fixture, PullRequest.class); + + assertThat(pr.id(), is(1L)); + assertThat(pr.nodeId(), is("MDExOlB1bGxSZXF1ZXN0NDI3NDI0Nw==")); assertThat(pr.mergeCommitSha().get(), is("e5bd3914e2e596debea16f433f57875b5b90bcd6")); assertThat(pr.merged(), is(false)); assertThat(pr.mergeable().get(), is(true)); @@ -46,6 +51,23 @@ public void testDeserializationPr() throws IOException { assertThat(pr.deletions(), is(3)); assertThat(pr.changedFiles(), is(5)); assertThat(pr.draft(), is(Optional.of(false))); + assertThat(pr.labels().size(),is(1)); + assertThat(pr.labels().get(0).name(),is("bug")); + assertThat(pr.labels().get(0).id(),is(42L)); + assertThat(pr.labels().get(0).color(),is("ff0000")); + } + + @Test + public void testDeserializationPrWithLargeId() throws IOException { + String fixture = + Resources.toString(getResource(this.getClass(), "pull_request_long_id.json"), defaultCharset()); + final PullRequest pr = Json.create().fromJson(fixture, PullRequest.class); + + assertThat(pr.id(), is(2459198527L)); + assertThat(pr.head().sha(), is("f74c7f420282f584acd2fb5964202e5b525c3ab8")); + assertThat(pr.merged(), is(false)); + assertThat(pr.mergeable().get(), is(false)); + assertThat(pr.draft(), is(Optional.of(true))); } @Test @@ -65,6 +87,36 @@ public void testSerializationMergeParams() throws IOException { assertThat(params.mergeMethod(), is(MergeMethod.merge)); } + @Test + public void testSerializationAutoMergeDisabled() throws IOException { + String fixture = + Resources.toString(getResource(this.getClass(), "pull_request_automerge_disabled.json"), defaultCharset()); + final PullRequest pr = Json.create().fromJson(fixture, PullRequest.class); + + assertThat(pr.id(), is(2439836648L)); + assertThat(pr.head().sha(), is("881ef333d1ffc01869b666f13d3b37d7af92b9a2")); + assertThat(pr.merged(), is(false)); + assertThat(pr.mergeable().get(), is(true)); + assertThat(pr.draft(), is(Optional.of(true))); + assertNull(pr.autoMerge()); + } + + @Test + public void testSerializationAutoMergeEnabled() throws IOException { + String fixture = + Resources.toString(getResource(this.getClass(), "pull_request_automerge_enabled.json"), defaultCharset()); + final PullRequest pr = Json.create().fromJson(fixture, PullRequest.class); + + assertThat(pr.id(), is(2439836648L)); + assertThat(pr.head().sha(), is("881ef333d1ffc01869b666f13d3b37d7af92b9a2")); + assertThat(pr.merged(), is(false)); + assertThat(pr.mergeable().get(), is(true)); + assertThat(pr.draft(), is(Optional.of(false))); + assertNotNull(pr.autoMerge()); + assertThat(pr.autoMerge().enabledBy().login(), is("octocat")); + assertThat(pr.autoMerge().mergeMethod(), is("squash")); + } + @Test public void testDeserializationMergeParamsOmitsFields() throws IOException { final MergeParameters params = ImmutableMergeParameters.builder() diff --git a/src/test/java/com/spotify/github/v3/prs/RequestReviewParametersTest.java b/src/test/java/com/spotify/github/v3/prs/RequestReviewParametersTest.java index a225fccf..863eccbb 100644 --- a/src/test/java/com/spotify/github/v3/prs/RequestReviewParametersTest.java +++ b/src/test/java/com/spotify/github/v3/prs/RequestReviewParametersTest.java @@ -25,7 +25,7 @@ import com.google.common.collect.ImmutableList; import com.spotify.github.jackson.Json; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class RequestReviewParametersTest { diff --git a/src/test/java/com/spotify/github/v3/prs/ReviewParametersTest.java b/src/test/java/com/spotify/github/v3/prs/ReviewParametersTest.java index 33d6e5f3..0d5919e8 100644 --- a/src/test/java/com/spotify/github/v3/prs/ReviewParametersTest.java +++ b/src/test/java/com/spotify/github/v3/prs/ReviewParametersTest.java @@ -20,17 +20,16 @@ package com.spotify.github.v3.prs; -import com.google.common.io.Resources; -import com.spotify.github.jackson.Json; -import org.junit.Test; - -import java.io.IOException; - import static com.google.common.io.Resources.getResource; import static java.nio.charset.Charset.defaultCharset; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; +import com.google.common.io.Resources; +import com.spotify.github.jackson.Json; +import java.io.IOException; +import org.junit.jupiter.api.Test; + public class ReviewParametersTest { @Test public void testDeserialization() throws IOException { diff --git a/src/test/java/com/spotify/github/v3/prs/ReviewRequestsTest.java b/src/test/java/com/spotify/github/v3/prs/ReviewRequestsTest.java index 6ae82907..99a2d0ee 100644 --- a/src/test/java/com/spotify/github/v3/prs/ReviewRequestsTest.java +++ b/src/test/java/com/spotify/github/v3/prs/ReviewRequestsTest.java @@ -28,8 +28,8 @@ import com.google.common.io.Resources; import com.spotify.github.jackson.Json; import java.io.IOException; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class ReviewRequestsTest { @@ -42,7 +42,7 @@ public static final void assertRequiredReviews(final ReviewRequests reviewReques assertThat(reviewRequests.teams().get(0).slug(), is("justice-league")); } - @Before + @BeforeEach public void setUp() throws Exception { fixture = Resources.toString(getResource(this.getClass(), "required_reviews.json"), defaultCharset()); diff --git a/src/test/java/com/spotify/github/v3/prs/ReviewTest.java b/src/test/java/com/spotify/github/v3/prs/ReviewTest.java index 387b16bc..cefe2a3a 100644 --- a/src/test/java/com/spotify/github/v3/prs/ReviewTest.java +++ b/src/test/java/com/spotify/github/v3/prs/ReviewTest.java @@ -28,7 +28,7 @@ import com.google.common.io.Resources; import com.spotify.github.jackson.Json; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class ReviewTest { @Test @@ -41,6 +41,20 @@ public void testDeserialization() throws IOException { Json.create().fromJson(fixture, Review.class); assertThat(review.state(), is(ReviewState.APPROVED)); assertThat(review.commitId(), is("ecdd80bb57125d7ba9641ffaa4d7d2c19d3f3091")); - assertThat(review.id(), is(80)); + assertThat(review.id(), is(80L)); } + + @Test + public void testDeserializationWithLargeId() throws IOException { + String fixture = + Resources.toString( + getResource(this.getClass(), "review_long_id.json"), + defaultCharset()); + final Review review = + Json.create().fromJson(fixture, Review.class); + assertThat(review.state(), is(ReviewState.APPROVED)); + assertThat(review.commitId(), is("ecdd80bb57125d7ba9641ffaa4d7d2c19d3f3091")); + assertThat(review.id(), is(2459198580L)); + } + } \ No newline at end of file diff --git a/src/test/java/com/spotify/github/v3/prs/requests/PullRequestCreateTest.java b/src/test/java/com/spotify/github/v3/prs/requests/PullRequestCreateTest.java index 4c2ff152..b8ed39e3 100644 --- a/src/test/java/com/spotify/github/v3/prs/requests/PullRequestCreateTest.java +++ b/src/test/java/com/spotify/github/v3/prs/requests/PullRequestCreateTest.java @@ -20,17 +20,14 @@ package com.spotify.github.v3.prs.requests; -import com.spotify.github.jackson.Json; -import com.spotify.github.v3.prs.ImmutablePullRequest; -import org.junit.Assert; -import org.junit.Test; - -import java.util.function.Function; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import com.spotify.github.jackson.Json; +import java.util.function.Function; +import org.junit.jupiter.api.Test; + public class PullRequestCreateTest { @Test diff --git a/src/test/java/com/spotify/github/v3/prs/requests/PullRequestParametersTest.java b/src/test/java/com/spotify/github/v3/prs/requests/PullRequestParametersTest.java index c99faec3..60ea9262 100644 --- a/src/test/java/com/spotify/github/v3/prs/requests/PullRequestParametersTest.java +++ b/src/test/java/com/spotify/github/v3/prs/requests/PullRequestParametersTest.java @@ -23,7 +23,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class PullRequestParametersTest { diff --git a/src/test/java/com/spotify/github/v3/repos/LanguagesTest.java b/src/test/java/com/spotify/github/v3/repos/LanguagesTest.java index 5a026bef..633ddfcd 100644 --- a/src/test/java/com/spotify/github/v3/repos/LanguagesTest.java +++ b/src/test/java/com/spotify/github/v3/repos/LanguagesTest.java @@ -28,7 +28,7 @@ import com.google.common.io.Resources; import com.spotify.github.jackson.Json; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class LanguagesTest { diff --git a/src/test/java/com/spotify/github/v3/repos/PushCommitTest.java b/src/test/java/com/spotify/github/v3/repos/PushCommitTest.java index 6ddf78fe..2d580388 100644 --- a/src/test/java/com/spotify/github/v3/repos/PushCommitTest.java +++ b/src/test/java/com/spotify/github/v3/repos/PushCommitTest.java @@ -28,14 +28,14 @@ import com.google.common.io.Resources; import com.spotify.github.jackson.Json; import java.io.IOException; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class PushCommitTest { private String fixture; - @Before + @BeforeEach public void setUp() throws Exception { fixture = Resources.toString(getResource(this.getClass(), "push_commit.json"), defaultCharset()); diff --git a/src/test/java/com/spotify/github/v3/repos/RepositoryTest.java b/src/test/java/com/spotify/github/v3/repos/RepositoryTest.java index 38aa0ad8..82f28aa7 100644 --- a/src/test/java/com/spotify/github/v3/repos/RepositoryTest.java +++ b/src/test/java/com/spotify/github/v3/repos/RepositoryTest.java @@ -29,14 +29,14 @@ import com.google.common.io.Resources; import com.spotify.github.jackson.Json; import java.io.IOException; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class RepositoryTest { private String fixture; - @Before + @BeforeEach public void setUp() throws Exception { fixture = Resources.toString(getResource(this.getClass(), "repository.json"), defaultCharset()); } @@ -49,5 +49,9 @@ public void testDeserialization() throws IOException { assertThat(repository.name(), is("Hello-World")); assertThat(repository.fullName(), is(repository.owner().login() + "/Hello-World")); assertThat(repository.isPrivate(), is(false)); + assertThat(repository.isArchived(), is(false)); + assertThat(repository.allowMergeCommit(), is(false)); + assertThat(repository.allowRebaseMerge(), is(true)); + assertThat(repository.allowSquashMerge(), is(true)); } } diff --git a/src/test/java/com/spotify/github/v3/repos/StatusTest.java b/src/test/java/com/spotify/github/v3/repos/StatusTest.java index 291721be..0cf5d1ec 100644 --- a/src/test/java/com/spotify/github/v3/repos/StatusTest.java +++ b/src/test/java/com/spotify/github/v3/repos/StatusTest.java @@ -30,7 +30,7 @@ import java.io.IOException; import java.net.URI; import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class StatusTest { diff --git a/src/test/java/com/spotify/github/v3/repos/requests/RepositoryCreateStatusTest.java b/src/test/java/com/spotify/github/v3/repos/requests/RepositoryCreateStatusTest.java index 66325633..7abf1bf9 100644 --- a/src/test/java/com/spotify/github/v3/repos/requests/RepositoryCreateStatusTest.java +++ b/src/test/java/com/spotify/github/v3/repos/requests/RepositoryCreateStatusTest.java @@ -28,7 +28,7 @@ import com.spotify.github.jackson.Json; import com.spotify.github.v3.repos.StatusState; import java.net.URI; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class RepositoryCreateStatusTest { diff --git a/src/test/java/com/spotify/github/v3/search/SearchTest.java b/src/test/java/com/spotify/github/v3/search/SearchTest.java index 3fa80009..b4678875 100644 --- a/src/test/java/com/spotify/github/v3/search/SearchTest.java +++ b/src/test/java/com/spotify/github/v3/search/SearchTest.java @@ -30,7 +30,7 @@ import com.spotify.github.v3.issues.Issue; import java.io.IOException; import java.net.URI; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class SearchTest { @@ -41,8 +41,8 @@ public static final void assertSearchIssues(final SearchIssues search) { assertThat( issues.url(), is(URI.create("https://api.github.com/repos/batterseapower/pinyin-toolkit/issues/132"))); - assertThat(issues.number(), is(132)); - assertThat(issues.id(), is(35802)); + assertThat(issues.number(), is(132L)); + assertThat(issues.id(), is(35802L)); assertThat(issues.title(), is("Line Number Indexes Beyond 20 Not Displayed")); } @@ -54,4 +54,17 @@ public void testDeserialization() throws IOException { final SearchIssues search = Json.create().fromJson(fixture, SearchIssues.class); assertSearchIssues(search); } + + @Test + public void testDeserializationWithLargeIssueId() throws IOException { + final String fixture = + Resources.toString(getResource(this.getClass(), "issues-long-id.json"), defaultCharset()); + + final SearchIssues search = Json.create().fromJson(fixture, SearchIssues.class); + assertThat(search.items().size(), is(1)); + + final Issue issue = search.items().get(0); + assertThat(issue.id(), is(2592843837L)); + assertThat(issue.number(), is(5514L)); + } } diff --git a/src/test/java/com/spotify/github/v3/search/requests/SearchParametersTest.java b/src/test/java/com/spotify/github/v3/search/requests/SearchParametersTest.java index 5d9cf409..5120c088 100644 --- a/src/test/java/com/spotify/github/v3/search/requests/SearchParametersTest.java +++ b/src/test/java/com/spotify/github/v3/search/requests/SearchParametersTest.java @@ -7,9 +7,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,7 +23,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class SearchParametersTest { @@ -34,8 +34,22 @@ public void testFullSerialize() { .q("bogus-query") .sort("bogus-sort") .order("bogus-order") + .per_page(50) + .page(2) .build(); + assertThat(params.serialize(), is("order=bogus-order&page=2&per_page=50&q=bogus-query&sort=bogus-sort")); + } + + @Test + public void testSerializeWithoutPageAndPerPageParameters() { + final SearchParameters params = + ImmutableSearchParameters.builder() + .q("bogus-query") + .sort("bogus-sort") + .order("bogus-order") + .build(); + assertThat(params.serialize(), is("order=bogus-order&q=bogus-query&sort=bogus-sort")); } } diff --git a/src/test/resources/com/spotify/github/v3/activity/events/fixtures/check_run_event.json b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/check_run_event.json new file mode 100644 index 00000000..cff1fd34 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/check_run_event.json @@ -0,0 +1,318 @@ +{ + "action": "created", + "check_run": { + "id": 128620228, + "node_id": "MDg6Q2hlY2tSdW4xMjg2MjAyMjg=", + "head_sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "external_id": "", + "url": "https://api.github.com/repos/Codertocat/Hello-World/check-runs/128620228", + "html_url": "https://github.com/Codertocat/Hello-World/runs/128620228", + "details_url": "https://octocoders.github.io", + "status": "queued", + "conclusion": null, + "started_at": "2019-05-15T15:21:12Z", + "completed_at": null, + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/Codertocat/Hello-World/check-runs/128620228/annotations" + }, + "name": "Octocoders-linter", + "check_suite": { + "id": 118578147, + "node_id": "MDEwOkNoZWNrU3VpdGUxMTg1NzgxNDc=", + "head_branch": "changes", + "head_sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "status": "queued", + "conclusion": null, + "url": "https://api.github.com/repos/Codertocat/Hello-World/check-suites/118578147", + "before": "6113728f27ae82c7b1a177c8d03f9e96e0adf246", + "after": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "pull_requests": [ + { + "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2", + "id": 279147437, + "number": 2, + "head": { + "ref": "changes", + "sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + }, + "base": { + "ref": "master", + "sha": "f95f852bd8fca8fcc58a9a2d6c842781e32a215e", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + } + } + ], + "app": { + "id": 29310, + "node_id": "MDM6QXBwMjkzMTA=", + "owner": { + "login": "Octocoders", + "id": 38302899, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjM4MzAyODk5", + "avatar_url": "https://avatars1.githubusercontent.com/u/38302899?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Octocoders", + "html_url": "https://github.com/Octocoders", + "followers_url": "https://api.github.com/users/Octocoders/followers", + "following_url": "https://api.github.com/users/Octocoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Octocoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Octocoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Octocoders/subscriptions", + "organizations_url": "https://api.github.com/users/Octocoders/orgs", + "repos_url": "https://api.github.com/users/Octocoders/repos", + "events_url": "https://api.github.com/users/Octocoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Octocoders/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "octocoders-linter", + "description": "", + "external_url": "https://octocoders.github.io", + "html_url": "https://github.com/apps/octocoders-linter", + "created_at": "2019-04-19T19:36:24Z", + "updated_at": "2019-04-19T19:36:56Z", + "permissions": { + "administration": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "issues": "write", + "members": "write", + "metadata": "read", + "organization_administration": "write", + "organization_hooks": "write", + "organization_plan": "read", + "organization_projects": "write", + "organization_user_blocking": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "statuses": "write", + "team_discussions": "write", + "vulnerability_alerts": "read" + }, + "events": [] + }, + "created_at": "2019-05-15T15:20:31Z", + "updated_at": "2019-05-15T15:20:31Z" + }, + "app": { + "id": 29310, + "node_id": "MDM6QXBwMjkzMTA=", + "owner": { + "login": "Octocoders", + "id": 38302899, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjM4MzAyODk5", + "avatar_url": "https://avatars1.githubusercontent.com/u/38302899?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Octocoders", + "html_url": "https://github.com/Octocoders", + "followers_url": "https://api.github.com/users/Octocoders/followers", + "following_url": "https://api.github.com/users/Octocoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Octocoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Octocoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Octocoders/subscriptions", + "organizations_url": "https://api.github.com/users/Octocoders/orgs", + "repos_url": "https://api.github.com/users/Octocoders/repos", + "events_url": "https://api.github.com/users/Octocoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Octocoders/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "octocoders-linter", + "description": "", + "external_url": "https://octocoders.github.io", + "html_url": "https://github.com/apps/octocoders-linter", + "created_at": "2019-04-19T19:36:24Z", + "updated_at": "2019-04-19T19:36:56Z", + "permissions": { + "administration": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "issues": "write", + "members": "write", + "metadata": "read", + "organization_administration": "write", + "organization_hooks": "write", + "organization_plan": "read", + "organization_projects": "write", + "organization_user_blocking": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "statuses": "write", + "team_discussions": "write", + "vulnerability_alerts": "read" + }, + "events": [] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2", + "id": 279147437, + "number": 2, + "head": { + "ref": "changes", + "sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + }, + "base": { + "ref": "master", + "sha": "f95f852bd8fca8fcc58a9a2d6c842781e32a215e", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + } + } + ], + "deployment": { + "url": "https://api.github.com/repos/Codertocat/Hello-World/deployments/326191728", + "id": 326191728, + "node_id": "MDEwOkRlcGxveW1lbnQzMjYxOTE3Mjg=", + "task": "deploy", + "original_environment": "lab", + "environment": "lab", + "description": null, + "created_at": "2021-02-18T08:22:48Z", + "updated_at": "2021-02-18T09:47:16Z", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments/326191728/statuses", + "repository_url": "https://api.github.com/repos/Codertocat/Hello-World" + } + }, + "repository": { + "id": 186853002, + "node_id": "MDEwOlJlcG9zaXRvcnkxODY4NTMwMDI=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "private": false, + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2019-05-15T15:19:25Z", + "updated_at": "2019-05-15T15:21:03Z", + "pushed_at": "2019-05-15T15:20:57Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Ruby", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 2, + "license": null, + "forks": 1, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/activity/events/fixtures/issue_comment_event.json b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/issue_comment_event.json index 6f6e7d89..650d4ce4 100644 --- a/src/test/resources/com/spotify/github/v3/activity/events/fixtures/issue_comment_event.json +++ b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/issue_comment_event.json @@ -32,7 +32,9 @@ { "url": "https://api.github.com/repos/baxterthehacker/public-repo/labels/bug", "name": "bug", - "color": "fc2929" + "color": "fc2929", + "default": false, + "id": 208045946 } ], "state": "open", @@ -50,6 +52,7 @@ "html_url": "https://github.com/baxterthehacker/public-repo/issues/2#issuecomment-99262140", "issue_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/2", "id": 99262140, + "node_id": "asd123", "user": { "login": "baxterthehacker", "id": 6752317, diff --git a/src/test/resources/com/spotify/github/v3/activity/events/fixtures/merge_group_event.json b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/merge_group_event.json new file mode 100644 index 00000000..531f706d --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/merge_group_event.json @@ -0,0 +1,116 @@ +{ + "action": "checks_requested", + "merge_group": { + "head_sha": "cd84187b3e9a3e8f5b5f5b5f5b5f5b5f5b5f5b5f", + "head_ref": "refs/heads/gh-readonly-queue/main/pr-123-cd84187b3e9a3e8f5b5f5b5f5b5f5b5f5b5f5b5f", + "base_sha": "9049f1265b7d61be4a8904a9a27120d2064dab3b", + "base_ref": "refs/heads/main" + }, + "repository": { + "id": 35129377, + "name": "public-repo", + "full_name": "baxterthehacker/public-repo", + "owner": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/baxterthehacker/public-repo", + "description": "", + "fork": false, + "url": "https://api.github.com/repos/baxterthehacker/public-repo", + "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks", + "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams", + "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks", + "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}", + "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events", + "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}", + "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}", + "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags", + "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages", + "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers", + "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors", + "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers", + "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription", + "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}", + "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges", + "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads", + "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}", + "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}", + "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}", + "deployments_url": "https://api.github.com/repos/baxterthehacker/public-repo/deployments", + "created_at": "2015-05-05T23:40:12Z", + "updated_at": "2016-08-15T17:19:01Z", + "pushed_at": "2016-10-03T23:37:43Z", + "git_url": "git://github.com/baxterthehacker/public-repo.git", + "ssh_url": "git@github.com:baxterthehacker/public-repo.git", + "clone_url": "https://github.com/baxterthehacker/public-repo.git", + "svn_url": "https://github.com/baxterthehacker/public-repo", + "homepage": null, + "size": 233, + "stargazers_count": 2, + "watchers_count": 2, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 2, + "mirror_url": null, + "open_issues_count": 5, + "forks": 2, + "open_issues": 5, + "watchers": 2, + "default_branch": "master" + }, + "sender": { + "login": "baxterthehacker", + "id": 6752317, + "avatar_url": "https://avatars.githubusercontent.com/u/6752317?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/baxterthehacker", + "html_url": "https://github.com/baxterthehacker", + "followers_url": "https://api.github.com/users/baxterthehacker/followers", + "following_url": "https://api.github.com/users/baxterthehacker/following{/other_user}", + "gists_url": "https://api.github.com/users/baxterthehacker/gists{/gist_id}", + "starred_url": "https://api.github.com/users/baxterthehacker/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/baxterthehacker/subscriptions", + "organizations_url": "https://api.github.com/users/baxterthehacker/orgs", + "repos_url": "https://api.github.com/users/baxterthehacker/repos", + "events_url": "https://api.github.com/users/baxterthehacker/events{/privacy}", + "received_events_url": "https://api.github.com/users/baxterthehacker/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/activity/events/fixtures/pull_request_event.json b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/pull_request_event.json index 5028672f..a58a1f19 100644 --- a/src/test/resources/com/spotify/github/v3/activity/events/fixtures/pull_request_event.json +++ b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/pull_request_event.json @@ -4,6 +4,7 @@ "pull_request": { "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1", "id": 34778301, + "node_id": "MDExOlB1bGxSZXF1ZXN0NDI3NDI0Nw==", "html_url": "https://github.com/baxterthehacker/public-repo/pull/1", "diff_url": "https://github.com/baxterthehacker/public-repo/pull/1.diff", "patch_url": "https://github.com/baxterthehacker/public-repo/pull/1.patch", diff --git a/src/test/resources/com/spotify/github/v3/activity/events/fixtures/pull_request_review_comment_event.json b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/pull_request_review_comment_event.json index a89b6b54..7db9196d 100644 --- a/src/test/resources/com/spotify/github/v3/activity/events/fixtures/pull_request_review_comment_event.json +++ b/src/test/resources/com/spotify/github/v3/activity/events/fixtures/pull_request_review_comment_event.json @@ -3,12 +3,23 @@ "comment": { "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments/29724692", "id": 29724692, + "node_id": "abc234", "diff_hunk": "@@ -1 +1 @@\n-# public-repo", "path": "README.md", "position": 1, - "original_position": 1, "commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", "original_commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", + "original_line": 1, + "original_position": 1, + "original_start_line": 1, + "line": 1, + "side": "RIGHT", + "start_line": 1, + "start_side": "RIGHT", + "author_association": "NONE", + "pull_request_review_id": 42, + "in_reply_to_id": 426899381, + "subject_type": "line", "user": { "login": "baxterthehacker", "id": 6752317, @@ -48,6 +59,7 @@ "pull_request": { "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1", "id": 34778301, + "node_id": "abc123", "html_url": "https://github.com/baxterthehacker/public-repo/pull/1", "diff_url": "https://github.com/baxterthehacker/public-repo/pull/1.diff", "patch_url": "https://github.com/baxterthehacker/public-repo/pull/1.patch", diff --git a/src/test/resources/com/spotify/github/v3/checks/check-suites-response-long-id.json b/src/test/resources/com/spotify/github/v3/checks/check-suites-response-long-id.json new file mode 100644 index 00000000..ca43441d --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/checks/check-suites-response-long-id.json @@ -0,0 +1,183 @@ +{ + "total_count": 1, + "check_suites": [ + { + "id": 14707641936, + "node_id": "MDEwOkNoZWNrU3VpdGU1", + "head_branch": "master", + "head_sha": "d6fde92930d4715a2b49857d24b940956b26d2d3", + "status": "completed", + "conclusion": "neutral", + "url": "https://api.github.com/repos/github/hello-world/check-suites/5", + "before": "146e867f55c26428e5f9fade55a9bbf5e95a7912", + "after": "d6fde92930d4715a2b49857d24b940956b26d2d3", + "pull_requests": [], + "app": { + "id": 1, + "slug": "octoapp", + "node_id": "MDExOkludGVncmF0aW9uMQ==", + "owner": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": true + }, + "name": "Octocat App", + "description": "", + "external_url": "https://example.com", + "html_url": "https://github.com/apps/octoapp", + "created_at": "2017-07-08T16:18:44-04:00", + "updated_at": "2017-07-08T16:18:44-04:00", + "permissions": { + "metadata": "read", + "contents": "read", + "issues": "write", + "single_file": "write" + }, + "events": [ + "push", + "pull_request" + ] + }, + "repository": { + "id": 1296269, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/octocat/Hello-World", + "description": "This your first repo!", + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_url": "git:github.com/octocat/Hello-World.git", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "clone_url": "https://github.com/octocat/Hello-World.git", + "mirror_url": "git:git.example.com/octocat/Hello-World", + "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", + "svn_url": "https://svn.github.com/octocat/Hello-World", + "homepage": "https://github.com", + "language": null, + "forks_count": 9, + "stargazers_count": 80, + "watchers_count": 80, + "size": 108, + "default_branch": "master", + "open_issues_count": 0, + "is_template": true, + "topics": [ + "octocat", + "atom", + "electron", + "api" + ], + "has_issues": true, + "has_projects": true, + "has_wiki": true, + "has_pages": false, + "has_downloads": true, + "archived": false, + "disabled": false, + "visibility": "public", + "pushed_at": "2011-01-26T19:06:43Z", + "created_at": "2011-01-26T19:01:12Z", + "updated_at": "2011-01-26T19:14:43Z", + "permissions": { + "admin": false, + "push": false, + "pull": true + }, + "temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O", + "delete_branch_on_merge": true, + "subscribers_count": 42, + "network_count": 0 + }, + "created_at": "2011-01-26T19:01:12Z", + "updated_at": "2011-01-26T19:14:43Z", + "head_commit": { + "id": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d", + "tree_id": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d", + "message": "Merge pull request #6 from Spaceghost/patch-1\n\nNew line at end of file.", + "timestamp": "2016-10-10T00:00:00Z", + "author": { + "name": "The Octocat", + "email": "octocat@nowhere.com" + }, + "committer": { + "name": "The Octocat", + "email": "octocat@nowhere.com" + } + }, + "latest_check_runs_count": 1, + "check_runs_url": "https://api.github.com/repos/octocat/Hello-World/check-suites/5/check-runs" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/checks/check-suites-response.json b/src/test/resources/com/spotify/github/v3/checks/check-suites-response.json new file mode 100644 index 00000000..d166c6f4 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/checks/check-suites-response.json @@ -0,0 +1,183 @@ +{ + "total_count": 1, + "check_suites": [ + { + "id": 5, + "node_id": "MDEwOkNoZWNrU3VpdGU1", + "head_branch": "master", + "head_sha": "d6fde92930d4715a2b49857d24b940956b26d2d3", + "status": "completed", + "conclusion": "neutral", + "url": "https://api.github.com/repos/github/hello-world/check-suites/5", + "before": "146e867f55c26428e5f9fade55a9bbf5e95a7912", + "after": "d6fde92930d4715a2b49857d24b940956b26d2d3", + "pull_requests": [], + "app": { + "id": 1, + "slug": "octoapp", + "node_id": "MDExOkludGVncmF0aW9uMQ==", + "owner": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": true + }, + "name": "Octocat App", + "description": "", + "external_url": "https://example.com", + "html_url": "https://github.com/apps/octoapp", + "created_at": "2017-07-08T16:18:44-04:00", + "updated_at": "2017-07-08T16:18:44-04:00", + "permissions": { + "metadata": "read", + "contents": "read", + "issues": "write", + "single_file": "write" + }, + "events": [ + "push", + "pull_request" + ] + }, + "repository": { + "id": 1296269, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/octocat/Hello-World", + "description": "This your first repo!", + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_url": "git:github.com/octocat/Hello-World.git", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "clone_url": "https://github.com/octocat/Hello-World.git", + "mirror_url": "git:git.example.com/octocat/Hello-World", + "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", + "svn_url": "https://svn.github.com/octocat/Hello-World", + "homepage": "https://github.com", + "language": null, + "forks_count": 9, + "stargazers_count": 80, + "watchers_count": 80, + "size": 108, + "default_branch": "master", + "open_issues_count": 0, + "is_template": true, + "topics": [ + "octocat", + "atom", + "electron", + "api" + ], + "has_issues": true, + "has_projects": true, + "has_wiki": true, + "has_pages": false, + "has_downloads": true, + "archived": false, + "disabled": false, + "visibility": "public", + "pushed_at": "2011-01-26T19:06:43Z", + "created_at": "2011-01-26T19:01:12Z", + "updated_at": "2011-01-26T19:14:43Z", + "permissions": { + "admin": false, + "push": false, + "pull": true + }, + "temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O", + "delete_branch_on_merge": true, + "subscribers_count": 42, + "network_count": 0 + }, + "created_at": "2011-01-26T19:01:12Z", + "updated_at": "2011-01-26T19:14:43Z", + "head_commit": { + "id": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d", + "tree_id": "7fd1a60b01f91b314f59955a4e4d4e80d8edf11d", + "message": "Merge pull request #6 from Spaceghost/patch-1\n\nNew line at end of file.", + "timestamp": "2016-10-10T00:00:00Z", + "author": { + "name": "The Octocat", + "email": "octocat@nowhere.com" + }, + "committer": { + "name": "The Octocat", + "email": "octocat@nowhere.com" + } + }, + "latest_check_runs_count": 1, + "check_runs_url": "https://api.github.com/repos/octocat/Hello-World/check-suites/5/check-runs" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/checks/checks-run-completed-long-id-response.json b/src/test/resources/com/spotify/github/v3/checks/checks-run-completed-long-id-response.json new file mode 100644 index 00000000..94232e82 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/checks/checks-run-completed-long-id-response.json @@ -0,0 +1,73 @@ +{ + "id": 6971753714, + "head_sha": "ce587453ced02b1526dfb4cb910479d431683101", + "node_id": "MDg6Q2hlY2tSdW40", + "external_id": "", + "url": "https://api.github.com/repos/github/hello-world/check-runs/6971753714", + "html_url": "http://github.com/github/hello-world/runs/6971753714", + "details_url": "https://example.com", + "status": "completed", + "conclusion": "neutral", + "started_at": "2018-05-04T01:14:52Z", + "completed_at": "2018-05-04T01:14:52Z", + "output": { + "title": "Mighty Readme report", + "summary": "There are 0 failures, 2 warnings, and 1 notice.", + "text": "You may have some misspelled words on lines 2 and 4. You also may want to add a section in your README about how to install your app.", + "annotations_count": 2, + "annotations_url": "https://api.github.com/repos/github/hello-world/check-runs/6971753714/annotations" + }, + "name": "mighty_readme", + "check_suite": { + "id": 5 + }, + "app": { + "id": 1, + "node_id": "MDExOkludGVncmF0aW9uMQ==", + "owner": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "hooks_url": "https://api.github.com/orgs/github/hooks", + "issues_url": "https://api.github.com/orgs/github/issues", + "members_url": "https://api.github.com/orgs/github/members{/member}", + "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "description": "A great organization" + }, + "name": "Super CI", + "description": "", + "external_url": "https://example.com", + "html_url": "https://github.com/apps/super-ci", + "created_at": "2017-07-08T16:18:44-04:00", + "updated_at": "2017-07-08T16:18:44-04:00" + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/github/hello-world/pulls/1", + "id": 1934, + "number": 3956, + "head": { + "ref": "say-hello", + "sha": "3dca65fa3e8d4b3da3f3d056c59aee1c50f41390", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + }, + "base": { + "ref": "master", + "sha": "e7fdf7640066d71ad16a86fbcbb9c6a10a18af4f", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + } + } + ] +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/comment_created_long_id.json b/src/test/resources/com/spotify/github/v3/clients/comment_created_long_id.json new file mode 100644 index 00000000..1ddd888a --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/comment_created_long_id.json @@ -0,0 +1,45 @@ +{ + "url": "https://api.github.com/repos/spotify/github-java-client/issues/comments/1958720937", + "html_url": "https://github.com/spotify/github-java-client/pull/180#issuecomment-1958720937", + "issue_url": "https://api.github.com/repos/spotify/github-java-client/issues/180", + "id": 2459198527, + "node_id": "IC_kwDODynaQc50v7Wp", + "user": { + "login": "vootelerotov", + "id": 1439555, + "node_id": "MDQ6VXNlcjE0Mzk1NTU=", + "avatar_url": "https://avatars.githubusercontent.com/u/1439555?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/vootelerotov", + "html_url": "https://github.com/vootelerotov", + "followers_url": "https://api.github.com/users/vootelerotov/followers", + "following_url": "https://api.github.com/users/vootelerotov/following{/other_user}", + "gists_url": "https://api.github.com/users/vootelerotov/gists{/gist_id}", + "starred_url": "https://api.github.com/users/vootelerotov/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/vootelerotov/subscriptions", + "organizations_url": "https://api.github.com/users/vootelerotov/orgs", + "repos_url": "https://api.github.com/users/vootelerotov/repos", + "events_url": "https://api.github.com/users/vootelerotov/events{/privacy}", + "received_events_url": "https://api.github.com/users/vootelerotov/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "created_at": "2024-02-22T05:19:44Z", + "updated_at": "2024-02-22T05:19:44Z", + "author_association": "CONTRIBUTOR", + "body": "Ran into this in the wild.", + "reactions": { + "url": "https://api.github.com/repos/spotify/github-java-client/issues/comments/1958720937/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "performed_via_github_app": null +} diff --git a/src/test/resources/com/spotify/github/v3/clients/commit.json b/src/test/resources/com/spotify/github/v3/clients/commit.json new file mode 100644 index 00000000..ea6a5b9f --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/commit.json @@ -0,0 +1,92 @@ +{ + "url": "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "html_url": "https://github.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/comments", + "commit": { + "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "author": { + "name": "Monalisa Octocat", + "email": "support@github.com", + "date": "2011-04-14T16:00:49Z" + }, + "committer": { + "name": "Monalisa Octocat", + "email": "support@github.com", + "date": "2011-04-14T16:00:49Z" + }, + "message": "Fix all the bugs", + "tree": { + "url": "https://api.github.com/repos/octocat/Hello-World/tree/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + }, + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP MESSAGE-----\n...\n-----END PGP MESSAGE-----", + "payload": "tree 6dcb09b5b57875f334f61aebed695e2e4193db5e\n..." + } + }, + "author": { + "login": "octocat", + "id": 1, + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "octocat", + "id": 1, + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + { + "url": "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + } + ], + "stats": { + "additions": 104, + "deletions": 4, + "total": 108 + }, + "files": [ + { + "filename": "file1.txt", + "additions": 10, + "deletions": 2, + "changes": 12, + "status": "modified", + "raw_url": "https://github.com/octocat/Hello-World/raw/7ca483543807a51b6079e54ac4cc392bc29ae284/file1.txt", + "blob_url": "https://github.com/octocat/Hello-World/blob/7ca483543807a51b6079e54ac4cc392bc29ae284/file1.txt", + "patch": "@@ -29,7 +29,7 @@\n....." + } + ] +} diff --git a/src/test/resources/com/spotify/github/v3/clients/diff.txt b/src/test/resources/com/spotify/github/v3/clients/diff.txt new file mode 100644 index 00000000..f05207ce --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/diff.txt @@ -0,0 +1,9 @@ +diff --git a/filename b/filename +index 01a9f34..500bb03 100644 +--- a/nf ++++ b/nf +@@ -1,3 +1,4 @@ + asdf ++asdf +-- +2.39.0 \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/list_members.json b/src/test/resources/com/spotify/github/v3/clients/list_members.json new file mode 100644 index 00000000..f8563088 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/list_members.json @@ -0,0 +1,42 @@ +[ + { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "octocat2", + "id": 2, + "node_id": "MDQ2VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat2_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat2", + "html_url": "https://github.com/octocat2", + "followers_url": "https://api.github.com/users/octocat2/followers", + "following_url": "https://api.github.com/users/octoca2t/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat2/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat2/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat2/subscriptions", + "organizations_url": "https://api.github.com/users/octocat2/orgs", + "repos_url": "https://api.github.com/users/octocat2/repos", + "events_url": "https://api.github.com/users/octocat2/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat2/received_events", + "type": "User", + "site_admin": false + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/list_members_page1.json b/src/test/resources/com/spotify/github/v3/clients/list_members_page1.json new file mode 100644 index 00000000..2da63ac2 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/list_members_page1.json @@ -0,0 +1,22 @@ +[ + { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/list_members_page2.json b/src/test/resources/com/spotify/github/v3/clients/list_members_page2.json new file mode 100644 index 00000000..25fc4c6d --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/list_members_page2.json @@ -0,0 +1,22 @@ +[ + { + "login": "octocat2", + "id": 2, + "node_id": "MDQ2VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat2_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat2", + "html_url": "https://github.com/octocat2", + "followers_url": "https://api.github.com/users/octocat2/followers", + "following_url": "https://api.github.com/users/octoca2t/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat2/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat2/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat2/subscriptions", + "organizations_url": "https://api.github.com/users/octocat2/orgs", + "repos_url": "https://api.github.com/users/octocat2/repos", + "events_url": "https://api.github.com/users/octocat2/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat2/received_events", + "type": "User", + "site_admin": false + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/list_team_invitations.json b/src/test/resources/com/spotify/github/v3/clients/list_team_invitations.json new file mode 100644 index 00000000..768256c7 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/list_team_invitations.json @@ -0,0 +1,68 @@ +[ + { + "id": 1, + "login": "octocat", + "node_id": "MDQ6VXNlcjE=", + "email": "octocat@github.com", + "role": "direct_member", + "created_at": "2016-11-30T06:46:10-08:00", + "failed_at": "", + "failed_reason": "", + "inviter": { + "login": "other_user", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/other_user", + "html_url": "https://github.com/other_user", + "followers_url": "https://api.github.com/users/other_user/followers", + "following_url": "https://api.github.com/users/other_user/following{/other_user}", + "gists_url": "https://api.github.com/users/other_user/gists{/gist_id}", + "starred_url": "https://api.github.com/users/other_user/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", + "organizations_url": "https://api.github.com/users/other_user/orgs", + "repos_url": "https://api.github.com/users/other_user/repos", + "events_url": "https://api.github.com/users/other_user/events{/privacy}", + "received_events_url": "https://api.github.com/users/other_user/received_events", + "type": "User", + "site_admin": false + }, + "team_count": 2, + "invitation_teams_url": "https://api.github.com/organizations/2/invitations/1/teams", + "invitation_source": "member" + }, + { + "id": 2, + "login": "monalisa", + "node_id": "MDQ3VXNlcjE=", + "email": "octocat2@github.com", + "role": "direct_member", + "created_at": "2016-11-30T06:46:10-08:00", + "failed_at": "", + "failed_reason": "", + "inviter": { + "login": "other_user", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/other_user", + "html_url": "https://github.com/other_user", + "followers_url": "https://api.github.com/users/other_user/followers", + "following_url": "https://api.github.com/users/other_user/following{/other_user}", + "gists_url": "https://api.github.com/users/other_user/gists{/gist_id}", + "starred_url": "https://api.github.com/users/other_user/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", + "organizations_url": "https://api.github.com/users/other_user/orgs", + "repos_url": "https://api.github.com/users/other_user/repos", + "events_url": "https://api.github.com/users/other_user/events{/privacy}", + "received_events_url": "https://api.github.com/users/other_user/received_events", + "type": "User", + "site_admin": false + }, + "team_count": 2, + "invitation_teams_url": "https://api.github.com/organizations/2/invitations/1/teams", + "invitation_source": "member" + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/membership.json b/src/test/resources/com/spotify/github/v3/clients/membership.json new file mode 100644 index 00000000..fce6fa69 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/membership.json @@ -0,0 +1,5 @@ +{ + "url": "https://api.github.com/teams/1/memberships/octocat", + "role": "maintainer", + "state": "active" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/membership_update.json b/src/test/resources/com/spotify/github/v3/clients/membership_update.json new file mode 100644 index 00000000..0efabc38 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/membership_update.json @@ -0,0 +1,3 @@ +{ + "role": "member" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/membership_update_response.json b/src/test/resources/com/spotify/github/v3/clients/membership_update_response.json new file mode 100644 index 00000000..0f2fd957 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/membership_update_response.json @@ -0,0 +1,5 @@ +{ + "url": "https://api.github.com/teams/1/memberships/octocat", + "role": "member", + "state": "active" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/org_membership.json b/src/test/resources/com/spotify/github/v3/clients/org_membership.json new file mode 100644 index 00000000..5143f7bb --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/org_membership.json @@ -0,0 +1,40 @@ +{ + "url": "https://api.github.com/orgs/github/memberships/octocat", + "state": "active", + "role": "member", + "organization_url": "https://api.github.com/orgs/octocat", + "organization": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "hooks_url": "https://api.github.com/orgs/github/hooks", + "issues_url": "https://api.github.com/orgs/github/issues", + "members_url": "https://api.github.com/orgs/github/members{/member}", + "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "description": "A great organization" + }, + "user": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/patch.txt b/src/test/resources/com/spotify/github/v3/clients/patch.txt new file mode 100644 index 00000000..aea14f65 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/patch.txt @@ -0,0 +1,18 @@ +From 81c53612268423500bb086afbf7f6545a97ce181 Thu Jul 17 00:00:00 2000 +From: Spotify +Date: Thu, 17 Jul 2023 22:12:00 -0700 +Subject: [PATCH] Update filename + +--- + filename | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/filename b/filename +index 01a9f34..500bb03 100644 +--- a/nf ++++ b/nf +@@ -1,3 +1,4 @@ + asdf ++asdf +-- +2.39.0 \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/pull_request.json b/src/test/resources/com/spotify/github/v3/clients/pull_request.json new file mode 100644 index 00000000..e92f7d2d --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/pull_request.json @@ -0,0 +1,533 @@ +{ + "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347", + "id": 1, + "node_id": "MDExOlB1bGxSZXF1ZXN0MQ==", + "html_url": "https://github.com/octocat/Hello-World/pull/1347", + "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff", + "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch", + "issue_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/commits", + "review_comments_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments", + "review_comment_url": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e", + "number": 1347, + "state": "open", + "locked": true, + "title": "Amazing new feature", + "user": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "body": "Please pull these awesome changes in!", + "labels": [ + { + "id": 208045946, + "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=", + "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug", + "name": "bug", + "description": "Something isn't working", + "color": "f29513", + "default": true + } + ], + "milestone": { + "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1", + "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels", + "id": 1002604, + "node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==", + "number": 1, + "state": "open", + "title": "v1.0", + "description": "Tracking milestone for version 1.0", + "creator": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 4, + "closed_issues": 8, + "created_at": "2011-04-10T20:09:31Z", + "updated_at": "2014-03-03T18:58:10Z", + "closed_at": "2013-02-12T13:22:01Z", + "due_on": "2012-10-09T23:39:01Z" + }, + "active_lock_reason": "too heated", + "created_at": "2011-01-26T19:01:12Z", + "updated_at": "2011-01-26T19:01:12Z", + "closed_at": "2011-01-26T19:01:12Z", + "merged_at": "2011-01-26T19:01:12Z", + "merge_commit_sha": "e5bd3914e2e596debea16f433f57875b5b90bcd6", + "assignee": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "assignees": [ + { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + { + "login": "hubot", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/hubot_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/hubot", + "html_url": "https://github.com/hubot", + "followers_url": "https://api.github.com/users/hubot/followers", + "following_url": "https://api.github.com/users/hubot/following{/other_user}", + "gists_url": "https://api.github.com/users/hubot/gists{/gist_id}", + "starred_url": "https://api.github.com/users/hubot/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/hubot/subscriptions", + "organizations_url": "https://api.github.com/users/hubot/orgs", + "repos_url": "https://api.github.com/users/hubot/repos", + "events_url": "https://api.github.com/users/hubot/events{/privacy}", + "received_events_url": "https://api.github.com/users/hubot/received_events", + "type": "User", + "site_admin": true + } + ], + "requested_reviewers": [ + { + "login": "other_user", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/other_user_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/other_user", + "html_url": "https://github.com/other_user", + "followers_url": "https://api.github.com/users/other_user/followers", + "following_url": "https://api.github.com/users/other_user/following{/other_user}", + "gists_url": "https://api.github.com/users/other_user/gists{/gist_id}", + "starred_url": "https://api.github.com/users/other_user/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/other_user/subscriptions", + "organizations_url": "https://api.github.com/users/other_user/orgs", + "repos_url": "https://api.github.com/users/other_user/repos", + "events_url": "https://api.github.com/users/other_user/events{/privacy}", + "received_events_url": "https://api.github.com/users/other_user/received_events", + "type": "User", + "site_admin": false + } + ], + "requested_teams": [ + { + "id": 1, + "node_id": "MDQ6VGVhbTE=", + "url": "https://api.github.com/teams/1", + "html_url": "https://github.com/orgs/github/teams/justice-league", + "name": "Justice League", + "slug": "justice-league", + "description": "A great team.", + "privacy": "closed", + "permission": "admin", + "members_url": "https://api.github.com/teams/1/members{/member}", + "repositories_url": "https://api.github.com/teams/1/repos" + } + ], + "head": { + "label": "octocat:new-topic", + "ref": "new-topic", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "user": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 1296269, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/octocat/Hello-World", + "description": "This your first repo!", + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_url": "git:github.com/octocat/Hello-World.git", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "clone_url": "https://github.com/octocat/Hello-World.git", + "mirror_url": "git:git.example.com/octocat/Hello-World", + "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", + "svn_url": "https://svn.github.com/octocat/Hello-World", + "homepage": "https://github.com", + "language": null, + "forks_count": 9, + "stargazers_count": 80, + "watchers_count": 80, + "size": 108, + "default_branch": "master", + "open_issues_count": 0, + "topics": [ + "octocat", + "atom", + "electron", + "api" + ], + "has_issues": true, + "has_projects": true, + "has_wiki": true, + "has_pages": false, + "has_downloads": true, + "archived": false, + "disabled": false, + "pushed_at": "2011-01-26T19:06:43Z", + "created_at": "2011-01-26T19:01:12Z", + "updated_at": "2011-01-26T19:14:43Z", + "permissions": { + "admin": false, + "push": false, + "pull": true + }, + "allow_rebase_merge": true, + "temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_forking": true, + "forks": 123, + "open_issues": 123, + "license": { + "key": "mit", + "name": "MIT License", + "url": "https://api.github.com/licenses/mit", + "spdx_id": "MIT", + "node_id": "MDc6TGljZW5zZW1pdA==" + }, + "watchers": 123 + } + }, + "base": { + "label": "octocat:master", + "ref": "master", + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "user": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 1296269, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/octocat/Hello-World", + "description": "This your first repo!", + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_url": "git:github.com/octocat/Hello-World.git", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "clone_url": "https://github.com/octocat/Hello-World.git", + "mirror_url": "git:git.example.com/octocat/Hello-World", + "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", + "svn_url": "https://svn.github.com/octocat/Hello-World", + "homepage": "https://github.com", + "language": null, + "forks_count": 9, + "stargazers_count": 80, + "watchers_count": 80, + "size": 108, + "default_branch": "master", + "open_issues_count": 0, + "topics": [ + "octocat", + "atom", + "electron", + "api" + ], + "has_issues": true, + "has_projects": true, + "has_wiki": true, + "has_pages": false, + "has_downloads": true, + "archived": false, + "disabled": false, + "pushed_at": "2011-01-26T19:06:43Z", + "created_at": "2011-01-26T19:01:12Z", + "updated_at": "2011-01-26T19:14:43Z", + "permissions": { + "admin": false, + "push": false, + "pull": true + }, + "allow_rebase_merge": true, + "temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O", + "allow_squash_merge": true, + "allow_merge_commit": true, + "forks": 123, + "open_issues": 123, + "license": { + "key": "mit", + "name": "MIT License", + "url": "https://api.github.com/licenses/mit", + "spdx_id": "MIT", + "node_id": "MDc6TGljZW5zZW1pdA==" + }, + "watchers": 123 + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347" + }, + "html": { + "href": "https://github.com/octocat/Hello-World/pull/1347" + }, + "issue": { + "href": "https://api.github.com/repos/octocat/Hello-World/issues/1347" + }, + "comments": { + "href": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e" + } + }, + "author_association": "OWNER", + "auto_merge": null, + "draft": false, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "clean", + "merged_by": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "comments": 10, + "review_comments": 0, + "maintainer_can_modify": true, + "commits": 3, + "additions": 100, + "deletions": 3, + "changed_files": 5 +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/pull_request_commits_page1.json b/src/test/resources/com/spotify/github/v3/clients/pull_request_commits_page1.json new file mode 100644 index 00000000..6f043338 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/pull_request_commits_page1.json @@ -0,0 +1,2422 @@ +[ + { + "sha": "443831b0008519c93a57f96fb45a455f8e1453d0", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjQ0MzgzMWIwMDA4NTE5YzkzYTU3Zjk2ZmI0NWE0NTVmOGUxNDUzZDA=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-11T18:32:31Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:01Z" + }, + "message": "STASH incomplete feature (no UI, some TODOs)", + "tree": { + "sha": "362e3e7da340b93600dfc03f42a5448b01603aa8", + "url": "https://api.github.com/repos/facebook/react/git/trees/362e3e7da340b93600dfc03f42a5448b01603aa8" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/443831b0008519c93a57f96fb45a455f8e1453d0", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UkACgkQWCcTbp05\ndTIJaw//fA9sw4IgzFBupPjSniwta+TMNubK6rLakXmX61gWYoKPRkQx0N1cK5k2\n0ohggmhLb7c75kjShLCYu1RTYHR1hN1xpahPjurvXMNXt908oBrzEoDR7XQg3NEO\n2yJ1d0QsCcPimzHztGkHnO1oD2S8XZ+NBwEpJxTNc+xAotkxS0dlS1nYyNsCnpRY\nozG9oyYMfaHyZOgrONl7wqwaBHS9RVvppiAiaRKy0p9eozf1hAUkPXRTIUbXg/fV\nGrOjrNvffe3CrI7quZDqzCcvfuL7CTrcw3+Gw0pRHcx/GtM25/9ewTScaR5B9CWB\nmd6gu2KMuP6/DfJWtBJ3zOuTTDvRRh8cds1BL4tPQuLn6g295HDNLgBB/4esCL3q\naR60UOuWS/L3JQeYWKS+JAJlm+cycDHpSbzznXSJpVbrGC0YseI1qZpgEMDwcwUX\n7aFfMSppPF4PkTqWvEuUnQdwtLhhm+yKFKkAPude4ZjSInIY/ioLhpcupb62C45K\nRVL4Ihj4Jzoorz5SFMgvTg12HLMho/fJcwNPWn2RZCG1i8xgjIP8Jgod5HzOhE7n\ngJjL7Gb+fjnSjMpASGf9Cqw7TWYLMD+H59VAk4ZOzlqxZ95P41ECw0GpST5uFBY8\nH7/bUkIf/M26xJzVv82nRApQsFgXDxa+bOuXUgURshZmVt3jOcU=\n=pF+Q\n-----END PGP SIGNATURE-----", + "payload": "tree 362e3e7da340b93600dfc03f42a5448b01603aa8\nparent 3f9205c3331b80f13a0376826d1ef859786c086b\nauthor eps1lon 1607711551 +0100\ncommitter eps1lon 1607985481 +0100\n\nSTASH incomplete feature (no UI, some TODOs)\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/443831b0008519c93a57f96fb45a455f8e1453d0", + "html_url": "https://github.com/facebook/react/commit/443831b0008519c93a57f96fb45a455f8e1453d0", + "comments_url": "https://api.github.com/repos/facebook/react/commits/443831b0008519c93a57f96fb45a455f8e1453d0/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "3f9205c3331b80f13a0376826d1ef859786c086b", + "url": "https://api.github.com/repos/facebook/react/commits/3f9205c3331b80f13a0376826d1ef859786c086b", + "html_url": "https://github.com/facebook/react/commit/3f9205c3331b80f13a0376826d1ef859786c086b" + } + ] + }, + { + "sha": "1a34269cb1fede931153172fdeaca034ccadcda2", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjFhMzQyNjljYjFmZWRlOTMxMTUzMTcyZmRlYWNhMDM0Y2NhZGNkYTI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-11T20:04:55Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:01Z" + }, + "message": "Add UI", + "tree": { + "sha": "a7ba32a1d26bf21c20303716b53047afc2b19eb3", + "url": "https://api.github.com/repos/facebook/react/git/trees/a7ba32a1d26bf21c20303716b53047afc2b19eb3" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/1a34269cb1fede931153172fdeaca034ccadcda2", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UkACgkQWCcTbp05\ndTI7+Q/+Jjp/tDSFgbwa2wzhfnbnVDIurh2ICKUKnET1s44gA0Fkn1SJcm6atpI+\nISt4pVkcjoxGoF4jpYLK/PhY8IQrAzZhvIJ40gUC2NLutsPsb8ugREyMhYCIlCTP\nD0QBZMM7RG5p5u1T9E+c+K44sInOcnNxX7ioCeFK1nF2BsbNfkV9SUOZlelop9bh\n/qSyAIiOj9aqsx7d2Pu1LDdhjBXXunPxSNk11shAyO/OcxBAPqdZaHT+XGHj/FpB\n9Q3Pj4tjTr9ERRkchcWUsRPL8zxlimVwBcX6an1k7fCRywzkQWgxyyP9msQDTD0a\nu18hLoHpJfbyBgO5nuD1SCAKJfopgqpQt+/CyQjbqOjaNOBA0m9wUk8U5gVHxOoG\n/ZcMNDXra2DvVj5MYENuYtLUzLXJJ6AD8vu/K9FhZUfHy6Yk4Gfrq9Kidx/YSirS\nJoAO2jHYTb/Lyg3iNeEQgYkVaMhMsLAQWRx9B56PhGdMTbHDeQ3r85LqbORWNCku\npzlPEq4S41cYZYlV6XZr6/gBWNW+neGEIFBMw7N1UJ/6VJiDsG0QVUTvDFilrOCV\nXvRCdpPY40C0NiahVHyO06gojGPzbEhfjvnXoxmKe+Hs6RNJDqF/fRSqGl3py3r2\n5pRmGf5vgc6aldIPO8uw1J/vEHwL80hjiT7wcT8kYikiMkweUWI=\n=vEUV\n-----END PGP SIGNATURE-----", + "payload": "tree a7ba32a1d26bf21c20303716b53047afc2b19eb3\nparent 443831b0008519c93a57f96fb45a455f8e1453d0\nauthor eps1lon 1607717095 +0100\ncommitter eps1lon 1607985481 +0100\n\nAdd UI\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/1a34269cb1fede931153172fdeaca034ccadcda2", + "html_url": "https://github.com/facebook/react/commit/1a34269cb1fede931153172fdeaca034ccadcda2", + "comments_url": "https://api.github.com/repos/facebook/react/commits/1a34269cb1fede931153172fdeaca034ccadcda2/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "443831b0008519c93a57f96fb45a455f8e1453d0", + "url": "https://api.github.com/repos/facebook/react/commits/443831b0008519c93a57f96fb45a455f8e1453d0", + "html_url": "https://github.com/facebook/react/commit/443831b0008519c93a57f96fb45a455f8e1453d0" + } + ] + }, + { + "sha": "6cc7fdb8642f40c34d067f991dd01bf45149883e", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjZjYzdmZGI4NjQyZjQwYzM0ZDA2N2Y5OTFkZDAxYmY0NTE0OTg4M2U=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T17:04:07Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:01Z" + }, + "message": "Add view for errors/warnings in inspected element", + "tree": { + "sha": "c03f88d24809204587b25a676afc09f98ef23c38", + "url": "https://api.github.com/repos/facebook/react/git/trees/c03f88d24809204587b25a676afc09f98ef23c38" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/6cc7fdb8642f40c34d067f991dd01bf45149883e", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UkACgkQWCcTbp05\ndTKExw/9GErqES02exoefSLGYts3V7CCZzAtB4n0x+VbCufwWHynaKXt+ELo1UFg\nA15+TvAK0C4C/XCkzDdDKYQwGs/OpCSyJSNDjwzeOHVtonkPeR8Jp9eE2wioQQUE\nxCXNDVFRuJHQHEkcI4MbWVyOOJAQjSenesAojd1xEmIGgxGjPmYaULfCjJoKQung\ntBxzDEj6ngd0Sjb7U6SQpiSJQwG2JWSdBaI3HgBhV5Pq54pgnoCgv+nJb51I715e\nMOhbC3qWfs9OQyEO7cvsfcONrN6+20F3oq9gM7COaBFCee1g19hIDQGCCgzhkJzP\nz+UFarXTvaz+PHmTTrxyDVqh2M0tnu3wjQlyXK72f7XdCF8Yk92zSqEbKFqiiSsM\nTxQ5awFvae7K4Egv1gTiS3+p6CB+ReuN108sIwrHd1UT9yvEnLqu+1Xi6Z+sH855\nYVwVUvT43xP2bmePLOYZCbAwDQuf12QcXymkhsPcO6yQgIq/Jdr7FhgaE4G7PhL0\nLebujMo/WAe3MLlrWiUUtkI9pV2CimBt/igmp5tiVR/QeB768C6RfrNSUB/1g8Dg\n2SuxvMJjRPiqvUrqEYHO6P5gkfhBZxHPjmAb+HQGqfCz4vG5vw+XvpH1oif3jB4A\nEv/w/gtyrB5sEdxW3bp9e20mESW5DxQPeHsF+b6JgaqzLbhMUa4=\n=JBgd\n-----END PGP SIGNATURE-----", + "payload": "tree c03f88d24809204587b25a676afc09f98ef23c38\nparent 1a34269cb1fede931153172fdeaca034ccadcda2\nauthor eps1lon 1607965447 +0100\ncommitter eps1lon 1607985481 +0100\n\nAdd view for errors/warnings in inspected element\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/6cc7fdb8642f40c34d067f991dd01bf45149883e", + "html_url": "https://github.com/facebook/react/commit/6cc7fdb8642f40c34d067f991dd01bf45149883e", + "comments_url": "https://api.github.com/repos/facebook/react/commits/6cc7fdb8642f40c34d067f991dd01bf45149883e/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "1a34269cb1fede931153172fdeaca034ccadcda2", + "url": "https://api.github.com/repos/facebook/react/commits/1a34269cb1fede931153172fdeaca034ccadcda2", + "html_url": "https://github.com/facebook/react/commit/1a34269cb1fede931153172fdeaca034ccadcda2" + } + ] + }, + { + "sha": "59ddad1a8affb078724fdb58b296bf9258f3a183", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjU5ZGRhZDFhOGFmZmIwNzg3MjRmZGI1OGIyOTZiZjkyNThmM2ExODM=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T17:51:01Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:02Z" + }, + "message": "Serialize args", + "tree": { + "sha": "22fa26394637f3952c0dae32deaf70fccfab7f83", + "url": "https://api.github.com/repos/facebook/react/git/trees/22fa26394637f3952c0dae32deaf70fccfab7f83" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/59ddad1a8affb078724fdb58b296bf9258f3a183", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UoACgkQWCcTbp05\ndTKKgg//cXGgGOH+MG7vM1+c0dYHViLuQU3aByyFv4L+sVpODFPRZFEo8VifqN9f\nOttCyW6Kj2zQ6pE/sp30wAEWSeT8ZkaN2FYoYyclP2BnIUsyzXnoznnjHv78yTvr\n5lVQTy0Xv7Eev55WEAKxqhneK0OzWv7dGCwNZV+wNX+oTQ3t/Ozmy0+rx50JBVeR\npYNKummF6fMTME8Ss/QfERIfpVkJ/4j9ADZwR7CKzHWgkxvMdbD++cM4d6YI9rM+\nizGCo8vldYO7Faeb6C83D/v1AWCIlRMTEheSU/yRCh/XQx0Sjl0oCeAcJ8OK2b9T\nwEgCipYRTy8XTMccI0S4hmeuxDSPDGasVEbCk2UROEbDpST8iO7ov65oE1fMJLaU\nR/7yu3DbPkaD91kek/yoEJvI/+Ym0MVkWNvvxcY8MT2dT+iE0EIrHpeifx5sFWDC\nB9uj65rKuJ+Uws70pZFT1YP0CDH0uAO98PgsUw+WPOybwVG9vQjP/qoAKLi1oySR\nLTAcUVqwruU1GhS5MTHwbOwJ2G8nBICzdHT3zot+7KhjQ1YC8o0sn6OxXWmWF80K\nbOzAQE7tRepCp0wPP98txGCO7XmNUAmCSFYAiOACzr6QExHFJYqbK2STDOOfzEEU\n7jSNx/r+iuumplx8MEBlzgu6NlfmIYloreG0ENhHSK8/308PKcM=\n=oTZ5\n-----END PGP SIGNATURE-----", + "payload": "tree 22fa26394637f3952c0dae32deaf70fccfab7f83\nparent 6cc7fdb8642f40c34d067f991dd01bf45149883e\nauthor eps1lon 1607968261 +0100\ncommitter eps1lon 1607985482 +0100\n\nSerialize args\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/59ddad1a8affb078724fdb58b296bf9258f3a183", + "html_url": "https://github.com/facebook/react/commit/59ddad1a8affb078724fdb58b296bf9258f3a183", + "comments_url": "https://api.github.com/repos/facebook/react/commits/59ddad1a8affb078724fdb58b296bf9258f3a183/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "6cc7fdb8642f40c34d067f991dd01bf45149883e", + "url": "https://api.github.com/repos/facebook/react/commits/6cc7fdb8642f40c34d067f991dd01bf45149883e", + "html_url": "https://github.com/facebook/react/commit/6cc7fdb8642f40c34d067f991dd01bf45149883e" + } + ] + }, + { + "sha": "9372221a7a6f83decce2df7f08ade4b45957386b", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjkzNzIyMjFhN2E2ZjgzZGVjY2UyZGY3ZjA4YWRlNGI0NTk1NzM4NmI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T17:53:18Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:02Z" + }, + "message": "Ignore component stacks in inline errors view", + "tree": { + "sha": "ce254bc5b1de16aa7ded980081abc2c599bd10e6", + "url": "https://api.github.com/repos/facebook/react/git/trees/ce254bc5b1de16aa7ded980081abc2c599bd10e6" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/9372221a7a6f83decce2df7f08ade4b45957386b", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UoACgkQWCcTbp05\ndTJUbQ//UN7uc5SVyFIqXLuLdaIH1aGcXul+G/EMYDaQ4fET+MYWrc71yHvfuRRU\nIqwEVOXJkTkjxh0VmmS/+sihkEQkoNHn/466g5ChJ3BRQ7lh4l0JDZD5TtJOE/nX\n65/7S56a3g/okBMtDHi2Io926WGvvARrbiT3YIkq7PXRpp67Ut9wtZp5W2xqmiDC\njg+zI4M9TDx0yxr/iiA4sH6+xa8mWze8Fr8H15/OHJbZ7lDV+d134PXXWjGf7Tbh\nxxiKMTDzuZMC00wDyElUl2rRluqGTIeJJz2ydPdg5YsO/8spxgy7/j1+ZGi7klZC\ncqU6q71HsGeK1ZFlnFGyB1ubSPgC20WLwWToHWxOvwI6BXXAq4aJWTjRVVHVya7B\n2YmC+LK7SErEuSvCBC+0v6OhCNeSY0d4hr0xXmKtzf6CUAIL1I5ffWC+ySbmv8/r\nSW/e5CRcN9lS0Av56lYXqQTKg3Dg6iGQ7BVPA9Ql2WjiH1H12YkhfjYh0Hmqgq1Y\n4qNoJSpfKKTbdyjgLfFKbJSV+MrhoH2x60gyjnrQITGw2kxXKR5rwjNBBr6eqynv\n9D2zaAjZNlIBXRkmJVaCcikcaSxRWVULMr9hwXT6fgCzVzi+3biHl7bk/Fznm42J\nnj240zft+gv/M5ncYFgooJ77lsy1VV/F7D9IDQisuCwVGsJxa04=\n=sxB5\n-----END PGP SIGNATURE-----", + "payload": "tree ce254bc5b1de16aa7ded980081abc2c599bd10e6\nparent 59ddad1a8affb078724fdb58b296bf9258f3a183\nauthor eps1lon 1607968398 +0100\ncommitter eps1lon 1607985482 +0100\n\nIgnore component stacks in inline errors view\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/9372221a7a6f83decce2df7f08ade4b45957386b", + "html_url": "https://github.com/facebook/react/commit/9372221a7a6f83decce2df7f08ade4b45957386b", + "comments_url": "https://api.github.com/repos/facebook/react/commits/9372221a7a6f83decce2df7f08ade4b45957386b/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "59ddad1a8affb078724fdb58b296bf9258f3a183", + "url": "https://api.github.com/repos/facebook/react/commits/59ddad1a8affb078724fdb58b296bf9258f3a183", + "html_url": "https://github.com/facebook/react/commit/59ddad1a8affb078724fdb58b296bf9258f3a183" + } + ] + }, + { + "sha": "34500ea568a52daddb1ec8f5858afb74c326e5d8", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjM0NTAwZWE1NjhhNTJkYWRkYjFlYzhmNTg1OGFmYjc0YzMyNmU1ZDg=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T18:37:54Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:03Z" + }, + "message": "Add icons for warnings and errors", + "tree": { + "sha": "0d5fcf8a526cba6e1129698edac774a92a7bb89a", + "url": "https://api.github.com/repos/facebook/react/git/trees/0d5fcf8a526cba6e1129698edac774a92a7bb89a" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/34500ea568a52daddb1ec8f5858afb74c326e5d8", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UsACgkQWCcTbp05\ndTJqjA/8CefBC+nGr5sppylJYb9hyH+kwr+RSCTALyQ8hhc8gJet+KkMRwG/cSo2\nPm+1TtkoUPcjDgktsyVWMpFsJxa/wtNniyf2C1iMQYuiAXw9B7GqUh4RarF7Aa4u\ni0WQ7FwWZ+YPn5Mxa7compMrJ4i7AmNnjbBhwS8gzxvGXLLZL32ZeanYYlNaR2DC\nECuXaj3yBKNgwGWhHgLdXPWAxnn7wXILgLLnHTBErqV29TsjaCxCTmtNsDXMKS0A\nT+e8pnSUrp0Ow02n39/XQObdgRz/KJlVYFbEJTmg37HXcR+RovRprCP/HY9+hoP8\nFiiQ8YU1ShTymkUqXrkSR0ylq5pYlWZsNLLccn+5aKxJN5d7xXmzEL9rPOmlvzb9\nwPetizjXT9qanrE8qTrmGSZCvMk/PJupHyIiFJ/AgPRdpLLiqaoA1VzJLBGD5GHR\ns/YEYC3KGM414G5U+Z8y5HZBth3BcXQTudkXamxJ9D4gDchilFwHrGRS0HVVYZLP\nEOFLBaTxaO5cJmcx/czHPo7D84N7PVUSEkzmQ+qqc3c4NWlwQqHCENeQxV+COy1K\n/NbsJaF6PjDv4MP3aiaWkbTN2eqTO5ojCM5709XfGmPcK+kfnTCAQi4kHcCoXmNb\nH1zfRNXoN19XLU5s81nVhU/qp9adR/IgcR4sXfTM0puS56d24NE=\n=FlSX\n-----END PGP SIGNATURE-----", + "payload": "tree 0d5fcf8a526cba6e1129698edac774a92a7bb89a\nparent 9372221a7a6f83decce2df7f08ade4b45957386b\nauthor eps1lon 1607971074 +0100\ncommitter eps1lon 1607985483 +0100\n\nAdd icons for warnings and errors\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/34500ea568a52daddb1ec8f5858afb74c326e5d8", + "html_url": "https://github.com/facebook/react/commit/34500ea568a52daddb1ec8f5858afb74c326e5d8", + "comments_url": "https://api.github.com/repos/facebook/react/commits/34500ea568a52daddb1ec8f5858afb74c326e5d8/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "9372221a7a6f83decce2df7f08ade4b45957386b", + "url": "https://api.github.com/repos/facebook/react/commits/9372221a7a6f83decce2df7f08ade4b45957386b", + "html_url": "https://github.com/facebook/react/commit/9372221a7a6f83decce2df7f08ade4b45957386b" + } + ] + }, + { + "sha": "962f08cf82500d925596d7d566cc697ba07c7394", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjk2MmYwOGNmODI1MDBkOTI1NTk2ZDdkNTY2Y2M2OTdiYTA3YzczOTQ=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T18:42:06Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:03Z" + }, + "message": "Cleanup unused console logs", + "tree": { + "sha": "9e6e0cc39547c223b30fe425f8c3ac1a68ee8737", + "url": "https://api.github.com/repos/facebook/react/git/trees/9e6e0cc39547c223b30fe425f8c3ac1a68ee8737" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/962f08cf82500d925596d7d566cc697ba07c7394", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UsACgkQWCcTbp05\ndTIsMg/7BE6QChDLAzHfFTTTYqSPtjvHKp8jkMpV+D8RshNMlaoSoMCOjtTQNm0z\n+YHTIYG/5YR6vXm3BsL2ZrZZR/rGeYw2tq62VVZOMoobGDmYNWynMQ/hhqU+NtOn\nsgFd5ADYdi7cxZQNRSfrctvx/J168lbOji7HwjHJdmj2yWBeOP0YYMqd4veQUsUY\nsbvtXNGtNbBCApHYzCmBjrHuyWcHz2t/PznJQSFwK3QdtRF4uQ1LNTowp8ep+fVx\nQGkluJcLGYm7pX5o/AtzdGovwmkpx9eSgdmlRAWqnPAMbSmzGD0uZtaWFSkRZvtz\n3lPZX/Fn5otkiyr+zn+LWRDCbVmmsUwMKmPBPtqZFf5HGGxU/33cLilu5bWwT5Pl\nENpsUQ1oLHy2DC/r/tYhPiWhLolzWo5I8qOATj19ysoGSopQUo0WiX01XXykq1tK\n0hL9WXOEtW7q36/nRYtkNIrPraEoGyYdBryd+20G3Jy23JDR8aLMb73eyXaC6uZ/\nd37NSJSciPziMU0uuasc1QvPCpnLkJfMkLijfRJMlmZtY46E4811unlH2lr3LcH1\nfhKUiu7pvk7veA/HEgPgCaxsxSl1rU7jTiojmUTbKw0CN5zbS0EroQNu1987BzJ0\nn8q0ZRfwzAgVRfMByfXqbl53EfmppPkOl8LoGN0ILNnkJRf1pk8=\n=Hyzu\n-----END PGP SIGNATURE-----", + "payload": "tree 9e6e0cc39547c223b30fe425f8c3ac1a68ee8737\nparent 34500ea568a52daddb1ec8f5858afb74c326e5d8\nauthor eps1lon 1607971326 +0100\ncommitter eps1lon 1607985483 +0100\n\nCleanup unused console logs\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/962f08cf82500d925596d7d566cc697ba07c7394", + "html_url": "https://github.com/facebook/react/commit/962f08cf82500d925596d7d566cc697ba07c7394", + "comments_url": "https://api.github.com/repos/facebook/react/commits/962f08cf82500d925596d7d566cc697ba07c7394/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "34500ea568a52daddb1ec8f5858afb74c326e5d8", + "url": "https://api.github.com/repos/facebook/react/commits/34500ea568a52daddb1ec8f5858afb74c326e5d8", + "html_url": "https://github.com/facebook/react/commit/34500ea568a52daddb1ec8f5858afb74c326e5d8" + } + ] + }, + { + "sha": "3104b1e7463aec62683125e9a5e63538fb533ad2", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjMxMDRiMWU3NDYzYWVjNjI2ODMxMjVlOWE1ZTYzNTM4ZmI1MzNhZDI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T18:48:54Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:03Z" + }, + "message": "Fix flow issues", + "tree": { + "sha": "076dc54776a0f91a7cb4254de6e2f2e96eb38093", + "url": "https://api.github.com/repos/facebook/react/git/trees/076dc54776a0f91a7cb4254de6e2f2e96eb38093" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/3104b1e7463aec62683125e9a5e63538fb533ad2", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UsACgkQWCcTbp05\ndTKlbxAApJluvNix710CinqHwcit86D8YFzFZmELKV9CR71J1ZTrMcbFS5vh3Aqn\n11XbbeBWuifqQyvO+1HH/IgUpcTFW5POiWuz5nDLXPZCPE4h+vdDhVcmPK6ZIM5k\nSp54M9A/ZlOlQDaQBDgZ7Ce323d/GfaJdAmIR1gHZPj071XJIlcMooV5ABxBL/2t\nfsFJWORMRN+EGEMLl+R0uOhFEfiGeW89wx0D2OqA0n9LIh7Cfa8y+fIhMn6CHo0v\n71jKjEF5NBg/BwFpS8A3bW9s594IDtbEITFDgPBHXUl3rDaIGXhVgs7cN42sWZGJ\nNkQwpQh75rP6nUpBo9LSWp5NpQT0gAcO5Rk47mPgm6MYhqadEhCSq3OVNWTxw1gD\nMOgBD+p5xErkxaXRtimvElSME7sdHu5DkvQoArxgXjkpInE2Kr+yUmP4BFoRL7QH\n1md9Y1966PMisQTCh8VhemWjh2U3FDT2yyxY8wanXq8nOHP48Q/qV5UFJosUOT6+\nQZOBRe0nJl//bAEeN7yptrPoq+KUAgkKTEnUDZEtFbr0UbwqvSeaHXT03OOz0yUl\nVEr71FDpI4DtNPO0a68Y7WnKRVB3KTl9wljYKCfbhA2D67kUjc1gzRaH0bh3N02V\n0EXL1oyd/cAlqFHF2/cJ5t6Y/a23HrrjvOI4BzC767z5Z2CCcZY=\n=MdlC\n-----END PGP SIGNATURE-----", + "payload": "tree 076dc54776a0f91a7cb4254de6e2f2e96eb38093\nparent 962f08cf82500d925596d7d566cc697ba07c7394\nauthor eps1lon 1607971734 +0100\ncommitter eps1lon 1607985483 +0100\n\nFix flow issues\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/3104b1e7463aec62683125e9a5e63538fb533ad2", + "html_url": "https://github.com/facebook/react/commit/3104b1e7463aec62683125e9a5e63538fb533ad2", + "comments_url": "https://api.github.com/repos/facebook/react/commits/3104b1e7463aec62683125e9a5e63538fb533ad2/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "962f08cf82500d925596d7d566cc697ba07c7394", + "url": "https://api.github.com/repos/facebook/react/commits/962f08cf82500d925596d7d566cc697ba07c7394", + "html_url": "https://github.com/facebook/react/commit/962f08cf82500d925596d7d566cc697ba07c7394" + } + ] + }, + { + "sha": "cf3f212b2a387797c59b82fb08da4a547ad641b8", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmNmM2YyMTJiMmEzODc3OTdjNTliODJmYjA4ZGE0YTU0N2FkNjQxYjg=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T20:19:38Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:04Z" + }, + "message": "Implement clear(all,byComponent,byErrorOrWarning)", + "tree": { + "sha": "3864a094edecc0304d08016e15a23f72c625757c", + "url": "https://api.github.com/repos/facebook/react/git/trees/3864a094edecc0304d08016e15a23f72c625757c" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/cf3f212b2a387797c59b82fb08da4a547ad641b8", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UwACgkQWCcTbp05\ndTLjkA/7BBtp2DX/lCGFqRyblbrwanQtd44flxrzoLs4cIjdINyj/UA3vNBecgk+\nkrQKUJd5P00j8vDM6Al7KQnKARoc878rZF74fd8+cINXsiM9cx9BbaRbPlNB13wf\nJ9nQgZ/fyGKHyWBqXwDqlAOxvYSwvw8Dn5hX419dCkoS2shhw7XYe+BObXk4lKar\nWb84e8UpMuS8N+07PrsJlPXarHq5gxE1Xc5EddxBPH8npmOKxgn/KDMZZQp9ixtV\nJFVahGYceVjC/85PQmQzi1TZx8q2hrU6ezq3I0qK8bKlDhSDs7r6B0rlWH6q67Eh\nX7Al2q9sRTIaM8nla2ATEsfaXo/L4FLYLjOxC9ISSBaFQtdKZ5MozCU2hqiffb0H\nywRwrqyKSUh/NrNPZ+VYg15MYzlBm+pj1K/5G3eIoCei4udeQDNEaEXtVWUbTpmi\n2yKIOJ/dkSHWDTB1n/MLZXMeks7mJ0nYsFHSiE8YxdJNI27Ypk+EXg+79eKi+vdh\nSnN5ItiFZjOgZ6D0DETS3XrWMfWq3k+nTdv2obTqed1YO9inNc29o2Yi+Gs/COnp\n0RejYWl2YuLXxp1dVOP37U/g0MSoI8PiMPKyLHqX6IH2Jdfi+ELHZpB2kPHdRENB\nAt05IcuZlDPT2Ge1Mc8vvQ8z3GNAqwixsIkkdrewXdmZi5eyv0Q=\n=v1Ni\n-----END PGP SIGNATURE-----", + "payload": "tree 3864a094edecc0304d08016e15a23f72c625757c\nparent 3104b1e7463aec62683125e9a5e63538fb533ad2\nauthor eps1lon 1607977178 +0100\ncommitter eps1lon 1607985484 +0100\n\nImplement clear(all,byComponent,byErrorOrWarning)\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/cf3f212b2a387797c59b82fb08da4a547ad641b8", + "html_url": "https://github.com/facebook/react/commit/cf3f212b2a387797c59b82fb08da4a547ad641b8", + "comments_url": "https://api.github.com/repos/facebook/react/commits/cf3f212b2a387797c59b82fb08da4a547ad641b8/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "3104b1e7463aec62683125e9a5e63538fb533ad2", + "url": "https://api.github.com/repos/facebook/react/commits/3104b1e7463aec62683125e9a5e63538fb533ad2", + "html_url": "https://github.com/facebook/react/commit/3104b1e7463aec62683125e9a5e63538fb533ad2" + } + ] + }, + { + "sha": "5b3fb893b3609953bd1bebfc401a158bd82d7abd", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjViM2ZiODkzYjM2MDk5NTNiZDFiZWJmYzQwMWExNThiZDgyZDdhYmQ=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T20:29:01Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:04Z" + }, + "message": "Expand showcase with error and warn/error mix", + "tree": { + "sha": "da464d2d9940b7076b157fe6f10e9257c32b43de", + "url": "https://api.github.com/repos/facebook/react/git/trees/da464d2d9940b7076b157fe6f10e9257c32b43de" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/5b3fb893b3609953bd1bebfc401a158bd82d7abd", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UwACgkQWCcTbp05\ndTJl3RAAlNVn3qmPYP/gxwMFM825kV0os1FnzmPo2/VUoWLfNlZ1tI5jCmUUosj1\nRP1Kclhix6q39jpWcsdp8CLBw/QrehdSKHEmD7+2OugdOaRGLvPE/z9ou6zCrMxR\nUQfcfG/LDQzFrP8KTe7Nj0/4mezFDTP9X+1Rhs9srNXH3UHo06IZsTcSjLjvMRlO\ndRRIp+PGBApWiNzBZyQDHZ5UiEh+4OlpFT0QayrX0sm4pquwYSNMHN6IQX16h2CO\n7UaZs/2rie0a+rRiKt9yu95A34GBrec0lY6Rm8xs6eaj1AfI/oPLOwWY1veGMiB7\nPcub1vYWhDEyilaG0MMm18QwPUQSW+Vhmaug3mbCEzveu+0Gy8a+axijDEfX9hWG\nSl+G0LsvVsjg07k4+ayXQiox3oJvtalGwF7qzpKBglVzwYqO9c4InsXkh30xRd9D\nBbPnMq7GB5Y280fVrbxWIqVgEzEAFxvyhLHDD+dxM8TASJSiMTPcfayTQrfIV8NB\nj8b/GKmy8M/cDpKc6Hn8hDtCVSUIqZfwCAY/rkVtA61/tyvo2xZc8WwzlhvgvSWV\n9Kg9hUwpTTnpOHZ7qmj5QSQE8pLewfpXetZUt5Z7/TTYdbaV/BhYHJ5QPOoW/AnF\nFA3gd6ze0c6ShgG/o13zGWQb+bVfzxRfMb3GDmL4qB0xaQdk5ng=\n=W+GF\n-----END PGP SIGNATURE-----", + "payload": "tree da464d2d9940b7076b157fe6f10e9257c32b43de\nparent cf3f212b2a387797c59b82fb08da4a547ad641b8\nauthor eps1lon 1607977741 +0100\ncommitter eps1lon 1607985484 +0100\n\nExpand showcase with error and warn/error mix\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/5b3fb893b3609953bd1bebfc401a158bd82d7abd", + "html_url": "https://github.com/facebook/react/commit/5b3fb893b3609953bd1bebfc401a158bd82d7abd", + "comments_url": "https://api.github.com/repos/facebook/react/commits/5b3fb893b3609953bd1bebfc401a158bd82d7abd/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "cf3f212b2a387797c59b82fb08da4a547ad641b8", + "url": "https://api.github.com/repos/facebook/react/commits/cf3f212b2a387797c59b82fb08da4a547ad641b8", + "html_url": "https://github.com/facebook/react/commit/cf3f212b2a387797c59b82fb08da4a547ad641b8" + } + ] + }, + { + "sha": "d2c8e339ed0c75b0b197905a7d5bd49eb88e6c49", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmQyYzhlMzM5ZWQwYzc1YjBiMTk3OTA1YTdkNWJkNDllYjg4ZTZjNDk=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T20:46:27Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:04Z" + }, + "message": "Fix missing color error background message", + "tree": { + "sha": "6ba913b9dd1691373a90ab8b56a01c58d5eb46d8", + "url": "https://api.github.com/repos/facebook/react/git/trees/6ba913b9dd1691373a90ab8b56a01c58d5eb46d8" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/d2c8e339ed0c75b0b197905a7d5bd49eb88e6c49", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6UwACgkQWCcTbp05\ndTILJA//S8eYWMq4zsU0SySITqps6UZUy8RDMzJ5b5sDsHfrHRcJdMy1XXiK/kws\nBZO24yA5/iAVqA9eOADjMYKaXo03ax3fMb6apGQHcvXuUE+M7nuGmQKgWuDL7dTf\nslnvMvOMPSKhkMwBrDvxITCo2ouFLSwD1dzWoDfWA+izhZJY6wLy0rn4Vjqn1PME\noN0DXLy4GVueKCqyvL+x8lf2ozYT52ihu5y7DlxEhur9HFIMrCtcNpsRygrgFEr2\nl19C4sJJRKprXUHP2TTQ2+H5tlaXIN2zUC+wI2P00nRzEJ2TZ1OVFnV9ZAM6T62h\nalMaGDWSNjhkcZ24UCSZzBFgNoMJ+6gPMceQVKNZ7QHJqc6Aue4s1CPiH82Scynq\nJz0TwC4LL/p+C/ZVqkUrEUV6PuZulFETk/AeP9lgTzVWChX+R2L7XeLQMqCLFgev\nICK46u1znh6pv/N92fEA0ZMtQA1hRiTS1PWG4ckKBRiR4Zrj7Win+kFX5t0hE5eN\nfxgJw0N46GXsMkS6sBTcaCSbQJ+cTkiB0mXW783oUB5dgDr9zb1KgmLCJ8UUOJ4/\n9o4iVpxFKprVRel3+RMME0o8MGfYtR4hkJA1rlqGn1bcGcorqE7PzL1BwxQ7Yd1o\nWUkQmeguy+cq2LvN9LQ3wgOMwvB3E/2DXu217YoO/YlXNqQcsck=\n=wqWW\n-----END PGP SIGNATURE-----", + "payload": "tree 6ba913b9dd1691373a90ab8b56a01c58d5eb46d8\nparent 5b3fb893b3609953bd1bebfc401a158bd82d7abd\nauthor eps1lon 1607978787 +0100\ncommitter eps1lon 1607985484 +0100\n\nFix missing color error background message\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/d2c8e339ed0c75b0b197905a7d5bd49eb88e6c49", + "html_url": "https://github.com/facebook/react/commit/d2c8e339ed0c75b0b197905a7d5bd49eb88e6c49", + "comments_url": "https://api.github.com/repos/facebook/react/commits/d2c8e339ed0c75b0b197905a7d5bd49eb88e6c49/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "5b3fb893b3609953bd1bebfc401a158bd82d7abd", + "url": "https://api.github.com/repos/facebook/react/commits/5b3fb893b3609953bd1bebfc401a158bd82d7abd", + "html_url": "https://github.com/facebook/react/commit/5b3fb893b3609953bd1bebfc401a158bd82d7abd" + } + ] + }, + { + "sha": "dc92fef4500a214ebb1ff8985686a0338414a93e", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmRjOTJmZWY0NTAwYTIxNGViYjFmZjg5ODU2ODZhMDMzODQxNGE5M2U=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T20:55:06Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:05Z" + }, + "message": "Highlight elements in tree\n\nBefore we had to vertically and horizontally scan.\nBy coloring the rows, vertically is sufficient.\nThough I'm having a hard time in light mode. Dark mode is better.", + "tree": { + "sha": "58c79609ae46c806549ce52381a16d62daef7b94", + "url": "https://api.github.com/repos/facebook/react/git/trees/58c79609ae46c806549ce52381a16d62daef7b94" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/dc92fef4500a214ebb1ff8985686a0338414a93e", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6U0ACgkQWCcTbp05\ndTKbFA//Vqo7oZllW97uPrjWh6TZK9koipPsQ6xdiC5M0UeH1mCFv5jafoGPb+RY\nyWz897KcGysQV67F/L+sfKJrYkyWp6oxFkr8vCy8RuIYlPvwltJReLyAdGohr0UP\nzx0IMfvXDE2se2F4mwVPN8BgvEIIYWhPbm6cX4Vbua7ELXfEC+2AxU9PaIv2VqqL\nJcaGgZBbQtcQbcqz6QRKurrW7qFqAUX/xd0gNOn35wT95cvp1sP1sfsSeAdXMsnZ\n+nzpyZmTWkPyebVmHQHPjL3zFhE2apC/EajI+53qOS6NBVSRzaVLv8UCPtfEAEID\nI3X2nyPWrr05YarRGvWoW3YjdvHMRWrb3RcR6ZEKq8/pFYML6xdiPKQyHQuut8yj\nrzROk+QZDW8nAyGUauR6cVL0r8T1UbYHUfUpxlHeQV2d++j6pPjAnJpKo2FuTw1h\nUcPsPsn01DSXZ0trrrohBPeGBuW2JNN8AjyItGfSBbkIWHnKtn1nM5SgTaYGbuAi\nOjhBaBrezGICxuCH9L8B/+iYYF7pJKPp4za3NbOf0xqVdtyuzWNndn79MHv91kj5\nV5PwpiSYEgftEVWrqZ0ViyTyXA48khliKrpfMX0W4WHoNK31PaY/PTxUSU83mCQk\nsndR+mbJNCfcNCfEfPKPWv0tC+Mt2uk42AqWoxgj/RyIkNPtmqs=\n=zf7f\n-----END PGP SIGNATURE-----", + "payload": "tree 58c79609ae46c806549ce52381a16d62daef7b94\nparent d2c8e339ed0c75b0b197905a7d5bd49eb88e6c49\nauthor eps1lon 1607979306 +0100\ncommitter eps1lon 1607985485 +0100\n\nHighlight elements in tree\n\nBefore we had to vertically and horizontally scan.\nBy coloring the rows, vertically is sufficient.\nThough I'm having a hard time in light mode. Dark mode is better.\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/dc92fef4500a214ebb1ff8985686a0338414a93e", + "html_url": "https://github.com/facebook/react/commit/dc92fef4500a214ebb1ff8985686a0338414a93e", + "comments_url": "https://api.github.com/repos/facebook/react/commits/dc92fef4500a214ebb1ff8985686a0338414a93e/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "d2c8e339ed0c75b0b197905a7d5bd49eb88e6c49", + "url": "https://api.github.com/repos/facebook/react/commits/d2c8e339ed0c75b0b197905a7d5bd49eb88e6c49", + "html_url": "https://github.com/facebook/react/commit/d2c8e339ed0c75b0b197905a7d5bd49eb88e6c49" + } + ] + }, + { + "sha": "153358378a13d387622664908e6f4999d3dad55e", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjE1MzM1ODM3OGExM2QzODc2MjI2NjQ5MDhlNmY0OTk5ZDNkYWQ1NWU=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T21:02:15Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:05Z" + }, + "message": "Better spacing for error/warning badges", + "tree": { + "sha": "09433ef98d0b16beaaf96858f70c8726a85e896f", + "url": "https://api.github.com/repos/facebook/react/git/trees/09433ef98d0b16beaaf96858f70c8726a85e896f" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/153358378a13d387622664908e6f4999d3dad55e", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6U0ACgkQWCcTbp05\ndTLIKxAApx7VI8mjQ51Mp1/JeEiwvTXpvYFJ4GNEtkjityfj6n6ebtEAUm//4iUV\nz4N1UvTw4EpX+0IDHa7vebzL7FR7ui0Nv+OrGob/kTX2C0jYji04gKwq7cItlC9R\nufuhqKKiR/NoguUSDNJcf/MjKR6/hGFzQT+tvHpQX6B0gQmdQKnVJ85Njx1tH8Li\n+EVkUXsBjlHnPX5D93XerYKWBNNm73GXYg/8bmLe/Tx0pDYW/IH9n8Ymkl0C6+NR\nPCAlGUzpaDyw768weq5jWwTQ7qqvdAYYFhz5/0xFVlVnfZD4ROK4ITZgsHGzMe6M\nkkjbEikjBkmrcXQQo/RIHVykqdVojzH93vPrmGYZvx8xYL5X87sSTzuVHsTwVMe5\nlwsuMWhWC++mBzU9+rewTe1Evsx2mBJGXanCNYfAcmKnIqgkCDRn1ayC8FOsYCq0\nrwPqSDse/FwQkEURmsY3kwlWZmqN/J0otLVFYBOYH6l3c07DtNJCaKj+Bd/xQnjf\na5U6SIa6LZJbCmD1linPsvNfmjsDCKUSLHPRbM8zUar7qcjWlov+kNj2294976ko\nxtmplLia2bVa/8v/zd0zkOhsM2NFaAXDdpKbSfsJyjMyHfCUUIIGer05noA63KTX\nrWSv8oJm2bWLBaJeyLJK6ru6qBt3WvHflmedMdvBKo+ZnkdYNF4=\n=pAoN\n-----END PGP SIGNATURE-----", + "payload": "tree 09433ef98d0b16beaaf96858f70c8726a85e896f\nparent dc92fef4500a214ebb1ff8985686a0338414a93e\nauthor eps1lon 1607979735 +0100\ncommitter eps1lon 1607985485 +0100\n\nBetter spacing for error/warning badges\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/153358378a13d387622664908e6f4999d3dad55e", + "html_url": "https://github.com/facebook/react/commit/153358378a13d387622664908e6f4999d3dad55e", + "comments_url": "https://api.github.com/repos/facebook/react/commits/153358378a13d387622664908e6f4999d3dad55e/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "dc92fef4500a214ebb1ff8985686a0338414a93e", + "url": "https://api.github.com/repos/facebook/react/commits/dc92fef4500a214ebb1ff8985686a0338414a93e", + "html_url": "https://github.com/facebook/react/commit/dc92fef4500a214ebb1ff8985686a0338414a93e" + } + ] + }, + { + "sha": "4b46d94ec9564da0cf0b1a0ed31eb17b92d9d928", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjRiNDZkOTRlYzk1NjRkYTBjZjBiMWEwZWQzMWViMTdiOTJkOWQ5Mjg=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:00:34Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:05Z" + }, + "message": "BROKEN Implement walking through elements with warning/errors", + "tree": { + "sha": "9b025d9189a20a8e5d961594db35eedd56ea5957", + "url": "https://api.github.com/repos/facebook/react/git/trees/9b025d9189a20a8e5d961594db35eedd56ea5957" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/4b46d94ec9564da0cf0b1a0ed31eb17b92d9d928", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6U0ACgkQWCcTbp05\ndTItug/+JpAKHZaauHeA96YxvpX7xj7X6SFaHbZwbEwhaB3NWK73uCvJdjXb6MuS\nGY6vQEmLMcUTiaGKLWsPZpCTX2sbZItk1Fdf8U6N7JbxngGLPeHqSEmoSqPspGem\n0ZEEBkCAvZY6QJkcZWu5KMXHyDJq/I8022BauD5htz1Clnsk++/XGurfm89rUR/J\nvG0GUiJBnX8Yxd0M/edQUQByo1z5wyDIzx7rW3aW9aqw6NODGz4FxoI1lkvQL9NG\niXRWQG7xn1UWQOxZuH08tE3/NOvk5XUSHf2w88fs8mW5OFXNPlA7CqjGjp80grLj\nVwDaHsN7PvHvKQd9arUITnJ8wr3egkxtQJy7NatkEK9KEGOpNd2/7J0PO+QBxBIr\nYDs52N9VY0HWxkwPWmDwYdEtI1mv/Ldi9vq5a9+W7Pg2eZ1VdAmEkhuJOf49tIgj\nPgH+jMkSiJhCuPnMk6/tKI/FoJ9GKgAut7LTVPvyelWCg4707ijcdaiYGxBfXlKW\ntEn6pdi8rsv3CtU8i95Pe6x03F0TEtVQfq4FnlygkQMbKyE/Ao2hIpMva3xGmaQm\nyLeNGSgMenObMa7rTQwt5I/J3pYTES6y8q6JHbcuv7bwyI9jw656EryPwghv0ECU\n+glDCs7TwlLWzQ00i33uc5oxiQIEXnZhLsiyLj8yr8+Pp4tkd10=\n=I6Vf\n-----END PGP SIGNATURE-----", + "payload": "tree 9b025d9189a20a8e5d961594db35eedd56ea5957\nparent 153358378a13d387622664908e6f4999d3dad55e\nauthor eps1lon 1607983234 +0100\ncommitter eps1lon 1607985485 +0100\n\nBROKEN Implement walking through elements with warning/errors\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/4b46d94ec9564da0cf0b1a0ed31eb17b92d9d928", + "html_url": "https://github.com/facebook/react/commit/4b46d94ec9564da0cf0b1a0ed31eb17b92d9d928", + "comments_url": "https://api.github.com/repos/facebook/react/commits/4b46d94ec9564da0cf0b1a0ed31eb17b92d9d928/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "153358378a13d387622664908e6f4999d3dad55e", + "url": "https://api.github.com/repos/facebook/react/commits/153358378a13d387622664908e6f4999d3dad55e", + "html_url": "https://github.com/facebook/react/commit/153358378a13d387622664908e6f4999d3dad55e" + } + ] + }, + { + "sha": "ae89ee8e16a94f5333a17ce950dcd4cf001033cc", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmFlODllZThlMTZhOTRmNTMzM2ExN2NlOTUwZGNkNGNmMDAxMDMzY2M=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:03:36Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:06Z" + }, + "message": "Remove nullish coalescing which is only used in the scheduling profiler", + "tree": { + "sha": "ca68f511c06b6f137e01f131e31dfc76ad366d9d", + "url": "https://api.github.com/repos/facebook/react/git/trees/ca68f511c06b6f137e01f131e31dfc76ad366d9d" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/ae89ee8e16a94f5333a17ce950dcd4cf001033cc", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6U4ACgkQWCcTbp05\ndTJ51A//Q5mdYqrLd1tLHlSpod5eVi2MW5bk1rkUvIjbOZNOCN8Afr5r4ieV/RNY\nzrJs4BfGgLiaziIYL9lie7xnnjQzIhpoOXQos+4GVx89v9wWVs2Htscse770E/1F\nWwBkyToHNFM51JKRG/FjEaRLCIzMDWpzsiSDabkw0yM3YESLHF11RZ7VNqKCnfzY\nLFWUS46FnPPKyaD1aoPjJIOE9W1D3rFGe57exIgrTh9HcPuGYxCxCxsxU6E4cWLI\n9OF4WFOYHQ9J66qpEjUXHuFR6Gk9LJ11SSObT7loxMuLoOcKBvL4KxTPtoZf/C3r\ndLvXkhkWd0sq/muazj6SmTRboO7l9oiGvU+D5XHwDGvaXi3Tid9Hjhl4QDLA+ftW\nLbPidihELlWyT9/JC8Piq7sxdjydGeBO7rSIGu42JqiRXnB6Fr8GapcM89JA07Mm\ndxbfcVrMo07A0PN1WKm1s6NTsQtZ3Se3kmGlB5ZAE5oMylJJvaEensPdlQq19ROt\np4iAxX+P7ZzJ0DiywR/KJQxj3+XLQsYlsqNHGsTHpZYJR5tcqmCAikjRYZk98tP1\n1iCt24eZMj4cHlNzQhyOJQnYgg29ezhyd9LHWHl65utDVSzx+nv227kWLJN0Rrk4\nghk8R0TCQJU5IHW1ybk5CWS480wAvFc3D1MCTQYNwOiM/GaVidw=\n=Fnie\n-----END PGP SIGNATURE-----", + "payload": "tree ca68f511c06b6f137e01f131e31dfc76ad366d9d\nparent 4b46d94ec9564da0cf0b1a0ed31eb17b92d9d928\nauthor eps1lon 1607983416 +0100\ncommitter eps1lon 1607985486 +0100\n\nRemove nullish coalescing which is only used in the scheduling profiler\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/ae89ee8e16a94f5333a17ce950dcd4cf001033cc", + "html_url": "https://github.com/facebook/react/commit/ae89ee8e16a94f5333a17ce950dcd4cf001033cc", + "comments_url": "https://api.github.com/repos/facebook/react/commits/ae89ee8e16a94f5333a17ce950dcd4cf001033cc/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "4b46d94ec9564da0cf0b1a0ed31eb17b92d9d928", + "url": "https://api.github.com/repos/facebook/react/commits/4b46d94ec9564da0cf0b1a0ed31eb17b92d9d928", + "html_url": "https://github.com/facebook/react/commit/4b46d94ec9564da0cf0b1a0ed31eb17b92d9d928" + } + ] + }, + { + "sha": "6e41fc1b8d93a412616afab74e5e5a6791ee7ec6", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjZlNDFmYzFiOGQ5M2E0MTI2MTZhZmFiNzRlNWU1YTY3OTFlZTdlYzY=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:09:24Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:06Z" + }, + "message": "Bail out if we have no errors/warnings", + "tree": { + "sha": "fa08c38ebd2d1fe856dc06ea1ae7a3d3e6e331a8", + "url": "https://api.github.com/repos/facebook/react/git/trees/fa08c38ebd2d1fe856dc06ea1ae7a3d3e6e331a8" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/6e41fc1b8d93a412616afab74e5e5a6791ee7ec6", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6U4ACgkQWCcTbp05\ndTKj3BAAqBWpeYJItj/mLnCQyd4X89DBdbvsbLzpBVzmVEpAmN4HHkdQ7rkfhY4/\n1xw2HsSGHXKVOlSQj4YtEstHSvhVTE2OMKk4L+5LiEae0BsQ2XQdTjE4TWAW///6\nrttAyoz6W8cUP8Gx/CUgE+FEdubjlV6VJcL3c0aA4nw4pNPug9Jma+6JqU8e+LFH\npWfXy4oe0C+BntlPVz2Nya+VvGWfSJZtMUGW8Uq6m2keIoTTSLqftcHXrvGVJpGb\nn0F6NTPBsHapebpS0IlAM3ybXj2prqre6SELqQNWLbUeEXD6c7uxS6213ortwFfg\nH2vUqOnu7xxmSUn+Q9Zl9i/gv9zVo+V7D+jAJeYdiXKzjorJS4CWbHkgDAmOOymI\nFfeWYSSOlU1gP6zelCFt1ZF3IOYiBQaSMHkzhYzVu6bdWUr3IPdsovFzyvO8GCj0\nHGaHrmfJvitkndAPtzkwxdvk9o1+Y8gfl3hz8THo7RWjT8QIaBwQFF46+xsSxu18\nUNbGyLldSii178BHSDNAlGavKyO7fR2N62u51fYfd41rUWJzmFRuhzVr+ZVP8pN+\nyQz9yR4VRk0JY13X3z0G8QpdyK/+kWH0bICkJD8+zhbgx0I3Mf5FG8UlIgxsPBmX\nqo+4a/ztLJAblEV8WIdUG1ZIJodKu/04w/Ls4znY3yxdocur2BY=\n=ZEW6\n-----END PGP SIGNATURE-----", + "payload": "tree fa08c38ebd2d1fe856dc06ea1ae7a3d3e6e331a8\nparent ae89ee8e16a94f5333a17ce950dcd4cf001033cc\nauthor eps1lon 1607983764 +0100\ncommitter eps1lon 1607985486 +0100\n\nBail out if we have no errors/warnings\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/6e41fc1b8d93a412616afab74e5e5a6791ee7ec6", + "html_url": "https://github.com/facebook/react/commit/6e41fc1b8d93a412616afab74e5e5a6791ee7ec6", + "comments_url": "https://api.github.com/repos/facebook/react/commits/6e41fc1b8d93a412616afab74e5e5a6791ee7ec6/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "ae89ee8e16a94f5333a17ce950dcd4cf001033cc", + "url": "https://api.github.com/repos/facebook/react/commits/ae89ee8e16a94f5333a17ce950dcd4cf001033cc", + "html_url": "https://github.com/facebook/react/commit/ae89ee8e16a94f5333a17ce950dcd4cf001033cc" + } + ] + }, + { + "sha": "8de25ff9481f5bd960d589841fef88a475b68daa", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjhkZTI1ZmY5NDgxZjViZDk2MGQ1ODk4NDFmZWY4OGE0NzViNjhkYWE=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:18:08Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:07Z" + }, + "message": "Hide inline errors toolbar if there are now errors at all", + "tree": { + "sha": "a67553f215c55ddbd6e2349f6006bbf294194e83", + "url": "https://api.github.com/repos/facebook/react/git/trees/a67553f215c55ddbd6e2349f6006bbf294194e83" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/8de25ff9481f5bd960d589841fef88a475b68daa", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6U8ACgkQWCcTbp05\ndTJ5hhAAh9du8gc+wYbu/VxxN6oWW9tiXxaNSoeM/x2nd+KZDXXS1nVGMdmNdHv0\nIGHagynw2XVlv8PnV9t967avIIy6AmQRtOo4tjuIod9YKcHzovxVgTqOUlualMmk\nUfrQKNeFioHoaUuOcfOt6pP7XtU5N3V98PJnWliSceOrziZFzqy9Zy/qeT5kOTA/\nIaAx145+Uda69u/wzg0MrvGIqpONGrNDDzwx3WT6ZpfRm7/SjCpNmJQi1tj4v7s9\n9sGWB/YNng/i0cQzodI7M3JxxElEhDoVie7ebLMsVoU+2erGuf0qS4kC20AtdndY\n7pRlOBFpsSS3JjA2Fft3GEwH1BNlzXm9dMG8goWnQ/aWmt9uIlY6QmhHuGV2QNwN\nmZMbacTgWeKR1da91lntVu+DW1IpFQi2KEzGkSpiKl5vV/OMYGAK++B9Irt76tYW\n//l+nThWGAvNKE46Kf0wMu7j0XLBXt1Y7BQV0J3SU9VbAfFAE9XN7NCxPs9pU9LN\nCLRc3SgEhbXPgwisZmP/z/Grld8NJWLLfOB+2LsPqyjq9KrtNwCpvchK2MXDDpc3\nXDztE7t6YZ2CjuL2qNNJGIS41fYy8KTzlEs44qruqBWATba0JU+5g2AE6XqzJZdq\nSWuP2boSXoKNyUbh2IzFnrRXz4utlye5K+jI3NN7I9o+4WUjuv4=\n=Jotg\n-----END PGP SIGNATURE-----", + "payload": "tree a67553f215c55ddbd6e2349f6006bbf294194e83\nparent 6e41fc1b8d93a412616afab74e5e5a6791ee7ec6\nauthor eps1lon 1607984288 +0100\ncommitter eps1lon 1607985487 +0100\n\nHide inline errors toolbar if there are now errors at all\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/8de25ff9481f5bd960d589841fef88a475b68daa", + "html_url": "https://github.com/facebook/react/commit/8de25ff9481f5bd960d589841fef88a475b68daa", + "comments_url": "https://api.github.com/repos/facebook/react/commits/8de25ff9481f5bd960d589841fef88a475b68daa/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "6e41fc1b8d93a412616afab74e5e5a6791ee7ec6", + "url": "https://api.github.com/repos/facebook/react/commits/6e41fc1b8d93a412616afab74e5e5a6791ee7ec6", + "html_url": "https://github.com/facebook/react/commit/6e41fc1b8d93a412616afab74e5e5a6791ee7ec6" + } + ] + }, + { + "sha": "3560c40a81d69918c6017a155ec13ef47b4a4009", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjM1NjBjNDBhODFkNjk5MThjNjAxN2ExNTVlYzEzZWY0N2I0YTQwMDk=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:20:02Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:07Z" + }, + "message": "Remove outdated todo", + "tree": { + "sha": "dd8fc9eaf1929b7afde1ad865769de0bf1274f02", + "url": "https://api.github.com/repos/facebook/react/git/trees/dd8fc9eaf1929b7afde1ad865769de0bf1274f02" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/3560c40a81d69918c6017a155ec13ef47b4a4009", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6U8ACgkQWCcTbp05\ndTIxJRAAmImF6QdGgSxmKjl6e6IXg2oZy8raGAyfd+pXD3k8I/XBTYB8dwNI+UUA\nnhB9YXD0rY7Pjz8I8kstYT0NVEUKUbFhQiG8XIVomg0sb5kkx8TwgleQTBrdRme8\nKV5AjGs8c0NoDFV6qjsK/d6TvkOrCVSr2YIg9gH55qByy+wfsDbLH/ZtoeyQaeYC\nvPUGHbHC3UBeSaYk7BY3RnoEe+j65Gbqwf4q4BYgX/PyrDy8RMo0743jB7FhURI+\nWlD33UN7BUAjBprOgcmEau9l34rhhTmKREr5Hedtw4rfxNCMLFwrvpSkwTnGXZJp\nV8GizbC+3ahon8YmGHBFj1Z4RlhrIwqN/96rFqq38Rxu5TiPnxtfE3bOrFCGdqgz\nhA8HQfobR1serxHvmK3mQ28GeFlL2Ymva7IgsEw5ELj6YNIx9sx75hETnms38X7F\nd36W2T+XJl70wkch0gnUPFYAk5O6H352vNExhevvSD9X7ZN6qlXeCmmO26rc85ZF\n56gDjtBe893e4TBovcEItyaoRHRlaT/fqq/3K8pmCTH5jpi3JDFYNxnffgIUMFA5\nhwl1fsb08HmS8wBi0koYZPmNjGADln/YRznMX8proNDTq+RlhBjmh+I3tfMbnDIr\nhOVc51lcYpy7bYNL82RvAzof32lxGhFSsXtM4tNCo1uFN2iaokc=\n=cRE7\n-----END PGP SIGNATURE-----", + "payload": "tree dd8fc9eaf1929b7afde1ad865769de0bf1274f02\nparent 8de25ff9481f5bd960d589841fef88a475b68daa\nauthor eps1lon 1607984402 +0100\ncommitter eps1lon 1607985487 +0100\n\nRemove outdated todo\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/3560c40a81d69918c6017a155ec13ef47b4a4009", + "html_url": "https://github.com/facebook/react/commit/3560c40a81d69918c6017a155ec13ef47b4a4009", + "comments_url": "https://api.github.com/repos/facebook/react/commits/3560c40a81d69918c6017a155ec13ef47b4a4009/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "8de25ff9481f5bd960d589841fef88a475b68daa", + "url": "https://api.github.com/repos/facebook/react/commits/8de25ff9481f5bd960d589841fef88a475b68daa", + "html_url": "https://github.com/facebook/react/commit/8de25ff9481f5bd960d589841fef88a475b68daa" + } + ] + }, + { + "sha": "45f756236f782b8891261661b40270416e2da48d", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjQ1Zjc1NjIzNmY3ODJiODg5MTI2MTY2MWI0MDI3MDQxNmUyZGE0OGQ=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:23:22Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:07Z" + }, + "message": "Add keys for error/warn messages", + "tree": { + "sha": "d2d4806c4d3aa1563cb6694346da036ed47e4340", + "url": "https://api.github.com/repos/facebook/react/git/trees/d2d4806c4d3aa1563cb6694346da036ed47e4340" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/45f756236f782b8891261661b40270416e2da48d", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6U8ACgkQWCcTbp05\ndTIsFw//YwSpF1agJPGUfcU84geZRb0qwiBp0NhNZC9bxg+KGIYpNDJE4mOIS7JU\n3y8uyb9vNTzjfqSgPUu9VgMIi1tUS7NgT1Nvks2Tf7zNCwvem2pmd/bzzFSp2PE6\nebJ9VCgJD34luuc8u7HuSXxyZR8RF9dUcI7TOKD7aR9Jd+AXM2YkerfpT5e8SIFC\nbYv8F86JHe/n6v67TKSUESdXv29LhWVuIIs1K8MG0lvZzjwcroDwVeodyTCWaIBY\nToI6BAaqXNQz6WoahBKWN2N4YM2RI04nmQjjGII6f9ZiiRDzSUyHmhfo7V5wFu0W\ngdkrPvuLR1UqjF1gFpFJK0GSX93JMPVB7Ha8xuW6v971QQ7GDTBgPJSQ3JcFAYw+\njFM8cPk48I9DSogv2cQccNZGG4Q5HTN/MQ7fWTB49TydkyV71psYvDltjBuGgJHk\n3yb2cnMPcOM4GW/CpHU3GgCAxn4nJOzl25b3r5BuZeWYyGg719CEZ3sQ0vIPjGz2\nWZjhUADXqOXc9AyXydyCx2vwNPSqvHVQAx8l2qOXkrxSP6OnH1b1qHTDEYCdM7VT\nV10Lc8mVC6/IBEfBrujNdNS1hcUmYw8O1REZXqFxzc67TtVQaXXnaIGdkRHK0QEO\nUHHvl7nn9MYv9Y4uuCdSBVlXZv/JwYikwUuqI9pFEanIrnzKzEk=\n=P31U\n-----END PGP SIGNATURE-----", + "payload": "tree d2d4806c4d3aa1563cb6694346da036ed47e4340\nparent 3560c40a81d69918c6017a155ec13ef47b4a4009\nauthor eps1lon 1607984602 +0100\ncommitter eps1lon 1607985487 +0100\n\nAdd keys for error/warn messages\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/45f756236f782b8891261661b40270416e2da48d", + "html_url": "https://github.com/facebook/react/commit/45f756236f782b8891261661b40270416e2da48d", + "comments_url": "https://api.github.com/repos/facebook/react/commits/45f756236f782b8891261661b40270416e2da48d/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "3560c40a81d69918c6017a155ec13ef47b4a4009", + "url": "https://api.github.com/repos/facebook/react/commits/3560c40a81d69918c6017a155ec13ef47b4a4009", + "html_url": "https://github.com/facebook/react/commit/3560c40a81d69918c6017a155ec13ef47b4a4009" + } + ] + }, + { + "sha": "1dafc3b6fb076fe8376c4c21b5cba40b8b6b5b8d", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjFkYWZjM2I2ZmIwNzZmZTgzNzZjNGMyMWI1Y2JhNDBiOGI2YjViOGQ=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:35:07Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T22:38:08Z" + }, + "message": "Diff cleanup", + "tree": { + "sha": "81b3503d153a5bd1ec06d17c8d8da568339c0337", + "url": "https://api.github.com/repos/facebook/react/git/trees/81b3503d153a5bd1ec06d17c8d8da568339c0337" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/1dafc3b6fb076fe8376c4c21b5cba40b8b6b5b8d", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X6VAACgkQWCcTbp05\ndTIggA/9FW+7D4SyiWOb72z7WVDnP766KZc8hayVRjxFymHkGCBrGeFo/e+yqlGi\ntVTiz9Uvy+pUj5toyThvxg1JhDKZfk24pxUDKjwlm6LIjoTfwe8HjOpkwi9wTmpV\nmbfR+uZNWsDhCF8uhazIaugKCgVwxgFHy2WTvJYE0+RU32u98KoxkMpSO60unam/\nJVJqPgRfubsTNvLtb4824LncxXJfCXLm+o10lFeezcQxq1HcjBkvv/OuRsRPzX8T\nlJstYgd7SjqPAK3el8Iv2a8DFMom/zijQCvDSUHV+hMVI/8l4/s29MQOZxW8pZmR\nYgA27aCUlXX8SCmu0InmJZ0JNHqavO9zXJsavdX8dZmFQqhprdcbvNqYUFmx4U9N\nvV6nDA0bONRoo1uM69nlQBu38nIimrPyAZY7/ZPsWA/ZugIO7KQ9bQjvagtiNB/y\n4cSDsyEvJepcUmSjaYh22P7nM6LAIy4YdLiHF+HVpp9dCSkHajqD7KweoW39JcHu\n4UFERLIp0NXMr01esFS6kz5n0p706glaGi+TTx5QbCfY+yg7XyO5kxjHMHR+asg1\nWufWth+PE8JPofvMSN7vewI5s70w8lYmN5MYU7RiiBzO5cC79wRDILRNEXnV/lqK\nmDlzr6efExJmIsT1nJuXcWXR6PtTcBobkFC8kMlrq2pRvb7jchc=\n=qxgt\n-----END PGP SIGNATURE-----", + "payload": "tree 81b3503d153a5bd1ec06d17c8d8da568339c0337\nparent 45f756236f782b8891261661b40270416e2da48d\nauthor eps1lon 1607985307 +0100\ncommitter eps1lon 1607985488 +0100\n\nDiff cleanup\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/1dafc3b6fb076fe8376c4c21b5cba40b8b6b5b8d", + "html_url": "https://github.com/facebook/react/commit/1dafc3b6fb076fe8376c4c21b5cba40b8b6b5b8d", + "comments_url": "https://api.github.com/repos/facebook/react/commits/1dafc3b6fb076fe8376c4c21b5cba40b8b6b5b8d/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "45f756236f782b8891261661b40270416e2da48d", + "url": "https://api.github.com/repos/facebook/react/commits/45f756236f782b8891261661b40270416e2da48d", + "html_url": "https://github.com/facebook/react/commit/45f756236f782b8891261661b40270416e2da48d" + } + ] + }, + { + "sha": "e3e95fbf56947aeda4c416ac93eec5c8b0bc4f38", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmUzZTk1ZmJmNTY5NDdhZWRhNGM0MTZhYzkzZWVjNWM4YjBiYzRmMzg=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T23:03:44Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-14T23:03:44Z" + }, + "message": "Fix flow errors due to missing type narrowing capabilities", + "tree": { + "sha": "c5d6f2936a23a95b16b990bae912c6e371c4db62", + "url": "https://api.github.com/repos/facebook/react/git/trees/c5d6f2936a23a95b16b990bae912c6e371c4db62" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/e3e95fbf56947aeda4c416ac93eec5c8b0bc4f38", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/X71AACgkQWCcTbp05\ndTK7yxAAqa1ao5GaJPrTn+2jFlxpPWVdS/yU2TbyocumsMGNsxCMXAqMp8NgLD/2\ns6Qb22F+i4mdjppqU2DMyDc3+bG0hmPzW/9FB28UekjayTfp6CaO2COq86khrFMa\n+tqva15adFxz5UEx5rSREeQ1M8b0YSaJiCPxaBpEF4+RzN37RGqZev+emMGtEfku\nWE+lM8i1Sr/omRGG+SnbfkOCA3AlBbBqskhnkBy3JGEoESWMULpL8kF+MTjB1k8u\ndC0ft7CLyv2wh2kXM3qOa5s5qsltxBn6u4elDAmTEpcDOA1627czPKR2MvKP8RCv\nayVvEipcENBYAMGc82XDoY0GGN4AL1NxIblvYQ3pGVszYLEMKb1SWLqned17dpYk\np804d7Bx805amuhwWrmHCDyhN56xHUflW9omqEkaUwDT+DlbYll2p8F4Yl6TfJE/\ngbXpKvPytP+7Nl/ivexgLrKFajvWv7v6EYcciiVBjihROdprDJEzM1mttDVd4WuN\ncyTx7Cxz1UDFxiNnmLqqVpUt3hCtt9NWfKsytii2lZAT85U4QEAFaUF4Lj0vWJRu\nUwBJBhNVp3evjwKzNFCQgUdmnbxZFz0H9DFROtB88DSfs4FrfX40oAsp1vNYvW89\nJmTFIvQ9fpdE2f1H9ouWmLZ3OLCxR4GudrMmUxId3SxnkQRbNJ4=\n=BoOB\n-----END PGP SIGNATURE-----", + "payload": "tree c5d6f2936a23a95b16b990bae912c6e371c4db62\nparent 1dafc3b6fb076fe8376c4c21b5cba40b8b6b5b8d\nauthor eps1lon 1607987024 +0100\ncommitter eps1lon 1607987024 +0100\n\nFix flow errors due to missing type narrowing capabilities\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/e3e95fbf56947aeda4c416ac93eec5c8b0bc4f38", + "html_url": "https://github.com/facebook/react/commit/e3e95fbf56947aeda4c416ac93eec5c8b0bc4f38", + "comments_url": "https://api.github.com/repos/facebook/react/commits/e3e95fbf56947aeda4c416ac93eec5c8b0bc4f38/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "1dafc3b6fb076fe8376c4c21b5cba40b8b6b5b8d", + "url": "https://api.github.com/repos/facebook/react/commits/1dafc3b6fb076fe8376c4c21b5cba40b8b6b5b8d", + "html_url": "https://github.com/facebook/react/commit/1dafc3b6fb076fe8376c4c21b5cba40b8b6b5b8d" + } + ] + }, + { + "sha": "2aafbdf7718d1f25e1c69d738183db46aec09d12", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjJhYWZiZGY3NzE4ZDFmMjVlMWM2OWQ3MzgxODNkYjQ2YWVjMDlkMTI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T14:19:08Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T14:19:08Z" + }, + "message": "Match UI of Search", + "tree": { + "sha": "2198afb099ba7da9a6753d7ade9f423be3117c6b", + "url": "https://api.github.com/repos/facebook/react/git/trees/2198afb099ba7da9a6753d7ade9f423be3117c6b" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/2aafbdf7718d1f25e1c69d738183db46aec09d12", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/YxdwACgkQWCcTbp05\ndTKf+Q/+Mk17VtK8Co6pzgkdc67h9M4mHESSi8OZXLDALEGCuUOmBL8lvpcV0y2O\nykyCE2usgTAbZnhoaulYpSHYXCfEYPi+QB1aSuBNfj3UBqQH//IOdvi/QE15SYLn\nq+UcUN0uVV66ZcWU3tp+piVipGrXjR4G4VYvQ1hdgnG5fgdTkHn9YEtW506J2XKU\nk2rFpICUghjvYaFTfwnTbXDtqAjolSDh0V/nL2jLGeltQcwZWSzJ2FA9fKzclIBd\n6gdzVnCb2h1ZXdpvutHe2HuuDbRyBnwM0n7ymY0LrdD41OISKzFP0qXS94jGkfbo\n2CvrPa4IIsErHMYdrNXlzaH3f7jIEIc7kj9tZ7JMoS5AmO1nOCcvB78Kr+3zZYDt\n62461+NHIpKwqaIGHjgNZKyThGh/iYkRJocCtDYyTVX91Vsr2fNTH3MzHNWl/CXt\nH9dRYSMtWjhrOirKp9IwPCRHhjCVCQN0lbmEfJa3Bm9ob0MISZXT/zbSIYkXom7G\nSruYUwN4LNMzQ2PXOnWG/gmxJlplwFcPhnye7G66tLp0NEFYsqX1VUF2v9MwxmPb\nUECcxnqlbHCEs9XE7cH90c6/lGb3G2jIzWsvkAOhsTc5yj2icFuIpRCh6dEWygu3\nMNlsDVnmjRW5Ray02adefebc1v7cS0BK0K6AE9a6z6ErQ0w1MAo=\n=FT9B\n-----END PGP SIGNATURE-----", + "payload": "tree 2198afb099ba7da9a6753d7ade9f423be3117c6b\nparent e3e95fbf56947aeda4c416ac93eec5c8b0bc4f38\nauthor eps1lon 1608041948 +0100\ncommitter eps1lon 1608041948 +0100\n\nMatch UI of Search\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/2aafbdf7718d1f25e1c69d738183db46aec09d12", + "html_url": "https://github.com/facebook/react/commit/2aafbdf7718d1f25e1c69d738183db46aec09d12", + "comments_url": "https://api.github.com/repos/facebook/react/commits/2aafbdf7718d1f25e1c69d738183db46aec09d12/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "e3e95fbf56947aeda4c416ac93eec5c8b0bc4f38", + "url": "https://api.github.com/repos/facebook/react/commits/e3e95fbf56947aeda4c416ac93eec5c8b0bc4f38", + "html_url": "https://github.com/facebook/react/commit/e3e95fbf56947aeda4c416ac93eec5c8b0bc4f38" + } + ] + }, + { + "sha": "64ad2e25324016e94167d6d1d29f07e362fe221b", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjY0YWQyZTI1MzI0MDE2ZTk0MTY3ZDZkMWQyOWYwN2UzNjJmZTIyMWI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T15:44:29Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T15:44:29Z" + }, + "message": "Match UX of Search", + "tree": { + "sha": "52cdc221c9bd96052c372eb797b6e4660b16c20f", + "url": "https://api.github.com/repos/facebook/react/git/trees/52cdc221c9bd96052c372eb797b6e4660b16c20f" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/64ad2e25324016e94167d6d1d29f07e362fe221b", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Y2d0ACgkQWCcTbp05\ndTI1tRAAtLxkPWmdCW+SbjTAvKChiaNvcAop4zB6qxRq4TcUVZNRT7L2gH9d6Yi+\nT6ekMhqms26EMECnBx5Lh4CiK8E1IK5Cg7GomLjkracsl55ZKUB6AgcenmEayNY1\n0llW/izU5Wsp+rk7Ex50JHTEa9Bn6yzp67sfNKHw5sgZLsJixZqaWXIlOdxQcivQ\n8q89lP/xJ6d419iZ5YJd9Fvx0fX6vtM1KG5elZ4wY34YvLSdaLjZw5NANRcG7c6h\nt9n8rXw2szRqPKIPLpSKTM8p2mwohrT4HI4oPLhyRYn+v8vdkLRWeAM3as/z2GaU\n749++iW4D7uaJJWEUKuLMTGP6SKjK9q2s4YlAiTykXaVqui1GvTr404hp/IPsqk8\n93se1GhRZya+L1XNjK4C4eCbP8/mDuX/aCdIuEGy74LJC8qfMY4DInULzBIPaLv/\nviMsgDdUsmzESifzzgWe7b+M6UAeEAkfO0q8RuVV+r4X3hIPuYMAq0sXTAz02vRa\necyvRMCtO8e99q+gF2x0hPCNHOTXijzOfWQk0nZB11m/2tbSXMkXVYIuhxlzYpdO\nylzw7v6Oc1m2Es3uDW1zx0WRRytsZc699KJXY2xd4jiBPQSV/DYET90V0c0JA3yi\nfDsEib3b1ZcgvTPanuFhakY5+DPau5nH8Y813PwBfk0LpM+qUno=\n=Hqdz\n-----END PGP SIGNATURE-----", + "payload": "tree 52cdc221c9bd96052c372eb797b6e4660b16c20f\nparent 2aafbdf7718d1f25e1c69d738183db46aec09d12\nauthor eps1lon 1608047069 +0100\ncommitter eps1lon 1608047069 +0100\n\nMatch UX of Search\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/64ad2e25324016e94167d6d1d29f07e362fe221b", + "html_url": "https://github.com/facebook/react/commit/64ad2e25324016e94167d6d1d29f07e362fe221b", + "comments_url": "https://api.github.com/repos/facebook/react/commits/64ad2e25324016e94167d6d1d29f07e362fe221b/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "2aafbdf7718d1f25e1c69d738183db46aec09d12", + "url": "https://api.github.com/repos/facebook/react/commits/2aafbdf7718d1f25e1c69d738183db46aec09d12", + "html_url": "https://github.com/facebook/react/commit/2aafbdf7718d1f25e1c69d738183db46aec09d12" + } + ] + }, + { + "sha": "826993e4badfde5e687978798c286c44e7b7b45d", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjgyNjk5M2U0YmFkZmRlNWU2ODc5Nzg3OThjMjg2YzQ0ZTdiN2I0NWQ=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T15:48:24Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T15:48:24Z" + }, + "message": "Resolve todo about args copying", + "tree": { + "sha": "63a75c0b39b52f884be09989e2d6b9275838cf1a", + "url": "https://api.github.com/repos/facebook/react/git/trees/63a75c0b39b52f884be09989e2d6b9275838cf1a" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/826993e4badfde5e687978798c286c44e7b7b45d", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Y2tAACgkQWCcTbp05\ndTJK3g//W3CH0gs+ZWavsXv1J/Tt1bVuO9HQJ8rGNwCx+yN4zL0Jb22GM1wR3IOG\nZWA9uIlD6rIEWEuoNc/d4tft7kY9vGAn57xkIXaUcvcV7mUABxWYtTPPhRI7UVti\nThch5gu1OBFTB1ST7vtce3C03xw+wik/lto42baMGlSGzcRXqAFWvaSp/CtBznlU\nRtZOwuUHe6CMHUF/aU0+8YYrhmQcspB1qCAXwuwG+2wqtvbX2cO/uUzl85AAaIH9\niuXweHFIvlGKpJ+XUROf5SgdTAXiEyTdA2KPZCXldY9x5ONzJyG/8x5fw4MaMMk4\nE1gx5OqMmCMHh63LSCFoBb9TCSCwj8Qf/DKg39B2qu2BZPGjaDx1ZEXqi5dngWc1\n9VELKmceKmsTJR4kOIFYOUuRCKOdJb27kIw9AS63XuQYMSHNfCV2ZCViI4Qv4kvH\n647CkI5wfOTvbmXGTDZ0padwxJepbf0g2bMNdZATIcXOonGsqfynKXdurxAL9U+o\nLZqzUtfNZDjEpdWKZKMXc8K2JlteShdUHqbWwnVV9L/1Qz8nAjbaP0qtuCDa/VWQ\nC/PGnNTNSM5da0+Zy6ueQNkKCNp2/32f7CfhwvGad3/ULbSEzrbMqjSNumI6KlI8\nIr7IcZ1UBTeqtBPxwXUhCxw+DNKCgjatIx/Cpymn0tyKbTRcfRQ=\n=V8rz\n-----END PGP SIGNATURE-----", + "payload": "tree 63a75c0b39b52f884be09989e2d6b9275838cf1a\nparent 64ad2e25324016e94167d6d1d29f07e362fe221b\nauthor eps1lon 1608047304 +0100\ncommitter eps1lon 1608047304 +0100\n\nResolve todo about args copying\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/826993e4badfde5e687978798c286c44e7b7b45d", + "html_url": "https://github.com/facebook/react/commit/826993e4badfde5e687978798c286c44e7b7b45d", + "comments_url": "https://api.github.com/repos/facebook/react/commits/826993e4badfde5e687978798c286c44e7b7b45d/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "64ad2e25324016e94167d6d1d29f07e362fe221b", + "url": "https://api.github.com/repos/facebook/react/commits/64ad2e25324016e94167d6d1d29f07e362fe221b", + "html_url": "https://github.com/facebook/react/commit/64ad2e25324016e94167d6d1d29f07e362fe221b" + } + ] + }, + { + "sha": "eb9c4ec42dbe937b5dd93db06e5c74a41838763d", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmViOWM0ZWM0MmRiZTkzN2I1ZGQ5M2RiMDZlNWM3NGE0MTgzODc2M2Q=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T16:28:02Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T16:28:02Z" + }, + "message": "Remove excess vertical ruler if no errors are recognized", + "tree": { + "sha": "6d7cb67b555f98bfaf87efb7456c452880d8ec4c", + "url": "https://api.github.com/repos/facebook/react/git/trees/6d7cb67b555f98bfaf87efb7456c452880d8ec4c" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/eb9c4ec42dbe937b5dd93db06e5c74a41838763d", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Y5BIACgkQWCcTbp05\ndTKj1Q/7BUmr707T7dngikOz9wLtXw4pxZN+jmQD9UXAkDsrB+zaFirL62wgEXd+\n8Kh0SaA9XXzKi3nMqR6De0SuobiAgfQ/DTfF0gYbJ3Gx1djKqDAZBB90WNf+bjw4\nd5RuM3IBoJKrjywGUKRNLc7kVGhJ2UoGE7DkRIJX32ZWJcQ+q/6Cjoia4Tb2Ga39\n42eMcu7qwnxg1zkqZTLiIEfnesTxX6Z7gM9fSZEnLMKq8TWVGrjPBOxLNrUAbSmV\nAGiArudf+1Q1rDQnFZMnzGMNK/uBmgw6pbY/BdMgVTBfAxfTgsdp4OUHcQ+3wwS8\naO2JAID8l2mfOvFOEWgjGwAOV47F+62bwZIIyI6EWM/g23cT4zb7FnsA81siVoNx\n42+K12qs6sXkId1q6+oAxhHKhRY+ovbgEA0urKRrqp6tlyH5H8BG1QqqrYPfg77r\nJsWYJkOnu4GaeyK+iDKtBerH6tXW4oP7Qx7VvsQ0zf0XceeUUOBkstOtCBjGJGQP\nF5SBsJ/UOzb8P1Qt3zrrhvb1gu3BeK2m1G27GwGlgT1owgQALqqfkM6inqM4Pq3P\nRz0jyJkKnza3ehUFIa0+ewNvBI9iBMulPsB7g1MdBCzxTl6mmiS3C0TnuZ+eTS3r\n1qx0LSPPPWLZ8eLq9VxLP0tR+9b8NVIMAB3oTSE/UBhJYjGrZnk=\n=p40H\n-----END PGP SIGNATURE-----", + "payload": "tree 6d7cb67b555f98bfaf87efb7456c452880d8ec4c\nparent 826993e4badfde5e687978798c286c44e7b7b45d\nauthor eps1lon 1608049682 +0100\ncommitter eps1lon 1608049682 +0100\n\nRemove excess vertical ruler if no errors are recognized\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/eb9c4ec42dbe937b5dd93db06e5c74a41838763d", + "html_url": "https://github.com/facebook/react/commit/eb9c4ec42dbe937b5dd93db06e5c74a41838763d", + "comments_url": "https://api.github.com/repos/facebook/react/commits/eb9c4ec42dbe937b5dd93db06e5c74a41838763d/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "826993e4badfde5e687978798c286c44e7b7b45d", + "url": "https://api.github.com/repos/facebook/react/commits/826993e4badfde5e687978798c286c44e7b7b45d", + "html_url": "https://github.com/facebook/react/commit/826993e4badfde5e687978798c286c44e7b7b45d" + } + ] + }, + { + "sha": "f0a3ab3e79b711c52393513609bbaae80e694d0b", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmYwYTNhYjNlNzliNzExYzUyMzkzNTEzNjA5YmJhYWU4MGU2OTRkMGI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T16:33:54Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T16:33:54Z" + }, + "message": "Fix icon alignment of errors/warnings in tree", + "tree": { + "sha": "8b0f52eb35480be9c9d4563f4e0fa4b3ae1b99ca", + "url": "https://api.github.com/repos/facebook/react/git/trees/8b0f52eb35480be9c9d4563f4e0fa4b3ae1b99ca" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/f0a3ab3e79b711c52393513609bbaae80e694d0b", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Y5XIACgkQWCcTbp05\ndTJjbQ/9EPdScIwYoLr7w7x+TyoYEKM+Tih70wb+oDBqUaPkumwrrbnd29IlZhKp\nooTQzR7b85mlROzCF0cXrvor6D/Hn73EHGibhua/sIQ4EXcGbqU3n9zaZj2wUmJA\nab/erpUfUYvnaZhfaRzNx0lB6DFxdD9paqiSMOgTFS34YXdfdKouRHZteFxuKLj6\nBuN7ujpCgdozmtczMKqnxR+KTbm8U2APYdeo2IULcsKQCr/6N5p49Ds3Q/0dX2H5\nu2JephNzEaz7v+HKglVLy/fvgXHOGSXp5t8gVRX6OtiYoiTIu7o/nTxnKsU0j0JT\nFJJoo+i6+AGlG5yMm7ar4O+pCDyOdAq2lu+a5iTA3cq9W6lq9vQWLfJjge3qaG26\nWOsv1Uha5lWovBDDNeJxXKcIp1VHlfYZ4kmW6MGdlpDaFSKjOml7hwA5TmCfCNHN\nXBv6h1D/wUd3+P/ZklrytRL8hogVqkOrc1fIQvhEsA2xgV/jsMpoAFK5nbkWv45Z\neguYuWcDcyMcm3lUklf/+IvdI2tP8PJhddcASqVyUydK9uPEzdO1N6Txn+PWqgp5\nsCyPFEVNcPSB33VuePyopYWt43vr9Fyfhp74rBtsq6FfVaQluxmP53qZeFWvAxRk\nZ6f9Uw3JDUsdfD5JV46H+xQEwHa/Bb3uUsQc+6WaB4io6cQMv+Y=\n=Ibdw\n-----END PGP SIGNATURE-----", + "payload": "tree 8b0f52eb35480be9c9d4563f4e0fa4b3ae1b99ca\nparent eb9c4ec42dbe937b5dd93db06e5c74a41838763d\nauthor eps1lon 1608050034 +0100\ncommitter eps1lon 1608050034 +0100\n\nFix icon alignment of errors/warnings in tree\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/f0a3ab3e79b711c52393513609bbaae80e694d0b", + "html_url": "https://github.com/facebook/react/commit/f0a3ab3e79b711c52393513609bbaae80e694d0b", + "comments_url": "https://api.github.com/repos/facebook/react/commits/f0a3ab3e79b711c52393513609bbaae80e694d0b/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "eb9c4ec42dbe937b5dd93db06e5c74a41838763d", + "url": "https://api.github.com/repos/facebook/react/commits/eb9c4ec42dbe937b5dd93db06e5c74a41838763d", + "html_url": "https://github.com/facebook/react/commit/eb9c4ec42dbe937b5dd93db06e5c74a41838763d" + } + ] + }, + { + "sha": "5b59bbce5d8055fbd7a616cfa64a4a03c685d21f", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjViNTliYmNlNWQ4MDU1ZmJkN2E2MTZjZmE2NGE0YTAzYzY4NWQyMWY=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T16:38:22Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T16:38:22Z" + }, + "message": "Fix warning text color in dark theme", + "tree": { + "sha": "c1b92ab905a0b3c3b89b0b235aa362844942e3ca", + "url": "https://api.github.com/repos/facebook/react/git/trees/c1b92ab905a0b3c3b89b0b235aa362844942e3ca" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/5b59bbce5d8055fbd7a616cfa64a4a03c685d21f", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Y5n4ACgkQWCcTbp05\ndTJFvw//e8DWTyCOl+Fd4RgWJeG195PkfM/69uzUNLb6zTEs2B3V5ysh9F1S+ukl\nS23vSCoJJvVO1Jik6ovubC89pD+UQHvqIC+hCsdqOLpkoME0b+YiAohuAzHQ00Me\n3/WxSRhm6NzbUbZzjdTMVPMKM7Gah74H+vWF/mlo40LJj1yyXMnOq1HXIsDE20q/\nn9i7qfp8n7DVCIL545NLrlTPi0d8fKOuDpA6r50fzM5Bu38s8eaHJeWzE2FvLBPi\nOmL+ozyJ4Ok+EcTlNaU2qRh6nWd3gi10QNX2LcN1wKaPldRZ1sYti6wwqYhj6bfZ\nCpnmkyfaX+gvu9O+ZI5Faq9tebyl0gkyp3NFbMBWMC+a2IcWPaVQd69uV3NPm23h\n1Y+QLdhX446AA6SMzGvucIeIzti8Wvv5CrAm0w7ahDUmXX8AAWYf4EDsZmM/IvAu\nEiB7ux8T1kLlp+wb/DZ2txOOOnZFOADQ3a/sDqnrvpBdHw6vXJ5aOXCeUxtGble3\n+RnD7eU31+55w+zsBavIF0E8fzAnGavOo4tUI1QaV5U4jc8OB/IlTa2YLSZtPhh4\nJyEF2wzsJKLkEqRZCueqXu/iyXW0kYRDIBs4JFpecM0DX1Ctkug/E+xGk7bf7wrk\ni2ywhbPVPBju5sQAIDCMMMG4PAFeqLpU3Z6NfBG92Hyp2JTG5DM=\n=jfgO\n-----END PGP SIGNATURE-----", + "payload": "tree c1b92ab905a0b3c3b89b0b235aa362844942e3ca\nparent f0a3ab3e79b711c52393513609bbaae80e694d0b\nauthor eps1lon 1608050302 +0100\ncommitter eps1lon 1608050302 +0100\n\nFix warning text color in dark theme\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/5b59bbce5d8055fbd7a616cfa64a4a03c685d21f", + "html_url": "https://github.com/facebook/react/commit/5b59bbce5d8055fbd7a616cfa64a4a03c685d21f", + "comments_url": "https://api.github.com/repos/facebook/react/commits/5b59bbce5d8055fbd7a616cfa64a4a03c685d21f/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "f0a3ab3e79b711c52393513609bbaae80e694d0b", + "url": "https://api.github.com/repos/facebook/react/commits/f0a3ab3e79b711c52393513609bbaae80e694d0b", + "html_url": "https://github.com/facebook/react/commit/f0a3ab3e79b711c52393513609bbaae80e694d0b" + } + ] + }, + { + "sha": "0ef80bce241c622a21d5da20b527822602599f36", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjBlZjgwYmNlMjQxYzYyMmEyMWQ1ZGEyMGI1Mjc4MjI2MDI1OTlmMzY=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T16:43:34Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T16:43:34Z" + }, + "message": "center icons with text instead of baseline", + "tree": { + "sha": "ca245927bedde6be191b2a88d2c43abd8e5726b0", + "url": "https://api.github.com/repos/facebook/react/git/trees/ca245927bedde6be191b2a88d2c43abd8e5726b0" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/0ef80bce241c622a21d5da20b527822602599f36", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Y57YACgkQWCcTbp05\ndTK/wQ//VvBmc5KzaqD9aEu62qlVXlWn7ew7ru4IswtIOBl1dedCCN6YNnQ5p0mY\naowLMxctoMWzUKt+Powy1tMksHanZvASW0/g+5n3arDP5l/Hj/+9LcTQoEz3Hx66\n68GodLiyvs/LrIhFHYnF9h3XkQkylVjwjxgshNWQI1muIdoPhSJvnyb3yxMpC2mm\no2WCdyW748HwuxSnjzw77S7PdctUoEG+TewTdndgVG120PHCFfuJjL8tEppZfy1w\n6wr6KQ3hISg6sh+CtbOTuolJARwgQzsadQnOu9r2yz/w/l3I8Yp+NylPycVp0FT2\nIENrCNo940g5VTxZshwIDho+HXhzhFZimuTGxY2WEmnLseCGDoZYRsadvQbaQEwW\ngODxyP0p6jOVWnt6aL5Jri7yFBajbFnV0ICOmnP8nLIwBjQABctf7CtKbatSNpWN\nCY3HZfAx2JpXLNZ+H0QhjN4An2ctp4aC/Mr2NGakLU1GYpup3c//ubhSv/xVSNuQ\nDbs0hnyyMVVhf6kPza2UJSPZrQbAESujTb616XsaNsih0TCch8Y3HWUpE0+TFbgw\nC7eZ2JRxc1nwzWLRmX9qC9XYdbXcrcPT080bwQvTaappPjLyUfcx/pJydqXwPSpS\ntsZVhmVs4InghhBOC7/l1kYJAgjCsjUjk00HQNY1mvwBm0k5tLE=\n=cAms\n-----END PGP SIGNATURE-----", + "payload": "tree ca245927bedde6be191b2a88d2c43abd8e5726b0\nparent 5b59bbce5d8055fbd7a616cfa64a4a03c685d21f\nauthor eps1lon 1608050614 +0100\ncommitter eps1lon 1608050614 +0100\n\ncenter icons with text instead of baseline\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/0ef80bce241c622a21d5da20b527822602599f36", + "html_url": "https://github.com/facebook/react/commit/0ef80bce241c622a21d5da20b527822602599f36", + "comments_url": "https://api.github.com/repos/facebook/react/commits/0ef80bce241c622a21d5da20b527822602599f36/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "5b59bbce5d8055fbd7a616cfa64a4a03c685d21f", + "url": "https://api.github.com/repos/facebook/react/commits/5b59bbce5d8055fbd7a616cfa64a4a03c685d21f", + "html_url": "https://github.com/facebook/react/commit/5b59bbce5d8055fbd7a616cfa64a4a03c685d21f" + } + ] + }, + { + "sha": "61924778be0a4934e02e6c214ea3c676868f95de", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjYxOTI0Nzc4YmUwYTQ5MzRlMDJlNmMyMTRlYTNjNjc2ODY4Zjk1ZGU=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T17:29:00Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-15T17:29:00Z" + }, + "message": "Clear errorsAndWarnings when elements are removed", + "tree": { + "sha": "5138be93763993cd4932b9a74c9d00d65483d4ce", + "url": "https://api.github.com/repos/facebook/react/git/trees/5138be93763993cd4932b9a74c9d00d65483d4ce" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/61924778be0a4934e02e6c214ea3c676868f95de", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Y8lwACgkQWCcTbp05\ndTJCuA//TvBpO/dJ+/NQkIhL0UpeNdgsxogkEOgxHtAN8kqt/OtPnFbqG8ClChyM\ns/1uqTTtBHuStbrcUsTUuBEjNZkCeyifrSzRbkDLkFtdGYVAO6u0xFDm3fAk9bGs\nxZQzzSKMselwlHMljsmvWQDnz3AYuQyx553dgzNCLQAVrPsoL3fth7Wso2qLkI70\n3fcgdzRLvmczlXKBgUDyrspsX0GIxlYR+B42LmTWLSjqaBoa3KZXtIVHu488K0w6\nrY0XuSLno68ZZfERPeLE+66dY3SIfWYv8LtMRANPYE3l+UT0gkrm2GhtymRQX60j\neNpD9CK999oNiBKja7yHVc02xA07MdDCaqzGCdyEwGBur7L4Z/gdgNz95fOoIwGc\nYk+j+MTiU5NSYYbg6yb07X4ebHr1eELgVdZHRlUPUvNfK3i1QhoVYCSbRKhXNyzU\nW+mm/7cRyPIPJ0hEag0MlbqQIHNHZSs3rcODJzpm9EzdqKzO4w+uqak3/N0Gw71g\nVoToisTTVrP2Dorn8U4F6KKrgo+XZU19gPINqnZ2SSHlbeG0eliSLDIYN+B/80kB\nnnmQGL+5oHnZqc3ec1QCOe+srK8YL+DuTeiiYn8gArvVeNgq3KuuLFPRSHkltdAa\nPT8r7rVyVmCaRT80RvSpsoZDbUkdWoTha2+P1+LjOrWLZySZLMo=\n=aj/8\n-----END PGP SIGNATURE-----", + "payload": "tree 5138be93763993cd4932b9a74c9d00d65483d4ce\nparent 0ef80bce241c622a21d5da20b527822602599f36\nauthor eps1lon 1608053340 +0100\ncommitter eps1lon 1608053340 +0100\n\nClear errorsAndWarnings when elements are removed\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/61924778be0a4934e02e6c214ea3c676868f95de", + "html_url": "https://github.com/facebook/react/commit/61924778be0a4934e02e6c214ea3c676868f95de", + "comments_url": "https://api.github.com/repos/facebook/react/commits/61924778be0a4934e02e6c214ea3c676868f95de/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "0ef80bce241c622a21d5da20b527822602599f36", + "url": "https://api.github.com/repos/facebook/react/commits/0ef80bce241c622a21d5da20b527822602599f36", + "html_url": "https://github.com/facebook/react/commit/0ef80bce241c622a21d5da20b527822602599f36" + } + ] + }, + { + "sha": "6e57b4d1798f031a844fd960038cacf29dc0b534", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjZlNTdiNGQxNzk4ZjAzMWE4NDRmZDk2MDAzOGNhY2YyOWRjMGI1MzQ=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-15T18:24:41Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-15T18:24:41Z" + }, + "message": "Tweaked inline element style for warning/error icons", + "tree": { + "sha": "219cb4c1ffada21259876d390df1a85767481617", + "url": "https://api.github.com/repos/facebook/react/git/trees/219cb4c1ffada21259876d390df1a85767481617" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/6e57b4d1798f031a844fd960038cacf29dc0b534", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/6e57b4d1798f031a844fd960038cacf29dc0b534", + "html_url": "https://github.com/facebook/react/commit/6e57b4d1798f031a844fd960038cacf29dc0b534", + "comments_url": "https://api.github.com/repos/facebook/react/commits/6e57b4d1798f031a844fd960038cacf29dc0b534/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "61924778be0a4934e02e6c214ea3c676868f95de", + "url": "https://api.github.com/repos/facebook/react/commits/61924778be0a4934e02e6c214ea3c676868f95de", + "html_url": "https://github.com/facebook/react/commit/61924778be0a4934e02e6c214ea3c676868f95de" + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/pull_request_commits_page2.json b/src/test/resources/com/spotify/github/v3/clients/pull_request_commits_page2.json new file mode 100644 index 00000000..1db28543 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/pull_request_commits_page2.json @@ -0,0 +1,1787 @@ +[ + { + "sha": "2df0740ec43c3d3ea6cb93dfc2808398a3d06c1e", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjJkZjA3NDBlYzQzYzNkM2VhNmNiOTNkZmMyODA4Mzk4YTNkMDZjMWU=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-15T20:20:16Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-15T20:20:16Z" + }, + "message": "Fixed minor regression in Element styles", + "tree": { + "sha": "e7862387c55b068011874af57595c12b4a21dc16", + "url": "https://api.github.com/repos/facebook/react/git/trees/e7862387c55b068011874af57595c12b4a21dc16" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/2df0740ec43c3d3ea6cb93dfc2808398a3d06c1e", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/2df0740ec43c3d3ea6cb93dfc2808398a3d06c1e", + "html_url": "https://github.com/facebook/react/commit/2df0740ec43c3d3ea6cb93dfc2808398a3d06c1e", + "comments_url": "https://api.github.com/repos/facebook/react/commits/2df0740ec43c3d3ea6cb93dfc2808398a3d06c1e/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "6e57b4d1798f031a844fd960038cacf29dc0b534", + "url": "https://api.github.com/repos/facebook/react/commits/6e57b4d1798f031a844fd960038cacf29dc0b534", + "html_url": "https://github.com/facebook/react/commit/6e57b4d1798f031a844fd960038cacf29dc0b534" + } + ] + }, + { + "sha": "52f0494e44d7c01f6f1c53f3c5dce83e07d27aa6", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjUyZjA0OTRlNDRkN2MwMWY2ZjFjNTNmM2M1ZGNlODNlMDdkMjdhYTY=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-15T20:20:26Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-15T20:20:26Z" + }, + "message": "Split errors and warnings into separate panels", + "tree": { + "sha": "1ff469cd73189d817cb4b60faecce7aac0fd64ad", + "url": "https://api.github.com/repos/facebook/react/git/trees/1ff469cd73189d817cb4b60faecce7aac0fd64ad" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/52f0494e44d7c01f6f1c53f3c5dce83e07d27aa6", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/52f0494e44d7c01f6f1c53f3c5dce83e07d27aa6", + "html_url": "https://github.com/facebook/react/commit/52f0494e44d7c01f6f1c53f3c5dce83e07d27aa6", + "comments_url": "https://api.github.com/repos/facebook/react/commits/52f0494e44d7c01f6f1c53f3c5dce83e07d27aa6/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "2df0740ec43c3d3ea6cb93dfc2808398a3d06c1e", + "url": "https://api.github.com/repos/facebook/react/commits/2df0740ec43c3d3ea6cb93dfc2808398a3d06c1e", + "html_url": "https://github.com/facebook/react/commit/2df0740ec43c3d3ea6cb93dfc2808398a3d06c1e" + } + ] + }, + { + "sha": "d64023c01cc983b34cf50617c8c9cc6d76ab7311", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmQ2NDAyM2MwMWNjOTgzYjM0Y2Y1MDYxN2M4YzljYzZkNzZhYjczMTE=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T07:03:50Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T08:49:16Z" + }, + "message": "Implement new message format\n\nWhen flushing messages we use a new type of operation instead that only contains the number of warnings/errors.\nThe messages itself are only sent as part of the inspectElement event.\nAlready implemented a mapping from the message to a unique id.\nThough I'm not quite sure why we wanted to do that.\nDid we want to send the message only once and then ids if it repeats?", + "tree": { + "sha": "5d11999c088b1514c751acdd60d362394b05dce9", + "url": "https://api.github.com/repos/facebook/react/git/trees/5d11999c088b1514c751acdd60d362394b05dce9" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/d64023c01cc983b34cf50617c8c9cc6d76ab7311", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/ZyhAACgkQWCcTbp05\ndTKtkw//aN7ZYyw8dLTX9JwE0ZbVxeToE0jRmn338ko2+h1P/WAZoztmnuELEoae\nI/STR32YFc4w8xkl2cd1sNRIKnCestfn9KH6Uj787UhrD23IrtFFnpf2EVnr7qPs\nf92akXGTCIuVBAMIFgMypQvAbF4E+fZ71MWS5K/xPMTLyUCcsE5clFKIKRofXoe2\nXPmEuz21fCdr7IPbFx65h9EMDDCM8H/rXYEN8fpFCMgnKmQUz95dF4cgvDqcpTX7\nOLYTMVlhoCP78+CXVC1JlASK4dZs3cq2bvqGL6lTDEncG7s8PHz7xU5ivg/DveHI\nhMeQgnz5re7W6fgDPexoJ5EnUASN0k5AZZla2XkTN/BL/+XHwpWw/GtlftwskaWJ\nT61ul0TIXtCwTW8Hodzeo2iOdvUXygs7ooPMkantnXJi8C/rmhn83pIUd1cryoyU\nECgsirEAbweJUwY0w5RWza+84I5MGwKFuceRII0/iE7t+eR/LZajBtrM9LgZ9RyC\nJBqqUhgTNVfq4Uw8SP4SqR/tk5DpcZZoEF5oWsnf6TLHejbg0TUOTtsf0DrqifDM\nwtbw15N7og/7a3NoJ0EYwJLziBiOzSWnrGd2EI4785dRAJQ2vnjVZE4wQrLBkXpN\nL0x0HknGffHXRlBmJbwz7ctvzAny5cECkr7HMroK+PXkfTe9PLA=\n=3VlL\n-----END PGP SIGNATURE-----", + "payload": "tree 5d11999c088b1514c751acdd60d362394b05dce9\nparent 52f0494e44d7c01f6f1c53f3c5dce83e07d27aa6\nauthor eps1lon 1608102230 +0100\ncommitter eps1lon 1608108556 +0100\n\nImplement new message format\n\nWhen flushing messages we use a new type of operation instead that only contains the number of warnings/errors.\nThe messages itself are only sent as part of the inspectElement event.\nAlready implemented a mapping from the message to a unique id.\nThough I'm not quite sure why we wanted to do that.\nDid we want to send the message only once and then ids if it repeats?\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/d64023c01cc983b34cf50617c8c9cc6d76ab7311", + "html_url": "https://github.com/facebook/react/commit/d64023c01cc983b34cf50617c8c9cc6d76ab7311", + "comments_url": "https://api.github.com/repos/facebook/react/commits/d64023c01cc983b34cf50617c8c9cc6d76ab7311/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "52f0494e44d7c01f6f1c53f3c5dce83e07d27aa6", + "url": "https://api.github.com/repos/facebook/react/commits/52f0494e44d7c01f6f1c53f3c5dce83e07d27aa6", + "html_url": "https://github.com/facebook/react/commit/52f0494e44d7c01f6f1c53f3c5dce83e07d27aa6" + } + ] + }, + { + "sha": "1bb99d9cc10270abb6136d6b1695b3e965967e5f", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjFiYjk5ZDljYzEwMjcwYWJiNjEzNmQ2YjE2OTViM2U5NjU5NjdlNWY=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T07:22:29Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T08:49:20Z" + }, + "message": "Reflect error/warning updates in tree when cleared", + "tree": { + "sha": "e520ca7ee748280f641ea1cac84a505321c30acd", + "url": "https://api.github.com/repos/facebook/react/git/trees/e520ca7ee748280f641ea1cac84a505321c30acd" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/1bb99d9cc10270abb6136d6b1695b3e965967e5f", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/ZyhAACgkQWCcTbp05\ndTIjmQ//Ub/9mVc0egLIGlZxHFtVPJXLsOQrzTiK1KpOewwe03oRCN/FQeQKz5Ds\nZGxy8Z8ztlVWizSEyNF2Ix5vJR1PCNKl0k3HS0Mzuhl5AYOURR97xzaawtrFqJ9n\nfkIi3emDTaOWd/iaiwr+dAU646vSpzaL/2KJyhA772FhGj3Q+eNER+qXMlO3NA1V\nqLBld4tDeYB5eBXnlXxIdxZtNVyqYTzBWWzXcIp9VWNSjYAbmgsQLKlsHNb7cF2p\nrdQoiUE8eMtwtMadu++zw4AteWO/J7azizQEFBHsFvp7Qi83+Keok8tWOAwPKAzA\nzRMhCtua7fxTDCMYii1zzyT+Vn1EMOD51RE/ZtnazHr8t/UKQkUVZnN0sejGA/Bf\nRTPcVHtNuPCx7uA296VQ4W7zK15z8y3ddfwwvFgLWg/x8VWK9OHs+n1oMjLyzvPn\n/Hny++5cJlYpAXKv0qemHg5WILeYtnv9CE5c+yFMDk6PUkLsAEYL3y3uhOp2O+4l\nPL1gskMONzVqwJD85hXqda+MHRTN/S15a0oIN4YboN/ZM9u0oAafcqVqK/48f/kt\nh7KJXVS7JF0gRXrDZ/wBt9FwdrXotewbD39PU/ZGynmay+NKX1RGNpuFnzyb5GFc\nAVFbQhim8N1+hYd6akkTqOUt7ro9fjQ2vAFxz2W5bzmB6OqjBq8=\n=ezct\n-----END PGP SIGNATURE-----", + "payload": "tree e520ca7ee748280f641ea1cac84a505321c30acd\nparent d64023c01cc983b34cf50617c8c9cc6d76ab7311\nauthor eps1lon 1608103349 +0100\ncommitter eps1lon 1608108560 +0100\n\nReflect error/warning updates in tree when cleared\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/1bb99d9cc10270abb6136d6b1695b3e965967e5f", + "html_url": "https://github.com/facebook/react/commit/1bb99d9cc10270abb6136d6b1695b3e965967e5f", + "comments_url": "https://api.github.com/repos/facebook/react/commits/1bb99d9cc10270abb6136d6b1695b3e965967e5f/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "d64023c01cc983b34cf50617c8c9cc6d76ab7311", + "url": "https://api.github.com/repos/facebook/react/commits/d64023c01cc983b34cf50617c8c9cc6d76ab7311", + "html_url": "https://github.com/facebook/react/commit/d64023c01cc983b34cf50617c8c9cc6d76ab7311" + } + ] + }, + { + "sha": "c197030fe4a3299e0e9c6fa0b6374dff1cabe2fe", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmMxOTcwMzBmZTRhMzI5OWUwZTljNmZhMGI2Mzc0ZGZmMWNhYmUyZmU=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T08:43:19Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T08:49:20Z" + }, + "message": "Fix inspectedElement not updating on error/warnings update", + "tree": { + "sha": "96b7b2c6935b35a748e9fcdfda68c69f14c2c72d", + "url": "https://api.github.com/repos/facebook/react/git/trees/96b7b2c6935b35a748e9fcdfda68c69f14c2c72d" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/c197030fe4a3299e0e9c6fa0b6374dff1cabe2fe", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/ZyhAACgkQWCcTbp05\ndTJCpw//Y/w8YH3ccCIScyvJhunzc81mj9Gv3tsuDCO7d2q2tJMzpNvRmqZsdWjU\n5QE4lQfwjOBFXhznEwcgt8oVHIj4cwSr/+0imu1TpuNvWOzggf9zonFuS/wTaGkz\nKN7sgZgArwes8NtsmlbnJ3Yr2DUkfeysrswLhDk6YTDjwMBAtcM9iqsK19ZXat0G\n1XKG8Ks7Zk4oMME7uNlY7gn3z46FmnXWwEiMrLiOKnCDLaVgKBA5y0kSd3UEE+Qj\nNmfEohYq9SJTE3fCbKDgqoFGv0eCtYfSoxcmlts3N7KHB9gkxjWQbTQ+2EHdRUow\n2gjFSDukKeTC2BnowiOMjkWcM9ypBobtl3XJUcVqfl1KNNKY6p8VbKdBAvTMZS4/\nYqRBYsnfIv4f2CrguJv9ONcw+bCqgYYxHvE9g0cpVm5NPEpxSEkPN2SoG9bJPh9E\nnRfZ2pUzkzguXLpjOof5hCImw5R4tbdDFbgXqghWszMKG/F/rMO4Lwmt0fmfW8VW\nxmKkbvor/H02ImhHZSTwU9PLp+KpH92B1AOMa68DZbdp9UACj5EnmyudRJDwEa4V\nG/4WH0EShkxDYoJNSJiYY/9i5aE8ZOQz2mNPGUm/wj23OoCnim1Tt3uzWWouJDj3\nBJrGK3k3KQb+eCxoCO+QehpUGVeO3ApQFd5xG+TawgZij7kZWNA=\n=cIY7\n-----END PGP SIGNATURE-----", + "payload": "tree 96b7b2c6935b35a748e9fcdfda68c69f14c2c72d\nparent 1bb99d9cc10270abb6136d6b1695b3e965967e5f\nauthor eps1lon 1608108199 +0100\ncommitter eps1lon 1608108560 +0100\n\nFix inspectedElement not updating on error/warnings update\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/c197030fe4a3299e0e9c6fa0b6374dff1cabe2fe", + "html_url": "https://github.com/facebook/react/commit/c197030fe4a3299e0e9c6fa0b6374dff1cabe2fe", + "comments_url": "https://api.github.com/repos/facebook/react/commits/c197030fe4a3299e0e9c6fa0b6374dff1cabe2fe/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "1bb99d9cc10270abb6136d6b1695b3e965967e5f", + "url": "https://api.github.com/repos/facebook/react/commits/1bb99d9cc10270abb6136d6b1695b3e965967e5f", + "html_url": "https://github.com/facebook/react/commit/1bb99d9cc10270abb6136d6b1695b3e965967e5f" + } + ] + }, + { + "sha": "2a3423f946a958590950ec426d2bc3cc894c0018", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjJhMzQyM2Y5NDZhOTU4NTkwOTUwZWM0MjZkMmJjM2NjODk0YzAwMTg=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T08:55:45Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T08:55:45Z" + }, + "message": "fix minor flow issues", + "tree": { + "sha": "72e50d7d7bde03ef6c159bc9b0ca64187b2c63ff", + "url": "https://api.github.com/repos/facebook/react/git/trees/72e50d7d7bde03ef6c159bc9b0ca64187b2c63ff" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/2a3423f946a958590950ec426d2bc3cc894c0018", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Zy5EACgkQWCcTbp05\ndTJzLhAAlkcQcp6hnzKXEoe1w+Mzneu7DWqO5Y7QX8f/e27+2xa6wsxq3fpZqApb\nlsgpoNbqM3Gt3JWIkAyOls7cMTsOa1F3FRlSZnwJCpWM902Ld+QhMSX24MTGJWla\nNcGO2B/BsezVlGS2PH++G2X64kThfV2a7myjvKYVbqrkLRB/1gY2YhaT7LdOFpkA\nJ73NAOBijvLOrcY2fUAMx2gKq5lbEK+WZdJAp7tnI4ND4XuR0fr+WIS8XyAwu/mP\nMn3VV97qh1+Wz+Wh8Q1cjzKUuhkc/mZQZbsSwLbnrstKlT+gyLwHIcEjlyU8nDER\nAMMJOB4I3Tq1xFUq1JqBapz4vqd9SSkngCwxMrk+CSihe90IIzC/YyLU1nGKYtdO\njFhYiquYKhXxdpBOGDLk6kMk7zUdvj/IC1iHh4IhNrBKnBrv2EDdis0XPumLOoeV\nFqI/xZsJZLika6SmZg2VgQvpF9JiHrKgiycjA/hQT25XK9juQkfKBs7Dc7/Olzt3\n2p9revQi9u3Pbv6Hqufhf0I9J1wW5YI3Q+Pqp+bebEE7xtDybFKOBCIxXflg0DYD\nx7s4HYQgWZAqxL/7IMAn/SMvW3b3Oy5FaqD24oLml/2kjmR1vNjHDDC4vWiDPV/M\n79FQrJV9IrWhpOp0XBaM+g5ZTVMpwzuTqDvRt5zi2ufKX+e8S5w=\n=dcfm\n-----END PGP SIGNATURE-----", + "payload": "tree 72e50d7d7bde03ef6c159bc9b0ca64187b2c63ff\nparent c197030fe4a3299e0e9c6fa0b6374dff1cabe2fe\nauthor eps1lon 1608108945 +0100\ncommitter eps1lon 1608108945 +0100\n\nfix minor flow issues\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/2a3423f946a958590950ec426d2bc3cc894c0018", + "html_url": "https://github.com/facebook/react/commit/2a3423f946a958590950ec426d2bc3cc894c0018", + "comments_url": "https://api.github.com/repos/facebook/react/commits/2a3423f946a958590950ec426d2bc3cc894c0018/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "c197030fe4a3299e0e9c6fa0b6374dff1cabe2fe", + "url": "https://api.github.com/repos/facebook/react/commits/c197030fe4a3299e0e9c6fa0b6374dff1cabe2fe", + "html_url": "https://github.com/facebook/react/commit/c197030fe4a3299e0e9c6fa0b6374dff1cabe2fe" + } + ] + }, + { + "sha": "9df59c6051258d332f4c35925c0da1dc9fa8ff4c", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjlkZjU5YzYwNTEyNThkMzMyZjRjMzU5MjVjMGRhMWRjOWZhOGZmNGM=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T09:14:35Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T09:14:35Z" + }, + "message": "Fix style regressions during rebase", + "tree": { + "sha": "e8cb78c99a9b7019210443dd8acf7f0e5cd69912", + "url": "https://api.github.com/repos/facebook/react/git/trees/e8cb78c99a9b7019210443dd8acf7f0e5cd69912" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/9df59c6051258d332f4c35925c0da1dc9fa8ff4c", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Zz/sACgkQWCcTbp05\ndTLYtg/+LhNbOw2W6JV0P0pZ1pDZAMii/vkSid5ZxWOqpDyKSCqMPf6FBjJAOPNq\nyHm8Gul9xoY8dZcdLnvU8jCC76zm6Isuo++5eMNIvfimNidfnvUUxImet9bvMoqO\nA4r1/Ebo3/biKfquUAONELbF4OTGuIA5W/TNEYKiFG0/0nENAFUhQKgV3gGqlPfi\nlmtt2ARmJQeBFIIPxiYAnem9UBFwm23SksbpAThmqX6x91vwf5tjzN6hHIlUKX9Z\n2rjy4XG5wa29VINfypYmuVAM7tbQmTP3u9TY3IjXPeBapfCulCRFW+aObP27QIO/\nc7+kyjeKpS4pjByBF7yYYg+pX9GoR1oQpzG7zTiksjCo4d0pHwkrsdZgwtvOwAqC\nwlB1ZEhHXezAi8f3VqNlTeLfrdc96xDaWH5g1beVVV2bJE3jpqDiGQQIJWBLRjFU\nuyhwKePnbWEcWVxe3L7K1/aWo2L0X8mcD1W0LeFfbaCnp3eph2MgKxNJzaIayNho\ndPGcl41yDlGGplB2KTKrWBvPPRIHtDteHQVE6YCfbGW4y9SHWi0olkSBv/f2VQlc\nwMrU6wr1YjZMXPLKakcegPj9JwdQJezj2Gk5a8c5IWzfAWJZfPX1cFcg1s90Owih\ne6zuE7ZI0bT4C1VgCrVA3RJ1C/lzm2wjL9sdJNN0lLfcoBnVBJ8=\n=zIS2\n-----END PGP SIGNATURE-----", + "payload": "tree e8cb78c99a9b7019210443dd8acf7f0e5cd69912\nparent 2a3423f946a958590950ec426d2bc3cc894c0018\nauthor eps1lon 1608110075 +0100\ncommitter eps1lon 1608110075 +0100\n\nFix style regressions during rebase\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/9df59c6051258d332f4c35925c0da1dc9fa8ff4c", + "html_url": "https://github.com/facebook/react/commit/9df59c6051258d332f4c35925c0da1dc9fa8ff4c", + "comments_url": "https://api.github.com/repos/facebook/react/commits/9df59c6051258d332f4c35925c0da1dc9fa8ff4c/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "2a3423f946a958590950ec426d2bc3cc894c0018", + "url": "https://api.github.com/repos/facebook/react/commits/2a3423f946a958590950ec426d2bc3cc894c0018", + "html_url": "https://github.com/facebook/react/commit/2a3423f946a958590950ec426d2bc3cc894c0018" + } + ] + }, + { + "sha": "bdf5d0ea634b0165887e08adc8ffc34375bc66e7", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmJkZjVkMGVhNjM0YjAxNjU4ODdlMDhhZGM4ZmZjMzQzNzViYzY2ZTc=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T09:18:45Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T09:18:45Z" + }, + "message": "Move Stepper UX to end of toolbar", + "tree": { + "sha": "0fad1eaf8a511cec14fb4a7b54241a6c4d97370f", + "url": "https://api.github.com/repos/facebook/react/git/trees/0fad1eaf8a511cec14fb4a7b54241a6c4d97370f" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/bdf5d0ea634b0165887e08adc8ffc34375bc66e7", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Z0PUACgkQWCcTbp05\ndTKQehAApdTy/tmgf9z9upZzsZNXxCu3UhNFtBpxQPW0299bZDTick+Oa+0pObhR\nbo5Ao9X4jSCgF/2A8caIo1armEpKodmPhsfdd/0hWi4k8iylYYDjk5X2onx2gQ3n\nGpuFi6z3A7WnfaB3DlNplkaO7xpdByxFbyYtW1uh4S0rnK/Kn5OFVvfJpLQIAFFA\nokkhNJLKns8DoBu4qi4jKwr+RwQR/VqtNbMOiZkdVeRdAYFfNFyAuiiZ6ZropRJ7\nwuURY6XrSLe4pU1RvHvlzf4dKY6ScgckJNsSbhzDhdynlwOn/ycPCW3kwy/P7MrN\noBRGfpq7DsFjrxtegqgIlBufCtif/5fgrBLtE5dH/0TexsxJXInPKwwmdQIu+MJi\nVjDcSRmTnP3aA8jGwKO/tWhnDwgkFIDS4y6JSfAJBMAzP0szWy1Jz91YbvXYo3I/\np6m1vfiHNByzon/ejZFiVVa0L2t/NuodLov9ctwmyTdwUdAp5bvWaLkkimM9CiH3\nULL9U7i1oKmzm7nNxEfl33WFAO/dpy4Xt2ZpS5QCtcKYPV1YMEUNy85kex0K6K9n\n/zh2BAXeHMyDAApqVgxLQl208ecK2LYdtqG5UDmwJuCcEcQEHNtZx8EkGoB08F6M\nR9KMuwNka7uTKDNZQXFT38sB6cgTc3/Exwcd4/9dkx93rfSblJA=\n=fbRE\n-----END PGP SIGNATURE-----", + "payload": "tree 0fad1eaf8a511cec14fb4a7b54241a6c4d97370f\nparent 9df59c6051258d332f4c35925c0da1dc9fa8ff4c\nauthor eps1lon 1608110325 +0100\ncommitter eps1lon 1608110325 +0100\n\nMove Stepper UX to end of toolbar\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/bdf5d0ea634b0165887e08adc8ffc34375bc66e7", + "html_url": "https://github.com/facebook/react/commit/bdf5d0ea634b0165887e08adc8ffc34375bc66e7", + "comments_url": "https://api.github.com/repos/facebook/react/commits/bdf5d0ea634b0165887e08adc8ffc34375bc66e7/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "9df59c6051258d332f4c35925c0da1dc9fa8ff4c", + "url": "https://api.github.com/repos/facebook/react/commits/9df59c6051258d332f4c35925c0da1dc9fa8ff4c", + "html_url": "https://github.com/facebook/react/commit/9df59c6051258d332f4c35925c0da1dc9fa8ff4c" + } + ] + }, + { + "sha": "3a37795a9e8a696a86a761a7424dc6b35a973909", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjNhMzc3OTVhOWU4YTY5NmE4NmE3NjFhNzQyNGRjNmIzNWE5NzM5MDk=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T09:20:24Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T09:20:24Z" + }, + "message": "Cleanup WIP artifacts", + "tree": { + "sha": "0da870f061b8b16968020e2ec7c7021d6d6d967c", + "url": "https://api.github.com/repos/facebook/react/git/trees/0da870f061b8b16968020e2ec7c7021d6d6d967c" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/3a37795a9e8a696a86a761a7424dc6b35a973909", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Z0VgACgkQWCcTbp05\ndTKVixAAsAON+H3axT8nTnwB7CAjOSkreF6J9Lwla6iDLx/QFQv8pG0uU5kNDPZB\nDD3gWt0QGRgVbotaTapyl5mqG7o3JGRMjAnIB4zMcug4Dz8lqLXlZrqxkxIdcugE\nc1FHwEp1QRJCJqtPfy7g7PqlaUTGQeT+c+wAQMJKMKoVkr7iyKRGq0i+PReBwWp2\nDKAEre+xQwwVq+6ZpuJwQVlm8lY7Tf1lAGVuL15ITUEsoZ0Vf2CFmxfE1ZsSEs42\n+EjerimduqJ4OaJ0GXZEciBmayZGSnU3NIiXgx7z6jYzwES57EFU1MTGG2tS8a9j\na6bmr6aCXl3lGi3Zzg5W+qPYkEukaIinaaghP70kjgxD9T4pxb6+cjxN2XyJhQuf\n/eW3gu+fxb4GDVqt4L+fn26qzJXW2YQBz3CkSkorsmyG4ouJpN3Qg+huhZI62hCx\ntAagzgmeUeNRPpVqaG6mAxa3iN6/q3Zu+Gmmh/khuIvTQNBuUBwu98rxv2ojLlga\nJHl8jqTuYTZbHr/m1SfSI7DXWxjEbgDYUWTlW/kEXlcXyHXk5KWY7+tNUh1Q7rId\nJ3bKVQbh6odxIsKgElensWWvb+2ykl7jtPCSNo4VWW+AUTrCaCBK/dcUfzHZQGkc\nEnfzbyxKpFajirFdibQL0tisGZ8k00v6Y5bPclMKFmirKoeOk70=\n=qHcQ\n-----END PGP SIGNATURE-----", + "payload": "tree 0da870f061b8b16968020e2ec7c7021d6d6d967c\nparent bdf5d0ea634b0165887e08adc8ffc34375bc66e7\nauthor eps1lon 1608110424 +0100\ncommitter eps1lon 1608110424 +0100\n\nCleanup WIP artifacts\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/3a37795a9e8a696a86a761a7424dc6b35a973909", + "html_url": "https://github.com/facebook/react/commit/3a37795a9e8a696a86a761a7424dc6b35a973909", + "comments_url": "https://api.github.com/repos/facebook/react/commits/3a37795a9e8a696a86a761a7424dc6b35a973909/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "bdf5d0ea634b0165887e08adc8ffc34375bc66e7", + "url": "https://api.github.com/repos/facebook/react/commits/bdf5d0ea634b0165887e08adc8ffc34375bc66e7", + "html_url": "https://github.com/facebook/react/commit/bdf5d0ea634b0165887e08adc8ffc34375bc66e7" + } + ] + }, + { + "sha": "91366be564aab76c4c8328eae7972844e4d244e6", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjkxMzY2YmU1NjRhYWI3NmM0YzgzMjhlYWU3OTcyODQ0ZTRkMjQ0ZTY=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T09:50:15Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T09:50:15Z" + }, + "message": "Add tally of errors/warnings", + "tree": { + "sha": "e7a71825c01bc86dc7fe01eed981e16f743eb766", + "url": "https://api.github.com/repos/facebook/react/git/trees/e7a71825c01bc86dc7fe01eed981e16f743eb766" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/91366be564aab76c4c8328eae7972844e4d244e6", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Z2FcACgkQWCcTbp05\ndTJ82hAAsuLBKt9Pj3Vdx+QBExmmJYhI2l3s/3yux9X91J/BW5yUHaoy5p8vQymm\nzwn3QJtI0hJxs++UGRv2F3a+EGvUFmdQe2kkmKE/pBuS82OEfxO3bPJ40ouzw2/B\noUCsIGkM8uEc1+ODE/qGVE0oWXDC737tWJow08kt9cqwaDk1ScwAb61uTqShhJyK\nNDy0QOpv+fWu7ZREtpEXFGtr1cnq1Q1c2uwU+T2J8FivZ4KLemumduYF1l8hZy/b\nw1Br3mPBHQoYQ5Etyd4Z3lxWQxPOihJOez9S9+sdGF3n2k32jLzy/tUUUAbNnNGv\nlsGm5kJjYiRYQq+NaSYJz074yu4ke3XSMLMp1TL9JPGXygIyKVJC9SHMQJtYdqec\nVqK1hC2wE12oguL9Z4oia281vXuDTd9Xoe2d8+hMqk0LQuz3ro2VefBJp/pjtkvv\nWn/0hE6sCJpEhQBnZgUOjft5I6phZjqm0YuE7nN8MAFBI5duA5UMBHe9ntKtr4fp\nGeC/W0bNQebLgLuYPmXW4QU0Nx7yZblxwux1d7uvD3OYRShiRGRH6gnYZTK8kTvm\nUAyEFggLY2f0pHWllP01VwC+0bH6v8oQxeWxAgTbtXuvz9wXUksS5gcrWH6sG9Sx\nKCufiwzvgcB5DkMtkhid6ZWdCpaIeRql39K+Snll7PilLgJwrwg=\n=e6jT\n-----END PGP SIGNATURE-----", + "payload": "tree e7a71825c01bc86dc7fe01eed981e16f743eb766\nparent 3a37795a9e8a696a86a761a7424dc6b35a973909\nauthor eps1lon 1608112215 +0100\ncommitter eps1lon 1608112215 +0100\n\nAdd tally of errors/warnings\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/91366be564aab76c4c8328eae7972844e4d244e6", + "html_url": "https://github.com/facebook/react/commit/91366be564aab76c4c8328eae7972844e4d244e6", + "comments_url": "https://api.github.com/repos/facebook/react/commits/91366be564aab76c4c8328eae7972844e4d244e6/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "3a37795a9e8a696a86a761a7424dc6b35a973909", + "url": "https://api.github.com/repos/facebook/react/commits/3a37795a9e8a696a86a761a7424dc6b35a973909", + "html_url": "https://github.com/facebook/react/commit/3a37795a9e8a696a86a761a7424dc6b35a973909" + } + ] + }, + { + "sha": "cc59c71ba644ede8acb1cf4112390f2c6ceef8e1", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmNjNTljNzFiYTY0NGVkZThhY2IxY2Y0MTEyMzkwZjJjNmNlZWY4ZTE=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T10:06:28Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T10:06:28Z" + }, + "message": "Remove nullish coalescing\n\nNot supported by jest config", + "tree": { + "sha": "978afbe5de988c6ea6a936d8528ee72c0c28dc30", + "url": "https://api.github.com/repos/facebook/react/git/trees/978afbe5de988c6ea6a936d8528ee72c0c28dc30" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/cc59c71ba644ede8acb1cf4112390f2c6ceef8e1", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/Z3CQACgkQWCcTbp05\ndTLs5xAApK2sfzQZX9c1L0Tp+mmh0QauT+1lyBbgzMghnSycSMxUeoG4qhUHWOnx\nnHE8OxelFbtsC5X+nk1cuMfb6cX64oa1tvoG5oM+tF3gALRSe2WVU8EDZyscbVrO\nLo9AQrTGJNMrjgi4T3SetFAQ6KFw+WaNgFzIxXZw8F2zWnQtV8XOVeH7e/heZPVQ\ndRZIIboh39bqYNBsJlcrA4wdLnG4yXYu9U9WkAP7cggnAJGoYSdcH2ItIiOKDzay\n0TDkL/3AdZR9m+wXaeL+kwAzq5RRaA+t3aU8y0SslGQW+YWGTUe3Q6zmQy9OQaps\nxJAmmCPWfOeYfQMc1P5MegtszHA2vb6T7I6eVnTAlCy2tHPfmSz3gIPqgo+JuPTB\n1vk2JFUaNEUYnBpbnRc6/UXmY8Zo14TfWDADM/zFB+K1cguh8acK9t+aYIMJNhcq\nGOdwXOIT937g4GcOWvCZvNhV6AqNru2yqt4lyG4s52gapeF0nPIbRiFpWtk91z+8\n66xTcULcAjauBXOUMB69b4xoM5mnMsHwIHerP3JRPOsaFuRUN3msQdR8hYtQPSas\nfzi3UOZS9X3XGzSEAyVo9N8jXoX9g8tjir6Zvn5hEaFrKGZ7CcdId6D+X5Eqp5bP\nkOn0CBF1842msrFPy0Qu/hZKo5Yy3wJPJserk4EPqwYQSfPuKW4=\n=GX/W\n-----END PGP SIGNATURE-----", + "payload": "tree 978afbe5de988c6ea6a936d8528ee72c0c28dc30\nparent 91366be564aab76c4c8328eae7972844e4d244e6\nauthor eps1lon 1608113188 +0100\ncommitter eps1lon 1608113188 +0100\n\nRemove nullish coalescing\n\nNot supported by jest config\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/cc59c71ba644ede8acb1cf4112390f2c6ceef8e1", + "html_url": "https://github.com/facebook/react/commit/cc59c71ba644ede8acb1cf4112390f2c6ceef8e1", + "comments_url": "https://api.github.com/repos/facebook/react/commits/cc59c71ba644ede8acb1cf4112390f2c6ceef8e1/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "91366be564aab76c4c8328eae7972844e4d244e6", + "url": "https://api.github.com/repos/facebook/react/commits/91366be564aab76c4c8328eae7972844e4d244e6", + "html_url": "https://github.com/facebook/react/commit/91366be564aab76c4c8328eae7972844e4d244e6" + } + ] + }, + { + "sha": "d164facbaa165423e5670fb208376aa510bf9df7", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmQxNjRmYWNiYWExNjU0MjNlNTY3MGZiMjA4Mzc2YWE1MTBiZjlkZjc=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T14:12:35Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T14:12:35Z" + }, + "message": "Cleaned up CSS colors and made some other small UI tweaks", + "tree": { + "sha": "51122f36200ca7ce01392f59f3e2f2d1b2c25364", + "url": "https://api.github.com/repos/facebook/react/git/trees/51122f36200ca7ce01392f59f3e2f2d1b2c25364" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/d164facbaa165423e5670fb208376aa510bf9df7", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/d164facbaa165423e5670fb208376aa510bf9df7", + "html_url": "https://github.com/facebook/react/commit/d164facbaa165423e5670fb208376aa510bf9df7", + "comments_url": "https://api.github.com/repos/facebook/react/commits/d164facbaa165423e5670fb208376aa510bf9df7/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "cc59c71ba644ede8acb1cf4112390f2c6ceef8e1", + "url": "https://api.github.com/repos/facebook/react/commits/cc59c71ba644ede8acb1cf4112390f2c6ceef8e1", + "html_url": "https://github.com/facebook/react/commit/cc59c71ba644ede8acb1cf4112390f2c6ceef8e1" + } + ] + }, + { + "sha": "860d0e7876c2d4824909aa95d7f86c1339c916ee", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjg2MGQwZTc4NzZjMmQ0ODI0OTA5YWE5NWQ3Zjg2YzEzMzljOTE2ZWU=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T14:43:39Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T14:43:39Z" + }, + "message": "Make more room in toolbar\n\nOnly show search controls when there is search text entered.\n\nDon't show error overlay in the owners view. (We also hide search controls in this view as well, to make more room for breadcrumbs", + "tree": { + "sha": "b66dcc52c867cf6448a721ea508334d574bad0fd", + "url": "https://api.github.com/repos/facebook/react/git/trees/b66dcc52c867cf6448a721ea508334d574bad0fd" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/860d0e7876c2d4824909aa95d7f86c1339c916ee", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/860d0e7876c2d4824909aa95d7f86c1339c916ee", + "html_url": "https://github.com/facebook/react/commit/860d0e7876c2d4824909aa95d7f86c1339c916ee", + "comments_url": "https://api.github.com/repos/facebook/react/commits/860d0e7876c2d4824909aa95d7f86c1339c916ee/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "d164facbaa165423e5670fb208376aa510bf9df7", + "url": "https://api.github.com/repos/facebook/react/commits/d164facbaa165423e5670fb208376aa510bf9df7", + "html_url": "https://github.com/facebook/react/commit/d164facbaa165423e5670fb208376aa510bf9df7" + } + ] + }, + { + "sha": "5cb19ac017b286cbd03a34c00bee728899027d07", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjVjYjE5YWMwMTdiMjg2Y2JkMDNhMzRjMDBiZWU3Mjg4OTkwMjdkMDc=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T15:23:32Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T15:23:32Z" + }, + "message": "Add new debug setting for showing inline warnings and errors\n\nRefactored console patching logic to account for this new setting.", + "tree": { + "sha": "30860f94c6092043678a13621d76cee2e08edfcc", + "url": "https://api.github.com/repos/facebook/react/git/trees/30860f94c6092043678a13621d76cee2e08edfcc" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/5cb19ac017b286cbd03a34c00bee728899027d07", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/5cb19ac017b286cbd03a34c00bee728899027d07", + "html_url": "https://github.com/facebook/react/commit/5cb19ac017b286cbd03a34c00bee728899027d07", + "comments_url": "https://api.github.com/repos/facebook/react/commits/5cb19ac017b286cbd03a34c00bee728899027d07/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "860d0e7876c2d4824909aa95d7f86c1339c916ee", + "url": "https://api.github.com/repos/facebook/react/commits/860d0e7876c2d4824909aa95d7f86c1339c916ee", + "html_url": "https://github.com/facebook/react/commit/860d0e7876c2d4824909aa95d7f86c1339c916ee" + } + ] + }, + { + "sha": "674e28779b9a204d4cdfe8b3c1d4dfeef78ee48d", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjY3NGUyODc3OWI5YTIwNGQ0Y2RmZThiM2MxZDRkZmVlZjc4ZWU0OGQ=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T15:39:55Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T15:39:55Z" + }, + "message": "Strip component stacks from errors/warnings before passing them to frontend", + "tree": { + "sha": "b35522412c8397ac7d2e61d42b33d6e9c73d21b7", + "url": "https://api.github.com/repos/facebook/react/git/trees/b35522412c8397ac7d2e61d42b33d6e9c73d21b7" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/674e28779b9a204d4cdfe8b3c1d4dfeef78ee48d", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/674e28779b9a204d4cdfe8b3c1d4dfeef78ee48d", + "html_url": "https://github.com/facebook/react/commit/674e28779b9a204d4cdfe8b3c1d4dfeef78ee48d", + "comments_url": "https://api.github.com/repos/facebook/react/commits/674e28779b9a204d4cdfe8b3c1d4dfeef78ee48d/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "5cb19ac017b286cbd03a34c00bee728899027d07", + "url": "https://api.github.com/repos/facebook/react/commits/5cb19ac017b286cbd03a34c00bee728899027d07", + "html_url": "https://github.com/facebook/react/commit/5cb19ac017b286cbd03a34c00bee728899027d07" + } + ] + }, + { + "sha": "0e0b8c7cd081d39cf4eb9138d7808349c95ad0a0", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjBlMGI4YzdjZDA4MWQzOWNmNGViOTEzOGQ3ODA4MzQ5Yzk1YWQwYTA=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T15:42:25Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T15:42:25Z" + }, + "message": "Fixed a spot I missed with new inline warnings setting", + "tree": { + "sha": "3195bd16377d77413330c4f7a714b266d936f4ef", + "url": "https://api.github.com/repos/facebook/react/git/trees/3195bd16377d77413330c4f7a714b266d936f4ef" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/0e0b8c7cd081d39cf4eb9138d7808349c95ad0a0", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/0e0b8c7cd081d39cf4eb9138d7808349c95ad0a0", + "html_url": "https://github.com/facebook/react/commit/0e0b8c7cd081d39cf4eb9138d7808349c95ad0a0", + "comments_url": "https://api.github.com/repos/facebook/react/commits/0e0b8c7cd081d39cf4eb9138d7808349c95ad0a0/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "674e28779b9a204d4cdfe8b3c1d4dfeef78ee48d", + "url": "https://api.github.com/repos/facebook/react/commits/674e28779b9a204d4cdfe8b3c1d4dfeef78ee48d", + "html_url": "https://github.com/facebook/react/commit/674e28779b9a204d4cdfe8b3c1d4dfeef78ee48d" + } + ] + }, + { + "sha": "5073d8193562a765b6c85fe225dfff7cc48a7d8a", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjUwNzNkODE5MzU2MmE3NjViNmM4NWZlMjI1ZGZmZjdjYzQ4YTdkOGE=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T16:26:26Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T16:26:26Z" + }, + "message": "De-dupe warnings and errors and show badge count for duplciate entries", + "tree": { + "sha": "6b806afca4cb96b814edf2393afc2da97742dc42", + "url": "https://api.github.com/repos/facebook/react/git/trees/6b806afca4cb96b814edf2393afc2da97742dc42" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/5073d8193562a765b6c85fe225dfff7cc48a7d8a", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/5073d8193562a765b6c85fe225dfff7cc48a7d8a", + "html_url": "https://github.com/facebook/react/commit/5073d8193562a765b6c85fe225dfff7cc48a7d8a", + "comments_url": "https://api.github.com/repos/facebook/react/commits/5073d8193562a765b6c85fe225dfff7cc48a7d8a/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "0e0b8c7cd081d39cf4eb9138d7808349c95ad0a0", + "url": "https://api.github.com/repos/facebook/react/commits/0e0b8c7cd081d39cf4eb9138d7808349c95ad0a0", + "html_url": "https://github.com/facebook/react/commit/0e0b8c7cd081d39cf4eb9138d7808349c95ad0a0" + } + ] + }, + { + "sha": "99de4ff77f62b5543db8536f71dd539b6598e626", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjk5ZGU0ZmY3N2Y2MmI1NTQzZGI4NTM2ZjcxZGQ1MzliNjU5OGU2MjY=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T16:32:39Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T16:32:39Z" + }, + "message": "Fixed dedupe badge text contrast", + "tree": { + "sha": "8f13c067a563ee7b8bdc8af8d1a2bf08b45c90d3", + "url": "https://api.github.com/repos/facebook/react/git/trees/8f13c067a563ee7b8bdc8af8d1a2bf08b45c90d3" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/99de4ff77f62b5543db8536f71dd539b6598e626", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/99de4ff77f62b5543db8536f71dd539b6598e626", + "html_url": "https://github.com/facebook/react/commit/99de4ff77f62b5543db8536f71dd539b6598e626", + "comments_url": "https://api.github.com/repos/facebook/react/commits/99de4ff77f62b5543db8536f71dd539b6598e626/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "5073d8193562a765b6c85fe225dfff7cc48a7d8a", + "url": "https://api.github.com/repos/facebook/react/commits/5073d8193562a765b6c85fe225dfff7cc48a7d8a", + "html_url": "https://github.com/facebook/react/commit/5073d8193562a765b6c85fe225dfff7cc48a7d8a" + } + ] + }, + { + "sha": "095c118e39fe7139bbf515efdbbd579b06eb83bd", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjA5NWMxMThlMzlmZTcxMzliYmY1MTVlZmRiYmQ1NzliMDZlYjgzYmQ=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T17:14:51Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T17:14:51Z" + }, + "message": "Oops I left a console log statement in", + "tree": { + "sha": "52b9db3bbc860712ed2a0963224c6ce528825edc", + "url": "https://api.github.com/repos/facebook/react/git/trees/52b9db3bbc860712ed2a0963224c6ce528825edc" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/095c118e39fe7139bbf515efdbbd579b06eb83bd", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/095c118e39fe7139bbf515efdbbd579b06eb83bd", + "html_url": "https://github.com/facebook/react/commit/095c118e39fe7139bbf515efdbbd579b06eb83bd", + "comments_url": "https://api.github.com/repos/facebook/react/commits/095c118e39fe7139bbf515efdbbd579b06eb83bd/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "99de4ff77f62b5543db8536f71dd539b6598e626", + "url": "https://api.github.com/repos/facebook/react/commits/99de4ff77f62b5543db8536f71dd539b6598e626", + "html_url": "https://github.com/facebook/react/commit/99de4ff77f62b5543db8536f71dd539b6598e626" + } + ] + }, + { + "sha": "e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmU4YjdkYzVmNTQ5ZjZjM2U2ZTQ0ZDQ1MDI4MWRjZDM1YWIzM2RmYWY=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T17:47:55Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T17:47:55Z" + }, + "message": "Minor UI tweaks", + "tree": { + "sha": "73826e1777deeccbf177a66b3888692b03ce3ef3", + "url": "https://api.github.com/repos/facebook/react/git/trees/73826e1777deeccbf177a66b3888692b03ce3ef3" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf", + "html_url": "https://github.com/facebook/react/commit/e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf", + "comments_url": "https://api.github.com/repos/facebook/react/commits/e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "095c118e39fe7139bbf515efdbbd579b06eb83bd", + "url": "https://api.github.com/repos/facebook/react/commits/095c118e39fe7139bbf515efdbbd579b06eb83bd", + "html_url": "https://github.com/facebook/react/commit/095c118e39fe7139bbf515efdbbd579b06eb83bd" + } + ] + }, + { + "sha": "3ab78dad37f03e83a9eb2024cf01175c31da9716", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjNhYjc4ZGFkMzdmMDNlODNhOWViMjAyNGNmMDExNzVjMzFkYTk3MTY=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T18:01:11Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T18:01:46Z" + }, + "message": "Clean up component stack remove in messages", + "tree": { + "sha": "48606957b02a829c62504708a48a5d749149a8e4", + "url": "https://api.github.com/repos/facebook/react/git/trees/48606957b02a829c62504708a48a5d749149a8e4" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/3ab78dad37f03e83a9eb2024cf01175c31da9716", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/aS4oACgkQWCcTbp05\ndTJKtA//duo+KKAuL2hkJOa10UZ0JA/XQxivjSsS3JZczcnTfEpmtZVxQitW3RrE\nzwkwU4QIcvjjfkwjyteheERKJkXBBnlC1E/iRDPDggSiOjJKCOD+C3ZBcltq/DCo\nROTTHs4/UrjRyev9nI66K3nvf5ukGuiMjZvKrgmTZLKdCZ/a3g7f6rkDMx9dDg4Z\n7yMR30srxEiG73EzIFEkYz+FkK8sHYggQRwT5r34ddxPnijevnuTkVh4gKA6E1yR\nrF3ljIBYF0Zncf/YqTOj2HRnDChds9fMmbBKUOY0GhBPYXXsmLmZ2XKZKfczFEvo\nfIqgvFZhw3bbzTZHJJpzCFUNEOph+xbDTKqNHyoTCg7LGAq9DrBmbUCJo0w7WOln\nhciHqqKo57lX0JOPB0erfT4WLIvng24kQjOxwc3N3BjUszdk27apj939yJi2AboC\nKsuIt7FO84oPQwJ+QvjezuHV4UTJHFASuEnKpQY6F60DOJW6fw54DhvX9GPcNSy7\nrgCLVHNhAk0ltXUS/skxI0mMZhcxZ+r7FKH+Dp3pxhzbXgriF1mOwbYPgKXgCuij\ni7eyHdPrQ5KqsO06IqS+no6nbqQHOngSvYf5U6PG0sLOWinIRXuqpnWPU1fqUMtj\nuJYR/Z+CfqXBibV8bLEuLa15046NXrU3GjM4cbZM/H9aBTKpL3M=\n=KZvZ\n-----END PGP SIGNATURE-----", + "payload": "tree 48606957b02a829c62504708a48a5d749149a8e4\nparent e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf\nauthor eps1lon 1608141671 +0100\ncommitter eps1lon 1608141706 +0100\n\nClean up component stack remove in messages\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/3ab78dad37f03e83a9eb2024cf01175c31da9716", + "html_url": "https://github.com/facebook/react/commit/3ab78dad37f03e83a9eb2024cf01175c31da9716", + "comments_url": "https://api.github.com/repos/facebook/react/commits/3ab78dad37f03e83a9eb2024cf01175c31da9716/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf", + "url": "https://api.github.com/repos/facebook/react/commits/e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf", + "html_url": "https://github.com/facebook/react/commit/e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf" + } + ] + }, + { + "sha": "15122d2a7e79a969b7f69166bcc181439bb5930c", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjE1MTIyZDJhN2U3OWE5NjliN2Y2OTE2NmJjYzE4MTQzOWJiNTkzMGM=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T18:10:40Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T18:10:40Z" + }, + "message": "Remove WIP artifacts", + "tree": { + "sha": "9fcef5af9b7a113223f4e8996632460e8d084158", + "url": "https://api.github.com/repos/facebook/react/git/trees/9fcef5af9b7a113223f4e8996632460e8d084158" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/15122d2a7e79a969b7f69166bcc181439bb5930c", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/aTaAACgkQWCcTbp05\ndTKcWRAAq76lymWDEUZv97/JJYqQ7j7mDKlYWKQzYSn082rdIZzg463S1p1hXPyH\nzdko9uni79rAw51xXN6oRuhkM60Cv3rBjg1FVRi+cdRPmAMAXesSnpbtc5DyW1Du\nRDZLPAZo8JJ3Xb8rLa+TyTfNjZgeddSjP67HATyWbGtEpMGANwwpi91unrv85b+4\nfDJUQaJMr+iL414kq061OHNcGTNEWZHUk/S8xcI+Z9hSlJytoT3HyAhIyRnUkTDP\noJBguwIHeOeftTAuilpRsCmaU8mRSAVYRjL4LqAfP25EZ7B1bQajm9LM9oHQJpvo\nBz7HAT+34OBX2ok+xzcp5Jl1s4D7LKgriq+xJ+kNFJipzKou2x+LOEPwql8ntDLn\nUw2NbDn3B0T4nlQ8hZNjIEDKCs0v2N2YIRySnfIfAwFKZvMwyShA7swZUbDKWGpx\n1usr2H41wz2qtJF3IXeaAPXPhgxH2toW9676St8yTBzxgw4TpBd5vmIKNh7yDsjS\nI/8CFBtefxekWi0+KVJOs7WFzauutLvjrzYgw4rZ1WWK44LcKPEoaXPY1Bw1Rm+l\nHUZ1COQO8KEGZsgwzfB8e4I9zy7sarArSRvhVAYJRq4joI3erKkEQMsVLq1OrDOv\nAdTOqUaegHFZHjGYkEEIo5OG/who7Vml2ETTg+5GLQoUAt6XcL0=\n=H78p\n-----END PGP SIGNATURE-----", + "payload": "tree 9fcef5af9b7a113223f4e8996632460e8d084158\nparent 3ab78dad37f03e83a9eb2024cf01175c31da9716\nauthor eps1lon 1608142240 +0100\ncommitter eps1lon 1608142240 +0100\n\nRemove WIP artifacts\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/15122d2a7e79a969b7f69166bcc181439bb5930c", + "html_url": "https://github.com/facebook/react/commit/15122d2a7e79a969b7f69166bcc181439bb5930c", + "comments_url": "https://api.github.com/repos/facebook/react/commits/15122d2a7e79a969b7f69166bcc181439bb5930c/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "3ab78dad37f03e83a9eb2024cf01175c31da9716", + "url": "https://api.github.com/repos/facebook/react/commits/3ab78dad37f03e83a9eb2024cf01175c31da9716", + "html_url": "https://github.com/facebook/react/commit/3ab78dad37f03e83a9eb2024cf01175c31da9716" + } + ] + }, + { + "sha": "26cecfa806e0ef8584db4985bfd145a71e643b66", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjI2Y2VjZmE4MDZlMGVmODU4NGRiNDk4NWJmZDE0NWE3MWU2NDNiNjY=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T18:28:54Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T18:28:54Z" + }, + "message": "Fixed bug when calculating total error/warning count", + "tree": { + "sha": "c2fdc0fddc2ad557508e7523b0c6ed18ed8da517", + "url": "https://api.github.com/repos/facebook/react/git/trees/c2fdc0fddc2ad557508e7523b0c6ed18ed8da517" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/26cecfa806e0ef8584db4985bfd145a71e643b66", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/26cecfa806e0ef8584db4985bfd145a71e643b66", + "html_url": "https://github.com/facebook/react/commit/26cecfa806e0ef8584db4985bfd145a71e643b66", + "comments_url": "https://api.github.com/repos/facebook/react/commits/26cecfa806e0ef8584db4985bfd145a71e643b66/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf", + "url": "https://api.github.com/repos/facebook/react/commits/e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf", + "html_url": "https://github.com/facebook/react/commit/e8b7dc5f549f6c3e6e44d450281dcd35ab33dfaf" + } + ] + }, + { + "sha": "1f1a161dafc6d53ec563508aea6a65b1f4538adc", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjFmMWExNjFkYWZjNmQ1M2VjNTYzNTA4YWVhNmE2NWIxZjQ1MzhhZGM=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T18:28:58Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T18:28:58Z" + }, + "message": "Merge branch 'devtools-show-warnings-in-tree' of github.com:eps1lon/react into devtools-show-warnings-in-tree", + "tree": { + "sha": "f8cf74e9a2674e3241efbb3e8c4dad20a816df77", + "url": "https://api.github.com/repos/facebook/react/git/trees/f8cf74e9a2674e3241efbb3e8c4dad20a816df77" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/1f1a161dafc6d53ec563508aea6a65b1f4538adc", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/1f1a161dafc6d53ec563508aea6a65b1f4538adc", + "html_url": "https://github.com/facebook/react/commit/1f1a161dafc6d53ec563508aea6a65b1f4538adc", + "comments_url": "https://api.github.com/repos/facebook/react/commits/1f1a161dafc6d53ec563508aea6a65b1f4538adc/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "26cecfa806e0ef8584db4985bfd145a71e643b66", + "url": "https://api.github.com/repos/facebook/react/commits/26cecfa806e0ef8584db4985bfd145a71e643b66", + "html_url": "https://github.com/facebook/react/commit/26cecfa806e0ef8584db4985bfd145a71e643b66" + }, + { + "sha": "15122d2a7e79a969b7f69166bcc181439bb5930c", + "url": "https://api.github.com/repos/facebook/react/commits/15122d2a7e79a969b7f69166bcc181439bb5930c", + "html_url": "https://github.com/facebook/react/commit/15122d2a7e79a969b7f69166bcc181439bb5930c" + } + ] + }, + { + "sha": "caf8678643bf6b91e8c8ae54f94de60b84c1a268", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmNhZjg2Nzg2NDNiZjZiOTFlOGM4YWU1NGY5NGRlNjBiODRjMWEyNjg=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T18:38:52Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T18:38:52Z" + }, + "message": "Use empty string instead of \"fixing\" the format string", + "tree": { + "sha": "c2506818c918b9aebb91724f5835f0a72b4caf5f", + "url": "https://api.github.com/repos/facebook/react/git/trees/c2506818c918b9aebb91724f5835f0a72b4caf5f" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/caf8678643bf6b91e8c8ae54f94de60b84c1a268", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/aVDwACgkQWCcTbp05\ndTIXdBAAhQi/hbQwEEvm/nGKCtglUC6Pdg6lxN6ndhn0O57qW7Og8z0l51tHeBQV\n2fLuxLg88BY4Nh4Gyb9s6jjjnyDsmoQnY5brwWwKR1ytCrpK5WWNmyqsmAH7atTM\nh/IrYUBFDGPp2hmGCgIpUs7Ko50X1TUxFsdcosV1bRXAFNapyjvNzqoxF4PKa3J4\ny0JlaTvPCiSFSUtIjo1s2U61BLZbfEhs6wwYUj2KJYm0mqI6Naq4c14GnFoVriNK\nNznO0rUviRiirkVmLhnbkLPNcSSZQdf0A8trxENva6H+LnwdErouzzZwsRJuqYcW\nXKb3Z7YTtXczdzY6HIWP6WKd/oShWWAc8GDF3Uto3fS2p0MxoCncc59ZnNyNUAeh\nWyldGBlKKmX6gdmFS790RPAhlYdXWyhR1jYG0kxJM5KVXtM3p3tHZQ9SvJIkSaHH\nGE/kSIfyVjZr7mxeTInHQpHgHKA66BT+ZZ65SjL247SHsZQtqaKdOCPqKTlYca7f\nJ9E09rr3MzA/+8Zx49PVSA1Gp3iFAD4VKnUQB7r0JjzdxrSnqP6HSAWEScf8l6mO\ntcxv4FUvbciWxfdfjH+fZMYYMa9uYck8yi3m/FHxensaRofwm+xNoGAAdCqOdwN7\nclgVPybNMAYsos1S2bsjY8eNA9m1XSArQ8fVImhjcduPplWc7Pc=\n=A2Jo\n-----END PGP SIGNATURE-----", + "payload": "tree c2506818c918b9aebb91724f5835f0a72b4caf5f\nparent 1f1a161dafc6d53ec563508aea6a65b1f4538adc\nauthor eps1lon 1608143932 +0100\ncommitter eps1lon 1608143932 +0100\n\nUse empty string instead of \"fixing\" the format string\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/caf8678643bf6b91e8c8ae54f94de60b84c1a268", + "html_url": "https://github.com/facebook/react/commit/caf8678643bf6b91e8c8ae54f94de60b84c1a268", + "comments_url": "https://api.github.com/repos/facebook/react/commits/caf8678643bf6b91e8c8ae54f94de60b84c1a268/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "1f1a161dafc6d53ec563508aea6a65b1f4538adc", + "url": "https://api.github.com/repos/facebook/react/commits/1f1a161dafc6d53ec563508aea6a65b1f4538adc", + "html_url": "https://github.com/facebook/react/commit/1f1a161dafc6d53ec563508aea6a65b1f4538adc" + } + ] + }, + { + "sha": "a4f005992b6bcddf67539cd04eafe0d1c50effc9", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmE0ZjAwNTk5MmI2YmNkZGY2NzUzOWNkMDRlYWZlMGQxYzUwZWZmYzk=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T19:03:17Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T19:03:17Z" + }, + "message": "Fix test harness to avoid double logging\n\nComponent stack generation relies on intentionally triggering common sources of errors when shallow-rendering a component. Our inline error test harness didn't trip these paths for most components, and so it appeared to double log most of its warnings/errors. This commit fixes that.", + "tree": { + "sha": "a1b02708f238017d0268f6fc562b60b1f85e6bb4", + "url": "https://api.github.com/repos/facebook/react/git/trees/a1b02708f238017d0268f6fc562b60b1f85e6bb4" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/a4f005992b6bcddf67539cd04eafe0d1c50effc9", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/a4f005992b6bcddf67539cd04eafe0d1c50effc9", + "html_url": "https://github.com/facebook/react/commit/a4f005992b6bcddf67539cd04eafe0d1c50effc9", + "comments_url": "https://api.github.com/repos/facebook/react/commits/a4f005992b6bcddf67539cd04eafe0d1c50effc9/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "caf8678643bf6b91e8c8ae54f94de60b84c1a268", + "url": "https://api.github.com/repos/facebook/react/commits/caf8678643bf6b91e8c8ae54f94de60b84c1a268", + "html_url": "https://github.com/facebook/react/commit/caf8678643bf6b91e8c8ae54f94de60b84c1a268" + } + ] + }, + { + "sha": "5719f31d19f21e72c8f72d91549b6cdcb2163970", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjU3MTlmMzFkMTlmMjFlNzJjOGY3MmQ5MTU0OWI2Y2RjYjIxNjM5NzA=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T19:07:23Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T19:07:23Z" + }, + "message": "Added a few more test cases to the inline error test harness", + "tree": { + "sha": "81748d53e3a447093f441669a743680acdad79c8", + "url": "https://api.github.com/repos/facebook/react/git/trees/81748d53e3a447093f441669a743680acdad79c8" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/5719f31d19f21e72c8f72d91549b6cdcb2163970", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/5719f31d19f21e72c8f72d91549b6cdcb2163970", + "html_url": "https://github.com/facebook/react/commit/5719f31d19f21e72c8f72d91549b6cdcb2163970", + "comments_url": "https://api.github.com/repos/facebook/react/commits/5719f31d19f21e72c8f72d91549b6cdcb2163970/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "a4f005992b6bcddf67539cd04eafe0d1c50effc9", + "url": "https://api.github.com/repos/facebook/react/commits/a4f005992b6bcddf67539cd04eafe0d1c50effc9", + "html_url": "https://github.com/facebook/react/commit/a4f005992b6bcddf67539cd04eafe0d1c50effc9" + } + ] + }, + { + "sha": "92ebd817278e1024094c02995a4f7a586777ef4f", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjkyZWJkODE3Mjc4ZTEwMjQwOTRjMDI5OTVhNGY3YTU4Njc3N2VmNGY=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T19:18:21Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T19:18:21Z" + }, + "message": "Show full message on click", + "tree": { + "sha": "cb815069fc425bdc6a2d16a6c8b01ec57b8ab067", + "url": "https://api.github.com/repos/facebook/react/git/trees/cb815069fc425bdc6a2d16a6c8b01ec57b8ab067" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/92ebd817278e1024094c02995a4f7a586777ef4f", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/aXX4ACgkQWCcTbp05\ndTIJ3g//YSC0lrvm4OssnHIgDIIYxhuq4dF9Z8itJhpZLYYPia+fN1q8vXRUhpqu\n4F3OnVztlQrQngdWBkke3AinsHGjWLB1E9VP8Zhlr04QvgU48hl8CDUtMrrh2DEm\nSmj3j/qaxdLBY+7Ii7RkVex68oVYSbQOkV3mx/OVOFPto7tNXgsD7m9WCFjeLCUd\nEy7dQXjMl3Wgt8Da+UQEqnqPllv5uX/oios9CXXqhZ9nTAjzpNKKHnPCzRYgVFj+\nwSSaO/0HRzOjv50xUYtTMcgshmGSBnhDvtN9Stuisi575EHU8boNsu7//VQ1Kbqm\nia4AaUFHE/cKgnAUTLdWEnxNhaLxhUQCCErITZeaoDLtdIgPV+7BntjoqJcJ/uws\nABXNZtjIKEj3hZLqdFNprdT5cJ+0+vJaMhOYIh3acu8uPfN+1ozy2BqDlK2n1pbP\nWMhT5EqeBR/CGZusydLCFuISg9Ez0IwGlZvm2dMXix3LRnUissCKUoGTRcGiNlRZ\noMhVYP0EzvHpb9NtfR8Pd2sXi7gRl6rmNo3ewfEOKq8Q3XPbqyBXqrD7MqHAQZzT\n2D1mXh6ycPhVAE30eubJyz0WBDln8kOg8Ubu1itjIX9ZQKEudLkluZF2DRLKkpkG\nRMy3B8PcNQCc0gEu5x5C+sA5T3SwSPaV1ztMjxLSzsMoPhqZJkA=\n=r+la\n-----END PGP SIGNATURE-----", + "payload": "tree cb815069fc425bdc6a2d16a6c8b01ec57b8ab067\nparent 5719f31d19f21e72c8f72d91549b6cdcb2163970\nauthor eps1lon 1608146301 +0100\ncommitter eps1lon 1608146301 +0100\n\nShow full message on click\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/92ebd817278e1024094c02995a4f7a586777ef4f", + "html_url": "https://github.com/facebook/react/commit/92ebd817278e1024094c02995a4f7a586777ef4f", + "comments_url": "https://api.github.com/repos/facebook/react/commits/92ebd817278e1024094c02995a4f7a586777ef4f/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "5719f31d19f21e72c8f72d91549b6cdcb2163970", + "url": "https://api.github.com/repos/facebook/react/commits/5719f31d19f21e72c8f72d91549b6cdcb2163970", + "html_url": "https://github.com/facebook/react/commit/5719f31d19f21e72c8f72d91549b6cdcb2163970" + } + ] + }, + { + "sha": "70099e5638be567f7ef1d0d0edfb165f0c5ef9b5", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjcwMDk5ZTU2MzhiZTU2N2Y3ZWYxZDBkMGVkZmIxNjVmMGM1ZWY5YjU=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T20:03:59Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T20:03:59Z" + }, + "message": "Revert 92ebd81", + "tree": { + "sha": "6bb1a109c054ae9aba211c743ea9db018177038c", + "url": "https://api.github.com/repos/facebook/react/git/trees/6bb1a109c054ae9aba211c743ea9db018177038c" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/70099e5638be567f7ef1d0d0edfb165f0c5ef9b5", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/70099e5638be567f7ef1d0d0edfb165f0c5ef9b5", + "html_url": "https://github.com/facebook/react/commit/70099e5638be567f7ef1d0d0edfb165f0c5ef9b5", + "comments_url": "https://api.github.com/repos/facebook/react/commits/70099e5638be567f7ef1d0d0edfb165f0c5ef9b5/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "92ebd817278e1024094c02995a4f7a586777ef4f", + "url": "https://api.github.com/repos/facebook/react/commits/92ebd817278e1024094c02995a4f7a586777ef4f", + "html_url": "https://github.com/facebook/react/commit/92ebd817278e1024094c02995a4f7a586777ef4f" + } + ] + }, + { + "sha": "1b6e29277ead380dc90bb560c339680c201dcab6", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjFiNmUyOTI3N2VhZDM4MGRjOTBiYjU2MGMzMzk2ODBjMjAxZGNhYjY=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T20:12:41Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T20:12:41Z" + }, + "message": "Show full error/warning text as tooltip on hover", + "tree": { + "sha": "f57387359f5556303c12460bb1d3c0474382bfa8", + "url": "https://api.github.com/repos/facebook/react/git/trees/f57387359f5556303c12460bb1d3c0474382bfa8" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/1b6e29277ead380dc90bb560c339680c201dcab6", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/1b6e29277ead380dc90bb560c339680c201dcab6", + "html_url": "https://github.com/facebook/react/commit/1b6e29277ead380dc90bb560c339680c201dcab6", + "comments_url": "https://api.github.com/repos/facebook/react/commits/1b6e29277ead380dc90bb560c339680c201dcab6/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "70099e5638be567f7ef1d0d0edfb165f0c5ef9b5", + "url": "https://api.github.com/repos/facebook/react/commits/70099e5638be567f7ef1d0d0edfb165f0c5ef9b5", + "html_url": "https://github.com/facebook/react/commit/70099e5638be567f7ef1d0d0edfb165f0c5ef9b5" + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/pull_request_commits_page3.json b/src/test/resources/com/spotify/github/v3/clients/pull_request_commits_page3.json new file mode 100644 index 00000000..979ae360 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/pull_request_commits_page3.json @@ -0,0 +1,1902 @@ +[ + { + "sha": "61bf6a36a5bfd9b297ce8057f449a5f99ff6a1ee", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjYxYmY2YTM2YTViZmQ5YjI5N2NlODA1N2Y0NDlhNWY5OWZmNmExZWU=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T20:15:45Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T20:15:45Z" + }, + "message": "inline errors and warnings are not implemented in legacy devtools", + "tree": { + "sha": "9c694f3fceb045ebadd915845d8fa3ff9b40b87d", + "url": "https://api.github.com/repos/facebook/react/git/trees/9c694f3fceb045ebadd915845d8fa3ff9b40b87d" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/61bf6a36a5bfd9b297ce8057f449a5f99ff6a1ee", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/aavEACgkQWCcTbp05\ndTJ5UQ/+KofyRcLItGUo23HKV4cagrKhHyKHQUtal4sfEAwPp1flh6Oh+dbw2dGB\nT2kUQyh0cWAQ1zcS/1e+ci3Mojm1hURI0A6x0FJJhsnBFLGEqz7pTGMyl0bCtFYE\nM8xysQSx6kmHFhZXEfXTreH8u0YBxLBMWJuQVU2+M/6Rs057LdWpth4dGiAcJfl1\nllPv4AdzVZ073rqFmVlgNvBgrUxuoSaI9KXVsim4X3Qdr5CP2MFfl34RdEAtW5hy\nxVX9O/dJjnHgyb38mbkwpM6mSeXuFsuY3biZeeLahqt/E3v+GOrAUlXUicEmbPKR\n8uFkyCzw/mY7qc7Zzw4RvIG4ncOsxiNqT55jhZn8MNARqpdCodENEYWQ2Bc1jWsE\nFsH2FYb0DdZN+asXUDx465bXlMDe+Tmfi5l8Kanh4oMUQ5Y8ASrg50tSprpZoAil\n/BBVuwHIVQwkE5LatG9DRWRbHDAneOEZ1Fy5UYvbQnyc+QTuPM8UbB4kmCQsLBVN\n7hMzx+FnpLaBmf1q+XH3p0FhHytaciZZCUhpdnlhw+xauQELFoFX3En/XeDMegTR\nIYHjOPWeh5wbiFLfrxe+RnATll+Ft4h4ecBfxhmHcE2dDnQCZvIWYJ9o58uRWzEb\ngIL+EdCKMnXfRDUIvbR202tPUPsivAXBKaaNxQn7VBi4V7PWp88=\n=ZzOy\n-----END PGP SIGNATURE-----", + "payload": "tree 9c694f3fceb045ebadd915845d8fa3ff9b40b87d\nparent 1b6e29277ead380dc90bb560c339680c201dcab6\nauthor eps1lon 1608149745 +0100\ncommitter eps1lon 1608149745 +0100\n\ninline errors and warnings are not implemented in legacy devtools\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/61bf6a36a5bfd9b297ce8057f449a5f99ff6a1ee", + "html_url": "https://github.com/facebook/react/commit/61bf6a36a5bfd9b297ce8057f449a5f99ff6a1ee", + "comments_url": "https://api.github.com/repos/facebook/react/commits/61bf6a36a5bfd9b297ce8057f449a5f99ff6a1ee/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "1b6e29277ead380dc90bb560c339680c201dcab6", + "url": "https://api.github.com/repos/facebook/react/commits/1b6e29277ead380dc90bb560c339680c201dcab6", + "html_url": "https://github.com/facebook/react/commit/1b6e29277ead380dc90bb560c339680c201dcab6" + } + ] + }, + { + "sha": "46cea44df06b30c4e44db1be886cffa9150ce298", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjQ2Y2VhNDRkZjA2YjMwYzRlNDRkYjFiZTg4NmNmZmE5MTUwY2UyOTg=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T20:29:46Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T20:29:46Z" + }, + "message": "Added missing key error test", + "tree": { + "sha": "711b0c3d1d86ef81ed8c9d9b9842c5d6a36dabdc", + "url": "https://api.github.com/repos/facebook/react/git/trees/711b0c3d1d86ef81ed8c9d9b9842c5d6a36dabdc" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/46cea44df06b30c4e44db1be886cffa9150ce298", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/46cea44df06b30c4e44db1be886cffa9150ce298", + "html_url": "https://github.com/facebook/react/commit/46cea44df06b30c4e44db1be886cffa9150ce298", + "comments_url": "https://api.github.com/repos/facebook/react/commits/46cea44df06b30c4e44db1be886cffa9150ce298/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "61bf6a36a5bfd9b297ce8057f449a5f99ff6a1ee", + "url": "https://api.github.com/repos/facebook/react/commits/61bf6a36a5bfd9b297ce8057f449a5f99ff6a1ee", + "html_url": "https://github.com/facebook/react/commit/61bf6a36a5bfd9b297ce8057f449a5f99ff6a1ee" + } + ] + }, + { + "sha": "8b5c8d9c354b7dc1a31cdcac8bf997a2d2c77f80", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjhiNWM4ZDljMzU0YjdkYzFhMzFjZGNhYzhiZjk5N2EyZDJjNzdmODA=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T20:53:09Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T20:53:09Z" + }, + "message": "Move batching to backend/console", + "tree": { + "sha": "792e668f200e1b711837638b03cd28c97a62c31d", + "url": "https://api.github.com/repos/facebook/react/git/trees/792e668f200e1b711837638b03cd28c97a62c31d" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/8b5c8d9c354b7dc1a31cdcac8bf997a2d2c77f80", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/ac7UACgkQWCcTbp05\ndTK4dg//eASfA66HR2GhpPTULA31taWaEk1DmiM+kKp7XKpdeCqOrMebcC6F8rkl\n8rWv17LsO66gQ2V0OeGdMUyQTXPiMj22yNPTZGNQ7BCHCkEVFUsUF3RjO3k6OTqJ\n87AKBwRsw/VUTBwo6ME24R/9u0q3q+BBEXoS1cOkN8hl6x331hYJAmyr5irSu3ql\nRyBnwbamtuqdiIMZD+ljou6L86c8YmTuk0pNIZjEF7DP3z4N452zPbyjwzVgQhN1\noTFc+BvWitUJ1YAHdelVsobmUDHshDME7wy9ZZioX2kD0psDFU+1jx6K1ZUMDs2h\nX1J+LW2sY5zjs5Zgsn6FwCM3C2O/k5RciGsS9Hj2dtej2ttlttAEs7+5kCyqhFX7\nnDSoKUAiaMlKO0YgFm0Q8RIhdOcOSuR0nQe8218jk1GmS2KmhiY21Uq6ZhqFIwrR\nk2LZpXH5NIZxbNGFFOgTE3SG6LUM/tUkvwYoeJgwrPIT9I0u7Lchnly572+dm8pB\naIeCI2K0YnoQlkbSYg2NXuYSFnuGa+sh2J5+dxsIcz2C7OmvLaPqN/eVcdyHzods\nfB9RaeaVb7imZEmM6a94pNKBO5yTOFDgUug1H3i9WvhcctSR/05+5JQNtEbfXnhg\n0p2aOVKUAH1c6mANPvMUcaP+TKSHY+4wLbfD+7NRLLeU4ges4c8=\n=OEqg\n-----END PGP SIGNATURE-----", + "payload": "tree 792e668f200e1b711837638b03cd28c97a62c31d\nparent 46cea44df06b30c4e44db1be886cffa9150ce298\nauthor eps1lon 1608151989 +0100\ncommitter eps1lon 1608151989 +0100\n\nMove batching to backend/console\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/8b5c8d9c354b7dc1a31cdcac8bf997a2d2c77f80", + "html_url": "https://github.com/facebook/react/commit/8b5c8d9c354b7dc1a31cdcac8bf997a2d2c77f80", + "comments_url": "https://api.github.com/repos/facebook/react/commits/8b5c8d9c354b7dc1a31cdcac8bf997a2d2c77f80/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "46cea44df06b30c4e44db1be886cffa9150ce298", + "url": "https://api.github.com/repos/facebook/react/commits/46cea44df06b30c4e44db1be886cffa9150ce298", + "html_url": "https://github.com/facebook/react/commit/46cea44df06b30c4e44db1be886cffa9150ce298" + } + ] + }, + { + "sha": "0146bc33b572a8caa2537c68907a0536964a5a9c", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjAxNDZiYzMzYjU3MmE4Y2FhMjUzN2M2ODkwN2EwNTM2OTY0YTVhOWM=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T21:04:45Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-16T21:04:45Z" + }, + "message": "move filter into forEach", + "tree": { + "sha": "edef577b2b6806681914010f72f86a4d3a88f269", + "url": "https://api.github.com/repos/facebook/react/git/trees/edef577b2b6806681914010f72f86a4d3a88f269" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/0146bc33b572a8caa2537c68907a0536964a5a9c", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/adm0ACgkQWCcTbp05\ndTLzsA/9EpDR5cXG/4TeASS+jHImAZi3T93c4obZNnRzBPBr3vkG41pzJmzc7/x0\nB2FNNcDd+SpjSVYzq4hkKFbvRpPvLwSB7rjRg/fFhAWSI56tNFn+3CBRnmrlj5ee\nwGInqpLX9sxPkVDeIRRA6/DOAAwwoplnqHukgKNz3SNZ7EgHsmRmClhTFnoLItgf\nnLihUiASfVPYz1ZqtUuuJgYkiBa6vqeO4CjeOYq7Y179mvs4Rl3E1JXJp19oljvJ\nITr59NJbAEMr1NmovpK+wPX+RKw/3E/MSylZlZfwjK4GEyz0iLnAnB4Jr/vkW9be\noTRa4WiGeDeReNeSrnT7rxMvH6nkBm6F6Yci10lpwadyP85WdqiocK3YK9Vxr1QF\nIatyEAbkKZ0va9pNlPCc6eZ9jFFv7Eb8d6VgDeK4LMRiHwTdoadfehRiNHkuwo3X\nQrvo1eVCDn1bQfWy2arDRPKiD7q9dqBizPWJBXLAiDea1PGn6/ASVMS4ozn4WVUx\ncetUtAxJelonv5XzlSQ8pcPntp4JV7RsJcLhU9CjHg53dD5yoABcHB4XMxelr2oi\nbowd1oNCUkrxsHFXcuyGaajQqFVwxd700ye3/pWY6GuUthM/0sN41pinYf5tEvxD\nkbA0uK7QVmXECD6ou2maF4h0knvwftGBAyB/TLnzHZHRd5mNOvw=\n=r3g8\n-----END PGP SIGNATURE-----", + "payload": "tree edef577b2b6806681914010f72f86a4d3a88f269\nparent 8b5c8d9c354b7dc1a31cdcac8bf997a2d2c77f80\nauthor eps1lon 1608152685 +0100\ncommitter eps1lon 1608152685 +0100\n\nmove filter into forEach\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/0146bc33b572a8caa2537c68907a0536964a5a9c", + "html_url": "https://github.com/facebook/react/commit/0146bc33b572a8caa2537c68907a0536964a5a9c", + "comments_url": "https://api.github.com/repos/facebook/react/commits/0146bc33b572a8caa2537c68907a0536964a5a9c/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "8b5c8d9c354b7dc1a31cdcac8bf997a2d2c77f80", + "url": "https://api.github.com/repos/facebook/react/commits/8b5c8d9c354b7dc1a31cdcac8bf997a2d2c77f80", + "html_url": "https://github.com/facebook/react/commit/8b5c8d9c354b7dc1a31cdcac8bf997a2d2c77f80" + } + ] + }, + { + "sha": "04ffb01ae43218916a1ed60991793250f7da3fcb", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjA0ZmZiMDFhZTQzMjE4OTE2YTFlZDYwOTkxNzkzMjUwZjdkYTNmY2I=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T21:26:48Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-16T21:26:48Z" + }, + "message": "Added some basic tests for inline errors/warnings", + "tree": { + "sha": "2e883a823bfc718df921aba3ca5f411cd04afe9f", + "url": "https://api.github.com/repos/facebook/react/git/trees/2e883a823bfc718df921aba3ca5f411cd04afe9f" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/04ffb01ae43218916a1ed60991793250f7da3fcb", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/04ffb01ae43218916a1ed60991793250f7da3fcb", + "html_url": "https://github.com/facebook/react/commit/04ffb01ae43218916a1ed60991793250f7da3fcb", + "comments_url": "https://api.github.com/repos/facebook/react/commits/04ffb01ae43218916a1ed60991793250f7da3fcb/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "0146bc33b572a8caa2537c68907a0536964a5a9c", + "url": "https://api.github.com/repos/facebook/react/commits/0146bc33b572a8caa2537c68907a0536964a5a9c", + "html_url": "https://github.com/facebook/react/commit/0146bc33b572a8caa2537c68907a0536964a5a9c" + } + ] + }, + { + "sha": "9380545c50e3b14a0e3a898c1767beb17f11f652", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjkzODA1NDVjNTBlM2IxNGEwZTNhODk4YzE3NjdiZWIxN2YxMWY2NTI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T14:53:41Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T14:53:41Z" + }, + "message": "Add tests for the store", + "tree": { + "sha": "e42ae8d43865b38b2570a9fc2692fa30781dc3f6", + "url": "https://api.github.com/repos/facebook/react/git/trees/e42ae8d43865b38b2570a9fc2692fa30781dc3f6" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/9380545c50e3b14a0e3a898c1767beb17f11f652", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/bcPUACgkQWCcTbp05\ndTJIbw//WoNUVNVYzxq+JO1sksVz1Xhc/vEzkTRowuGXCLukm92ahy8oB79O/K7f\nqvHCB734JueaIhcefFaUVMTr7VEEz78uJEiqZF0MWNx9Op/1uhVXZ/TRiJjlAE5f\ncmKdX8ssGTP8+zhAgB5lZ5jeci02qGFvb2pC0PUygF24QXXC8gPzqXR7MRoNhtjD\n6KbjaOXx/0sgLsk8VmfsML7Smmz9lG6YqB7+3mM5z1+8CF2ksXlGmYlDZ7e3vROM\nY6Rw7/hPJHOrU9HCSKOYazB8jRo6PiiIzvp1MJZLnqXgyoruhm9cfg8SrkdcRA0n\nI+9nObhrZz5m/u+eChBT6xIgcLKD6TE+DioLrpSNHwSr6h4d5s+6nJa9uG11CqGs\nZo8XGwTyfqvVBv0MGgvD/mxsybETZOOvCTfuYpaB6LiLGIuircHD9Y39be+nsCrm\nGEVjxuRvXjlBenDPTlCAWYrJGmwDda8xW+C4DTLjS5bdfVyEMfSaMtdKZok6mDT3\nFWNAyKQyMSvaLCiKldpCMC0d+XcbVj0MF9VaUdRi1TGImqiV/mN8LTZFnPTrVXMP\n9pYduGfxBWGTuoj9fEqOxJh36bZfxzmsNQaARsVbIkzrqxgI5xhiGJrDpNyX3kPi\nvdulPh1JQws0oE0bOnnlxRiPyiNpwhd5B/SpgJQBZdyyO4HTHSk=\n=p3P9\n-----END PGP SIGNATURE-----", + "payload": "tree e42ae8d43865b38b2570a9fc2692fa30781dc3f6\nparent 04ffb01ae43218916a1ed60991793250f7da3fcb\nauthor eps1lon 1608216821 +0100\ncommitter eps1lon 1608216821 +0100\n\nAdd tests for the store\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/9380545c50e3b14a0e3a898c1767beb17f11f652", + "html_url": "https://github.com/facebook/react/commit/9380545c50e3b14a0e3a898c1767beb17f11f652", + "comments_url": "https://api.github.com/repos/facebook/react/commits/9380545c50e3b14a0e3a898c1767beb17f11f652/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "04ffb01ae43218916a1ed60991793250f7da3fcb", + "url": "https://api.github.com/repos/facebook/react/commits/04ffb01ae43218916a1ed60991793250f7da3fcb", + "html_url": "https://github.com/facebook/react/commit/04ffb01ae43218916a1ed60991793250f7da3fcb" + } + ] + }, + { + "sha": "8ff2c7b098eda20f73c05e8e997a61c258231864", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjhmZjJjN2IwOThlZGEyMGY3M2MwNWU4ZTk5N2E2MWMyNTgyMzE4NjQ=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T16:07:54Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T16:07:54Z" + }, + "message": "Update snapshot due to previous typo fix", + "tree": { + "sha": "a914fb41ebfae2a51e7608d503fe33984e4be2d2", + "url": "https://api.github.com/repos/facebook/react/git/trees/a914fb41ebfae2a51e7608d503fe33984e4be2d2" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/8ff2c7b098eda20f73c05e8e997a61c258231864", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/bgloACgkQWCcTbp05\ndTIELRAAprOLIDb3QLEeos9ctxu4sITTciv+JVm3RYEi5BYmvkc/WbGzBilU9FhW\nfbVirYIkqq+VEi0NhJ1oHFwLf9+tvosZuXqLXV6doakUVtOg5glmatidiD8h61mS\nScxfuU3WdH1YyKo/bGexdA0ZGv/mq4lbqQhBxSkFeE9zSuZt/nC97k1d01ZrHQg0\nkObtSBHlu0vKTX0Vr0gAN3Wumfw5mOoEmbkx4HR+qHV8YaYsXOEou1RXSQJKCQJo\nbdl27y0sJEvMo2qzEDwE9XjMp8UUcgcDuMNe3irjryMA9rFbolNoo+mQ3POyvmL5\nFSxsV9t/taKjXpcw1+1e5cb1LkKblXnS8/tOiIXnq8HIh+19CFLPV+hl2ASrp9CR\nWdVl5RDIxCK/2rw7RRPXvabHKMohnOlCRmylxv5RGRiXPpCRVQ4sViJfWpaQjDj6\nXAuf7897awWlnJSnIMs0exepPX/SSds5hyg/eCMyKnfGzcsOG0c4ktkYJjbRIgyU\nTk7HOKvJYziJdCJYTva5Aoga9yqXqxgVcXQ14G9CeVXXYwmm1s+pePpNoESomWeV\nkETg42uOKxZ/I5GSV7kVq2AVQ+eNwEvlos/dUYgnvwSwmL/FwDcwsytT914HqT6L\nG8vW/a/cJaDx/bRDEbTsSoz2ZtXs0mKDmzIOzKYO2mWyhd/9Nm0=\n=ZApv\n-----END PGP SIGNATURE-----", + "payload": "tree a914fb41ebfae2a51e7608d503fe33984e4be2d2\nparent 9380545c50e3b14a0e3a898c1767beb17f11f652\nauthor eps1lon 1608221274 +0100\ncommitter eps1lon 1608221274 +0100\n\nUpdate snapshot due to previous typo fix\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/8ff2c7b098eda20f73c05e8e997a61c258231864", + "html_url": "https://github.com/facebook/react/commit/8ff2c7b098eda20f73c05e8e997a61c258231864", + "comments_url": "https://api.github.com/repos/facebook/react/commits/8ff2c7b098eda20f73c05e8e997a61c258231864/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "9380545c50e3b14a0e3a898c1767beb17f11f652", + "url": "https://api.github.com/repos/facebook/react/commits/9380545c50e3b14a0e3a898c1767beb17f11f652", + "html_url": "https://github.com/facebook/react/commit/9380545c50e3b14a0e3a898c1767beb17f11f652" + } + ] + }, + { + "sha": "5824f48de70c944eaf0c1c82f5baf3ca770f86e4", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjU4MjRmNDhkZTcwYzk0NGVhZjBjMWM4MmY1YmFmM2NhNzcwZjg2ZTQ=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T16:09:40Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T16:09:40Z" + }, + "message": "Remove outdated snapshot", + "tree": { + "sha": "2b08941996c079067be8348c06621b137e79b312", + "url": "https://api.github.com/repos/facebook/react/git/trees/2b08941996c079067be8348c06621b137e79b312" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/5824f48de70c944eaf0c1c82f5baf3ca770f86e4", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/5824f48de70c944eaf0c1c82f5baf3ca770f86e4", + "html_url": "https://github.com/facebook/react/commit/5824f48de70c944eaf0c1c82f5baf3ca770f86e4", + "comments_url": "https://api.github.com/repos/facebook/react/commits/5824f48de70c944eaf0c1c82f5baf3ca770f86e4/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "8ff2c7b098eda20f73c05e8e997a61c258231864", + "url": "https://api.github.com/repos/facebook/react/commits/8ff2c7b098eda20f73c05e8e997a61c258231864", + "html_url": "https://github.com/facebook/react/commit/8ff2c7b098eda20f73c05e8e997a61c258231864" + } + ] + }, + { + "sha": "8fc62494adfa8cc20eb94478eb64508b452adb69", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjhmYzYyNDk0YWRmYThjYzIwZWI5NDQ3OGViNjQ1MDhiNDUyYWRiNjk=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T16:29:01Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T16:29:01Z" + }, + "message": "Refactored error/warning message queueing and bundling", + "tree": { + "sha": "c7177d9d798a5ef60bf3479504f751d84c114d42", + "url": "https://api.github.com/repos/facebook/react/git/trees/c7177d9d798a5ef60bf3479504f751d84c114d42" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/8fc62494adfa8cc20eb94478eb64508b452adb69", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/8fc62494adfa8cc20eb94478eb64508b452adb69", + "html_url": "https://github.com/facebook/react/commit/8fc62494adfa8cc20eb94478eb64508b452adb69", + "comments_url": "https://api.github.com/repos/facebook/react/commits/8fc62494adfa8cc20eb94478eb64508b452adb69/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "5824f48de70c944eaf0c1c82f5baf3ca770f86e4", + "url": "https://api.github.com/repos/facebook/react/commits/5824f48de70c944eaf0c1c82f5baf3ca770f86e4", + "html_url": "https://github.com/facebook/react/commit/5824f48de70c944eaf0c1c82f5baf3ca770f86e4" + } + ] + }, + { + "sha": "f0ceff6f856e846c18d9267de8294752b5c8f4f8", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmYwY2VmZjZmODU2ZTg0NmMxOGQ5MjY3ZGU4Mjk0NzUyYjVjOGY0Zjg=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T16:36:53Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T16:36:53Z" + }, + "message": "Fixed Store tests by updating to inline snapshots", + "tree": { + "sha": "e2dc5f4d8a81d5f388593635b6362865da1e87f4", + "url": "https://api.github.com/repos/facebook/react/git/trees/e2dc5f4d8a81d5f388593635b6362865da1e87f4" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/f0ceff6f856e846c18d9267de8294752b5c8f4f8", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/f0ceff6f856e846c18d9267de8294752b5c8f4f8", + "html_url": "https://github.com/facebook/react/commit/f0ceff6f856e846c18d9267de8294752b5c8f4f8", + "comments_url": "https://api.github.com/repos/facebook/react/commits/f0ceff6f856e846c18d9267de8294752b5c8f4f8/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "8fc62494adfa8cc20eb94478eb64508b452adb69", + "url": "https://api.github.com/repos/facebook/react/commits/8fc62494adfa8cc20eb94478eb64508b452adb69", + "html_url": "https://github.com/facebook/react/commit/8fc62494adfa8cc20eb94478eb64508b452adb69" + } + ] + }, + { + "sha": "1c1874c4bed1718f32b951a52bc669fe6d992d14", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjFjMTg3NGM0YmVkMTcxOGYzMmI5NTFhNTJiYzY2OWZlNmQ5OTJkMTQ=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T17:04:55Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T17:04:55Z" + }, + "message": "Prevent siliencing of unexpected console calls in inspectedElementContext-test", + "tree": { + "sha": "93c091bf94cbdb16f98ecbdd93c710b035937c72", + "url": "https://api.github.com/repos/facebook/react/git/trees/93c091bf94cbdb16f98ecbdd93c710b035937c72" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/1c1874c4bed1718f32b951a52bc669fe6d992d14", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/bj7cACgkQWCcTbp05\ndTJY1g/+PSzg4b++rN4nz+8mqopGFgC9tEC7ot9me7CidrOFbFyjzuO3uBCWyPnw\ntD2pi/2irF++6KNiST96TSGjX3MC/U/24wUiiijkpgEZHESPWBO+/COHE1tJX4RD\nDtBd1c7mFYHnpCNrtLDC3n+64k0fdrOZQQkGYZKPOzif2qeYQ5V63nQOjQEeuQec\nQLQkkvrn3t0TuB7yNtcBwGrC117mekoW6P/qeHn/DDTwwhUX4kLwjlqItbmxsu4Y\n/mAVHtFuMqoLNQBNwWq33vtohFO8RK22q5XO+ByrNp9uJ71xTeQYH5YJhLnCAwmY\nCx0jJbqMoy6g0qOqoN+J1ASnurmLD0a6jVdp13awHSBljUs7085D/qosQc0rTMVS\nZhHRm1zmyyJHtqrn22ijOTfC+H2PUY5rzb4S287XhAEHZNqAWQuKenA80yBDJCC6\n0+uXmMJm4/JHiFMMv8jWHur9GwuiGfXuC8las2HcoJ9uHFWq7Ytb/BJnHF9F4OSu\nQOvdchW+28zRYjfEoLQpKx0sCt6Dq6c67puOeQMZtghtwdaVD/zWYuNBN5hJXX7K\n1Fywb56vwytkgUkA7JQqbAvIjrJiafMxquLU5ck0Vk89uSyrieG4MlHSEbwl9VII\nmeXiC6LK+ReMEKpCEB2GNazY3wa9aNh12L+Jj8eKSc1VszWO1Jo=\n=jXp6\n-----END PGP SIGNATURE-----", + "payload": "tree 93c091bf94cbdb16f98ecbdd93c710b035937c72\nparent f0ceff6f856e846c18d9267de8294752b5c8f4f8\nauthor eps1lon 1608224695 +0100\ncommitter eps1lon 1608224695 +0100\n\nPrevent siliencing of unexpected console calls in inspectedElementContext-test\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/1c1874c4bed1718f32b951a52bc669fe6d992d14", + "html_url": "https://github.com/facebook/react/commit/1c1874c4bed1718f32b951a52bc669fe6d992d14", + "comments_url": "https://api.github.com/repos/facebook/react/commits/1c1874c4bed1718f32b951a52bc669fe6d992d14/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "f0ceff6f856e846c18d9267de8294752b5c8f4f8", + "url": "https://api.github.com/repos/facebook/react/commits/f0ceff6f856e846c18d9267de8294752b5c8f4f8", + "html_url": "https://github.com/facebook/react/commit/f0ceff6f856e846c18d9267de8294752b5c8f4f8" + } + ] + }, + { + "sha": "ef149c9e0dc7aa95c256849dbe99d5d29d248335", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmVmMTQ5YzllMGRjN2FhOTVjMjU2ODQ5ZGJlOTlkNWQyOWQyNDgzMzU=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T17:39:36Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T17:39:36Z" + }, + "message": "Actually use generic param", + "tree": { + "sha": "034a89d997ea5a2788d7154e712cd32c4b95dab2", + "url": "https://api.github.com/repos/facebook/react/git/trees/034a89d997ea5a2788d7154e712cd32c4b95dab2" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/ef149c9e0dc7aa95c256849dbe99d5d29d248335", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/bl9gACgkQWCcTbp05\ndTJ1Tw//VMzPMHDn7ogH68I2MQ5x5JvVz9FJDh4HeAKDUjjHZ3u0i1IB0JDEJsyO\nm/RGptpyiNhurWa/wbaTWzZ7XXTbs6Xx90t3j6ciIOBEvDdBb/KQ+LbY3EoozwsJ\nHIn4yWE+Q4laANamsZIasoQblNXjP5QAmU+5sA4asWAf2EXYpo8McqdoKCnsyT2K\neJVnTzqkXIJLUWvgsUh12xdsrpfQ5cxh0kMj6erifGMM9f2VvRXMdZrbdIqqe3X4\nRyqTeoDvfJXYW7Zk5udp6C+5ZhL1Vd5vbrApm3V3dUYWV9t8Iic79iF8waUUBh8o\nUFUCWS3aQHrIdZ06wSSd6O2o6tpCJqzQh5lyvsIs3ZCbya7as+e40OCVb5PquzO0\nl3V2+UewIqazhXBNXBqKLo4jyFAn3w41DtFMvGPJW+3pTtrLU7qcbQ7B05LYUUbE\nBojV10OjB3XbY4VR+S9Rqm8q8VST5ZaTN5ent0SXLDbPMW97tHv+GUqPRrDUFfci\nJ3DAvv1dk65fEmo0y2HIQTsYBr8q/yBGZnzSSaT4CCmhXWz7gqT/z59IoQpS5fFx\nAjM7jhDvqCDj66THykuov0XimauW2LNDPShGQGgBeR5VnESxd5uT13lG/63OwR4X\ng4qefC7S8KlfRKjqHOBz6Uw+aEl6UCsnw0MY0rk1eLupFX6SFw8=\n=EWhf\n-----END PGP SIGNATURE-----", + "payload": "tree 034a89d997ea5a2788d7154e712cd32c4b95dab2\nparent 1c1874c4bed1718f32b951a52bc669fe6d992d14\nauthor eps1lon 1608226776 +0100\ncommitter eps1lon 1608226776 +0100\n\nActually use generic param\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/ef149c9e0dc7aa95c256849dbe99d5d29d248335", + "html_url": "https://github.com/facebook/react/commit/ef149c9e0dc7aa95c256849dbe99d5d29d248335", + "comments_url": "https://api.github.com/repos/facebook/react/commits/ef149c9e0dc7aa95c256849dbe99d5d29d248335/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "1c1874c4bed1718f32b951a52bc669fe6d992d14", + "url": "https://api.github.com/repos/facebook/react/commits/1c1874c4bed1718f32b951a52bc669fe6d992d14", + "html_url": "https://github.com/facebook/react/commit/1c1874c4bed1718f32b951a52bc669fe6d992d14" + } + ] + }, + { + "sha": "5c892ac2db1d1174731e0a07704be7e7562a9496", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjVjODkyYWMyZGIxZDExNzQ3MzFlMGEwNzcwNGJlN2U3NTYyYTk0OTY=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T17:46:52Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T17:46:52Z" + }, + "message": "Use inline snapshots", + "tree": { + "sha": "574b2c9db0a3267e3ba4acc1da6db9b6f0e5332a", + "url": "https://api.github.com/repos/facebook/react/git/trees/574b2c9db0a3267e3ba4acc1da6db9b6f0e5332a" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/5c892ac2db1d1174731e0a07704be7e7562a9496", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/bmYwACgkQWCcTbp05\ndTLeGRAAked7WzXdQnJce34Qb4v3PWT/2N9f3EqkANvlg6zYhwr03V/ebQw2Wl1M\nILpTCfYxmj7oowSuarPFq/a24Jlo3yx9If++y8dvFCq6jGZ0wAoWpm8014xQX6ku\nqfV+pLwvYlSQo2r7yXzdDIGUCA3HmSCaHPaupyEjDkK2S14puKtp/dmk5qe0kbxA\n6xivxiwP1HaIEZ7Hlu49w1FTXlfezDILXiN1xYB2WXT+fQqLmmil155t0WrxK5jP\nS+ultpiYEDbtF1ZjKWup0jzxmL6Oxf3RkzBzm83x9juc2QNRaXUB+Lc2jj7hSBOq\nF7gA6RquNzOgagxCTo12U3onrs89F0P+R7CEMFdnJS7ZZLftPdzj4rqXxEQ67Myv\ntImNfIxs6TtDb7zo/iVADrVZXrJgYqnsfqhdnJDvPxgrxcEUwgB5lE9xUgwex8At\nGm50PuGhurzf1L/Lkdc7qZpkKtIOH5jzOfqWLuiu8QE4zc/Cd6JS1wXnkbKNYfrU\n0n002dz3PBhBGnawNb8zZ7Nmbc9JWR9pnODkWioU6m3RSJWYvyYGUlIZQOa4D07E\nI9tALyDCqLUZ0iGSsqnJnWojsQbWNScrL5RUtGdvg/BlprrZnCAEJZJwJ4gxVi56\nYcR4t9WtXKEOuGJIN4jOA81MzCUc5UWdbKCrdC0VtFx3hmciboo=\n=4n2E\n-----END PGP SIGNATURE-----", + "payload": "tree 574b2c9db0a3267e3ba4acc1da6db9b6f0e5332a\nparent ef149c9e0dc7aa95c256849dbe99d5d29d248335\nauthor eps1lon 1608227212 +0100\ncommitter eps1lon 1608227212 +0100\n\nUse inline snapshots\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/5c892ac2db1d1174731e0a07704be7e7562a9496", + "html_url": "https://github.com/facebook/react/commit/5c892ac2db1d1174731e0a07704be7e7562a9496", + "comments_url": "https://api.github.com/repos/facebook/react/commits/5c892ac2db1d1174731e0a07704be7e7562a9496/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "ef149c9e0dc7aa95c256849dbe99d5d29d248335", + "url": "https://api.github.com/repos/facebook/react/commits/ef149c9e0dc7aa95c256849dbe99d5d29d248335", + "html_url": "https://github.com/facebook/react/commit/ef149c9e0dc7aa95c256849dbe99d5d29d248335" + } + ] + }, + { + "sha": "8953cdc6503b8a73ce5f27a5522865ac48c6c465", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjg5NTNjZGM2NTAzYjhhNzNjZTVmMjdhNTUyMjg2NWFjNDhjNmM0NjU=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T18:46:14Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T18:56:11Z" + }, + "message": "Add tests for clearing errors and warnings", + "tree": { + "sha": "d6cfd9c239da749ea68557f404234104e09eda01", + "url": "https://api.github.com/repos/facebook/react/git/trees/d6cfd9c239da749ea68557f404234104e09eda01" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/8953cdc6503b8a73ce5f27a5522865ac48c6c465", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/bqcsACgkQWCcTbp05\ndTLDUA//bDq3xwMKoFaIE1z0toaAveWlTQniac9qdEVCd4Vw3m1ZurlSnWAgC1i2\nZtgbSrGC+vw8kJeh+Ya7ma09U0WKilySO8AeKaaoHVe8OJOw8+r/VCzfRigMazbf\nYmqt6gwVaiJ2YIn1tzsjAABwRQrnzwVKwzzsOarnQMjfJLZOYeYwc6myJdRef/AW\nsXVE7Z8SVlfcExQm8CYS98VCH9p7Z0zHAaBKVYw2BoNehxU6oGRPXSNIw58rGS5H\nQGuPuDRa6GKyDIM7N0uXaCWiTwzIDq2yvh4UxeAHipM0h61vPoOkV5xV7K+1KHdN\nBHRvbe7CZ8+gOqIm+MS9LPcq/18ltlfFI9Oskm6kS5xyyb0GDd84ZegMVGyC1DMG\n0whkKKnke3JmSUPRKbhinheST/YN7qLrsvZtlCfLtvyNwVXpPMwo+M3aiDHhHVvj\nkapol/9xn/mvG2sTxyOu1srHDF4Ra3vowiIU0yR/0dJhPjIqjYQGXcqpNxhZBDT4\n8QvrSKup7UII4LvsgXdnsVHLFxhnsRCeXSlUMF2SuhdakXEt43EFB3xHhEMD4tYb\nssF7IUmvX85PxCOTMZXbq0RTGFpCRIeVAdjU92E5ywlkHg0gCIZuKz6WWq3VDs4h\nDeGoaeW8lmqCJ7VyRYNLd69+mQWe2bzp3YTs2Z//tBHJYbPOjoE=\n=RhyO\n-----END PGP SIGNATURE-----", + "payload": "tree d6cfd9c239da749ea68557f404234104e09eda01\nparent 5c892ac2db1d1174731e0a07704be7e7562a9496\nauthor eps1lon 1608230774 +0100\ncommitter eps1lon 1608231371 +0100\n\nAdd tests for clearing errors and warnings\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/8953cdc6503b8a73ce5f27a5522865ac48c6c465", + "html_url": "https://github.com/facebook/react/commit/8953cdc6503b8a73ce5f27a5522865ac48c6c465", + "comments_url": "https://api.github.com/repos/facebook/react/commits/8953cdc6503b8a73ce5f27a5522865ac48c6c465/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "5c892ac2db1d1174731e0a07704be7e7562a9496", + "url": "https://api.github.com/repos/facebook/react/commits/5c892ac2db1d1174731e0a07704be7e7562a9496", + "html_url": "https://github.com/facebook/react/commit/5c892ac2db1d1174731e0a07704be7e7562a9496" + } + ] + }, + { + "sha": "268a25b169aa612447622a8c5ba5afedce6e584e", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjI2OGEyNWIxNjlhYTYxMjQ0NzYyMmE4YzViYTVhZmVkY2U2ZTU4NGU=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T18:58:13Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T18:58:13Z" + }, + "message": "Run the minimal amount of timer necessary", + "tree": { + "sha": "aeb3df251ff62f2260906fbdb1efd73492bf8483", + "url": "https://api.github.com/repos/facebook/react/git/trees/aeb3df251ff62f2260906fbdb1efd73492bf8483" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/268a25b169aa612447622a8c5ba5afedce6e584e", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/bqkUACgkQWCcTbp05\ndTIHQBAAqWs4RVLuLKUrwNeRrsYKzKm4jzQTuLtJcuJFmkqci1L1y2sHHiwdE6k4\nMs3+1NWLUJElPgAVGOEqiV1GdtS4ku1yntJAeyRu/E5FpGV7ZnNDJdsy//vC1CA3\n860a4DFU5M4vHB6ycYcIOW2GipLYJ43VzGoID9ErxnHPhoy7Y2SqQckZZTkerKFj\nK4OKkHHxhB5yXVt7dY/lTua+uEWaDK6MeWLAdVnAHpXJS5j8fk8nR37bQeGe5ZJf\ntThYihTsz5mQ6HdJ47yy4zGUzGdw6rvlqhzRAwzb9ld8OQEvFBKY/iZOWYZNHZF0\nklXTFpQBFBn9k1+U6h0lT9TuGH2jnI5u2wN92Q03G5uOM6Em0k9dbslk+td7h8wY\nv1RqPvaNXowEHtoMqk5zsieVcTvUFALyAsZ7o362Vrf1o1WrdDRJy344qtGy/y4x\npxVG59AYd0IeB8sttHxf5Choi90C4K4bOGJSRphd8G3e5UBC7i3S9q3KpJ2/tI45\n3b8zFI5LuJrpeu2zVj2BSfhGC2shMfJYmX/dYzZ9gUqL26Fw/aDxsnjIO/LWob94\nYEQp8kUFbqF27UIgxleLJ1arVNnRv4+TFVhWn+q/7Hq0vzbrM8zbMpsq357VXj1X\n/LaY3oCinx6lZRk5k4t6qTA4RFIHUKx+lNkuj4IPDO/3pQxnR8I=\n=MK7j\n-----END PGP SIGNATURE-----", + "payload": "tree aeb3df251ff62f2260906fbdb1efd73492bf8483\nparent 8953cdc6503b8a73ce5f27a5522865ac48c6c465\nauthor eps1lon 1608231493 +0100\ncommitter eps1lon 1608231493 +0100\n\nRun the minimal amount of timer necessary\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/268a25b169aa612447622a8c5ba5afedce6e584e", + "html_url": "https://github.com/facebook/react/commit/268a25b169aa612447622a8c5ba5afedce6e584e", + "comments_url": "https://api.github.com/repos/facebook/react/commits/268a25b169aa612447622a8c5ba5afedce6e584e/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "8953cdc6503b8a73ce5f27a5522865ac48c6c465", + "url": "https://api.github.com/repos/facebook/react/commits/8953cdc6503b8a73ce5f27a5522865ac48c6c465", + "html_url": "https://github.com/facebook/react/commit/8953cdc6503b8a73ce5f27a5522865ac48c6c465" + } + ] + }, + { + "sha": "6e167a3f300352c21aa696258c1b8759c1a1a082", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjZlMTY3YTNmMzAwMzUyYzIxYWE2OTYyNThjMWI4NzU5YzFhMWEwODI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T19:07:50Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-17T19:07:50Z" + }, + "message": "Revert \"Run the minimal amount of timer necessary\"\n\nThis reverts commit 268a25b169aa612447622a8c5ba5afedce6e584e.", + "tree": { + "sha": "d6cfd9c239da749ea68557f404234104e09eda01", + "url": "https://api.github.com/repos/facebook/react/git/trees/d6cfd9c239da749ea68557f404234104e09eda01" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/6e167a3f300352c21aa696258c1b8759c1a1a082", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/brIgACgkQWCcTbp05\ndTIpKBAAnQHFjeV52CTgQI/CjPUe7IIfYgWt20EL9gkcQmVG0gAJCVVybAwUwtOT\naseGsLauomKfm30s6besa5JyGCPuL/fV0vIj6m2dAPHuKPkxjs0843Ir73Kw90eV\n+4FtYGF0y/dhEXbbNC2/T/ScCTBbta2I8mzVTyZHJtkg37fS58vJaUOeLwNxjGGs\n3haEbgtxP9tpld7gjsdAN5z60DG9owuyDBZOdGet9GQMW9TBQjDryojqx0XU2BOf\nE5AGObTu8UVnriEOOJL/b7q+R6VTbysBMT4TC37/CnwnnBAKBXD18tT4rbuk9d8z\n5ScMDesAsVS2a+Ig9NcklB26TQ2oZBvrlN6duU1/b7kfHy0J9HlCMFJqgz3g5zX1\n8i3QjfHHI9vhmAkBpNVt2PVPjmLQr/N/gsZTnGoPNV7wJGXdTbJqwens30bCmyU5\nvjQB2dVJBDS/rSls/H5K6UCEOJ9fuvfLVNS7uP0ivE4hd/9P4RO4gEv+lc5kVpqH\neu1IF8YITkXEZAru2ibjbBzLAjlFj8bkcvcLR1D8XUBgreg1R7Gc+djozBAGpJI4\nxytp5bP7Ll2oUUur3K/AQUSci190YIyo+xCcFmyRofcczfy9GaLZ+Gjbpu81LTUi\nvepCWBzdeyiVq+d2Uz38pSdt+/csoxRCQJ/hZBlUyBoSiX84U54=\n=yV+S\n-----END PGP SIGNATURE-----", + "payload": "tree d6cfd9c239da749ea68557f404234104e09eda01\nparent 268a25b169aa612447622a8c5ba5afedce6e584e\nauthor eps1lon 1608232070 +0100\ncommitter eps1lon 1608232070 +0100\n\nRevert \"Run the minimal amount of timer necessary\"\n\nThis reverts commit 268a25b169aa612447622a8c5ba5afedce6e584e.\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/6e167a3f300352c21aa696258c1b8759c1a1a082", + "html_url": "https://github.com/facebook/react/commit/6e167a3f300352c21aa696258c1b8759c1a1a082", + "comments_url": "https://api.github.com/repos/facebook/react/commits/6e167a3f300352c21aa696258c1b8759c1a1a082/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "268a25b169aa612447622a8c5ba5afedce6e584e", + "url": "https://api.github.com/repos/facebook/react/commits/268a25b169aa612447622a8c5ba5afedce6e584e", + "html_url": "https://github.com/facebook/react/commit/268a25b169aa612447622a8c5ba5afedce6e584e" + } + ] + }, + { + "sha": "69410ac9edb9b13cead6dbb46f9a0ea0b2c6cd0d", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjY5NDEwYWM5ZWRiOWIxM2NlYWQ2ZGJiNDZmOWEwZWEwYjJjNmNkMGQ=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T21:35:10Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T21:35:10Z" + }, + "message": "Added tests (and bug fix) for select next/prev error", + "tree": { + "sha": "26180e46fa934907bf58d77563ef679d598cc6f9", + "url": "https://api.github.com/repos/facebook/react/git/trees/26180e46fa934907bf58d77563ef679d598cc6f9" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/69410ac9edb9b13cead6dbb46f9a0ea0b2c6cd0d", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/69410ac9edb9b13cead6dbb46f9a0ea0b2c6cd0d", + "html_url": "https://github.com/facebook/react/commit/69410ac9edb9b13cead6dbb46f9a0ea0b2c6cd0d", + "comments_url": "https://api.github.com/repos/facebook/react/commits/69410ac9edb9b13cead6dbb46f9a0ea0b2c6cd0d/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "6e167a3f300352c21aa696258c1b8759c1a1a082", + "url": "https://api.github.com/repos/facebook/react/commits/6e167a3f300352c21aa696258c1b8759c1a1a082", + "html_url": "https://github.com/facebook/react/commit/6e167a3f300352c21aa696258c1b8759c1a1a082" + } + ] + }, + { + "sha": "6feb404501cae6cbc827d13229e0b02d81884e35", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjZmZWI0MDQ1MDFjYWU2Y2JjODI3ZDEzMjI5ZTBiMDJkODE4ODRlMzU=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T23:16:41Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T23:16:41Z" + }, + "message": "Fixed an error (but left a TODO for a better future fix)", + "tree": { + "sha": "8fec934be9a7e4c0851b7c912b71186d62fbcf9a", + "url": "https://api.github.com/repos/facebook/react/git/trees/8fec934be9a7e4c0851b7c912b71186d62fbcf9a" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/6feb404501cae6cbc827d13229e0b02d81884e35", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/6feb404501cae6cbc827d13229e0b02d81884e35", + "html_url": "https://github.com/facebook/react/commit/6feb404501cae6cbc827d13229e0b02d81884e35", + "comments_url": "https://api.github.com/repos/facebook/react/commits/6feb404501cae6cbc827d13229e0b02d81884e35/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "69410ac9edb9b13cead6dbb46f9a0ea0b2c6cd0d", + "url": "https://api.github.com/repos/facebook/react/commits/69410ac9edb9b13cead6dbb46f9a0ea0b2c6cd0d", + "html_url": "https://github.com/facebook/react/commit/69410ac9edb9b13cead6dbb46f9a0ea0b2c6cd0d" + } + ] + }, + { + "sha": "da0aad2cbf39db3fbf2d6bda303ac33f5b7f92bd", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmRhMGFhZDJjYmYzOWRiM2ZiZjJkNmJkYTMwM2FjMzNmNWI3ZjkyYmQ=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T23:28:36Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-17T23:28:36Z" + }, + "message": "Added two (failing) tests that we should fix", + "tree": { + "sha": "b7324c78c7814e6d20c3fbccfe7dcf44dc7ad59a", + "url": "https://api.github.com/repos/facebook/react/git/trees/b7324c78c7814e6d20c3fbccfe7dcf44dc7ad59a" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/da0aad2cbf39db3fbf2d6bda303ac33f5b7f92bd", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/da0aad2cbf39db3fbf2d6bda303ac33f5b7f92bd", + "html_url": "https://github.com/facebook/react/commit/da0aad2cbf39db3fbf2d6bda303ac33f5b7f92bd", + "comments_url": "https://api.github.com/repos/facebook/react/commits/da0aad2cbf39db3fbf2d6bda303ac33f5b7f92bd/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "6feb404501cae6cbc827d13229e0b02d81884e35", + "url": "https://api.github.com/repos/facebook/react/commits/6feb404501cae6cbc827d13229e0b02d81884e35", + "html_url": "https://github.com/facebook/react/commit/6feb404501cae6cbc827d13229e0b02d81884e35" + } + ] + }, + { + "sha": "35b16ce7d9e14ff5544a4c61293eae1c0ff510eb", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjM1YjE2Y2U3ZDllMTRmZjU1NDRhNGM2MTI5M2VhZTFjMGZmNTEwZWI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T10:04:09Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T10:04:09Z" + }, + "message": "select(Prev|Next)Error -> select(Prev|Next)ErrorOrWarning\n\nMaybe we should've called it \"Issues\" instead but now that we've written it so often we might as well keep it?!\nOr just avoid the bikeshedding altogether...", + "tree": { + "sha": "fb266c3ad9eafaa1a83e14154c7db9bbdde4d6ef", + "url": "https://api.github.com/repos/facebook/react/git/trees/fb266c3ad9eafaa1a83e14154c7db9bbdde4d6ef" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/35b16ce7d9e14ff5544a4c61293eae1c0ff510eb", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/cfpkACgkQWCcTbp05\ndTIZCBAAkoxAgP5gw2nGRxL8ow4BKwgL3/Gn8NM3UGoLoe3jhP7ceWTrPPejO8Q+\n4gRW+teqz5kfHNYlAnmOUSue3aKKXX2v3dF5mG4KiwFYNpOkPlE/6fFTLbyvqxVv\nFdu6x1Vm8dZceqc5y8a2jcn+ajCjf/YEZE5LYmpigl7Cjlrk6tF3BlsY2s8NiZ3/\nPRgH3aOOsEHWIkmLlMriTI8m6caN1EcC7PBi3DDiTzfDIz4FK8ZTxIWqyS0vEAIP\n/NZ+rysV/xj+dW8zvx4h9RBNyX/y8AYnFJIJfkxOoiBDTW5U73tqtkqz8Gau/6ue\nHrSBMlRyR1Fby5dnjI1+2pmKp8PjrcN2LOO9VNRKrS9BWWCTDWyshnOfPm1shtMC\nMSxRTY2kP1f9fkNVI7a15FqU88r7ZbUezY82e7n+3hM3+7WZCTESAnWMLddr8Ung\nCAr/oI7C3VsHx8ImoI1VmqaijBj8xW4+b+zTB1UoyrtV1L5MSr/K4lah01Oiw3id\n6pYXOVw1PAPOO1UMiRHtVYKbq4SnvCLKORMmtU1COZjMJkxF5obWWJh/6Pt7NOQL\nooZ939mug+lr/7tXvAT7xYEkBHwxgsfiYdApok4yp2ryfQ7HgRAlNu/2gxol73tf\nNLXam3otNwsjrB86O0Ib9dfmBLhCqBRA9toJ5CZN/zsXsjC1+JU=\n=uyxh\n-----END PGP SIGNATURE-----", + "payload": "tree fb266c3ad9eafaa1a83e14154c7db9bbdde4d6ef\nparent da0aad2cbf39db3fbf2d6bda303ac33f5b7f92bd\nauthor eps1lon 1608285849 +0100\ncommitter eps1lon 1608285849 +0100\n\nselect(Prev|Next)Error -> select(Prev|Next)ErrorOrWarning\n\nMaybe we should've called it \"Issues\" instead but now that we've written it so often we might as well keep it?!\nOr just avoid the bikeshedding altogether...\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/35b16ce7d9e14ff5544a4c61293eae1c0ff510eb", + "html_url": "https://github.com/facebook/react/commit/35b16ce7d9e14ff5544a4c61293eae1c0ff510eb", + "comments_url": "https://api.github.com/repos/facebook/react/commits/35b16ce7d9e14ff5544a4c61293eae1c0ff510eb/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "da0aad2cbf39db3fbf2d6bda303ac33f5b7f92bd", + "url": "https://api.github.com/repos/facebook/react/commits/da0aad2cbf39db3fbf2d6bda303ac33f5b7f92bd", + "html_url": "https://github.com/facebook/react/commit/da0aad2cbf39db3fbf2d6bda303ac33f5b7f92bd" + } + ] + }, + { + "sha": "af1c54ed8f5a528028eac4512f5a33f143a5c5e1", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmFmMWM1NGVkOGY1YTUyODAyOGVhYzQ1MTJmNWEzM2YxNDNhNWM1ZTE=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T10:55:40Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T10:55:40Z" + }, + "message": "Serialize errors and warnings in snapshots", + "tree": { + "sha": "869af1b4246b10b959639fbed569293342feff22", + "url": "https://api.github.com/repos/facebook/react/git/trees/869af1b4246b10b959639fbed569293342feff22" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/af1c54ed8f5a528028eac4512f5a33f143a5c5e1", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/ciqwACgkQWCcTbp05\ndTIdiA//TYIhuuUpXOhHA6HBHBaSnaBGOJZYixs694+yNenUn/NhE3qITcltp1aN\nF5Z2qAFNq0G4F2iXAj+fJGHsw801BmYmTSUNbnOelh1u5GhAvM6vud6duZ4hQjZ3\nHA3rDbIJkswiLEhaY2DSOj8S4sWBSX/Jl49XqUdWeuqrR4wxIJjpYJ3QiFcslOq4\nh8YG1C+A9QtVKr1pAAxWRrK6BOCe1PMSQUPm+WCSpUUh4f9mdCzwaEBjd5BMLJWc\nMZ12gVm9T3SMstnMGu4eoESNQbrtR1a+t382J1s+7AEphX0QizOhhmKaWD+NK3zT\ne5vqfAhtf5S3OdNwjkwd3zgNpPOxveCTpAQlATL7AOK8Jpn1E9RCrMpfTMvfixSg\nWp3ODz7i731mIdW3j1Ywvz3K/7J1B60qVfVhdD7oudb4OYOfE2UpJqvl/3ngeY5z\nSLHIBAqL1+4pp+PMdVcU2AhDUOX1KER5L5NG/0FW2mqcbz/P1XK7CptS/dbblKq/\nDIrsdISRTh9HXzV+ozOwSLHNyVKe1PteaaJLMl/fMmnxH8bzsTRRFDNdX1JqfGYl\nSA0ZVXS0YnynskNQOIbG8v1BatfJaItc15sBVvU4WwYfULzVmdyac3N9PZxWv9zL\nrT1JgSiCIzs1RlKaAPdw9WYmH+jripXUv8qym5yM0jWmBFFwtJE=\n=dL8u\n-----END PGP SIGNATURE-----", + "payload": "tree 869af1b4246b10b959639fbed569293342feff22\nparent 35b16ce7d9e14ff5544a4c61293eae1c0ff510eb\nauthor eps1lon 1608288940 +0100\ncommitter eps1lon 1608288940 +0100\n\nSerialize errors and warnings in snapshots\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/af1c54ed8f5a528028eac4512f5a33f143a5c5e1", + "html_url": "https://github.com/facebook/react/commit/af1c54ed8f5a528028eac4512f5a33f143a5c5e1", + "comments_url": "https://api.github.com/repos/facebook/react/commits/af1c54ed8f5a528028eac4512f5a33f143a5c5e1/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "35b16ce7d9e14ff5544a4c61293eae1c0ff510eb", + "url": "https://api.github.com/repos/facebook/react/commits/35b16ce7d9e14ff5544a4c61293eae1c0ff510eb", + "html_url": "https://github.com/facebook/react/commit/35b16ce7d9e14ff5544a4c61293eae1c0ff510eb" + } + ] + }, + { + "sha": "a0a8e060f0a7681abbad222fdd2a0c6e9943185d", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmEwYThlMDYwZjBhNzY4MWFiYmFkMjIyZmRkMmEwYzZlOTk0MzE4NWQ=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T11:22:58Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T11:22:58Z" + }, + "message": "Use statefulStore serializer in \"inline error/warning\" tests", + "tree": { + "sha": "c3b2f6cab2521fe8be6ceb84ca6e614fc86addad", + "url": "https://api.github.com/repos/facebook/react/git/trees/c3b2f6cab2521fe8be6ceb84ca6e614fc86addad" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/a0a8e060f0a7681abbad222fdd2a0c6e9943185d", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/ckRIACgkQWCcTbp05\ndTJo0Q/+LBIgbUD4ORK150MXzleKcy2bmmenA8oJDbNJnVe+WU03JsY30O8xIap8\nm6bSKl29KfJqkh77dPe385DaByxfDe0kdm3NBjUJhWxz56tixOB7yvBGU3droPmn\nQUxbcOFP/KWUj6nv+zCRvMOM8/omj0DeTbT8oQGL4AsrT3/RwMzQXG78hp/mE+qe\nbsQOOEww13ZpMoRS5776apBU3Va2gQ7hzpL0Fy1u28iS2DdzsPRXhjZsS4iHEfN/\nC9ogoycwwvXoA0+2fNSlExCzSfFbjnHD1yns6TXK0I98+UpkjZOoaxHXYlAMtgk+\nijplfasF75Bt59dvQdu6F3Hm5H98gLgwe6GOPumxHidcBs+Q5kNAHG4g4Y23QVrs\njj9Av0dEyxQanPLZt6PBtMeULP8oQGuEzuxvCCz7Q6SbbZnzk53SvjzlLPQCHO9Z\noCU3OU+9nOqen52jZt2xH+sRSxvnBnWKHuyuZycQz8gFQeu0CvGt6X+7nTQc0TTy\nKshAbJqoShQyq2QWH7y4xAUiuSsSyNWhTMaMa6a2N7/GFLpBDfOFORYWoXCyOsIW\nzxTdfXgfkfmyVP4spRIeUCHGLmpheJU3grG1nfia19rBQNSZqqE1cZFHGdSi7DO8\nwltWAu/IZye7KG3s9oSwxUC2s7lq2eG7NxeeKQ8KCKrJMr5Xafg=\n=5WKu\n-----END PGP SIGNATURE-----", + "payload": "tree c3b2f6cab2521fe8be6ceb84ca6e614fc86addad\nparent af1c54ed8f5a528028eac4512f5a33f143a5c5e1\nauthor eps1lon 1608290578 +0100\ncommitter eps1lon 1608290578 +0100\n\nUse statefulStore serializer in \"inline error/warning\" tests\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/a0a8e060f0a7681abbad222fdd2a0c6e9943185d", + "html_url": "https://github.com/facebook/react/commit/a0a8e060f0a7681abbad222fdd2a0c6e9943185d", + "comments_url": "https://api.github.com/repos/facebook/react/commits/a0a8e060f0a7681abbad222fdd2a0c6e9943185d/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "af1c54ed8f5a528028eac4512f5a33f143a5c5e1", + "url": "https://api.github.com/repos/facebook/react/commits/af1c54ed8f5a528028eac4512f5a33f143a5c5e1", + "html_url": "https://github.com/facebook/react/commit/af1c54ed8f5a528028eac4512f5a33f143a5c5e1" + } + ] + }, + { + "sha": "5b5d2c52ae2b02e57e9bbfb95cc55933a51ed79d", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjViNWQyYzUyYWUyYjAyZTU3ZTliYmZiOTVjYzU1OTMzYTUxZWQ3OWQ=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T11:27:22Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T11:27:22Z" + }, + "message": "Fix flow issues", + "tree": { + "sha": "da0e5f6c1164d81a0ccbfa4910101d86a0ccda59", + "url": "https://api.github.com/repos/facebook/react/git/trees/da0e5f6c1164d81a0ccbfa4910101d86a0ccda59" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/5b5d2c52ae2b02e57e9bbfb95cc55933a51ed79d", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/ckhoACgkQWCcTbp05\ndTI7xQ//fo+/xEv/NpQEdB+EVwXIS4GByuyOE4ZW8KLKcK+xyPpwYz+rzOwgw02t\nyyBZpuSgQ3F1QwUeRDJYxBSzUZeToKAkUzMBuSPDsnehy2sIjn/0NTAZjATwDaL1\npP+kMe4hyhH/+Hb++SNnN0ldg670g98eFUtSx5bV8i0DqSPhtk/aBBZXI0NSmUPS\nDRxhrfB5srQlLlNCE92wFf/W0Mmhni0+fqpTh4JjUGHZdoajp7/FMud+HeXJrUaS\nqF2WyYitCTqtPYHIhiffdyGUy9SYxPNS40r6IWzpcQgEBPQ5qAPmrAzLIAMhM+de\nTCamA797+qG6+XGB/KPrQ/d7sIel0cDDyJFyJA2JXpy7RWNxL1rI27/a7e3ZhRyg\nF8YtbaEiSuM58oqfTkB0nlDRlRtyPMjf6VW/8lsrxDdsgD+DnibFi+mTa1TDNvPr\n9mRvGz08NcvLQ6WoDoj3kgm4yFNL/Cd7YEkGsim2+gxQs9lcbmiAx5LB7vtISyHP\nPA5Db+6ndh7xEET+s+MhPtapPnZZ0C9FZyKEUcyZJWo8n0XErF2sG9xykeHcetSW\nOdJTR8A0a6uoLbLfwTCrliSZmXNeKT6vJhfKYcDRg56iHGqEklXbrK+AC7pi/TEN\nx8s1asUGeoe9iNoH4sOCz0Y1cvAD/KSFEmOABZvu+ChC2ksA2bE=\n=qIHU\n-----END PGP SIGNATURE-----", + "payload": "tree da0e5f6c1164d81a0ccbfa4910101d86a0ccda59\nparent a0a8e060f0a7681abbad222fdd2a0c6e9943185d\nauthor eps1lon 1608290842 +0100\ncommitter eps1lon 1608290842 +0100\n\nFix flow issues\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/5b5d2c52ae2b02e57e9bbfb95cc55933a51ed79d", + "html_url": "https://github.com/facebook/react/commit/5b5d2c52ae2b02e57e9bbfb95cc55933a51ed79d", + "comments_url": "https://api.github.com/repos/facebook/react/commits/5b5d2c52ae2b02e57e9bbfb95cc55933a51ed79d/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "a0a8e060f0a7681abbad222fdd2a0c6e9943185d", + "url": "https://api.github.com/repos/facebook/react/commits/a0a8e060f0a7681abbad222fdd2a0c6e9943185d", + "html_url": "https://github.com/facebook/react/commit/a0a8e060f0a7681abbad222fdd2a0c6e9943185d" + } + ] + }, + { + "sha": "4a8738371577f8d4cb8222c1fe49580f47206bdc", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjRhODczODM3MTU3N2Y4ZDRjYjgyMjJjMWZlNDk1ODBmNDcyMDZiZGM=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T13:27:39Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T13:27:39Z" + }, + "message": "Refactored treeContext tests to use inline snapshots and Jest serializer", + "tree": { + "sha": "177aab2fa080508dd16c3f9c7ad1ff36b13e40ad", + "url": "https://api.github.com/repos/facebook/react/git/trees/177aab2fa080508dd16c3f9c7ad1ff36b13e40ad" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/4a8738371577f8d4cb8222c1fe49580f47206bdc", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/4a8738371577f8d4cb8222c1fe49580f47206bdc", + "html_url": "https://github.com/facebook/react/commit/4a8738371577f8d4cb8222c1fe49580f47206bdc", + "comments_url": "https://api.github.com/repos/facebook/react/commits/4a8738371577f8d4cb8222c1fe49580f47206bdc/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "5b5d2c52ae2b02e57e9bbfb95cc55933a51ed79d", + "url": "https://api.github.com/repos/facebook/react/commits/5b5d2c52ae2b02e57e9bbfb95cc55933a51ed79d", + "html_url": "https://github.com/facebook/react/commit/5b5d2c52ae2b02e57e9bbfb95cc55933a51ed79d" + } + ] + }, + { + "sha": "781d8b7775fd0aa619d60471939da3a7573d5fba", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjc4MWQ4Yjc3NzVmZDBhYTYxOWQ2MDQ3MTkzOWRhM2E3NTczZDVmYmE=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T15:56:55Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T15:56:55Z" + }, + "message": "Inline warnings/errors should respect component filters\n\nAdjusting filters should also update errors/warnings.\n\nAlso optimized the filter update flow by adding a REMOVE_ALL operation.\n\nLots of new tests added.", + "tree": { + "sha": "26ab0ffec17099d37d8a7bc89e19944682284ddd", + "url": "https://api.github.com/repos/facebook/react/git/trees/26ab0ffec17099d37d8a7bc89e19944682284ddd" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/781d8b7775fd0aa619d60471939da3a7573d5fba", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/781d8b7775fd0aa619d60471939da3a7573d5fba", + "html_url": "https://github.com/facebook/react/commit/781d8b7775fd0aa619d60471939da3a7573d5fba", + "comments_url": "https://api.github.com/repos/facebook/react/commits/781d8b7775fd0aa619d60471939da3a7573d5fba/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "4a8738371577f8d4cb8222c1fe49580f47206bdc", + "url": "https://api.github.com/repos/facebook/react/commits/4a8738371577f8d4cb8222c1fe49580f47206bdc", + "html_url": "https://github.com/facebook/react/commit/4a8738371577f8d4cb8222c1fe49580f47206bdc" + } + ] + }, + { + "sha": "e69c0b4f9a38f1dff6f1d29403bc7503be929e80", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmU2OWMwYjRmOWEzOGYxZGZmNmYxZDI5NDAzYmM3NTAzYmU5MjllODA=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T16:18:28Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T16:18:28Z" + }, + "message": "Add error/warning counts to serialized store snapshot\n\nThis lets us remove ugly expect-map-to-be statements", + "tree": { + "sha": "fb54811e8b4dd6f6ea7a3a4587856f79a2601999", + "url": "https://api.github.com/repos/facebook/react/git/trees/fb54811e8b4dd6f6ea7a3a4587856f79a2601999" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/e69c0b4f9a38f1dff6f1d29403bc7503be929e80", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/e69c0b4f9a38f1dff6f1d29403bc7503be929e80", + "html_url": "https://github.com/facebook/react/commit/e69c0b4f9a38f1dff6f1d29403bc7503be929e80", + "comments_url": "https://api.github.com/repos/facebook/react/commits/e69c0b4f9a38f1dff6f1d29403bc7503be929e80/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "781d8b7775fd0aa619d60471939da3a7573d5fba", + "url": "https://api.github.com/repos/facebook/react/commits/781d8b7775fd0aa619d60471939da3a7573d5fba", + "html_url": "https://github.com/facebook/react/commit/781d8b7775fd0aa619d60471939da3a7573d5fba" + } + ] + }, + { + "sha": "bc788d82066ece29b858a072c8da262babf78202", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmJjNzg4ZDgyMDY2ZWNlMjliODU4YTA3MmM4ZGEyNjJiYWJmNzgyMDI=", + "commit": { + "author": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T17:45:47Z" + }, + "committer": { + "name": "eps1lon", + "email": "silbermann.sebastian@gmail.com", + "date": "2020-12-18T17:45:47Z" + }, + "message": "Add failing test for uncommitted renders", + "tree": { + "sha": "6dc204c237700e69a1355e75d4dd6a686f591493", + "url": "https://api.github.com/repos/facebook/react/git/trees/6dc204c237700e69a1355e75d4dd6a686f591493" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/bc788d82066ece29b858a072c8da262babf78202", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEdEe9h5RnyYkE768zWCcTbp05dTIFAl/c6ssACgkQWCcTbp05\ndTI/HBAAsX1qdq2tZs+1ifhylxrcH1UvbR+AaDZ1dDp7RHXiJ23WnQURRiFq1u2g\nQpBv6wLTxTi58dLXG0VQbw6rwvYvjseramKirD5t4JefEInGWp2cP6HOykW66GrX\nFK90Ut4CXNsJcNkXj/DQoSJYRPxgf8Rq0AbH3KODPTgRNTjFDgv6bJtsFJtGIQbV\nAljLW2SMrT8C7q+kt8NWsRyMrpvMCU1Nn5DrFxclBIni4lNj5dwmx6AgTC/5UKiN\nX0DyXtc2Y6/6fV78AN6wvghUHoZ1wxfMb0RA1LxEbWFfE/Lepz9uTjbRgbzG9l9p\nF59SKT0tbHB2CulqpKoy4LxT6TP9l2D8ucdfgm39nO+C5c96QVEDvOOaA5rFAVd5\nL9rjcqjkttbNyGFcFCfETY5IDed5ss/7K/LjcbY09JtVSRFbD2At8qVT9pK3J6hm\ni5Q78X1wq/3dWbL0zdDe19lyhKDxRwUNVdi5rpdiSl/Z2iP0OXFq3t1wyqvT1hNS\nZ3nrIKUqVIIm3P12YtXxupd37wlWQboR4CVIzld8zaqyIjA0/aMSDKaSc0cjLfdj\nImB0r93Phx9RrxDnfRNSzw2dqJOv2r+9GKCrJLt2UBJccLvCmYncygsmKCrtM1im\nWU0X3N0Ty+y0QMIDzhT9z7pA6Wf3+Nrszk5dirQAZhc/NsoaNhA=\n=WqmE\n-----END PGP SIGNATURE-----", + "payload": "tree 6dc204c237700e69a1355e75d4dd6a686f591493\nparent e69c0b4f9a38f1dff6f1d29403bc7503be929e80\nauthor eps1lon 1608313547 +0100\ncommitter eps1lon 1608313547 +0100\n\nAdd failing test for uncommitted renders\n", + "verified_at": "2024-11-11T02:11:17Z" + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/bc788d82066ece29b858a072c8da262babf78202", + "html_url": "https://github.com/facebook/react/commit/bc788d82066ece29b858a072c8da262babf78202", + "comments_url": "https://api.github.com/repos/facebook/react/commits/bc788d82066ece29b858a072c8da262babf78202/comments", + "author": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "eps1lon", + "id": 12292047, + "node_id": "MDQ6VXNlcjEyMjkyMDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/12292047?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/eps1lon", + "html_url": "https://github.com/eps1lon", + "followers_url": "https://api.github.com/users/eps1lon/followers", + "following_url": "https://api.github.com/users/eps1lon/following{/other_user}", + "gists_url": "https://api.github.com/users/eps1lon/gists{/gist_id}", + "starred_url": "https://api.github.com/users/eps1lon/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/eps1lon/subscriptions", + "organizations_url": "https://api.github.com/users/eps1lon/orgs", + "repos_url": "https://api.github.com/users/eps1lon/repos", + "events_url": "https://api.github.com/users/eps1lon/events{/privacy}", + "received_events_url": "https://api.github.com/users/eps1lon/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "e69c0b4f9a38f1dff6f1d29403bc7503be929e80", + "url": "https://api.github.com/repos/facebook/react/commits/e69c0b4f9a38f1dff6f1d29403bc7503be929e80", + "html_url": "https://github.com/facebook/react/commit/e69c0b4f9a38f1dff6f1d29403bc7503be929e80" + } + ] + }, + { + "sha": "22c3f4bf903ec46e4915557d605f0433a3963900", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjIyYzNmNGJmOTAzZWM0NmU0OTE1NTU3ZDYwNWYwNDMzYTM5NjM5MDA=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T18:52:42Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T18:52:42Z" + }, + "message": "Don't send errors/warnings for suspended or errored subtrees", + "tree": { + "sha": "b3f879545ab4eda881278f299ca522e74a977c1b", + "url": "https://api.github.com/repos/facebook/react/git/trees/b3f879545ab4eda881278f299ca522e74a977c1b" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/22c3f4bf903ec46e4915557d605f0433a3963900", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/22c3f4bf903ec46e4915557d605f0433a3963900", + "html_url": "https://github.com/facebook/react/commit/22c3f4bf903ec46e4915557d605f0433a3963900", + "comments_url": "https://api.github.com/repos/facebook/react/commits/22c3f4bf903ec46e4915557d605f0433a3963900/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "bc788d82066ece29b858a072c8da262babf78202", + "url": "https://api.github.com/repos/facebook/react/commits/bc788d82066ece29b858a072c8da262babf78202", + "html_url": "https://github.com/facebook/react/commit/bc788d82066ece29b858a072c8da262babf78202" + } + ] + }, + { + "sha": "b9fa573d12b29f79db11663cf40b84c66ce37d03", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOmI5ZmE1NzNkMTJiMjlmNzlkYjExNjYzY2Y0MGI4NGM2NmNlMzdkMDM=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T19:18:19Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T19:18:19Z" + }, + "message": "Wait to flush errors/warnings until the next commit; it's safer", + "tree": { + "sha": "2b76ccc4079a89072bb22d7ba99941fe0463258f", + "url": "https://api.github.com/repos/facebook/react/git/trees/2b76ccc4079a89072bb22d7ba99941fe0463258f" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/b9fa573d12b29f79db11663cf40b84c66ce37d03", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/b9fa573d12b29f79db11663cf40b84c66ce37d03", + "html_url": "https://github.com/facebook/react/commit/b9fa573d12b29f79db11663cf40b84c66ce37d03", + "comments_url": "https://api.github.com/repos/facebook/react/commits/b9fa573d12b29f79db11663cf40b84c66ce37d03/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "22c3f4bf903ec46e4915557d605f0433a3963900", + "url": "https://api.github.com/repos/facebook/react/commits/22c3f4bf903ec46e4915557d605f0433a3963900", + "html_url": "https://github.com/facebook/react/commit/22c3f4bf903ec46e4915557d605f0433a3963900" + } + ] + }, + { + "sha": "4c9bf6508bd8c0bf61802b1da7641f6295d19f68", + "node_id": "MDY6Q29tbWl0MTUyODQ3MzAwOjRjOWJmNjUwOGJkOGMwYmY2MTgwMmIxZGE3NjQxZjYyOTVkMTlmNjg=", + "commit": { + "author": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T20:10:42Z" + }, + "committer": { + "name": "Brian Vaughn", + "email": "bvaughn@fb.com", + "date": "2020-12-18T20:10:42Z" + }, + "message": "Remove separate 'errorsAndWarnings' event in favor of 'mutations'\n\nThis avoids the case of one being updated without the other.", + "tree": { + "sha": "9484071f785ba18dfeeec8f4d6069ca9915b02c6", + "url": "https://api.github.com/repos/facebook/react/git/trees/9484071f785ba18dfeeec8f4d6069ca9915b02c6" + }, + "url": "https://api.github.com/repos/facebook/react/git/commits/4c9bf6508bd8c0bf61802b1da7641f6295d19f68", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/facebook/react/commits/4c9bf6508bd8c0bf61802b1da7641f6295d19f68", + "html_url": "https://github.com/facebook/react/commit/4c9bf6508bd8c0bf61802b1da7641f6295d19f68", + "comments_url": "https://api.github.com/repos/facebook/react/commits/4c9bf6508bd8c0bf61802b1da7641f6295d19f68/comments", + "author": null, + "committer": null, + "parents": [ + { + "sha": "b9fa573d12b29f79db11663cf40b84c66ce37d03", + "url": "https://api.github.com/repos/facebook/react/commits/b9fa573d12b29f79db11663cf40b84c66ce37d03", + "html_url": "https://github.com/facebook/react/commit/b9fa573d12b29f79db11663cf40b84c66ce37d03" + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/pull_request_review_comment_reply.json b/src/test/resources/com/spotify/github/v3/clients/pull_request_review_comment_reply.json new file mode 100644 index 00000000..361fcded --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/pull_request_review_comment_reply.json @@ -0,0 +1,56 @@ +{ + "url": "https://api.github.com/repos/octocat/Hello-World/pulls/comments/1", + "pull_request_review_id": 42, + "id": 10, + "node_id": "MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDEw", + "diff_hunk": "@@ -16,33 +16,40 @@ public class Connection : IConnection...", + "path": "file1.txt", + "position": 1, + "original_position": 4, + "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "original_commit_id": "9c48853fa3dc5c1c3d6f1f1cd1f2743e72652840", + "in_reply_to_id": 426899381, + "user": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "body": "Great stuff!", + "created_at": "2011-04-14T16:00:49Z", + "updated_at": "2011-04-14T16:00:49Z", + "html_url": "https://github.com/octocat/Hello-World/pull/1#discussion-diff-1", + "pull_request_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1", + "author_association": "NONE", + "_links": { + "self": { + "href": "https://api.github.com/repos/octocat/Hello-World/pulls/comments/1" + }, + "html": { + "href": "https://github.com/octocat/Hello-World/pull/1#discussion-diff-1" + }, + "pull_request": { + "href": "https://api.github.com/repos/octocat/Hello-World/pulls/1" + } + }, + "start_line": 1, + "original_start_line": 1, + "start_side": "RIGHT", + "line": 2, + "original_line": 2, + "side": "RIGHT" + } diff --git a/src/test/resources/com/spotify/github/v3/clients/recursive-tree.json b/src/test/resources/com/spotify/github/v3/clients/recursive-tree.json new file mode 100644 index 00000000..9cd1876d --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/recursive-tree.json @@ -0,0 +1,61 @@ +{ + "sha": "9c27bd92524e2b57b569d4c86695b3993d9b8f9f", + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/trees/9c27bd92524e2b57b569d4c86695b3993d9b8f9f", + "tree": [ + { + "path": "README.md", + "mode": "100644", + "type": "blob", + "sha": "6e091fd045dc88806e5c70357326af7fa0e1ccde", + "size": 12, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/6e091fd045dc88806e5c70357326af7fa0e1ccde" + }, + { + "path": "UserGeneratedContentUtils.java", + "mode": "100644", + "type": "blob", + "sha": "77b3e188803e717a0a5ce53b818b0ed6fb6b8a23", + "size": 5340, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/77b3e188803e717a0a5ce53b818b0ed6fb6b8a23" + }, + { + "path": "readme.md", + "mode": "100644", + "type": "blob", + "sha": "f09eac953086b8760f600822f057141d7b311165", + "size": 29, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/f09eac953086b8760f600822f057141d7b311165" + }, + { + "path": "src", + "mode": "040000", + "type": "tree", + "sha": "26cd96e1394d6c4982d8cec879ad4fefa423bec8", + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/trees/26cd96e1394d6c4982d8cec879ad4fefa423bec8" + }, + { + "path": "src/public", + "mode": "040000", + "type": "tree", + "sha": "a296470ebb9709103115d6e21d9bc54cdfc8b120", + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/trees/a296470ebb9709103115d6e21d9bc54cdfc8b120" + }, + { + "path": "src/public/UserGeneratedContentUtils.java", + "mode": "100644", + "type": "blob", + "sha": "77b3e188803e717a0a5ce53b818b0ed6fb6b8a23", + "size": 5340, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/77b3e188803e717a0a5ce53b818b0ed6fb6b8a23" + }, + { + "path": "src/public/test.txt", + "mode": "100644", + "type": "blob", + "sha": "c57eff55ebc0c54973903af5f72bac72762cf4f4", + "size": 12, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/c57eff55ebc0c54973903af5f72bac72762cf4f4" + } + ], + "truncated": false +} diff --git a/src/test/resources/com/spotify/github/v3/clients/reference.json b/src/test/resources/com/spotify/github/v3/clients/reference.json index 31bd8fd6..72642201 100644 --- a/src/test/resources/com/spotify/github/v3/clients/reference.json +++ b/src/test/resources/com/spotify/github/v3/clients/reference.json @@ -7,4 +7,4 @@ "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" } -} \ No newline at end of file +} diff --git a/src/test/resources/com/spotify/github/v3/clients/repository_invitation.json b/src/test/resources/com/spotify/github/v3/clients/repository_invitation.json new file mode 100644 index 00000000..3b8849f9 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/repository_invitation.json @@ -0,0 +1,117 @@ +{ + "id": 1, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "repository": { + "id": 1296269, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/octocat/Hello-World", + "description": "This your first repo!", + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_url": "git:github.com/octocat/Hello-World.git", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "hooks_url": "http://api.github.com/repos/octocat/Hello-World/hooks" + }, + "invitee": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "inviter": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "permissions": "write", + "created_at": "2016-06-13T14:52:50-05:00", + "url": "https://api.github.com/user/repository_invitations/1296269", + "html_url": "https://github.com/octocat/Hello-World/invitations" +} diff --git a/src/test/resources/com/spotify/github/v3/clients/shalink.json b/src/test/resources/com/spotify/github/v3/clients/shalink.json new file mode 100644 index 00000000..34e01e37 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/shalink.json @@ -0,0 +1,4 @@ +{ + "sha": "8fc4e0fe57752b892a921806a1352e4cc72dff37", + "url": "https://github.com/octocat/Hello-World/repos/someowner/somerepo/git/blobs/8fc4e0fe57752b892a921806a1352e4cc72dff37" +} diff --git a/src/test/resources/com/spotify/github/v3/clients/team_get.json b/src/test/resources/com/spotify/github/v3/clients/team_get.json new file mode 100644 index 00000000..fc7c4810 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/team_get.json @@ -0,0 +1,49 @@ +{ + "id": 1, + "node_id": "MDQ6VGVhbTE=", + "url": "https://api.github.com/teams/1", + "html_url": "https://github.com/orgs/github/teams/justice-league", + "name": "Justice League", + "slug": "justice-league", + "description": "A great team.", + "privacy": "closed", + "notification_setting": "notifications_enabled", + "permission": "admin", + "members_url": "https://api.github.com/teams/1/members{/member}", + "repositories_url": "https://api.github.com/teams/1/repos", + "parent": null, + "members_count": 3, + "repos_count": 10, + "created_at": "2017-07-14T16:53:42Z", + "updated_at": "2017-08-17T12:37:15Z", + "organization": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "hooks_url": "https://api.github.com/orgs/github/hooks", + "issues_url": "https://api.github.com/orgs/github/issues", + "members_url": "https://api.github.com/orgs/github/members{/member}", + "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "description": "A great organization", + "name": "github", + "company": "GitHub", + "blog": "https://github.com/blog", + "location": "San Francisco", + "email": "octocat@github.com", + "is_verified": true, + "has_organization_projects": true, + "has_repository_projects": true, + "public_repos": 2, + "public_gists": 1, + "followers": 20, + "following": 0, + "html_url": "https://github.com/octocat", + "created_at": "2008-01-14T04:33:35Z", + "updated_at": "2017-08-17T12:37:15Z", + "type": "Organization" + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/teams_list.json b/src/test/resources/com/spotify/github/v3/clients/teams_list.json new file mode 100644 index 00000000..176196f5 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/teams_list.json @@ -0,0 +1,100 @@ +[ + { + "id": 1, + "node_id": "MDQ6VGVhbTE=", + "url": "https://api.github.com/teams/1", + "html_url": "https://github.com/orgs/github/teams/justice-league", + "name": "Justice League", + "slug": "justice-league", + "description": "A great team.", + "privacy": "closed", + "notification_setting": "notifications_enabled", + "permission": "admin", + "members_url": "https://api.github.com/teams/1/members{/member}", + "repositories_url": "https://api.github.com/teams/1/repos", + "parent": null, + "members_count": 3, + "repos_count": 10, + "created_at": "2017-07-14T16:53:42Z", + "updated_at": "2017-08-17T12:37:15Z", + "organization": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "hooks_url": "https://api.github.com/orgs/github/hooks", + "issues_url": "https://api.github.com/orgs/github/issues", + "members_url": "https://api.github.com/orgs/github/members{/member}", + "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "description": "A great organization", + "name": "github", + "company": "GitHub", + "blog": "https://github.com/blog", + "location": "San Francisco", + "email": "octocat@github.com", + "is_verified": true, + "has_organization_projects": true, + "has_repository_projects": true, + "public_repos": 2, + "public_gists": 1, + "followers": 20, + "following": 0, + "html_url": "https://github.com/octocat", + "created_at": "2008-01-14T04:33:35Z", + "updated_at": "2017-08-17T12:37:15Z", + "type": "Organization" + } + }, + { + "id": 2, + "node_id": "MDQ6VGVhbTE=", + "url": "https://api.github.com/teams/2", + "html_url": "https://github.com/orgs/github/teams/x-men", + "name": "X-Men", + "slug": "x-men", + "description": "A x-cellent team.", + "privacy": "closed", + "notification_setting": "notifications_enabled", + "permission": "admin", + "members_url": "https://api.github.com/teams/2/members{/member}", + "repositories_url": "https://api.github.com/teams/1/repos", + "parent": null, + "members_count": 3, + "repos_count": 10, + "created_at": "2017-07-14T16:53:42Z", + "updated_at": "2017-08-17T12:37:15Z", + "organization": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "hooks_url": "https://api.github.com/orgs/github/hooks", + "issues_url": "https://api.github.com/orgs/github/issues", + "members_url": "https://api.github.com/orgs/github/members{/member}", + "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "description": "A great organization", + "name": "github", + "company": "GitHub", + "blog": "https://github.com/blog", + "location": "San Francisco", + "email": "octocat@github.com", + "is_verified": true, + "has_organization_projects": true, + "has_repository_projects": true, + "public_repos": 2, + "public_gists": 1, + "followers": 20, + "following": 0, + "html_url": "https://github.com/octocat", + "created_at": "2008-01-14T04:33:35Z", + "updated_at": "2017-08-17T12:37:15Z", + "type": "Organization" + } + } +] \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/teams_patch.json b/src/test/resources/com/spotify/github/v3/clients/teams_patch.json new file mode 100644 index 00000000..f6f37cff --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/teams_patch.json @@ -0,0 +1,3 @@ +{ + "name": "Justice League2" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/teams_patch_response.json b/src/test/resources/com/spotify/github/v3/clients/teams_patch_response.json new file mode 100644 index 00000000..da527c2d --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/teams_patch_response.json @@ -0,0 +1,49 @@ +{ + "id": 1, + "node_id": "MDQ6VGVhbTE=", + "url": "https://api.github.com/teams/1", + "html_url": "https://github.com/orgs/github/teams/justice-league", + "name": "Justice League2", + "slug": "justice-league", + "description": "A great team.", + "privacy": "closed", + "notification_setting": "notifications_enabled", + "permission": "admin", + "members_url": "https://api.github.com/teams/1/members{/member}", + "repositories_url": "https://api.github.com/teams/1/repos", + "parent": null, + "members_count": 3, + "repos_count": 10, + "created_at": "2017-07-14T16:53:42Z", + "updated_at": "2017-08-17T12:37:15Z", + "organization": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "hooks_url": "https://api.github.com/orgs/github/hooks", + "issues_url": "https://api.github.com/orgs/github/issues", + "members_url": "https://api.github.com/orgs/github/members{/member}", + "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "description": "A great organization", + "name": "github", + "company": "GitHub", + "blog": "https://github.com/blog", + "location": "San Francisco", + "email": "octocat@github.com", + "is_verified": true, + "has_organization_projects": true, + "has_repository_projects": true, + "public_repos": 2, + "public_gists": 1, + "followers": 20, + "following": 0, + "html_url": "https://github.com/octocat", + "created_at": "2008-01-14T04:33:35Z", + "updated_at": "2017-08-17T12:37:15Z", + "type": "Organization" + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/teams_request.json b/src/test/resources/com/spotify/github/v3/clients/teams_request.json new file mode 100644 index 00000000..57143515 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/teams_request.json @@ -0,0 +1,3 @@ +{ + "name": "Justice League" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/clients/tree.json b/src/test/resources/com/spotify/github/v3/clients/tree.json new file mode 100644 index 00000000..93751c8b --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/clients/tree.json @@ -0,0 +1,38 @@ +{ + "sha": "9c27bd92524e2b57b569d4c86695b3993d9b8f9f", + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/trees/9c27bd92524e2b57b569d4c86695b3993d9b8f9f", + "tree": [ + { + "path": "README.md", + "mode": "100644", + "type": "blob", + "sha": "6e091fd045dc88806e5c70357326af7fa0e1ccde", + "size": 12, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/6e091fd045dc88806e5c70357326af7fa0e1ccde" + }, + { + "path": "UserGeneratedContentUtils.java", + "mode": "100644", + "type": "blob", + "sha": "77b3e188803e717a0a5ce53b818b0ed6fb6b8a23", + "size": 5340, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/77b3e188803e717a0a5ce53b818b0ed6fb6b8a23" + }, + { + "path": "readme.md", + "mode": "100644", + "type": "blob", + "sha": "f09eac953086b8760f600822f057141d7b311165", + "size": 29, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/f09eac953086b8760f600822f057141d7b311165" + }, + { + "path": "src", + "mode": "040000", + "type": "tree", + "sha": "26cd96e1394d6c4982d8cec879ad4fefa423bec8", + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/trees/26cd96e1394d6c4982d8cec879ad4fefa423bec8" + } + ], + "truncated": false +} diff --git a/src/test/resources/com/spotify/github/v3/git/tree.json b/src/test/resources/com/spotify/github/v3/git/tree.json new file mode 100644 index 00000000..93751c8b --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/git/tree.json @@ -0,0 +1,38 @@ +{ + "sha": "9c27bd92524e2b57b569d4c86695b3993d9b8f9f", + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/trees/9c27bd92524e2b57b569d4c86695b3993d9b8f9f", + "tree": [ + { + "path": "README.md", + "mode": "100644", + "type": "blob", + "sha": "6e091fd045dc88806e5c70357326af7fa0e1ccde", + "size": 12, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/6e091fd045dc88806e5c70357326af7fa0e1ccde" + }, + { + "path": "UserGeneratedContentUtils.java", + "mode": "100644", + "type": "blob", + "sha": "77b3e188803e717a0a5ce53b818b0ed6fb6b8a23", + "size": 5340, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/77b3e188803e717a0a5ce53b818b0ed6fb6b8a23" + }, + { + "path": "readme.md", + "mode": "100644", + "type": "blob", + "sha": "f09eac953086b8760f600822f057141d7b311165", + "size": 29, + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/blobs/f09eac953086b8760f600822f057141d7b311165" + }, + { + "path": "src", + "mode": "040000", + "type": "tree", + "sha": "26cd96e1394d6c4982d8cec879ad4fefa423bec8", + "url": "https://ghe.spotify.net/api/v3/repos/ugc-sharing/test-repo/git/trees/26cd96e1394d6c4982d8cec879ad4fefa423bec8" + } + ], + "truncated": false +} diff --git a/src/test/resources/com/spotify/github/v3/git/treeItem.json b/src/test/resources/com/spotify/github/v3/git/treeItem.json new file mode 100644 index 00000000..dca7c6d9 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/git/treeItem.json @@ -0,0 +1,8 @@ +{ + "path": "README.md", + "mode": "100644", + "type": "blob", + "sha": "6e091fd045dc88806e5c70357326af7fa0e1ccde", + "size": 12, + "url": "https://github.com/octocat/Hello-World/repos/someowner/somerepo/git/blobs/6e091fd045dc88806e5c70357326af7fa0e1ccde" +} diff --git a/src/test/resources/com/spotify/github/v3/githubapp/access-token.json b/src/test/resources/com/spotify/github/v3/githubapp/access-token.json new file mode 100644 index 00000000..0085c41a --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/githubapp/access-token.json @@ -0,0 +1,4 @@ +{ + "token": "ghs_16C7e42F292c6912E7710c838347Ae178B4a", + "expires_at": "2024-08-10T05:54:58Z" +} diff --git a/src/test/resources/com/spotify/github/v3/githubapp/authenticated-app.json b/src/test/resources/com/spotify/github/v3/githubapp/authenticated-app.json new file mode 100644 index 00000000..b5dc8eab --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/githubapp/authenticated-app.json @@ -0,0 +1,43 @@ +{ + "id": 1, + "slug": "octoapp", + "node_id": "MDExOkludGVncmF0aW9uMQ==", + "owner": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "User", + "site_admin": true + }, + "name": "Octocat App", + "description": "Test GitHub App", + "external_url": "https://example.com", + "html_url": "https://github.com/apps/octoapp", + "created_at": "2017-07-08T16:18:44-04:00", + "updated_at": "2017-07-08T16:18:44-04:00", + "permissions": { + "metadata": "read", + "contents": "read", + "issues": "write", + "single_file": "write" + }, + "events": ["push", "pull_request"], + "installations_count": 5, + "client_id": "Iv1.8a61f9b3a7aba766", + "client_secret": "1726be1638095a19edd134c77bde3aa2ece1e5d8", + "webhook_secret": "e340154128314309424b7c8e90325147d99fdafa", + "pem": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAuEPzOUE+kiEH1WLiMeBytTEF856j0hgg...\n-----END RSA PRIVATE KEY-----" +} diff --git a/src/test/resources/com/spotify/github/v3/githubapp/installation.json b/src/test/resources/com/spotify/github/v3/githubapp/installation.json new file mode 100644 index 00000000..6d2fcbfb --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/githubapp/installation.json @@ -0,0 +1,34 @@ +{ + "id": 1, + "account": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "hooks_url": "https://api.github.com/orgs/github/hooks", + "issues_url": "https://api.github.com/orgs/github/issues", + "members_url": "https://api.github.com/orgs/github/members{/member}", + "public_members_url": "https://api.github.com/orgs/github/public_members{/member}", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "description": "A great organization" + }, + "access_tokens_url": "https://api.github.com/installations/1/access_tokens", + "repositories_url": "https://api.github.com/installation/repositories", + "html_url": "https://github.com/organizations/github/settings/installations/1", + "app_id": 1, + "target_id": 1, + "target_type": "Organization", + "permissions": { + "metadata": "read", + "contents": "read", + "issues": "write", + "single_file": "write" + }, + "events": [ + "push", + "pull_request" + ], + "single_file_name": "config.yml" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/hooks/requests/pull-request-closed.json b/src/test/resources/com/spotify/github/v3/hooks/requests/pull-request-closed.json index 3dae534b..4bbb9113 100644 --- a/src/test/resources/com/spotify/github/v3/hooks/requests/pull-request-closed.json +++ b/src/test/resources/com/spotify/github/v3/hooks/requests/pull-request-closed.json @@ -4,6 +4,7 @@ "pull_request": { "url": "https://github.com/api/v3/repos/abba/custom-abba-metric-web/pulls/1", "id": 320629, + "node_id": "MDExOlB1bGxSZXF1ZXN0NDI3NDI0Nw==", "html_url": "https://github.com/abba/custom-abba-metric-web/pull/1", "diff_url": "https://github.com/abba/custom-abba-metric-web/pull/1.diff", "patch_url": "https://github.com/abba/custom-abba-metric-web/pull/1.patch", diff --git a/src/test/resources/com/spotify/github/v3/issues/issue.json b/src/test/resources/com/spotify/github/v3/issues/issue.json index 00e88af9..768fcdbf 100644 --- a/src/test/resources/com/spotify/github/v3/issues/issue.json +++ b/src/test/resources/com/spotify/github/v3/issues/issue.json @@ -1,5 +1,6 @@ { - "id": 1, + "id": 2, + "node_id": "MDU6SXNzdWUx", "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347", "repository_url": "https://api.github.com/repos/octocat/Hello-World", "labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}", @@ -13,6 +14,7 @@ "user": { "login": "octocat", "id": 1, + "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", @@ -31,14 +33,19 @@ }, "labels": [ { + "id": 208045946, + "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=", "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug", "name": "bug", - "color": "f29513" + "description": "Something isn't working", + "color": "f29513", + "default": true } ], "assignee": { "login": "octocat", "id": 1, + "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", @@ -55,11 +62,34 @@ "type": "User", "site_admin": false }, + "assignees": [ + { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + } + ], "milestone": { "url": "https://api.github.com/repos/octocat/Hello-World/milestones/1", "html_url": "https://github.com/octocat/Hello-World/milestones/v1.0", "labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels", "id": 1002604, + "node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==", "number": 1, "state": "open", "title": "v1.0", @@ -67,6 +97,7 @@ "creator": { "login": "octocat", "id": 1, + "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "", "url": "https://api.github.com/users/octocat", @@ -90,7 +121,8 @@ "closed_at": "2013-02-12T13:22:01Z", "due_on": "2012-10-09T23:39:01Z" }, - "locked": false, + "locked": true, + "active_lock_reason": "too heated", "comments": 0, "pull_request": { "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347", @@ -100,5 +132,27 @@ }, "closed_at": null, "created_at": "2011-04-22T13:33:48Z", - "updated_at": "2011-04-22T13:33:48Z" + "updated_at": "2011-04-22T13:33:48Z", + "closed_by": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "author_association": "COLLABORATOR", + "state_reason": "completed" } \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/prs/pull_request.json b/src/test/resources/com/spotify/github/v3/prs/pull_request.json index 007c8266..7d1014db 100644 --- a/src/test/resources/com/spotify/github/v3/prs/pull_request.json +++ b/src/test/resources/com/spotify/github/v3/prs/pull_request.json @@ -1,6 +1,7 @@ { "id": 1, "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347", + "node_id": "MDExOlB1bGxSZXF1ZXN0NDI3NDI0Nw==", "html_url": "https://github.com/octocat/Hello-World/pull/1347", "diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff", "patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch", @@ -68,6 +69,16 @@ "closed_at": "2013-02-12T13:22:01Z", "due_on": "2012-10-09T23:39:01Z" }, + "labels": [ + { + "id": 42, + "node_id": "MDU6TGFiZWw0Mg==", + "url": "https://api.github.com/repos/batterseapower/pinyin-toolkit/labels/bug", + "name": "bug", + "color": "ff0000", + "default": true + } + ], "locked": false, "created_at": "2011-01-26T19:01:12Z", "updated_at": "2011-01-26T19:01:12Z", diff --git a/src/test/resources/com/spotify/github/v3/prs/pull_request_automerge_disabled.json b/src/test/resources/com/spotify/github/v3/prs/pull_request_automerge_disabled.json new file mode 100644 index 00000000..e4d01d2a --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/prs/pull_request_automerge_disabled.json @@ -0,0 +1,359 @@ +{ + "url": "https://api.github.com/repos/spotify/github-java-client/pulls/226", + "id": 2439836648, + "node_id": "PR_kwDODynaQc6RbPPo", + "html_url": "https://github.com/spotify/github-java-client/pull/226", + "diff_url": "https://github.com/spotify/github-java-client/pull/226.diff", + "patch_url": "https://github.com/spotify/github-java-client/pull/226.patch", + "issue_url": "https://api.github.com/repos/spotify/github-java-client/issues/226", + "number": 226, + "state": "open", + "locked": false, + "title": "feat: Add auto_merge field to the PR class", + "user": { + "login": "octocat", + "id": 1445581, + "node_id": "MDQ6VXNlcjE0NDU1ODE=", + "avatar_url": "https://avatars.githubusercontent.com/u/1445581?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "Adds the auto_merge field to the PR class", + "created_at": "2025-04-04T15:21:13Z", + "updated_at": "2025-04-04T15:23:48Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "62862b387d19968ab0777ac90044929ece5b94ca", + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "draft": true, + "commits_url": "https://api.github.com/repos/spotify/github-java-client/pulls/226/commits", + "review_comments_url": "https://api.github.com/repos/spotify/github-java-client/pulls/226/comments", + "review_comment_url": "https://api.github.com/repos/spotify/github-java-client/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/spotify/github-java-client/issues/226/comments", + "statuses_url": "https://api.github.com/repos/spotify/github-java-client/statuses/881ef333d1ffc01869b666f13d3b37d7af92b9a2", + "head": { + "label": "spotify:feat/add-auto-merge-field", + "ref": "feat/add-auto-merge-field", + "sha": "881ef333d1ffc01869b666f13d3b37d7af92b9a2", + "user": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 254401089, + "node_id": "MDEwOlJlcG9zaXRvcnkyNTQ0MDEwODk=", + "name": "github-java-client", + "full_name": "spotify/github-java-client", + "private": false, + "owner": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/spotify/github-java-client", + "description": "A Java client to Github API", + "fork": false, + "url": "https://api.github.com/repos/spotify/github-java-client", + "forks_url": "https://api.github.com/repos/spotify/github-java-client/forks", + "keys_url": "https://api.github.com/repos/spotify/github-java-client/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/spotify/github-java-client/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/spotify/github-java-client/teams", + "hooks_url": "https://api.github.com/repos/spotify/github-java-client/hooks", + "issue_events_url": "https://api.github.com/repos/spotify/github-java-client/issues/events{/number}", + "events_url": "https://api.github.com/repos/spotify/github-java-client/events", + "assignees_url": "https://api.github.com/repos/spotify/github-java-client/assignees{/user}", + "branches_url": "https://api.github.com/repos/spotify/github-java-client/branches{/branch}", + "tags_url": "https://api.github.com/repos/spotify/github-java-client/tags", + "blobs_url": "https://api.github.com/repos/spotify/github-java-client/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/spotify/github-java-client/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/spotify/github-java-client/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/spotify/github-java-client/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/spotify/github-java-client/statuses/{sha}", + "languages_url": "https://api.github.com/repos/spotify/github-java-client/languages", + "stargazers_url": "https://api.github.com/repos/spotify/github-java-client/stargazers", + "contributors_url": "https://api.github.com/repos/spotify/github-java-client/contributors", + "subscribers_url": "https://api.github.com/repos/spotify/github-java-client/subscribers", + "subscription_url": "https://api.github.com/repos/spotify/github-java-client/subscription", + "commits_url": "https://api.github.com/repos/spotify/github-java-client/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/spotify/github-java-client/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/spotify/github-java-client/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/spotify/github-java-client/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/spotify/github-java-client/contents/{+path}", + "compare_url": "https://api.github.com/repos/spotify/github-java-client/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/spotify/github-java-client/merges", + "archive_url": "https://api.github.com/repos/spotify/github-java-client/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/spotify/github-java-client/downloads", + "issues_url": "https://api.github.com/repos/spotify/github-java-client/issues{/number}", + "pulls_url": "https://api.github.com/repos/spotify/github-java-client/pulls{/number}", + "milestones_url": "https://api.github.com/repos/spotify/github-java-client/milestones{/number}", + "notifications_url": "https://api.github.com/repos/spotify/github-java-client/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/spotify/github-java-client/labels{/name}", + "releases_url": "https://api.github.com/repos/spotify/github-java-client/releases{/id}", + "deployments_url": "https://api.github.com/repos/spotify/github-java-client/deployments", + "created_at": "2020-04-09T14:55:56Z", + "updated_at": "2025-03-29T17:20:38Z", + "pushed_at": "2025-04-04T15:20:42Z", + "git_url": "git://github.com/spotify/github-java-client.git", + "ssh_url": "git@github.com:spotify/github-java-client.git", + "clone_url": "https://github.com/spotify/github-java-client.git", + "svn_url": "https://github.com/spotify/github-java-client", + "homepage": "", + "size": 3067, + "stargazers_count": 144, + "watchers_count": 144, + "language": "Java", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": true, + "has_discussions": false, + "forks_count": 93, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 23, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 93, + "open_issues": 23, + "watchers": 144, + "default_branch": "master" + } + }, + "base": { + "label": "spotify:master", + "ref": "master", + "sha": "bcd052d1b2508ae1e97cbeea16f198ce796743ad", + "user": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 254401089, + "node_id": "MDEwOlJlcG9zaXRvcnkyNTQ0MDEwODk=", + "name": "github-java-client", + "full_name": "spotify/github-java-client", + "private": false, + "owner": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/spotify/github-java-client", + "description": "A Java client to Github API", + "fork": false, + "url": "https://api.github.com/repos/spotify/github-java-client", + "forks_url": "https://api.github.com/repos/spotify/github-java-client/forks", + "keys_url": "https://api.github.com/repos/spotify/github-java-client/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/spotify/github-java-client/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/spotify/github-java-client/teams", + "hooks_url": "https://api.github.com/repos/spotify/github-java-client/hooks", + "issue_events_url": "https://api.github.com/repos/spotify/github-java-client/issues/events{/number}", + "events_url": "https://api.github.com/repos/spotify/github-java-client/events", + "assignees_url": "https://api.github.com/repos/spotify/github-java-client/assignees{/user}", + "branches_url": "https://api.github.com/repos/spotify/github-java-client/branches{/branch}", + "tags_url": "https://api.github.com/repos/spotify/github-java-client/tags", + "blobs_url": "https://api.github.com/repos/spotify/github-java-client/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/spotify/github-java-client/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/spotify/github-java-client/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/spotify/github-java-client/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/spotify/github-java-client/statuses/{sha}", + "languages_url": "https://api.github.com/repos/spotify/github-java-client/languages", + "stargazers_url": "https://api.github.com/repos/spotify/github-java-client/stargazers", + "contributors_url": "https://api.github.com/repos/spotify/github-java-client/contributors", + "subscribers_url": "https://api.github.com/repos/spotify/github-java-client/subscribers", + "subscription_url": "https://api.github.com/repos/spotify/github-java-client/subscription", + "commits_url": "https://api.github.com/repos/spotify/github-java-client/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/spotify/github-java-client/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/spotify/github-java-client/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/spotify/github-java-client/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/spotify/github-java-client/contents/{+path}", + "compare_url": "https://api.github.com/repos/spotify/github-java-client/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/spotify/github-java-client/merges", + "archive_url": "https://api.github.com/repos/spotify/github-java-client/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/spotify/github-java-client/downloads", + "issues_url": "https://api.github.com/repos/spotify/github-java-client/issues{/number}", + "pulls_url": "https://api.github.com/repos/spotify/github-java-client/pulls{/number}", + "milestones_url": "https://api.github.com/repos/spotify/github-java-client/milestones{/number}", + "notifications_url": "https://api.github.com/repos/spotify/github-java-client/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/spotify/github-java-client/labels{/name}", + "releases_url": "https://api.github.com/repos/spotify/github-java-client/releases{/id}", + "deployments_url": "https://api.github.com/repos/spotify/github-java-client/deployments", + "created_at": "2020-04-09T14:55:56Z", + "updated_at": "2025-03-29T17:20:38Z", + "pushed_at": "2025-04-04T15:20:42Z", + "git_url": "git://github.com/spotify/github-java-client.git", + "ssh_url": "git@github.com:spotify/github-java-client.git", + "clone_url": "https://github.com/spotify/github-java-client.git", + "svn_url": "https://github.com/spotify/github-java-client", + "homepage": "", + "size": 3067, + "stargazers_count": 144, + "watchers_count": 144, + "language": "Java", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": true, + "has_discussions": false, + "forks_count": 93, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 23, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 93, + "open_issues": 23, + "watchers": 144, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/spotify/github-java-client/pulls/226" + }, + "html": { + "href": "https://github.com/spotify/github-java-client/pull/226" + }, + "issue": { + "href": "https://api.github.com/repos/spotify/github-java-client/issues/226" + }, + "comments": { + "href": "https://api.github.com/repos/spotify/github-java-client/issues/226/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/spotify/github-java-client/pulls/226/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/spotify/github-java-client/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/spotify/github-java-client/pulls/226/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/spotify/github-java-client/statuses/881ef333d1ffc01869b666f13d3b37d7af92b9a2" + } + }, + "author_association": "MEMBER", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "blocked", + "merged_by": null, + "comments": 1, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 1, + "additions": 54, + "deletions": 2, + "changed_files": 2 +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/prs/pull_request_automerge_enabled.json b/src/test/resources/com/spotify/github/v3/prs/pull_request_automerge_enabled.json new file mode 100644 index 00000000..6d71517a --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/prs/pull_request_automerge_enabled.json @@ -0,0 +1,384 @@ +{ + "url": "https://api.github.com/repos/spotify/github-java-client/pulls/226", + "id": 2439836648, + "node_id": "PR_kwDODynaQc6RbPPo", + "html_url": "https://github.com/spotify/github-java-client/pull/226", + "diff_url": "https://github.com/spotify/github-java-client/pull/226.diff", + "patch_url": "https://github.com/spotify/github-java-client/pull/226.patch", + "issue_url": "https://api.github.com/repos/spotify/github-java-client/issues/226", + "number": 226, + "state": "open", + "locked": false, + "title": "feat: Add auto_merge field to the PR class", + "user": { + "login": "octocat", + "id": 1445581, + "node_id": "MDQ6VXNlcjE0NDU1ODE=", + "avatar_url": "https://avatars.githubusercontent.com/u/1445581?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "Adds the auto_merge field to the PR class", + "created_at": "2025-04-04T15:21:13Z", + "updated_at": "2025-04-05T07:45:19Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "62862b387d19968ab0777ac90044929ece5b94ca", + "assignee": null, + "assignees": [], + "requested_reviewers": [], + "requested_teams": [], + "labels": [], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/spotify/github-java-client/pulls/226/commits", + "review_comments_url": "https://api.github.com/repos/spotify/github-java-client/pulls/226/comments", + "review_comment_url": "https://api.github.com/repos/spotify/github-java-client/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/spotify/github-java-client/issues/226/comments", + "statuses_url": "https://api.github.com/repos/spotify/github-java-client/statuses/881ef333d1ffc01869b666f13d3b37d7af92b9a2", + "head": { + "label": "spotify:feat/add-auto-merge-field", + "ref": "feat/add-auto-merge-field", + "sha": "881ef333d1ffc01869b666f13d3b37d7af92b9a2", + "user": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 254401089, + "node_id": "MDEwOlJlcG9zaXRvcnkyNTQ0MDEwODk=", + "name": "github-java-client", + "full_name": "spotify/github-java-client", + "private": false, + "owner": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/spotify/github-java-client", + "description": "A Java client to Github API", + "fork": false, + "url": "https://api.github.com/repos/spotify/github-java-client", + "forks_url": "https://api.github.com/repos/spotify/github-java-client/forks", + "keys_url": "https://api.github.com/repos/spotify/github-java-client/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/spotify/github-java-client/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/spotify/github-java-client/teams", + "hooks_url": "https://api.github.com/repos/spotify/github-java-client/hooks", + "issue_events_url": "https://api.github.com/repos/spotify/github-java-client/issues/events{/number}", + "events_url": "https://api.github.com/repos/spotify/github-java-client/events", + "assignees_url": "https://api.github.com/repos/spotify/github-java-client/assignees{/user}", + "branches_url": "https://api.github.com/repos/spotify/github-java-client/branches{/branch}", + "tags_url": "https://api.github.com/repos/spotify/github-java-client/tags", + "blobs_url": "https://api.github.com/repos/spotify/github-java-client/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/spotify/github-java-client/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/spotify/github-java-client/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/spotify/github-java-client/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/spotify/github-java-client/statuses/{sha}", + "languages_url": "https://api.github.com/repos/spotify/github-java-client/languages", + "stargazers_url": "https://api.github.com/repos/spotify/github-java-client/stargazers", + "contributors_url": "https://api.github.com/repos/spotify/github-java-client/contributors", + "subscribers_url": "https://api.github.com/repos/spotify/github-java-client/subscribers", + "subscription_url": "https://api.github.com/repos/spotify/github-java-client/subscription", + "commits_url": "https://api.github.com/repos/spotify/github-java-client/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/spotify/github-java-client/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/spotify/github-java-client/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/spotify/github-java-client/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/spotify/github-java-client/contents/{+path}", + "compare_url": "https://api.github.com/repos/spotify/github-java-client/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/spotify/github-java-client/merges", + "archive_url": "https://api.github.com/repos/spotify/github-java-client/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/spotify/github-java-client/downloads", + "issues_url": "https://api.github.com/repos/spotify/github-java-client/issues{/number}", + "pulls_url": "https://api.github.com/repos/spotify/github-java-client/pulls{/number}", + "milestones_url": "https://api.github.com/repos/spotify/github-java-client/milestones{/number}", + "notifications_url": "https://api.github.com/repos/spotify/github-java-client/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/spotify/github-java-client/labels{/name}", + "releases_url": "https://api.github.com/repos/spotify/github-java-client/releases{/id}", + "deployments_url": "https://api.github.com/repos/spotify/github-java-client/deployments", + "created_at": "2020-04-09T14:55:56Z", + "updated_at": "2025-03-29T17:20:38Z", + "pushed_at": "2025-04-04T15:20:42Z", + "git_url": "git://github.com/spotify/github-java-client.git", + "ssh_url": "git@github.com:spotify/github-java-client.git", + "clone_url": "https://github.com/spotify/github-java-client.git", + "svn_url": "https://github.com/spotify/github-java-client", + "homepage": "", + "size": 3067, + "stargazers_count": 144, + "watchers_count": 144, + "language": "Java", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": true, + "has_discussions": false, + "forks_count": 93, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 23, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 93, + "open_issues": 23, + "watchers": 144, + "default_branch": "master" + } + }, + "base": { + "label": "spotify:master", + "ref": "master", + "sha": "bcd052d1b2508ae1e97cbeea16f198ce796743ad", + "user": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 254401089, + "node_id": "MDEwOlJlcG9zaXRvcnkyNTQ0MDEwODk=", + "name": "github-java-client", + "full_name": "spotify/github-java-client", + "private": false, + "owner": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/spotify/github-java-client", + "description": "A Java client to Github API", + "fork": false, + "url": "https://api.github.com/repos/spotify/github-java-client", + "forks_url": "https://api.github.com/repos/spotify/github-java-client/forks", + "keys_url": "https://api.github.com/repos/spotify/github-java-client/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/spotify/github-java-client/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/spotify/github-java-client/teams", + "hooks_url": "https://api.github.com/repos/spotify/github-java-client/hooks", + "issue_events_url": "https://api.github.com/repos/spotify/github-java-client/issues/events{/number}", + "events_url": "https://api.github.com/repos/spotify/github-java-client/events", + "assignees_url": "https://api.github.com/repos/spotify/github-java-client/assignees{/user}", + "branches_url": "https://api.github.com/repos/spotify/github-java-client/branches{/branch}", + "tags_url": "https://api.github.com/repos/spotify/github-java-client/tags", + "blobs_url": "https://api.github.com/repos/spotify/github-java-client/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/spotify/github-java-client/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/spotify/github-java-client/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/spotify/github-java-client/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/spotify/github-java-client/statuses/{sha}", + "languages_url": "https://api.github.com/repos/spotify/github-java-client/languages", + "stargazers_url": "https://api.github.com/repos/spotify/github-java-client/stargazers", + "contributors_url": "https://api.github.com/repos/spotify/github-java-client/contributors", + "subscribers_url": "https://api.github.com/repos/spotify/github-java-client/subscribers", + "subscription_url": "https://api.github.com/repos/spotify/github-java-client/subscription", + "commits_url": "https://api.github.com/repos/spotify/github-java-client/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/spotify/github-java-client/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/spotify/github-java-client/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/spotify/github-java-client/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/spotify/github-java-client/contents/{+path}", + "compare_url": "https://api.github.com/repos/spotify/github-java-client/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/spotify/github-java-client/merges", + "archive_url": "https://api.github.com/repos/spotify/github-java-client/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/spotify/github-java-client/downloads", + "issues_url": "https://api.github.com/repos/spotify/github-java-client/issues{/number}", + "pulls_url": "https://api.github.com/repos/spotify/github-java-client/pulls{/number}", + "milestones_url": "https://api.github.com/repos/spotify/github-java-client/milestones{/number}", + "notifications_url": "https://api.github.com/repos/spotify/github-java-client/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/spotify/github-java-client/labels{/name}", + "releases_url": "https://api.github.com/repos/spotify/github-java-client/releases{/id}", + "deployments_url": "https://api.github.com/repos/spotify/github-java-client/deployments", + "created_at": "2020-04-09T14:55:56Z", + "updated_at": "2025-03-29T17:20:38Z", + "pushed_at": "2025-04-04T15:20:42Z", + "git_url": "git://github.com/spotify/github-java-client.git", + "ssh_url": "git@github.com:spotify/github-java-client.git", + "clone_url": "https://github.com/spotify/github-java-client.git", + "svn_url": "https://github.com/spotify/github-java-client", + "homepage": "", + "size": 3067, + "stargazers_count": 144, + "watchers_count": 144, + "language": "Java", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": true, + "has_discussions": false, + "forks_count": 93, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 23, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 93, + "open_issues": 23, + "watchers": 144, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/spotify/github-java-client/pulls/226" + }, + "html": { + "href": "https://github.com/spotify/github-java-client/pull/226" + }, + "issue": { + "href": "https://api.github.com/repos/spotify/github-java-client/issues/226" + }, + "comments": { + "href": "https://api.github.com/repos/spotify/github-java-client/issues/226/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/spotify/github-java-client/pulls/226/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/spotify/github-java-client/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/spotify/github-java-client/pulls/226/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/spotify/github-java-client/statuses/881ef333d1ffc01869b666f13d3b37d7af92b9a2" + } + }, + "author_association": "MEMBER", + "auto_merge": { + "enabled_by": { + "login": "octocat", + "id": 1445581, + "node_id": "MDQ6VXNlcjE0NDU1ODE=", + "avatar_url": "https://avatars.githubusercontent.com/u/1445581?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "merge_method": "squash", + "commit_title": "feat: Add auto_merge field to the PR class (#226)", + "commit_message": "" + }, + "active_lock_reason": null, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "blocked", + "merged_by": null, + "comments": 1, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 1, + "additions": 54, + "deletions": 2, + "changed_files": 2 +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/prs/pull_request_long_id.json b/src/test/resources/com/spotify/github/v3/prs/pull_request_long_id.json new file mode 100644 index 00000000..fa4ef17f --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/prs/pull_request_long_id.json @@ -0,0 +1,389 @@ +{ + "url": "https://api.github.com/repos/spotify/scio/pulls/5525", + "id": 2459198527, + "node_id": "PR_kwDOAfa55s6COrI0", + "html_url": "https://github.com/spotify/scio/pull/5525", + "diff_url": "https://github.com/spotify/scio/pull/5525.diff", + "patch_url": "https://github.com/spotify/scio/pull/5525.patch", + "issue_url": "https://api.github.com/repos/spotify/scio/issues/5525", + "number": 5525, + "state": "open", + "locked": false, + "title": "Update beam to 2.61", + "user": { + "login": "RustedBones", + "id": 2845540, + "node_id": "MDQ6VXNlcjI4NDU1NDA=", + "avatar_url": "https://avatars.githubusercontent.com/u/2845540?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/RustedBones", + "html_url": "https://github.com/RustedBones", + "followers_url": "https://api.github.com/users/RustedBones/followers", + "following_url": "https://api.github.com/users/RustedBones/following{/other_user}", + "gists_url": "https://api.github.com/users/RustedBones/gists{/gist_id}", + "starred_url": "https://api.github.com/users/RustedBones/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/RustedBones/subscriptions", + "organizations_url": "https://api.github.com/users/RustedBones/orgs", + "repos_url": "https://api.github.com/users/RustedBones/repos", + "events_url": "https://api.github.com/users/RustedBones/events{/privacy}", + "received_events_url": "https://api.github.com/users/RustedBones/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "Leverages upstream [changes](https://github.com/apache/beam/pull/32482) available for BQ:\r\nAnnotated BQ typed avro translation leverages logical-type to have symmetric read/write. This fixes integration testfailure introduced in https://github.com/spotify/scio/pull/5523", + "created_at": "2024-11-18T10:56:55Z", + "updated_at": "2024-11-19T17:06:12Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + + ], + "milestone": null, + "draft": true, + "commits_url": "https://api.github.com/repos/spotify/scio/pulls/5525/commits", + "review_comments_url": "https://api.github.com/repos/spotify/scio/pulls/5525/comments", + "review_comment_url": "https://api.github.com/repos/spotify/scio/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/spotify/scio/issues/5525/comments", + "statuses_url": "https://api.github.com/repos/spotify/scio/statuses/f74c7f420282f584acd2fb5964202e5b525c3ab8", + "head": { + "label": "spotify:beam-2.61", + "ref": "beam-2.61", + "sha": "f74c7f420282f584acd2fb5964202e5b525c3ab8", + "user": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 32946662, + "node_id": "MDEwOlJlcG9zaXRvcnkzMjk0NjY2Mg==", + "name": "scio", + "full_name": "spotify/scio", + "private": false, + "owner": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/spotify/scio", + "description": "A Scala API for Apache Beam and Google Cloud Dataflow.", + "fork": false, + "url": "https://api.github.com/repos/spotify/scio", + "forks_url": "https://api.github.com/repos/spotify/scio/forks", + "keys_url": "https://api.github.com/repos/spotify/scio/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/spotify/scio/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/spotify/scio/teams", + "hooks_url": "https://api.github.com/repos/spotify/scio/hooks", + "issue_events_url": "https://api.github.com/repos/spotify/scio/issues/events{/number}", + "events_url": "https://api.github.com/repos/spotify/scio/events", + "assignees_url": "https://api.github.com/repos/spotify/scio/assignees{/user}", + "branches_url": "https://api.github.com/repos/spotify/scio/branches{/branch}", + "tags_url": "https://api.github.com/repos/spotify/scio/tags", + "blobs_url": "https://api.github.com/repos/spotify/scio/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/spotify/scio/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/spotify/scio/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/spotify/scio/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/spotify/scio/statuses/{sha}", + "languages_url": "https://api.github.com/repos/spotify/scio/languages", + "stargazers_url": "https://api.github.com/repos/spotify/scio/stargazers", + "contributors_url": "https://api.github.com/repos/spotify/scio/contributors", + "subscribers_url": "https://api.github.com/repos/spotify/scio/subscribers", + "subscription_url": "https://api.github.com/repos/spotify/scio/subscription", + "commits_url": "https://api.github.com/repos/spotify/scio/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/spotify/scio/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/spotify/scio/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/spotify/scio/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/spotify/scio/contents/{+path}", + "compare_url": "https://api.github.com/repos/spotify/scio/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/spotify/scio/merges", + "archive_url": "https://api.github.com/repos/spotify/scio/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/spotify/scio/downloads", + "issues_url": "https://api.github.com/repos/spotify/scio/issues{/number}", + "pulls_url": "https://api.github.com/repos/spotify/scio/pulls{/number}", + "milestones_url": "https://api.github.com/repos/spotify/scio/milestones{/number}", + "notifications_url": "https://api.github.com/repos/spotify/scio/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/spotify/scio/labels{/name}", + "releases_url": "https://api.github.com/repos/spotify/scio/releases{/id}", + "deployments_url": "https://api.github.com/repos/spotify/scio/deployments", + "created_at": "2015-03-26T19:07:34Z", + "updated_at": "2024-11-25T02:07:06Z", + "pushed_at": "2024-11-22T09:28:47Z", + "git_url": "git://github.com/spotify/scio.git", + "ssh_url": "git@github.com:spotify/scio.git", + "clone_url": "https://github.com/spotify/scio.git", + "svn_url": "https://github.com/spotify/scio", + "homepage": "https://spotify.github.io/scio", + "size": 70358, + "stargazers_count": 2559, + "watchers_count": 2559, + "language": "Scala", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": true, + "has_discussions": true, + "forks_count": 514, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 143, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "batch", + "beam", + "bigquery", + "data", + "dataflow", + "google-cloud", + "ml", + "scala", + "scio", + "streaming" + ], + "visibility": "public", + "forks": 514, + "open_issues": 143, + "watchers": 2559, + "default_branch": "main" + } + }, + "base": { + "label": "spotify:main", + "ref": "main", + "sha": "49f43b028dbd5df9dfb5362714c9fbd76e9019db", + "user": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 32946662, + "node_id": "MDEwOlJlcG9zaXRvcnkzMjk0NjY2Mg==", + "name": "scio", + "full_name": "spotify/scio", + "private": false, + "owner": { + "login": "spotify", + "id": 251374, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MTM3NA==", + "avatar_url": "https://avatars.githubusercontent.com/u/251374?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/spotify", + "html_url": "https://github.com/spotify", + "followers_url": "https://api.github.com/users/spotify/followers", + "following_url": "https://api.github.com/users/spotify/following{/other_user}", + "gists_url": "https://api.github.com/users/spotify/gists{/gist_id}", + "starred_url": "https://api.github.com/users/spotify/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/spotify/subscriptions", + "organizations_url": "https://api.github.com/users/spotify/orgs", + "repos_url": "https://api.github.com/users/spotify/repos", + "events_url": "https://api.github.com/users/spotify/events{/privacy}", + "received_events_url": "https://api.github.com/users/spotify/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/spotify/scio", + "description": "A Scala API for Apache Beam and Google Cloud Dataflow.", + "fork": false, + "url": "https://api.github.com/repos/spotify/scio", + "forks_url": "https://api.github.com/repos/spotify/scio/forks", + "keys_url": "https://api.github.com/repos/spotify/scio/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/spotify/scio/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/spotify/scio/teams", + "hooks_url": "https://api.github.com/repos/spotify/scio/hooks", + "issue_events_url": "https://api.github.com/repos/spotify/scio/issues/events{/number}", + "events_url": "https://api.github.com/repos/spotify/scio/events", + "assignees_url": "https://api.github.com/repos/spotify/scio/assignees{/user}", + "branches_url": "https://api.github.com/repos/spotify/scio/branches{/branch}", + "tags_url": "https://api.github.com/repos/spotify/scio/tags", + "blobs_url": "https://api.github.com/repos/spotify/scio/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/spotify/scio/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/spotify/scio/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/spotify/scio/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/spotify/scio/statuses/{sha}", + "languages_url": "https://api.github.com/repos/spotify/scio/languages", + "stargazers_url": "https://api.github.com/repos/spotify/scio/stargazers", + "contributors_url": "https://api.github.com/repos/spotify/scio/contributors", + "subscribers_url": "https://api.github.com/repos/spotify/scio/subscribers", + "subscription_url": "https://api.github.com/repos/spotify/scio/subscription", + "commits_url": "https://api.github.com/repos/spotify/scio/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/spotify/scio/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/spotify/scio/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/spotify/scio/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/spotify/scio/contents/{+path}", + "compare_url": "https://api.github.com/repos/spotify/scio/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/spotify/scio/merges", + "archive_url": "https://api.github.com/repos/spotify/scio/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/spotify/scio/downloads", + "issues_url": "https://api.github.com/repos/spotify/scio/issues{/number}", + "pulls_url": "https://api.github.com/repos/spotify/scio/pulls{/number}", + "milestones_url": "https://api.github.com/repos/spotify/scio/milestones{/number}", + "notifications_url": "https://api.github.com/repos/spotify/scio/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/spotify/scio/labels{/name}", + "releases_url": "https://api.github.com/repos/spotify/scio/releases{/id}", + "deployments_url": "https://api.github.com/repos/spotify/scio/deployments", + "created_at": "2015-03-26T19:07:34Z", + "updated_at": "2024-11-25T02:07:06Z", + "pushed_at": "2024-11-22T09:28:47Z", + "git_url": "git://github.com/spotify/scio.git", + "ssh_url": "git@github.com:spotify/scio.git", + "clone_url": "https://github.com/spotify/scio.git", + "svn_url": "https://github.com/spotify/scio", + "homepage": "https://spotify.github.io/scio", + "size": 70358, + "stargazers_count": 2559, + "watchers_count": 2559, + "language": "Scala", + "has_issues": true, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": true, + "has_discussions": true, + "forks_count": 514, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 143, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "batch", + "beam", + "bigquery", + "data", + "dataflow", + "google-cloud", + "ml", + "scala", + "scio", + "streaming" + ], + "visibility": "public", + "forks": 514, + "open_issues": 143, + "watchers": 2559, + "default_branch": "main" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/spotify/scio/pulls/5525" + }, + "html": { + "href": "https://github.com/spotify/scio/pull/5525" + }, + "issue": { + "href": "https://api.github.com/repos/spotify/scio/issues/5525" + }, + "comments": { + "href": "https://api.github.com/repos/spotify/scio/issues/5525/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/spotify/scio/pulls/5525/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/spotify/scio/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/spotify/scio/pulls/5525/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/spotify/scio/statuses/f74c7f420282f584acd2fb5964202e5b525c3ab8" + } + }, + "author_association": "CONTRIBUTOR", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": false, + "rebaseable": false, + "mergeable_state": "dirty", + "merged_by": null, + "comments": 1, + "review_comments": 1, + "maintainer_can_modify": false, + "commits": 3, + "additions": 89, + "deletions": 152, + "changed_files": 12 +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/prs/review_long_id.json b/src/test/resources/com/spotify/github/v3/prs/review_long_id.json new file mode 100644 index 00000000..4dcca923 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/prs/review_long_id.json @@ -0,0 +1,38 @@ +{ + "id": 2459198580, + "node_id": "MDE3OlB1bGxSZXF1ZXN0UmV2aWV3ODA=", + "user": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "body": "Here is the body for the review.", + "submitted_at": "2019-11-17T17:43:43Z", + "commit_id": "ecdd80bb57125d7ba9641ffaa4d7d2c19d3f3091", + "state": "APPROVED", + "html_url": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80", + "pull_request_url": "https://api.github.com/repos/octocat/Hello-World/pulls/12", + "_links": { + "html": { + "href": "https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80" + }, + "pull_request": { + "href": "https://api.github.com/repos/octocat/Hello-World/pulls/12" + } + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/repos/branch-escape-chars-url-variation-two.json b/src/test/resources/com/spotify/github/v3/repos/branch-escape-chars-url-variation-two.json new file mode 100644 index 00000000..2efa0c3a --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/branch-escape-chars-url-variation-two.json @@ -0,0 +1,9 @@ +{ + "name": "unescaped-percent-sign-%", + "commit": { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc" + }, + "protected": true, + "protection_url": "https://api.github.com/api/v3/repos/octocat/Hello-World/branches/branch-name-with-slashes/unescaped-percent-sign-%/protection" +} diff --git a/src/test/resources/com/spotify/github/v3/repos/branch-escape-chars.json b/src/test/resources/com/spotify/github/v3/repos/branch-escape-chars.json new file mode 100644 index 00000000..82066263 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/branch-escape-chars.json @@ -0,0 +1,9 @@ +{ + "name": "unescaped-percent-sign-%", + "commit": { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc" + }, + "protected": true, + "protection_url": "https://api.github.com/repos/octocat/Hello-World/branches/unescaped-percent-sign-%/protection" +} diff --git a/src/test/resources/com/spotify/github/v3/repos/branch-no-protection-fields.json b/src/test/resources/com/spotify/github/v3/repos/branch-no-protection-fields.json new file mode 100644 index 00000000..023b3d02 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/branch-no-protection-fields.json @@ -0,0 +1,7 @@ +{ + "name": "master", + "commit": { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc" + } +} diff --git a/src/test/resources/com/spotify/github/v3/repos/branch-not-protected.json b/src/test/resources/com/spotify/github/v3/repos/branch-not-protected.json new file mode 100644 index 00000000..ed5ad96f --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/branch-not-protected.json @@ -0,0 +1,8 @@ +{ + "name": "master", + "commit": { + "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc" + }, + "protected": false +} diff --git a/src/test/resources/com/spotify/github/v3/repos/branch.json b/src/test/resources/com/spotify/github/v3/repos/branch.json index fdf8a19b..7725d38c 100644 --- a/src/test/resources/com/spotify/github/v3/repos/branch.json +++ b/src/test/resources/com/spotify/github/v3/repos/branch.json @@ -5,5 +5,15 @@ "url": "https://api.github.com/repos/octocat/Hello-World/commits/c5b97d5ae6c19d5c5df71a34c7fbeeda2479ccbc" }, "protected": true, + "protection": { + "enabled": true, + "required_status_checks": { + "enforcement_level": "non_admins", + "contexts": [ + "Context 1", + "Context 2" + ] + } + }, "protection_url": "https://api.github.com/repos/octocat/Hello-World/branches/master/protection" } diff --git a/src/test/resources/com/spotify/github/v3/repos/create-content-repsonse.json b/src/test/resources/com/spotify/github/v3/repos/create-content-repsonse.json new file mode 100644 index 00000000..9ddec3dc --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/create-content-repsonse.json @@ -0,0 +1,52 @@ +{ + "content": { + "name": "README.md", + "path": "test/README.md", + "sha": "95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "size": 9, + "url": "https://api.github.com/repos/someowner/somerepo/contents/test/README.md", + "html_url": "https://github.com/someowner/somerepo/blob/master/test/README.md", + "git_url": "https://api.github.com/repos/someowner/somerepo/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "download_url": "https://raw.githubusercontent.com/someowner/HelloWorld/master/test/README.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/someowner/somerepo/contents/test/README.md", + "git": "https://api.github.com/repos/someowner/somerepo/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "html": "https://github.com/someowner/somerepo/blob/master/test/README.md" + } + }, + "commit": { + "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", + "node_id": "MDY6Q29tbWl0NzYzODQxN2RiNmQ1OWYzYzQzMWQzZTFmMjYxY2M2MzcxNTU2ODRjZA==", + "url": "https://api.github.com/repos/someowner/somerepo/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", + "html_url": "https://github.com/someowner/somerepo/git/commit/7638417db6d59f3c431d3e1f261cc637155684cd", + "author": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "committer": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "message": "my commit message", + "tree": { + "url": "https://api.github.com/repos/someowner/somerepo/git/trees/691272480426f78a0138979dd3ce63b77f706feb", + "sha": "691272480426f78a0138979dd3ce63b77f706feb" + }, + "parents": [ + { + "url": "https://api.github.com/repos/someowner/somerepo/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", + "html_url": "https://github.com/someowner/somerepo/git/commit/1acc419d4d6a9ce985db7be48c6349a0475975b5", + "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/repos/create-content-request.json b/src/test/resources/com/spotify/github/v3/repos/create-content-request.json new file mode 100644 index 00000000..360f517c --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/create-content-request.json @@ -0,0 +1,4 @@ +{ + "message": "my commit message", + "content": "encoded content ..." +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/repos/reference.json b/src/test/resources/com/spotify/github/v3/repos/reference.json new file mode 100644 index 00000000..a82b9895 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/reference.json @@ -0,0 +1,10 @@ +{ + "ref": "refs/heads/newBranch", + "node_id": "MDM6UmVmMTc3NzI0OnJlZnMvaGVhZHMvYW5uYS9uZXd0ZXN0", + "url": "https://github.com/octocat/Hello-World/repos/someowner/somerepo/git/refs/heads/newBranch", + "object": { + "sha": "3d9d329812e120d3a01111cfee1f91cacebc7793", + "type": "commit", + "url": "https://github.com/octocat/Hello-World/repos/someowner/somerepo/git/commits/3d9d329812e120d3a01111cfee1f91cacebc7793" + } +} diff --git a/src/test/resources/com/spotify/github/v3/repos/repository.json b/src/test/resources/com/spotify/github/v3/repos/repository.json index 5637b300..fdc68ba0 100644 --- a/src/test/resources/com/spotify/github/v3/repos/repository.json +++ b/src/test/resources/com/spotify/github/v3/repos/repository.json @@ -23,6 +23,7 @@ "full_name": "octocat/Hello-World", "description": "This your first repo!", "private": false, + "archived": false, "fork": true, "url": "https://api.github.com/repos/octocat/Hello-World", "html_url": "https://github.com/octocat/Hello-World", @@ -80,6 +81,9 @@ "forks": 2, "has_pages": false, "has_downloads": true, + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": true, "pushed_at": "2011-01-26T19:06:43Z", "created_at": "2011-01-26T19:01:12Z", "updated_at": "2011-01-26T19:14:43Z", diff --git a/src/test/resources/com/spotify/github/v3/repos/repository_get.json b/src/test/resources/com/spotify/github/v3/repos/repository_get.json index 3e9fbf30..219e7b90 100644 --- a/src/test/resources/com/spotify/github/v3/repos/repository_get.json +++ b/src/test/resources/com/spotify/github/v3/repos/repository_get.json @@ -23,6 +23,7 @@ "full_name": "octocat/Hello-World", "description": "This your first repo!", "private": false, + "archived": false, "fork": false, "url": "https://api.github.com/repos/octocat/Hello-World", "html_url": "https://github.com/octocat/Hello-World", diff --git a/src/test/resources/com/spotify/github/v3/repos/repository_invitation.json b/src/test/resources/com/spotify/github/v3/repos/repository_invitation.json new file mode 100644 index 00000000..3b8849f9 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/repository_invitation.json @@ -0,0 +1,117 @@ +{ + "id": 1, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "repository": { + "id": 1296269, + "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/octocat/Hello-World", + "description": "This your first repo!", + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_url": "git:github.com/octocat/Hello-World.git", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "hooks_url": "http://api.github.com/repos/octocat/Hello-World/hooks" + }, + "invitee": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "inviter": { + "login": "octocat", + "id": 1, + "node_id": "MDQ6VXNlcjE=", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": false + }, + "permissions": "write", + "created_at": "2016-06-13T14:52:50-05:00", + "url": "https://api.github.com/user/repository_invitations/1296269", + "html_url": "https://github.com/octocat/Hello-World/invitations" +} diff --git a/src/test/resources/com/spotify/github/v3/repos/shaLink.json b/src/test/resources/com/spotify/github/v3/repos/shaLink.json new file mode 100644 index 00000000..34e01e37 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/shaLink.json @@ -0,0 +1,4 @@ +{ + "sha": "8fc4e0fe57752b892a921806a1352e4cc72dff37", + "url": "https://github.com/octocat/Hello-World/repos/someowner/somerepo/git/blobs/8fc4e0fe57752b892a921806a1352e4cc72dff37" +} diff --git a/src/test/resources/com/spotify/github/v3/repos/update-content-repsonse.json b/src/test/resources/com/spotify/github/v3/repos/update-content-repsonse.json new file mode 100644 index 00000000..9ddec3dc --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/update-content-repsonse.json @@ -0,0 +1,52 @@ +{ + "content": { + "name": "README.md", + "path": "test/README.md", + "sha": "95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "size": 9, + "url": "https://api.github.com/repos/someowner/somerepo/contents/test/README.md", + "html_url": "https://github.com/someowner/somerepo/blob/master/test/README.md", + "git_url": "https://api.github.com/repos/someowner/somerepo/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "download_url": "https://raw.githubusercontent.com/someowner/HelloWorld/master/test/README.md", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/someowner/somerepo/contents/test/README.md", + "git": "https://api.github.com/repos/someowner/somerepo/git/blobs/95b966ae1c166bd92f8ae7d1c313e738c731dfc3", + "html": "https://github.com/someowner/somerepo/blob/master/test/README.md" + } + }, + "commit": { + "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", + "node_id": "MDY6Q29tbWl0NzYzODQxN2RiNmQ1OWYzYzQzMWQzZTFmMjYxY2M2MzcxNTU2ODRjZA==", + "url": "https://api.github.com/repos/someowner/somerepo/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", + "html_url": "https://github.com/someowner/somerepo/git/commit/7638417db6d59f3c431d3e1f261cc637155684cd", + "author": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "committer": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "message": "my commit message", + "tree": { + "url": "https://api.github.com/repos/someowner/somerepo/git/trees/691272480426f78a0138979dd3ce63b77f706feb", + "sha": "691272480426f78a0138979dd3ce63b77f706feb" + }, + "parents": [ + { + "url": "https://api.github.com/repos/someowner/somerepo/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", + "html_url": "https://github.com/someowner/somerepo/git/commit/1acc419d4d6a9ce985db7be48c6349a0475975b5", + "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } + } +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/repos/update-content-request.json b/src/test/resources/com/spotify/github/v3/repos/update-content-request.json new file mode 100644 index 00000000..16bf148c --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/repos/update-content-request.json @@ -0,0 +1,6 @@ +{ + "message": "my commit message", + "content": "encoded content ...", + "sha": "12345", + "branch": "test-branch" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/search/issues-long-id.json b/src/test/resources/com/spotify/github/v3/search/issues-long-id.json new file mode 100644 index 00000000..428a6431 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/search/issues-long-id.json @@ -0,0 +1,72 @@ +{ + "total_count": 280, + "incomplete_results": false, + "items": [ + { + "url": "https://api.github.com/repos/spotify/scio/issues/5514", + "repository_url": "https://api.github.com/repos/spotify/scio", + "labels_url": "https://api.github.com/repos/spotify/scio/issues/5514/labels{/name}", + "comments_url": "https://api.github.com/repos/spotify/scio/issues/5514/comments", + "events_url": "https://api.github.com/repos/spotify/scio/issues/5514/events", + "html_url": "https://github.com/spotify/scio/issues/5514", + "id": 2592843837, + "node_id": "I_kwDOAfa55s6ai6g9", + "number": 5514, + "title": "Benchmark BigTable maxPendingRequests", + "user": { + "login": "kellen", + "id": 486691, + "node_id": "MDQ6VXNlcjQ4NjY5MQ==", + "avatar_url": "https://avatars.githubusercontent.com/u/486691?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/kellen", + "html_url": "https://github.com/kellen", + "followers_url": "https://api.github.com/users/kellen/followers", + "following_url": "https://api.github.com/users/kellen/following{/other_user}", + "gists_url": "https://api.github.com/users/kellen/gists{/gist_id}", + "starred_url": "https://api.github.com/users/kellen/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/kellen/subscriptions", + "organizations_url": "https://api.github.com/users/kellen/orgs", + "repos_url": "https://api.github.com/users/kellen/repos", + "events_url": "https://api.github.com/users/kellen/events{/privacy}", + "received_events_url": "https://api.github.com/users/kellen/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 0, + "created_at": "2024-10-16T19:23:04Z", + "updated_at": "2024-10-16T19:23:40Z", + "closed_at": null, + "author_association": "CONTRIBUTOR", + "active_lock_reason": null, + "body": "Internal spotify discussions suggest that the default `maxPendingRequests=1` may no longer be a good default and a higher value (e.g. `6`) may make more sense for batch. \r\n\r\nWe should write some benchmarks to verify and update the documentation and/or defaults based on the benchmark results.\r\n", + "closed_by": null, + "reactions": { + "url": "https://api.github.com/repos/spotify/scio/issues/5514/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/spotify/scio/issues/5514/timeline", + "performed_via_github_app": null, + "state_reason": null + } + ] +} diff --git a/src/test/resources/com/spotify/github/v3/search/issues.json b/src/test/resources/com/spotify/github/v3/search/issues.json index 3c2cf3c7..9e6f5fb9 100644 --- a/src/test/resources/com/spotify/github/v3/search/issues.json +++ b/src/test/resources/com/spotify/github/v3/search/issues.json @@ -32,9 +32,12 @@ }, "labels": [ { + "id": 42, + "node_id": "MDU6TGFiZWw0Mg==", "url": "https://api.github.com/repos/batterseapower/pinyin-toolkit/labels/bug", "name": "bug", - "color": "ff0000" + "color": "ff0000", + "default": true } ], "state": "open", diff --git a/src/test/resources/com/spotify/github/v3/treeItem.json b/src/test/resources/com/spotify/github/v3/treeItem.json new file mode 100644 index 00000000..dca7c6d9 --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/treeItem.json @@ -0,0 +1,8 @@ +{ + "path": "README.md", + "mode": "100644", + "type": "blob", + "sha": "6e091fd045dc88806e5c70357326af7fa0e1ccde", + "size": 12, + "url": "https://github.com/octocat/Hello-World/repos/someowner/somerepo/git/blobs/6e091fd045dc88806e5c70357326af7fa0e1ccde" +} diff --git a/src/test/resources/com/spotify/github/v3/workflows/workflows-get-workflow-response.json b/src/test/resources/com/spotify/github/v3/workflows/workflows-get-workflow-response.json new file mode 100644 index 00000000..a636603a --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/workflows/workflows-get-workflow-response.json @@ -0,0 +1,13 @@ +{ + "id": 161335, + "node_id": "MDg6V29ya2Zsb3cxNjEzMzU=", + "name": "CI", + "path": ".github/workflows/blank.yaml", + "state": "active", + "created_at": "2020-01-08T23:48:37.000-08:00", + "updated_at": "2020-01-08T23:50:21.000-08:00", + "deleted_at": "2020-01-09T23:50:21.000-08:00", + "url": "https://api.github.com/repos/octo-org/octo-repo/actions/workflows/161335", + "html_url": "https://github.com/octo-org/octo-repo/blob/master/.github/workflows/161335", + "badge_url": "https://github.com/octo-org/octo-repo/workflows/CI/badge.svg" +} \ No newline at end of file diff --git a/src/test/resources/com/spotify/github/v3/workflows/workflows-list-workflows-response.json b/src/test/resources/com/spotify/github/v3/workflows/workflows-list-workflows-response.json new file mode 100644 index 00000000..d572e59f --- /dev/null +++ b/src/test/resources/com/spotify/github/v3/workflows/workflows-list-workflows-response.json @@ -0,0 +1,31 @@ +{ + "total_count": 2, + "workflows": [ + { + "id": 161335, + "node_id": "MDg6V29ya2Zsb3cxNjEzMzU=", + "name": "CI", + "path": ".github/workflows/blank.yaml", + "state": "active", + "created_at": "2020-01-08T23:48:37.000-08:00", + "updated_at": "2020-01-08T23:50:21.000-08:00", + "deleted_at": "2020-01-09T23:50:21.000-08:00", + "url": "https://api.github.com/repos/octo-org/octo-repo/actions/workflows/161335", + "html_url": "https://github.com/octo-org/octo-repo/blob/master/.github/workflows/161335", + "badge_url": "https://github.com/octo-org/octo-repo/workflows/CI/badge.svg" + }, + { + "id": 269289, + "node_id": "MDE4OldvcmtmbG93IFNlY29uZGFyeTI2OTI4OQ==", + "name": "Linter", + "path": ".github/workflows/linter.yaml", + "state": "active", + "created_at": "2020-01-08T23:48:37.000-08:00", + "updated_at": "2020-01-08T23:50:21.000-08:00", + "deleted_at": "2020-01-09T23:50:21.000-08:00", + "url": "https://api.github.com/repos/octo-org/octo-repo/actions/workflows/269289", + "html_url": "https://github.com/octo-org/octo-repo/blob/master/.github/workflows/269289", + "badge_url": "https://github.com/octo-org/octo-repo/workflows/Linter/badge.svg" + } + ] +} \ No newline at end of file